diff --git a/assets/model_pricing.json b/assets/model_pricing.json index e429a74..74fc4a2 100644 --- a/assets/model_pricing.json +++ b/assets/model_pricing.json @@ -875,6 +875,30 @@ "output_cost_per_token": 3e-05, "provider": "openai" }, + { + "model_pattern": "gpt-5.6", + "input_cost_per_token": 5e-06, + "output_cost_per_token": 3e-05, + "provider": "openai" + }, + { + "model_pattern": "gpt-5.6-luna", + "input_cost_per_token": 1e-06, + "output_cost_per_token": 6e-06, + "provider": "openai" + }, + { + "model_pattern": "gpt-5.6-sol", + "input_cost_per_token": 5e-06, + "output_cost_per_token": 3e-05, + "provider": "openai" + }, + { + "model_pattern": "gpt-5.6-terra", + "input_cost_per_token": 2.5e-06, + "output_cost_per_token": 1.5e-05, + "provider": "openai" + }, { "model_pattern": "gpt-audio", "input_cost_per_token": 2.5e-06, @@ -929,6 +953,18 @@ "output_cost_per_token": 1.6e-05, "provider": "openai" }, + { + "model_pattern": "gpt-realtime-2.1", + "input_cost_per_token": 4e-06, + "output_cost_per_token": 2.4e-05, + "provider": "openai" + }, + { + "model_pattern": "gpt-realtime-2.1-mini", + "input_cost_per_token": 6e-07, + "output_cost_per_token": 2.4e-06, + "provider": "openai" + }, { "model_pattern": "gpt-realtime-2025-08-28", "input_cost_per_token": 4e-06, diff --git a/crates/optimizer/crates/optimize-core/src/orchestrator.rs b/crates/optimizer/crates/optimize-core/src/orchestrator.rs index 6c1a4a9..60177b1 100644 --- a/crates/optimizer/crates/optimize-core/src/orchestrator.rs +++ b/crates/optimizer/crates/optimize-core/src/orchestrator.rs @@ -280,350 +280,5 @@ fn decisions_hash(edits: &[(usize, BufferId, EditScript)]) -> u64 { } #[cfg(test)] -mod tests { - use super::*; - use crate::budget::HeuristicBudgetCounter; - use crate::traits::{CacheModel, Pricing, UniformScorer}; - use crate::types::{Message, Role}; - - struct TestStrategy; - impl CacheStrategy for TestStrategy { - fn pricing(&self) -> Pricing { - Pricing { - input: 1.0, - cached_read: 0.5, - cache_write_mult: 1.0, - } - } - fn model(&self) -> CacheModel { - CacheModel::ExplicitBreakpoints - } - fn breakpoint_at(&self, frontier: usize) -> Option { - Some(frontier) - } - } - - fn long_user(text: &str) -> Message { - Message { - role: Role::User, - blocks: vec![ContentBlock::Text(text.into())], - protection: Protection::Mutable, - client_cache_marker: false, - } - } - - fn convo(n: usize) -> Conversation { - let long = "The quick brown fox jumps over the lazy dog again and again across \ - the wide green field toward the distant blue mountains beyond the river \ - and the tall dark trees under a bright and cloudless summer sky at noon." - .to_string(); - Conversation::new((0..n).map(|_| long_user(&long)).collect()) - } - - #[test] - fn shadow_never_renders_but_reports() { - let conv = convo(20); - let policy = Policy { - mode: Mode::Shadow, - ..Default::default() - }; - let mut ws = Workspace::new(); - let out = optimize( - &conv, - &policy, - &TestStrategy, - &UniformScorer, - &HeuristicBudgetCounter::default(), - &mut ws, - ); - assert!(out.rendered.is_none()); - assert_eq!(out.report.mode, Mode::Shadow); - assert!(out.report.frontier > 0); - assert!(out.report.removed_tokens_est > 0); - } - - #[test] - fn live_renders_and_is_deterministic() { - let conv = convo(20); - let policy = Policy { - mode: Mode::Live, - ..Default::default() - }; - let mut ws = Workspace::new(); - let a = optimize( - &conv, - &policy, - &TestStrategy, - &UniformScorer, - &HeuristicBudgetCounter::default(), - &mut ws, - ); - let b = optimize( - &conv, - &policy, - &TestStrategy, - &UniformScorer, - &HeuristicBudgetCounter::default(), - &mut ws, - ); - assert!(a.rendered.is_some()); - assert_eq!(a.report.decisions_hash, b.report.decisions_hash); - assert!(a.report.applied); - } - - #[test] - fn cost_delta_is_signed_and_nonzero_for_compression() { - let conv = convo(20); - let policy = Policy { - mode: Mode::Live, - ..Default::default() - }; - let mut ws = Workspace::new(); - let out = optimize( - &conv, - &policy, - &TestStrategy, - &UniformScorer, - &HeuristicBudgetCounter::default(), - &mut ws, - ); - assert!(out.report.applied); - assert!(out.report.removed_tokens_est > 0); - let expected = net_cost_delta_usd( - out.report.removed_tokens_est, - out.report.rewrite_suffix_tokens, - policy.horizon, - &TestStrategy.pricing(), - &TestStrategy.model(), - ); - assert!(expected > 0.0, "fixture should have a positive delta"); - assert_eq!(out.report.est_cost_delta_usd, expected); - } - - #[test] - fn cost_delta_is_zero_for_noop() { - // No message reaches `min_len`, so no edits are produced and dt stays 0 — the - // report's delta must be exactly 0.0, not the raw formula's rewrite-cost artifact. - let conv = Conversation::new(vec![long_user("too short to compress")]); - let policy = Policy { - mode: Mode::Live, - ..Default::default() - }; - let mut ws = Workspace::new(); - let out = optimize( - &conv, - &policy, - &TestStrategy, - &UniformScorer, - &HeuristicBudgetCounter::default(), - &mut ws, - ); - assert!(!out.report.applied); - assert_eq!(out.report.removed_tokens_est, 0); - assert_eq!(out.report.est_cost_delta_usd, 0.0); - } - - #[test] - fn route_override_turns_off_one_route_while_another_still_compresses() { - use crate::policy::{OptimizationPolicy, RouteOverride}; - use std::collections::HashMap; - - let conv = convo(20); - let mut routes = HashMap::new(); - routes.insert( - "batch".to_string(), - RouteOverride { - mode: Some(Mode::Off), - ratios: None, - pricing: None, - }, - ); - let opt_policy = OptimizationPolicy { - mode: Mode::Live, - routes, - ..OptimizationPolicy::default() - }; - - // Overridden route: rendered stays None, i.e. output ≡ input for that route. - let mut ws = Workspace::new(); - let off = optimize_for_route( - &conv, - &opt_policy, - "batch", - &TestStrategy, - &UniformScorer, - &HeuristicBudgetCounter::default(), - &mut ws, - ); - assert!(off.rendered.is_none()); - assert!(!off.report.applied); - - // Unlisted route: falls back to the top-level Live default and still compresses. - let mut ws2 = Workspace::new(); - let on = optimize_for_route( - &conv, - &opt_policy, - "interactive", - &TestStrategy, - &UniformScorer, - &HeuristicBudgetCounter::default(), - &mut ws2, - ); - assert!(on.rendered.is_some()); - assert!(on.report.applied); - } - - /// Scores like `UniformScorer` but sleeps on its very first call, so the deadline - /// check ahead of every later message is guaranteed to observe it expired - /// regardless of scheduler jitter (sleep duration >> deadline budget below). - struct SlowFirstCallScorer { - calls: std::sync::atomic::AtomicUsize, - } - impl TokenScorer for SlowFirstCallScorer { - fn score_words( - &self, - words: &[&str], - _ws: &mut Workspace, - ) -> Result, crate::error::ScoreError> { - if self.calls.fetch_add(1, std::sync::atomic::Ordering::SeqCst) == 0 { - std::thread::sleep(Duration::from_millis(50)); - } - Ok(vec![0.5; words.len()]) - } - fn artifact_hash(&self) -> u64 { - 0 - } - } - - #[test] - fn deadline_expiry_leaves_later_messages_byte_identical() { - // 20 messages -> frontier eligible_end=16 (keep_recent=4), all long enough to - // clear `min_len`. A 50ms sleep on the first scorer call vs. a 10ms deadline - // guarantees message 0 is scored before the deadline and every later message - // observes it already expired (elapsed only ever grows). - let conv = convo(20); - let mut policy = Policy { - mode: Mode::Live, - ..Default::default() - }; - policy.compression.deadline = Duration::from_millis(10); - - let scorer = SlowFirstCallScorer { - calls: std::sync::atomic::AtomicUsize::new(0), - }; - let mut ws = Workspace::new(); - let out = optimize( - &conv, - &policy, - &TestStrategy, - &scorer, - &HeuristicBudgetCounter::default(), - &mut ws, - ); - - assert!( - out.report.messages_compressed >= 1, - "message scored before the deadline should be compressed" - ); - assert!( - out.report.messages_skipped_deadline >= 1, - "messages after deadline expiry must be counted as skipped" - ); - assert_eq!( - out.report.messages_compressed as usize + out.report.messages_skipped_deadline as usize, - out.report.frontier, - "every eligible message is either compressed or explicitly deadline-skipped, never silently dropped" - ); - - let rendered = out - .rendered - .expect("gate should apply given a nonzero saving"); - let compressed = out.report.messages_compressed as usize; - // Deadline-skipped messages must render byte-identical to the source. - for i in compressed..out.report.frontier { - assert_eq!( - rendered.messages[i].blocks[0], conv.messages[i].blocks[0], - "deadline-skipped message {i} must stay byte-identical" - ); - } - // The message scored before the deadline actually got edited. - assert_ne!( - rendered.messages[0].blocks[0], conv.messages[0].blocks[0], - "the message scored before the deadline should have been edited" - ); - } - - /// M4.3: `Policy::pricing_override`, loaded from a config string via - /// `Pricing::from_config_str` (no hardcoded-table constant), must be what the - /// orchestrator actually uses for the cost gate/report — not `TestStrategy`'s own - /// (hardcoded-in-code) `pricing()`. Proven by asserting the report's dollar delta - /// matches `net_cost_delta_usd` computed with the config pricing and differs from - /// the value that would result from `TestStrategy::pricing()` alone. - #[test] - fn pricing_comes_from_config() { - use crate::traits::Pricing; - - let conv = convo(20); - - // Deliberately far from `TestStrategy::pricing()` (input:1.0, cached_read:0.5, - // cache_write_mult:1.0) and from any of `anyllm_optimize_passes::cost_gate`'s - // hardcoded tables — proves the number really came from this config string. - let config_pricing = - Pricing::from_config_str("input=9.0\ncached_read=0.05\ncache_write_mult=2.0\n") - .expect("well-formed config parses"); - assert_ne!(config_pricing, TestStrategy.pricing()); - - let policy = Policy { - mode: Mode::Live, - pricing_override: Some(config_pricing), - ..Default::default() - }; - let mut ws = Workspace::new(); - let out = optimize( - &conv, - &policy, - &TestStrategy, - &UniformScorer, - &HeuristicBudgetCounter::default(), - &mut ws, - ); - assert!(out.report.applied); - assert!(out.report.removed_tokens_est > 0); - - let expected_from_config = net_cost_delta_usd( - out.report.removed_tokens_est, - out.report.rewrite_suffix_tokens, - policy.horizon, - &config_pricing, - &TestStrategy.model(), - ); - let would_be_from_hardcoded_strategy = net_cost_delta_usd( - out.report.removed_tokens_est, - out.report.rewrite_suffix_tokens, - policy.horizon, - &TestStrategy.pricing(), - &TestStrategy.model(), - ); - - assert_eq!(out.report.est_cost_delta_usd, expected_from_config); - assert_ne!( - out.report.est_cost_delta_usd, would_be_from_hardcoded_strategy, - "orchestrator must use the config-loaded pricing, not the strategy's hardcoded table" - ); - } - - #[test] - fn empty_conversation_is_noop() { - let conv = Conversation::default(); - let out = optimize( - &conv, - &Policy::default(), - &TestStrategy, - &UniformScorer, - &HeuristicBudgetCounter::default(), - &mut Workspace::new(), - ); - assert!(out.rendered.is_none()); - assert_eq!(out.report.frontier, 0); - } -} +#[path = "orchestrator_tests.rs"] +mod tests; diff --git a/crates/optimizer/crates/optimize-core/src/orchestrator_tests.rs b/crates/optimizer/crates/optimize-core/src/orchestrator_tests.rs new file mode 100644 index 0000000..f2532d7 --- /dev/null +++ b/crates/optimizer/crates/optimize-core/src/orchestrator_tests.rs @@ -0,0 +1,345 @@ +use super::*; +use crate::budget::HeuristicBudgetCounter; +use crate::traits::{CacheModel, Pricing, UniformScorer}; +use crate::types::{Message, Role}; + +struct TestStrategy; +impl CacheStrategy for TestStrategy { + fn pricing(&self) -> Pricing { + Pricing { + input: 1.0, + cached_read: 0.5, + cache_write_mult: 1.0, + } + } + fn model(&self) -> CacheModel { + CacheModel::ExplicitBreakpoints + } + fn breakpoint_at(&self, frontier: usize) -> Option { + Some(frontier) + } +} + +fn long_user(text: &str) -> Message { + Message { + role: Role::User, + blocks: vec![ContentBlock::Text(text.into())], + protection: Protection::Mutable, + client_cache_marker: false, + } +} + +fn convo(n: usize) -> Conversation { + let long = "The quick brown fox jumps over the lazy dog again and again across \ + the wide green field toward the distant blue mountains beyond the river \ + and the tall dark trees under a bright and cloudless summer sky at noon." + .to_string(); + Conversation::new((0..n).map(|_| long_user(&long)).collect()) +} + +#[test] +fn shadow_never_renders_but_reports() { + let conv = convo(20); + let policy = Policy { + mode: Mode::Shadow, + ..Default::default() + }; + let mut ws = Workspace::new(); + let out = optimize( + &conv, + &policy, + &TestStrategy, + &UniformScorer, + &HeuristicBudgetCounter::default(), + &mut ws, + ); + assert!(out.rendered.is_none()); + assert_eq!(out.report.mode, Mode::Shadow); + assert!(out.report.frontier > 0); + assert!(out.report.removed_tokens_est > 0); +} + +#[test] +fn live_renders_and_is_deterministic() { + let conv = convo(20); + let policy = Policy { + mode: Mode::Live, + ..Default::default() + }; + let mut ws = Workspace::new(); + let a = optimize( + &conv, + &policy, + &TestStrategy, + &UniformScorer, + &HeuristicBudgetCounter::default(), + &mut ws, + ); + let b = optimize( + &conv, + &policy, + &TestStrategy, + &UniformScorer, + &HeuristicBudgetCounter::default(), + &mut ws, + ); + assert!(a.rendered.is_some()); + assert_eq!(a.report.decisions_hash, b.report.decisions_hash); + assert!(a.report.applied); +} + +#[test] +fn cost_delta_is_signed_and_nonzero_for_compression() { + let conv = convo(20); + let policy = Policy { + mode: Mode::Live, + ..Default::default() + }; + let mut ws = Workspace::new(); + let out = optimize( + &conv, + &policy, + &TestStrategy, + &UniformScorer, + &HeuristicBudgetCounter::default(), + &mut ws, + ); + assert!(out.report.applied); + assert!(out.report.removed_tokens_est > 0); + let expected = net_cost_delta_usd( + out.report.removed_tokens_est, + out.report.rewrite_suffix_tokens, + policy.horizon, + &TestStrategy.pricing(), + &TestStrategy.model(), + ); + assert!(expected > 0.0, "fixture should have a positive delta"); + assert_eq!(out.report.est_cost_delta_usd, expected); +} + +#[test] +fn cost_delta_is_zero_for_noop() { + // No message reaches `min_len`, so no edits are produced and dt stays 0 — the + // report's delta must be exactly 0.0, not the raw formula's rewrite-cost artifact. + let conv = Conversation::new(vec![long_user("too short to compress")]); + let policy = Policy { + mode: Mode::Live, + ..Default::default() + }; + let mut ws = Workspace::new(); + let out = optimize( + &conv, + &policy, + &TestStrategy, + &UniformScorer, + &HeuristicBudgetCounter::default(), + &mut ws, + ); + assert!(!out.report.applied); + assert_eq!(out.report.removed_tokens_est, 0); + assert_eq!(out.report.est_cost_delta_usd, 0.0); +} + +#[test] +fn route_override_turns_off_one_route_while_another_still_compresses() { + use crate::policy::{OptimizationPolicy, RouteOverride}; + use std::collections::HashMap; + + let conv = convo(20); + let mut routes = HashMap::new(); + routes.insert( + "batch".to_string(), + RouteOverride { + mode: Some(Mode::Off), + ratios: None, + pricing: None, + }, + ); + let opt_policy = OptimizationPolicy { + mode: Mode::Live, + routes, + ..OptimizationPolicy::default() + }; + + // Overridden route: rendered stays None, i.e. output ≡ input for that route. + let mut ws = Workspace::new(); + let off = optimize_for_route( + &conv, + &opt_policy, + "batch", + &TestStrategy, + &UniformScorer, + &HeuristicBudgetCounter::default(), + &mut ws, + ); + assert!(off.rendered.is_none()); + assert!(!off.report.applied); + + // Unlisted route: falls back to the top-level Live default and still compresses. + let mut ws2 = Workspace::new(); + let on = optimize_for_route( + &conv, + &opt_policy, + "interactive", + &TestStrategy, + &UniformScorer, + &HeuristicBudgetCounter::default(), + &mut ws2, + ); + assert!(on.rendered.is_some()); + assert!(on.report.applied); +} + +/// Scores like `UniformScorer` but sleeps on its very first call, so the deadline +/// check ahead of every later message is guaranteed to observe it expired +/// regardless of scheduler jitter (sleep duration >> deadline budget below). +struct SlowFirstCallScorer { + calls: std::sync::atomic::AtomicUsize, +} +impl TokenScorer for SlowFirstCallScorer { + fn score_words( + &self, + words: &[&str], + _ws: &mut Workspace, + ) -> Result, crate::error::ScoreError> { + if self.calls.fetch_add(1, std::sync::atomic::Ordering::SeqCst) == 0 { + std::thread::sleep(Duration::from_millis(50)); + } + Ok(vec![0.5; words.len()]) + } + fn artifact_hash(&self) -> u64 { + 0 + } +} + +#[test] +fn deadline_expiry_leaves_later_messages_byte_identical() { + // 20 messages -> frontier eligible_end=16 (keep_recent=4), all long enough to + // clear `min_len`. A 50ms sleep on the first scorer call vs. a 10ms deadline + // guarantees message 0 is scored before the deadline and every later message + // observes it already expired (elapsed only ever grows). + let conv = convo(20); + let mut policy = Policy { + mode: Mode::Live, + ..Default::default() + }; + policy.compression.deadline = Duration::from_millis(10); + + let scorer = SlowFirstCallScorer { + calls: std::sync::atomic::AtomicUsize::new(0), + }; + let mut ws = Workspace::new(); + let out = optimize( + &conv, + &policy, + &TestStrategy, + &scorer, + &HeuristicBudgetCounter::default(), + &mut ws, + ); + + assert!( + out.report.messages_compressed >= 1, + "message scored before the deadline should be compressed" + ); + assert!( + out.report.messages_skipped_deadline >= 1, + "messages after deadline expiry must be counted as skipped" + ); + assert_eq!( + out.report.messages_compressed as usize + out.report.messages_skipped_deadline as usize, + out.report.frontier, + "every eligible message is either compressed or explicitly deadline-skipped, never silently dropped" + ); + + let rendered = out + .rendered + .expect("gate should apply given a nonzero saving"); + let compressed = out.report.messages_compressed as usize; + // Deadline-skipped messages must render byte-identical to the source. + for i in compressed..out.report.frontier { + assert_eq!( + rendered.messages[i].blocks[0], conv.messages[i].blocks[0], + "deadline-skipped message {i} must stay byte-identical" + ); + } + // The message scored before the deadline actually got edited. + assert_ne!( + rendered.messages[0].blocks[0], conv.messages[0].blocks[0], + "the message scored before the deadline should have been edited" + ); +} + +/// M4.3: `Policy::pricing_override`, loaded from a config string via +/// `Pricing::from_config_str` (no hardcoded-table constant), must be what the +/// orchestrator actually uses for the cost gate/report — not `TestStrategy`'s own +/// (hardcoded-in-code) `pricing()`. Proven by asserting the report's dollar delta +/// matches `net_cost_delta_usd` computed with the config pricing and differs from +/// the value that would result from `TestStrategy::pricing()` alone. +#[test] +fn pricing_comes_from_config() { + use crate::traits::Pricing; + + let conv = convo(20); + + // Deliberately far from `TestStrategy::pricing()` (input:1.0, cached_read:0.5, + // cache_write_mult:1.0) and from any of `anyllm_optimize_passes::cost_gate`'s + // hardcoded tables — proves the number really came from this config string. + let config_pricing = + Pricing::from_config_str("input=9.0\ncached_read=0.05\ncache_write_mult=2.0\n") + .expect("well-formed config parses"); + assert_ne!(config_pricing, TestStrategy.pricing()); + + let policy = Policy { + mode: Mode::Live, + pricing_override: Some(config_pricing), + ..Default::default() + }; + let mut ws = Workspace::new(); + let out = optimize( + &conv, + &policy, + &TestStrategy, + &UniformScorer, + &HeuristicBudgetCounter::default(), + &mut ws, + ); + assert!(out.report.applied); + assert!(out.report.removed_tokens_est > 0); + + let expected_from_config = net_cost_delta_usd( + out.report.removed_tokens_est, + out.report.rewrite_suffix_tokens, + policy.horizon, + &config_pricing, + &TestStrategy.model(), + ); + let would_be_from_hardcoded_strategy = net_cost_delta_usd( + out.report.removed_tokens_est, + out.report.rewrite_suffix_tokens, + policy.horizon, + &TestStrategy.pricing(), + &TestStrategy.model(), + ); + + assert_eq!(out.report.est_cost_delta_usd, expected_from_config); + assert_ne!( + out.report.est_cost_delta_usd, would_be_from_hardcoded_strategy, + "orchestrator must use the config-loaded pricing, not the strategy's hardcoded table" + ); +} + +#[test] +fn empty_conversation_is_noop() { + let conv = Conversation::default(); + let out = optimize( + &conv, + &Policy::default(), + &TestStrategy, + &UniformScorer, + &HeuristicBudgetCounter::default(), + &mut Workspace::new(), + ); + assert!(out.rendered.is_none()); + assert_eq!(out.report.frontier, 0); +} diff --git a/crates/optimizer/optimize-cli/src/client.rs b/crates/optimizer/optimize-cli/src/client.rs new file mode 100644 index 0000000..20499ca --- /dev/null +++ b/crates/optimizer/optimize-cli/src/client.rs @@ -0,0 +1,195 @@ +use super::{Api, Args}; +use anyhow::{Context, Result}; +use serde_json::{json, Value}; + +/// One provider response: token usage + the assistant text. +pub struct Reply { + pub prompt_tokens: u64, + pub text: String, +} + +/// Send one request and return usage + assistant text. +pub fn send_request( + client: &reqwest::blocking::Client, + args: &Args, + body: &Value, + base_url: &str, + api: Api, +) -> Result { + let mut body = body.clone(); + let url = match api { + Api::Openai => format!("{}/chat/completions", base_url.trim_end_matches('/')), + Api::Anthropic => format!("{}/messages", base_url.trim_end_matches('/')), + }; + if args.stream { + body["stream"] = json!(true); + if api == Api::Openai { + body["stream_options"] = json!({ "include_usage": true }); + } + } + + let key = resolve_key(args); + let mut req = client.post(&url).json(&body); + req = match api { + Api::Openai => { + if key.is_empty() { + req + } else { + req.bearer_auth(key) + } + } + Api::Anthropic => req + .header("x-api-key", key) + .header("anthropic-version", "2023-06-01"), + }; + + let resp = req.send().context("request send failed")?; + let status = resp.status(); + let text = resp.text().context("reading response body")?; + if !status.is_success() { + anyhow::bail!( + "HTTP {status}: {}", + text.chars().take(300).collect::() + ); + } + Ok(Reply { + prompt_tokens: extract_prompt_tokens(&text, api, args.stream) + .context("could not find prompt/input token usage")?, + text: extract_response_text(&text, api, args.stream), + }) +} + +pub fn resolve_key(args: &Args) -> String { + args.api_key + .clone() + .or_else(|| std::env::var("OPENROUTER_API_KEY").ok()) + .or_else(|| std::env::var("ANYLLM_API_KEY").ok()) + .unwrap_or_default() +} + +/// Pull prompt tokens from a JSON body or an SSE stream. +pub fn extract_prompt_tokens(text: &str, api: Api, stream: bool) -> Result { + let want = |v: &Value| -> Option { + match api { + Api::Openai => v.get("usage")?.get("prompt_tokens")?.as_u64(), + Api::Anthropic => v + .get("usage") + .or_else(|| v.get("message").and_then(|m| m.get("usage")))? + .get("input_tokens")? + .as_u64(), + } + }; + if !stream { + let v: Value = serde_json::from_str(text)?; + return want(&v).context("usage field missing"); + } + for payload in sse_payloads(text) { + if let Ok(v) = serde_json::from_str::(&payload) { + if let Some(n) = want(&v) { + return Ok(n); + } + } + } + anyhow::bail!("no usage in stream") +} + +/// Pull assistant text from a JSON body or an SSE stream. Best-effort: returns "" if the +/// shape is unrecognized (quality just reads as low, never panics). +pub fn extract_response_text(text: &str, api: Api, stream: bool) -> String { + if !stream { + let Ok(v) = serde_json::from_str::(text) else { + return String::new(); + }; + return match api { + Api::Openai => v + .pointer("/choices/0/message/content") + .and_then(|c| c.as_str()) + .unwrap_or("") + .to_string(), + Api::Anthropic => v + .get("content") + .and_then(|c| c.as_array()) + .map(|blocks| { + blocks + .iter() + .filter_map(|b| b.get("text").and_then(|t| t.as_str())) + .collect::>() + .join("") + }) + .unwrap_or_default(), + }; + } + // Streaming: concatenate incremental text deltas. + let mut out = String::new(); + for payload in sse_payloads(text) { + let Ok(v) = serde_json::from_str::(&payload) else { + continue; + }; + match api { + Api::Openai => { + if let Some(s) = v + .pointer("/choices/0/delta/content") + .and_then(|c| c.as_str()) + { + out.push_str(s); + } + } + Api::Anthropic => { + if let Some(s) = v.pointer("/delta/text").and_then(|c| c.as_str()) { + out.push_str(s); + } + } + } + } + out +} + +/// Yield the JSON payload of each `data:` SSE line, skipping `[DONE]`. +pub fn sse_payloads(text: &str) -> impl Iterator + '_ { + text.lines().filter_map(|line| { + let payload = line.trim().strip_prefix("data:")?.trim(); + (payload != "[DONE]").then(|| payload.to_string()) + }) +} + +/// Ask an OpenAI-compatible judge model to score 1-5 how well `comp` preserves `raw`. +pub fn run_judge( + client: &reqwest::blocking::Client, + args: &Args, + raw: &str, + comp: &str, +) -> Result { + let model = args.judge_model.as_ref().context("no judge model")?; + let base = args.judge_base_url.as_deref().unwrap_or(&args.base_url); + let prompt = format!( + "You compare two AI assistant responses to the same user request. Response A is \ + from the full prompt; Response B is from a compressed prompt. Score 1-5 how well B \ + preserves A's meaning and quality (5 = equivalent, 1 = badly degraded). Reply with \ + ONLY the integer.\n\n[A]\n{raw}\n\n[B]\n{comp}" + ); + let body = json!({ + "model": model, + "messages": [{ "role": "user", "content": prompt }], + "max_tokens": 4, + "temperature": 0, + }); + let url = format!("{}/chat/completions", base.trim_end_matches('/')); + let key = resolve_key(args); + let mut req = client.post(&url).json(&body); + if !key.is_empty() { + req = req.bearer_auth(key); + } + let resp = req.send().context("judge send failed")?; + let text = resp.text().context("reading judge response")?; + let v: Value = serde_json::from_str(&text).context("judge response not JSON")?; + let content = v + .pointer("/choices/0/message/content") + .and_then(|c| c.as_str()) + .context("judge response has no content")?; + let score = content + .chars() + .find(|c| ('1'..='5').contains(c)) + .and_then(|c| c.to_digit(10)) + .context("no 1-5 score in judge reply")?; + Ok(score) +} diff --git a/crates/optimizer/optimize-cli/src/cost.rs b/crates/optimizer/optimize-cli/src/cost.rs new file mode 100644 index 0000000..e15bdca --- /dev/null +++ b/crates/optimizer/optimize-cli/src/cost.rs @@ -0,0 +1,78 @@ +use super::{build_scorer, compress, ensure_model, load_inputs, Api, Args}; +use anyhow::Result; +use anyllm_optimize_core::{net_cost_delta_usd, CacheModel, CacheStrategy}; +use anyllm_optimize_passes::{AnthropicStrategy, OpenAiStrategy}; + +/// EH-0002 bite 2: simulate the frozen-frontier policy over `--input` and print the +/// signed net USD delta (compress vs skip) the cost gate computes per row, plus a class +/// total. Fully offline (no network) — the ΔT/S the gate uses come straight out of the +/// same `optimize()` call the harness already runs, so this is not a re-derivation, it is +/// the actual decision the frontier + cost gate would make. +pub fn run_net_cost(args: &Args) -> Result<()> { + let bodies = load_inputs(args)?; + let scorer = build_scorer(args); + let (pricing, model) = match args.cost_model.unwrap_or(args.api) { + Api::Openai => ( + OpenAiStrategy::default().pricing(), + OpenAiStrategy::default().model(), + ), + Api::Anthropic => ( + AnthropicStrategy::default().pricing(), + AnthropicStrategy::default().model(), + ), + }; + println!( + "{:<4} {:>9} {:>7} {:>7} {:>7} {:>14}", + "#", "frontier", "dt", "s", "apply", "net_usd" + ); + let mut total_dt = 0u64; + let mut total_net = 0.0f64; + for (i, mut raw) in bodies.into_iter().enumerate() { + ensure_model(&mut raw, args); + // dt/s come from `--api`'s adapter (must match the input's wire shape, or tool/JSON + // blocks get mis-segmented — see cost_model doc comment); the apply decision is + // then re-derived under `--cost-model`'s pricing/CacheModel, since `report.applied` + // reflects `--api`'s own strategy and the two can differ. + let (_compressed, report) = compress(&raw, args, scorer.as_ref()); + let dt = report.removed_tokens_est; + let s = report.rewrite_suffix_tokens; + let applies = anyllm_optimize_core::should_apply(dt, s, args.horizon, &pricing, &model); + // Only realize the delta when the gate actually applies: `applies == false` means + // the original is forwarded untouched, so the realized cost change is $0, not the + // negative "forced a no-op rewrite" value the raw formula would give for dt=0 + // (which `should_apply` exists precisely to avoid ever choosing). + let net = if applies { + net_cost_delta_usd(dt, s, args.horizon, &pricing, &model) + } else { + 0.0 + }; + total_dt += dt; + total_net += net; + println!( + "{:<4} {:>9} {:>7} {:>7} {:>7} {:>14.6}", + i, report.frontier, dt, s, applies, net + ); + } + let model_name = match model { + CacheModel::ImplicitPrefix => "implicit-prefix", + CacheModel::ExplicitBreakpoints => "explicit-breakpoints", + }; + println!( + "\nNET COST (horizon={}, {model_name}, input=${:.2}/Mtok cached_read=${:.2}/Mtok \ + write_mult={:.2}x): total ΔT={total_dt} tokens, net_delta=${total_net:.6}", + args.horizon, pricing.input, pricing.cached_read, pricing.cache_write_mult, + ); + Ok(()) +} + +pub fn opt_u64(v: Option) -> String { + v.map(|x| x.to_string()).unwrap_or_else(|| "-".into()) +} + +pub fn pct(raw: u64, comp: u64) -> f64 { + if raw == 0 { + 0.0 + } else { + (raw.saturating_sub(comp)) as f64 / raw as f64 * 100.0 + } +} diff --git a/crates/optimizer/optimize-cli/src/main.rs b/crates/optimizer/optimize-cli/src/main.rs index c840426..5e8e8ed 100644 --- a/crates/optimizer/optimize-cli/src/main.rs +++ b/crates/optimizer/optimize-cli/src/main.rs @@ -17,12 +17,9 @@ //! Fail-open: a network/API error on one row is reported and skipped; the process exits //! non-zero if any row failed, but never panics. -use std::collections::HashSet; - use anyhow::{Context, Result}; use anyllm_optimize_core::{ - net_cost_delta_usd, BudgetCounter, CacheModel, CacheStrategy, HeuristicBudgetCounter, Mode, - OptimizationReport, Policy, TokenScorer, UniformScorer, Workspace, + Mode, OptimizationReport, Policy, TokenScorer, UniformScorer, Workspace, }; use anyllm_optimize_passes::adapter::{anthropic, openai}; use anyllm_optimize_passes::{ @@ -32,10 +29,22 @@ use clap::{Parser, ValueEnum}; use serde_json::{json, Value}; #[cfg(feature = "tiktoken")] -mod budget_tiktoken; +pub(crate) mod budget_tiktoken; + +mod client; +mod cost; +#[cfg(test)] +mod tests; +mod token_count; +mod utils; + +use client::{run_judge, send_request}; +use cost::{opt_u64, pct, run_net_cost}; +use token_count::{build_counter, count_local}; +use utils::{jaccard, wrap_prompt}; #[derive(Clone, Copy, Debug, ValueEnum, PartialEq)] -enum Api { +pub(crate) enum Api { Openai, Anthropic, } @@ -45,71 +54,65 @@ enum Api { name = "optimize-eval", about = "FFEC token-savings + response-quality harness" )] -struct Args { +pub(crate) struct Args { /// Wire format of the target endpoint. #[arg(long, value_enum, default_value_t = Api::Openai)] - api: Api, + pub(crate) api: Api, /// Base URL. Ollama: http://localhost:11434/v1 ; OpenRouter: https://openrouter.ai/api/v1 #[arg(long, default_value = "http://localhost:11434/v1")] - base_url: String, + pub(crate) base_url: String, /// Target model id. #[arg(long)] - model: String, + pub(crate) model: String, /// API key. Falls back to $OPENROUTER_API_KEY / $ANYLLM_API_KEY; empty for local. #[arg(long)] - api_key: Option, + pub(crate) api_key: Option, /// JSONL file: each line a request body (`{"messages":[...]}`) or a bare prompt string. #[arg(long, conflicts_with = "prompt")] - input: Option, + pub(crate) input: Option, /// A single prompt to test instead of --input. #[arg(long)] - prompt: Option, + pub(crate) prompt: Option, /// Use streaming requests (adds include_usage for OpenAI). #[arg(long)] - stream: bool, + pub(crate) stream: bool, /// max_tokens for the response (Anthropic requires it; also sent to OpenAI). #[arg(long, default_value_t = 256)] - max_tokens: u64, + pub(crate) max_tokens: u64, /// Expected remaining turns (cost-gate horizon). #[arg(long, default_value_t = 8)] - horizon: u64, + pub(crate) horizon: u64, /// Only compute local estimates; do not call the network (no quality signal). #[arg(long)] - offline: bool, + pub(crate) offline: bool, /// Print both full responses per row (raw vs compressed) for eyeballing quality. #[arg(long)] - show_responses: bool, + pub(crate) show_responses: bool, /// Optional OpenAI-compatible judge model. If set, scores 1-5 how well the compressed /// response preserves the raw response's meaning/quality (adds one call per row). #[arg(long)] - judge_model: Option, + pub(crate) judge_model: Option, /// Base URL for the judge (defaults to --base-url). Must be OpenAI-compatible. #[arg(long)] - judge_base_url: Option, + pub(crate) judge_base_url: Option, /// Simulate the frozen-frontier cost gate over the input and print the signed net /// USD delta (compress vs skip) per row plus a class total, using the CacheModel and /// Pricing table implied by `--api` unless `--cost-model` overrides it (EH-0002 M0.3: /// offline, no network needed). #[arg(long)] - net_cost: bool, + pub(crate) net_cost: bool, /// Override which provider's CacheModel/Pricing the `--net-cost` dollar math uses, /// independent of `--api` (which must still match the input's actual wire shape — the /// adapter mis-segments tool/content blocks if it doesn't). Defaults to `--api`. #[arg(long, value_enum)] - cost_model: Option, + pub(crate) cost_model: Option, /// M3.6: directory containing `model.onnx` + `tokenizer.json` for the real /// LLMLingua2Pass ML scorer (ROADMAP D8), used in place of `UniformScorer` for every /// message the frontier already selects. Requires building with `--features onnx`; /// without it (or on a load failure) this falls back to `UniformScorer` with a /// warning, per the fail-open invariant — never a hard error. #[arg(long)] - llmlingua2_model_dir: Option, -} - -/// One provider response: token usage + the assistant text. -struct Reply { - prompt_tokens: u64, - text: String, + pub(crate) llmlingua2_model_dir: Option, } fn main() -> std::process::ExitCode { @@ -254,7 +257,11 @@ fn run(args: &Args) -> Result { /// `UniformScorer` by default, or the real `LLMLingua2Pass` when `--llmlingua2-model-dir` /// resolves (see `build_scorer`) — either way it is only ever invoked by `optimize()` for /// frontier-eligible D8 targets (tool results, old RAG blocks, old assistant messages). -fn compress(raw: &Value, args: &Args, scorer: &dyn TokenScorer) -> (Value, OptimizationReport) { +pub(crate) fn compress( + raw: &Value, + args: &Args, + scorer: &dyn TokenScorer, +) -> (Value, OptimizationReport) { let policy = Policy { mode: Mode::Live, horizon: args.horizon, @@ -271,7 +278,7 @@ fn compress(raw: &Value, args: &Args, scorer: &dyn TokenScorer) -> (Value, Optim &policy, &OpenAiStrategy::default(), scorer, - &HeuristicBudgetCounter::default(), + &anyllm_optimize_core::HeuristicBudgetCounter::default(), &mut ws, ); if let Some(r) = &res.rendered { @@ -286,7 +293,7 @@ fn compress(raw: &Value, args: &Args, scorer: &dyn TokenScorer) -> (Value, Optim &policy, &AnthropicStrategy::default(), scorer, - &HeuristicBudgetCounter::default(), + &anyllm_optimize_core::HeuristicBudgetCounter::default(), &mut ws, ); if let Some(r) = &res.rendered { @@ -305,7 +312,7 @@ fn compress(raw: &Value, args: &Args, scorer: &dyn TokenScorer) -> (Value, Optim /// that gating already lives in `optimize()`/`compress_message` (see `LLMLingua2Pass`'s /// doc comment). Any failure to build/load falls back to `UniformScorer` with a warning /// rather than aborting the run. -fn build_scorer(args: &Args) -> Box { +pub(crate) fn build_scorer(args: &Args) -> Box { let Some(dir) = &args.llmlingua2_model_dir else { return Box::new(UniformScorer); }; @@ -364,339 +371,16 @@ fn fnv1a64(bytes: &[u8]) -> u64 { .fold(OFFSET, |h, &b| (h ^ b as u64).wrapping_mul(PRIME)) } -/// EH-0002 bite 2: simulate the frozen-frontier policy over `--input` and print the -/// signed net USD delta (compress vs skip) the cost gate computes per row, plus a class -/// total. Fully offline (no network) — the ΔT/S the gate uses come straight out of the -/// same `optimize()` call the harness already runs, so this is not a re-derivation, it is -/// the actual decision the frontier + cost gate would make. -fn run_net_cost(args: &Args) -> Result<()> { - let bodies = load_inputs(args)?; - let scorer = build_scorer(args); - let (pricing, model) = match args.cost_model.unwrap_or(args.api) { - Api::Openai => ( - OpenAiStrategy::default().pricing(), - OpenAiStrategy::default().model(), - ), - Api::Anthropic => ( - AnthropicStrategy::default().pricing(), - AnthropicStrategy::default().model(), - ), - }; - println!( - "{:<4} {:>9} {:>7} {:>7} {:>7} {:>14}", - "#", "frontier", "dt", "s", "apply", "net_usd" - ); - let mut total_dt = 0u64; - let mut total_net = 0.0f64; - for (i, mut raw) in bodies.into_iter().enumerate() { - ensure_model(&mut raw, args); - // dt/s come from `--api`'s adapter (must match the input's wire shape, or tool/JSON - // blocks get mis-segmented — see cost_model doc comment); the apply decision is - // then re-derived under `--cost-model`'s pricing/CacheModel, since `report.applied` - // reflects `--api`'s own strategy and the two can differ. - let (_compressed, report) = compress(&raw, args, scorer.as_ref()); - let dt = report.removed_tokens_est; - let s = report.rewrite_suffix_tokens; - let applies = anyllm_optimize_core::should_apply(dt, s, args.horizon, &pricing, &model); - // Only realize the delta when the gate actually applies: `applies == false` means - // the original is forwarded untouched, so the realized cost change is $0, not the - // negative "forced a no-op rewrite" value the raw formula would give for dt=0 - // (which `should_apply` exists precisely to avoid ever choosing). - let net = if applies { - net_cost_delta_usd(dt, s, args.horizon, &pricing, &model) - } else { - 0.0 - }; - total_dt += dt; - total_net += net; - println!( - "{:<4} {:>9} {:>7} {:>7} {:>7} {:>14.6}", - i, report.frontier, dt, s, applies, net - ); - } - let model_name = match model { - CacheModel::ImplicitPrefix => "implicit-prefix", - CacheModel::ExplicitBreakpoints => "explicit-breakpoints", - }; - println!( - "\nNET COST (horizon={}, {model_name}, input=${:.2}/Mtok cached_read=${:.2}/Mtok \ - write_mult={:.2}x): total ΔT={total_dt} tokens, net_delta=${total_net:.6}", - args.horizon, pricing.input, pricing.cached_read, pricing.cache_write_mult, - ); - Ok(()) -} - -fn ensure_model(body: &mut Value, args: &Args) { +pub(crate) fn ensure_model(body: &mut Value, args: &Args) { body["model"] = json!(args.model); if args.api == Api::Anthropic && body.get("max_tokens").is_none() { body["max_tokens"] = json!(args.max_tokens); } } -/// M3.6: selects the counter `count_local` uses to report est_raw/est_comp. Defaults to -/// `HeuristicBudgetCounter` (bytes/3.6, provider-agnostic). When this binary is built with -/// `--features tiktoken` AND the target is OpenAI-shaped, uses the exact `o200k_base` -/// tokenizer instead — Anthropic's tokenizer is unpublished, so the heuristic remains the -/// only option there (ROADMAP risk 3). This only changes what the harness *reports*; the -/// planning counter `compress()` passes into `optimize()` is unchanged. -#[cfg_attr(not(feature = "tiktoken"), allow(unused_variables))] -fn build_counter(args: &Args) -> Box { - #[cfg(feature = "tiktoken")] - if args.api == Api::Openai { - return Box::new(budget_tiktoken::TiktokenBudgetCounter); - } - Box::new(HeuristicBudgetCounter::default()) -} - -/// Local token estimate: sum the budget counter over all message text content. -fn count_local(body: &Value, api: Api, counter: &dyn BudgetCounter) -> u64 { - let mut total = 0u64; - if api == Api::Anthropic { - if let Some(sys) = body.get("system") { - total += count_content(sys, counter); - } - } - if let Some(msgs) = body.get("messages").and_then(|m| m.as_array()) { - for m in msgs { - if let Some(c) = m.get("content") { - total += count_content(c, counter); - } - } - } - total -} - -fn count_content(c: &Value, counter: &dyn BudgetCounter) -> u64 { - match c { - Value::String(s) => counter.count(s), - Value::Array(parts) => parts - .iter() - .map(|p| match p.get("text").and_then(|t| t.as_str()) { - Some(t) => counter.count(t), - None => counter.count(&p.to_string()), - }) - .sum(), - _ => 0, - } -} - -/// Send one request and return usage + assistant text. -fn send_request( - client: &reqwest::blocking::Client, - args: &Args, - body: &Value, - base_url: &str, - api: Api, -) -> Result { - let mut body = body.clone(); - let url = match api { - Api::Openai => format!("{}/chat/completions", base_url.trim_end_matches('/')), - Api::Anthropic => format!("{}/messages", base_url.trim_end_matches('/')), - }; - if args.stream { - body["stream"] = json!(true); - if api == Api::Openai { - body["stream_options"] = json!({ "include_usage": true }); - } - } - - let key = resolve_key(args); - let mut req = client.post(&url).json(&body); - req = match api { - Api::Openai => { - if key.is_empty() { - req - } else { - req.bearer_auth(key) - } - } - Api::Anthropic => req - .header("x-api-key", key) - .header("anthropic-version", "2023-06-01"), - }; - - let resp = req.send().context("request send failed")?; - let status = resp.status(); - let text = resp.text().context("reading response body")?; - if !status.is_success() { - anyhow::bail!( - "HTTP {status}: {}", - text.chars().take(300).collect::() - ); - } - Ok(Reply { - prompt_tokens: extract_prompt_tokens(&text, api, args.stream) - .context("could not find prompt/input token usage")?, - text: extract_response_text(&text, api, args.stream), - }) -} - -fn resolve_key(args: &Args) -> String { - args.api_key - .clone() - .or_else(|| std::env::var("OPENROUTER_API_KEY").ok()) - .or_else(|| std::env::var("ANYLLM_API_KEY").ok()) - .unwrap_or_default() -} - -/// Pull prompt tokens from a JSON body or an SSE stream. -fn extract_prompt_tokens(text: &str, api: Api, stream: bool) -> Result { - let want = |v: &Value| -> Option { - match api { - Api::Openai => v.get("usage")?.get("prompt_tokens")?.as_u64(), - Api::Anthropic => v - .get("usage") - .or_else(|| v.get("message").and_then(|m| m.get("usage")))? - .get("input_tokens")? - .as_u64(), - } - }; - if !stream { - let v: Value = serde_json::from_str(text)?; - return want(&v).context("usage field missing"); - } - for payload in sse_payloads(text) { - if let Ok(v) = serde_json::from_str::(&payload) { - if let Some(n) = want(&v) { - return Ok(n); - } - } - } - anyhow::bail!("no usage in stream") -} - -/// Pull assistant text from a JSON body or an SSE stream. Best-effort: returns "" if the -/// shape is unrecognized (quality just reads as low, never panics). -fn extract_response_text(text: &str, api: Api, stream: bool) -> String { - if !stream { - let Ok(v) = serde_json::from_str::(text) else { - return String::new(); - }; - return match api { - Api::Openai => v - .pointer("/choices/0/message/content") - .and_then(|c| c.as_str()) - .unwrap_or("") - .to_string(), - Api::Anthropic => v - .get("content") - .and_then(|c| c.as_array()) - .map(|blocks| { - blocks - .iter() - .filter_map(|b| b.get("text").and_then(|t| t.as_str())) - .collect::>() - .join("") - }) - .unwrap_or_default(), - }; - } - // Streaming: concatenate incremental text deltas. - let mut out = String::new(); - for payload in sse_payloads(text) { - let Ok(v) = serde_json::from_str::(&payload) else { - continue; - }; - match api { - Api::Openai => { - if let Some(s) = v - .pointer("/choices/0/delta/content") - .and_then(|c| c.as_str()) - { - out.push_str(s); - } - } - Api::Anthropic => { - if let Some(s) = v.pointer("/delta/text").and_then(|c| c.as_str()) { - out.push_str(s); - } - } - } - } - out -} - -/// Yield the JSON payload of each `data:` SSE line, skipping `[DONE]`. -fn sse_payloads(text: &str) -> impl Iterator + '_ { - text.lines().filter_map(|line| { - let payload = line.trim().strip_prefix("data:")?.trim(); - (payload != "[DONE]").then(|| payload.to_string()) - }) -} - -/// Word-level Jaccard similarity in [0,1]. 1.0 if both empty. -fn jaccard(a: &str, b: &str) -> f64 { - let sa: HashSet<&str> = a.split_whitespace().collect(); - let sb: HashSet<&str> = b.split_whitespace().collect(); - if sa.is_empty() && sb.is_empty() { - return 1.0; - } - let inter = sa.intersection(&sb).count() as f64; - let uni = sa.union(&sb).count() as f64; - if uni == 0.0 { - 1.0 - } else { - inter / uni - } -} - -/// Ask an OpenAI-compatible judge model to score 1-5 how well `comp` preserves `raw`. -fn run_judge( - client: &reqwest::blocking::Client, - args: &Args, - raw: &str, - comp: &str, -) -> Result { - let model = args.judge_model.as_ref().context("no judge model")?; - let base = args.judge_base_url.as_deref().unwrap_or(&args.base_url); - let prompt = format!( - "You compare two AI assistant responses to the same user request. Response A is \ - from the full prompt; Response B is from a compressed prompt. Score 1-5 how well B \ - preserves A's meaning and quality (5 = equivalent, 1 = badly degraded). Reply with \ - ONLY the integer.\n\n[A]\n{raw}\n\n[B]\n{comp}" - ); - let body = json!({ - "model": model, - "messages": [{ "role": "user", "content": prompt }], - "max_tokens": 4, - "temperature": 0, - }); - let url = format!("{}/chat/completions", base.trim_end_matches('/')); - let key = resolve_key(args); - let mut req = client.post(&url).json(&body); - if !key.is_empty() { - req = req.bearer_auth(key); - } - let resp = req.send().context("judge send failed")?; - let text = resp.text().context("reading judge response")?; - let v: Value = serde_json::from_str(&text).context("judge response not JSON")?; - let content = v - .pointer("/choices/0/message/content") - .and_then(|c| c.as_str()) - .context("judge response has no content")?; - let score = content - .chars() - .find(|c| ('1'..='5').contains(c)) - .and_then(|c| c.to_digit(10)) - .context("no 1-5 score in judge reply")?; - Ok(score) -} - -fn opt_u64(v: Option) -> String { - v.map(|x| x.to_string()).unwrap_or_else(|| "-".into()) -} - -fn pct(raw: u64, comp: u64) -> f64 { - if raw == 0 { - 0.0 - } else { - (raw.saturating_sub(comp)) as f64 / raw as f64 * 100.0 - } -} - /// Load input bodies. Each JSONL line is either a full request body (`{"messages":...}`) /// or a bare JSON string (treated as one user message). -fn load_inputs(args: &Args) -> Result> { +pub(crate) fn load_inputs(args: &Args) -> Result> { if let Some(p) = &args.prompt { return Ok(vec![wrap_prompt(p)]); } @@ -721,54 +405,3 @@ fn load_inputs(args: &Args) -> Result> { } Ok(out) } - -fn wrap_prompt(p: &str) -> Value { - json!({ "messages": [ { "role": "user", "content": p } ] }) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn jaccard_bounds() { - assert_eq!(jaccard("", ""), 1.0); - assert_eq!(jaccard("a b c", "a b c"), 1.0); - assert!((jaccard("a b", "b c") - 1.0 / 3.0).abs() < 1e-9); - assert_eq!(jaccard("a", "z"), 0.0); - } - - #[test] - fn extract_openai_nonstream() { - let body = - r#"{"choices":[{"message":{"content":"hello there"}}],"usage":{"prompt_tokens":42}}"#; - assert_eq!(extract_prompt_tokens(body, Api::Openai, false).unwrap(), 42); - assert_eq!( - extract_response_text(body, Api::Openai, false), - "hello there" - ); - } - - #[test] - fn extract_anthropic_nonstream() { - let body = r#"{"content":[{"type":"text","text":"hi"},{"type":"text","text":" world"}],"usage":{"input_tokens":7}}"#; - assert_eq!( - extract_prompt_tokens(body, Api::Anthropic, false).unwrap(), - 7 - ); - assert_eq!( - extract_response_text(body, Api::Anthropic, false), - "hi world" - ); - } - - #[test] - fn extract_openai_stream_text_and_usage() { - let sse = "data: {\"choices\":[{\"delta\":{\"content\":\"foo\"}}]}\n\ - data: {\"choices\":[{\"delta\":{\"content\":\"bar\"}}]}\n\ - data: {\"choices\":[],\"usage\":{\"prompt_tokens\":11}}\n\ - data: [DONE]\n"; - assert_eq!(extract_prompt_tokens(sse, Api::Openai, true).unwrap(), 11); - assert_eq!(extract_response_text(sse, Api::Openai, true), "foobar"); - } -} diff --git a/crates/optimizer/optimize-cli/src/tests.rs b/crates/optimizer/optimize-cli/src/tests.rs new file mode 100644 index 0000000..2f5c036 --- /dev/null +++ b/crates/optimizer/optimize-cli/src/tests.rs @@ -0,0 +1,43 @@ +use super::{client::*, utils::*, Api}; + +#[test] +fn jaccard_bounds() { + assert_eq!(jaccard("", ""), 1.0); + assert_eq!(jaccard("a b c", "a b c"), 1.0); + assert!((jaccard("a b", "b c") - 1.0 / 3.0).abs() < 1e-9); + assert_eq!(jaccard("a", "z"), 0.0); +} + +#[test] +fn extract_openai_nonstream() { + let body = + r#"{"choices":[{"message":{"content":"hello there"}}],"usage":{"prompt_tokens":42}}"#; + assert_eq!(extract_prompt_tokens(body, Api::Openai, false).unwrap(), 42); + assert_eq!( + extract_response_text(body, Api::Openai, false), + "hello there" + ); +} + +#[test] +fn extract_anthropic_nonstream() { + let body = r#"{"content":[{"type":"text","text":"hi"},{"type":"text","text":" world"}],"usage":{"input_tokens":7}}"#; + assert_eq!( + extract_prompt_tokens(body, Api::Anthropic, false).unwrap(), + 7 + ); + assert_eq!( + extract_response_text(body, Api::Anthropic, false), + "hi world" + ); +} + +#[test] +fn extract_openai_stream_text_and_usage() { + let sse = "data: {\"choices\":[{\"delta\":{\"content\":\"foo\"}}]}\n\ + data: {\"choices\":[{\"delta\":{\"content\":\"bar\"}}]}\n\ + data: {\"choices\":[],\"usage\":{\"prompt_tokens\":11}}\n\ + data: [DONE]\n"; + assert_eq!(extract_prompt_tokens(sse, Api::Openai, true).unwrap(), 11); + assert_eq!(extract_response_text(sse, Api::Openai, true), "foobar"); +} diff --git a/crates/optimizer/optimize-cli/src/token_count.rs b/crates/optimizer/optimize-cli/src/token_count.rs new file mode 100644 index 0000000..2bc37a8 --- /dev/null +++ b/crates/optimizer/optimize-cli/src/token_count.rs @@ -0,0 +1,50 @@ +use super::{Api, Args}; +use anyllm_optimize_core::{BudgetCounter, HeuristicBudgetCounter}; +use serde_json::Value; + +/// M3.6: selects the counter `count_local` uses to report est_raw/est_comp. Defaults to +/// `HeuristicBudgetCounter` (bytes/3.6, provider-agnostic). When this binary is built with +/// `--features tiktoken` AND the target is OpenAI-shaped, uses the exact `o200k_base` +/// tokenizer instead — Anthropic's tokenizer is unpublished, so the heuristic remains the +/// only option there (ROADMAP risk 3). This only changes what the harness *reports*; the +/// planning counter `compress()` passes into `optimize()` is unchanged. +#[cfg_attr(not(feature = "tiktoken"), allow(unused_variables))] +pub fn build_counter(args: &Args) -> Box { + #[cfg(feature = "tiktoken")] + if args.api == Api::Openai { + return Box::new(super::budget_tiktoken::TiktokenBudgetCounter); + } + Box::new(HeuristicBudgetCounter::default()) +} + +/// Local token estimate: sum the budget counter over all message text content. +pub fn count_local(body: &Value, api: Api, counter: &dyn BudgetCounter) -> u64 { + let mut total = 0u64; + if api == Api::Anthropic { + if let Some(sys) = body.get("system") { + total += count_content(sys, counter); + } + } + if let Some(msgs) = body.get("messages").and_then(|m| m.as_array()) { + for m in msgs { + if let Some(c) = m.get("content") { + total += count_content(c, counter); + } + } + } + total +} + +pub fn count_content(c: &Value, counter: &dyn BudgetCounter) -> u64 { + match c { + Value::String(s) => counter.count(s), + Value::Array(parts) => parts + .iter() + .map(|p| match p.get("text").and_then(|t| t.as_str()) { + Some(t) => counter.count(t), + None => counter.count(&p.to_string()), + }) + .sum(), + _ => 0, + } +} diff --git a/crates/optimizer/optimize-cli/src/utils.rs b/crates/optimizer/optimize-cli/src/utils.rs new file mode 100644 index 0000000..d4d5c69 --- /dev/null +++ b/crates/optimizer/optimize-cli/src/utils.rs @@ -0,0 +1,22 @@ +use serde_json::{json, Value}; +use std::collections::HashSet; + +/// Word-level Jaccard similarity in [0,1]. 1.0 if both empty. +pub fn jaccard(a: &str, b: &str) -> f64 { + let sa: HashSet<&str> = a.split_whitespace().collect(); + let sb: HashSet<&str> = b.split_whitespace().collect(); + if sa.is_empty() && sb.is_empty() { + return 1.0; + } + let inter = sa.intersection(&sb).count() as f64; + let uni = sa.union(&sb).count() as f64; + if uni == 0.0 { + 1.0 + } else { + inter / uni + } +} + +pub fn wrap_prompt(p: &str) -> Value { + json!({ "messages": [ { "role": "user", "content": p } ] }) +} diff --git a/crates/providers/README.md b/crates/providers/README.md index 7221607..896d4b7 100644 --- a/crates/providers/README.md +++ b/crates/providers/README.md @@ -1,6 +1,6 @@ # anyllm_providers -Provider and model catalog for the [anyllm-proxy](https://github.com/whit3rabbit/llm-translate-api) workspace. +Provider and model catalog. ## What this crate is diff --git a/crates/proxy/admin-ui/dist/index.html b/crates/proxy/admin-ui/dist/index.html index 1cc5068..0095e83 100644 --- a/crates/proxy/admin-ui/dist/index.html +++ b/crates/proxy/admin-ui/dist/index.html @@ -12,17 +12,17 @@ `);for(i=r=0;ri||c[r]!==l[i]){var u=` `+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(``)&&(u=u.replace(``,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{Ce=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?Se(n):``}function Te(e,t){switch(e.tag){case 26:case 27:case 5:return Se(e.type);case 16:return Se(`Lazy`);case 13:return e.child!==t&&t!==null?Se(`Suspense Fallback`):Se(`Suspense`);case 19:return Se(`SuspenseList`);case 0:case 15:return we(e.type,!1);case 11:return we(e.type.render,!1);case 1:return we(e.type,!0);case 31:return Se(`Activity`);default:return``}}function Ee(e){try{var t=``,n=null;do t+=Te(e,n),n=e,e=e.return;while(e);return t}catch(e){return` Error generating stack: `+e.message+` -`+e.stack}}var De=Object.prototype.hasOwnProperty,Oe=t.unstable_scheduleCallback,ke=t.unstable_cancelCallback,Ae=t.unstable_shouldYield,je=t.unstable_requestPaint,Me=t.unstable_now,Ne=t.unstable_getCurrentPriorityLevel,Pe=t.unstable_ImmediatePriority,Fe=t.unstable_UserBlockingPriority,Ie=t.unstable_NormalPriority,Le=t.unstable_LowPriority,Re=t.unstable_IdlePriority,ze=t.log,Be=t.unstable_setDisableYieldValue,Ve=null,He=null;function Ue(e){if(typeof ze==`function`&&Be(e),He&&typeof He.setStrictMode==`function`)try{He.setStrictMode(Ve,e)}catch{}}var We=Math.clz32?Math.clz32:qe,Ge=Math.log,Ke=Math.LN2;function qe(e){return e>>>=0,e===0?32:31-(Ge(e)/Ke|0)|0}var Je=256,Ye=262144,Xe=4194304;function Ze(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function Qe(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=Ze(n))):i=Ze(o):i=Ze(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=Ze(n))):i=Ze(o)):i=Ze(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function $e(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function et(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function tt(){var e=Xe;return Xe<<=1,!(Xe&62914560)&&(Xe=4194304),e}function nt(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function rt(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function it(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0`u`||window.document===void 0||window.document.createElement===void 0),gn=!1;if(hn)try{var _n={};Object.defineProperty(_n,"passive",{get:function(){gn=!0}}),window.addEventListener(`test`,_n,_n),window.removeEventListener(`test`,_n,_n)}catch{gn=!1}var vn=null,yn=null,bn=null;function xn(){if(bn)return bn;var e,t=yn,n=t.length,r,i=`value`in vn?vn.value:vn.textContent,a=i.length;for(e=0;e=Xn),$n=` `,er=!1;function tr(e,t){switch(e){case`keyup`:return Jn.indexOf(t.keyCode)!==-1;case`keydown`:return t.keyCode!==229;case`keypress`:case`mousedown`:case`focusout`:return!0;default:return!1}}function nr(e){return e=e.detail,typeof e==`object`&&`data`in e?e.data:null}var rr=!1;function ir(e,t){switch(e){case`compositionend`:return nr(t);case`keypress`:return t.which===32?(er=!0,$n):null;case`textInput`:return e=t.data,e===$n&&er?null:e;default:return null}}function ar(e,t){if(rr)return e===`compositionend`||!Yn&&tr(e,t)?(e=xn(),bn=yn=vn=null,rr=!1,e):null;switch(e){case`paste`:return null;case`keypress`:if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}a:{for(;n;){if(n.nextSibling){n=n.nextSibling;break a}n=n.parentNode}n=void 0}n=Dr(n)}}function kr(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?kr(e,t.parentNode):`contains`in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Ar(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=Ht(e.document);t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href==`string`}catch{n=!1}if(n)e=t.contentWindow;else break;t=Ht(e.document)}return t}function jr(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t===`input`&&(e.type===`text`||e.type===`search`||e.type===`tel`||e.type===`url`||e.type===`password`)||t===`textarea`||e.contentEditable===`true`)}var Mr=hn&&`documentMode`in document&&11>=document.documentMode,Nr=null,Pr=null,Fr=null,Ir=!1;function Lr(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Ir||Nr==null||Nr!==Ht(r)||(r=Nr,`selectionStart`in r&&jr(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Fr&&Er(Fr,r)||(Fr=r,r=Dd(Pr,`onSelect`),0>=o,i-=o,ki=1<<32-We(t)+i|n<h?(g=d,d=null):g=d.sibling;var _=p(i,d,s[h],c);if(_===null){d===null&&(d=g);break}e&&d&&_.alternate===null&&t(i,d),a=o(_,a,h),u===null?l=_:u.sibling=_,u=_,d=g}if(h===s.length)return n(i,d),I&&ji(i,h),l;if(d===null){for(;hg?(_=h,h=null):_=h.sibling;var y=p(a,h,v.value,l);if(y===null){h===null&&(h=_);break}e&&h&&y.alternate===null&&t(a,h),s=o(y,s,g),d===null?u=y:d.sibling=y,d=y,h=_}if(v.done)return n(a,h),I&&ji(a,g),u;if(h===null){for(;!v.done;g++,v=c.next())v=f(a,v.value,l),v!==null&&(s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return I&&ji(a,g),u}for(h=r(h);!v.done;g++,v=c.next())v=m(h,a,g,v.value,l),v!==null&&(e&&v.alternate!==null&&h.delete(v.key===null?g:v.key),s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return e&&h.forEach(function(e){return t(a,e)}),I&&ji(a,g),u}function b(e,r,o,c){if(typeof o==`object`&&o&&o.type===y&&o.key===null&&(o=o.props.children),typeof o==`object`&&o){switch(o.$$typeof){case _:a:{for(var l=o.key;r!==null;){if(r.key===l){if(l=o.type,l===y){if(r.tag===7){n(e,r.sibling),c=a(r,o.props.children),c.return=e,e=c;break a}}else if(r.elementType===l||typeof l==`object`&&l&&l.$$typeof===O&&Aa(l)===r.type){n(e,r.sibling),c=a(r,o.props),Ia(c,o),c.return=e,e=c;break a}n(e,r);break}else t(e,r);r=r.sibling}o.type===y?(c=gi(o.props.children,e.mode,c,o.key),c.return=e,e=c):(c=hi(o.type,o.key,o.props,null,e.mode,c),Ia(c,o),c.return=e,e=c)}return s(e);case v:a:{for(l=o.key;r!==null;){if(r.key===l)if(r.tag===4&&r.stateNode.containerInfo===o.containerInfo&&r.stateNode.implementation===o.implementation){n(e,r.sibling),c=a(r,o.children||[]),c.return=e,e=c;break a}else{n(e,r);break}else t(e,r);r=r.sibling}c=yi(o,e.mode,c),c.return=e,e=c}return s(e);case O:return o=Aa(o),b(e,r,o,c)}if(oe(o))return h(e,r,o,c);if(re(o)){if(l=re(o),typeof l!=`function`)throw Error(i(150));return o=l.call(o),g(e,r,o,c)}if(typeof o.then==`function`)return b(e,r,Fa(o),c);if(o.$$typeof===C)return b(e,r,ia(e,o),c);La(e,o)}return typeof o==`string`&&o!==``||typeof o==`number`||typeof o==`bigint`?(o=``+o,r!==null&&r.tag===6?(n(e,r.sibling),c=a(r,o),c.return=e,e=c):(n(e,r),c=_i(o,e.mode,c),c.return=e,e=c),s(e)):n(e,r)}return function(e,t,n,r){try{Pa=0;var i=b(e,t,n,r);return R=null,i}catch(t){if(t===wa||t===Ea)throw t;var a=di(29,t,null,e.mode);return a.lanes=r,a.return=e,a}}}var za=Ra(!0),Ba=Ra(!1),Va=!1;function Ha(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Ua(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function Wa(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function Ga(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,G&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,t=ci(e),si(e,null,n),t}return ii(e,r,t,n),ci(e)}function Ka(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,n&4194048)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,ot(e,n)}}function qa(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var o={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};a===null?i=a=o:a=a.next=o,n=n.next}while(n!==null);a===null?i=a=t:a=a.next=t}else i=a=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:a,shared:r.shared,callbacks:r.callbacks},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var Ja=!1;function Ya(){if(Ja){var e=ha;if(e!==null)throw e}}function Xa(e,t,n,r){Ja=!1;var i=e.updateQueue;Va=!1;var a=i.firstBaseUpdate,o=i.lastBaseUpdate,s=i.shared.pending;if(s!==null){i.shared.pending=null;var c=s,l=c.next;c.next=null,o===null?a=l:o.next=l,o=c;var u=e.alternate;u!==null&&(u=u.updateQueue,s=u.lastBaseUpdate,s!==o&&(s===null?u.firstBaseUpdate=l:s.next=l,u.lastBaseUpdate=c))}if(a!==null){var d=i.baseState;o=0,u=l=c=null,s=a;do{var f=s.lane&-536870913,p=f!==s.lane;if(p?(J&f)===f:(r&f)===f){f!==0&&f===ma&&(Ja=!0),u!==null&&(u=u.next={lane:0,tag:s.tag,payload:s.payload,callback:null,next:null});a:{var h=e,g=s;f=t;var _=n;switch(g.tag){case 1:if(h=g.payload,typeof h==`function`){d=h.call(_,d,f);break a}d=h;break a;case 3:h.flags=h.flags&-65537|128;case 0:if(h=g.payload,f=typeof h==`function`?h.call(_,d,f):h,f==null)break a;d=m({},d,f);break a;case 2:Va=!0}}f=s.callback,f!==null&&(e.flags|=64,p&&(e.flags|=8192),p=i.callbacks,p===null?i.callbacks=[f]:p.push(f))}else p={lane:f,tag:s.tag,payload:s.payload,callback:s.callback,next:null},u===null?(l=u=p,c=d):u=u.next=p,o|=f;if(s=s.next,s===null){if(s=i.shared.pending,s===null)break;p=s,s=p.next,p.next=null,i.lastBaseUpdate=p,i.shared.pending=null}}while(1);u===null&&(c=d),i.baseState=c,i.firstBaseUpdate=l,i.lastBaseUpdate=u,a===null&&(i.shared.lanes=0),Kl|=o,e.lanes=o,e.memoizedState=d}}function Za(e,t){if(typeof e!=`function`)throw Error(i(191,e));e.call(t)}function Qa(e,t){var n=e.callbacks;if(n!==null)for(e.callbacks=null,e=0;ea?a:8;var o=k.T,s={};k.T=s,Is(e,!1,t,n);try{var c=i(),l=k.S;l!==null&&l(s,c),typeof c==`object`&&c&&typeof c.then==`function`?Fs(e,t,va(c,r),mu(e)):Fs(e,t,r,mu(e))}catch(n){Fs(e,t,{then:function(){},status:`rejected`,reason:n},mu())}finally{A.p=a,o!==null&&s.types!==null&&(o.types=s.types),k.T=o}}function Ts(){}function Es(e,t,n,r){if(e.tag!==5)throw Error(i(476));var a=Ds(e).queue;ws(e,a,t,se,n===null?Ts:function(){return Os(e),n(r)})}function Ds(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:se,baseState:se,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Lo,lastRenderedState:se},next:null};var n={};return t.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Lo,lastRenderedState:n},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function Os(e){var t=Ds(e);t.next===null&&(t=e.alternate.memoizedState),Fs(e,t.next.queue,{},mu())}function ks(){return L($f)}function As(){return Mo().memoizedState}function js(){return Mo().memoizedState}function Ms(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var n=mu();e=Wa(n);var r=Ga(t,e,n);r!==null&&(gu(r,t,n),Ka(r,t,n)),t={cache:ua()},e.payload=t;return}t=t.return}}function Ns(e,t,n){var r=mu();n={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},Ls(e)?Rs(t,n):(n=ai(e,t,n,r),n!==null&&(gu(n,e,r),H(n,t,r)))}function Ps(e,t,n){Fs(e,t,n,mu())}function Fs(e,t,n,r){var i={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(Ls(e))Rs(t,i);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var o=t.lastRenderedState,s=a(o,n);if(i.hasEagerState=!0,i.eagerState=s,Tr(s,o))return ii(e,t,i,0),K===null&&ri(),!1}catch{}if(n=ai(e,t,i,r),n!==null)return gu(n,e,r),H(n,t,r),!0}return!1}function Is(e,t,n,r){if(r={lane:2,revertLane:fd(),gesture:null,action:r,hasEagerState:!1,eagerState:null,next:null},Ls(e)){if(t)throw Error(i(479))}else t=ai(e,n,r,2),t!==null&&gu(t,e,2)}function Ls(e){var t=e.alternate;return e===z||t!==null&&t===z}function Rs(e,t){_o=go=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function H(e,t,n){if(n&4194048){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,ot(e,n)}}var zs={readContext:L,use:Fo,useCallback:So,useContext:So,useEffect:So,useImperativeHandle:So,useLayoutEffect:So,useInsertionEffect:So,useMemo:So,useReducer:So,useRef:So,useState:So,useDebugValue:So,useDeferredValue:So,useTransition:So,useSyncExternalStore:So,useId:So,useHostTransitionStatus:So,useFormState:So,useActionState:So,useOptimistic:So,useMemoCache:So,useCacheRefresh:So};zs.useEffectEvent=So;var Bs={readContext:L,use:Fo,useCallback:function(e,t){return jo().memoizedState=[e,t===void 0?null:t],e},useContext:L,useEffect:ds,useImperativeHandle:function(e,t,n){n=n==null?null:n.concat([e]),ls(4194308,4,_s.bind(null,t,e),n)},useLayoutEffect:function(e,t){return ls(4194308,4,e,t)},useInsertionEffect:function(e,t){ls(4,2,e,t)},useMemo:function(e,t){var n=jo();t=t===void 0?null:t;var r=e();if(vo){Ue(!0);try{e()}finally{Ue(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=jo();if(n!==void 0){var i=n(t);if(vo){Ue(!0);try{n(t)}finally{Ue(!1)}}}else i=t;return r.memoizedState=r.baseState=i,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:i},r.queue=e,e=e.dispatch=Ns.bind(null,z,e),[r.memoizedState,e]},useRef:function(e){var t=jo();return e={current:e},t.memoizedState=e},useState:function(e){e=qo(e);var t=e.queue,n=Ps.bind(null,z,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:ys,useDeferredValue:function(e,t){return Ss(jo(),e,t)},useTransition:function(){var e=qo(!1);return e=ws.bind(null,z,e.queue,!0,!1),jo().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=z,a=jo();if(I){if(n===void 0)throw Error(i(407));n=n()}else{if(n=t(),K===null)throw Error(i(349));J&127||Ho(r,t,n)}a.memoizedState=n;var o={value:n,getSnapshot:t};return a.queue=o,ds(Wo.bind(null,r,o,e),[e]),r.flags|=2048,ss(9,{destroy:void 0},Uo.bind(null,r,o,n,t),null),n},useId:function(){var e=jo(),t=K.identifierPrefix;if(I){var n=Ai,r=ki;n=(r&~(1<<32-We(r)-1)).toString(32)+n,t=`_`+t+`R_`+n,n=yo++,0<\/script>`,o=o.removeChild(o.firstChild);break;case`select`:o=typeof r.is==`string`?s.createElement(`select`,{is:r.is}):s.createElement(`select`),r.multiple?o.multiple=!0:r.size&&(o.size=r.size);break;default:o=typeof r.is==`string`?s.createElement(a,{is:r.is}):s.createElement(a)}}o[ft]=t,o[pt]=r;a:for(s=t.child;s!==null;){if(s.tag===5||s.tag===6)o.appendChild(s.stateNode);else if(s.tag!==4&&s.tag!==27&&s.child!==null){s.child.return=s,s=s.child;continue}if(s===t)break a;for(;s.sibling===null;){if(s.return===null||s.return===t)break a;s=s.return}s.sibling.return=s.return,s=s.sibling}t.stateNode=o;a:switch(Fd(o,a,r),a){case`button`:case`input`:case`select`:case`textarea`:r=!!r.autoFocus;break a;case`img`:r=!0;break a;default:r=!1}r&&Nc(t)}}return Rc(t),Pc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==r&&Nc(t);else{if(typeof r!=`string`&&t.stateNode===null)throw Error(i(166));if(e=me.current,Wi(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,a=Ii,a!==null)switch(a.tag){case 27:case 5:r=a.memoizedProps}e[ft]=t,e=!!(e.nodeValue===n||r!==null&&!0===r.suppressHydrationWarning||Nd(e.nodeValue,n)),e||Vi(t,!0)}else e=Vd(e).createTextNode(r),e[ft]=t,t.stateNode=e}return Rc(t),null;case 31:if(n=t.memoizedState,e===null||e.memoizedState!==null){if(r=Wi(t),n!==null){if(e===null){if(!r)throw Error(i(318));if(e=t.memoizedState,e=e===null?null:e.dehydrated,!e)throw Error(i(557));e[ft]=t}else Gi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;Rc(t),e=!1}else n=Ki(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e)return t.flags&256?(uo(t),t):(uo(t),null);if(t.flags&128)throw Error(i(558))}return Rc(t),null;case 13:if(r=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(a=Wi(t),r!==null&&r.dehydrated!==null){if(e===null){if(!a)throw Error(i(318));if(a=t.memoizedState,a=a===null?null:a.dehydrated,!a)throw Error(i(317));a[ft]=t}else Gi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;Rc(t),a=!1}else a=Ki(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=a),a=!0;if(!a)return t.flags&256?(uo(t),t):(uo(t),null)}return uo(t),t.flags&128?(t.lanes=n,t):(n=r!==null,e=e!==null&&e.memoizedState!==null,n&&(r=t.child,a=null,r.alternate!==null&&r.alternate.memoizedState!==null&&r.alternate.memoizedState.cachePool!==null&&(a=r.alternate.memoizedState.cachePool.pool),o=null,r.memoizedState!==null&&r.memoizedState.cachePool!==null&&(o=r.memoizedState.cachePool.pool),o!==a&&(r.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),Ic(t,t.updateQueue),Rc(t),null);case 4:return _e(),e===null&&Cd(t.stateNode.containerInfo),Rc(t),null;case 10:return Qi(t.type),Rc(t),null;case 19:if(de(fo),r=t.memoizedState,r===null)return Rc(t),null;if(a=(t.flags&128)!=0,o=r.rendering,o===null)if(a)Lc(r,!1);else{if(Gl!==0||e!==null&&e.flags&128)for(e=t.child;e!==null;){if(o=po(e),o!==null){for(t.flags|=128,Lc(r,!1),e=o.updateQueue,t.updateQueue=e,Ic(t,e),t.subtreeFlags=0,e=n,n=t.child;n!==null;)mi(n,e),n=n.sibling;return j(fo,fo.current&1|2),I&&ji(t,r.treeForkCount),t.child}e=e.sibling}r.tail!==null&&Me()>nu&&(t.flags|=128,a=!0,Lc(r,!1),t.lanes=4194304)}else{if(!a)if(e=po(o),e!==null){if(t.flags|=128,a=!0,e=e.updateQueue,t.updateQueue=e,Ic(t,e),Lc(r,!0),r.tail===null&&r.tailMode===`hidden`&&!o.alternate&&!I)return Rc(t),null}else 2*Me()-r.renderingStartTime>nu&&n!==536870912&&(t.flags|=128,a=!0,Lc(r,!1),t.lanes=4194304);r.isBackwards?(o.sibling=t.child,t.child=o):(e=r.last,e===null?t.child=o:e.sibling=o,r.last=o)}return r.tail===null?(Rc(t),null):(e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=Me(),e.sibling=null,n=fo.current,j(fo,a?n&1|2:n&1),I&&ji(t,r.treeForkCount),e);case 22:case 23:return uo(t),ro(),r=t.memoizedState!==null,e===null?r&&(t.flags|=8192):e.memoizedState!==null!==r&&(t.flags|=8192),r?n&536870912&&!(t.flags&128)&&(Rc(t),t.subtreeFlags&6&&(t.flags|=8192)):Rc(t),n=t.updateQueue,n!==null&&Ic(t,n.retryQueue),n=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(n=e.memoizedState.cachePool.pool),r=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),e!==null&&de(ba),null;case 24:return n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),Qi(la),Rc(t),null;case 25:return null;case 30:return null}throw Error(i(156,t.tag))}function Bc(e,t){switch(Pi(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Qi(la),_e(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return ye(t),null;case 31:if(t.memoizedState!==null){if(uo(t),t.alternate===null)throw Error(i(340));Gi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(uo(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(i(340));Gi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return de(fo),null;case 4:return _e(),null;case 10:return Qi(t.type),null;case 22:case 23:return uo(t),ro(),e!==null&&de(ba),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return Qi(la),null;case 25:return null;default:return null}}function Vc(e,t){switch(Pi(t),t.tag){case 3:Qi(la),_e();break;case 26:case 27:case 5:ye(t);break;case 4:_e();break;case 31:t.memoizedState!==null&&uo(t);break;case 13:uo(t);break;case 19:de(fo);break;case 10:Qi(t.type);break;case 22:case 23:uo(t),ro(),e!==null&&de(ba);break;case 24:Qi(la)}}function Hc(e,t){try{var n=t.updateQueue,r=n===null?null:n.lastEffect;if(r!==null){var i=r.next;n=i;do{if((n.tag&e)===e){r=void 0;var a=n.create,o=n.inst;r=a(),o.destroy=r}n=n.next}while(n!==i)}}catch(e){X(t,t.return,e)}}function Uc(e,t,n){try{var r=t.updateQueue,i=r===null?null:r.lastEffect;if(i!==null){var a=i.next;r=a;do{if((r.tag&e)===e){var o=r.inst,s=o.destroy;if(s!==void 0){o.destroy=void 0,i=t;var c=n,l=s;try{l()}catch(e){X(i,c,e)}}}r=r.next}while(r!==a)}}catch(e){X(t,t.return,e)}}function Wc(e){var t=e.updateQueue;if(t!==null){var n=e.stateNode;try{Qa(t,n)}catch(t){X(e,e.return,t)}}}function Gc(e,t,n){n.props=Ks(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){X(e,t,n)}}function Kc(e,t){try{var n=e.ref;if(n!==null){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;case 30:r=e.stateNode;break;default:r=e.stateNode}typeof n==`function`?e.refCleanup=n(r):n.current=r}}catch(n){X(e,t,n)}}function qc(e,t){var n=e.ref,r=e.refCleanup;if(n!==null)if(typeof r==`function`)try{r()}catch(n){X(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof n==`function`)try{n(null)}catch(n){X(e,t,n)}else n.current=null}function Jc(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{a:switch(t){case`button`:case`input`:case`select`:case`textarea`:n.autoFocus&&r.focus();break a;case`img`:n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){X(e,e.return,t)}}function Yc(e,t,n){try{var r=e.stateNode;Id(r,e.type,n,t),r[pt]=t}catch(t){X(e,e.return,t)}}function Xc(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&Qd(e.type)||e.tag===4}function Zc(e){a:for(;;){for(;e.sibling===null;){if(e.return===null||Xc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&Qd(e.type)||e.flags&2||e.child===null||e.tag===4)continue a;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Qc(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?(n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n).insertBefore(e,t):(t=n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n,t.appendChild(e),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=on));else if(r!==4&&(r===27&&Qd(e.type)&&(n=e.stateNode,t=null),e=e.child,e!==null))for(Qc(e,t,n),e=e.sibling;e!==null;)Qc(e,t,n),e=e.sibling}function $c(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(r===27&&Qd(e.type)&&(n=e.stateNode),e=e.child,e!==null))for($c(e,t,n),e=e.sibling;e!==null;)$c(e,t,n),e=e.sibling}function el(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,i=t.attributes;i.length;)t.removeAttributeNode(i[0]);Fd(t,r,n),t[ft]=e,t[pt]=n}catch(t){X(e,e.return,t)}}var tl=!1,nl=!1,rl=!1,il=typeof WeakSet==`function`?WeakSet:Set,al=null;function ol(e,t){if(e=e.containerInfo,zd=cp,e=Ar(e),jr(e)){if(`selectionStart`in e)var n={start:e.selectionStart,end:e.selectionEnd};else a:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var a=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break a}var s=0,c=-1,l=-1,u=0,d=0,f=e,p=null;b:for(;;){for(var m;f!==n||a!==0&&f.nodeType!==3||(c=s+a),f!==o||r!==0&&f.nodeType!==3||(l=s+r),f.nodeType===3&&(s+=f.nodeValue.length),(m=f.firstChild)!==null;)p=f,f=m;for(;;){if(f===e)break b;if(p===n&&++u===a&&(c=s),p===o&&++d===r&&(l=s),(m=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=m}n=c===-1||l===-1?null:{start:c,end:l}}else n=null}n=n||{start:0,end:0}}else n=null;for(Bd={focusedElem:e,selectionRange:n},cp=!1,al=t;al!==null;)if(t=al,e=t.child,t.subtreeFlags&1028&&e!==null)e.return=t,al=e;else for(;al!==null;){switch(t=al,o=t.alternate,e=t.flags,t.tag){case 0:if(e&4&&(e=t.updateQueue,e=e===null?null:e.events,e!==null))for(n=0;n title`))),Fd(o,r,n),o[ft]=e,Tt(o),r=o;break a;case`link`:var s=Hf(`link`,`href`,a).get(r+(n.href||``));if(s){for(var c=0;cg&&(o=g,g=h,h=o);var _=Or(s,h),v=Or(s,g);if(_&&v&&(p.rangeCount!==1||p.anchorNode!==_.node||p.anchorOffset!==_.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var y=d.createRange();y.setStart(_.node,_.offset),p.removeAllRanges(),h>g?(p.addRange(y),p.extend(v.node,v.offset)):(y.setEnd(v.node,v.offset),p.addRange(y))}}}}for(d=[],p=s;p=p.parentNode;)p.nodeType===1&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for(typeof s.focus==`function`&&s.focus(),s=0;sn?32:n,k.T=null,n=uu,uu=null;var o=ou,s=cu;if(au=0,su=ou=null,cu=0,G&6)throw Error(i(331));var c=G;if(G|=4,Fl(o.current),Dl(o,o.current,s,n),G=c,ad(0,!1),He&&typeof He.onPostCommitFiberRoot==`function`)try{He.onPostCommitFiberRoot(Ve,o)}catch{}return!0}finally{A.p=a,k.T=r,Hu(e,t)}}function Gu(e,t,n){t=xi(n,t),t=Qs(e.stateNode,t,2),e=Ga(e,t,2),e!==null&&(rt(e,2),id(e))}function X(e,t,n){if(e.tag===3)Gu(e,e,n);else for(;t!==null;){if(t.tag===3){Gu(t,e,n);break}else if(t.tag===1){var r=t.stateNode;if(typeof t.type.getDerivedStateFromError==`function`||typeof r.componentDidCatch==`function`&&(iu===null||!iu.has(r))){e=xi(n,e),n=$s(2),r=Ga(t,n,2),r!==null&&(ec(n,r,t,e),rt(r,2),id(r));break}}t=t.return}}function Ku(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new zl;var i=new Set;r.set(t,i)}else i=r.get(t),i===void 0&&(i=new Set,r.set(t,i));i.has(n)||(Ul=!0,i.add(n),e=qu.bind(null,e,t,n),t.then(e,e))}function qu(e,t,n){var r=e.pingCache;r!==null&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,K===e&&(J&n)===n&&(Gl===4||Gl===3&&(J&62914560)===J&&300>Me()-eu?!(G&2)&&Cu(e,0):Jl|=n,Xl===J&&(Xl=0)),id(e)}function Ju(e,t){t===0&&(t=tt()),e=oi(e,t),e!==null&&(rt(e,t),id(e))}function Yu(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Ju(e,n)}function Xu(e,t){var n=0;switch(e.tag){case 31:case 13:var r=e.stateNode,a=e.memoizedState;a!==null&&(n=a.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(i(314))}r!==null&&r.delete(t),Ju(e,n)}function Zu(e,t){return Oe(e,t)}var Qu=null,$u=null,ed=!1,td=!1,nd=!1,rd=0;function id(e){e!==$u&&e.next===null&&($u===null?Qu=$u=e:$u=$u.next=e),td=!0,ed||(ed=!0,dd())}function ad(e,t){if(!nd&&td){nd=!0;do for(var n=!1,r=Qu;r!==null;){if(!t)if(e!==0){var i=r.pendingLanes;if(i===0)var a=0;else{var o=r.suspendedLanes,s=r.pingedLanes;a=(1<<31-We(42|e)+1)-1,a&=i&~(o&~s),a=a&201326741?a&201326741|1:a?a|2:0}a!==0&&(n=!0,ud(r,a))}else a=J,a=Qe(r,r===K?a:0,r.cancelPendingCommit!==null||r.timeoutHandle!==-1),!(a&3)||$e(r,a)||(n=!0,ud(r,a));r=r.next}while(n);nd=!1}}function od(){sd()}function sd(){td=ed=!1;var e=0;rd!==0&&Kd()&&(e=rd);for(var t=Me(),n=null,r=Qu;r!==null;){var i=r.next,a=cd(r,t);a===0?(r.next=null,n===null?Qu=i:n.next=i,i===null&&($u=n)):(n=r,(e!==0||a&3)&&(td=!0)),r=i}au!==0&&au!==5||ad(e,!1),rd!==0&&(rd=0)}function cd(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,i=e.expirationTimes,a=e.pendingLanes&-62914561;0s)break;var u=c.transferSize,d=c.initiatorType;u&&Ld(d)&&(c=c.responseEnd,o+=u*(c`u`?null:document;function Sf(e,t,n){var r=xf;if(r&&typeof t==`string`&&t){var i=Wt(t);i=`link[rel="`+e+`"][href="`+i+`"]`,typeof n==`string`&&(i+=`[crossorigin="`+n+`"]`),gf.has(i)||(gf.add(i),e={rel:e,crossOrigin:n,href:t},r.querySelector(i)===null&&(t=r.createElement(`link`),Fd(t,`link`,e),Tt(t),r.head.appendChild(t)))}}function Cf(e){vf.D(e),Sf(`dns-prefetch`,e,null)}function wf(e,t){vf.C(e,t),Sf(`preconnect`,e,t)}function Tf(e,t,n){vf.L(e,t,n);var r=xf;if(r&&e&&t){var i=`link[rel="preload"][as="`+Wt(t)+`"]`;t===`image`&&n&&n.imageSrcSet?(i+=`[imagesrcset="`+Wt(n.imageSrcSet)+`"]`,typeof n.imageSizes==`string`&&(i+=`[imagesizes="`+Wt(n.imageSizes)+`"]`)):i+=`[href="`+Wt(e)+`"]`;var a=i;switch(t){case`style`:a=jf(e);break;case`script`:a=Ff(e)}hf.has(a)||(e=m({rel:`preload`,href:t===`image`&&n&&n.imageSrcSet?void 0:e,as:t},n),hf.set(a,e),r.querySelector(i)!==null||t===`style`&&r.querySelector(Mf(a))||t===`script`&&r.querySelector(If(a))||(t=r.createElement(`link`),Fd(t,`link`,e),Tt(t),r.head.appendChild(t)))}}function Ef(e,t){vf.m(e,t);var n=xf;if(n&&e){var r=t&&typeof t.as==`string`?t.as:`script`,i=`link[rel="modulepreload"][as="`+Wt(r)+`"][href="`+Wt(e)+`"]`,a=i;switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:a=Ff(e)}if(!hf.has(a)&&(e=m({rel:`modulepreload`,href:e},t),hf.set(a,e),n.querySelector(i)===null)){switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:if(n.querySelector(If(a)))return}r=n.createElement(`link`),Fd(r,`link`,e),Tt(r),n.head.appendChild(r)}}}function Df(e,t,n){vf.S(e,t,n);var r=xf;if(r&&e){var i=wt(r).hoistableStyles,a=jf(e);t=t||`default`;var o=i.get(a);if(!o){var s={loading:0,preload:null};if(o=r.querySelector(Mf(a)))s.loading=5;else{e=m({rel:`stylesheet`,href:e,"data-precedence":t},n),(n=hf.get(a))&&zf(e,n);var c=o=r.createElement(`link`);Tt(c),Fd(c,`link`,e),c._p=new Promise(function(e,t){c.onload=e,c.onerror=t}),c.addEventListener(`load`,function(){s.loading|=1}),c.addEventListener(`error`,function(){s.loading|=2}),s.loading|=4,Rf(o,t,r)}o={type:`stylesheet`,instance:o,count:1,state:s},i.set(a,o)}}}function Of(e,t){vf.X(e,t);var n=xf;if(n&&e){var r=wt(n).hoistableScripts,i=Ff(e),a=r.get(i);a||(a=n.querySelector(If(i)),a||(e=m({src:e,async:!0},t),(t=hf.get(i))&&Bf(e,t),a=n.createElement(`script`),Tt(a),Fd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function kf(e,t){vf.M(e,t);var n=xf;if(n&&e){var r=wt(n).hoistableScripts,i=Ff(e),a=r.get(i);a||(a=n.querySelector(If(i)),a||(e=m({src:e,async:!0,type:`module`},t),(t=hf.get(i))&&Bf(e,t),a=n.createElement(`script`),Tt(a),Fd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function Af(e,t,n,r){var a=(a=me.current)?_f(a):null;if(!a)throw Error(i(446));switch(e){case`meta`:case`title`:return null;case`style`:return typeof n.precedence==`string`&&typeof n.href==`string`?(t=jf(n.href),n=wt(a).hoistableStyles,r=n.get(t),r||(r={type:`style`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};case`link`:if(n.rel===`stylesheet`&&typeof n.href==`string`&&typeof n.precedence==`string`){e=jf(n.href);var o=wt(a).hoistableStyles,s=o.get(e);if(s||(a=a.ownerDocument||a,s={type:`stylesheet`,instance:null,count:0,state:{loading:0,preload:null}},o.set(e,s),(o=a.querySelector(Mf(e)))&&!o._p&&(s.instance=o,s.state.loading=5),hf.has(e)||(n={rel:`preload`,as:`style`,href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},hf.set(e,n),o||Pf(a,e,n,s.state))),t&&r===null)throw Error(i(528,``));return s}if(t&&r!==null)throw Error(i(529,``));return null;case`script`:return t=n.async,n=n.src,typeof n==`string`&&t&&typeof t!=`function`&&typeof t!=`symbol`?(t=Ff(n),n=wt(a).hoistableScripts,r=n.get(t),r||(r={type:`script`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};default:throw Error(i(444,e))}}function jf(e){return`href="`+Wt(e)+`"`}function Mf(e){return`link[rel="stylesheet"][`+e+`]`}function Nf(e){return m({},e,{"data-precedence":e.precedence,precedence:null})}function Pf(e,t,n,r){e.querySelector(`link[rel="preload"][as="style"][`+t+`]`)?r.loading=1:(t=e.createElement(`link`),r.preload=t,t.addEventListener(`load`,function(){return r.loading|=1}),t.addEventListener(`error`,function(){return r.loading|=2}),Fd(t,`link`,n),Tt(t),e.head.appendChild(t))}function Ff(e){return`[src="`+Wt(e)+`"]`}function If(e){return`script[async]`+e}function Lf(e,t,n){if(t.count++,t.instance===null)switch(t.type){case`style`:var r=e.querySelector(`style[data-href~="`+Wt(n.href)+`"]`);if(r)return t.instance=r,Tt(r),r;var a=m({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return r=(e.ownerDocument||e).createElement(`style`),Tt(r),Fd(r,`style`,a),Rf(r,n.precedence,e),t.instance=r;case`stylesheet`:a=jf(n.href);var o=e.querySelector(Mf(a));if(o)return t.state.loading|=4,t.instance=o,Tt(o),o;r=Nf(n),(a=hf.get(a))&&zf(r,a),o=(e.ownerDocument||e).createElement(`link`),Tt(o);var s=o;return s._p=new Promise(function(e,t){s.onload=e,s.onerror=t}),Fd(o,`link`,r),t.state.loading|=4,Rf(o,n.precedence,e),t.instance=o;case`script`:return o=Ff(n.src),(a=e.querySelector(If(o)))?(t.instance=a,Tt(a),a):(r=n,(a=hf.get(o))&&(r=m({},n),Bf(r,a)),e=e.ownerDocument||e,a=e.createElement(`script`),Tt(a),Fd(a,`link`,r),e.head.appendChild(a),t.instance=a);case`void`:return null;default:throw Error(i(443,t.type))}else t.type===`stylesheet`&&!(t.state.loading&4)&&(r=t.instance,t.state.loading|=4,Rf(r,n.precedence,e));return t.instance}function Rf(e,t,n){for(var r=n.querySelectorAll(`link[rel="stylesheet"][data-precedence],style[data-precedence]`),i=r.length?r[r.length-1]:null,a=i,o=0;o title`):null)}function Wf(e,t,n){if(n===1||t.itemProp!=null)return!1;switch(e){case`meta`:case`title`:return!0;case`style`:if(typeof t.precedence!=`string`||typeof t.href!=`string`||t.href===``)break;return!0;case`link`:if(typeof t.rel!=`string`||typeof t.href!=`string`||t.href===``||t.onLoad||t.onError)break;switch(t.rel){case`stylesheet`:return e=t.disabled,typeof t.precedence==`string`&&e==null;default:return!0}case`script`:if(t.async&&typeof t.async!=`function`&&typeof t.async!=`symbol`&&!t.onLoad&&!t.onError&&t.src&&typeof t.src==`string`)return!0}return!1}function Gf(e){return!(e.type===`stylesheet`&&!(e.state.loading&3))}function Kf(e,t,n,r){if(n.type===`stylesheet`&&(typeof r.media!=`string`||!1!==matchMedia(r.media).matches)&&!(n.state.loading&4)){if(n.instance===null){var i=jf(r.href),a=t.querySelector(Mf(i));if(a){t=a._p,typeof t==`object`&&t&&typeof t.then==`function`&&(e.count++,e=Yf.bind(e),t.then(e,e)),n.state.loading|=4,n.instance=a,Tt(a);return}a=t.ownerDocument||t,r=Nf(r),(i=hf.get(i))&&zf(r,i),a=a.createElement(`link`),Tt(a);var o=a;o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),Fd(a,`link`,r),n.instance=a}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(n,t),(t=n.state.preload)&&!(n.state.loading&3)&&(e.count++,n=Yf.bind(e),t.addEventListener(`load`,n),t.addEventListener(`error`,n))}}var qf=0;function Jf(e,t){return e.stylesheets&&e.count===0&&Zf(e,e.stylesheets),0qf?50:800)+t);return e.unsuspend=n,function(){e.unsuspend=null,clearTimeout(r),clearTimeout(i)}}:null}function Yf(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Zf(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var Xf=null;function Zf(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,Xf=new Map,t.forEach(Qf,e),Xf=null,Yf.call(e))}function Qf(e,t){if(!(t.state.loading&4)){var n=Xf.get(e);if(n)var r=n.get(null);else{n=new Map,Xf.set(e,n);for(var i=e.querySelectorAll(`link[data-precedence],style[data-precedence]`),a=0;a{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=g()})),v=class{constructor(){this.listeners=new Set,this.subscribe=this.subscribe.bind(this)}subscribe(e){return this.listeners.add(e),this.onSubscribe(),()=>{this.listeners.delete(e),this.onUnsubscribe()}}hasListeners(){return this.listeners.size>0}onSubscribe(){}onUnsubscribe(){}};function y(e,t){if(t.has(e))throw TypeError(`Cannot initialize the same private elements twice on an object`)}var b=o((()=>{}));function x(e,t,n){y(e,t),t.set(e,n)}var S=o((()=>{b()}));function C(e,t,n){if(typeof e==`function`?e===t:e.has(t))return arguments.length<3?t:n;throw TypeError(`Private element is not present on this object`)}var w=o((()=>{}));function T(e,t,n){return e.set(C(e,t),n),n}var E=o((()=>{w()}));function D(e,t){return e.get(C(e,t))}var O=o((()=>{w()}));S(),E(),O();var ee,te,ne,re=new(ee=new WeakMap,te=new WeakMap,ne=new WeakMap,class extends v{constructor(){super(),x(this,ee,void 0),x(this,te,void 0),x(this,ne,void 0),T(ne,this,e=>{if(typeof window<`u`&&window.addEventListener){let t=()=>e();return window.addEventListener(`visibilitychange`,t,!1),()=>{window.removeEventListener(`visibilitychange`,t)}}})}onSubscribe(){D(te,this)||this.setEventListener(D(ne,this))}onUnsubscribe(){this.hasListeners()||(D(te,this)?.call(this),T(te,this,void 0))}setEventListener(e){T(ne,this,e),D(te,this)?.call(this),T(te,this,e(e=>{typeof e==`boolean`?this.setFocused(e):this.onFocus()}))}setFocused(e){D(ee,this)!==e&&(T(ee,this,e),this.onFocus())}onFocus(){let e=this.isFocused();this.listeners.forEach(t=>{t(e)})}isFocused(){return typeof D(ee,this)==`boolean`?D(ee,this):globalThis.document?.visibilityState!==`hidden`}});S(),O(),E();var ie,ae,oe={setTimeout:(e,t)=>setTimeout(e,t),clearTimeout:e=>clearTimeout(e),setInterval:(e,t)=>setInterval(e,t),clearInterval:e=>clearInterval(e)},k=new(ie=new WeakMap,ae=new WeakMap,class{constructor(){x(this,ie,oe),x(this,ae,!1)}setTimeoutProvider(e){T(ie,this,e)}setTimeout(e,t){return D(ie,this).setTimeout(e,t)}clearTimeout(e){D(ie,this).clearTimeout(e)}setInterval(e,t){return D(ie,this).setInterval(e,t)}clearInterval(e){D(ie,this).clearInterval(e)}});function A(e){setTimeout(e,0)}var se=typeof window>`u`||`Deno`in globalThis;function ce(){}function le(e,t){return typeof e==`function`?e(t):e}function ue(e){return typeof e==`number`&&e>=0&&e!==1/0}function de(e,t){return Math.max(e+(t||0)-Date.now(),0)}function j(e,t){return typeof e==`function`?e(t):e}function fe(e,t){return typeof e==`function`?e(t):e}function pe(e,t){let{type:n=`all`,exact:r,fetchStatus:i,predicate:a,queryKey:o,stale:s}=e;if(o){if(r){if(t.queryHash!==he(o,t.options))return!1}else if(!_e(t.queryKey,o))return!1}if(n!==`all`){let e=t.isActive();if(n===`active`&&!e||n===`inactive`&&e)return!1}return!(typeof s==`boolean`&&t.isStale()!==s||i&&i!==t.state.fetchStatus||a&&!a(t))}function me(e,t){let{exact:n,status:r,predicate:i,mutationKey:a}=e;if(a){if(!t.options.mutationKey)return!1;if(n){if(ge(t.options.mutationKey)!==ge(a))return!1}else if(!_e(t.options.mutationKey,a))return!1}return!(r&&t.state.status!==r||i&&!i(t))}function he(e,t){return(t?.queryKeyHashFn||ge)(e)}function ge(e){return JSON.stringify(e,(e,t)=>Se(t)?Object.keys(t).sort().reduce((e,n)=>(e[n]=t[n],e),{}):t)}function _e(e,t){return e===t?!0:typeof e==typeof t&&e&&t&&typeof e==`object`&&typeof t==`object`?Object.keys(t).every(n=>_e(e[n],t[n])):!1}var ve=Object.prototype.hasOwnProperty;function ye(e,t,n=0){if(e===t)return e;if(n>500)return t;let r=xe(e)&&xe(t);if(!r&&!(Se(e)&&Se(t)))return t;let i=(r?e:Object.keys(e)).length,a=r?t:Object.keys(t),o=a.length,s=r?Array(o):{},c=0;for(let l=0;l{k.setTimeout(t,e)})}function Te(e,t,n){return typeof n.structuralSharing==`function`?n.structuralSharing(e,t):n.structuralSharing===!1?t:ye(e,t)}function Ee(e,t,n=0){let r=[...e,t];return n&&r.length>n?r.slice(1):r}function De(e,t,n=0){let r=[t,...e];return n&&r.length>n?r.slice(0,-1):r}var Oe=Symbol();function ke(e,t){return!e.queryFn&&t?.initialPromise?()=>t.initialPromise:!e.queryFn||e.queryFn===Oe?()=>Promise.reject(Error(`Missing queryFn: '${e.queryHash}'`)):e.queryFn}function Ae(e,t){return typeof e==`function`?e(...t):!!e}function je(e,t,n){let r=!1,i;return Object.defineProperty(e,"signal",{enumerable:!0,get:()=>(i??(i=t()),r?i:(r=!0,i.aborted?n():i.addEventListener(`abort`,n,{once:!0}),i))}),e}var Me=(()=>{let e=()=>se;return{isServer(){return e()},setIsServer(t){e=t}}})();function Ne(){let e,t,n=new Promise((n,r)=>{e=n,t=r});n.status=`pending`,n.catch(()=>{});function r(e){Object.assign(n,e),delete n.resolve,delete n.reject}return n.resolve=t=>{r({status:`fulfilled`,value:t}),e(t)},n.reject=e=>{r({status:`rejected`,reason:e}),t(e)},n}var Pe=A;function Fe(){let e=[],t=0,n=e=>{e()},r=e=>{e()},i=Pe,a=r=>{t?e.push(r):i(()=>{n(r)})},o=()=>{let t=e;e=[],t.length&&i(()=>{r(()=>{t.forEach(e=>{n(e)})})})};return{batch:e=>{let n;t++;try{n=e()}finally{t--,t||o()}return n},batchCalls:e=>(...t)=>{a(()=>{e(...t)})},schedule:a,setNotifyFunction:e=>{n=e},setBatchNotifyFunction:e=>{r=e},setScheduler:e=>{i=e}}}var Ie=Fe();S(),E(),O();var Le,Re,ze,Be=new(Le=new WeakMap,Re=new WeakMap,ze=new WeakMap,class extends v{constructor(){super(),x(this,Le,!0),x(this,Re,void 0),x(this,ze,void 0),T(ze,this,e=>{if(typeof window<`u`&&window.addEventListener){let t=()=>e(!0),n=()=>e(!1);return window.addEventListener(`online`,t,!1),window.addEventListener(`offline`,n,!1),()=>{window.removeEventListener(`online`,t),window.removeEventListener(`offline`,n)}}})}onSubscribe(){D(Re,this)||this.setEventListener(D(ze,this))}onUnsubscribe(){this.hasListeners()||(D(Re,this)?.call(this),T(Re,this,void 0))}setEventListener(e){T(ze,this,e),D(Re,this)?.call(this),T(Re,this,e(this.setOnline.bind(this)))}setOnline(e){D(Le,this)!==e&&(T(Le,this,e),this.listeners.forEach(t=>{t(e)}))}isOnline(){return D(Le,this)}});function Ve(e){return Math.min(1e3*2**e,3e4)}function He(e){return(e??`online`)===`online`?Be.isOnline():!0}var Ue=class extends Error{constructor(e){super(`CancelledError`),this.revert=e?.revert,this.silent=e?.silent}};function We(e){let t=!1,n=0,r,i=Ne(),a=()=>i.status!==`pending`,o=t=>{if(!a()){let n=new Ue(t);f(n),e.onCancel?.(n)}},s=()=>{t=!0},c=()=>{t=!1},l=()=>re.isFocused()&&(e.networkMode===`always`||Be.isOnline())&&e.canRun(),u=()=>He(e.networkMode)&&e.canRun(),d=e=>{a()||(r?.(),i.resolve(e))},f=e=>{a()||(r?.(),i.reject(e))},p=()=>new Promise(t=>{r=e=>{(a()||l())&&t(e)},e.onPause?.()}).then(()=>{r=void 0,a()||e.onContinue?.()}),m=()=>{if(a())return;let r,i=n===0?e.initialPromise:void 0;try{r=i??e.fn()}catch(e){r=Promise.reject(e)}Promise.resolve(r).then(d).catch(r=>{if(a())return;let i=e.retry??(Me.isServer()?0:3),o=e.retryDelay??Ve,s=typeof o==`function`?o(n,r):o,c=i===!0||typeof i==`number`&&nl()?void 0:p()).then(()=>{t?f(r):m()})})};return{promise:i,status:()=>i.status,cancel:o,continue:()=>(r?.(),i),cancelRetry:s,continueRetry:c,canStart:u,start:()=>(u()?m():p().then(m),i)}}S(),E(),O();var Ge,Ke=(Ge=new WeakMap,class{constructor(){x(this,Ge,void 0)}destroy(){this.clearGcTimeout()}scheduleGc(){this.clearGcTimeout(),ue(this.gcTime)&&T(Ge,this,k.setTimeout(()=>{this.optionalRemove()},this.gcTime))}updateGcTime(e){this.gcTime=Math.max(this.gcTime||0,e??(Me.isServer()?1/0:300*1e3))}clearGcTimeout(){D(Ge,this)&&(k.clearTimeout(D(Ge,this)),T(Ge,this,void 0))}});function qe(e,t){y(e,t),t.add(e)}var Je=o((()=>{b()}));Je(),S(),E(),O(),w();var Ye,Xe,Ze,Qe,$e,et,tt,nt,rt=(Ye=new WeakMap,Xe=new WeakMap,Ze=new WeakMap,Qe=new WeakMap,$e=new WeakMap,et=new WeakMap,tt=new WeakMap,nt=new WeakSet,class extends Ke{constructor(e){super(),qe(this,nt),x(this,Ye,void 0),x(this,Xe,void 0),x(this,Ze,void 0),x(this,Qe,void 0),x(this,$e,void 0),x(this,et,void 0),x(this,tt,void 0),T(tt,this,!1),T(et,this,e.defaultOptions),this.setOptions(e.options),this.observers=[],T(Qe,this,e.client),T(Ze,this,D(Qe,this).getQueryCache()),this.queryKey=e.queryKey,this.queryHash=e.queryHash,T(Ye,this,ct(this.options)),this.state=e.state??D(Ye,this),this.scheduleGc()}get meta(){return this.options.meta}get promise(){return D($e,this)?.promise}setOptions(e){if(this.options={...D(et,this),...e},this.updateGcTime(this.options.gcTime),this.state&&this.state.data===void 0){let e=ct(this.options);e.data!==void 0&&(this.setState(st(e.data,e.dataUpdatedAt)),T(Ye,this,e))}}optionalRemove(){!this.observers.length&&this.state.fetchStatus===`idle`&&D(Ze,this).remove(this)}setData(e,t){let n=Te(this.state.data,e,this.options);return C(nt,this,at).call(this,{data:n,type:`success`,dataUpdatedAt:t?.updatedAt,manual:t?.manual}),n}setState(e,t){C(nt,this,at).call(this,{type:`setState`,state:e,setStateOptions:t})}cancel(e){let t=D($e,this)?.promise;return D($e,this)?.cancel(e),t?t.then(ce).catch(ce):Promise.resolve()}destroy(){super.destroy(),this.cancel({silent:!0})}get resetState(){return D(Ye,this)}reset(){this.destroy(),this.setState(this.resetState)}isActive(){return this.observers.some(e=>fe(e.options.enabled,this)!==!1)}isDisabled(){return this.getObserversCount()>0?!this.isActive():this.options.queryFn===Oe||!this.isFetched()}isFetched(){return this.state.dataUpdateCount+this.state.errorUpdateCount>0}isStatic(){return this.getObserversCount()>0?this.observers.some(e=>j(e.options.staleTime,this)===`static`):!1}isStale(){return this.getObserversCount()>0?this.observers.some(e=>e.getCurrentResult().isStale):this.state.data===void 0||this.state.isInvalidated}isStaleByTime(e=0){return this.state.data===void 0?!0:e===`static`?!1:this.state.isInvalidated?!0:!de(this.state.dataUpdatedAt,e)}onFocus(){this.observers.find(e=>e.shouldFetchOnWindowFocus())?.refetch({cancelRefetch:!1}),D($e,this)?.continue()}onOnline(){this.observers.find(e=>e.shouldFetchOnReconnect())?.refetch({cancelRefetch:!1}),D($e,this)?.continue()}addObserver(e){this.observers.includes(e)||(this.observers.push(e),this.clearGcTimeout(),D(Ze,this).notify({type:`observerAdded`,query:this,observer:e}))}removeObserver(e){this.observers.includes(e)&&(this.observers=this.observers.filter(t=>t!==e),this.observers.length||(D($e,this)&&(D(tt,this)||C(nt,this,it).call(this)?D($e,this).cancel({revert:!0}):D($e,this).cancelRetry()),this.scheduleGc()),D(Ze,this).notify({type:`observerRemoved`,query:this,observer:e}))}getObserversCount(){return this.observers.length}invalidate(){this.state.isInvalidated||C(nt,this,at).call(this,{type:`invalidate`})}async fetch(e,t){if(this.state.fetchStatus!==`idle`&&D($e,this)?.status()!==`rejected`){if(this.state.data!==void 0&&t?.cancelRefetch)this.cancel({silent:!0});else if(D($e,this))return D($e,this).continueRetry(),D($e,this).promise}if(e&&this.setOptions(e),!this.options.queryFn){let e=this.observers.find(e=>e.options.queryFn);e&&this.setOptions(e.options)}let n=new AbortController,r=e=>{Object.defineProperty(e,"signal",{enumerable:!0,get:()=>(T(tt,this,!0),n.signal)})},i=()=>{let e=ke(this.options,t),n=(()=>{let e={client:D(Qe,this),queryKey:this.queryKey,meta:this.meta};return r(e),e})();return T(tt,this,!1),this.options.persister?this.options.persister(e,n,this):e(n)},a=(()=>{let e={fetchOptions:t,options:this.options,queryKey:this.queryKey,client:D(Qe,this),state:this.state,fetchFn:i};return r(e),e})();this.options.behavior?.onFetch(a,this),T(Xe,this,this.state),(this.state.fetchStatus===`idle`||this.state.fetchMeta!==a.fetchOptions?.meta)&&C(nt,this,at).call(this,{type:`fetch`,meta:a.fetchOptions?.meta}),T($e,this,We({initialPromise:t?.initialPromise,fn:a.fetchFn,onCancel:e=>{e instanceof Ue&&e.revert&&this.setState({...D(Xe,this),fetchStatus:`idle`}),n.abort()},onFail:(e,t)=>{C(nt,this,at).call(this,{type:`failed`,failureCount:e,error:t})},onPause:()=>{C(nt,this,at).call(this,{type:`pause`})},onContinue:()=>{C(nt,this,at).call(this,{type:`continue`})},retry:a.options.retry,retryDelay:a.options.retryDelay,networkMode:a.options.networkMode,canRun:()=>!0}));try{let e=await D($e,this).start();if(e===void 0)throw Error(`${this.queryHash} data is undefined`);return this.setData(e),D(Ze,this).config.onSuccess?.(e,this),D(Ze,this).config.onSettled?.(e,this.state.error,this),e}catch(e){if(e instanceof Ue){if(e.silent)return D($e,this).promise;if(e.revert){if(this.state.data===void 0)throw e;return this.state.data}}throw C(nt,this,at).call(this,{type:`error`,error:e}),D(Ze,this).config.onError?.(e,this),D(Ze,this).config.onSettled?.(this.state.data,e,this),e}finally{this.scheduleGc()}}});function it(){return this.state.fetchStatus===`paused`&&this.state.status===`pending`}function at(e){let t=t=>{switch(e.type){case`failed`:return{...t,fetchFailureCount:e.failureCount,fetchFailureReason:e.error};case`pause`:return{...t,fetchStatus:`paused`};case`continue`:return{...t,fetchStatus:`fetching`};case`fetch`:return{...t,...ot(t.data,this.options),fetchMeta:e.meta??null};case`success`:let n={...t,...st(e.data,e.dataUpdatedAt),dataUpdateCount:t.dataUpdateCount+1,...!e.manual&&{fetchStatus:`idle`,fetchFailureCount:0,fetchFailureReason:null}};return T(Xe,this,e.manual?n:void 0),n;case`error`:let r=e.error;return{...t,error:r,errorUpdateCount:t.errorUpdateCount+1,errorUpdatedAt:Date.now(),fetchFailureCount:t.fetchFailureCount+1,fetchFailureReason:r,fetchStatus:`idle`,status:`error`,isInvalidated:!0};case`invalidate`:return{...t,isInvalidated:!0};case`setState`:return{...t,...e.state}}};this.state=t(this.state),Ie.batch(()=>{this.observers.forEach(e=>{e.onQueryUpdate()}),D(Ze,this).notify({query:this,type:`updated`,action:e})})}function ot(e,t){return{fetchFailureCount:0,fetchFailureReason:null,fetchStatus:He(t.networkMode)?`fetching`:`paused`,...e===void 0&&{error:null,status:`pending`}}}function st(e,t){return{data:e,dataUpdatedAt:t??Date.now(),error:null,isInvalidated:!1,status:`success`}}function ct(e){let t=typeof e.initialData==`function`?e.initialData():e.initialData,n=t!==void 0,r=n?typeof e.initialDataUpdatedAt==`function`?e.initialDataUpdatedAt():e.initialDataUpdatedAt:0;return{data:t,dataUpdateCount:0,dataUpdatedAt:n?r??Date.now():0,error:null,errorUpdateCount:0,errorUpdatedAt:0,fetchFailureCount:0,fetchFailureReason:null,fetchMeta:null,isInvalidated:!1,status:n?`success`:`pending`,fetchStatus:`idle`}}Je(),S(),E(),O(),w();var lt,M,ut,dt,ft,pt,mt,ht,gt,_t,vt,yt,bt,xt,St,Ct,wt=(lt=new WeakMap,M=new WeakMap,ut=new WeakMap,dt=new WeakMap,ft=new WeakMap,pt=new WeakMap,mt=new WeakMap,ht=new WeakMap,gt=new WeakMap,_t=new WeakMap,vt=new WeakMap,yt=new WeakMap,bt=new WeakMap,xt=new WeakMap,St=new WeakMap,Ct=new WeakSet,class extends v{constructor(e,t){super(),qe(this,Ct),x(this,lt,void 0),x(this,M,void 0),x(this,ut,void 0),x(this,dt,void 0),x(this,ft,void 0),x(this,pt,void 0),x(this,mt,void 0),x(this,ht,void 0),x(this,gt,void 0),x(this,_t,void 0),x(this,vt,void 0),x(this,yt,void 0),x(this,bt,void 0),x(this,xt,void 0),x(this,St,new Set),this.options=t,T(lt,this,e),T(ht,this,null),T(mt,this,Ne()),this.bindMethods(),this.setOptions(t)}bindMethods(){this.refetch=this.refetch.bind(this)}onSubscribe(){this.listeners.size===1&&(D(M,this).addObserver(this),Ft(D(M,this),this.options)?C(Ct,this,Tt).call(this):this.updateResult(),C(Ct,this,kt).call(this))}onUnsubscribe(){this.hasListeners()||this.destroy()}shouldFetchOnReconnect(){return It(D(M,this),this.options,this.options.refetchOnReconnect)}shouldFetchOnWindowFocus(){return It(D(M,this),this.options,this.options.refetchOnWindowFocus)}destroy(){this.listeners=new Set,C(Ct,this,At).call(this),C(Ct,this,jt).call(this),D(M,this).removeObserver(this)}setOptions(e){let t=this.options,n=D(M,this);if(this.options=D(lt,this).defaultQueryOptions(e),this.options.enabled!==void 0&&typeof this.options.enabled!=`boolean`&&typeof this.options.enabled!=`function`&&typeof fe(this.options.enabled,D(M,this))!=`boolean`)throw Error(`Expected enabled to be a boolean or a callback that returns a boolean`);C(Ct,this,Mt).call(this),D(M,this).setOptions(this.options),t._defaulted&&!be(this.options,t)&&D(lt,this).getQueryCache().notify({type:`observerOptionsUpdated`,query:D(M,this),observer:this});let r=this.hasListeners();r&&Lt(D(M,this),n,this.options,t)&&C(Ct,this,Tt).call(this),this.updateResult(),r&&(D(M,this)!==n||fe(this.options.enabled,D(M,this))!==fe(t.enabled,D(M,this))||j(this.options.staleTime,D(M,this))!==j(t.staleTime,D(M,this)))&&C(Ct,this,Et).call(this);let i=C(Ct,this,Dt).call(this);r&&(D(M,this)!==n||fe(this.options.enabled,D(M,this))!==fe(t.enabled,D(M,this))||i!==D(xt,this))&&C(Ct,this,Ot).call(this,i)}getOptimisticResult(e){let t=D(lt,this).getQueryCache().build(D(lt,this),e),n=this.createResult(t,e);return zt(this,n)&&(T(dt,this,n),T(pt,this,this.options),T(ft,this,D(M,this).state)),n}getCurrentResult(){return D(dt,this)}trackResult(e,t){return new Proxy(e,{get:(e,n)=>(this.trackProp(n),t?.(n),n===`promise`&&(this.trackProp(`data`),!this.options.experimental_prefetchInRender&&D(mt,this).status===`pending`&&D(mt,this).reject(Error(`experimental_prefetchInRender feature flag is not enabled`))),Reflect.get(e,n))})}trackProp(e){D(St,this).add(e)}getCurrentQuery(){return D(M,this)}refetch({...e}={}){return this.fetch({...e})}fetchOptimistic(e){let t=D(lt,this).defaultQueryOptions(e),n=D(lt,this).getQueryCache().build(D(lt,this),t);return n.fetch().then(()=>this.createResult(n,t))}fetch(e){return C(Ct,this,Tt).call(this,{...e,cancelRefetch:e.cancelRefetch??!0}).then(()=>(this.updateResult(),D(dt,this)))}createResult(e,t){let n=D(M,this),r=this.options,i=D(dt,this),a=D(ft,this),o=D(pt,this),s=e===n?D(ut,this):e.state,{state:c}=e,l={...c},u=!1,d;if(t._optimisticResults){let i=this.hasListeners(),a=!i&&Ft(e,t),o=i&&Lt(e,n,t,r);(a||o)&&(l={...l,...ot(c.data,e.options)}),t._optimisticResults===`isRestoring`&&(l.fetchStatus=`idle`)}let{error:f,errorUpdatedAt:p,status:m}=l;d=l.data;let h=!1;if(t.placeholderData!==void 0&&d===void 0&&m===`pending`){let e;i?.isPlaceholderData&&t.placeholderData===o?.placeholderData?(e=i.data,h=!0):e=typeof t.placeholderData==`function`?t.placeholderData(D(vt,this)?.state.data,D(vt,this)):t.placeholderData,e!==void 0&&(m=`success`,d=Te(i?.data,e,t),u=!0)}if(t.select&&d!==void 0&&!h)if(i&&d===a?.data&&t.select===D(gt,this))d=D(_t,this);else try{T(gt,this,t.select),d=t.select(d),d=Te(i?.data,d,t),T(_t,this,d),T(ht,this,null)}catch(e){T(ht,this,e)}D(ht,this)&&(f=D(ht,this),d=D(_t,this),p=Date.now(),m=`error`);let g=l.fetchStatus===`fetching`,_=m===`pending`,v=m===`error`,y=_&&g,b=d!==void 0,x={status:m,fetchStatus:l.fetchStatus,isPending:_,isSuccess:m===`success`,isError:v,isInitialLoading:y,isLoading:y,data:d,dataUpdatedAt:l.dataUpdatedAt,error:f,errorUpdatedAt:p,failureCount:l.fetchFailureCount,failureReason:l.fetchFailureReason,errorUpdateCount:l.errorUpdateCount,isFetched:e.isFetched(),isFetchedAfterMount:l.dataUpdateCount>s.dataUpdateCount||l.errorUpdateCount>s.errorUpdateCount,isFetching:g,isRefetching:g&&!_,isLoadingError:v&&!b,isPaused:l.fetchStatus===`paused`,isPlaceholderData:u,isRefetchError:v&&b,isStale:Rt(e,t),refetch:this.refetch,promise:D(mt,this),isEnabled:fe(t.enabled,e)!==!1};if(this.options.experimental_prefetchInRender){let t=x.data!==void 0,r=x.status===`error`&&!t,i=e=>{r?e.reject(x.error):t&&e.resolve(x.data)},a=()=>{i(T(mt,this,x.promise=Ne()))},o=D(mt,this);switch(o.status){case`pending`:e.queryHash===n.queryHash&&i(o);break;case`fulfilled`:(r||x.data!==o.value)&&a();break;case`rejected`:(!r||x.error!==o.reason)&&a();break}}return x}updateResult(){let e=D(dt,this),t=this.createResult(D(M,this),this.options);T(ft,this,D(M,this).state),T(pt,this,this.options),D(ft,this).data!==void 0&&T(vt,this,D(M,this)),!be(t,e)&&(T(dt,this,t),C(Ct,this,Nt).call(this,{listeners:(()=>{if(!e)return!0;let{notifyOnChangeProps:t}=this.options,n=typeof t==`function`?t():t;if(n===`all`||!n&&!D(St,this).size)return!0;let r=new Set(n??D(St,this));return this.options.throwOnError&&r.add(`error`),Object.keys(D(dt,this)).some(t=>{let n=t;return D(dt,this)[n]!==e[n]&&r.has(n)})})()}))}onQueryUpdate(){this.updateResult(),this.hasListeners()&&C(Ct,this,kt).call(this)}});function Tt(e){C(Ct,this,Mt).call(this);let t=D(M,this).fetch(this.options,e);return e?.throwOnError||(t=t.catch(ce)),t}function Et(){C(Ct,this,At).call(this);let e=j(this.options.staleTime,D(M,this));if(Me.isServer()||D(dt,this).isStale||!ue(e))return;let t=de(D(dt,this).dataUpdatedAt,e)+1;T(yt,this,k.setTimeout(()=>{D(dt,this).isStale||this.updateResult()},t))}function Dt(){return(typeof this.options.refetchInterval==`function`?this.options.refetchInterval(D(M,this)):this.options.refetchInterval)??!1}function Ot(e){C(Ct,this,jt).call(this),T(xt,this,e),!(Me.isServer()||fe(this.options.enabled,D(M,this))===!1||!ue(D(xt,this))||D(xt,this)===0)&&T(bt,this,k.setInterval(()=>{(this.options.refetchIntervalInBackground||re.isFocused())&&C(Ct,this,Tt).call(this)},D(xt,this)))}function kt(){C(Ct,this,Et).call(this),C(Ct,this,Ot).call(this,C(Ct,this,Dt).call(this))}function At(){D(yt,this)&&(k.clearTimeout(D(yt,this)),T(yt,this,void 0))}function jt(){D(bt,this)&&(k.clearInterval(D(bt,this)),T(bt,this,void 0))}function Mt(){let e=D(lt,this).getQueryCache().build(D(lt,this),this.options);if(e===D(M,this))return;let t=D(M,this);T(M,this,e),T(ut,this,e.state),this.hasListeners()&&(t?.removeObserver(this),e.addObserver(this))}function Nt(e){Ie.batch(()=>{e.listeners&&this.listeners.forEach(e=>{e(D(dt,this))}),D(lt,this).getQueryCache().notify({query:D(M,this),type:`observerResultsUpdated`})})}function Pt(e,t){return fe(t.enabled,e)!==!1&&e.state.data===void 0&&!(e.state.status===`error`&&t.retryOnMount===!1)}function Ft(e,t){return Pt(e,t)||e.state.data!==void 0&&It(e,t,t.refetchOnMount)}function It(e,t,n){if(fe(t.enabled,e)!==!1&&j(t.staleTime,e)!==`static`){let r=typeof n==`function`?n(e):n;return r===`always`||r!==!1&&Rt(e,t)}return!1}function Lt(e,t,n,r){return(e!==t||fe(r.enabled,e)===!1)&&(!n.suspense||e.state.status!==`error`)&&Rt(e,n)}function Rt(e,t){return fe(t.enabled,e)!==!1&&e.isStaleByTime(j(t.staleTime,e))}function zt(e,t){return!be(e.getCurrentResult(),t)}function Bt(e){return{onFetch:(t,n)=>{let r=t.options,i=t.fetchOptions?.meta?.fetchMore?.direction,a=t.state.data?.pages||[],o=t.state.data?.pageParams||[],s={pages:[],pageParams:[]},c=0,l=async()=>{let n=!1,l=e=>{je(e,()=>t.signal,()=>n=!0)},u=ke(t.options,t.fetchOptions),d=async(e,r,i)=>{if(n)return Promise.reject();if(r==null&&e.pages.length)return Promise.resolve(e);let a=await u((()=>{let e={client:t.client,queryKey:t.queryKey,pageParam:r,direction:i?`backward`:`forward`,meta:t.options.meta};return l(e),e})()),{maxPages:o}=t.options,s=i?De:Ee;return{pages:s(e.pages,a,o),pageParams:s(e.pageParams,r,o)}};if(i&&a.length){let e=i===`backward`,t=e?Ht:Vt,n={pages:a,pageParams:o};s=await d(n,t(r,n),e)}else{let t=e??a.length;do{let e=c===0?o[0]??r.initialPageParam:Vt(r,s);if(c>0&&e==null)break;s=await d(s,e),c++}while(ct.options.persister?.(l,{client:t.client,queryKey:t.queryKey,meta:t.options.meta,signal:t.signal},n):t.fetchFn=l}}}function Vt(e,{pages:t,pageParams:n}){let r=t.length-1;return t.length>0?e.getNextPageParam(t[r],t,n[r],n):void 0}function Ht(e,{pages:t,pageParams:n}){return t.length>0?e.getPreviousPageParam?.(t[0],t,n[0],n):void 0}Je(),S(),E(),O(),w();var Ut,Wt,Gt,Kt,qt,Jt=(Ut=new WeakMap,Wt=new WeakMap,Gt=new WeakMap,Kt=new WeakMap,qt=new WeakSet,class extends Ke{constructor(e){super(),qe(this,qt),x(this,Ut,void 0),x(this,Wt,void 0),x(this,Gt,void 0),x(this,Kt,void 0),T(Ut,this,e.client),this.mutationId=e.mutationId,T(Gt,this,e.mutationCache),T(Wt,this,[]),this.state=e.state||Xt(),this.setOptions(e.options),this.scheduleGc()}setOptions(e){this.options=e,this.updateGcTime(this.options.gcTime)}get meta(){return this.options.meta}addObserver(e){D(Wt,this).includes(e)||(D(Wt,this).push(e),this.clearGcTimeout(),D(Gt,this).notify({type:`observerAdded`,mutation:this,observer:e}))}removeObserver(e){T(Wt,this,D(Wt,this).filter(t=>t!==e)),this.scheduleGc(),D(Gt,this).notify({type:`observerRemoved`,mutation:this,observer:e})}optionalRemove(){D(Wt,this).length||(this.state.status===`pending`?this.scheduleGc():D(Gt,this).remove(this))}continue(){return D(Kt,this)?.continue()??this.execute(this.state.variables)}async execute(e){let t=()=>{C(qt,this,Yt).call(this,{type:`continue`})},n={client:D(Ut,this),meta:this.options.meta,mutationKey:this.options.mutationKey};T(Kt,this,We({fn:()=>this.options.mutationFn?this.options.mutationFn(e,n):Promise.reject(Error(`No mutationFn found`)),onFail:(e,t)=>{C(qt,this,Yt).call(this,{type:`failed`,failureCount:e,error:t})},onPause:()=>{C(qt,this,Yt).call(this,{type:`pause`})},onContinue:t,retry:this.options.retry??0,retryDelay:this.options.retryDelay,networkMode:this.options.networkMode,canRun:()=>D(Gt,this).canRun(this)}));let r=this.state.status===`pending`,i=!D(Kt,this).canStart();try{if(r)t();else{C(qt,this,Yt).call(this,{type:`pending`,variables:e,isPaused:i}),D(Gt,this).config.onMutate&&await D(Gt,this).config.onMutate(e,this,n);let t=await this.options.onMutate?.(e,n);t!==this.state.context&&C(qt,this,Yt).call(this,{type:`pending`,context:t,variables:e,isPaused:i})}let a=await D(Kt,this).start();return await D(Gt,this).config.onSuccess?.(a,e,this.state.context,this,n),await this.options.onSuccess?.(a,e,this.state.context,n),await D(Gt,this).config.onSettled?.(a,null,this.state.variables,this.state.context,this,n),await this.options.onSettled?.(a,null,e,this.state.context,n),C(qt,this,Yt).call(this,{type:`success`,data:a}),a}catch(t){try{await D(Gt,this).config.onError?.(t,e,this.state.context,this,n)}catch(e){Promise.reject(e)}try{await this.options.onError?.(t,e,this.state.context,n)}catch(e){Promise.reject(e)}try{await D(Gt,this).config.onSettled?.(void 0,t,this.state.variables,this.state.context,this,n)}catch(e){Promise.reject(e)}try{await this.options.onSettled?.(void 0,t,e,this.state.context,n)}catch(e){Promise.reject(e)}throw C(qt,this,Yt).call(this,{type:`error`,error:t}),t}finally{D(Gt,this).runNext(this)}}});function Yt(e){let t=t=>{switch(e.type){case`failed`:return{...t,failureCount:e.failureCount,failureReason:e.error};case`pause`:return{...t,isPaused:!0};case`continue`:return{...t,isPaused:!1};case`pending`:return{...t,context:e.context,data:void 0,failureCount:0,failureReason:null,error:null,isPaused:e.isPaused,status:`pending`,variables:e.variables,submittedAt:Date.now()};case`success`:return{...t,data:e.data,failureCount:0,failureReason:null,error:null,status:`success`,isPaused:!1};case`error`:return{...t,data:void 0,error:e.error,failureCount:t.failureCount+1,failureReason:e.error,isPaused:!1,status:`error`}}};this.state=t(this.state),Ie.batch(()=>{D(Wt,this).forEach(t=>{t.onMutationUpdate(e)}),D(Gt,this).notify({mutation:this,type:`updated`,action:e})})}function Xt(){return{context:void 0,data:void 0,error:null,failureCount:0,failureReason:null,isPaused:!1,status:`idle`,variables:void 0,submittedAt:0}}S(),E(),O();var Zt,Qt,$t,en=(Zt=new WeakMap,Qt=new WeakMap,$t=new WeakMap,class extends v{constructor(e={}){super(),x(this,Zt,void 0),x(this,Qt,void 0),x(this,$t,void 0),this.config=e,T(Zt,this,new Set),T(Qt,this,new Map),T($t,this,0)}build(e,t,n){var r;let i=new Jt({client:e,mutationCache:this,mutationId:T($t,this,(r=D($t,this),++r)),options:e.defaultMutationOptions(t),state:n});return this.add(i),i}add(e){D(Zt,this).add(e);let t=tn(e);if(typeof t==`string`){let n=D(Qt,this).get(t);n?n.push(e):D(Qt,this).set(t,[e])}this.notify({type:`added`,mutation:e})}remove(e){if(D(Zt,this).delete(e)){let t=tn(e);if(typeof t==`string`){let n=D(Qt,this).get(t);if(n)if(n.length>1){let t=n.indexOf(e);t!==-1&&n.splice(t,1)}else n[0]===e&&D(Qt,this).delete(t)}}this.notify({type:`removed`,mutation:e})}canRun(e){let t=tn(e);if(typeof t==`string`){let n=D(Qt,this).get(t)?.find(e=>e.state.status===`pending`);return!n||n===e}else return!0}runNext(e){let t=tn(e);return typeof t==`string`?(D(Qt,this).get(t)?.find(t=>t!==e&&t.state.isPaused))?.continue()??Promise.resolve():Promise.resolve()}clear(){Ie.batch(()=>{D(Zt,this).forEach(e=>{this.notify({type:`removed`,mutation:e})}),D(Zt,this).clear(),D(Qt,this).clear()})}getAll(){return Array.from(D(Zt,this))}find(e){let t={exact:!0,...e};return this.getAll().find(e=>me(t,e))}findAll(e={}){return this.getAll().filter(t=>me(e,t))}notify(e){Ie.batch(()=>{this.listeners.forEach(t=>{t(e)})})}resumePausedMutations(){let e=this.getAll().filter(e=>e.state.isPaused);return Ie.batch(()=>Promise.all(e.map(e=>e.continue().catch(ce))))}});function tn(e){return e.options.scope?.id}Je(),S(),E(),w(),O();var nn,rn,an,on,sn,cn=(nn=new WeakMap,rn=new WeakMap,an=new WeakMap,on=new WeakMap,sn=new WeakSet,class extends v{constructor(e,t){super(),qe(this,sn),x(this,nn,void 0),x(this,rn,void 0),x(this,an,void 0),x(this,on,void 0),T(nn,this,e),this.setOptions(t),this.bindMethods(),C(sn,this,ln).call(this)}bindMethods(){this.mutate=this.mutate.bind(this),this.reset=this.reset.bind(this)}setOptions(e){let t=this.options;this.options=D(nn,this).defaultMutationOptions(e),be(this.options,t)||D(nn,this).getMutationCache().notify({type:`observerOptionsUpdated`,mutation:D(an,this),observer:this}),t?.mutationKey&&this.options.mutationKey&&ge(t.mutationKey)!==ge(this.options.mutationKey)?this.reset():D(an,this)?.state.status===`pending`&&D(an,this).setOptions(this.options)}onUnsubscribe(){this.hasListeners()||D(an,this)?.removeObserver(this)}onMutationUpdate(e){C(sn,this,ln).call(this),C(sn,this,un).call(this,e)}getCurrentResult(){return D(rn,this)}reset(){D(an,this)?.removeObserver(this),T(an,this,void 0),C(sn,this,ln).call(this),C(sn,this,un).call(this)}mutate(e,t){return T(on,this,t),D(an,this)?.removeObserver(this),T(an,this,D(nn,this).getMutationCache().build(D(nn,this),this.options)),D(an,this).addObserver(this),D(an,this).execute(e)}});function ln(){let e=D(an,this)?.state??Xt();T(rn,this,{...e,isPending:e.status===`pending`,isSuccess:e.status===`success`,isError:e.status===`error`,isIdle:e.status===`idle`,mutate:this.mutate,reset:this.reset})}function un(e){Ie.batch(()=>{if(D(on,this)&&this.hasListeners()){let t=D(rn,this).variables,n=D(rn,this).context,r={client:D(nn,this),meta:this.options.meta,mutationKey:this.options.mutationKey};if(e?.type===`success`){try{D(on,this).onSuccess?.(e.data,t,n,r)}catch(e){Promise.reject(e)}try{D(on,this).onSettled?.(e.data,null,t,n,r)}catch(e){Promise.reject(e)}}else if(e?.type===`error`){try{D(on,this).onError?.(e.error,t,n,r)}catch(e){Promise.reject(e)}try{D(on,this).onSettled?.(void 0,e.error,t,n,r)}catch(e){Promise.reject(e)}}}this.listeners.forEach(e=>{e(D(rn,this))})})}S(),E(),O();var dn,fn=(dn=new WeakMap,class extends v{constructor(e={}){super(),x(this,dn,void 0),this.config=e,T(dn,this,new Map)}build(e,t,n){let r=t.queryKey,i=t.queryHash??he(r,t),a=this.get(i);return a||(a=new rt({client:e,queryKey:r,queryHash:i,options:e.defaultQueryOptions(t),state:n,defaultOptions:e.getQueryDefaults(r)}),this.add(a)),a}add(e){D(dn,this).has(e.queryHash)||(D(dn,this).set(e.queryHash,e),this.notify({type:`added`,query:e}))}remove(e){let t=D(dn,this).get(e.queryHash);t&&(e.destroy(),t===e&&D(dn,this).delete(e.queryHash),this.notify({type:`removed`,query:e}))}clear(){Ie.batch(()=>{this.getAll().forEach(e=>{this.remove(e)})})}get(e){return D(dn,this).get(e)}getAll(){return[...D(dn,this).values()]}find(e){let t={exact:!0,...e};return this.getAll().find(e=>pe(t,e))}findAll(e={}){let t=this.getAll();return Object.keys(e).length>0?t.filter(t=>pe(e,t)):t}notify(e){Ie.batch(()=>{this.listeners.forEach(t=>{t(e)})})}onFocus(){Ie.batch(()=>{this.getAll().forEach(e=>{e.onFocus()})})}onOnline(){Ie.batch(()=>{this.getAll().forEach(e=>{e.onOnline()})})}});S(),E(),O();var pn,mn,hn,gn,_n,vn,yn,bn,xn=(pn=new WeakMap,mn=new WeakMap,hn=new WeakMap,gn=new WeakMap,_n=new WeakMap,vn=new WeakMap,yn=new WeakMap,bn=new WeakMap,class{constructor(e={}){x(this,pn,void 0),x(this,mn,void 0),x(this,hn,void 0),x(this,gn,void 0),x(this,_n,void 0),x(this,vn,void 0),x(this,yn,void 0),x(this,bn,void 0),T(pn,this,e.queryCache||new fn),T(mn,this,e.mutationCache||new en),T(hn,this,e.defaultOptions||{}),T(gn,this,new Map),T(_n,this,new Map),T(vn,this,0)}mount(){var e;T(vn,this,(e=D(vn,this),e++,e)),D(vn,this)===1&&(T(yn,this,re.subscribe(async e=>{e&&(await this.resumePausedMutations(),D(pn,this).onFocus())})),T(bn,this,Be.subscribe(async e=>{e&&(await this.resumePausedMutations(),D(pn,this).onOnline())})))}unmount(){var e;T(vn,this,(e=D(vn,this),e--,e)),D(vn,this)===0&&(D(yn,this)?.call(this),T(yn,this,void 0),D(bn,this)?.call(this),T(bn,this,void 0))}isFetching(e){return D(pn,this).findAll({...e,fetchStatus:`fetching`}).length}isMutating(e){return D(mn,this).findAll({...e,status:`pending`}).length}getQueryData(e){let t=this.defaultQueryOptions({queryKey:e});return D(pn,this).get(t.queryHash)?.state.data}ensureQueryData(e){let t=this.defaultQueryOptions(e),n=D(pn,this).build(this,t),r=n.state.data;return r===void 0?this.fetchQuery(e):(e.revalidateIfStale&&n.isStaleByTime(j(t.staleTime,n))&&this.prefetchQuery(t),Promise.resolve(r))}getQueriesData(e){return D(pn,this).findAll(e).map(({queryKey:e,state:t})=>[e,t.data])}setQueryData(e,t,n){let r=this.defaultQueryOptions({queryKey:e}),i=D(pn,this).get(r.queryHash)?.state.data,a=le(t,i);if(a!==void 0)return D(pn,this).build(this,r).setData(a,{...n,manual:!0})}setQueriesData(e,t,n){return Ie.batch(()=>D(pn,this).findAll(e).map(({queryKey:e})=>[e,this.setQueryData(e,t,n)]))}getQueryState(e){let t=this.defaultQueryOptions({queryKey:e});return D(pn,this).get(t.queryHash)?.state}removeQueries(e){let t=D(pn,this);Ie.batch(()=>{t.findAll(e).forEach(e=>{t.remove(e)})})}resetQueries(e,t){let n=D(pn,this);return Ie.batch(()=>(n.findAll(e).forEach(e=>{e.reset()}),this.refetchQueries({type:`active`,...e},t)))}cancelQueries(e,t={}){let n={revert:!0,...t},r=Ie.batch(()=>D(pn,this).findAll(e).map(e=>e.cancel(n)));return Promise.all(r).then(ce).catch(ce)}invalidateQueries(e,t={}){return Ie.batch(()=>(D(pn,this).findAll(e).forEach(e=>{e.invalidate()}),e?.refetchType===`none`?Promise.resolve():this.refetchQueries({...e,type:e?.refetchType??e?.type??`active`},t)))}refetchQueries(e,t={}){let n={...t,cancelRefetch:t.cancelRefetch??!0},r=Ie.batch(()=>D(pn,this).findAll(e).filter(e=>!e.isDisabled()&&!e.isStatic()).map(e=>{let t=e.fetch(void 0,n);return n.throwOnError||(t=t.catch(ce)),e.state.fetchStatus===`paused`?Promise.resolve():t}));return Promise.all(r).then(ce)}fetchQuery(e){let t=this.defaultQueryOptions(e);t.retry===void 0&&(t.retry=!1);let n=D(pn,this).build(this,t);return n.isStaleByTime(j(t.staleTime,n))?n.fetch(t):Promise.resolve(n.state.data)}prefetchQuery(e){return this.fetchQuery(e).then(ce).catch(ce)}fetchInfiniteQuery(e){return e.behavior=Bt(e.pages),this.fetchQuery(e)}prefetchInfiniteQuery(e){return this.fetchInfiniteQuery(e).then(ce).catch(ce)}ensureInfiniteQueryData(e){return e.behavior=Bt(e.pages),this.ensureQueryData(e)}resumePausedMutations(){return Be.isOnline()?D(mn,this).resumePausedMutations():Promise.resolve()}getQueryCache(){return D(pn,this)}getMutationCache(){return D(mn,this)}getDefaultOptions(){return D(hn,this)}setDefaultOptions(e){T(hn,this,e)}setQueryDefaults(e,t){D(gn,this).set(ge(e),{queryKey:e,defaultOptions:t})}getQueryDefaults(e){let t=[...D(gn,this).values()],n={};return t.forEach(t=>{_e(e,t.queryKey)&&Object.assign(n,t.defaultOptions)}),n}setMutationDefaults(e,t){D(_n,this).set(ge(e),{mutationKey:e,defaultOptions:t})}getMutationDefaults(e){let t=[...D(_n,this).values()],n={};return t.forEach(t=>{_e(e,t.mutationKey)&&Object.assign(n,t.defaultOptions)}),n}defaultQueryOptions(e){if(e._defaulted)return e;let t={...D(hn,this).queries,...this.getQueryDefaults(e.queryKey),...e,_defaulted:!0};return t.queryHash||(t.queryHash=he(t.queryKey,t)),t.refetchOnReconnect===void 0&&(t.refetchOnReconnect=t.networkMode!==`always`),t.throwOnError===void 0&&(t.throwOnError=!!t.suspense),!t.networkMode&&t.persister&&(t.networkMode=`offlineFirst`),t.queryFn===Oe&&(t.enabled=!1),t}defaultMutationOptions(e){return e?._defaulted?e:{...D(hn,this).mutations,...e?.mutationKey&&this.getMutationDefaults(e.mutationKey),...e,_defaulted:!0}}clear(){D(pn,this).clear(),D(mn,this).clear()}}),Sn=s((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),Cn=s(((e,t)=>{t.exports=Sn()})),N=l(d(),1),P=Cn(),wn=N.createContext(void 0),F=e=>{let t=N.useContext(wn);if(e)return e;if(!t)throw Error(`No QueryClient set, use QueryClientProvider to set one`);return t},Tn=({client:e,children:t})=>(N.useEffect(()=>(e.mount(),()=>{e.unmount()}),[e]),(0,P.jsx)(wn.Provider,{value:e,children:t})),En=N.createContext(!1),Dn=()=>N.useContext(En);En.Provider;function On(){let e=!1;return{clearReset:()=>{e=!1},reset:()=>{e=!0},isReset:()=>e}}var kn=N.createContext(On()),An=()=>N.useContext(kn),jn=(e,t,n)=>{let r=n?.state.error&&typeof e.throwOnError==`function`?Ae(e.throwOnError,[n.state.error,n]):e.throwOnError;(e.suspense||e.experimental_prefetchInRender||r)&&(t.isReset()||(e.retryOnMount=!1))},Mn=e=>{N.useEffect(()=>{e.clearReset()},[e])},Nn=({result:e,errorResetBoundary:t,throwOnError:n,query:r,suspense:i})=>e.isError&&!t.isReset()&&!e.isFetching&&r&&(i&&e.data===void 0||Ae(n,[e.error,r])),Pn=e=>{if(e.suspense){let t=1e3,n=e=>e===`static`?e:Math.max(e??t,t),r=e.staleTime;e.staleTime=typeof r==`function`?(...e)=>n(r(...e)):n(r),typeof e.gcTime==`number`&&(e.gcTime=Math.max(e.gcTime,t))}},Fn=(e,t)=>e.isLoading&&e.isFetching&&!t,In=(e,t)=>e?.suspense&&t.isPending,Ln=(e,t,n)=>t.fetchOptimistic(e).catch(()=>{n.clearReset()});function Rn(e,t,n){let r=Dn(),i=An(),a=F(n),o=a.defaultQueryOptions(e);a.getDefaultOptions().queries?._experimental_beforeQuery?.(o);let s=a.getQueryCache().get(o.queryHash);o._optimisticResults=r?`isRestoring`:`optimistic`,Pn(o),jn(o,i,s),Mn(i);let c=!a.getQueryCache().get(o.queryHash),[l]=N.useState(()=>new t(a,o)),u=l.getOptimisticResult(o),d=!r&&e.subscribed!==!1;if(N.useSyncExternalStore(N.useCallback(e=>{let t=d?l.subscribe(Ie.batchCalls(e)):ce;return l.updateResult(),t},[l,d]),()=>l.getCurrentResult(),()=>l.getCurrentResult()),N.useEffect(()=>{l.setOptions(o)},[o,l]),In(o,u))throw Ln(o,l,i);if(Nn({result:u,errorResetBoundary:i,throwOnError:o.throwOnError,query:s,suspense:o.suspense}))throw u.error;return a.getDefaultOptions().queries?._experimental_afterQuery?.(o,u),o.experimental_prefetchInRender&&!Me.isServer()&&Fn(u,r)&&(c?Ln(o,l,i):s?.promise)?.catch(ce).finally(()=>{l.updateResult()}),o.notifyOnChangeProps?u:l.trackResult(u)}function zn(e,t){return Rn(e,wt,t)}function Bn(e,t){let n=F(t),[r]=N.useState(()=>new cn(n,e));N.useEffect(()=>{r.setOptions(e)},[r,e]);let i=N.useSyncExternalStore(N.useCallback(e=>r.subscribe(Ie.batchCalls(e)),[r]),()=>r.getCurrentResult(),()=>r.getCurrentResult()),a=N.useCallback((e,t)=>{r.mutate(e,t).catch(ce)},[r]);if(i.error&&Ae(r.options.throwOnError,[i.error]))throw i.error;return{...i,mutate:a,mutateAsync:i.mutate}}var Vn=l(h()),Hn=_();function Un(){return Un=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0&&(t.hash=e.substr(n),e=e.substr(0,n));let r=e.indexOf(`?`);r>=0&&(t.search=e.substr(r),e=e.substr(0,r)),e&&(t.pathname=e)}return t}function er(e,t,n,r){r===void 0&&(r={});let{window:i=document.defaultView,v5Compat:a=!1}=r,o=i.history,s=Wn.Pop,c=null,l=u();l??(l=0,o.replaceState(Un({},o.state,{idx:l}),``));function u(){return(o.state||{idx:null}).idx}function d(){s=Wn.Pop;let e=u(),t=e==null?null:e-l;l=e,c&&c({action:s,location:h.location,delta:t})}function f(e,t){s=Wn.Push;let r=Zn(h.location,e,t);n&&n(r,e),l=u()+1;let d=Xn(r,l),f=h.createHref(r);try{o.pushState(d,``,f)}catch(e){if(e instanceof DOMException&&e.name===`DataCloneError`)throw e;i.location.assign(f)}a&&c&&c({action:s,location:h.location,delta:1})}function p(e,t){s=Wn.Replace;let r=Zn(h.location,e,t);n&&n(r,e),l=u();let i=Xn(r,l),d=h.createHref(r);o.replaceState(i,``,d),a&&c&&c({action:s,location:h.location,delta:0})}function m(e){let t=i.location.origin===`null`?i.location.href:i.location.origin,n=typeof e==`string`?e:Qn(e);return n=n.replace(/ $/,`%20`),qn(t,`No window.location.(origin|href) available to create URL for href: `+n),new URL(n,t)}let h={get action(){return s},get location(){return e(i,o)},listen(e){if(c)throw Error(`A history only accepts one active listener`);return i.addEventListener(Gn,d),c=e,()=>{i.removeEventListener(Gn,d),c=null}},createHref(e){return t(i,e)},createURL:m,encodeLocation(e){let t=m(e);return{pathname:t.pathname,search:t.search,hash:t.hash}},push:f,replace:p,go(e){return o.go(e)}};return h}var tr;(function(e){e.data=`data`,e.deferred=`deferred`,e.redirect=`redirect`,e.error=`error`})(tr||(tr={}));function nr(e,t,n){return n===void 0&&(n=`/`),rr(e,t,n,!1)}function rr(e,t,n,r){let i=br((typeof t==`string`?$n(t):t).pathname||`/`,n);if(i==null)return null;let a=ir(e);or(a);let o=null,s=yr(i);for(let e=0;o==null&&e{let o={relativePath:a===void 0?e.path||``:a,caseSensitive:e.caseSensitive===!0,childrenIndex:i,route:e};o.relativePath.startsWith(`/`)&&(qn(o.relativePath.startsWith(r),`Absolute route path "`+o.relativePath+`" nested under path `+(`"`+r+`" is not valid. An absolute child route path `)+`must start with the combined path of all its parent routes.`),o.relativePath=o.relativePath.slice(r.length));let s=Ar([r,o.relativePath]),c=n.concat(o);e.children&&e.children.length>0&&(qn(e.index!==!0,`Index routes must not have child routes. Please remove `+(`all child routes from route path "`+s+`".`)),ir(e.children,t,c,s)),!(e.path==null&&!e.index)&&t.push({path:s,score:mr(s,e.index),routesMeta:c})};return e.forEach((e,t)=>{var n;if(e.path===``||!((n=e.path)!=null&&n.includes(`?`)))i(e,t);else for(let n of ar(e.path))i(e,t,n)}),t}function ar(e){let t=e.split(`/`);if(t.length===0)return[];let[n,...r]=t,i=n.endsWith(`?`),a=n.replace(/\?$/,``);if(r.length===0)return i?[a,``]:[a];let o=ar(r.join(`/`)),s=[];return s.push(...o.map(e=>e===``?a:[a,e].join(`/`))),i&&s.push(...o),s.map(t=>e.startsWith(`/`)&&t===``?`/`:t)}function or(e){e.sort((e,t)=>e.score===t.score?hr(e.routesMeta.map(e=>e.childrenIndex),t.routesMeta.map(e=>e.childrenIndex)):t.score-e.score)}var sr=/^:[\w-]+$/,cr=3,lr=2,ur=1,dr=10,fr=-2,pr=e=>e===`*`;function mr(e,t){let n=e.split(`/`),r=n.length;return n.some(pr)&&(r+=fr),t&&(r+=lr),n.filter(e=>!pr(e)).reduce((e,t)=>e+(sr.test(t)?cr:t===``?ur:dr),r)}function hr(e,t){return e.length===t.length&&e.slice(0,-1).every((e,n)=>e===t[n])?e[e.length-1]-t[t.length-1]:0}function gr(e,t,n){n===void 0&&(n=!1);let{routesMeta:r}=e,i={},a=`/`,o=[];for(let e=0;e{let{paramName:r,isOptional:i}=t;if(r===`*`){let e=s[n]||``;o=a.slice(0,a.length-e.length).replace(/(.)\/+$/,`$1`)}let c=s[n];return i&&!c?e[r]=void 0:e[r]=(c||``).replace(/%2F/g,`/`),e},{}),pathname:a,pathnameBase:o,pattern:e}}function vr(e,t,n){t===void 0&&(t=!1),n===void 0&&(n=!0),Jn(e===`*`||!e.endsWith(`*`)||e.endsWith(`/*`),`Route path "`+e+`" will be treated as if it were `+(`"`+e.replace(/\*$/,`/*`)+'" because the `*` character must ')+"always follow a `/` in the pattern. To get rid of this warning, "+(`please change the route path to "`+e.replace(/\*$/,`/*`)+`".`));let r=[],i=`^`+e.replace(/\/*\*?$/,``).replace(/^\/*/,`/`).replace(/[\\.*+^${}|()[\]]/g,`\\$&`).replace(/\/:([\w-]+)(\?)?/g,(e,t,n)=>(r.push({paramName:t,isOptional:n!=null}),n?`/?([^\\/]+)?`:`/([^\\/]+)`));return e.endsWith(`*`)?(r.push({paramName:`*`}),i+=e===`*`||e===`/*`?`(.*)$`:`(?:\\/(.+)|\\/*)$`):n?i+=`\\/*$`:e!==``&&e!==`/`&&(i+=`(?:(?=\\/|$))`),[new RegExp(i,t?void 0:`i`),r]}function yr(e){try{return e.split(`/`).map(e=>decodeURIComponent(e).replace(/\//g,`%2F`)).join(`/`)}catch(t){return Jn(!1,`The URL path "`+e+`" could not be decoded because it is is a malformed URL segment. This is probably due to a bad percent `+(`encoding (`+t+`).`)),e}}function br(e,t){if(t===`/`)return e;if(!e.toLowerCase().startsWith(t.toLowerCase()))return null;let n=t.endsWith(`/`)?t.length-1:t.length,r=e.charAt(n);return r&&r!==`/`?null:e.slice(n)||`/`}var xr=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,Sr=e=>xr.test(e);function Cr(e,t){t===void 0&&(t=`/`);let{pathname:n,search:r=``,hash:i=``}=typeof e==`string`?$n(e):e,a;if(n)if(Sr(n))a=n;else{if(n.includes(`//`)){let e=n;n=kr(n),Jn(!1,`Pathnames cannot have embedded double slashes - normalizing `+(e+` -> `+n))}a=n.startsWith(`/`)?wr(n.substring(1),`/`):wr(n,t)}else a=t;return{pathname:a,search:Mr(r),hash:Nr(i)}}function wr(e,t){let n=t.replace(/\/+$/,``).split(`/`);return e.split(`/`).forEach(e=>{e===`..`?n.length>1&&n.pop():e!==`.`&&n.push(e)}),n.length>1?n.join(`/`):`/`}function Tr(e,t,n,r){return`Cannot include a '`+e+`' character in a manually specified `+("`to."+t+"` field ["+JSON.stringify(r)+`]. Please separate it out to the `)+("`to."+n+"` field. Alternatively you may provide the full path as ")+`a string in and the router will parse it for you.`}function Er(e){return e.filter((e,t)=>t===0||e.route.path&&e.route.path.length>0)}function Dr(e,t){let n=Er(e);return t?n.map((e,t)=>t===n.length-1?e.pathname:e.pathnameBase):n.map(e=>e.pathnameBase)}function Or(e,t,n,r){r===void 0&&(r=!1);let i;typeof e==`string`?i=$n(e):(i=Un({},e),qn(!i.pathname||!i.pathname.includes(`?`),Tr(`?`,`pathname`,`search`,i)),qn(!i.pathname||!i.pathname.includes(`#`),Tr(`#`,`pathname`,`hash`,i)),qn(!i.search||!i.search.includes(`#`),Tr(`#`,`search`,`hash`,i)));let a=e===``||i.pathname===``,o=a?`/`:i.pathname,s;if(o==null)s=n;else{let e=t.length-1;if(!r&&o.startsWith(`..`)){let t=o.split(`/`);for(;t[0]===`..`;)t.shift(),--e;i.pathname=t.join(`/`)}s=e>=0?t[e]:`/`}let c=Cr(i,s),l=o&&o!==`/`&&o.endsWith(`/`),u=(a||o===`.`)&&n.endsWith(`/`);return!c.pathname.endsWith(`/`)&&(l||u)&&(c.pathname+=`/`),c}var kr=e=>e.replace(/\/\/+/g,`/`),Ar=e=>kr(e.join(`/`)),jr=e=>e.replace(/\/+$/,``).replace(/^\/*/,`/`),Mr=e=>!e||e===`?`?``:e.startsWith(`?`)?e:`?`+e,Nr=e=>!e||e===`#`?``:e.startsWith(`#`)?e:`#`+e;function Pr(e){return e!=null&&typeof e.status==`number`&&typeof e.statusText==`string`&&typeof e.internal==`boolean`&&`data`in e}var Fr=[`post`,`put`,`patch`,`delete`];new Set(Fr);var Ir=[`get`,...Fr];new Set(Ir);function Lr(){return Lr=Object.assign?Object.assign.bind():function(e){for(var t=1;t{s.current=!0}),N.useCallback(function(n,i){if(i===void 0&&(i={}),!s.current)return;if(typeof n==`number`){r.go(n);return}let c=Or(n,JSON.parse(o),a,i.relative===`path`);e==null&&t!==`/`&&(c.pathname=c.pathname===`/`?t:Ar([t,c.pathname])),(i.replace?r.replace:r.push)(c,i.state,i)},[t,r,o,a,e])}function Xr(e,t){let{relative:n}=t===void 0?{}:t,{future:r}=N.useContext(Br),{matches:i}=N.useContext(Hr),{pathname:a}=Kr(),o=JSON.stringify(Dr(i,r.v7_relativeSplatPath));return N.useMemo(()=>Or(e,JSON.parse(o),a,n===`path`),[e,o,a,n])}function Zr(e,t){return Qr(e,t)}function Qr(e,t,n,r){!Gr()&&qn(!1);let{navigator:i}=N.useContext(Br),{matches:a}=N.useContext(Hr),o=a[a.length-1],s=o?o.params:{};o&&o.pathname;let c=o?o.pathnameBase:`/`;o&&o.route;let l=Kr(),u;if(t){let e=typeof t==`string`?$n(t):t;!(c===`/`||e.pathname?.startsWith(c))&&qn(!1),u=e}else u=l;let d=u.pathname||`/`,f=d;if(c!==`/`){let e=c.replace(/^\//,``).split(`/`);f=`/`+d.replace(/^\//,``).split(`/`).slice(e.length).join(`/`)}let p=nr(e,{pathname:f}),m=ri(p&&p.map(e=>Object.assign({},e,{params:Object.assign({},s,e.params),pathname:Ar([c,i.encodeLocation?i.encodeLocation(e.pathname).pathname:e.pathname]),pathnameBase:e.pathnameBase===`/`?c:Ar([c,i.encodeLocation?i.encodeLocation(e.pathnameBase).pathname:e.pathnameBase])})),a,n,r);return t&&m?N.createElement(Vr.Provider,{value:{location:Lr({pathname:`/`,search:``,hash:``,state:null,key:`default`},u),navigationType:Wn.Pop}},m):m}function $r(){let e=ui(),t=Pr(e)?e.status+` `+e.statusText:e instanceof Error?e.message:JSON.stringify(e),n=e instanceof Error?e.stack:null;return N.createElement(N.Fragment,null,N.createElement(`h2`,null,`Unexpected Application Error!`),N.createElement(`h3`,{style:{fontStyle:`italic`}},t),n?N.createElement(`pre`,{style:{padding:`0.5rem`,backgroundColor:`rgba(200,200,200, 0.5)`}},n):null,null)}var ei=N.createElement($r,null),ti=class extends N.Component{constructor(e){super(e),this.state={location:e.location,revalidation:e.revalidation,error:e.error}}static getDerivedStateFromError(e){return{error:e}}static getDerivedStateFromProps(e,t){return t.location!==e.location||t.revalidation!==`idle`&&e.revalidation===`idle`?{error:e.error,location:e.location,revalidation:e.revalidation}:{error:e.error===void 0?t.error:e.error,location:t.location,revalidation:e.revalidation||t.revalidation}}componentDidCatch(e,t){console.error(`React Router caught the following error during render`,e,t)}render(){return this.state.error===void 0?this.props.children:N.createElement(Hr.Provider,{value:this.props.routeContext},N.createElement(Ur.Provider,{value:this.state.error,children:this.props.component}))}};function ni(e){let{routeContext:t,match:n,children:r}=e,i=N.useContext(Rr);return i&&i.static&&i.staticContext&&(n.route.errorElement||n.route.ErrorBoundary)&&(i.staticContext._deepestRenderedBoundaryId=n.route.id),N.createElement(Hr.Provider,{value:t},r)}function ri(e,t,n,r){if(t===void 0&&(t=[]),n===void 0&&(n=null),r===void 0&&(r=null),e==null){var i;if(!n)return null;if(n.errors)e=n.matches;else if((i=r)!=null&&i.v7_partialHydration&&t.length===0&&!n.initialized&&n.matches.length>0)e=n.matches;else return null}let a=e,o=n?.errors;if(o!=null){let e=a.findIndex(e=>e.route.id&&o?.[e.route.id]!==void 0);!(e>=0)&&qn(!1),a=a.slice(0,Math.min(a.length,e+1))}let s=!1,c=-1;if(n&&r&&r.v7_partialHydration)for(let e=0;e=0?a.slice(0,c+1):[a[0]];break}}}return a.reduceRight((e,r,i)=>{let l,u=!1,d=null,f=null;n&&(l=o&&r.route.id?o[r.route.id]:void 0,d=r.route.errorElement||ei,s&&(c<0&&i===0?(pi(`route-fallback`,!1,"No `HydrateFallback` element provided to render during initial hydration"),u=!0,f=null):c===i&&(u=!0,f=r.route.hydrateFallbackElement||null)));let p=t.concat(a.slice(0,i+1)),m=()=>{let t;return t=l?d:u?f:r.route.Component?N.createElement(r.route.Component,null):r.route.element?r.route.element:e,N.createElement(ni,{match:r,routeContext:{outlet:e,matches:p,isDataRoute:n!=null},children:t})};return n&&(r.route.ErrorBoundary||r.route.errorElement||i===0)?N.createElement(ti,{location:n.location,revalidation:n.revalidation,component:d,error:l,children:m(),routeContext:{outlet:null,matches:p,isDataRoute:!0}}):m()},null)}var ii=function(e){return e.UseBlocker=`useBlocker`,e.UseRevalidator=`useRevalidator`,e.UseNavigateStable=`useNavigate`,e}(ii||{}),ai=function(e){return e.UseBlocker=`useBlocker`,e.UseLoaderData=`useLoaderData`,e.UseActionData=`useActionData`,e.UseRouteError=`useRouteError`,e.UseNavigation=`useNavigation`,e.UseRouteLoaderData=`useRouteLoaderData`,e.UseMatches=`useMatches`,e.UseRevalidator=`useRevalidator`,e.UseNavigateStable=`useNavigate`,e.UseRouteId=`useRouteId`,e}(ai||{});function oi(e){let t=N.useContext(Rr);return!t&&qn(!1),t}function si(e){let t=N.useContext(zr);return!t&&qn(!1),t}function ci(e){let t=N.useContext(Hr);return!t&&qn(!1),t}function li(e){let t=ci(e),n=t.matches[t.matches.length-1];return!n.route.id&&qn(!1),n.route.id}function ui(){let e=N.useContext(Ur),t=si(ai.UseRouteError),n=li(ai.UseRouteError);return e===void 0?t.errors?.[n]:e}function di(){let{router:e}=oi(ii.UseNavigateStable),t=li(ai.UseNavigateStable),n=N.useRef(!1);return qr(()=>{n.current=!0}),N.useCallback(function(r,i){i===void 0&&(i={}),n.current&&(typeof r==`number`?e.navigate(r):e.navigate(r,Lr({fromRouteId:t},i)))},[e,t])}var fi={};function pi(e,t,n){!t&&!fi[e]&&(fi[e]=!0)}var mi=(e,t,n)=>(``+t+("You can use the `"+e+"` future flag to opt-in early. ")+(`For more information, see `+n+`.`),void 0);function hi(e,t){e?.v7_startTransition===void 0&&mi(`v7_startTransition`,"React Router will begin wrapping state updates in `React.startTransition` in v7",`https://reactrouter.com/v6/upgrading/future#v7_starttransition`),e?.v7_relativeSplatPath===void 0&&(!t||t.v7_relativeSplatPath===void 0)&&mi(`v7_relativeSplatPath`,`Relative route resolution within Splat routes is changing in v7`,`https://reactrouter.com/v6/upgrading/future#v7_relativesplatpath`),t&&(t.v7_fetcherPersist===void 0&&mi(`v7_fetcherPersist`,`The persistence behavior of fetchers is changing in v7`,`https://reactrouter.com/v6/upgrading/future#v7_fetcherpersist`),t.v7_normalizeFormMethod===void 0&&mi(`v7_normalizeFormMethod`,"Casing of `formMethod` fields is being normalized to uppercase in v7",`https://reactrouter.com/v6/upgrading/future#v7_normalizeformmethod`),t.v7_partialHydration===void 0&&mi(`v7_partialHydration`,"`RouterProvider` hydration behavior is changing in v7",`https://reactrouter.com/v6/upgrading/future#v7_partialhydration`),t.v7_skipActionErrorRevalidation===void 0&&mi(`v7_skipActionErrorRevalidation`,"The revalidation behavior after 4xx/5xx `action` responses is changing in v7",`https://reactrouter.com/v6/upgrading/future#v7_skipactionerrorrevalidation`))}function gi(e){let{to:t,replace:n,state:r,relative:i}=e;!Gr()&&qn(!1);let{future:a,static:o}=N.useContext(Br),{matches:s}=N.useContext(Hr),{pathname:c}=Kr(),l=Jr(),u=Or(t,Dr(s,a.v7_relativeSplatPath),c,i===`path`),d=JSON.stringify(u);return N.useEffect(()=>l(JSON.parse(d),{replace:n,state:r,relative:i}),[l,d,i,n,r]),null}function _i(e){qn(!1)}function vi(e){let{basename:t=`/`,children:n=null,location:r,navigationType:i=Wn.Pop,navigator:a,static:o=!1,future:s}=e;Gr()&&qn(!1);let c=t.replace(/^\/*/,`/`),l=N.useMemo(()=>({basename:c,navigator:a,static:o,future:Lr({v7_relativeSplatPath:!1},s)}),[c,s,a,o]);typeof r==`string`&&(r=$n(r));let{pathname:u=`/`,search:d=``,hash:f=``,state:p=null,key:m=`default`}=r,h=N.useMemo(()=>{let e=br(u,c);return e==null?null:{location:{pathname:e,search:d,hash:f,state:p,key:m},navigationType:i}},[c,u,d,f,p,m,i]);return h==null?null:N.createElement(Br.Provider,{value:l},N.createElement(Vr.Provider,{children:n,value:h}))}function yi(e){let{children:t,location:n}=e;return Zr(xi(t),n)}var bi=function(e){return e[e.pending=0]=`pending`,e[e.success=1]=`success`,e[e.error=2]=`error`,e}(bi||{});new Promise(()=>{}),N.Component;function xi(e,t){t===void 0&&(t=[]);let n=[];return N.Children.forEach(e,(e,r)=>{if(!N.isValidElement(e))return;let i=[...t,r];if(e.type===N.Fragment){n.push.apply(n,xi(e.props.children,i));return}e.type!==_i&&qn(!1),!(!e.props.index||!e.props.children)&&qn(!1);let a={id:e.props.id||i.join(`-`),caseSensitive:e.props.caseSensitive,element:e.props.element,Component:e.props.Component,index:e.props.index,path:e.props.path,loader:e.props.loader,action:e.props.action,errorElement:e.props.errorElement,ErrorBoundary:e.props.ErrorBoundary,hasErrorBoundary:e.props.ErrorBoundary!=null||e.props.errorElement!=null,shouldRevalidate:e.props.shouldRevalidate,handle:e.props.handle,lazy:e.props.lazy};e.props.children&&(a.children=xi(e.props.children,i)),n.push(a)}),n}function Si(){return Si=Object.assign?Object.assign.bind():function(e){for(var t=1;t{let r=e[n];return t.concat(Array.isArray(r)?r.map(e=>[n,e]):[[n,r]])},[]))}function Di(e,t){let n=Ei(e);return t&&t.forEach((e,r)=>{n.has(r)||t.getAll(r).forEach(e=>{n.append(r,e)})}),n}var Oi=[`onClick`,`relative`,`reloadDocument`,`replace`,`state`,`target`,`to`,`preventScrollReset`,`viewTransition`],ki=[`aria-current`,`caseSensitive`,`className`,`end`,`style`,`to`,`viewTransition`,`children`],Ai=`6`;try{window.__reactRouterVersion=Ai}catch{}var ji=N.createContext({isTransitioning:!1}),Mi=N.startTransition;function Ni(e){let{basename:t,children:n,future:r,window:i}=e,a=N.useRef();a.current??(a.current=Kn({window:i,v5Compat:!0}));let o=a.current,[s,c]=N.useState({action:o.action,location:o.location}),{v7_startTransition:l}=r||{},u=N.useCallback(e=>{l&&Mi?Mi(()=>c(e)):c(e)},[c,l]);return N.useLayoutEffect(()=>o.listen(u),[o,u]),N.useEffect(()=>hi(r),[r]),N.createElement(vi,{basename:t,children:n,location:s.location,navigationType:s.action,navigator:o,future:r})}var Pi=typeof window<`u`&&window.document!==void 0&&window.document.createElement!==void 0,Fi=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,Ii=N.forwardRef(function(e,t){let{onClick:n,relative:r,reloadDocument:i,replace:a,state:o,target:s,to:c,preventScrollReset:l,viewTransition:u}=e,d=Ci(e,Oi),{basename:f}=N.useContext(Br),p,m=!1;if(typeof c==`string`&&Fi.test(c)&&(p=c,Pi))try{let e=new URL(window.location.href),t=c.startsWith(`//`)?new URL(e.protocol+c):new URL(c),n=br(t.pathname,f);t.origin===e.origin&&n!=null?c=n+t.search+t.hash:m=!0}catch{}let h=Wr(c,{relative:r}),g=Bi(c,{replace:a,state:o,target:s,preventScrollReset:l,relative:r,viewTransition:u});function _(e){n&&n(e),e.defaultPrevented||g(e)}return N.createElement(`a`,Si({},d,{href:p||h,onClick:m||i?n:_,ref:t,target:s}))}),Li=N.forwardRef(function(e,t){let{"aria-current":n=`page`,caseSensitive:r=!1,className:i=``,end:a=!1,style:o,to:s,viewTransition:c,children:l}=e,u=Ci(e,ki),d=Xr(s,{relative:u.relative}),f=Kr(),p=N.useContext(zr),{navigator:m,basename:h}=N.useContext(Br),g=p!=null&&Hi(d)&&c===!0,_=m.encodeLocation?m.encodeLocation(d).pathname:d.pathname,v=f.pathname,y=p&&p.navigation&&p.navigation.location?p.navigation.location.pathname:null;r||(v=v.toLowerCase(),y=y?y.toLowerCase():null,_=_.toLowerCase()),y&&h&&(y=br(y,h)||y);let b=_!==`/`&&_.endsWith(`/`)?_.length-1:_.length,x=v===_||!a&&v.startsWith(_)&&v.charAt(b)===`/`,S=y!=null&&(y===_||!a&&y.startsWith(_)&&y.charAt(_.length)===`/`),C={isActive:x,isPending:S,isTransitioning:g},w=x?n:void 0,T;T=typeof i==`function`?i(C):[i,x?`active`:null,S?`pending`:null,g?`transitioning`:null].filter(Boolean).join(` `);let E=typeof o==`function`?o(C):o;return N.createElement(Ii,Si({},u,{"aria-current":w,className:T,ref:t,style:E,to:s,viewTransition:c}),typeof l==`function`?l(C):l)}),I;(function(e){e.UseScrollRestoration=`useScrollRestoration`,e.UseSubmit=`useSubmit`,e.UseSubmitFetcher=`useSubmitFetcher`,e.UseFetcher=`useFetcher`,e.useViewTransitionState=`useViewTransitionState`})(I||(I={}));var Ri;(function(e){e.UseFetcher=`useFetcher`,e.UseFetchers=`useFetchers`,e.UseScrollRestoration=`useScrollRestoration`})(Ri||(Ri={}));function zi(e){let t=N.useContext(Rr);return!t&&qn(!1),t}function Bi(e,t){let{target:n,replace:r,state:i,preventScrollReset:a,relative:o,viewTransition:s}=t===void 0?{}:t,c=Jr(),l=Kr(),u=Xr(e,{relative:o});return N.useCallback(t=>{Ti(t,n)&&(t.preventDefault(),c(e,{replace:r===void 0?Qn(l)===Qn(u):r,state:i,preventScrollReset:a,relative:o,viewTransition:s}))},[l,c,u,r,i,n,e,a,o,s])}function Vi(e){let t=N.useRef(Ei(e)),n=N.useRef(!1),r=Kr(),i=N.useMemo(()=>Di(r.search,n.current?null:t.current),[r.search]),a=Jr();return[i,N.useCallback((e,t)=>{let r=Ei(typeof e==`function`?e(i):e);n.current=!0,a(`?`+r,t)},[a,i])]}function Hi(e,t){t===void 0&&(t={});let n=N.useContext(ji);n??qn(!1);let{basename:r}=zi(I.useViewTransitionState),i=Xr(e,{relative:t.relative});if(!n.isTransitioning)return!1;let a=br(n.currentLocation.pathname,r)||n.currentLocation.pathname,o=br(n.nextLocation.pathname,r)||n.nextLocation.pathname;return _r(i.pathname,o)!=null||_r(i.pathname,a)!=null}var Ui=e=>{let t,n=new Set,r=(e,r)=>{let i=typeof e==`function`?e(t):e;if(!Object.is(i,t)){let e=t;t=r??(typeof i!=`object`||!i)?i:Object.assign({},t,i),n.forEach(n=>n(t,e))}},i=()=>t,a={setState:r,getState:i,getInitialState:()=>o,subscribe:e=>(n.add(e),()=>n.delete(e))},o=t=e(r,i,a);return a},Wi=(e=>e?Ui(e):Ui),Gi=e=>e;function Ki(e,t=Gi){let n=N.useSyncExternalStore(e.subscribe,N.useCallback(()=>t(e.getState()),[e,t]),N.useCallback(()=>t(e.getInitialState()),[e,t]));return N.useDebugValue(n),n}var qi=e=>{let t=Wi(e),n=e=>Ki(t,e);return Object.assign(n,t),n},Ji=(e=>e?qi(e):qi),Yi=`anyllm_admin_token`;function Xi(){try{let e=new URLSearchParams(window.location.search),t=e.get(`token`);if(!t)return null;e.delete(`token`);let n=e.toString(),r=window.location.pathname+(n?`?${n}`:``)+window.location.hash;return window.history.replaceState(null,``,r),t}catch{return null}}function Zi(){let e=Xi();if(e)return Qi(e),e;try{return window.sessionStorage.getItem(Yi)}catch{return null}}function Qi(e){try{window.sessionStorage.setItem(Yi,e)}catch{}}function $i(){try{window.sessionStorage.removeItem(Yi)}catch{}}var ea=Ji(e=>({token:Zi(),login(t){Qi(t),e({token:t})},logout(){$i(),e({token:null})}})),ta=Ji(e=>({status:`disconnected`,lastEvent:null,setStatus:t=>e({status:t}),pushEvent:t=>e({lastEvent:t})})),na=3e4,ra=1e3,L=null,ia=null,aa=0,oa=!1;function sa(){oa=!1,!(L&&(L.readyState===WebSocket.OPEN||L.readyState===WebSocket.CONNECTING))&&la()}function ca(){oa=!0,ia&&clearTimeout(ia),L?.close(),L=null,ta.getState().setStatus(`disconnected`)}function la(){if(oa)return;let e=ea.getState().token;if(!e)return;ta.getState().setStatus(`connecting`);let t=location.protocol===`https:`?`wss:`:`ws:`;L=new WebSocket(`${t}//${location.host}/admin/ws`),L.onopen=()=>{L.send(JSON.stringify({token:e}))},L.onmessage=e=>{let t;try{t=JSON.parse(e.data)}catch{return}if(typeof t==`object`&&t&&`status`in t&&t.status===`authenticated`){aa=0,ta.getState().setStatus(`connected`);return}typeof t==`object`&&t&&`type`in t&&ta.getState().pushEvent(t)},L.onclose=()=>{if(oa)return;ta.getState().setStatus(`disconnected`);let e=Math.min(ra*2**aa,na);aa++,ia=setTimeout(la,e)},L.onerror=()=>{L?.close()}}var ua=5,da=4e3,fa=1,pa=Ji(e=>({toasts:[],push({variant:t,message:n,ttlMs:r}){let i=fa++,a=r===void 0?t===`error`?null:da:r;return e(e=>{let r=[...e.toasts,{id:i,variant:t,message:n,ttlMs:a}];return{toasts:r.length>ua?r.slice(-5):r}}),i},dismiss(t){e(e=>({toasts:e.toasts.filter(e=>e.id!==t)}))},clear(){e({toasts:[]})}}));function ma(e){return pa.getState().push(e)}function ha(){let e=Promise.resolve();return function(t){let n=e.then(t,t);return e=n.catch(()=>void 0),n}}var ga=ha();async function _a(e,t){let n=await e(`/admin/csrf-token`,{headers:{Authorization:`Bearer ${t()}`}});if(!n.ok)throw Error(`Failed to fetch CSRF token`);let r=await n.json();if(typeof r.csrf_token!=`string`)throw Error(`Failed to fetch CSRF token`);return r.csrf_token}function va(e,t,n){return{Authorization:`Bearer ${t}`,"X-CSRF-Token":e,...n?{"Content-Type":n}:{}}}function ya(e){return e.status===204||e.headers.get(`content-length`)===`0`}async function ba(e,t,n,r,i){let a=async a=>i.fetchImpl(t,{method:e,headers:va(a,i.getToken(),r),body:n}),o=await _a(i.fetchImpl,i.getToken),s=await a(o);if(s.status===403&&(o=await _a(i.fetchImpl,i.getToken),s=await a(o)),await i.handleAuthAndErrors(s),!ya(s))return s.json()}function xa(e){"@babel/helpers - typeof";return xa=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},xa(e)}var Sa=o((()=>{}));function Ca(e,t){if(xa(e)!=`object`||!e)return e;var n=e[Symbol.toPrimitive];if(n!==void 0){var r=n.call(e,t||`default`);if(xa(r)!=`object`)return r;throw TypeError(`@@toPrimitive must return a primitive value.`)}return(t===`string`?String:Number)(e)}var wa=o((()=>{Sa()}));function Ta(e){var t=Ca(e,`string`);return xa(t)==`symbol`?t:t+``}var Ea=o((()=>{Sa(),wa()}));function Da(e,t,n){return(t=Ta(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}o((()=>{Ea()}))();function Oa(){return ea.getState().token??``}var ka=class extends Error{constructor(e){super(`Rate limited. Retry in ${e}s.`),Da(this,`retryAfterSeconds`,void 0),this.name=`RateLimitError`,this.retryAfterSeconds=e}};function Aa(e){let t=e.headers.get(`retry-after`);if(!t)return 1;let n=Number(t);if(Number.isFinite(n)&&n>0)return Math.ceil(n);let r=Date.parse(t);return Number.isNaN(r)?1:Math.max(1,Math.ceil((r-Date.now())/1e3))}async function ja(e){if(e.status===401)throw ea.getState().logout(),Error(`Unauthorized`);if(e.status===429){let t=Aa(e);throw ma({variant:`warn`,message:`Admin API rate-limited. Retry in ${t}s.`,ttlMs:t*1e3}),new ka(t)}if(!e.ok){let t=await e.text().catch(()=>e.statusText);throw Error(t||`HTTP ${e.status}`)}}async function Ma(e,t){let n=await fetch(e,{...t,headers:{Authorization:`Bearer ${Oa()}`,...t?.headers??{}}});return await ja(n),n.json()}async function Na(e,t,n,r){return ga(()=>ba(e,t,n,r,{fetchImpl:(e,t)=>fetch(e,t),getToken:Oa,handleAuthAndErrors:ja}))}function R(e,t,n){return Na(e,t,n===void 0?void 0:JSON.stringify(n),n===void 0?void 0:`application/json`)}function Pa(e,t){return Na(`POST`,e,t)}function Fa(e=!0){return zn({queryKey:[`status`],queryFn:()=>Ma(`/admin/api/status`),enabled:e,refetchInterval:1e4})}function Ia(){return zn({queryKey:[`metrics`],queryFn:()=>Ma(`/admin/api/metrics`),refetchInterval:5e3,staleTime:0})}function La(e,t){return zn({queryKey:[`observability`,e,t],queryFn:()=>Ma(`/admin/api/observability/overview?window=${e}&backend=${encodeURIComponent(t)}`),refetchInterval:3e4,staleTime:0})}function Ra(e){let t=new URLSearchParams;return t.set(`limit`,String(e.page_size)),t.set(`offset`,String((e.page-1)*e.page_size)),e.backend&&t.set(`backend`,e.backend),e.status&&t.set(`status`,e.status),e.since&&t.set(`since`,e.since),e.until&&t.set(`until`,e.until),e.model&&t.set(`model`,e.model),zn({queryKey:[`requests`,e],queryFn:()=>Ma(`/admin/api/requests?${t}`),staleTime:1/0})}function za(){return zn({queryKey:[`keys`],queryFn:()=>Ma(`/admin/api/keys`).then(e=>e.keys),staleTime:1/0})}function Ba(){let e=F();return Bn({mutationFn:e=>R(`POST`,`/admin/api/keys`,e),onSuccess:()=>{e.invalidateQueries({queryKey:[`keys`]})}})}function Va(){let e=F();return Bn({mutationFn:({id:e,body:t})=>R(`PUT`,`/admin/api/keys/${e}`,t),onSuccess:()=>{e.invalidateQueries({queryKey:[`keys`]})}})}function Ha(){let e=F();return Bn({mutationFn:e=>R(`DELETE`,`/admin/api/keys/${e}`),onSuccess:()=>{e.invalidateQueries({queryKey:[`keys`]})}})}function Ua(){return zn({queryKey:[`backends`],queryFn:()=>Ma(`/admin/api/backends`).then(e=>e.backends),staleTime:1/0})}function Wa(){return zn({queryKey:[`config`],queryFn:()=>Promise.all([Ma(`/admin/api/config`),Ma(`/admin/api/config/overrides`)]).then(([e,t])=>({...e,entries:t.overrides??[],env:{}})),staleTime:1/0})}function Ga(){let e=F();return Bn({mutationFn:e=>R(`PUT`,`/admin/api/config`,e),onSuccess:()=>{e.invalidateQueries({queryKey:[`config`]}),ma({variant:`success`,message:`Setting saved — applied live, no restart needed`})}})}function Ka(){let e=F();return Bn({mutationFn:e=>R(`DELETE`,`/admin/api/config/overrides/${encodeURIComponent(e)}`),onSuccess:()=>{e.invalidateQueries({queryKey:[`config`]})}})}function qa(){return zn({queryKey:[`optimizer-model`],queryFn:()=>Ma(`/admin/api/optimizer/model`),refetchInterval:e=>e.state.data?.downloading?2e3:!1})}function Ja(){let e=F();return Bn({mutationFn:()=>R(`POST`,`/admin/api/optimizer/model`),onSuccess:()=>{e.invalidateQueries({queryKey:[`optimizer-model`]}),ma({variant:`success`,message:`Model download started`})}})}function Ya(){return zn({queryKey:[`env`],queryFn:()=>Ma(`/admin/api/env`),staleTime:1/0})}function Xa(){return zn({queryKey:[`models`],queryFn:()=>Ma(`/admin/api/models`),staleTime:1/0})}function Za(){let e=F();return Bn({mutationFn:e=>R(`POST`,`/admin/api/models`,e),onSuccess:()=>{e.invalidateQueries({queryKey:[`models`]})}})}function Qa(){let e=F();return Bn({mutationFn:e=>R(`DELETE`,`/admin/api/models/${encodeURIComponent(e)}`),onSuccess:()=>{e.invalidateQueries({queryKey:[`models`]})}})}function $a(){return Bn({mutationFn:e=>R(`POST`,`/admin/api/models/discover`,e)})}function eo(e){return zn({queryKey:[`audit`,e],queryFn:()=>Ma(`/admin/api/audit?limit=${e.page_size}&offset=${(e.page-1)*e.page_size}`),staleTime:1/0})}function to(e){return zn({queryKey:[`traffic`,e],queryFn:()=>Ma(`/admin/api/traffic?window=${e}`),refetchInterval:3e4,staleTime:0})}function no(){return zn({queryKey:[`uptime`],queryFn:()=>Ma(`/admin/api/uptime`),refetchInterval:3e4,staleTime:0})}function ro(){return Bn({mutationFn:e=>{let t=new FormData;return t.append(`file`,e),Pa(`/admin/api/env/import`,t)}})}function io(){return zn({queryKey:[`catalog-providers`],queryFn:()=>Ma(`/admin/api/catalog/providers`).then(e=>e.providers),staleTime:1/0})}function ao(){return zn({queryKey:[`favorites`],queryFn:()=>Ma(`/admin/api/favorites`).then(e=>e.favorites),staleTime:1/0})}function oo(){let e=F();return Bn({mutationFn:({providerId:e,on:t})=>t?R(`POST`,`/admin/api/favorites`,{provider_id:e}):R(`DELETE`,`/admin/api/favorites/${encodeURIComponent(e)}`),onSuccess:()=>{e.invalidateQueries({queryKey:[`favorites`]})}})}function so(){return zn({queryKey:[`managed-backends`],queryFn:()=>Ma(`/admin/api/backends/managed`),staleTime:1/0})}function co(){let e=F();return Bn({mutationFn:e=>R(`POST`,`/admin/api/backends/managed`,e),onSuccess:()=>{e.invalidateQueries({queryKey:[`managed-backends`]}),e.invalidateQueries({queryKey:[`status`]})}})}function lo(){let e=F();return Bn({mutationFn:({name:e,data:t})=>R(`PUT`,`/admin/api/backends/managed/${e}`,t),onSuccess:()=>{e.invalidateQueries({queryKey:[`managed-backends`]})}})}function uo(){let e=F();return Bn({mutationFn:e=>R(`DELETE`,`/admin/api/backends/managed/${e}`),onSuccess:()=>{e.invalidateQueries({queryKey:[`managed-backends`]}),e.invalidateQueries({queryKey:[`status`]})}})}function fo(){return zn({queryKey:[`routes`],queryFn:()=>Ma(`/admin/api/routes`),staleTime:1/0})}function po(){let e=F();return Bn({mutationFn:e=>R(`POST`,`/admin/api/routes`,e),onSuccess:()=>{e.invalidateQueries({queryKey:[`routes`]})}})}function mo(){let e=F();return Bn({mutationFn:({id:e,data:t})=>R(`PUT`,`/admin/api/routes/${e}`,t),onSuccess:()=>{e.invalidateQueries({queryKey:[`routes`]})}})}function z(){let e=F();return Bn({mutationFn:e=>R(`DELETE`,`/admin/api/routes/${e}`),onSuccess:()=>{e.invalidateQueries({queryKey:[`routes`]})}})}function B(e){return zn({queryKey:[`route-providers`,e],queryFn:()=>Ma(`/admin/api/routes/${e}/providers`),enabled:!!e,staleTime:1/0})}function ho(){let e=F();return Bn({mutationFn:({routeId:e,data:t})=>R(`POST`,`/admin/api/routes/${e}/providers`,t),onSuccess:(t,{routeId:n})=>{e.invalidateQueries({queryKey:[`route-providers`,n]}),e.invalidateQueries({queryKey:[`routes`]})}})}function go(){let e=F();return Bn({mutationFn:({routeId:e,providerId:t,data:n})=>R(`PUT`,`/admin/api/routes/${e}/providers/${t}`,n),onSuccess:(t,{routeId:n})=>{e.invalidateQueries({queryKey:[`route-providers`,n]})}})}function _o(){let e=F();return Bn({mutationFn:({routeId:e,providerId:t})=>R(`DELETE`,`/admin/api/routes/${e}/providers/${t}`),onSuccess:(t,{routeId:n})=>{e.invalidateQueries({queryKey:[`route-providers`,n]}),e.invalidateQueries({queryKey:[`routes`]})}})}function vo(){let e=F();return Bn({mutationFn:({routeId:e,data:t})=>R(`PUT`,`/admin/api/routes/${e}/providers/reorder`,t),onSuccess:(t,{routeId:n})=>{e.invalidateQueries({queryKey:[`route-providers`,n]}),e.invalidateQueries({queryKey:[`routes`]})}})}async function yo(){let e=ea.getState().token??``,t=await fetch(`/admin/api/env/export`,{headers:{Authorization:`Bearer ${e}`}});if(!t.ok)throw Error(`Export failed: HTTP ${t.status}`);let n=await t.blob(),r=URL.createObjectURL(n),i=document.createElement(`a`);i.href=r,i.download=`.anyllm.env`,document.body.appendChild(i),i.click(),document.body.removeChild(i),URL.revokeObjectURL(r)}function V(...e){let t=[],n=e=>{e&&(typeof e==`string`||typeof e==`number`?t.push(String(e)):Array.isArray(e)&&e.forEach(n))};return e.forEach(n),t.join(` `)}var bo=(0,N.forwardRef)(({glyph:e=`✦`,solid:t,static:n,className:r,...i},a)=>(0,P.jsx)(`span`,{ref:a,"aria-hidden":`true`,className:V(`pui-sparkle`,!n&&`pui-sparkle--blink`,t&&`pui-sparkle--solid`,r),...i,children:e}));bo.displayName=`Sparkle`;var xo=(0,N.forwardRef)(({as:e,static:t,className:n,children:r,...i},a)=>(0,P.jsx)(e??`span`,{ref:a,className:V(`pui-gradient-text`,!t&&`pui-gradient-text--animate`,n),...i,children:r}));xo.displayName=`GradientText`;var So=(0,N.forwardRef)(({color:e,static:t,className:n,style:r,...i},a)=>(0,P.jsx)(`span`,{ref:a,"aria-hidden":`true`,className:V(`pui-dot`,!t&&`pui-dot--pulse`,n),style:e?{...r,background:e,color:e}:r,...i}));So.displayName=`StatusDot`;var Co=new Set([`flash1`,`flash2`,`flash3`,`glow1`,`glow2`,`glow3`]),wo=(0,N.forwardRef)(({text:e,animation:t=`wave`,color:n=`glow1`,className:r,style:i,...a},o)=>{let s=Co.has(n),c=s?`pui-quest--${n}`:void 0,l=s?void 0:{color:n};if(t===`wave`){let t=[...e];return(0,P.jsx)(`span`,{ref:o,className:V(`pui-quest`,`pui-quest--wave`,c,r),style:{...l,...i},...a,children:t.map((e,t)=>(0,P.jsx)(`span`,{className:`pui-quest__char`,style:{animationDelay:`${-(t+1)*50}ms`},children:e===` `?`\xA0`:e},t))})}let u=t===`scroll`?`pui-quest__scroll`:`pui-quest__slide`;return(0,P.jsx)(`span`,{ref:o,className:V(`pui-quest`,`pui-quest--${t}`,c,r),style:{...l,...i},...a,children:(0,P.jsx)(`span`,{className:u,children:e})})});wo.displayName=`QuestText`;function To({variant:e=`glow`,size:t=`md`,sparkle:n,loading:r,block:i,as:a,className:o,children:s,disabled:c,...l},u){let d=a??`button`;return(0,P.jsxs)(d,{ref:u,className:V(`pui-btn`,`pui-btn--${e}`,t!==`md`&&`pui-btn--${t}`,i&&`pui-btn--block`,o),disabled:d===`button`?c||r:void 0,"aria-busy":r||void 0,...l,children:[r?(0,P.jsx)(`span`,{className:`pui-btn__spinner`,"aria-hidden":!0}):null,(0,P.jsx)(`span`,{children:s}),n?(0,P.jsx)(bo,{}):null]})}var Eo=(0,N.forwardRef)(To),Do=(0,N.forwardRef)(({hideSparkle:e,trailing:t,className:n,children:r,...i},a)=>(0,P.jsxs)(`div`,{ref:a,className:V(`pui-sticky-banner`,n),...i,children:[!e&&(0,P.jsx)(bo,{}),(0,P.jsx)(`span`,{children:r}),t]}));Do.displayName=`StickyBanner`;var Oo=(0,N.forwardRef)(({icon:e,statusColor:t,className:n,children:r,...i},a)=>(0,P.jsxs)(`span`,{ref:a,className:V(`pui-eyebrow`,n),...i,children:[e===!1?null:e??(0,P.jsx)(So,{color:t}),(0,P.jsx)(`span`,{children:r})]}));Oo.displayName=`EyebrowPill`;function ko({words:e,typeMs:t=70,deleteMs:n=32,holdMs:r=1500,loop:i=!0,onWordReached:a}){let[o,s]=(0,N.useState)(``),[c,l]=(0,N.useState)(0),[u,d]=(0,N.useState)(!1),[f,p]=(0,N.useState)(!1),m=(0,N.useRef)({word:o,index:c,isDeleting:u});return m.current={word:o,index:c,isDeleting:u},(0,N.useEffect)(()=>{let o=null,c=!1;if(!e.length)return;let u=()=>{if(c)return;let{word:f,index:h,isDeleting:g}=m.current,_=e[h],v=g?_.slice(0,f.length-1):_.slice(0,f.length+1);if(s(v),!g&&v===_){if(a?.(_,h),!i&&h===e.length-1){p(!0);return}o=setTimeout(()=>{c||(d(!0),o=setTimeout(u,n))},r);return}g&&v===``&&(d(!1),l(t=>(t+1)%e.length)),o=setTimeout(u,g?n:t)};return o=setTimeout(u,t),()=>{c=!0,o&&clearTimeout(o)}},[]),{word:o,index:c,isDeleting:u,isComplete:f}}var Ao=(0,N.forwardRef)(({words:e,typeMs:t,deleteMs:n,holdMs:r,loop:i,onWordReached:a,hideCursor:o,cursor:s,renderWord:c,className:l,...u},d)=>{let{word:f,index:p}=ko({words:e,typeMs:t,deleteMs:n,holdMs:r,loop:i,onWordReached:a});return(0,P.jsxs)(`span`,{ref:d,className:V(`pui-rotator`,l),...u,children:[c?c(f,p):f,!o&&(0,P.jsx)(`span`,{"aria-hidden":`true`,className:V(`pui-rotator__cursor`,s===void 0&&`pui-rotator__cursor--block`,`pui-rotator__cursor--blink`),children:s})]})});Ao.displayName=`Rotator`;var jo=(0,N.forwardRef)(({words:e,intervalMs:t=2200,transitionMs:n=500,direction:r=`up`,gradient:i,className:a,style:o,...s},c)=>{let[l,u]=(0,N.useState)(0);(0,N.useEffect)(()=>{if(!e.length)return;let n=setInterval(()=>u(t=>(t+1)%e.length),t);return()=>clearInterval(n)},[t,e.length]);let d={...o,"--pui-roll-ms":`${n}ms`},f=(l-1+e.length)%e.length;return(0,P.jsxs)(`span`,{ref:c,className:V(`pui-roll`,r===`down`&&`pui-roll--down`,i&&`pui-roll--gradient`,a),style:d,...s,children:[(0,P.jsx)(`span`,{className:`pui-roll__sizer`,"aria-hidden":`true`,children:e[l]}),e.map((e,t)=>(0,P.jsx)(`span`,{className:V(`pui-roll__word`,t===l&&`pui-roll__word--active`,t===f&&l!==f&&`pui-roll__word--past`),"aria-hidden":t===l?void 0:`true`,children:e},t))]})});jo.displayName=`WordRoll`;var Mo=(0,N.forwardRef)(({placeholder:e=`Describe what you want to build…`,defaultValue:t,value:n,onChange:r,onSubmit:i,leading:a,ctaLabel:o=`Generate`,hideCta:s,className:c,...l},u)=>{let d=n!==void 0,[f,p]=(0,N.useState)(t??``),m=d?n:f;return(0,P.jsxs)(`form`,{ref:u,className:V(`pui-prompt`,c),onSubmit:e=>{e.preventDefault(),i?.(m)},...l,children:[a===!1?null:(0,P.jsx)(`span`,{className:`pui-prompt__icon`,children:a??(0,P.jsx)(bo,{})}),(0,P.jsx)(`input`,{className:`pui-prompt__input`,type:`text`,placeholder:e,value:m,onChange:e=>{let t=e.target.value;d||p(t),r?.(t)},autoComplete:`off`}),!s&&(0,P.jsx)(Eo,{type:`submit`,variant:`glow`,sparkle:!0,children:o})]})});Mo.displayName=`PromptHero`;var No=[`GPT-5 Turbo Vision`,`Claude Opus 4.7`,`Gemini 3 Pro`],Po=(0,N.forwardRef)(({value:e,defaultValue:t=``,onChange:n,onSubmit:r,placeholder:i=`Build me a…`,rows:a=3,models:o=No,model:s,defaultModel:c,onModelChange:l,onAddContext:u,onVoice:d,hideAddContext:f,hideModel:p,hideVoice:m,hideSend:h,submitOnCmdEnter:g=!0,toolbarExtras:_,className:v,...y},b)=>{let x=e!==void 0,[S,C]=(0,N.useState)(t),w=x?e:S,T=s!==void 0,[E,D]=(0,N.useState)(c??o[0]??``),O=T?s:E,[ee,te]=(0,N.useState)(!1),ne=(0,N.useRef)(null);(0,N.useEffect)(()=>{if(!ee)return;let e=e=>{var t;(t=ne.current)!=null&&t.contains(e.target)||te(!1)};return document.addEventListener(`mousedown`,e),()=>document.removeEventListener(`mousedown`,e)},[ee]);let re=e=>{x||C(e),n?.(e)},ie=e=>{T||D(e),l?.(e),te(!1)},ae=e=>{e?.preventDefault(),r?.(w,{model:O})},oe=e=>{g&&e.key===`Enter`&&(e.metaKey||e.ctrlKey)&&(e.preventDefault(),ae())};return(0,P.jsxs)(`form`,{ref:b,className:V(`pui-promptbox`,v),onSubmit:ae,...y,children:[(0,P.jsx)(`textarea`,{className:`pui-promptbox__textarea`,value:w,onChange:e=>re(e.target.value),onKeyDown:oe,placeholder:i,rows:a}),(0,P.jsxs)(`div`,{className:`pui-promptbox__toolbar`,children:[!f&&(0,P.jsx)(`button`,{type:`button`,className:`pui-promptbox__iconbtn`,onClick:u,title:`Add context`,"aria-label":`Add context`,children:(0,P.jsx)(Fo,{})}),!p&&o.length>0&&(0,P.jsxs)(`div`,{className:`pui-promptbox__model-wrap`,ref:ne,children:[(0,P.jsxs)(`button`,{type:`button`,className:`pui-promptbox__model`,onClick:()=>te(e=>!e),"aria-expanded":ee,"aria-haspopup":`menu`,children:[(0,P.jsx)(`span`,{children:O}),(0,P.jsx)(Io,{})]}),ee&&(0,P.jsx)(`div`,{className:`pui-promptbox__menu`,role:`menu`,children:o.map(e=>(0,P.jsxs)(`button`,{type:`button`,className:V(`pui-promptbox__menu-item`,e===O&&`pui-promptbox__menu-item--active`),onClick:()=>ie(e),role:`menuitemradio`,"aria-checked":e===O,children:[(0,P.jsx)(`span`,{children:e}),e===O&&(0,P.jsx)(zo,{})]},e))})]}),(0,P.jsx)(`div`,{className:`pui-promptbox__spacer`}),_,!m&&(0,P.jsx)(`button`,{type:`button`,className:`pui-promptbox__iconbtn`,onClick:d,title:`Voice mode`,"aria-label":`Voice mode`,children:(0,P.jsx)(Lo,{})}),!h&&(0,P.jsx)(`button`,{type:`submit`,className:`pui-promptbox__iconbtn pui-promptbox__send`,title:`Send`,"aria-label":`Send`,children:(0,P.jsx)(Ro,{})})]})]})});Po.displayName=`Prompt`;function Fo(){return(0,P.jsx)(`svg`,{viewBox:`0 0 24 24`,width:`14`,height:`14`,fill:`none`,stroke:`currentColor`,strokeWidth:`2.2`,strokeLinecap:`round`,"aria-hidden":`true`,children:(0,P.jsx)(`path`,{d:`M12 5v14M5 12h14`})})}function Io(){return(0,P.jsx)(`svg`,{viewBox:`0 0 24 24`,width:`10`,height:`10`,fill:`none`,stroke:`currentColor`,strokeWidth:`2.5`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,children:(0,P.jsx)(`polyline`,{points:`6 9 12 15 18 9`})})}function Lo(){return(0,P.jsxs)(`svg`,{viewBox:`0 0 24 24`,width:`14`,height:`14`,fill:`none`,stroke:`currentColor`,strokeWidth:`1.9`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,children:[(0,P.jsx)(`rect`,{x:`9`,y:`2`,width:`6`,height:`12`,rx:`3`}),(0,P.jsx)(`path`,{d:`M19 10a7 7 0 0 1-14 0`}),(0,P.jsx)(`line`,{x1:`12`,y1:`19`,x2:`12`,y2:`22`})]})}function Ro(){return(0,P.jsxs)(`svg`,{viewBox:`0 0 24 24`,width:`14`,height:`14`,fill:`none`,stroke:`currentColor`,strokeWidth:`2.4`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,children:[(0,P.jsx)(`line`,{x1:`12`,y1:`19`,x2:`12`,y2:`5`}),(0,P.jsx)(`polyline`,{points:`5 12 12 5 19 12`})]})}function zo(){return(0,P.jsx)(`svg`,{viewBox:`0 0 24 24`,width:`12`,height:`12`,fill:`none`,stroke:`currentColor`,strokeWidth:`2.4`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,children:(0,P.jsx)(`polyline`,{points:`20 6 9 17 4 12`})})}var Bo=` .\`'",:;Il!i><~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$`,Vo=[`#a78bfa`,`#ec4899`,`#67e8f9`,`#fbbf24`];function Ho(e,t,n={}){let{cols:r,rows:i,fontSize:a=11,fontFamily:o=`JetBrains Mono, ui-monospace, monospace`,charRamp:s=Bo,colorful:c=!1,palette:l,baseOpacity:u=1,reactive:d=!0,rippleStrength:f=1.4,rippleRadius:p=6,spotlightOpacity:m,spotlightRadius:h=8,frameMs:g=50}=n,_=(0,N.useMemo)(()=>l??(c?Vo:null),[l,c]);(0,N.useEffect)(()=>{let n=e.current,c=t.current;if(!n||!c)return;let l=n.getContext(`2d`);if(!l)return;let v=0,y=0,b=0,x=0,S=0,C=0,w=new Float32Array,T=1,E={x:-9999,y:-9999},D=()=>{w=new Float32Array(b*x);for(let e=0;e{let e=c.getBoundingClientRect();e.width===0||e.height===0||(T=Math.min(window.devicePixelRatio||1,2),n.width=Math.max(1,Math.floor(e.width*T)),n.height=Math.max(1,Math.floor(e.height*T)),l.setTransform(T,0,0,T,0,0),l.font=`${a}px ${o}`,l.textBaseline=`top`,S=l.measureText(`M`).width||a*.6,C=a*1.15,b=r??Math.max(1,Math.floor(e.width/S)),x=i??Math.max(1,Math.floor(e.height/C)),r!==void 0&&(S=e.width/b),i!==void 0&&(C=e.height/x),D())},ee=e=>{if(e-y=r.left-24&&E.x<=r.right+24&&E.y>=r.top-24&&E.y<=r.bottom+24;l.clearRect(0,0,r.width,r.height);let c=s.length-1,T=typeof m==`number`&&m!==u,D=h*h*2;for(let e=0;e1&&(te=1)}if(te<=.01)continue;let ne=`#c8c8d4`;if(_&&_.length){let r=(n*.1+e*.07+t*.12)%_.length;ne=_[Math.floor(Math.abs(r))%_.length]}l.globalAlpha=te,l.fillStyle=ne,l.fillText(ee,n*S,e*C)}l.globalAlpha=1,v=requestAnimationFrame(ee)},te=e=>{E.x=e.clientX,E.y=e.clientY},ne=new ResizeObserver(O);return ne.observe(c),O(),d&&window.addEventListener(`mousemove`,te,{passive:!0}),v=requestAnimationFrame(ee),()=>{cancelAnimationFrame(v),ne.disconnect(),d&&window.removeEventListener(`mousemove`,te)}},[e,t,r,i,a,o,s,_,u,d,f,p,m,h,g])}var Uo=(0,N.forwardRef)(({variant:e=`panel`,cols:t,rows:n,fontSize:r,fontFamily:i,charRamp:a,colorful:o,palette:s,baseOpacity:c,reactive:l,rippleStrength:u,rippleRadius:d,spotlightOpacity:f,spotlightRadius:p,frameMs:m,className:h,...g},_)=>{let v=(0,N.useRef)(null),y=(0,N.useRef)(null);return Ho(y,v,{cols:t,rows:n,fontSize:r,fontFamily:i,charRamp:a,colorful:o,palette:s,baseOpacity:c,reactive:l,rippleStrength:u,rippleRadius:d,spotlightOpacity:f,spotlightRadius:p,frameMs:m}),(0,P.jsx)(`div`,{ref:e=>{v.current=e,typeof _==`function`?_(e):_&&(_.current=e)},className:V(`pui-ascii`,e===`panel`&&`pui-ascii--panel`,h),"aria-hidden":`true`,...g,children:(0,P.jsx)(`canvas`,{ref:y})})});Uo.displayName=`AsciiHero`;var Wo=`circle(0px at -9999px -9999px)`,Go=(0,N.forwardRef)(({text_default:e,text_reveal:t,pattern:n=`0 1 0 1 `,pattern_size_default:r=14,pattern_size_reveal:i=22,scopeSize:a=320,fontSize:o,fontFamily:s,className:c,style:l,...u},d)=>{let f=(0,N.useRef)(null),p=(0,N.useRef)(null);(0,N.useEffect)(()=>{let e=f.current,t=p.current;if(!e||!t)return;let n=a/2,r=0,i=0,o=0,s=!1,c=()=>{if(r=0,!s)return;s=!1;let e=`circle(${n}px at ${i}px ${o}px)`;t.style.clipPath=e,t.style.webkitClipPath=e},l=t=>{let n=e.getBoundingClientRect();i=t.clientX-n.left,o=t.clientY-n.top,s=!0,r||(r=requestAnimationFrame(c))},u=()=>{r&&(cancelAnimationFrame(r),r=0,s=!1),t.style.clipPath=Wo,t.style.webkitClipPath=Wo};return e.addEventListener(`pointermove`,l),e.addEventListener(`pointerleave`,u),e.addEventListener(`pointercancel`,u),()=>{e.removeEventListener(`pointermove`,l),e.removeEventListener(`pointerleave`,u),e.removeEventListener(`pointercancel`,u),r&&cancelAnimationFrame(r)}},[a]);let m=e=>{f.current=e,typeof d==`function`?d(e):d&&(d.current=e)},h=(0,N.useMemo)(()=>n.repeat(40),[n]),g=(0,N.useMemo)(()=>Array.from({length:40},(e,t)=>t),[]),_=e=>(0,P.jsx)(`div`,{className:`pui-goldeneye__pattern`,style:{fontSize:`${e}px`},children:g.map(e=>(0,P.jsx)(`div`,{className:`pui-goldeneye__pattern-row`,children:h},e))}),v={...s?{"--pui-goldeneye-font":s}:{},...o==null?{}:{"--pui-goldeneye-headline-size":typeof o==`number`?`${o}px`:o}};return(0,P.jsxs)(`div`,{ref:m,className:V(`pui-goldeneye`,c),style:{...v,...l},...u,children:[(0,P.jsxs)(`div`,{className:`pui-goldeneye__base`,children:[_(r),(0,P.jsx)(`div`,{className:`pui-goldeneye__headline`,children:e})]}),(0,P.jsxs)(`div`,{ref:p,className:`pui-goldeneye__scope`,"aria-hidden":`true`,style:{clipPath:Wo,WebkitClipPath:Wo},children:[_(i),(0,P.jsx)(`div`,{className:`pui-goldeneye__headline`,children:t})]})]})});Go.displayName=`Goldeneye`;var Ko=[{color:`rgba(124,58,237,0.45)`,x:20,y:30,size:60},{color:`rgba(236,72,153,0.35)`,x:80,y:25,size:50},{color:`rgba(6,182,212,0.30)`,x:50,y:80,size:50}],qo=(0,N.forwardRef)(({blobs:e=Ko,blur:t=50,static:n,animated:r,repulsion:i=.18,className:a,style:o,...s},c)=>{let l=(0,N.useRef)([]);(0,N.useEffect)(()=>{if(!r)return;let t=e.map(e=>({x:e.x,y:e.y,homeX:e.x,homeY:e.y,size:e.size??50,vx:(Math.random()-.5)*.06,vy:(Math.random()-.5)*.06})),n=0,a=()=>{for(let e=0;e.001){let e=(l-c)/l*i;n.vx+=o/c*e,n.vy+=s/c*e}}n.vx+=(Math.random()-.5)*.012,n.vy+=(Math.random()-.5)*.012,n.x+=n.vx,n.y+=n.vy,n.x<-10&&(n.x=-10,n.vx=Math.abs(n.vx)*.6),n.x>110&&(n.x=110,n.vx=-Math.abs(n.vx)*.6),n.y<-10&&(n.y=-10,n.vy=Math.abs(n.vy)*.6),n.y>110&&(n.y=110,n.vy=-Math.abs(n.vy)*.6);let r=l.current[e];r&&(r.style.left=`${n.x}%`,r.style.top=`${n.y}%`)}n=requestAnimationFrame(a)};return n=requestAnimationFrame(a),()=>cancelAnimationFrame(n)},[r,e,i]);let u={...o,filter:`blur(${t}px) saturate(140%)`};return(0,P.jsx)(`div`,{ref:c,"aria-hidden":`true`,className:V(`pui-aurora`,!n&&!r&&`pui-aurora--drift`,a),style:u,...s,children:e.map((e,t)=>{let n=e.size??50;return(0,P.jsx)(`div`,{ref:e=>{l.current[t]=e},className:`pui-aurora__blob`,style:{position:`absolute`,left:`${e.x}%`,top:`${e.y}%`,width:`${n}%`,height:`${n}%`,background:`radial-gradient(circle at center, ${e.color} 0%, transparent 70%)`,transform:`translate(-50%, -50%)`,pointerEvents:`none`,borderRadius:`50%`}},t)})})});qo.displayName=`Aurora`;var Jo=(0,N.forwardRef)(({density:e=70,speed:t=.4,linkDistance:n=140,colors:r=[`#a78bfa`,`#f0abfc`,`#67e8f9`],linkColor:i=`#7c3aed`,hoverDistance:a=200,hoverGravity:o=.005,hoverBrighten:s=.8,baseOpacity:c=.45,overscan:l=80,className:u,...d},f)=>{let p=(0,N.useRef)(null),m=(0,N.useRef)(null);return(0,N.useEffect)(()=>{let u=p.current,d=m.current,f=d.getContext(`2d`);if(!f)return;let h=0,g=0,_=1,v={x:-9999,y:-9999},y=0,b=[],x=()=>{let n=-l,i=h+l,a=-l,o=g+l;b=Array.from({length:e},()=>({x:n+Math.random()*(i-n),y:a+Math.random()*(o-a),vx:(Math.random()-.5)*t*2,vy:(Math.random()-.5)*t*2,r:1+Math.random()*1.6,color:r[Math.floor(Math.random()*r.length)]}))},S=()=>{let e=u.getBoundingClientRect();_=Math.min(window.devicePixelRatio||1,2),h=e.width,g=e.height,d.width=h*_,d.height=g*_,d.style.width=`${h}px`,d.style.height=`${g}px`,f.setTransform(_,0,0,_,0,0),x()},C=()=>{f.clearRect(0,0,h,g);let e=-l,t=h+l,r=-l,u=g+l,d=v.x>-9e3;for(let n of b)if(n.x+=n.vx,n.y+=n.vy,(n.xt)&&(n.vx*=-1),(n.yu)&&(n.vy*=-1),a>0&&o>0&&d){let e=v.x-n.x,t=v.y-n.y,r=Math.hypot(e,t);if(r{if(!d||a<=0||s<=0)return 0;let n=Math.hypot(v.x-e,v.y-t);return n>=a?0:(1-n/a)*s};f.lineWidth=1;for(let e=0;e{let t=u.getBoundingClientRect();v.x=e.clientX-t.left,v.y=e.clientY-t.top},T=()=>{v.x=-9999,v.y=-9999},E=new ResizeObserver(S);return E.observe(u),S(),u.addEventListener(`mousemove`,w),u.addEventListener(`mouseleave`,T),y=requestAnimationFrame(C),()=>{cancelAnimationFrame(y),E.disconnect(),u.removeEventListener(`mousemove`,w),u.removeEventListener(`mouseleave`,T)}},[e,t,n,a,o,s,c,l,r,i]),(0,P.jsx)(`div`,{ref:e=>{p.current=e,typeof f==`function`?f(e):f&&(f.current=e)},"aria-hidden":`true`,className:V(`pui-node-graph`,u),...d,children:(0,P.jsx)(`canvas`,{ref:m})})});Jo.displayName=`NodeGraphBackground`;function Yo(e,t){if(e.startsWith(`#`)){let n,r,i;return e.length===4?(n=parseInt(e[1]+e[1],16),r=parseInt(e[2]+e[2],16),i=parseInt(e[3]+e[3],16)):(n=parseInt(e.slice(1,3),16),r=parseInt(e.slice(3,5),16),i=parseInt(e.slice(5,7),16)),`rgba(${n},${r},${i},${t})`}return e}var Xo=(0,N.forwardRef)(({count:e=18,glyphs:t=[`✦`,`✧`,`✶`,`✺`,`✹`,`·`],durationS:n=[8,18],sizeRange:r=[8,20],className:i,...a},o)=>{let s=(0,N.useMemo)(()=>Array.from({length:e},()=>({glyph:t[Math.floor(Math.random()*t.length)],left:Math.random()*100,duration:n[0]+Math.random()*(n[1]-n[0]),delay:Math.random()*n[1],size:r[0]+Math.random()*(r[1]-r[0]),opacity:.4+Math.random()*.5})),[e,t,n,r]);return(0,P.jsx)(`div`,{ref:o,"aria-hidden":`true`,className:V(`pui-sparkle-field`,i),...a,children:s.map((e,t)=>(0,P.jsx)(`span`,{className:`pui-sparkle-field__item`,style:{left:`${e.left}%`,fontSize:`${e.size}px`,"--pui-sparkle-peak":e.opacity.toFixed(2),animationDuration:`${e.duration}s`,animationDelay:`${e.delay}s`},children:e.glyph},t))})});Xo.displayName=`FloatingSparkles`;var Zo=(0,N.forwardRef)(({breathing:e,glowOnHover:t=!0,className:n,children:r,...i},a)=>(0,P.jsx)(`article`,{ref:a,className:V(`pui-glass-card`,e&&`pui-glass-card--breathing`,t&&`pui-glass-card--glow-hover`,n),...i,children:r}));Zo.displayName=`GlassCard`;var Qo=(0,N.forwardRef)(({className:e,...t},n)=>(0,P.jsx)(`div`,{ref:n,className:V(`pui-glass-card__icon`,e),...t}));Qo.displayName=`GlassCard.Icon`;var $o=(0,N.forwardRef)(({className:e,...t},n)=>(0,P.jsx)(`h3`,{ref:n,className:V(`pui-glass-card__title`,e),...t}));$o.displayName=`GlassCard.Title`;var es=(0,N.forwardRef)(({className:e,...t},n)=>(0,P.jsx)(`p`,{ref:n,className:V(`pui-glass-card__body`,e),...t}));es.displayName=`GlassCard.Body`;var ts=(0,N.forwardRef)(({className:e,children:t,...n},r)=>(0,P.jsxs)(`a`,{ref:r,className:V(`pui-glass-card__link`,e),...n,children:[(0,P.jsx)(`span`,{children:t}),(0,P.jsx)(`span`,{className:`pui-arrow`,children:`→`})]}));ts.displayName=`GlassCard.Link`;var ns=Object.assign(Zo,{Icon:Qo,Title:$o,Body:es,Link:ts}),rs=(0,N.forwardRef)(({filename:e,tokens:t,loop:n=!0,charMs:r=[14,42],thinkingLabel:i=`AI is writing…`,className:a,children:o,...s},c)=>(0,P.jsx)(`div`,{ref:c,"data-theme":`dark`,className:V(`pui-ide`,a),...s,children:o??(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(is,{filename:e,thinking:i}),(0,P.jsx)(as,{tokens:t??[],loop:n,charMs:r})]})}));rs.displayName=`MockIDE`;var is=(0,N.forwardRef)(({filename:e,thinking:t,className:n,children:r,...i},a)=>(0,P.jsxs)(`div`,{ref:a,className:V(`pui-ide__chrome`,n),...i,children:[(0,P.jsx)(`span`,{className:`pui-ide__dot pui-ide__dot--red`}),(0,P.jsx)(`span`,{className:`pui-ide__dot pui-ide__dot--yellow`}),(0,P.jsx)(`span`,{className:`pui-ide__dot pui-ide__dot--green`}),e&&(0,P.jsx)(`span`,{className:`pui-ide__tab`,children:e}),r,t!==!1&&(0,P.jsxs)(`span`,{className:`pui-ide__thinking`,children:[(0,P.jsx)(`span`,{className:`pui-spinner`}),(0,P.jsx)(`span`,{children:t})]})]}));is.displayName=`MockIDE.Chrome`;var as=(0,N.forwardRef)(({tokens:e,loop:t=!0,charMs:n=[14,42],className:r,...i},a)=>{let o=(0,N.useRef)(null),s=a??o,[c,l]=(0,N.useState)(0);return(0,N.useEffect)(()=>{let r=s.current;if(!r||!e.length)return;let i=!1,a=null,o=0,c=0,u=``,d=e=>e.replace(/&/g,`&`).replace(//g,`>`),f=()=>{if(i)return;if(o>=e.length){t&&(a=setTimeout(()=>{o=0,c=0,r.innerHTML=u,l(e=>e+1),f()},3e3));return}let s=e[o];if(c+=1,c>s.c.length){o+=1,c=0,f();return}let p=``;for(let t=0;t${d(n.c)}`:d(n.c)}let m=s.c.slice(0,c);p+=s.cls?`${d(m)}`:d(m),p+=u,r.innerHTML=p;let[h,g]=n,_=h+Math.random()*(g-h)+(m.endsWith(` -`)?120:0);a=setTimeout(f,_)};return r.innerHTML=u,f(),()=>{i=!0,a&&clearTimeout(a)}},[e,t,n,s,c]),(0,P.jsx)(`pre`,{ref:s,className:V(`pui-ide__body`,r),...i})});as.displayName=`MockIDE.Body`,Object.assign(rs,{Chrome:is,Body:as});var os=(0,N.forwardRef)(({role:e,agent:t,thinking:n,icon:r,className:i,children:a,...o},s)=>(0,P.jsxs)(`div`,{ref:s,className:V(`pui-bubble`,e===`user`?`pui-bubble--user`:`pui-bubble--ai`,i),...o,children:[e===`ai`&&(t||n!==!1||r!==!1)&&(0,P.jsxs)(`div`,{className:`pui-bubble__meta`,children:[r===!1?null:r??(0,P.jsx)(bo,{}),t&&(0,P.jsx)(`span`,{children:t}),n!==!1&&(0,P.jsxs)(`span`,{className:`pui-bubble__thinking-pill`,children:[(0,P.jsx)(`span`,{className:`pui-spinner pui-spinner--sm`}),(0,P.jsx)(`span`,{children:n??`thinking…`})]})]}),(0,P.jsx)(`div`,{className:`pui-bubble__stream`,children:a})]}));os.displayName=`ChatBubble`;var ss=e=>e.split(/(\s+)/);function cs({text:e,speedMs:t=[18,80],tokenize:n=ss,loop:r=!1,loopDelayMs:i=6e3,onComplete:a}){let[o,s]=(0,N.useState)(``),[c,l]=(0,N.useState)(!1),u=(0,N.useRef)(a);return u.current=a,(0,N.useEffect)(()=>{let a=!1,o=null,c=n(e),d=()=>Array.isArray(t)?t[0]+Math.random()*(t[1]-t[0]):t,f=()=>{let e=0,t=``,n=()=>{var p;if(!a){if(e>=c.length){l(!0),(p=u.current)==null||p.call(u),r&&(o=setTimeout(()=>{a||(l(!1),s(``),f())},i));return}t+=c[e],e+=1,s(t),o=setTimeout(n,d())}};n()};return f(),()=>{a=!0,o&&clearTimeout(o)}},[]),{output:o,isStreaming:!c,isComplete:c}}var ls=(0,N.forwardRef)(({text:e,speedMs:t,tokenize:n,loop:r,loopDelayMs:i,onComplete:a,hideCaret:o,className:s,...c},l)=>{let{output:u,isStreaming:d}=cs({text:e,speedMs:t,tokenize:n,loop:r,loopDelayMs:i,onComplete:a});return(0,P.jsxs)(`span`,{ref:l,className:V(s),...c,children:[u,!o&&d&&(0,P.jsx)(`span`,{className:`pui-bubble__stream-caret`})]})});ls.displayName=`TokenStream`;var us=[`·`,`✢`,`✳`,`✶`,`✻`,`✽`],ds=`Accomplishing.Actioning.Actualizing.Architecting.Baking.Beaming.Befuddling.Billowing.Blanching.Bloviating.Boogieing.Boondoggling.Booping.Bootstrapping.Brewing.Bunning.Burrowing.Calculating.Canoodling.Caramelizing.Cascading.Catapulting.Cerebrating.Channeling.Channelling.Choreographing.Churning.Clauding.Coalescing.Cogitating.Combobulating.Composing.Computing.Concocting.Considering.Contemplating.Cooking.Crafting.Creating.Crunching.Crystallizing.Cultivating.Deciphering.Deliberating.Determining.Dilly-dallying.Discombobulating.Doing.Doodling.Drizzling.Ebbing.Effecting.Elucidating.Embellishing.Enchanting.Envisioning.Evaporating.Fermenting.Fiddle-faddling.Finagling.Flambéing.Flibbertigibbeting.Flowing.Flummoxing.Fluttering.Forging.Forming.Frolicking.Frosting.Gallivanting.Galloping.Garnishing.Generating.Gesticulating.Germinating.Gitifying.Grooving.Gusting.Harmonizing.Hashing.Hatching.Herding.Honking.Hullaballooing.Hyperspacing.Ideating.Imagining.Improvising.Incubating.Inferring.Infusing.Ionizing.Jitterbugging.Julienning.Kneading.Leavening.Levitating.Lollygagging.Manifesting.Marinating.Meandering.Metamorphosing.Misting.Moonwalking.Moseying.Mulling.Mustering.Musing.Nebulizing.Nesting.Newspapering.Noodling.Nucleating.Orbiting.Orchestrating.Osmosing.Perambulating.Percolating.Perusing.Philosophising.Photosynthesizing.Pollinating.Pondering.Pontificating.Pouncing.Precipitating.Prestidigitating.Processing.Proofing.Propagating.Puttering.Puzzling.Quantumizing.Razzle-dazzling.Razzmatazzing.Recombobulating.Reticulating.Roosting.Ruminating.Sautéing.Scampering.Schlepping.Scurrying.Seasoning.Shenaniganing.Shimmying.Simmering.Skedaddling.Sketching.Slithering.Smooshing.Sock-hopping.Spelunking.Spinning.Sprouting.Stewing.Sublimating.Swirling.Swooping.Symbioting.Synthesizing.Tempering.Thinking.Thundering.Tinkering.Tomfoolering.Topsy-turvying.Transfiguring.Transmuting.Twisting.Undulating.Unfurling.Unravelling.Vibing.Waddling.Wandering.Warping.Whatchamacalliting.Whirlpooling.Whirring.Whisking.Wibbling.Working.Wrangling.Zesting.Zigzagging`.split(`.`);function fs(e){return e[Math.floor(Math.random()*e.length)]}var ps=(0,N.forwardRef)(({verbs:e=ds,glyphs:t=us,glyphInterval:n=250,verbInterval:r,ellipsis:i=`…`,info:a,glyphColor:o,className:s,...c},l)=>{let[u,d]=(0,N.useState)(0),[f,p]=(0,N.useState)(()=>e.length?fs(e):``);(0,N.useEffect)(()=>{if(!t.length)return;let e=setInterval(()=>{d(e=>(e+1)%t.length)},n);return()=>clearInterval(e)},[n,t.length]),(0,N.useEffect)(()=>{if(r==null||!e.length)return;let t=setInterval(()=>{p(fs(e))},r);return()=>clearInterval(t)},[e,r]);let m=f;return(0,P.jsxs)(`span`,{ref:l,className:V(`pui-wibble`,s),...c,children:[(0,P.jsx)(`span`,{className:`pui-wibble__glyph`,"aria-hidden":`true`,style:o?{color:o}:void 0,children:t[u]??``}),(0,P.jsxs)(`span`,{className:`pui-wibble__verb`,children:[m,i]}),a!=null&&a!==!1&&(0,P.jsxs)(`span`,{className:`pui-wibble__info`,children:[`(`,a,`)`]})]})});ps.displayName=`WibblingSpinner`;var ms=(0,N.forwardRef)(({label:e=`Ask AI`,open:t,defaultOpen:n,onOpenChange:r,popover:i,className:a,onClick:o,...s},c)=>{let l=t!==void 0,[u,d]=(0,N.useState)(n??!1),f=l?!!t:u,p=()=>{let e=!f;l||d(e),r?.(e)},m=()=>{l||d(!1),r?.(!1)};return(0,P.jsxs)(P.Fragment,{children:[(0,P.jsxs)(`button`,{ref:c,className:V(`pui-fab`,a),onClick:e=>{o?.(e),p()},"aria-expanded":f,...s,children:[(0,P.jsx)(bo,{}),(0,P.jsx)(`span`,{children:e})]}),f&&(0,P.jsx)(`div`,{role:`dialog`,className:`pui-fab-popover`,children:(0,P.jsx)(hs.Provider,{value:m,children:i})})]})});ms.displayName=`ChatFAB`;var hs=(0,N.createContext)(()=>{}),gs=(0,N.forwardRef)(({onClose:e,className:t,children:n,...r},i)=>{let a=(0,N.useContext)(hs);return(0,P.jsxs)(`div`,{ref:i,className:V(`pui-fab-popover__header`,t),...r,children:[(0,P.jsx)(bo,{}),(0,P.jsx)(`span`,{children:n}),(0,P.jsx)(`button`,{type:`button`,"aria-label":`Close`,className:`pui-fab-popover__close`,onClick:e??a,children:`×`})]})});gs.displayName=`ChatFAB.Header`;var _s=(0,N.forwardRef)(({className:e,...t},n)=>(0,P.jsx)(`div`,{ref:n,className:V(`pui-fab-popover__body`,e),...t}));_s.displayName=`ChatFAB.Body`,Object.assign(ms,{Header:gs,Body:_s});var vs=(0,N.forwardRef)(({logos:e,speed:t=40,gap:n=56,fade:r=!0,pauseOnHover:i,className:a,style:o,...s},c)=>{let l={...o??{},"--pui-marquee-speed":`${t}s`,"--pui-marquee-gap":`${n}px`},u=(e,t)=>e.kind===`img`?(0,P.jsx)(`span`,{className:`pui-marquee__item`,children:(0,P.jsx)(`img`,{src:e.src,alt:e.alt??``})},`a${t}`):(0,P.jsx)(`span`,{className:`pui-marquee__item`,children:e.node},e.key??`b${t}`);return(0,P.jsx)(`div`,{ref:c,className:V(`pui-marquee`,r&&`pui-marquee--fade`,i&&`pui-marquee--paused-on-hover`,a),style:l,"aria-label":`Trusted by`,...s,children:(0,P.jsxs)(`div`,{className:`pui-marquee__track`,children:[e.map(u),e.map((t,n)=>u(t,n+e.length))]})})});vs.displayName=`LogoMarquee`;var ys=(0,N.forwardRef)(({heading:e,logos:t,className:n,...r},i)=>(0,P.jsxs)(`div`,{ref:i,className:V(`pui-logo-row`,n),...r,children:[e&&(0,P.jsx)(`p`,{className:`pui-logo-row__heading`,children:e}),(0,P.jsx)(`div`,{className:`pui-logo-row__items`,children:t.map((e,t)=>e.kind===`img`?(0,P.jsx)(`img`,{src:e.src,alt:e.alt??``},t):(0,P.jsx)(`span`,{className:`pui-logo-row__text`,children:e.node},e.key??t))})]}));ys.displayName=`LogoRow`;var bs=(0,N.forwardRef)(({rows:e,intensity:t=240,startDirection:n=`left`,gap:r=12,fade:i=!0,gradient:a=!1,static:o,className:s,style:c,...l},u)=>{let d=(0,N.useRef)(null);(0,N.useEffect)(()=>{let e=d.current;if(!e||o||window.matchMedia?.call(window,`(prefers-reduced-motion: reduce)`).matches)return;let n=0,r=()=>{n=0;let r=e.getBoundingClientRect(),i=window.innerHeight||document.documentElement.clientHeight,a=(i-r.top)/(i+r.height),o=(Math.min(1,Math.max(0,a))-.5)*t;e.style.setProperty(`--pui-slip`,`${o}px`)},i=()=>{n||(n=requestAnimationFrame(r))};return r(),window.addEventListener(`scroll`,i,{passive:!0}),window.addEventListener(`resize`,i,{passive:!0}),()=>{n&&cancelAnimationFrame(n),window.removeEventListener(`scroll`,i),window.removeEventListener(`resize`,i)}},[t,o]);let f=e=>{d.current=e,typeof u==`function`?u(e):u&&(u.current=e)},p=n===`left`?-1:1,m={...c??{},"--pui-slip-gap":`${r}px`};return(0,P.jsx)(`div`,{ref:f,className:V(`pui-slippy`,i&&`pui-slippy--fade`,s),style:m,"aria-label":`Featured terms`,...l,children:e.map((e,t)=>(0,P.jsx)(`div`,{className:`pui-slippy__row`,style:{"--pui-slip-dir":t%2==0?p:-p},children:e.map((e,n)=>{let r=typeof e==`string`?{label:e}:e;return(0,P.jsx)(`span`,{className:V(`pui-slippy__word`,(a||typeof e==`object`&&e.gradient)&&`pui-slippy__word--gradient`),children:r.label},typeof e==`object`&&e.key||`${t}-${n}`)})},t))})});bs.displayName=`SlippyWords`;function xs({target:e,durationMs:t=1800,from:n=0,ease:r=e=>1-(1-e)**3}){let[i,a]=(0,N.useState)(n);return(0,N.useEffect)(()=>{let i=0,o=performance.now(),s=c=>{let l=Math.min(1,(c-o)/t);a(Math.floor(n+(e-n)*r(l))),l<1&&(i=requestAnimationFrame(s))};return i=requestAnimationFrame(s),()=>cancelAnimationFrame(i)},[]),i}var Ss=(0,N.forwardRef)(({target:e,durationMs:t,from:n,ease:r,format:i=e=>e.toLocaleString(),className:a,...o},s)=>{let c=xs({target:e,durationMs:t,from:n,ease:r});return(0,P.jsx)(`span`,{ref:s,className:V(`pui-stat`,a),...o,children:i(c)})});Ss.displayName=`StatCounter`;var Cs=(0,N.forwardRef)(({icon:e,iconNode:t,title:n,subtitle:r,className:i,...a},o)=>(0,P.jsxs)(`a`,{ref:o,className:V(`pui-community`,i),...a,children:[t??(e&&(0,P.jsx)(`img`,{className:`pui-community__icon`,src:e,alt:``})),(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`div`,{className:`pui-community__top`,children:n}),(0,P.jsx)(`div`,{className:`pui-community__bottom`,children:r})]})]}));Cs.displayName=`CommunityBadge`;var ws=(0,N.forwardRef)(({featured:e,className:t,...n},r)=>(0,P.jsx)(`article`,{ref:r,className:V(`pui-price`,e&&`pui-price--featured`,t),...n}));ws.displayName=`PricingCard`;var Ts=(0,N.forwardRef)(({hideSparkle:e,className:t,children:n,...r},i)=>(0,P.jsxs)(`div`,{ref:i,className:V(`pui-price__flag`,t),...r,children:[!e&&(0,P.jsx)(bo,{solid:!0}),(0,P.jsx)(`span`,{children:n})]}));Ts.displayName=`PricingCard.Flag`;var Es=(0,N.forwardRef)(({className:e,...t},n)=>(0,P.jsx)(`div`,{ref:n,className:V(`pui-price__tier`,e),...t}));Es.displayName=`PricingCard.Tier`;var Ds=(0,N.forwardRef)(({unit:e,className:t,children:n,...r},i)=>(0,P.jsxs)(`div`,{ref:i,className:V(`pui-price__amount`,t),...r,children:[n,e&&(0,P.jsx)(`span`,{className:`pui-price__amount-unit`,children:e})]}));Ds.displayName=`PricingCard.Amount`;var Os=(0,N.forwardRef)(({className:e,...t},n)=>(0,P.jsx)(`p`,{ref:n,className:V(`pui-price__blurb`,e),...t}));Os.displayName=`PricingCard.Blurb`;var ks=(0,N.forwardRef)(({className:e,...t},n)=>(0,P.jsx)(`ul`,{ref:n,className:V(`pui-price__features`,e),...t}));ks.displayName=`PricingCard.Features`;var As=(0,N.forwardRef)(({className:e,children:t,...n},r)=>(0,P.jsx)(`a`,{ref:r,className:V(`pui-btn pui-btn--glow pui-btn--block`,e),...n,children:(0,P.jsx)(`span`,{children:t})}));As.displayName=`PricingCard.CTA`,Object.assign(ws,{Flag:Ts,Tier:Es,Amount:Ds,Blurb:Os,Features:ks,CTA:As});var js=(0,N.forwardRef)(({before:e,after:t,brand:n,beforeLabel:r=`Before`,afterLabel:i=`After`,className:a,children:o,...s},c)=>(0,P.jsx)(`div`,{ref:c,className:V(`pui-ba`,a),...s,children:o??(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(Ms,{label:r,children:(0,P.jsx)(`ul`,{children:(e??[]).map((e,t)=>(0,P.jsx)(`li`,{children:e},t))})}),(0,P.jsx)(Ps,{brand:n}),(0,P.jsx)(Ns,{label:i,children:(0,P.jsx)(`ul`,{children:(t??[]).map((e,t)=>(0,P.jsx)(`li`,{children:e},t))})})]})}));js.displayName=`BeforeAfter`;var Ms=(0,N.forwardRef)(({label:e=`Before`,className:t,children:n,...r},i)=>(0,P.jsxs)(`div`,{ref:i,className:V(`pui-ba__panel pui-ba__panel--before`,t),...r,children:[(0,P.jsx)(`div`,{className:`pui-ba__tag`,children:e}),n]}));Ms.displayName=`BeforeAfter.Before`;var Ns=(0,N.forwardRef)(({label:e=`After`,className:t,children:n,...r},i)=>(0,P.jsxs)(`div`,{ref:i,className:V(`pui-ba__panel pui-ba__panel--after`,t),...r,children:[(0,P.jsx)(`div`,{className:`pui-ba__tag`,children:e}),n]}));Ns.displayName=`BeforeAfter.After`;var Ps=(0,N.forwardRef)(({brand:e,className:t,...n},r)=>(0,P.jsxs)(`div`,{ref:r,className:V(`pui-ba__arrow`,t),...n,children:[(0,P.jsx)(bo,{}),e?(0,P.jsxs)(`span`,{children:[`with `,e]}):(0,P.jsx)(`span`,{children:`after`}),(0,P.jsx)(`span`,{children:`→`})]}));Ps.displayName=`BeforeAfter.Arrow`,Object.assign(js,{Before:Ms,After:Ns,Arrow:Ps});var Fs=(0,N.forwardRef)(({placeholder:e=`you@startup.ai`,defaultValue:t=``,ctaLabel:n=`Notify me`,leading:r,footnote:i,onSubmit:a,className:o,...s},c)=>{let[l,u]=(0,N.useState)(t);return(0,P.jsxs)(`div`,{className:V(`pui-waitlist-wrap`,o),children:[(0,P.jsxs)(`form`,{ref:c,className:`pui-waitlist`,onSubmit:e=>{e.preventDefault(),a?.(l)},...s,children:[r===!1?null:(0,P.jsx)(`span`,{className:`pui-waitlist__icon`,"aria-hidden":`true`,children:r??(0,P.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`1.6`,strokeLinecap:`round`,strokeLinejoin:`round`,width:`18`,height:`18`,children:[(0,P.jsx)(`rect`,{x:`3`,y:`5`,width:`18`,height:`14`,rx:`2`}),(0,P.jsx)(`path`,{d:`M3 7l9 6 9-6`})]})}),(0,P.jsx)(`input`,{className:`pui-waitlist__input`,type:`email`,placeholder:e,value:l,onChange:e=>u(e.target.value)}),(0,P.jsx)(Eo,{type:`submit`,variant:`solid`,children:n})]}),i&&(0,P.jsx)(`div`,{className:`pui-waitlist__footnote`,children:i})]})});Fs.displayName=`WaitlistForm`;function Is({open:e,defaultOpen:t=!1,onOpenChange:n,timer:r=0,title:i,children:a,closeLabel:o=`Maybe later`,closeOnEscape:s=!1,closeOnBackdrop:c=!1,container:l,className:u}){let d=e!==void 0,[f,p]=(0,N.useState)(t),m=d?e:f,h=e=>{d||p(e),n?.(e)};if((0,N.useEffect)(()=>{if(r<=0||m)return;let e=setTimeout(()=>h(!0),r);return()=>clearTimeout(e)},[]),(0,N.useEffect)(()=>{if(!m||!s)return;let e=e=>{e.key===`Escape`&&(e.preventDefault(),h(!1))};return document.addEventListener(`keydown`,e),()=>document.removeEventListener(`keydown`,e)},[m,s]),(0,N.useEffect)(()=>{if(!m)return;let e=e=>{e.target instanceof HTMLInputElement||e.target instanceof HTMLTextAreaElement||[`[`,`]`,`j`,`k`,`ArrowLeft`,`ArrowRight`].includes(e.key)&&e.stopPropagation()};return document.addEventListener(`keydown`,e,{capture:!0}),()=>document.removeEventListener(`keydown`,e,{capture:!0})},[m]),(0,N.useEffect)(()=>{if(!m)return;let e=document.body.style.overflow;return document.body.style.overflow=`hidden`,()=>{document.body.style.overflow=e}},[m]),!m)return null;let g=l??(typeof document<`u`?document.body:null);return g?(0,Vn.createPortal)((0,P.jsxs)(`div`,{className:`pui-popover-overlay`,role:`dialog`,"aria-modal":`true`,children:[(0,P.jsx)(`div`,{className:`pui-popover-backdrop`,"aria-hidden":`true`,onClick:c?()=>h(!1):void 0}),(0,P.jsxs)(`div`,{className:V(`pui-popover`,u),children:[i&&(0,P.jsx)(`div`,{className:`pui-popover__title`,children:i}),(0,P.jsx)(`div`,{className:`pui-popover__body`,children:a}),o!==!1&&(0,P.jsx)(`button`,{type:`button`,className:`pui-popover__dismiss`,onClick:()=>h(!1),children:o})]})]}),g):null}Is.displayName=`Popover`;var Ls={primary:`wave`,secondary:`ghost`,danger:`ghost`,icon:`ghost`},Rs={sm:`sm`,md:`md`};function H({tone:e=`secondary`,size:t=`md`,loading:n=!1,block:r=!1,className:i,children:a,disabled:o,...s}){return(0,P.jsx)(Eo,{variant:Ls[e],size:Rs[t],loading:n,block:r,className:[`admin-button`,`admin-button-${e}`,i].filter(Boolean).join(` `),disabled:o||n,...s,children:a})}var zs={ok:`var(--ok)`,warn:`var(--warn)`,err:`var(--err)`,dim:`var(--text-3)`};function Bs({status:e,pulse:t}){return(0,P.jsx)(So,{color:zs[e],static:!t,className:`admin-status-dot`})}function U({children:e,className:t,breathing:n=!1,glowOnHover:r=!1,...i}){return(0,P.jsx)(ns,{breathing:n,glowOnHover:r,className:[`admin-surface`,t].filter(Boolean).join(` `),...i,children:e})}function Vs({label:e=`Loading`,info:t,className:n}){return(0,P.jsx)(ps,{verbs:[e],glyphs:[`.`,`o`,`O`,`o`],glyphInterval:220,ellipsis:`...`,info:t,glyphColor:`var(--accent)`,className:[`admin-loading`,n].filter(Boolean).join(` `)})}function Hs({value:e,precision:t=0,durationMs:n=450,className:r,format:i}){let a=10**t,o=Math.round(e*a),s=(0,N.useRef)(o),c=s.current;return(0,N.useEffect)(()=>{s.current=o},[o]),(0,P.jsx)(Ss,{target:o,from:c,durationMs:n,className:r,format:e=>{let t=e/a;return i?i(t):t.toLocaleString()}},o)}function Us(){let e=ea(e=>e.login),[t,n]=(0,N.useState)(``),[r,i]=(0,N.useState)(!1);async function a(t){t.preventDefault();let r=t.currentTarget.elements.namedItem(`token`).value.trim();if(r){i(!0),n(``);try{if(!(await fetch(`/admin/api/metrics`,{headers:{Authorization:`Bearer ${r}`}})).ok)throw Error(`Invalid token`);e(r)}catch{n(`Invalid token`)}finally{i(!1)}}}return(0,P.jsxs)(`div`,{className:`login-overlay`,children:[(0,P.jsx)(Jo,{density:24,speed:.16,linkDistance:110,hoverDistance:120,hoverGravity:.002,baseOpacity:.18,colors:[`#e8a030`,`#4caf6e`,`#5aa9e6`],linkColor:`#e8a030`,className:`login-node-bg`}),(0,P.jsxs)(U,{className:`login-card`,glowOnHover:!0,breathing:!0,children:[(0,P.jsxs)(`div`,{className:`login-title`,children:[(0,P.jsx)(`span`,{className:`prompt`,children:`>\xA0`}),`proxy admin`]}),(0,P.jsxs)(`form`,{onSubmit:a,children:[(0,P.jsx)(`input`,{type:`password`,name:`token`,placeholder:`Admin token`,autoComplete:`current-password`,autoFocus:!0}),(0,P.jsx)(H,{type:`submit`,tone:`primary`,loading:r,block:!0,children:`Sign in`})]}),(0,P.jsx)(`div`,{className:`login-error`,children:t})]})]})}var Ws=[{label:`Overview`,items:[{to:`/dashboard`,label:`Dashboard`},{to:`/requests`,label:`Request Log`},{to:`/traffic`,label:`Traffic`}]},{label:`Configure`,items:[{to:`/providers`,label:`Providers`},{to:`/routes`,label:`Routes`},{to:`/models`,label:`Models`},{to:`/backends`,label:`Backends`}]},{label:`Access`,items:[{to:`/keys`,label:`API Keys`},{to:`/audit`,label:`Audit Log`}]},{label:`System`,items:[{to:`/settings`,label:`Settings`},{to:`/uptime`,label:`Uptime`}]}];function Gs(){let e=ea(e=>e.logout),t=ta(e=>e.status);return(0,P.jsxs)(`aside`,{className:`sidebar`,children:[(0,P.jsx)(`div`,{className:`sidebar-brand`,children:`anyllm`}),(0,P.jsx)(`div`,{className:`sidebar-scroll`,children:Ws.map(e=>(0,P.jsxs)(`div`,{className:`sidebar-group`,children:[(0,P.jsx)(`div`,{className:`sidebar-group-label`,children:e.label}),e.items.map(e=>(0,P.jsx)(Li,{to:e.to,className:({isActive:e})=>`sidebar-item${e?` active`:``}`,children:e.label},e.to))]},e.label))}),(0,P.jsxs)(`div`,{className:`sidebar-footer`,children:[(0,P.jsx)(`span`,{className:`ws-status ${t===`connected`?`connected`:`disconnected`}`,children:t===`connected`?`Live`:`Offline`}),(0,P.jsx)(H,{size:`sm`,onClick:e,children:`Sign out`})]})]})}function Ks(){let e=pa(e=>e.toasts);return e.length===0?null:(0,P.jsx)(`div`,{className:`toast-stack`,role:`region`,"aria-label":`Notifications`,children:e.map(e=>(0,P.jsx)(qs,{toast:e},e.id))})}function qs({toast:e}){let t=pa(e=>e.dismiss);return(0,N.useEffect)(()=>{if(e.ttlMs==null)return;let n=window.setTimeout(()=>t(e.id),e.ttlMs);return()=>window.clearTimeout(n)},[e.id,e.ttlMs,t]),(0,P.jsxs)(`div`,{className:`toast toast-${e.variant}`,role:`status`,children:[(0,P.jsx)(`div`,{className:`toast-message`,children:e.message}),(0,P.jsx)(`button`,{type:`button`,className:`toast-close`,"aria-label":`Dismiss`,onClick:()=>t(e.id),children:`×`})]})}function Js({req:e}){return(0,P.jsxs)(`div`,{className:`feed-detail`,children:[(0,P.jsx)(`span`,{className:`label`,children:`Request ID`}),(0,P.jsx)(`span`,{className:`val`,children:e.request_id}),(0,P.jsx)(`span`,{className:`label`,children:`Backend`}),(0,P.jsx)(`span`,{className:`val`,children:e.backend}),(0,P.jsx)(`span`,{className:`label`,children:`Model (req)`}),(0,P.jsx)(`span`,{className:`val`,children:e.model_requested??`—`}),(0,P.jsx)(`span`,{className:`label`,children:`Model (mapped)`}),(0,P.jsx)(`span`,{className:`val`,children:e.model_mapped??`—`}),(0,P.jsx)(`span`,{className:`label`,children:`Latency`}),(0,P.jsxs)(`span`,{className:`val`,children:[e.latency_ms,` ms`]}),(0,P.jsx)(`span`,{className:`label`,children:`Tokens in/out`}),(0,P.jsxs)(`span`,{className:`val`,children:[e.input_tokens??`—`,` / `,e.output_tokens??`—`]}),(0,P.jsx)(`span`,{className:`label`,children:`Cost`}),(0,P.jsx)(`span`,{className:`val`,children:e.cost_usd==null?`—`:`$${e.cost_usd.toFixed(6)}`}),e.error_message&&(0,P.jsx)(`div`,{className:`error-msg`,children:e.error_message})]})}function Ys(e){return e<300?`status-2xx`:e<500?`status-4xx`:`status-5xx`}function Xs({req:e}){let[t,n]=(0,N.useState)(!1);return(0,P.jsxs)(P.Fragment,{children:[(0,P.jsxs)(`div`,{className:`feed-row`,onClick:()=>n(e=>!e),children:[(0,P.jsx)(`span`,{className:`mono dim`,children:e.timestamp.slice(11,19)}),(0,P.jsx)(`span`,{className:`mono ${Ys(e.status_code)}`,children:e.status_code}),(0,P.jsxs)(`span`,{className:`mono`,children:[e.latency_ms,`ms`]}),(0,P.jsxs)(`span`,{className:`mono`,style:{overflow:`hidden`,textOverflow:`ellipsis`,whiteSpace:`nowrap`},children:[e.model_requested??e.backend,e.is_streaming&&(0,P.jsx)(`span`,{className:`streaming-badge`,children:`stream`})]}),(0,P.jsx)(`span`,{className:`mono dim`,children:e.input_tokens??`—`}),(0,P.jsx)(`span`,{className:`mono dim`,children:e.output_tokens??`—`}),(0,P.jsx)(`span`,{className:`mono dim`,children:e.cost_usd==null?`—`:`$${e.cost_usd.toFixed(5)}`})]}),t&&(0,P.jsx)(Js,{req:e})]})}var Zs=200;function Qs({initial:e}){let[t,n]=(0,N.useState)(e??[]),[r,i]=(0,N.useState)(!1),a=(0,N.useRef)(r);a.current=r;let o=ta(e=>e.lastEvent);return(0,N.useEffect)(()=>{!o||o.type!==`request_completed`||a.current||n(e=>[o.data,...e].slice(0,Zs))},[o]),(0,P.jsxs)(`div`,{children:[(0,P.jsxs)(`div`,{className:`section-header`,children:[(0,P.jsx)(`span`,{className:`section-label`,children:`Live Feed`}),(0,P.jsx)(H,{size:`sm`,tone:r?`primary`:`secondary`,onClick:()=>i(e=>!e),children:r?`Resume`:`Pause`})]}),(0,P.jsxs)(`div`,{className:`feed`,children:[(0,P.jsxs)(`div`,{className:`feed-header`,children:[(0,P.jsx)(`span`,{children:`Time`}),(0,P.jsx)(`span`,{children:`Status`}),(0,P.jsx)(`span`,{children:`Latency`}),(0,P.jsx)(`span`,{children:`Model`}),(0,P.jsx)(`span`,{children:`In`}),(0,P.jsx)(`span`,{children:`Out`}),(0,P.jsx)(`span`,{children:`Cost`})]}),t.length===0?(0,P.jsx)(`div`,{className:`empty`,children:`Waiting for requests…`}):t.map(e=>(0,P.jsx)(Xs,{req:e},e.request_id))]})]})}function $s({text:e}){return(0,P.jsx)(`span`,{className:`info-tip`,title:e,"aria-label":e,role:`img`,children:`?`})}function ec({series:e,gridColor:t=`var(--border-sub)`,height:n=130}){let r=n,i={top:8,right:8,bottom:0,left:0},a=600-i.left-i.right,o=r-i.top-i.bottom,s=e.flatMap(e=>e.data),c=Math.max(...s,1),l=Math.max(...e.map(e=>e.data.length),2);function u(e){return i.left+e/(l-1)*a}function d(e){return i.top+o-e/c*o}let f=Array.from({length:4},(e,t)=>i.top+t/3*o);return(0,P.jsxs)(`svg`,{className:`chart-svg`,viewBox:`0 0 600 ${r}`,preserveAspectRatio:`none`,style:{height:n},children:[f.map((e,n)=>(0,P.jsx)(`line`,{className:`chart-grid-line`,x1:i.left,y1:e,x2:600-i.right,y2:e,stroke:t},n)),e.map((e,t)=>{if(e.data.length<2)return null;let n=e.data.map((e,t)=>`${u(t)},${d(e)}`).join(` `);return(0,P.jsxs)(`g`,{children:[(0,P.jsx)(`polygon`,{className:`chart-area`,points:[`${u(0)},${i.top+o}`,...e.data.map((e,t)=>`${u(t)},${d(e)}`),`${u(e.data.length-1)},${i.top+o}`].join(` `),fill:e.color}),(0,P.jsx)(`polyline`,{className:`chart-line${e.secondary?` secondary`:``}`,points:n,stroke:e.color})]},t)})]})}function tc({loading:e,error:t,empty:n,message:r}){return e?(0,P.jsx)(`div`,{className:`empty`,children:(0,P.jsx)(Vs,{})}):t?(0,P.jsx)(`div`,{className:`empty error`,children:t}):n?(0,P.jsx)(`div`,{className:`empty`,children:r??`No data`}):null}function nc(){let[e,t]=(0,N.useState)(6),[n,r]=(0,N.useState)(``),{data:i}=Ua(),{data:a,isLoading:o,error:s}=La(e,n),c=a?[{label:`Requests`,color:`#e8a030`,data:a.series.map(e=>e.requests)},{label:`Errors`,color:`#e05252`,data:a.series.map(e=>e.errors),secondary:!0}]:[],l=a?[{label:`Input`,color:`#4caf6e`,data:a.series.map(e=>e.input_tokens)},{label:`Output`,color:`#6eb5c0`,data:a.series.map(e=>e.output_tokens),secondary:!0}]:[],u=a?[{label:`Cost`,color:`#c87dd4`,data:a.series.map(e=>e.cost_usd)}]:[];return(0,P.jsxs)(`div`,{children:[(0,P.jsxs)(`div`,{className:`operator-controls`,children:[(0,P.jsx)(`span`,{className:`section-label`,style:{marginBottom:0},children:`Operator View`}),(0,P.jsxs)(`div`,{className:`form-row`,style:{flexWrap:`wrap`,gap:6,marginTop:0},children:[(0,P.jsxs)(`select`,{value:e,onChange:e=>t(Number(e.target.value)),children:[(0,P.jsx)(`option`,{value:1,children:`Last 1 hour`}),(0,P.jsx)(`option`,{value:6,children:`Last 6 hours`}),(0,P.jsx)(`option`,{value:24,children:`Last 24 hours`})]}),(0,P.jsxs)(`select`,{value:n,onChange:e=>r(e.target.value),children:[(0,P.jsx)(`option`,{value:``,children:`All backends`}),i?.map(e=>(0,P.jsx)(`option`,{value:e.name,children:e.name},e.name))]})]})]}),a&&(0,P.jsxs)(`div`,{className:`stats-row`,children:[(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsx)(`div`,{className:`stat-label`,children:`Input Tokens`}),(0,P.jsx)(`div`,{className:`stat-value`,children:(0,P.jsx)(Hs,{value:a.total_input_tokens})})]}),(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsx)(`div`,{className:`stat-label`,children:`Output Tokens`}),(0,P.jsx)(`div`,{className:`stat-value`,children:(0,P.jsx)(Hs,{value:a.total_output_tokens})})]}),(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsxs)(`div`,{className:`stat-label`,children:[`Window Failures`,(0,P.jsx)($s,{text:`Failed (error) requests within the selected time window and backend filter.`})]}),(0,P.jsx)(`div`,{className:`stat-value`,children:(0,P.jsx)(Hs,{value:a.total_errors})})]}),(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsxs)(`div`,{className:`stat-label`,children:[`Window Cost`,(0,P.jsx)($s,{text:`Estimated USD spend within the selected time window and backend filter, from model pricing.`})]}),(0,P.jsx)(`div`,{className:`stat-value`,children:(0,P.jsx)(Hs,{value:a.total_cost_usd,precision:2,format:e=>`$${e.toFixed(2)}`})})]})]}),(0,P.jsx)(tc,{loading:o,error:s?.message}),a&&(0,P.jsxs)(`div`,{className:`operator-grid`,children:[(0,P.jsxs)(U,{className:`chart-card`,children:[(0,P.jsxs)(`div`,{className:`chart-header`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`div`,{className:`chart-title`,children:`Request Volume`}),(0,P.jsx)(`div`,{className:`chart-subtitle`,children:`Rolling request count and errors`})]}),(0,P.jsx)(`div`,{className:`chart-value`,children:a.total_requests})]}),(0,P.jsx)(ec,{series:c})]}),(0,P.jsxs)(U,{className:`chart-card`,children:[(0,P.jsxs)(`div`,{className:`chart-header`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`div`,{className:`chart-title`,children:`Tokens`}),(0,P.jsx)(`div`,{className:`chart-subtitle`,children:`Input and output usage`})]}),(0,P.jsx)(`div`,{className:`chart-value`,children:(a.total_input_tokens+a.total_output_tokens).toLocaleString()})]}),(0,P.jsx)(ec,{series:l})]}),(0,P.jsxs)(U,{className:`chart-card`,children:[(0,P.jsxs)(`div`,{className:`chart-header`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`div`,{className:`chart-title`,children:`Estimated Cost`}),(0,P.jsx)(`div`,{className:`chart-subtitle`,children:`USD by minute bucket`})]}),(0,P.jsxs)(`div`,{className:`chart-value`,children:[`$`,a.total_cost_usd.toFixed(4)]})]}),(0,P.jsx)(ec,{series:u})]})]})]})}function rc(){let{data:e}=Ia();return(0,P.jsxs)(`div`,{children:[(0,P.jsxs)(`div`,{className:`stats-row`,children:[(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsx)(`div`,{className:`stat-label`,children:`Requests/min`}),(0,P.jsx)(`div`,{className:`stat-value`,children:e?(0,P.jsx)(Hs,{value:e.requests_per_minute,precision:1,format:e=>e.toFixed(1)}):`—`})]}),(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsx)(`div`,{className:`stat-label`,children:`Error Rate`}),(0,P.jsx)(`div`,{className:`stat-value`,children:e?(0,P.jsx)(Hs,{value:e.error_rate*100,precision:1,format:e=>`${e.toFixed(1)}%`}):`—`})]}),(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsxs)(`div`,{className:`stat-label`,children:[`P50 Latency`,(0,P.jsx)($s,{text:`Median response latency — half of requests were faster than this.`})]}),(0,P.jsx)(`div`,{className:`stat-value`,children:e?(0,P.jsx)(Hs,{value:e.p50_latency_ms??0,format:e=>`${Math.round(e)}ms`}):`—`})]}),(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsxs)(`div`,{className:`stat-label`,children:[`P95 Latency`,(0,P.jsx)($s,{text:`95th-percentile latency — 95% of requests were faster than this. Captures tail slowness.`})]}),(0,P.jsx)(`div`,{className:`stat-value`,children:e?(0,P.jsx)(Hs,{value:e.p95_latency_ms??0,format:e=>`${Math.round(e)}ms`}):`—`})]}),(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsx)(`div`,{className:`stat-label`,children:`Total Requests`}),(0,P.jsx)(`div`,{className:`stat-value`,children:(0,P.jsx)(Hs,{value:e?.total_requests??0})})]})]}),(0,P.jsxs)(`div`,{className:`stats-row`,style:{marginBottom:16},children:[(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsx)(`div`,{className:`stat-label`,children:`Streams Started`}),(0,P.jsx)(`div`,{className:`stat-value`,children:(0,P.jsx)(Hs,{value:e?.streams_started??0})})]}),(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsx)(`div`,{className:`stat-label`,children:`Completed`}),(0,P.jsx)(`div`,{className:`stat-value ok`,children:(0,P.jsx)(Hs,{value:e?.streams_completed??0})})]}),(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsx)(`div`,{className:`stat-label`,children:`Failed`}),(0,P.jsx)(`div`,{className:`stat-value`,style:{color:`var(--err)`},children:(0,P.jsx)(Hs,{value:e?.streams_failed??0})})]}),(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsx)(`div`,{className:`stat-label`,children:`Client Disconnects`}),(0,P.jsx)(`div`,{className:`stat-value`,style:{color:`var(--warn)`},children:(0,P.jsx)(Hs,{value:e?.streams_client_disconnected??0})})]})]}),(e?.pxpipe_compressed_total??0)>0&&(0,P.jsxs)(`div`,{className:`stats-row`,style:{marginBottom:16},children:[(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsx)(`div`,{className:`stat-label`,children:`Image-Compressed Requests`}),(0,P.jsx)(`div`,{className:`stat-value`,children:(0,P.jsx)(Hs,{value:e?.pxpipe_compressed_total??0})})]}),(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsx)(`div`,{className:`stat-label`,children:`Images Emitted`}),(0,P.jsx)(`div`,{className:`stat-value`,children:(0,P.jsx)(Hs,{value:e?.pxpipe_images_total??0})})]}),(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsx)(`div`,{className:`stat-label`,children:`Chars Imaged`}),(0,P.jsx)(`div`,{className:`stat-value`,children:(0,P.jsx)(Hs,{value:e?.pxpipe_imaged_chars_total??0})})]})]}),(0,P.jsx)(nc,{}),(0,P.jsx)(`div`,{style:{marginTop:16},children:(0,P.jsx)(Qs,{})})]})}function ic({page:e,hasMore:t,onPrev:n,onNext:r}){return(0,P.jsxs)(`div`,{className:`pagination`,children:[(0,P.jsx)(H,{size:`sm`,onClick:n,disabled:e<=1,children:`Prev`}),(0,P.jsxs)(`span`,{children:[`Page `,e]}),(0,P.jsx)(H,{size:`sm`,onClick:r,disabled:!t,children:`Next`})]})}function ac({query:e,children:t,empty:n,loading:r,errorTitle:i=`Failed to load`,skeletonRows:a=3}){return e.isLoading&&e.data===void 0?(0,P.jsx)(P.Fragment,{children:r??(0,P.jsx)(oc,{count:a})}):e.isError?(0,P.jsxs)(`div`,{className:`async-error`,role:`alert`,children:[(0,P.jsx)(`div`,{className:`async-error-title`,children:i}),(0,P.jsx)(`div`,{className:`async-error-message`,children:e.error instanceof Error?e.error.message:String(e.error)}),(0,P.jsx)(H,{type:`button`,onClick:()=>{e.refetch()},disabled:e.isFetching,loading:e.isFetching,children:`Retry`})]}):e.data===void 0?null:n&&n.when(e.data)?(0,P.jsx)(P.Fragment,{children:n.render()}):(0,P.jsx)(P.Fragment,{children:t(e.data)})}function oc({count:e}){return(0,P.jsx)(`div`,{className:`skeleton-stack`,"aria-hidden":`true`,children:Array.from({length:e},(e,t)=>(0,P.jsx)(`div`,{className:`skeleton skeleton-row`},t))})}function sc(){let[e,t]=Vi(),n=Math.max(1,Number(e.get(`page`)??`1`)||1),r=e.get(`backend`)??``,i=e.get(`status`)??``;function a(n,r,i){let a=new URLSearchParams(e);r?a.set(n,r):a.delete(n),i?.resetPage&&a.delete(`page`),t(a,{replace:!0})}function o(r){let i=new URLSearchParams(e),a=r(n);a<=1?i.delete(`page`):i.set(`page`,String(a)),t(i,{replace:!0})}let s=Ra({page:n,page_size:50,backend:r,status:i}),{data:c}=Ua();return(0,P.jsxs)(`div`,{children:[(0,P.jsxs)(`div`,{className:`section-header`,children:[(0,P.jsx)(`span`,{className:`section-label`,children:`Request Log`}),(0,P.jsxs)(`div`,{className:`form-row`,style:{marginTop:0},children:[(0,P.jsxs)(`select`,{name:`requestlog-backend`,value:r,onChange:e=>a(`backend`,e.target.value,{resetPage:!0}),children:[(0,P.jsx)(`option`,{value:``,children:`All backends`}),c?.map(e=>(0,P.jsx)(`option`,{value:e.name,children:e.name},e.name))]}),(0,P.jsxs)(`select`,{name:`requestlog-status`,value:i,onChange:e=>a(`status`,e.target.value,{resetPage:!0}),children:[(0,P.jsx)(`option`,{value:``,children:`All status`}),(0,P.jsx)(`option`,{value:`ok`,children:`2xx`}),(0,P.jsx)(`option`,{value:`error`,children:`4xx/5xx`})]})]})]}),(0,P.jsx)(ac,{query:s,errorTitle:`Failed to load request log`,empty:{when:e=>e.requests.length===0&&n===1,render:()=>(0,P.jsxs)(`div`,{className:`empty-cta`,children:[(0,P.jsx)(`div`,{className:`empty-cta-title`,children:`No requests logged`}),(0,P.jsx)(`div`,{className:`empty-cta-body`,children:`Send a request through the proxy and it will appear here. Only proxied traffic is logged; admin API calls are in the Audit tab.`})]})},children:e=>(0,P.jsxs)(P.Fragment,{children:[(0,P.jsxs)(`div`,{className:`feed`,children:[(0,P.jsxs)(`div`,{className:`feed-header`,children:[(0,P.jsx)(`span`,{children:`Time`}),(0,P.jsx)(`span`,{children:`Status`}),(0,P.jsx)(`span`,{children:`Latency`}),(0,P.jsx)(`span`,{children:`Model`}),(0,P.jsx)(`span`,{children:`In`}),(0,P.jsx)(`span`,{children:`Out`}),(0,P.jsx)(`span`,{children:`Cost`})]}),e.requests.map(e=>(0,P.jsx)(Xs,{req:e},e.request_id))]}),(0,P.jsx)(ic,{page:n,hasMore:e.has_more,onPrev:()=>o(e=>Math.max(1,e-1)),onNext:()=>o(e=>e+1)})]})})]})}var cc=[`a[href]`,`button:not([disabled])`,`textarea:not([disabled])`,`input:not([disabled])`,`select:not([disabled])`,`[tabindex]:not([tabindex="-1"])`].join(`,`);function lc({open:e,onClose:t,title:n,size:r=`md`,children:i,footer:a,dismissable:o=!0}){let s=(0,N.useRef)(null),c=(0,N.useRef)(null);return(0,N.useEffect)(()=>{if(!e)return;c.current=document.activeElement??null;let t=s.current;return t&&(t.querySelector(cc)??t).focus(),()=>{c.current?.focus?.()}},[e]),(0,N.useEffect)(()=>{if(!e)return;let t=document.body.style.overflow;return document.body.style.overflow=`hidden`,()=>{document.body.style.overflow=t}},[e]),(0,N.useEffect)(()=>{if(!e)return;let n=e=>{if(e.key===`Escape`&&o){e.stopPropagation(),t();return}if(e.key!==`Tab`)return;let n=s.current;if(!n)return;let r=Array.from(n.querySelectorAll(cc)).filter(e=>!e.hasAttribute(`data-focus-skip`));if(r.length===0){e.preventDefault();return}let i=r[0],a=r[r.length-1],c=document.activeElement;e.shiftKey&&c===i?(e.preventDefault(),a.focus()):!e.shiftKey&&c===a&&(e.preventDefault(),i.focus())};return document.addEventListener(`keydown`,n),()=>document.removeEventListener(`keydown`,n)},[e,o,t]),e?(0,Vn.createPortal)((0,P.jsx)(`div`,{className:`modal-backdrop-v2`,onClick:()=>{o&&t()},children:(0,P.jsxs)(`div`,{ref:s,className:`modal-v2 modal-${r}`,role:`dialog`,"aria-modal":`true`,"aria-label":n,tabIndex:-1,onClick:e=>e.stopPropagation(),children:[(0,P.jsxs)(`div`,{className:`modal-header`,children:[(0,P.jsx)(`div`,{className:`modal-title`,children:n}),(0,P.jsx)(`button`,{type:`button`,className:`modal-close`,"aria-label":`Close`,onClick:t,disabled:!o,children:`×`})]}),(0,P.jsx)(`div`,{className:`modal-body`,children:i}),a!=null&&(0,P.jsx)(`div`,{className:`modal-footer`,children:a})]})}),document.body):null}function uc({open:e,onClose:t,onConfirm:n,title:r,message:i,confirmLabel:a=`Delete`,cancelLabel:o=`Cancel`,variant:s=`danger`}){let[c,l]=(0,N.useState)(!1),[u,d]=(0,N.useState)(null),f=async()=>{l(!0),d(null);try{await n(),t()}catch(e){d(e instanceof Error?e.message:String(e))}finally{l(!1)}},p=()=>{c||(d(null),t())};return(0,P.jsxs)(lc,{open:e,onClose:p,title:r,size:`sm`,dismissable:!c,footer:(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(H,{type:`button`,onClick:p,disabled:c,children:o}),(0,P.jsx)(H,{type:`button`,tone:s===`danger`?`danger`:`primary`,onClick:f,disabled:c,loading:c,children:a})]}),children:[(0,P.jsx)(`div`,{className:`confirm-message`,children:i}),u&&(0,P.jsx)(`div`,{className:`confirm-error`,role:`alert`,children:u})]})}var dc=`env_import_pending_restart`;function fc(){return sessionStorage.getItem(dc)===`1`}function pc(e){return`${Math.round(e/1e6)} MB`}function mc({configured:e=!0}){let{data:t,isLoading:n,error:r}=Wa(),{data:i}=qa(),a=Ja(),{data:o}=Ya(),{data:s}=Fa(),c=Ga(),l=Ka(),u=ro(),d=(0,N.useRef)(null),[f,p]=(0,N.useState)({}),[m,h]=(0,N.useState)(null),[g,_]=(0,N.useState)(null),[v,y]=(0,N.useState)(null),[b,x]=(0,N.useState)(fc),[S,C]=(0,N.useState)(null);function w(){if(!S)return Promise.resolve();let e=S;return l.mutateAsync(e).then(()=>void 0)}function T(e,t){c.mutate({[e]:f[e]??t})}function E(e,t){c.mutate({[e]:t})}function D(){return(t?.pxpipe_models??``).split(`,`).map(e=>e.trim()).filter(Boolean)}function O(e){let t=e.toLowerCase();return D().some(e=>t.includes(e.toLowerCase()))}function ee(e,t){let n=D(),r=t?O(e)?n:[...n,e]:n.filter(t=>!e.toLowerCase().includes(t.toLowerCase()));c.mutate({pxpipe_models:r.join(`,`)})}function te(e){let t=e.target.files?.[0];t&&(h(null),_(null),u.mutate(t,{onSuccess(e){h(e),sessionStorage.setItem(dc,`1`),x(!0)},onError(e){try{let t=JSON.parse(e.message);if(t.hard_errors){_(t);return}}catch{}_({hard_errors:[e.message],warnings:[]})}}),d.current&&(d.current.value=``))}async function ne(){y(null);try{await yo()}catch(e){y(e instanceof Error?e.message:String(e))}}function re(){sessionStorage.removeItem(dc),x(!1)}let ie=s?`http://${window.location.hostname}:${s.proxy_port}`:``;return(0,P.jsxs)(`div`,{children:[s&&(0,P.jsxs)(`div`,{className:`proxy-status-badge`,style:{marginBottom:16,fontSize:13,display:`flex`,alignItems:`center`,gap:8},children:[(0,P.jsx)(`span`,{style:{color:s.proxy_running?`var(--ok, green)`:`var(--warn, orange)`},children:s.proxy_running?`●`:`○`}),s.proxy_running?(0,P.jsxs)(`span`,{children:[`Proxy running — `,(0,P.jsx)(`span`,{className:`mono`,children:ie})]}):(0,P.jsxs)(`span`,{children:[`Proxy unreachable on `,(0,P.jsx)(`span`,{className:`mono`,children:ie})]})]}),!e&&(0,P.jsxs)(`div`,{style:{marginBottom:20,padding:`12px 16px`,border:`1px solid var(--border)`,borderLeft:`3px solid var(--warn)`,borderRadius:`var(--r)`,fontSize:13},children:[(0,P.jsx)(`div`,{style:{fontWeight:600,marginBottom:8},children:`No backend configured — nothing to forward requests to.`}),(0,P.jsxs)(`div`,{style:{marginBottom:10},children:[`Add a backend on the `,(0,P.jsx)(`span`,{className:`mono`,children:`Backends`}),` tab, or configure one via env. The proxy needs a backend endpoint (where to forward) and a listen port (where to accept). LISTEN_PORT defaults to 3000. Create a `,(0,P.jsx)(`span`,{className:`mono`,children:`.anyllm.env`}),` and import it below, or pass it at startup: `,(0,P.jsx)(`span`,{className:`mono`,children:`anyllm-proxy --webui --env-file .anyllm.env`})]}),(0,P.jsxs)(`div`,{style:{display:`grid`,gridTemplateColumns:`1fr 1fr 1fr`,gap:10},children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`div`,{style:{fontWeight:600,marginBottom:4,fontSize:12},children:`OpenAI`}),(0,P.jsx)(`pre`,{style:{margin:0,padding:`6px 10px`,background:`var(--surface-2)`,borderRadius:`var(--r)`,fontSize:11,overflowX:`auto`},children:`OPENAI_API_KEY=sk-... +`+e.stack}}var De=Object.prototype.hasOwnProperty,Oe=t.unstable_scheduleCallback,ke=t.unstable_cancelCallback,Ae=t.unstable_shouldYield,je=t.unstable_requestPaint,Me=t.unstable_now,Ne=t.unstable_getCurrentPriorityLevel,Pe=t.unstable_ImmediatePriority,Fe=t.unstable_UserBlockingPriority,Ie=t.unstable_NormalPriority,Le=t.unstable_LowPriority,Re=t.unstable_IdlePriority,ze=t.log,Be=t.unstable_setDisableYieldValue,Ve=null,He=null;function Ue(e){if(typeof ze==`function`&&Be(e),He&&typeof He.setStrictMode==`function`)try{He.setStrictMode(Ve,e)}catch{}}var We=Math.clz32?Math.clz32:qe,Ge=Math.log,Ke=Math.LN2;function qe(e){return e>>>=0,e===0?32:31-(Ge(e)/Ke|0)|0}var Je=256,Ye=262144,Xe=4194304;function Ze(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function Qe(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=Ze(n))):i=Ze(o):i=Ze(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=Ze(n))):i=Ze(o)):i=Ze(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function $e(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function et(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function tt(){var e=Xe;return Xe<<=1,!(Xe&62914560)&&(Xe=4194304),e}function nt(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function rt(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function it(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0`u`||window.document===void 0||window.document.createElement===void 0),gn=!1;if(hn)try{var _n={};Object.defineProperty(_n,"passive",{get:function(){gn=!0}}),window.addEventListener(`test`,_n,_n),window.removeEventListener(`test`,_n,_n)}catch{gn=!1}var vn=null,yn=null,bn=null;function xn(){if(bn)return bn;var e,t=yn,n=t.length,r,i=`value`in vn?vn.value:vn.textContent,a=i.length;for(e=0;e=Zn),er=` `,tr=!1;function nr(e,t){switch(e){case`keyup`:return Yn.indexOf(t.keyCode)!==-1;case`keydown`:return t.keyCode!==229;case`keypress`:case`mousedown`:case`focusout`:return!0;default:return!1}}function rr(e){return e=e.detail,typeof e==`object`&&`data`in e?e.data:null}var ir=!1;function ar(e,t){switch(e){case`compositionend`:return rr(t);case`keypress`:return t.which===32?(tr=!0,er):null;case`textInput`:return e=t.data,e===er&&tr?null:e;default:return null}}function or(e,t){if(ir)return e===`compositionend`||!Xn&&nr(e,t)?(e=xn(),bn=yn=vn=null,ir=!1,e):null;switch(e){case`paste`:return null;case`keypress`:if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}a:{for(;n;){if(n.nextSibling){n=n.nextSibling;break a}n=n.parentNode}n=void 0}n=Or(n)}}function Ar(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Ar(e,t.parentNode):`contains`in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function jr(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=Ht(e.document);t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href==`string`}catch{n=!1}if(n)e=t.contentWindow;else break;t=Ht(e.document)}return t}function Mr(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t===`input`&&(e.type===`text`||e.type===`search`||e.type===`tel`||e.type===`url`||e.type===`password`)||t===`textarea`||e.contentEditable===`true`)}var Nr=hn&&`documentMode`in document&&11>=document.documentMode,Pr=null,Fr=null,Ir=null,Lr=!1;function Rr(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Lr||Pr==null||Pr!==Ht(r)||(r=Pr,`selectionStart`in r&&Mr(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Ir&&Dr(Ir,r)||(Ir=r,r=Dd(Fr,`onSelect`),0>=o,i-=o,Ai=1<<32-We(t)+i|n<h?(g=d,d=null):g=d.sibling;var _=p(i,d,s[h],c);if(_===null){d===null&&(d=g);break}e&&d&&_.alternate===null&&t(i,d),a=o(_,a,h),u===null?l=_:u.sibling=_,u=_,d=g}if(h===s.length)return n(i,d),I&&Mi(i,h),l;if(d===null){for(;hg?(_=h,h=null):_=h.sibling;var y=p(a,h,v.value,l);if(y===null){h===null&&(h=_);break}e&&h&&y.alternate===null&&t(a,h),s=o(y,s,g),d===null?u=y:d.sibling=y,d=y,h=_}if(v.done)return n(a,h),I&&Mi(a,g),u;if(h===null){for(;!v.done;g++,v=c.next())v=f(a,v.value,l),v!==null&&(s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return I&&Mi(a,g),u}for(h=r(h);!v.done;g++,v=c.next())v=m(h,a,g,v.value,l),v!==null&&(e&&v.alternate!==null&&h.delete(v.key===null?g:v.key),s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return e&&h.forEach(function(e){return t(a,e)}),I&&Mi(a,g),u}function b(e,r,o,c){if(typeof o==`object`&&o&&o.type===y&&o.key===null&&(o=o.props.children),typeof o==`object`&&o){switch(o.$$typeof){case _:a:{for(var l=o.key;r!==null;){if(r.key===l){if(l=o.type,l===y){if(r.tag===7){n(e,r.sibling),c=a(r,o.props.children),c.return=e,e=c;break a}}else if(r.elementType===l||typeof l==`object`&&l&&l.$$typeof===O&&Aa(l)===r.type){n(e,r.sibling),c=a(r,o.props),Ia(c,o),c.return=e,e=c;break a}n(e,r);break}else t(e,r);r=r.sibling}o.type===y?(c=_i(o.props.children,e.mode,c,o.key),c.return=e,e=c):(c=gi(o.type,o.key,o.props,null,e.mode,c),Ia(c,o),c.return=e,e=c)}return s(e);case v:a:{for(l=o.key;r!==null;){if(r.key===l)if(r.tag===4&&r.stateNode.containerInfo===o.containerInfo&&r.stateNode.implementation===o.implementation){n(e,r.sibling),c=a(r,o.children||[]),c.return=e,e=c;break a}else{n(e,r);break}else t(e,r);r=r.sibling}c=bi(o,e.mode,c),c.return=e,e=c}return s(e);case O:return o=Aa(o),b(e,r,o,c)}if(oe(o))return h(e,r,o,c);if(re(o)){if(l=re(o),typeof l!=`function`)throw Error(i(150));return o=l.call(o),g(e,r,o,c)}if(typeof o.then==`function`)return b(e,r,Fa(o),c);if(o.$$typeof===C)return b(e,r,ia(e,o),c);La(e,o)}return typeof o==`string`&&o!==``||typeof o==`number`||typeof o==`bigint`?(o=``+o,r!==null&&r.tag===6?(n(e,r.sibling),c=a(r,o),c.return=e,e=c):(n(e,r),c=vi(o,e.mode,c),c.return=e,e=c),s(e)):n(e,r)}return function(e,t,n,r){try{Pa=0;var i=b(e,t,n,r);return R=null,i}catch(t){if(t===wa||t===Ea)throw t;var a=fi(29,t,null,e.mode);return a.lanes=r,a.return=e,a}}}var za=Ra(!0),Ba=Ra(!1),Va=!1;function Ha(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Ua(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function Wa(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function Ga(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,G&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,t=li(e),ci(e,null,n),t}return ai(e,r,t,n),li(e)}function Ka(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,n&4194048)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,ot(e,n)}}function qa(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var o={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};a===null?i=a=o:a=a.next=o,n=n.next}while(n!==null);a===null?i=a=t:a=a.next=t}else i=a=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:a,shared:r.shared,callbacks:r.callbacks},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var Ja=!1;function Ya(){if(Ja){var e=ha;if(e!==null)throw e}}function Xa(e,t,n,r){Ja=!1;var i=e.updateQueue;Va=!1;var a=i.firstBaseUpdate,o=i.lastBaseUpdate,s=i.shared.pending;if(s!==null){i.shared.pending=null;var c=s,l=c.next;c.next=null,o===null?a=l:o.next=l,o=c;var u=e.alternate;u!==null&&(u=u.updateQueue,s=u.lastBaseUpdate,s!==o&&(s===null?u.firstBaseUpdate=l:s.next=l,u.lastBaseUpdate=c))}if(a!==null){var d=i.baseState;o=0,u=l=c=null,s=a;do{var f=s.lane&-536870913,p=f!==s.lane;if(p?(J&f)===f:(r&f)===f){f!==0&&f===ma&&(Ja=!0),u!==null&&(u=u.next={lane:0,tag:s.tag,payload:s.payload,callback:null,next:null});a:{var h=e,g=s;f=t;var _=n;switch(g.tag){case 1:if(h=g.payload,typeof h==`function`){d=h.call(_,d,f);break a}d=h;break a;case 3:h.flags=h.flags&-65537|128;case 0:if(h=g.payload,f=typeof h==`function`?h.call(_,d,f):h,f==null)break a;d=m({},d,f);break a;case 2:Va=!0}}f=s.callback,f!==null&&(e.flags|=64,p&&(e.flags|=8192),p=i.callbacks,p===null?i.callbacks=[f]:p.push(f))}else p={lane:f,tag:s.tag,payload:s.payload,callback:s.callback,next:null},u===null?(l=u=p,c=d):u=u.next=p,o|=f;if(s=s.next,s===null){if(s=i.shared.pending,s===null)break;p=s,s=p.next,p.next=null,i.lastBaseUpdate=p,i.shared.pending=null}}while(1);u===null&&(c=d),i.baseState=c,i.firstBaseUpdate=l,i.lastBaseUpdate=u,a===null&&(i.shared.lanes=0),Kl|=o,e.lanes=o,e.memoizedState=d}}function Za(e,t){if(typeof e!=`function`)throw Error(i(191,e));e.call(t)}function Qa(e,t){var n=e.callbacks;if(n!==null)for(e.callbacks=null,e=0;ea?a:8;var o=k.T,s={};k.T=s,Is(e,!1,t,n);try{var c=i(),l=k.S;l!==null&&l(s,c),typeof c==`object`&&c&&typeof c.then==`function`?Fs(e,t,va(c,r),mu(e)):Fs(e,t,r,mu(e))}catch(n){Fs(e,t,{then:function(){},status:`rejected`,reason:n},mu())}finally{A.p=a,o!==null&&s.types!==null&&(o.types=s.types),k.T=o}}function Ts(){}function Es(e,t,n,r){if(e.tag!==5)throw Error(i(476));var a=Ds(e).queue;ws(e,a,t,se,n===null?Ts:function(){return Os(e),n(r)})}function Ds(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:se,baseState:se,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Lo,lastRenderedState:se},next:null};var n={};return t.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Lo,lastRenderedState:n},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function Os(e){var t=Ds(e);t.next===null&&(t=e.alternate.memoizedState),Fs(e,t.next.queue,{},mu())}function ks(){return L($f)}function As(){return Mo().memoizedState}function js(){return Mo().memoizedState}function Ms(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var n=mu();e=Wa(n);var r=Ga(t,e,n);r!==null&&(gu(r,t,n),Ka(r,t,n)),t={cache:ua()},e.payload=t;return}t=t.return}}function Ns(e,t,n){var r=mu();n={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},Ls(e)?Rs(t,n):(n=oi(e,t,n,r),n!==null&&(gu(n,e,r),H(n,t,r)))}function Ps(e,t,n){Fs(e,t,n,mu())}function Fs(e,t,n,r){var i={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(Ls(e))Rs(t,i);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var o=t.lastRenderedState,s=a(o,n);if(i.hasEagerState=!0,i.eagerState=s,Er(s,o))return ai(e,t,i,0),K===null&&ii(),!1}catch{}if(n=oi(e,t,i,r),n!==null)return gu(n,e,r),H(n,t,r),!0}return!1}function Is(e,t,n,r){if(r={lane:2,revertLane:fd(),gesture:null,action:r,hasEagerState:!1,eagerState:null,next:null},Ls(e)){if(t)throw Error(i(479))}else t=oi(e,n,r,2),t!==null&&gu(t,e,2)}function Ls(e){var t=e.alternate;return e===z||t!==null&&t===z}function Rs(e,t){_o=go=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function H(e,t,n){if(n&4194048){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,ot(e,n)}}var zs={readContext:L,use:Fo,useCallback:So,useContext:So,useEffect:So,useImperativeHandle:So,useLayoutEffect:So,useInsertionEffect:So,useMemo:So,useReducer:So,useRef:So,useState:So,useDebugValue:So,useDeferredValue:So,useTransition:So,useSyncExternalStore:So,useId:So,useHostTransitionStatus:So,useFormState:So,useActionState:So,useOptimistic:So,useMemoCache:So,useCacheRefresh:So};zs.useEffectEvent=So;var Bs={readContext:L,use:Fo,useCallback:function(e,t){return jo().memoizedState=[e,t===void 0?null:t],e},useContext:L,useEffect:ds,useImperativeHandle:function(e,t,n){n=n==null?null:n.concat([e]),ls(4194308,4,_s.bind(null,t,e),n)},useLayoutEffect:function(e,t){return ls(4194308,4,e,t)},useInsertionEffect:function(e,t){ls(4,2,e,t)},useMemo:function(e,t){var n=jo();t=t===void 0?null:t;var r=e();if(vo){Ue(!0);try{e()}finally{Ue(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=jo();if(n!==void 0){var i=n(t);if(vo){Ue(!0);try{n(t)}finally{Ue(!1)}}}else i=t;return r.memoizedState=r.baseState=i,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:i},r.queue=e,e=e.dispatch=Ns.bind(null,z,e),[r.memoizedState,e]},useRef:function(e){var t=jo();return e={current:e},t.memoizedState=e},useState:function(e){e=qo(e);var t=e.queue,n=Ps.bind(null,z,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:ys,useDeferredValue:function(e,t){return Ss(jo(),e,t)},useTransition:function(){var e=qo(!1);return e=ws.bind(null,z,e.queue,!0,!1),jo().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=z,a=jo();if(I){if(n===void 0)throw Error(i(407));n=n()}else{if(n=t(),K===null)throw Error(i(349));J&127||Ho(r,t,n)}a.memoizedState=n;var o={value:n,getSnapshot:t};return a.queue=o,ds(Wo.bind(null,r,o,e),[e]),r.flags|=2048,ss(9,{destroy:void 0},Uo.bind(null,r,o,n,t),null),n},useId:function(){var e=jo(),t=K.identifierPrefix;if(I){var n=ji,r=Ai;n=(r&~(1<<32-We(r)-1)).toString(32)+n,t=`_`+t+`R_`+n,n=yo++,0<\/script>`,o=o.removeChild(o.firstChild);break;case`select`:o=typeof r.is==`string`?s.createElement(`select`,{is:r.is}):s.createElement(`select`),r.multiple?o.multiple=!0:r.size&&(o.size=r.size);break;default:o=typeof r.is==`string`?s.createElement(a,{is:r.is}):s.createElement(a)}}o[ft]=t,o[pt]=r;a:for(s=t.child;s!==null;){if(s.tag===5||s.tag===6)o.appendChild(s.stateNode);else if(s.tag!==4&&s.tag!==27&&s.child!==null){s.child.return=s,s=s.child;continue}if(s===t)break a;for(;s.sibling===null;){if(s.return===null||s.return===t)break a;s=s.return}s.sibling.return=s.return,s=s.sibling}t.stateNode=o;a:switch(Fd(o,a,r),a){case`button`:case`input`:case`select`:case`textarea`:r=!!r.autoFocus;break a;case`img`:r=!0;break a;default:r=!1}r&&Nc(t)}}return Rc(t),Pc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==r&&Nc(t);else{if(typeof r!=`string`&&t.stateNode===null)throw Error(i(166));if(e=me.current,Wi(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,a=Li,a!==null)switch(a.tag){case 27:case 5:r=a.memoizedProps}e[ft]=t,e=!!(e.nodeValue===n||r!==null&&!0===r.suppressHydrationWarning||Nd(e.nodeValue,n)),e||Vi(t,!0)}else e=Vd(e).createTextNode(r),e[ft]=t,t.stateNode=e}return Rc(t),null;case 31:if(n=t.memoizedState,e===null||e.memoizedState!==null){if(r=Wi(t),n!==null){if(e===null){if(!r)throw Error(i(318));if(e=t.memoizedState,e=e===null?null:e.dehydrated,!e)throw Error(i(557));e[ft]=t}else Gi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;Rc(t),e=!1}else n=Ki(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e)return t.flags&256?(uo(t),t):(uo(t),null);if(t.flags&128)throw Error(i(558))}return Rc(t),null;case 13:if(r=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(a=Wi(t),r!==null&&r.dehydrated!==null){if(e===null){if(!a)throw Error(i(318));if(a=t.memoizedState,a=a===null?null:a.dehydrated,!a)throw Error(i(317));a[ft]=t}else Gi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;Rc(t),a=!1}else a=Ki(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=a),a=!0;if(!a)return t.flags&256?(uo(t),t):(uo(t),null)}return uo(t),t.flags&128?(t.lanes=n,t):(n=r!==null,e=e!==null&&e.memoizedState!==null,n&&(r=t.child,a=null,r.alternate!==null&&r.alternate.memoizedState!==null&&r.alternate.memoizedState.cachePool!==null&&(a=r.alternate.memoizedState.cachePool.pool),o=null,r.memoizedState!==null&&r.memoizedState.cachePool!==null&&(o=r.memoizedState.cachePool.pool),o!==a&&(r.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),Ic(t,t.updateQueue),Rc(t),null);case 4:return _e(),e===null&&Cd(t.stateNode.containerInfo),Rc(t),null;case 10:return Qi(t.type),Rc(t),null;case 19:if(de(fo),r=t.memoizedState,r===null)return Rc(t),null;if(a=(t.flags&128)!=0,o=r.rendering,o===null)if(a)Lc(r,!1);else{if(Gl!==0||e!==null&&e.flags&128)for(e=t.child;e!==null;){if(o=po(e),o!==null){for(t.flags|=128,Lc(r,!1),e=o.updateQueue,t.updateQueue=e,Ic(t,e),t.subtreeFlags=0,e=n,n=t.child;n!==null;)hi(n,e),n=n.sibling;return j(fo,fo.current&1|2),I&&Mi(t,r.treeForkCount),t.child}e=e.sibling}r.tail!==null&&Me()>nu&&(t.flags|=128,a=!0,Lc(r,!1),t.lanes=4194304)}else{if(!a)if(e=po(o),e!==null){if(t.flags|=128,a=!0,e=e.updateQueue,t.updateQueue=e,Ic(t,e),Lc(r,!0),r.tail===null&&r.tailMode===`hidden`&&!o.alternate&&!I)return Rc(t),null}else 2*Me()-r.renderingStartTime>nu&&n!==536870912&&(t.flags|=128,a=!0,Lc(r,!1),t.lanes=4194304);r.isBackwards?(o.sibling=t.child,t.child=o):(e=r.last,e===null?t.child=o:e.sibling=o,r.last=o)}return r.tail===null?(Rc(t),null):(e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=Me(),e.sibling=null,n=fo.current,j(fo,a?n&1|2:n&1),I&&Mi(t,r.treeForkCount),e);case 22:case 23:return uo(t),ro(),r=t.memoizedState!==null,e===null?r&&(t.flags|=8192):e.memoizedState!==null!==r&&(t.flags|=8192),r?n&536870912&&!(t.flags&128)&&(Rc(t),t.subtreeFlags&6&&(t.flags|=8192)):Rc(t),n=t.updateQueue,n!==null&&Ic(t,n.retryQueue),n=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(n=e.memoizedState.cachePool.pool),r=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),e!==null&&de(ba),null;case 24:return n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),Qi(la),Rc(t),null;case 25:return null;case 30:return null}throw Error(i(156,t.tag))}function Bc(e,t){switch(Fi(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Qi(la),_e(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return ye(t),null;case 31:if(t.memoizedState!==null){if(uo(t),t.alternate===null)throw Error(i(340));Gi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(uo(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(i(340));Gi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return de(fo),null;case 4:return _e(),null;case 10:return Qi(t.type),null;case 22:case 23:return uo(t),ro(),e!==null&&de(ba),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return Qi(la),null;case 25:return null;default:return null}}function Vc(e,t){switch(Fi(t),t.tag){case 3:Qi(la),_e();break;case 26:case 27:case 5:ye(t);break;case 4:_e();break;case 31:t.memoizedState!==null&&uo(t);break;case 13:uo(t);break;case 19:de(fo);break;case 10:Qi(t.type);break;case 22:case 23:uo(t),ro(),e!==null&&de(ba);break;case 24:Qi(la)}}function Hc(e,t){try{var n=t.updateQueue,r=n===null?null:n.lastEffect;if(r!==null){var i=r.next;n=i;do{if((n.tag&e)===e){r=void 0;var a=n.create,o=n.inst;r=a(),o.destroy=r}n=n.next}while(n!==i)}}catch(e){X(t,t.return,e)}}function Uc(e,t,n){try{var r=t.updateQueue,i=r===null?null:r.lastEffect;if(i!==null){var a=i.next;r=a;do{if((r.tag&e)===e){var o=r.inst,s=o.destroy;if(s!==void 0){o.destroy=void 0,i=t;var c=n,l=s;try{l()}catch(e){X(i,c,e)}}}r=r.next}while(r!==a)}}catch(e){X(t,t.return,e)}}function Wc(e){var t=e.updateQueue;if(t!==null){var n=e.stateNode;try{Qa(t,n)}catch(t){X(e,e.return,t)}}}function Gc(e,t,n){n.props=Ks(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){X(e,t,n)}}function Kc(e,t){try{var n=e.ref;if(n!==null){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;case 30:r=e.stateNode;break;default:r=e.stateNode}typeof n==`function`?e.refCleanup=n(r):n.current=r}}catch(n){X(e,t,n)}}function qc(e,t){var n=e.ref,r=e.refCleanup;if(n!==null)if(typeof r==`function`)try{r()}catch(n){X(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof n==`function`)try{n(null)}catch(n){X(e,t,n)}else n.current=null}function Jc(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{a:switch(t){case`button`:case`input`:case`select`:case`textarea`:n.autoFocus&&r.focus();break a;case`img`:n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){X(e,e.return,t)}}function Yc(e,t,n){try{var r=e.stateNode;Id(r,e.type,n,t),r[pt]=t}catch(t){X(e,e.return,t)}}function Xc(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&Qd(e.type)||e.tag===4}function Zc(e){a:for(;;){for(;e.sibling===null;){if(e.return===null||Xc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&Qd(e.type)||e.flags&2||e.child===null||e.tag===4)continue a;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Qc(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?(n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n).insertBefore(e,t):(t=n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n,t.appendChild(e),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=on));else if(r!==4&&(r===27&&Qd(e.type)&&(n=e.stateNode,t=null),e=e.child,e!==null))for(Qc(e,t,n),e=e.sibling;e!==null;)Qc(e,t,n),e=e.sibling}function $c(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(r===27&&Qd(e.type)&&(n=e.stateNode),e=e.child,e!==null))for($c(e,t,n),e=e.sibling;e!==null;)$c(e,t,n),e=e.sibling}function el(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,i=t.attributes;i.length;)t.removeAttributeNode(i[0]);Fd(t,r,n),t[ft]=e,t[pt]=n}catch(t){X(e,e.return,t)}}var tl=!1,nl=!1,rl=!1,il=typeof WeakSet==`function`?WeakSet:Set,al=null;function ol(e,t){if(e=e.containerInfo,zd=cp,e=jr(e),Mr(e)){if(`selectionStart`in e)var n={start:e.selectionStart,end:e.selectionEnd};else a:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var a=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break a}var s=0,c=-1,l=-1,u=0,d=0,f=e,p=null;b:for(;;){for(var m;f!==n||a!==0&&f.nodeType!==3||(c=s+a),f!==o||r!==0&&f.nodeType!==3||(l=s+r),f.nodeType===3&&(s+=f.nodeValue.length),(m=f.firstChild)!==null;)p=f,f=m;for(;;){if(f===e)break b;if(p===n&&++u===a&&(c=s),p===o&&++d===r&&(l=s),(m=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=m}n=c===-1||l===-1?null:{start:c,end:l}}else n=null}n=n||{start:0,end:0}}else n=null;for(Bd={focusedElem:e,selectionRange:n},cp=!1,al=t;al!==null;)if(t=al,e=t.child,t.subtreeFlags&1028&&e!==null)e.return=t,al=e;else for(;al!==null;){switch(t=al,o=t.alternate,e=t.flags,t.tag){case 0:if(e&4&&(e=t.updateQueue,e=e===null?null:e.events,e!==null))for(n=0;n title`))),Fd(o,r,n),o[ft]=e,Tt(o),r=o;break a;case`link`:var s=Hf(`link`,`href`,a).get(r+(n.href||``));if(s){for(var c=0;cg&&(o=g,g=h,h=o);var _=kr(s,h),v=kr(s,g);if(_&&v&&(p.rangeCount!==1||p.anchorNode!==_.node||p.anchorOffset!==_.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var y=d.createRange();y.setStart(_.node,_.offset),p.removeAllRanges(),h>g?(p.addRange(y),p.extend(v.node,v.offset)):(y.setEnd(v.node,v.offset),p.addRange(y))}}}}for(d=[],p=s;p=p.parentNode;)p.nodeType===1&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for(typeof s.focus==`function`&&s.focus(),s=0;sn?32:n,k.T=null,n=uu,uu=null;var o=ou,s=cu;if(au=0,su=ou=null,cu=0,G&6)throw Error(i(331));var c=G;if(G|=4,Fl(o.current),Dl(o,o.current,s,n),G=c,ad(0,!1),He&&typeof He.onPostCommitFiberRoot==`function`)try{He.onPostCommitFiberRoot(Ve,o)}catch{}return!0}finally{A.p=a,k.T=r,Hu(e,t)}}function Gu(e,t,n){t=Si(n,t),t=Qs(e.stateNode,t,2),e=Ga(e,t,2),e!==null&&(rt(e,2),id(e))}function X(e,t,n){if(e.tag===3)Gu(e,e,n);else for(;t!==null;){if(t.tag===3){Gu(t,e,n);break}else if(t.tag===1){var r=t.stateNode;if(typeof t.type.getDerivedStateFromError==`function`||typeof r.componentDidCatch==`function`&&(iu===null||!iu.has(r))){e=Si(n,e),n=$s(2),r=Ga(t,n,2),r!==null&&(ec(n,r,t,e),rt(r,2),id(r));break}}t=t.return}}function Ku(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new zl;var i=new Set;r.set(t,i)}else i=r.get(t),i===void 0&&(i=new Set,r.set(t,i));i.has(n)||(Ul=!0,i.add(n),e=qu.bind(null,e,t,n),t.then(e,e))}function qu(e,t,n){var r=e.pingCache;r!==null&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,K===e&&(J&n)===n&&(Gl===4||Gl===3&&(J&62914560)===J&&300>Me()-eu?!(G&2)&&Cu(e,0):Jl|=n,Xl===J&&(Xl=0)),id(e)}function Ju(e,t){t===0&&(t=tt()),e=si(e,t),e!==null&&(rt(e,t),id(e))}function Yu(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Ju(e,n)}function Xu(e,t){var n=0;switch(e.tag){case 31:case 13:var r=e.stateNode,a=e.memoizedState;a!==null&&(n=a.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(i(314))}r!==null&&r.delete(t),Ju(e,n)}function Zu(e,t){return Oe(e,t)}var Qu=null,$u=null,ed=!1,td=!1,nd=!1,rd=0;function id(e){e!==$u&&e.next===null&&($u===null?Qu=$u=e:$u=$u.next=e),td=!0,ed||(ed=!0,dd())}function ad(e,t){if(!nd&&td){nd=!0;do for(var n=!1,r=Qu;r!==null;){if(!t)if(e!==0){var i=r.pendingLanes;if(i===0)var a=0;else{var o=r.suspendedLanes,s=r.pingedLanes;a=(1<<31-We(42|e)+1)-1,a&=i&~(o&~s),a=a&201326741?a&201326741|1:a?a|2:0}a!==0&&(n=!0,ud(r,a))}else a=J,a=Qe(r,r===K?a:0,r.cancelPendingCommit!==null||r.timeoutHandle!==-1),!(a&3)||$e(r,a)||(n=!0,ud(r,a));r=r.next}while(n);nd=!1}}function od(){sd()}function sd(){td=ed=!1;var e=0;rd!==0&&Kd()&&(e=rd);for(var t=Me(),n=null,r=Qu;r!==null;){var i=r.next,a=cd(r,t);a===0?(r.next=null,n===null?Qu=i:n.next=i,i===null&&($u=n)):(n=r,(e!==0||a&3)&&(td=!0)),r=i}au!==0&&au!==5||ad(e,!1),rd!==0&&(rd=0)}function cd(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,i=e.expirationTimes,a=e.pendingLanes&-62914561;0s)break;var u=c.transferSize,d=c.initiatorType;u&&Ld(d)&&(c=c.responseEnd,o+=u*(c`u`?null:document;function Sf(e,t,n){var r=xf;if(r&&typeof t==`string`&&t){var i=Wt(t);i=`link[rel="`+e+`"][href="`+i+`"]`,typeof n==`string`&&(i+=`[crossorigin="`+n+`"]`),gf.has(i)||(gf.add(i),e={rel:e,crossOrigin:n,href:t},r.querySelector(i)===null&&(t=r.createElement(`link`),Fd(t,`link`,e),Tt(t),r.head.appendChild(t)))}}function Cf(e){vf.D(e),Sf(`dns-prefetch`,e,null)}function wf(e,t){vf.C(e,t),Sf(`preconnect`,e,t)}function Tf(e,t,n){vf.L(e,t,n);var r=xf;if(r&&e&&t){var i=`link[rel="preload"][as="`+Wt(t)+`"]`;t===`image`&&n&&n.imageSrcSet?(i+=`[imagesrcset="`+Wt(n.imageSrcSet)+`"]`,typeof n.imageSizes==`string`&&(i+=`[imagesizes="`+Wt(n.imageSizes)+`"]`)):i+=`[href="`+Wt(e)+`"]`;var a=i;switch(t){case`style`:a=jf(e);break;case`script`:a=Ff(e)}hf.has(a)||(e=m({rel:`preload`,href:t===`image`&&n&&n.imageSrcSet?void 0:e,as:t},n),hf.set(a,e),r.querySelector(i)!==null||t===`style`&&r.querySelector(Mf(a))||t===`script`&&r.querySelector(If(a))||(t=r.createElement(`link`),Fd(t,`link`,e),Tt(t),r.head.appendChild(t)))}}function Ef(e,t){vf.m(e,t);var n=xf;if(n&&e){var r=t&&typeof t.as==`string`?t.as:`script`,i=`link[rel="modulepreload"][as="`+Wt(r)+`"][href="`+Wt(e)+`"]`,a=i;switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:a=Ff(e)}if(!hf.has(a)&&(e=m({rel:`modulepreload`,href:e},t),hf.set(a,e),n.querySelector(i)===null)){switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:if(n.querySelector(If(a)))return}r=n.createElement(`link`),Fd(r,`link`,e),Tt(r),n.head.appendChild(r)}}}function Df(e,t,n){vf.S(e,t,n);var r=xf;if(r&&e){var i=wt(r).hoistableStyles,a=jf(e);t=t||`default`;var o=i.get(a);if(!o){var s={loading:0,preload:null};if(o=r.querySelector(Mf(a)))s.loading=5;else{e=m({rel:`stylesheet`,href:e,"data-precedence":t},n),(n=hf.get(a))&&zf(e,n);var c=o=r.createElement(`link`);Tt(c),Fd(c,`link`,e),c._p=new Promise(function(e,t){c.onload=e,c.onerror=t}),c.addEventListener(`load`,function(){s.loading|=1}),c.addEventListener(`error`,function(){s.loading|=2}),s.loading|=4,Rf(o,t,r)}o={type:`stylesheet`,instance:o,count:1,state:s},i.set(a,o)}}}function Of(e,t){vf.X(e,t);var n=xf;if(n&&e){var r=wt(n).hoistableScripts,i=Ff(e),a=r.get(i);a||(a=n.querySelector(If(i)),a||(e=m({src:e,async:!0},t),(t=hf.get(i))&&Bf(e,t),a=n.createElement(`script`),Tt(a),Fd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function kf(e,t){vf.M(e,t);var n=xf;if(n&&e){var r=wt(n).hoistableScripts,i=Ff(e),a=r.get(i);a||(a=n.querySelector(If(i)),a||(e=m({src:e,async:!0,type:`module`},t),(t=hf.get(i))&&Bf(e,t),a=n.createElement(`script`),Tt(a),Fd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function Af(e,t,n,r){var a=(a=me.current)?_f(a):null;if(!a)throw Error(i(446));switch(e){case`meta`:case`title`:return null;case`style`:return typeof n.precedence==`string`&&typeof n.href==`string`?(t=jf(n.href),n=wt(a).hoistableStyles,r=n.get(t),r||(r={type:`style`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};case`link`:if(n.rel===`stylesheet`&&typeof n.href==`string`&&typeof n.precedence==`string`){e=jf(n.href);var o=wt(a).hoistableStyles,s=o.get(e);if(s||(a=a.ownerDocument||a,s={type:`stylesheet`,instance:null,count:0,state:{loading:0,preload:null}},o.set(e,s),(o=a.querySelector(Mf(e)))&&!o._p&&(s.instance=o,s.state.loading=5),hf.has(e)||(n={rel:`preload`,as:`style`,href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},hf.set(e,n),o||Pf(a,e,n,s.state))),t&&r===null)throw Error(i(528,``));return s}if(t&&r!==null)throw Error(i(529,``));return null;case`script`:return t=n.async,n=n.src,typeof n==`string`&&t&&typeof t!=`function`&&typeof t!=`symbol`?(t=Ff(n),n=wt(a).hoistableScripts,r=n.get(t),r||(r={type:`script`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};default:throw Error(i(444,e))}}function jf(e){return`href="`+Wt(e)+`"`}function Mf(e){return`link[rel="stylesheet"][`+e+`]`}function Nf(e){return m({},e,{"data-precedence":e.precedence,precedence:null})}function Pf(e,t,n,r){e.querySelector(`link[rel="preload"][as="style"][`+t+`]`)?r.loading=1:(t=e.createElement(`link`),r.preload=t,t.addEventListener(`load`,function(){return r.loading|=1}),t.addEventListener(`error`,function(){return r.loading|=2}),Fd(t,`link`,n),Tt(t),e.head.appendChild(t))}function Ff(e){return`[src="`+Wt(e)+`"]`}function If(e){return`script[async]`+e}function Lf(e,t,n){if(t.count++,t.instance===null)switch(t.type){case`style`:var r=e.querySelector(`style[data-href~="`+Wt(n.href)+`"]`);if(r)return t.instance=r,Tt(r),r;var a=m({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return r=(e.ownerDocument||e).createElement(`style`),Tt(r),Fd(r,`style`,a),Rf(r,n.precedence,e),t.instance=r;case`stylesheet`:a=jf(n.href);var o=e.querySelector(Mf(a));if(o)return t.state.loading|=4,t.instance=o,Tt(o),o;r=Nf(n),(a=hf.get(a))&&zf(r,a),o=(e.ownerDocument||e).createElement(`link`),Tt(o);var s=o;return s._p=new Promise(function(e,t){s.onload=e,s.onerror=t}),Fd(o,`link`,r),t.state.loading|=4,Rf(o,n.precedence,e),t.instance=o;case`script`:return o=Ff(n.src),(a=e.querySelector(If(o)))?(t.instance=a,Tt(a),a):(r=n,(a=hf.get(o))&&(r=m({},n),Bf(r,a)),e=e.ownerDocument||e,a=e.createElement(`script`),Tt(a),Fd(a,`link`,r),e.head.appendChild(a),t.instance=a);case`void`:return null;default:throw Error(i(443,t.type))}else t.type===`stylesheet`&&!(t.state.loading&4)&&(r=t.instance,t.state.loading|=4,Rf(r,n.precedence,e));return t.instance}function Rf(e,t,n){for(var r=n.querySelectorAll(`link[rel="stylesheet"][data-precedence],style[data-precedence]`),i=r.length?r[r.length-1]:null,a=i,o=0;o title`):null)}function Wf(e,t,n){if(n===1||t.itemProp!=null)return!1;switch(e){case`meta`:case`title`:return!0;case`style`:if(typeof t.precedence!=`string`||typeof t.href!=`string`||t.href===``)break;return!0;case`link`:if(typeof t.rel!=`string`||typeof t.href!=`string`||t.href===``||t.onLoad||t.onError)break;switch(t.rel){case`stylesheet`:return e=t.disabled,typeof t.precedence==`string`&&e==null;default:return!0}case`script`:if(t.async&&typeof t.async!=`function`&&typeof t.async!=`symbol`&&!t.onLoad&&!t.onError&&t.src&&typeof t.src==`string`)return!0}return!1}function Gf(e){return!(e.type===`stylesheet`&&!(e.state.loading&3))}function Kf(e,t,n,r){if(n.type===`stylesheet`&&(typeof r.media!=`string`||!1!==matchMedia(r.media).matches)&&!(n.state.loading&4)){if(n.instance===null){var i=jf(r.href),a=t.querySelector(Mf(i));if(a){t=a._p,typeof t==`object`&&t&&typeof t.then==`function`&&(e.count++,e=Yf.bind(e),t.then(e,e)),n.state.loading|=4,n.instance=a,Tt(a);return}a=t.ownerDocument||t,r=Nf(r),(i=hf.get(i))&&zf(r,i),a=a.createElement(`link`),Tt(a);var o=a;o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),Fd(a,`link`,r),n.instance=a}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(n,t),(t=n.state.preload)&&!(n.state.loading&3)&&(e.count++,n=Yf.bind(e),t.addEventListener(`load`,n),t.addEventListener(`error`,n))}}var qf=0;function Jf(e,t){return e.stylesheets&&e.count===0&&Zf(e,e.stylesheets),0qf?50:800)+t);return e.unsuspend=n,function(){e.unsuspend=null,clearTimeout(r),clearTimeout(i)}}:null}function Yf(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Zf(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var Xf=null;function Zf(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,Xf=new Map,t.forEach(Qf,e),Xf=null,Yf.call(e))}function Qf(e,t){if(!(t.state.loading&4)){var n=Xf.get(e);if(n)var r=n.get(null);else{n=new Map,Xf.set(e,n);for(var i=e.querySelectorAll(`link[data-precedence],style[data-precedence]`),a=0;a{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=g()})),v=class{constructor(){this.listeners=new Set,this.subscribe=this.subscribe.bind(this)}subscribe(e){return this.listeners.add(e),this.onSubscribe(),()=>{this.listeners.delete(e),this.onUnsubscribe()}}hasListeners(){return this.listeners.size>0}onSubscribe(){}onUnsubscribe(){}};function y(e,t){if(t.has(e))throw TypeError(`Cannot initialize the same private elements twice on an object`)}var b=o((()=>{}));function x(e,t,n){y(e,t),t.set(e,n)}var S=o((()=>{b()}));function C(e,t,n){if(typeof e==`function`?e===t:e.has(t))return arguments.length<3?t:n;throw TypeError(`Private element is not present on this object`)}var w=o((()=>{}));function T(e,t,n){return e.set(C(e,t),n),n}var E=o((()=>{w()}));function D(e,t){return e.get(C(e,t))}var O=o((()=>{w()}));S(),E(),O();var ee,te,ne,re=new(ee=new WeakMap,te=new WeakMap,ne=new WeakMap,class extends v{constructor(){super(),x(this,ee,void 0),x(this,te,void 0),x(this,ne,void 0),T(ne,this,e=>{if(typeof window<`u`&&window.addEventListener){let t=()=>e();return window.addEventListener(`visibilitychange`,t,!1),()=>{window.removeEventListener(`visibilitychange`,t)}}})}onSubscribe(){D(te,this)||this.setEventListener(D(ne,this))}onUnsubscribe(){this.hasListeners()||(D(te,this)?.call(this),T(te,this,void 0))}setEventListener(e){T(ne,this,e),D(te,this)?.call(this),T(te,this,e(e=>{typeof e==`boolean`?this.setFocused(e):this.onFocus()}))}setFocused(e){D(ee,this)!==e&&(T(ee,this,e),this.onFocus())}onFocus(){let e=this.isFocused();this.listeners.forEach(t=>{t(e)})}isFocused(){return typeof D(ee,this)==`boolean`?D(ee,this):globalThis.document?.visibilityState!==`hidden`}});S(),O(),E();var ie,ae,oe={setTimeout:(e,t)=>setTimeout(e,t),clearTimeout:e=>clearTimeout(e),setInterval:(e,t)=>setInterval(e,t),clearInterval:e=>clearInterval(e)},k=new(ie=new WeakMap,ae=new WeakMap,class{constructor(){x(this,ie,oe),x(this,ae,!1)}setTimeoutProvider(e){T(ie,this,e)}setTimeout(e,t){return D(ie,this).setTimeout(e,t)}clearTimeout(e){D(ie,this).clearTimeout(e)}setInterval(e,t){return D(ie,this).setInterval(e,t)}clearInterval(e){D(ie,this).clearInterval(e)}});function A(e){setTimeout(e,0)}var se=typeof window>`u`||`Deno`in globalThis;function ce(){}function le(e,t){return typeof e==`function`?e(t):e}function ue(e){return typeof e==`number`&&e>=0&&e!==1/0}function de(e,t){return Math.max(e+(t||0)-Date.now(),0)}function j(e,t){return typeof e==`function`?e(t):e}function fe(e,t){return typeof e==`function`?e(t):e}function pe(e,t){let{type:n=`all`,exact:r,fetchStatus:i,predicate:a,queryKey:o,stale:s}=e;if(o){if(r){if(t.queryHash!==he(o,t.options))return!1}else if(!_e(t.queryKey,o))return!1}if(n!==`all`){let e=t.isActive();if(n===`active`&&!e||n===`inactive`&&e)return!1}return!(typeof s==`boolean`&&t.isStale()!==s||i&&i!==t.state.fetchStatus||a&&!a(t))}function me(e,t){let{exact:n,status:r,predicate:i,mutationKey:a}=e;if(a){if(!t.options.mutationKey)return!1;if(n){if(ge(t.options.mutationKey)!==ge(a))return!1}else if(!_e(t.options.mutationKey,a))return!1}return!(r&&t.state.status!==r||i&&!i(t))}function he(e,t){return(t?.queryKeyHashFn||ge)(e)}function ge(e){return JSON.stringify(e,(e,t)=>Se(t)?Object.keys(t).sort().reduce((e,n)=>(e[n]=t[n],e),{}):t)}function _e(e,t){return e===t?!0:typeof e==typeof t&&e&&t&&typeof e==`object`&&typeof t==`object`?Object.keys(t).every(n=>_e(e[n],t[n])):!1}var ve=Object.prototype.hasOwnProperty;function ye(e,t,n=0){if(e===t)return e;if(n>500)return t;let r=xe(e)&&xe(t);if(!r&&!(Se(e)&&Se(t)))return t;let i=(r?e:Object.keys(e)).length,a=r?t:Object.keys(t),o=a.length,s=r?Array(o):{},c=0;for(let l=0;l{k.setTimeout(t,e)})}function Te(e,t,n){return typeof n.structuralSharing==`function`?n.structuralSharing(e,t):n.structuralSharing===!1?t:ye(e,t)}function Ee(e,t,n=0){let r=[...e,t];return n&&r.length>n?r.slice(1):r}function De(e,t,n=0){let r=[t,...e];return n&&r.length>n?r.slice(0,-1):r}var Oe=Symbol();function ke(e,t){return!e.queryFn&&t?.initialPromise?()=>t.initialPromise:!e.queryFn||e.queryFn===Oe?()=>Promise.reject(Error(`Missing queryFn: '${e.queryHash}'`)):e.queryFn}function Ae(e,t){return typeof e==`function`?e(...t):!!e}function je(e,t,n){let r=!1,i;return Object.defineProperty(e,"signal",{enumerable:!0,get:()=>(i??(i=t()),r?i:(r=!0,i.aborted?n():i.addEventListener(`abort`,n,{once:!0}),i))}),e}var Me=(()=>{let e=()=>se;return{isServer(){return e()},setIsServer(t){e=t}}})();function Ne(){let e,t,n=new Promise((n,r)=>{e=n,t=r});n.status=`pending`,n.catch(()=>{});function r(e){Object.assign(n,e),delete n.resolve,delete n.reject}return n.resolve=t=>{r({status:`fulfilled`,value:t}),e(t)},n.reject=e=>{r({status:`rejected`,reason:e}),t(e)},n}var Pe=A;function Fe(){let e=[],t=0,n=e=>{e()},r=e=>{e()},i=Pe,a=r=>{t?e.push(r):i(()=>{n(r)})},o=()=>{let t=e;e=[],t.length&&i(()=>{r(()=>{t.forEach(e=>{n(e)})})})};return{batch:e=>{let n;t++;try{n=e()}finally{t--,t||o()}return n},batchCalls:e=>(...t)=>{a(()=>{e(...t)})},schedule:a,setNotifyFunction:e=>{n=e},setBatchNotifyFunction:e=>{r=e},setScheduler:e=>{i=e}}}var Ie=Fe();S(),E(),O();var Le,Re,ze,Be=new(Le=new WeakMap,Re=new WeakMap,ze=new WeakMap,class extends v{constructor(){super(),x(this,Le,!0),x(this,Re,void 0),x(this,ze,void 0),T(ze,this,e=>{if(typeof window<`u`&&window.addEventListener){let t=()=>e(!0),n=()=>e(!1);return window.addEventListener(`online`,t,!1),window.addEventListener(`offline`,n,!1),()=>{window.removeEventListener(`online`,t),window.removeEventListener(`offline`,n)}}})}onSubscribe(){D(Re,this)||this.setEventListener(D(ze,this))}onUnsubscribe(){this.hasListeners()||(D(Re,this)?.call(this),T(Re,this,void 0))}setEventListener(e){T(ze,this,e),D(Re,this)?.call(this),T(Re,this,e(this.setOnline.bind(this)))}setOnline(e){D(Le,this)!==e&&(T(Le,this,e),this.listeners.forEach(t=>{t(e)}))}isOnline(){return D(Le,this)}});function Ve(e){return Math.min(1e3*2**e,3e4)}function He(e){return(e??`online`)===`online`?Be.isOnline():!0}var Ue=class extends Error{constructor(e){super(`CancelledError`),this.revert=e?.revert,this.silent=e?.silent}};function We(e){let t=!1,n=0,r,i=Ne(),a=()=>i.status!==`pending`,o=t=>{if(!a()){let n=new Ue(t);f(n),e.onCancel?.(n)}},s=()=>{t=!0},c=()=>{t=!1},l=()=>re.isFocused()&&(e.networkMode===`always`||Be.isOnline())&&e.canRun(),u=()=>He(e.networkMode)&&e.canRun(),d=e=>{a()||(r?.(),i.resolve(e))},f=e=>{a()||(r?.(),i.reject(e))},p=()=>new Promise(t=>{r=e=>{(a()||l())&&t(e)},e.onPause?.()}).then(()=>{r=void 0,a()||e.onContinue?.()}),m=()=>{if(a())return;let r,i=n===0?e.initialPromise:void 0;try{r=i??e.fn()}catch(e){r=Promise.reject(e)}Promise.resolve(r).then(d).catch(r=>{if(a())return;let i=e.retry??(Me.isServer()?0:3),o=e.retryDelay??Ve,s=typeof o==`function`?o(n,r):o,c=i===!0||typeof i==`number`&&nl()?void 0:p()).then(()=>{t?f(r):m()})})};return{promise:i,status:()=>i.status,cancel:o,continue:()=>(r?.(),i),cancelRetry:s,continueRetry:c,canStart:u,start:()=>(u()?m():p().then(m),i)}}S(),E(),O();var Ge,Ke=(Ge=new WeakMap,class{constructor(){x(this,Ge,void 0)}destroy(){this.clearGcTimeout()}scheduleGc(){this.clearGcTimeout(),ue(this.gcTime)&&T(Ge,this,k.setTimeout(()=>{this.optionalRemove()},this.gcTime))}updateGcTime(e){this.gcTime=Math.max(this.gcTime||0,e??(Me.isServer()?1/0:300*1e3))}clearGcTimeout(){D(Ge,this)&&(k.clearTimeout(D(Ge,this)),T(Ge,this,void 0))}});function qe(e,t){y(e,t),t.add(e)}var Je=o((()=>{b()}));Je(),S(),E(),O(),w();var Ye,Xe,Ze,Qe,$e,et,tt,nt,rt=(Ye=new WeakMap,Xe=new WeakMap,Ze=new WeakMap,Qe=new WeakMap,$e=new WeakMap,et=new WeakMap,tt=new WeakMap,nt=new WeakSet,class extends Ke{constructor(e){super(),qe(this,nt),x(this,Ye,void 0),x(this,Xe,void 0),x(this,Ze,void 0),x(this,Qe,void 0),x(this,$e,void 0),x(this,et,void 0),x(this,tt,void 0),T(tt,this,!1),T(et,this,e.defaultOptions),this.setOptions(e.options),this.observers=[],T(Qe,this,e.client),T(Ze,this,D(Qe,this).getQueryCache()),this.queryKey=e.queryKey,this.queryHash=e.queryHash,T(Ye,this,ct(this.options)),this.state=e.state??D(Ye,this),this.scheduleGc()}get meta(){return this.options.meta}get promise(){return D($e,this)?.promise}setOptions(e){if(this.options={...D(et,this),...e},this.updateGcTime(this.options.gcTime),this.state&&this.state.data===void 0){let e=ct(this.options);e.data!==void 0&&(this.setState(st(e.data,e.dataUpdatedAt)),T(Ye,this,e))}}optionalRemove(){!this.observers.length&&this.state.fetchStatus===`idle`&&D(Ze,this).remove(this)}setData(e,t){let n=Te(this.state.data,e,this.options);return C(nt,this,at).call(this,{data:n,type:`success`,dataUpdatedAt:t?.updatedAt,manual:t?.manual}),n}setState(e,t){C(nt,this,at).call(this,{type:`setState`,state:e,setStateOptions:t})}cancel(e){let t=D($e,this)?.promise;return D($e,this)?.cancel(e),t?t.then(ce).catch(ce):Promise.resolve()}destroy(){super.destroy(),this.cancel({silent:!0})}get resetState(){return D(Ye,this)}reset(){this.destroy(),this.setState(this.resetState)}isActive(){return this.observers.some(e=>fe(e.options.enabled,this)!==!1)}isDisabled(){return this.getObserversCount()>0?!this.isActive():this.options.queryFn===Oe||!this.isFetched()}isFetched(){return this.state.dataUpdateCount+this.state.errorUpdateCount>0}isStatic(){return this.getObserversCount()>0?this.observers.some(e=>j(e.options.staleTime,this)===`static`):!1}isStale(){return this.getObserversCount()>0?this.observers.some(e=>e.getCurrentResult().isStale):this.state.data===void 0||this.state.isInvalidated}isStaleByTime(e=0){return this.state.data===void 0?!0:e===`static`?!1:this.state.isInvalidated?!0:!de(this.state.dataUpdatedAt,e)}onFocus(){this.observers.find(e=>e.shouldFetchOnWindowFocus())?.refetch({cancelRefetch:!1}),D($e,this)?.continue()}onOnline(){this.observers.find(e=>e.shouldFetchOnReconnect())?.refetch({cancelRefetch:!1}),D($e,this)?.continue()}addObserver(e){this.observers.includes(e)||(this.observers.push(e),this.clearGcTimeout(),D(Ze,this).notify({type:`observerAdded`,query:this,observer:e}))}removeObserver(e){this.observers.includes(e)&&(this.observers=this.observers.filter(t=>t!==e),this.observers.length||(D($e,this)&&(D(tt,this)||C(nt,this,it).call(this)?D($e,this).cancel({revert:!0}):D($e,this).cancelRetry()),this.scheduleGc()),D(Ze,this).notify({type:`observerRemoved`,query:this,observer:e}))}getObserversCount(){return this.observers.length}invalidate(){this.state.isInvalidated||C(nt,this,at).call(this,{type:`invalidate`})}async fetch(e,t){if(this.state.fetchStatus!==`idle`&&D($e,this)?.status()!==`rejected`){if(this.state.data!==void 0&&t?.cancelRefetch)this.cancel({silent:!0});else if(D($e,this))return D($e,this).continueRetry(),D($e,this).promise}if(e&&this.setOptions(e),!this.options.queryFn){let e=this.observers.find(e=>e.options.queryFn);e&&this.setOptions(e.options)}let n=new AbortController,r=e=>{Object.defineProperty(e,"signal",{enumerable:!0,get:()=>(T(tt,this,!0),n.signal)})},i=()=>{let e=ke(this.options,t),n=(()=>{let e={client:D(Qe,this),queryKey:this.queryKey,meta:this.meta};return r(e),e})();return T(tt,this,!1),this.options.persister?this.options.persister(e,n,this):e(n)},a=(()=>{let e={fetchOptions:t,options:this.options,queryKey:this.queryKey,client:D(Qe,this),state:this.state,fetchFn:i};return r(e),e})();this.options.behavior?.onFetch(a,this),T(Xe,this,this.state),(this.state.fetchStatus===`idle`||this.state.fetchMeta!==a.fetchOptions?.meta)&&C(nt,this,at).call(this,{type:`fetch`,meta:a.fetchOptions?.meta}),T($e,this,We({initialPromise:t?.initialPromise,fn:a.fetchFn,onCancel:e=>{e instanceof Ue&&e.revert&&this.setState({...D(Xe,this),fetchStatus:`idle`}),n.abort()},onFail:(e,t)=>{C(nt,this,at).call(this,{type:`failed`,failureCount:e,error:t})},onPause:()=>{C(nt,this,at).call(this,{type:`pause`})},onContinue:()=>{C(nt,this,at).call(this,{type:`continue`})},retry:a.options.retry,retryDelay:a.options.retryDelay,networkMode:a.options.networkMode,canRun:()=>!0}));try{let e=await D($e,this).start();if(e===void 0)throw Error(`${this.queryHash} data is undefined`);return this.setData(e),D(Ze,this).config.onSuccess?.(e,this),D(Ze,this).config.onSettled?.(e,this.state.error,this),e}catch(e){if(e instanceof Ue){if(e.silent)return D($e,this).promise;if(e.revert){if(this.state.data===void 0)throw e;return this.state.data}}throw C(nt,this,at).call(this,{type:`error`,error:e}),D(Ze,this).config.onError?.(e,this),D(Ze,this).config.onSettled?.(this.state.data,e,this),e}finally{this.scheduleGc()}}});function it(){return this.state.fetchStatus===`paused`&&this.state.status===`pending`}function at(e){let t=t=>{switch(e.type){case`failed`:return{...t,fetchFailureCount:e.failureCount,fetchFailureReason:e.error};case`pause`:return{...t,fetchStatus:`paused`};case`continue`:return{...t,fetchStatus:`fetching`};case`fetch`:return{...t,...ot(t.data,this.options),fetchMeta:e.meta??null};case`success`:let n={...t,...st(e.data,e.dataUpdatedAt),dataUpdateCount:t.dataUpdateCount+1,...!e.manual&&{fetchStatus:`idle`,fetchFailureCount:0,fetchFailureReason:null}};return T(Xe,this,e.manual?n:void 0),n;case`error`:let r=e.error;return{...t,error:r,errorUpdateCount:t.errorUpdateCount+1,errorUpdatedAt:Date.now(),fetchFailureCount:t.fetchFailureCount+1,fetchFailureReason:r,fetchStatus:`idle`,status:`error`,isInvalidated:!0};case`invalidate`:return{...t,isInvalidated:!0};case`setState`:return{...t,...e.state}}};this.state=t(this.state),Ie.batch(()=>{this.observers.forEach(e=>{e.onQueryUpdate()}),D(Ze,this).notify({query:this,type:`updated`,action:e})})}function ot(e,t){return{fetchFailureCount:0,fetchFailureReason:null,fetchStatus:He(t.networkMode)?`fetching`:`paused`,...e===void 0&&{error:null,status:`pending`}}}function st(e,t){return{data:e,dataUpdatedAt:t??Date.now(),error:null,isInvalidated:!1,status:`success`}}function ct(e){let t=typeof e.initialData==`function`?e.initialData():e.initialData,n=t!==void 0,r=n?typeof e.initialDataUpdatedAt==`function`?e.initialDataUpdatedAt():e.initialDataUpdatedAt:0;return{data:t,dataUpdateCount:0,dataUpdatedAt:n?r??Date.now():0,error:null,errorUpdateCount:0,errorUpdatedAt:0,fetchFailureCount:0,fetchFailureReason:null,fetchMeta:null,isInvalidated:!1,status:n?`success`:`pending`,fetchStatus:`idle`}}Je(),S(),E(),O(),w();var lt,M,ut,dt,ft,pt,mt,ht,gt,_t,vt,yt,bt,xt,St,Ct,wt=(lt=new WeakMap,M=new WeakMap,ut=new WeakMap,dt=new WeakMap,ft=new WeakMap,pt=new WeakMap,mt=new WeakMap,ht=new WeakMap,gt=new WeakMap,_t=new WeakMap,vt=new WeakMap,yt=new WeakMap,bt=new WeakMap,xt=new WeakMap,St=new WeakMap,Ct=new WeakSet,class extends v{constructor(e,t){super(),qe(this,Ct),x(this,lt,void 0),x(this,M,void 0),x(this,ut,void 0),x(this,dt,void 0),x(this,ft,void 0),x(this,pt,void 0),x(this,mt,void 0),x(this,ht,void 0),x(this,gt,void 0),x(this,_t,void 0),x(this,vt,void 0),x(this,yt,void 0),x(this,bt,void 0),x(this,xt,void 0),x(this,St,new Set),this.options=t,T(lt,this,e),T(ht,this,null),T(mt,this,Ne()),this.bindMethods(),this.setOptions(t)}bindMethods(){this.refetch=this.refetch.bind(this)}onSubscribe(){this.listeners.size===1&&(D(M,this).addObserver(this),Ft(D(M,this),this.options)?C(Ct,this,Tt).call(this):this.updateResult(),C(Ct,this,kt).call(this))}onUnsubscribe(){this.hasListeners()||this.destroy()}shouldFetchOnReconnect(){return It(D(M,this),this.options,this.options.refetchOnReconnect)}shouldFetchOnWindowFocus(){return It(D(M,this),this.options,this.options.refetchOnWindowFocus)}destroy(){this.listeners=new Set,C(Ct,this,At).call(this),C(Ct,this,jt).call(this),D(M,this).removeObserver(this)}setOptions(e){let t=this.options,n=D(M,this);if(this.options=D(lt,this).defaultQueryOptions(e),this.options.enabled!==void 0&&typeof this.options.enabled!=`boolean`&&typeof this.options.enabled!=`function`&&typeof fe(this.options.enabled,D(M,this))!=`boolean`)throw Error(`Expected enabled to be a boolean or a callback that returns a boolean`);C(Ct,this,Mt).call(this),D(M,this).setOptions(this.options),t._defaulted&&!be(this.options,t)&&D(lt,this).getQueryCache().notify({type:`observerOptionsUpdated`,query:D(M,this),observer:this});let r=this.hasListeners();r&&Lt(D(M,this),n,this.options,t)&&C(Ct,this,Tt).call(this),this.updateResult(),r&&(D(M,this)!==n||fe(this.options.enabled,D(M,this))!==fe(t.enabled,D(M,this))||j(this.options.staleTime,D(M,this))!==j(t.staleTime,D(M,this)))&&C(Ct,this,Et).call(this);let i=C(Ct,this,Dt).call(this);r&&(D(M,this)!==n||fe(this.options.enabled,D(M,this))!==fe(t.enabled,D(M,this))||i!==D(xt,this))&&C(Ct,this,Ot).call(this,i)}getOptimisticResult(e){let t=D(lt,this).getQueryCache().build(D(lt,this),e),n=this.createResult(t,e);return zt(this,n)&&(T(dt,this,n),T(pt,this,this.options),T(ft,this,D(M,this).state)),n}getCurrentResult(){return D(dt,this)}trackResult(e,t){return new Proxy(e,{get:(e,n)=>(this.trackProp(n),t?.(n),n===`promise`&&(this.trackProp(`data`),!this.options.experimental_prefetchInRender&&D(mt,this).status===`pending`&&D(mt,this).reject(Error(`experimental_prefetchInRender feature flag is not enabled`))),Reflect.get(e,n))})}trackProp(e){D(St,this).add(e)}getCurrentQuery(){return D(M,this)}refetch({...e}={}){return this.fetch({...e})}fetchOptimistic(e){let t=D(lt,this).defaultQueryOptions(e),n=D(lt,this).getQueryCache().build(D(lt,this),t);return n.fetch().then(()=>this.createResult(n,t))}fetch(e){return C(Ct,this,Tt).call(this,{...e,cancelRefetch:e.cancelRefetch??!0}).then(()=>(this.updateResult(),D(dt,this)))}createResult(e,t){let n=D(M,this),r=this.options,i=D(dt,this),a=D(ft,this),o=D(pt,this),s=e===n?D(ut,this):e.state,{state:c}=e,l={...c},u=!1,d;if(t._optimisticResults){let i=this.hasListeners(),a=!i&&Ft(e,t),o=i&&Lt(e,n,t,r);(a||o)&&(l={...l,...ot(c.data,e.options)}),t._optimisticResults===`isRestoring`&&(l.fetchStatus=`idle`)}let{error:f,errorUpdatedAt:p,status:m}=l;d=l.data;let h=!1;if(t.placeholderData!==void 0&&d===void 0&&m===`pending`){let e;i?.isPlaceholderData&&t.placeholderData===o?.placeholderData?(e=i.data,h=!0):e=typeof t.placeholderData==`function`?t.placeholderData(D(vt,this)?.state.data,D(vt,this)):t.placeholderData,e!==void 0&&(m=`success`,d=Te(i?.data,e,t),u=!0)}if(t.select&&d!==void 0&&!h)if(i&&d===a?.data&&t.select===D(gt,this))d=D(_t,this);else try{T(gt,this,t.select),d=t.select(d),d=Te(i?.data,d,t),T(_t,this,d),T(ht,this,null)}catch(e){T(ht,this,e)}D(ht,this)&&(f=D(ht,this),d=D(_t,this),p=Date.now(),m=`error`);let g=l.fetchStatus===`fetching`,_=m===`pending`,v=m===`error`,y=_&&g,b=d!==void 0,x={status:m,fetchStatus:l.fetchStatus,isPending:_,isSuccess:m===`success`,isError:v,isInitialLoading:y,isLoading:y,data:d,dataUpdatedAt:l.dataUpdatedAt,error:f,errorUpdatedAt:p,failureCount:l.fetchFailureCount,failureReason:l.fetchFailureReason,errorUpdateCount:l.errorUpdateCount,isFetched:e.isFetched(),isFetchedAfterMount:l.dataUpdateCount>s.dataUpdateCount||l.errorUpdateCount>s.errorUpdateCount,isFetching:g,isRefetching:g&&!_,isLoadingError:v&&!b,isPaused:l.fetchStatus===`paused`,isPlaceholderData:u,isRefetchError:v&&b,isStale:Rt(e,t),refetch:this.refetch,promise:D(mt,this),isEnabled:fe(t.enabled,e)!==!1};if(this.options.experimental_prefetchInRender){let t=x.data!==void 0,r=x.status===`error`&&!t,i=e=>{r?e.reject(x.error):t&&e.resolve(x.data)},a=()=>{i(T(mt,this,x.promise=Ne()))},o=D(mt,this);switch(o.status){case`pending`:e.queryHash===n.queryHash&&i(o);break;case`fulfilled`:(r||x.data!==o.value)&&a();break;case`rejected`:(!r||x.error!==o.reason)&&a();break}}return x}updateResult(){let e=D(dt,this),t=this.createResult(D(M,this),this.options);T(ft,this,D(M,this).state),T(pt,this,this.options),D(ft,this).data!==void 0&&T(vt,this,D(M,this)),!be(t,e)&&(T(dt,this,t),C(Ct,this,Nt).call(this,{listeners:(()=>{if(!e)return!0;let{notifyOnChangeProps:t}=this.options,n=typeof t==`function`?t():t;if(n===`all`||!n&&!D(St,this).size)return!0;let r=new Set(n??D(St,this));return this.options.throwOnError&&r.add(`error`),Object.keys(D(dt,this)).some(t=>{let n=t;return D(dt,this)[n]!==e[n]&&r.has(n)})})()}))}onQueryUpdate(){this.updateResult(),this.hasListeners()&&C(Ct,this,kt).call(this)}});function Tt(e){C(Ct,this,Mt).call(this);let t=D(M,this).fetch(this.options,e);return e?.throwOnError||(t=t.catch(ce)),t}function Et(){C(Ct,this,At).call(this);let e=j(this.options.staleTime,D(M,this));if(Me.isServer()||D(dt,this).isStale||!ue(e))return;let t=de(D(dt,this).dataUpdatedAt,e)+1;T(yt,this,k.setTimeout(()=>{D(dt,this).isStale||this.updateResult()},t))}function Dt(){return(typeof this.options.refetchInterval==`function`?this.options.refetchInterval(D(M,this)):this.options.refetchInterval)??!1}function Ot(e){C(Ct,this,jt).call(this),T(xt,this,e),!(Me.isServer()||fe(this.options.enabled,D(M,this))===!1||!ue(D(xt,this))||D(xt,this)===0)&&T(bt,this,k.setInterval(()=>{(this.options.refetchIntervalInBackground||re.isFocused())&&C(Ct,this,Tt).call(this)},D(xt,this)))}function kt(){C(Ct,this,Et).call(this),C(Ct,this,Ot).call(this,C(Ct,this,Dt).call(this))}function At(){D(yt,this)&&(k.clearTimeout(D(yt,this)),T(yt,this,void 0))}function jt(){D(bt,this)&&(k.clearInterval(D(bt,this)),T(bt,this,void 0))}function Mt(){let e=D(lt,this).getQueryCache().build(D(lt,this),this.options);if(e===D(M,this))return;let t=D(M,this);T(M,this,e),T(ut,this,e.state),this.hasListeners()&&(t?.removeObserver(this),e.addObserver(this))}function Nt(e){Ie.batch(()=>{e.listeners&&this.listeners.forEach(e=>{e(D(dt,this))}),D(lt,this).getQueryCache().notify({query:D(M,this),type:`observerResultsUpdated`})})}function Pt(e,t){return fe(t.enabled,e)!==!1&&e.state.data===void 0&&!(e.state.status===`error`&&t.retryOnMount===!1)}function Ft(e,t){return Pt(e,t)||e.state.data!==void 0&&It(e,t,t.refetchOnMount)}function It(e,t,n){if(fe(t.enabled,e)!==!1&&j(t.staleTime,e)!==`static`){let r=typeof n==`function`?n(e):n;return r===`always`||r!==!1&&Rt(e,t)}return!1}function Lt(e,t,n,r){return(e!==t||fe(r.enabled,e)===!1)&&(!n.suspense||e.state.status!==`error`)&&Rt(e,n)}function Rt(e,t){return fe(t.enabled,e)!==!1&&e.isStaleByTime(j(t.staleTime,e))}function zt(e,t){return!be(e.getCurrentResult(),t)}function Bt(e){return{onFetch:(t,n)=>{let r=t.options,i=t.fetchOptions?.meta?.fetchMore?.direction,a=t.state.data?.pages||[],o=t.state.data?.pageParams||[],s={pages:[],pageParams:[]},c=0,l=async()=>{let n=!1,l=e=>{je(e,()=>t.signal,()=>n=!0)},u=ke(t.options,t.fetchOptions),d=async(e,r,i)=>{if(n)return Promise.reject();if(r==null&&e.pages.length)return Promise.resolve(e);let a=await u((()=>{let e={client:t.client,queryKey:t.queryKey,pageParam:r,direction:i?`backward`:`forward`,meta:t.options.meta};return l(e),e})()),{maxPages:o}=t.options,s=i?De:Ee;return{pages:s(e.pages,a,o),pageParams:s(e.pageParams,r,o)}};if(i&&a.length){let e=i===`backward`,t=e?Ht:Vt,n={pages:a,pageParams:o};s=await d(n,t(r,n),e)}else{let t=e??a.length;do{let e=c===0?o[0]??r.initialPageParam:Vt(r,s);if(c>0&&e==null)break;s=await d(s,e),c++}while(ct.options.persister?.(l,{client:t.client,queryKey:t.queryKey,meta:t.options.meta,signal:t.signal},n):t.fetchFn=l}}}function Vt(e,{pages:t,pageParams:n}){let r=t.length-1;return t.length>0?e.getNextPageParam(t[r],t,n[r],n):void 0}function Ht(e,{pages:t,pageParams:n}){return t.length>0?e.getPreviousPageParam?.(t[0],t,n[0],n):void 0}Je(),S(),E(),O(),w();var Ut,Wt,Gt,Kt,qt,Jt=(Ut=new WeakMap,Wt=new WeakMap,Gt=new WeakMap,Kt=new WeakMap,qt=new WeakSet,class extends Ke{constructor(e){super(),qe(this,qt),x(this,Ut,void 0),x(this,Wt,void 0),x(this,Gt,void 0),x(this,Kt,void 0),T(Ut,this,e.client),this.mutationId=e.mutationId,T(Gt,this,e.mutationCache),T(Wt,this,[]),this.state=e.state||Xt(),this.setOptions(e.options),this.scheduleGc()}setOptions(e){this.options=e,this.updateGcTime(this.options.gcTime)}get meta(){return this.options.meta}addObserver(e){D(Wt,this).includes(e)||(D(Wt,this).push(e),this.clearGcTimeout(),D(Gt,this).notify({type:`observerAdded`,mutation:this,observer:e}))}removeObserver(e){T(Wt,this,D(Wt,this).filter(t=>t!==e)),this.scheduleGc(),D(Gt,this).notify({type:`observerRemoved`,mutation:this,observer:e})}optionalRemove(){D(Wt,this).length||(this.state.status===`pending`?this.scheduleGc():D(Gt,this).remove(this))}continue(){return D(Kt,this)?.continue()??this.execute(this.state.variables)}async execute(e){let t=()=>{C(qt,this,Yt).call(this,{type:`continue`})},n={client:D(Ut,this),meta:this.options.meta,mutationKey:this.options.mutationKey};T(Kt,this,We({fn:()=>this.options.mutationFn?this.options.mutationFn(e,n):Promise.reject(Error(`No mutationFn found`)),onFail:(e,t)=>{C(qt,this,Yt).call(this,{type:`failed`,failureCount:e,error:t})},onPause:()=>{C(qt,this,Yt).call(this,{type:`pause`})},onContinue:t,retry:this.options.retry??0,retryDelay:this.options.retryDelay,networkMode:this.options.networkMode,canRun:()=>D(Gt,this).canRun(this)}));let r=this.state.status===`pending`,i=!D(Kt,this).canStart();try{if(r)t();else{C(qt,this,Yt).call(this,{type:`pending`,variables:e,isPaused:i}),D(Gt,this).config.onMutate&&await D(Gt,this).config.onMutate(e,this,n);let t=await this.options.onMutate?.(e,n);t!==this.state.context&&C(qt,this,Yt).call(this,{type:`pending`,context:t,variables:e,isPaused:i})}let a=await D(Kt,this).start();return await D(Gt,this).config.onSuccess?.(a,e,this.state.context,this,n),await this.options.onSuccess?.(a,e,this.state.context,n),await D(Gt,this).config.onSettled?.(a,null,this.state.variables,this.state.context,this,n),await this.options.onSettled?.(a,null,e,this.state.context,n),C(qt,this,Yt).call(this,{type:`success`,data:a}),a}catch(t){try{await D(Gt,this).config.onError?.(t,e,this.state.context,this,n)}catch(e){Promise.reject(e)}try{await this.options.onError?.(t,e,this.state.context,n)}catch(e){Promise.reject(e)}try{await D(Gt,this).config.onSettled?.(void 0,t,this.state.variables,this.state.context,this,n)}catch(e){Promise.reject(e)}try{await this.options.onSettled?.(void 0,t,e,this.state.context,n)}catch(e){Promise.reject(e)}throw C(qt,this,Yt).call(this,{type:`error`,error:t}),t}finally{D(Gt,this).runNext(this)}}});function Yt(e){let t=t=>{switch(e.type){case`failed`:return{...t,failureCount:e.failureCount,failureReason:e.error};case`pause`:return{...t,isPaused:!0};case`continue`:return{...t,isPaused:!1};case`pending`:return{...t,context:e.context,data:void 0,failureCount:0,failureReason:null,error:null,isPaused:e.isPaused,status:`pending`,variables:e.variables,submittedAt:Date.now()};case`success`:return{...t,data:e.data,failureCount:0,failureReason:null,error:null,status:`success`,isPaused:!1};case`error`:return{...t,data:void 0,error:e.error,failureCount:t.failureCount+1,failureReason:e.error,isPaused:!1,status:`error`}}};this.state=t(this.state),Ie.batch(()=>{D(Wt,this).forEach(t=>{t.onMutationUpdate(e)}),D(Gt,this).notify({mutation:this,type:`updated`,action:e})})}function Xt(){return{context:void 0,data:void 0,error:null,failureCount:0,failureReason:null,isPaused:!1,status:`idle`,variables:void 0,submittedAt:0}}S(),E(),O();var Zt,Qt,$t,en=(Zt=new WeakMap,Qt=new WeakMap,$t=new WeakMap,class extends v{constructor(e={}){super(),x(this,Zt,void 0),x(this,Qt,void 0),x(this,$t,void 0),this.config=e,T(Zt,this,new Set),T(Qt,this,new Map),T($t,this,0)}build(e,t,n){var r;let i=new Jt({client:e,mutationCache:this,mutationId:T($t,this,(r=D($t,this),++r)),options:e.defaultMutationOptions(t),state:n});return this.add(i),i}add(e){D(Zt,this).add(e);let t=tn(e);if(typeof t==`string`){let n=D(Qt,this).get(t);n?n.push(e):D(Qt,this).set(t,[e])}this.notify({type:`added`,mutation:e})}remove(e){if(D(Zt,this).delete(e)){let t=tn(e);if(typeof t==`string`){let n=D(Qt,this).get(t);if(n)if(n.length>1){let t=n.indexOf(e);t!==-1&&n.splice(t,1)}else n[0]===e&&D(Qt,this).delete(t)}}this.notify({type:`removed`,mutation:e})}canRun(e){let t=tn(e);if(typeof t==`string`){let n=D(Qt,this).get(t)?.find(e=>e.state.status===`pending`);return!n||n===e}else return!0}runNext(e){let t=tn(e);return typeof t==`string`?(D(Qt,this).get(t)?.find(t=>t!==e&&t.state.isPaused))?.continue()??Promise.resolve():Promise.resolve()}clear(){Ie.batch(()=>{D(Zt,this).forEach(e=>{this.notify({type:`removed`,mutation:e})}),D(Zt,this).clear(),D(Qt,this).clear()})}getAll(){return Array.from(D(Zt,this))}find(e){let t={exact:!0,...e};return this.getAll().find(e=>me(t,e))}findAll(e={}){return this.getAll().filter(t=>me(e,t))}notify(e){Ie.batch(()=>{this.listeners.forEach(t=>{t(e)})})}resumePausedMutations(){let e=this.getAll().filter(e=>e.state.isPaused);return Ie.batch(()=>Promise.all(e.map(e=>e.continue().catch(ce))))}});function tn(e){return e.options.scope?.id}Je(),S(),E(),w(),O();var nn,rn,an,on,sn,cn=(nn=new WeakMap,rn=new WeakMap,an=new WeakMap,on=new WeakMap,sn=new WeakSet,class extends v{constructor(e,t){super(),qe(this,sn),x(this,nn,void 0),x(this,rn,void 0),x(this,an,void 0),x(this,on,void 0),T(nn,this,e),this.setOptions(t),this.bindMethods(),C(sn,this,ln).call(this)}bindMethods(){this.mutate=this.mutate.bind(this),this.reset=this.reset.bind(this)}setOptions(e){let t=this.options;this.options=D(nn,this).defaultMutationOptions(e),be(this.options,t)||D(nn,this).getMutationCache().notify({type:`observerOptionsUpdated`,mutation:D(an,this),observer:this}),t?.mutationKey&&this.options.mutationKey&&ge(t.mutationKey)!==ge(this.options.mutationKey)?this.reset():D(an,this)?.state.status===`pending`&&D(an,this).setOptions(this.options)}onUnsubscribe(){this.hasListeners()||D(an,this)?.removeObserver(this)}onMutationUpdate(e){C(sn,this,ln).call(this),C(sn,this,un).call(this,e)}getCurrentResult(){return D(rn,this)}reset(){D(an,this)?.removeObserver(this),T(an,this,void 0),C(sn,this,ln).call(this),C(sn,this,un).call(this)}mutate(e,t){return T(on,this,t),D(an,this)?.removeObserver(this),T(an,this,D(nn,this).getMutationCache().build(D(nn,this),this.options)),D(an,this).addObserver(this),D(an,this).execute(e)}});function ln(){let e=D(an,this)?.state??Xt();T(rn,this,{...e,isPending:e.status===`pending`,isSuccess:e.status===`success`,isError:e.status===`error`,isIdle:e.status===`idle`,mutate:this.mutate,reset:this.reset})}function un(e){Ie.batch(()=>{if(D(on,this)&&this.hasListeners()){let t=D(rn,this).variables,n=D(rn,this).context,r={client:D(nn,this),meta:this.options.meta,mutationKey:this.options.mutationKey};if(e?.type===`success`){try{D(on,this).onSuccess?.(e.data,t,n,r)}catch(e){Promise.reject(e)}try{D(on,this).onSettled?.(e.data,null,t,n,r)}catch(e){Promise.reject(e)}}else if(e?.type===`error`){try{D(on,this).onError?.(e.error,t,n,r)}catch(e){Promise.reject(e)}try{D(on,this).onSettled?.(void 0,e.error,t,n,r)}catch(e){Promise.reject(e)}}}this.listeners.forEach(e=>{e(D(rn,this))})})}S(),E(),O();var dn,fn=(dn=new WeakMap,class extends v{constructor(e={}){super(),x(this,dn,void 0),this.config=e,T(dn,this,new Map)}build(e,t,n){let r=t.queryKey,i=t.queryHash??he(r,t),a=this.get(i);return a||(a=new rt({client:e,queryKey:r,queryHash:i,options:e.defaultQueryOptions(t),state:n,defaultOptions:e.getQueryDefaults(r)}),this.add(a)),a}add(e){D(dn,this).has(e.queryHash)||(D(dn,this).set(e.queryHash,e),this.notify({type:`added`,query:e}))}remove(e){let t=D(dn,this).get(e.queryHash);t&&(e.destroy(),t===e&&D(dn,this).delete(e.queryHash),this.notify({type:`removed`,query:e}))}clear(){Ie.batch(()=>{this.getAll().forEach(e=>{this.remove(e)})})}get(e){return D(dn,this).get(e)}getAll(){return[...D(dn,this).values()]}find(e){let t={exact:!0,...e};return this.getAll().find(e=>pe(t,e))}findAll(e={}){let t=this.getAll();return Object.keys(e).length>0?t.filter(t=>pe(e,t)):t}notify(e){Ie.batch(()=>{this.listeners.forEach(t=>{t(e)})})}onFocus(){Ie.batch(()=>{this.getAll().forEach(e=>{e.onFocus()})})}onOnline(){Ie.batch(()=>{this.getAll().forEach(e=>{e.onOnline()})})}});S(),E(),O();var pn,mn,hn,gn,_n,vn,yn,bn,xn=(pn=new WeakMap,mn=new WeakMap,hn=new WeakMap,gn=new WeakMap,_n=new WeakMap,vn=new WeakMap,yn=new WeakMap,bn=new WeakMap,class{constructor(e={}){x(this,pn,void 0),x(this,mn,void 0),x(this,hn,void 0),x(this,gn,void 0),x(this,_n,void 0),x(this,vn,void 0),x(this,yn,void 0),x(this,bn,void 0),T(pn,this,e.queryCache||new fn),T(mn,this,e.mutationCache||new en),T(hn,this,e.defaultOptions||{}),T(gn,this,new Map),T(_n,this,new Map),T(vn,this,0)}mount(){var e;T(vn,this,(e=D(vn,this),e++,e)),D(vn,this)===1&&(T(yn,this,re.subscribe(async e=>{e&&(await this.resumePausedMutations(),D(pn,this).onFocus())})),T(bn,this,Be.subscribe(async e=>{e&&(await this.resumePausedMutations(),D(pn,this).onOnline())})))}unmount(){var e;T(vn,this,(e=D(vn,this),e--,e)),D(vn,this)===0&&(D(yn,this)?.call(this),T(yn,this,void 0),D(bn,this)?.call(this),T(bn,this,void 0))}isFetching(e){return D(pn,this).findAll({...e,fetchStatus:`fetching`}).length}isMutating(e){return D(mn,this).findAll({...e,status:`pending`}).length}getQueryData(e){let t=this.defaultQueryOptions({queryKey:e});return D(pn,this).get(t.queryHash)?.state.data}ensureQueryData(e){let t=this.defaultQueryOptions(e),n=D(pn,this).build(this,t),r=n.state.data;return r===void 0?this.fetchQuery(e):(e.revalidateIfStale&&n.isStaleByTime(j(t.staleTime,n))&&this.prefetchQuery(t),Promise.resolve(r))}getQueriesData(e){return D(pn,this).findAll(e).map(({queryKey:e,state:t})=>[e,t.data])}setQueryData(e,t,n){let r=this.defaultQueryOptions({queryKey:e}),i=D(pn,this).get(r.queryHash)?.state.data,a=le(t,i);if(a!==void 0)return D(pn,this).build(this,r).setData(a,{...n,manual:!0})}setQueriesData(e,t,n){return Ie.batch(()=>D(pn,this).findAll(e).map(({queryKey:e})=>[e,this.setQueryData(e,t,n)]))}getQueryState(e){let t=this.defaultQueryOptions({queryKey:e});return D(pn,this).get(t.queryHash)?.state}removeQueries(e){let t=D(pn,this);Ie.batch(()=>{t.findAll(e).forEach(e=>{t.remove(e)})})}resetQueries(e,t){let n=D(pn,this);return Ie.batch(()=>(n.findAll(e).forEach(e=>{e.reset()}),this.refetchQueries({type:`active`,...e},t)))}cancelQueries(e,t={}){let n={revert:!0,...t},r=Ie.batch(()=>D(pn,this).findAll(e).map(e=>e.cancel(n)));return Promise.all(r).then(ce).catch(ce)}invalidateQueries(e,t={}){return Ie.batch(()=>(D(pn,this).findAll(e).forEach(e=>{e.invalidate()}),e?.refetchType===`none`?Promise.resolve():this.refetchQueries({...e,type:e?.refetchType??e?.type??`active`},t)))}refetchQueries(e,t={}){let n={...t,cancelRefetch:t.cancelRefetch??!0},r=Ie.batch(()=>D(pn,this).findAll(e).filter(e=>!e.isDisabled()&&!e.isStatic()).map(e=>{let t=e.fetch(void 0,n);return n.throwOnError||(t=t.catch(ce)),e.state.fetchStatus===`paused`?Promise.resolve():t}));return Promise.all(r).then(ce)}fetchQuery(e){let t=this.defaultQueryOptions(e);t.retry===void 0&&(t.retry=!1);let n=D(pn,this).build(this,t);return n.isStaleByTime(j(t.staleTime,n))?n.fetch(t):Promise.resolve(n.state.data)}prefetchQuery(e){return this.fetchQuery(e).then(ce).catch(ce)}fetchInfiniteQuery(e){return e.behavior=Bt(e.pages),this.fetchQuery(e)}prefetchInfiniteQuery(e){return this.fetchInfiniteQuery(e).then(ce).catch(ce)}ensureInfiniteQueryData(e){return e.behavior=Bt(e.pages),this.ensureQueryData(e)}resumePausedMutations(){return Be.isOnline()?D(mn,this).resumePausedMutations():Promise.resolve()}getQueryCache(){return D(pn,this)}getMutationCache(){return D(mn,this)}getDefaultOptions(){return D(hn,this)}setDefaultOptions(e){T(hn,this,e)}setQueryDefaults(e,t){D(gn,this).set(ge(e),{queryKey:e,defaultOptions:t})}getQueryDefaults(e){let t=[...D(gn,this).values()],n={};return t.forEach(t=>{_e(e,t.queryKey)&&Object.assign(n,t.defaultOptions)}),n}setMutationDefaults(e,t){D(_n,this).set(ge(e),{mutationKey:e,defaultOptions:t})}getMutationDefaults(e){let t=[...D(_n,this).values()],n={};return t.forEach(t=>{_e(e,t.mutationKey)&&Object.assign(n,t.defaultOptions)}),n}defaultQueryOptions(e){if(e._defaulted)return e;let t={...D(hn,this).queries,...this.getQueryDefaults(e.queryKey),...e,_defaulted:!0};return t.queryHash||(t.queryHash=he(t.queryKey,t)),t.refetchOnReconnect===void 0&&(t.refetchOnReconnect=t.networkMode!==`always`),t.throwOnError===void 0&&(t.throwOnError=!!t.suspense),!t.networkMode&&t.persister&&(t.networkMode=`offlineFirst`),t.queryFn===Oe&&(t.enabled=!1),t}defaultMutationOptions(e){return e?._defaulted?e:{...D(hn,this).mutations,...e?.mutationKey&&this.getMutationDefaults(e.mutationKey),...e,_defaulted:!0}}clear(){D(pn,this).clear(),D(mn,this).clear()}}),Sn=s((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),Cn=s(((e,t)=>{t.exports=Sn()})),N=l(d(),1),P=Cn(),wn=N.createContext(void 0),Tn=e=>{let t=N.useContext(wn);if(e)return e;if(!t)throw Error(`No QueryClient set, use QueryClientProvider to set one`);return t},En=({client:e,children:t})=>(N.useEffect(()=>(e.mount(),()=>{e.unmount()}),[e]),(0,P.jsx)(wn.Provider,{value:e,children:t})),Dn=N.createContext(!1),On=()=>N.useContext(Dn);Dn.Provider;function kn(){let e=!1;return{clearReset:()=>{e=!1},reset:()=>{e=!0},isReset:()=>e}}var An=N.createContext(kn()),jn=()=>N.useContext(An),Mn=(e,t,n)=>{let r=n?.state.error&&typeof e.throwOnError==`function`?Ae(e.throwOnError,[n.state.error,n]):e.throwOnError;(e.suspense||e.experimental_prefetchInRender||r)&&(t.isReset()||(e.retryOnMount=!1))},Nn=e=>{N.useEffect(()=>{e.clearReset()},[e])},Pn=({result:e,errorResetBoundary:t,throwOnError:n,query:r,suspense:i})=>e.isError&&!t.isReset()&&!e.isFetching&&r&&(i&&e.data===void 0||Ae(n,[e.error,r])),Fn=e=>{if(e.suspense){let t=1e3,n=e=>e===`static`?e:Math.max(e??t,t),r=e.staleTime;e.staleTime=typeof r==`function`?(...e)=>n(r(...e)):n(r),typeof e.gcTime==`number`&&(e.gcTime=Math.max(e.gcTime,t))}},In=(e,t)=>e.isLoading&&e.isFetching&&!t,Ln=(e,t)=>e?.suspense&&t.isPending,Rn=(e,t,n)=>t.fetchOptimistic(e).catch(()=>{n.clearReset()});function zn(e,t,n){let r=On(),i=jn(),a=Tn(n),o=a.defaultQueryOptions(e);a.getDefaultOptions().queries?._experimental_beforeQuery?.(o);let s=a.getQueryCache().get(o.queryHash);o._optimisticResults=r?`isRestoring`:`optimistic`,Fn(o),Mn(o,i,s),Nn(i);let c=!a.getQueryCache().get(o.queryHash),[l]=N.useState(()=>new t(a,o)),u=l.getOptimisticResult(o),d=!r&&e.subscribed!==!1;if(N.useSyncExternalStore(N.useCallback(e=>{let t=d?l.subscribe(Ie.batchCalls(e)):ce;return l.updateResult(),t},[l,d]),()=>l.getCurrentResult(),()=>l.getCurrentResult()),N.useEffect(()=>{l.setOptions(o)},[o,l]),Ln(o,u))throw Rn(o,l,i);if(Pn({result:u,errorResetBoundary:i,throwOnError:o.throwOnError,query:s,suspense:o.suspense}))throw u.error;return a.getDefaultOptions().queries?._experimental_afterQuery?.(o,u),o.experimental_prefetchInRender&&!Me.isServer()&&In(u,r)&&(c?Rn(o,l,i):s?.promise)?.catch(ce).finally(()=>{l.updateResult()}),o.notifyOnChangeProps?u:l.trackResult(u)}function Bn(e,t){return zn(e,wt,t)}function Vn(e,t){let n=Tn(t),[r]=N.useState(()=>new cn(n,e));N.useEffect(()=>{r.setOptions(e)},[r,e]);let i=N.useSyncExternalStore(N.useCallback(e=>r.subscribe(Ie.batchCalls(e)),[r]),()=>r.getCurrentResult(),()=>r.getCurrentResult()),a=N.useCallback((e,t)=>{r.mutate(e,t).catch(ce)},[r]);if(i.error&&Ae(r.options.throwOnError,[i.error]))throw i.error;return{...i,mutate:a,mutateAsync:i.mutate}}var Hn=l(h()),Un=_();function Wn(){return Wn=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0&&(t.hash=e.substr(n),e=e.substr(0,n));let r=e.indexOf(`?`);r>=0&&(t.search=e.substr(r),e=e.substr(0,r)),e&&(t.pathname=e)}return t}function tr(e,t,n,r){r===void 0&&(r={});let{window:i=document.defaultView,v5Compat:a=!1}=r,o=i.history,s=Gn.Pop,c=null,l=u();l??(l=0,o.replaceState(Wn({},o.state,{idx:l}),``));function u(){return(o.state||{idx:null}).idx}function d(){s=Gn.Pop;let e=u(),t=e==null?null:e-l;l=e,c&&c({action:s,location:h.location,delta:t})}function f(e,t){s=Gn.Push;let r=Qn(h.location,e,t);n&&n(r,e),l=u()+1;let d=Zn(r,l),f=h.createHref(r);try{o.pushState(d,``,f)}catch(e){if(e instanceof DOMException&&e.name===`DataCloneError`)throw e;i.location.assign(f)}a&&c&&c({action:s,location:h.location,delta:1})}function p(e,t){s=Gn.Replace;let r=Qn(h.location,e,t);n&&n(r,e),l=u();let i=Zn(r,l),d=h.createHref(r);o.replaceState(i,``,d),a&&c&&c({action:s,location:h.location,delta:0})}function m(e){let t=i.location.origin===`null`?i.location.href:i.location.origin,n=typeof e==`string`?e:$n(e);return n=n.replace(/ $/,`%20`),Jn(t,`No window.location.(origin|href) available to create URL for href: `+n),new URL(n,t)}let h={get action(){return s},get location(){return e(i,o)},listen(e){if(c)throw Error(`A history only accepts one active listener`);return i.addEventListener(Kn,d),c=e,()=>{i.removeEventListener(Kn,d),c=null}},createHref(e){return t(i,e)},createURL:m,encodeLocation(e){let t=m(e);return{pathname:t.pathname,search:t.search,hash:t.hash}},push:f,replace:p,go(e){return o.go(e)}};return h}var nr;(function(e){e.data=`data`,e.deferred=`deferred`,e.redirect=`redirect`,e.error=`error`})(nr||(nr={}));function rr(e,t,n){return n===void 0&&(n=`/`),ir(e,t,n,!1)}function ir(e,t,n,r){let i=xr((typeof t==`string`?er(t):t).pathname||`/`,n);if(i==null)return null;let a=ar(e);sr(a);let o=null,s=br(i);for(let e=0;o==null&&e{let o={relativePath:a===void 0?e.path||``:a,caseSensitive:e.caseSensitive===!0,childrenIndex:i,route:e};o.relativePath.startsWith(`/`)&&(Jn(o.relativePath.startsWith(r),`Absolute route path "`+o.relativePath+`" nested under path `+(`"`+r+`" is not valid. An absolute child route path `)+`must start with the combined path of all its parent routes.`),o.relativePath=o.relativePath.slice(r.length));let s=jr([r,o.relativePath]),c=n.concat(o);e.children&&e.children.length>0&&(Jn(e.index!==!0,`Index routes must not have child routes. Please remove `+(`all child routes from route path "`+s+`".`)),ar(e.children,t,c,s)),!(e.path==null&&!e.index)&&t.push({path:s,score:hr(s,e.index),routesMeta:c})};return e.forEach((e,t)=>{var n;if(e.path===``||!((n=e.path)!=null&&n.includes(`?`)))i(e,t);else for(let n of or(e.path))i(e,t,n)}),t}function or(e){let t=e.split(`/`);if(t.length===0)return[];let[n,...r]=t,i=n.endsWith(`?`),a=n.replace(/\?$/,``);if(r.length===0)return i?[a,``]:[a];let o=or(r.join(`/`)),s=[];return s.push(...o.map(e=>e===``?a:[a,e].join(`/`))),i&&s.push(...o),s.map(t=>e.startsWith(`/`)&&t===``?`/`:t)}function sr(e){e.sort((e,t)=>e.score===t.score?gr(e.routesMeta.map(e=>e.childrenIndex),t.routesMeta.map(e=>e.childrenIndex)):t.score-e.score)}var cr=/^:[\w-]+$/,lr=3,ur=2,dr=1,fr=10,pr=-2,mr=e=>e===`*`;function hr(e,t){let n=e.split(`/`),r=n.length;return n.some(mr)&&(r+=pr),t&&(r+=ur),n.filter(e=>!mr(e)).reduce((e,t)=>e+(cr.test(t)?lr:t===``?dr:fr),r)}function gr(e,t){return e.length===t.length&&e.slice(0,-1).every((e,n)=>e===t[n])?e[e.length-1]-t[t.length-1]:0}function _r(e,t,n){n===void 0&&(n=!1);let{routesMeta:r}=e,i={},a=`/`,o=[];for(let e=0;e{let{paramName:r,isOptional:i}=t;if(r===`*`){let e=s[n]||``;o=a.slice(0,a.length-e.length).replace(/(.)\/+$/,`$1`)}let c=s[n];return i&&!c?e[r]=void 0:e[r]=(c||``).replace(/%2F/g,`/`),e},{}),pathname:a,pathnameBase:o,pattern:e}}function yr(e,t,n){t===void 0&&(t=!1),n===void 0&&(n=!0),Yn(e===`*`||!e.endsWith(`*`)||e.endsWith(`/*`),`Route path "`+e+`" will be treated as if it were `+(`"`+e.replace(/\*$/,`/*`)+'" because the `*` character must ')+"always follow a `/` in the pattern. To get rid of this warning, "+(`please change the route path to "`+e.replace(/\*$/,`/*`)+`".`));let r=[],i=`^`+e.replace(/\/*\*?$/,``).replace(/^\/*/,`/`).replace(/[\\.*+^${}|()[\]]/g,`\\$&`).replace(/\/:([\w-]+)(\?)?/g,(e,t,n)=>(r.push({paramName:t,isOptional:n!=null}),n?`/?([^\\/]+)?`:`/([^\\/]+)`));return e.endsWith(`*`)?(r.push({paramName:`*`}),i+=e===`*`||e===`/*`?`(.*)$`:`(?:\\/(.+)|\\/*)$`):n?i+=`\\/*$`:e!==``&&e!==`/`&&(i+=`(?:(?=\\/|$))`),[new RegExp(i,t?void 0:`i`),r]}function br(e){try{return e.split(`/`).map(e=>decodeURIComponent(e).replace(/\//g,`%2F`)).join(`/`)}catch(t){return Yn(!1,`The URL path "`+e+`" could not be decoded because it is is a malformed URL segment. This is probably due to a bad percent `+(`encoding (`+t+`).`)),e}}function xr(e,t){if(t===`/`)return e;if(!e.toLowerCase().startsWith(t.toLowerCase()))return null;let n=t.endsWith(`/`)?t.length-1:t.length,r=e.charAt(n);return r&&r!==`/`?null:e.slice(n)||`/`}var Sr=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,Cr=e=>Sr.test(e);function wr(e,t){t===void 0&&(t=`/`);let{pathname:n,search:r=``,hash:i=``}=typeof e==`string`?er(e):e,a;if(n)if(Cr(n))a=n;else{if(n.includes(`//`)){let e=n;n=Ar(n),Yn(!1,`Pathnames cannot have embedded double slashes - normalizing `+(e+` -> `+n))}a=n.startsWith(`/`)?Tr(n.substring(1),`/`):Tr(n,t)}else a=t;return{pathname:a,search:Nr(r),hash:Pr(i)}}function Tr(e,t){let n=t.replace(/\/+$/,``).split(`/`);return e.split(`/`).forEach(e=>{e===`..`?n.length>1&&n.pop():e!==`.`&&n.push(e)}),n.length>1?n.join(`/`):`/`}function Er(e,t,n,r){return`Cannot include a '`+e+`' character in a manually specified `+("`to."+t+"` field ["+JSON.stringify(r)+`]. Please separate it out to the `)+("`to."+n+"` field. Alternatively you may provide the full path as ")+`a string in and the router will parse it for you.`}function Dr(e){return e.filter((e,t)=>t===0||e.route.path&&e.route.path.length>0)}function Or(e,t){let n=Dr(e);return t?n.map((e,t)=>t===n.length-1?e.pathname:e.pathnameBase):n.map(e=>e.pathnameBase)}function kr(e,t,n,r){r===void 0&&(r=!1);let i;typeof e==`string`?i=er(e):(i=Wn({},e),Jn(!i.pathname||!i.pathname.includes(`?`),Er(`?`,`pathname`,`search`,i)),Jn(!i.pathname||!i.pathname.includes(`#`),Er(`#`,`pathname`,`hash`,i)),Jn(!i.search||!i.search.includes(`#`),Er(`#`,`search`,`hash`,i)));let a=e===``||i.pathname===``,o=a?`/`:i.pathname,s;if(o==null)s=n;else{let e=t.length-1;if(!r&&o.startsWith(`..`)){let t=o.split(`/`);for(;t[0]===`..`;)t.shift(),--e;i.pathname=t.join(`/`)}s=e>=0?t[e]:`/`}let c=wr(i,s),l=o&&o!==`/`&&o.endsWith(`/`),u=(a||o===`.`)&&n.endsWith(`/`);return!c.pathname.endsWith(`/`)&&(l||u)&&(c.pathname+=`/`),c}var Ar=e=>e.replace(/\/\/+/g,`/`),jr=e=>Ar(e.join(`/`)),Mr=e=>e.replace(/\/+$/,``).replace(/^\/*/,`/`),Nr=e=>!e||e===`?`?``:e.startsWith(`?`)?e:`?`+e,Pr=e=>!e||e===`#`?``:e.startsWith(`#`)?e:`#`+e;function Fr(e){return e!=null&&typeof e.status==`number`&&typeof e.statusText==`string`&&typeof e.internal==`boolean`&&`data`in e}var Ir=[`post`,`put`,`patch`,`delete`];new Set(Ir);var Lr=[`get`,...Ir];new Set(Lr);function Rr(){return Rr=Object.assign?Object.assign.bind():function(e){for(var t=1;t{s.current=!0}),N.useCallback(function(n,i){if(i===void 0&&(i={}),!s.current)return;if(typeof n==`number`){r.go(n);return}let c=kr(n,JSON.parse(o),a,i.relative===`path`);e==null&&t!==`/`&&(c.pathname=c.pathname===`/`?t:jr([t,c.pathname])),(i.replace?r.replace:r.push)(c,i.state,i)},[t,r,o,a,e])}function Zr(e,t){let{relative:n}=t===void 0?{}:t,{future:r}=N.useContext(Vr),{matches:i}=N.useContext(Ur),{pathname:a}=qr(),o=JSON.stringify(Or(i,r.v7_relativeSplatPath));return N.useMemo(()=>kr(e,JSON.parse(o),a,n===`path`),[e,o,a,n])}function Qr(e,t){return $r(e,t)}function $r(e,t,n,r){!Kr()&&Jn(!1);let{navigator:i}=N.useContext(Vr),{matches:a}=N.useContext(Ur),o=a[a.length-1],s=o?o.params:{};o&&o.pathname;let c=o?o.pathnameBase:`/`;o&&o.route;let l=qr(),u;if(t){let e=typeof t==`string`?er(t):t;!(c===`/`||e.pathname?.startsWith(c))&&Jn(!1),u=e}else u=l;let d=u.pathname||`/`,f=d;if(c!==`/`){let e=c.replace(/^\//,``).split(`/`);f=`/`+d.replace(/^\//,``).split(`/`).slice(e.length).join(`/`)}let p=rr(e,{pathname:f}),m=ii(p&&p.map(e=>Object.assign({},e,{params:Object.assign({},s,e.params),pathname:jr([c,i.encodeLocation?i.encodeLocation(e.pathname).pathname:e.pathname]),pathnameBase:e.pathnameBase===`/`?c:jr([c,i.encodeLocation?i.encodeLocation(e.pathnameBase).pathname:e.pathnameBase])})),a,n,r);return t&&m?N.createElement(Hr.Provider,{value:{location:Rr({pathname:`/`,search:``,hash:``,state:null,key:`default`},u),navigationType:Gn.Pop}},m):m}function ei(){let e=di(),t=Fr(e)?e.status+` `+e.statusText:e instanceof Error?e.message:JSON.stringify(e),n=e instanceof Error?e.stack:null;return N.createElement(N.Fragment,null,N.createElement(`h2`,null,`Unexpected Application Error!`),N.createElement(`h3`,{style:{fontStyle:`italic`}},t),n?N.createElement(`pre`,{style:{padding:`0.5rem`,backgroundColor:`rgba(200,200,200, 0.5)`}},n):null,null)}var ti=N.createElement(ei,null),ni=class extends N.Component{constructor(e){super(e),this.state={location:e.location,revalidation:e.revalidation,error:e.error}}static getDerivedStateFromError(e){return{error:e}}static getDerivedStateFromProps(e,t){return t.location!==e.location||t.revalidation!==`idle`&&e.revalidation===`idle`?{error:e.error,location:e.location,revalidation:e.revalidation}:{error:e.error===void 0?t.error:e.error,location:t.location,revalidation:e.revalidation||t.revalidation}}componentDidCatch(e,t){console.error(`React Router caught the following error during render`,e,t)}render(){return this.state.error===void 0?this.props.children:N.createElement(Ur.Provider,{value:this.props.routeContext},N.createElement(Wr.Provider,{value:this.state.error,children:this.props.component}))}};function ri(e){let{routeContext:t,match:n,children:r}=e,i=N.useContext(zr);return i&&i.static&&i.staticContext&&(n.route.errorElement||n.route.ErrorBoundary)&&(i.staticContext._deepestRenderedBoundaryId=n.route.id),N.createElement(Ur.Provider,{value:t},r)}function ii(e,t,n,r){if(t===void 0&&(t=[]),n===void 0&&(n=null),r===void 0&&(r=null),e==null){var i;if(!n)return null;if(n.errors)e=n.matches;else if((i=r)!=null&&i.v7_partialHydration&&t.length===0&&!n.initialized&&n.matches.length>0)e=n.matches;else return null}let a=e,o=n?.errors;if(o!=null){let e=a.findIndex(e=>e.route.id&&o?.[e.route.id]!==void 0);!(e>=0)&&Jn(!1),a=a.slice(0,Math.min(a.length,e+1))}let s=!1,c=-1;if(n&&r&&r.v7_partialHydration)for(let e=0;e=0?a.slice(0,c+1):[a[0]];break}}}return a.reduceRight((e,r,i)=>{let l,u=!1,d=null,f=null;n&&(l=o&&r.route.id?o[r.route.id]:void 0,d=r.route.errorElement||ti,s&&(c<0&&i===0?(mi(`route-fallback`,!1,"No `HydrateFallback` element provided to render during initial hydration"),u=!0,f=null):c===i&&(u=!0,f=r.route.hydrateFallbackElement||null)));let p=t.concat(a.slice(0,i+1)),m=()=>{let t;return t=l?d:u?f:r.route.Component?N.createElement(r.route.Component,null):r.route.element?r.route.element:e,N.createElement(ri,{match:r,routeContext:{outlet:e,matches:p,isDataRoute:n!=null},children:t})};return n&&(r.route.ErrorBoundary||r.route.errorElement||i===0)?N.createElement(ni,{location:n.location,revalidation:n.revalidation,component:d,error:l,children:m(),routeContext:{outlet:null,matches:p,isDataRoute:!0}}):m()},null)}var ai=function(e){return e.UseBlocker=`useBlocker`,e.UseRevalidator=`useRevalidator`,e.UseNavigateStable=`useNavigate`,e}(ai||{}),oi=function(e){return e.UseBlocker=`useBlocker`,e.UseLoaderData=`useLoaderData`,e.UseActionData=`useActionData`,e.UseRouteError=`useRouteError`,e.UseNavigation=`useNavigation`,e.UseRouteLoaderData=`useRouteLoaderData`,e.UseMatches=`useMatches`,e.UseRevalidator=`useRevalidator`,e.UseNavigateStable=`useNavigate`,e.UseRouteId=`useRouteId`,e}(oi||{});function si(e){let t=N.useContext(zr);return!t&&Jn(!1),t}function ci(e){let t=N.useContext(Br);return!t&&Jn(!1),t}function li(e){let t=N.useContext(Ur);return!t&&Jn(!1),t}function ui(e){let t=li(e),n=t.matches[t.matches.length-1];return!n.route.id&&Jn(!1),n.route.id}function di(){let e=N.useContext(Wr),t=ci(oi.UseRouteError),n=ui(oi.UseRouteError);return e===void 0?t.errors?.[n]:e}function fi(){let{router:e}=si(ai.UseNavigateStable),t=ui(oi.UseNavigateStable),n=N.useRef(!1);return Jr(()=>{n.current=!0}),N.useCallback(function(r,i){i===void 0&&(i={}),n.current&&(typeof r==`number`?e.navigate(r):e.navigate(r,Rr({fromRouteId:t},i)))},[e,t])}var pi={};function mi(e,t,n){!t&&!pi[e]&&(pi[e]=!0)}var hi=(e,t,n)=>(``+t+("You can use the `"+e+"` future flag to opt-in early. ")+(`For more information, see `+n+`.`),void 0);function gi(e,t){e?.v7_startTransition===void 0&&hi(`v7_startTransition`,"React Router will begin wrapping state updates in `React.startTransition` in v7",`https://reactrouter.com/v6/upgrading/future#v7_starttransition`),e?.v7_relativeSplatPath===void 0&&(!t||t.v7_relativeSplatPath===void 0)&&hi(`v7_relativeSplatPath`,`Relative route resolution within Splat routes is changing in v7`,`https://reactrouter.com/v6/upgrading/future#v7_relativesplatpath`),t&&(t.v7_fetcherPersist===void 0&&hi(`v7_fetcherPersist`,`The persistence behavior of fetchers is changing in v7`,`https://reactrouter.com/v6/upgrading/future#v7_fetcherpersist`),t.v7_normalizeFormMethod===void 0&&hi(`v7_normalizeFormMethod`,"Casing of `formMethod` fields is being normalized to uppercase in v7",`https://reactrouter.com/v6/upgrading/future#v7_normalizeformmethod`),t.v7_partialHydration===void 0&&hi(`v7_partialHydration`,"`RouterProvider` hydration behavior is changing in v7",`https://reactrouter.com/v6/upgrading/future#v7_partialhydration`),t.v7_skipActionErrorRevalidation===void 0&&hi(`v7_skipActionErrorRevalidation`,"The revalidation behavior after 4xx/5xx `action` responses is changing in v7",`https://reactrouter.com/v6/upgrading/future#v7_skipactionerrorrevalidation`))}function _i(e){let{to:t,replace:n,state:r,relative:i}=e;!Kr()&&Jn(!1);let{future:a,static:o}=N.useContext(Vr),{matches:s}=N.useContext(Ur),{pathname:c}=qr(),l=Yr(),u=kr(t,Or(s,a.v7_relativeSplatPath),c,i===`path`),d=JSON.stringify(u);return N.useEffect(()=>l(JSON.parse(d),{replace:n,state:r,relative:i}),[l,d,i,n,r]),null}function vi(e){Jn(!1)}function yi(e){let{basename:t=`/`,children:n=null,location:r,navigationType:i=Gn.Pop,navigator:a,static:o=!1,future:s}=e;Kr()&&Jn(!1);let c=t.replace(/^\/*/,`/`),l=N.useMemo(()=>({basename:c,navigator:a,static:o,future:Rr({v7_relativeSplatPath:!1},s)}),[c,s,a,o]);typeof r==`string`&&(r=er(r));let{pathname:u=`/`,search:d=``,hash:f=``,state:p=null,key:m=`default`}=r,h=N.useMemo(()=>{let e=xr(u,c);return e==null?null:{location:{pathname:e,search:d,hash:f,state:p,key:m},navigationType:i}},[c,u,d,f,p,m,i]);return h==null?null:N.createElement(Vr.Provider,{value:l},N.createElement(Hr.Provider,{children:n,value:h}))}function bi(e){let{children:t,location:n}=e;return Qr(Si(t),n)}var xi=function(e){return e[e.pending=0]=`pending`,e[e.success=1]=`success`,e[e.error=2]=`error`,e}(xi||{});new Promise(()=>{}),N.Component;function Si(e,t){t===void 0&&(t=[]);let n=[];return N.Children.forEach(e,(e,r)=>{if(!N.isValidElement(e))return;let i=[...t,r];if(e.type===N.Fragment){n.push.apply(n,Si(e.props.children,i));return}e.type!==vi&&Jn(!1),!(!e.props.index||!e.props.children)&&Jn(!1);let a={id:e.props.id||i.join(`-`),caseSensitive:e.props.caseSensitive,element:e.props.element,Component:e.props.Component,index:e.props.index,path:e.props.path,loader:e.props.loader,action:e.props.action,errorElement:e.props.errorElement,ErrorBoundary:e.props.ErrorBoundary,hasErrorBoundary:e.props.ErrorBoundary!=null||e.props.errorElement!=null,shouldRevalidate:e.props.shouldRevalidate,handle:e.props.handle,lazy:e.props.lazy};e.props.children&&(a.children=Si(e.props.children,i)),n.push(a)}),n}function Ci(){return Ci=Object.assign?Object.assign.bind():function(e){for(var t=1;t{let r=e[n];return t.concat(Array.isArray(r)?r.map(e=>[n,e]):[[n,r]])},[]))}function Oi(e,t){let n=Di(e);return t&&t.forEach((e,r)=>{n.has(r)||t.getAll(r).forEach(e=>{n.append(r,e)})}),n}var ki=[`onClick`,`relative`,`reloadDocument`,`replace`,`state`,`target`,`to`,`preventScrollReset`,`viewTransition`],Ai=[`aria-current`,`caseSensitive`,`className`,`end`,`style`,`to`,`viewTransition`,`children`],ji=`6`;try{window.__reactRouterVersion=ji}catch{}var Mi=N.createContext({isTransitioning:!1}),Ni=N.startTransition;function Pi(e){let{basename:t,children:n,future:r,window:i}=e,a=N.useRef();a.current??(a.current=qn({window:i,v5Compat:!0}));let o=a.current,[s,c]=N.useState({action:o.action,location:o.location}),{v7_startTransition:l}=r||{},u=N.useCallback(e=>{l&&Ni?Ni(()=>c(e)):c(e)},[c,l]);return N.useLayoutEffect(()=>o.listen(u),[o,u]),N.useEffect(()=>gi(r),[r]),N.createElement(yi,{basename:t,children:n,location:s.location,navigationType:s.action,navigator:o,future:r})}var Fi=typeof window<`u`&&window.document!==void 0&&window.document.createElement!==void 0,Ii=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,Li=N.forwardRef(function(e,t){let{onClick:n,relative:r,reloadDocument:i,replace:a,state:o,target:s,to:c,preventScrollReset:l,viewTransition:u}=e,d=wi(e,ki),{basename:f}=N.useContext(Vr),p,m=!1;if(typeof c==`string`&&Ii.test(c)&&(p=c,Fi))try{let e=new URL(window.location.href),t=c.startsWith(`//`)?new URL(e.protocol+c):new URL(c),n=xr(t.pathname,f);t.origin===e.origin&&n!=null?c=n+t.search+t.hash:m=!0}catch{}let h=Gr(c,{relative:r}),g=Bi(c,{replace:a,state:o,target:s,preventScrollReset:l,relative:r,viewTransition:u});function _(e){n&&n(e),e.defaultPrevented||g(e)}return N.createElement(`a`,Ci({},d,{href:p||h,onClick:m||i?n:_,ref:t,target:s}))}),F=N.forwardRef(function(e,t){let{"aria-current":n=`page`,caseSensitive:r=!1,className:i=``,end:a=!1,style:o,to:s,viewTransition:c,children:l}=e,u=wi(e,Ai),d=Zr(s,{relative:u.relative}),f=qr(),p=N.useContext(Br),{navigator:m,basename:h}=N.useContext(Vr),g=p!=null&&Hi(d)&&c===!0,_=m.encodeLocation?m.encodeLocation(d).pathname:d.pathname,v=f.pathname,y=p&&p.navigation&&p.navigation.location?p.navigation.location.pathname:null;r||(v=v.toLowerCase(),y=y?y.toLowerCase():null,_=_.toLowerCase()),y&&h&&(y=xr(y,h)||y);let b=_!==`/`&&_.endsWith(`/`)?_.length-1:_.length,x=v===_||!a&&v.startsWith(_)&&v.charAt(b)===`/`,S=y!=null&&(y===_||!a&&y.startsWith(_)&&y.charAt(_.length)===`/`),C={isActive:x,isPending:S,isTransitioning:g},w=x?n:void 0,T;T=typeof i==`function`?i(C):[i,x?`active`:null,S?`pending`:null,g?`transitioning`:null].filter(Boolean).join(` `);let E=typeof o==`function`?o(C):o;return N.createElement(Li,Ci({},u,{"aria-current":w,className:T,ref:t,style:E,to:s,viewTransition:c}),typeof l==`function`?l(C):l)}),I;(function(e){e.UseScrollRestoration=`useScrollRestoration`,e.UseSubmit=`useSubmit`,e.UseSubmitFetcher=`useSubmitFetcher`,e.UseFetcher=`useFetcher`,e.useViewTransitionState=`useViewTransitionState`})(I||(I={}));var Ri;(function(e){e.UseFetcher=`useFetcher`,e.UseFetchers=`useFetchers`,e.UseScrollRestoration=`useScrollRestoration`})(Ri||(Ri={}));function zi(e){let t=N.useContext(zr);return!t&&Jn(!1),t}function Bi(e,t){let{target:n,replace:r,state:i,preventScrollReset:a,relative:o,viewTransition:s}=t===void 0?{}:t,c=Yr(),l=qr(),u=Zr(e,{relative:o});return N.useCallback(t=>{Ei(t,n)&&(t.preventDefault(),c(e,{replace:r===void 0?$n(l)===$n(u):r,state:i,preventScrollReset:a,relative:o,viewTransition:s}))},[l,c,u,r,i,n,e,a,o,s])}function Vi(e){let t=N.useRef(Di(e)),n=N.useRef(!1),r=qr(),i=N.useMemo(()=>Oi(r.search,n.current?null:t.current),[r.search]),a=Yr();return[i,N.useCallback((e,t)=>{let r=Di(typeof e==`function`?e(i):e);n.current=!0,a(`?`+r,t)},[a,i])]}function Hi(e,t){t===void 0&&(t={});let n=N.useContext(Mi);n??Jn(!1);let{basename:r}=zi(I.useViewTransitionState),i=Zr(e,{relative:t.relative});if(!n.isTransitioning)return!1;let a=xr(n.currentLocation.pathname,r)||n.currentLocation.pathname,o=xr(n.nextLocation.pathname,r)||n.nextLocation.pathname;return vr(i.pathname,o)!=null||vr(i.pathname,a)!=null}var Ui=e=>{let t,n=new Set,r=(e,r)=>{let i=typeof e==`function`?e(t):e;if(!Object.is(i,t)){let e=t;t=r??(typeof i!=`object`||!i)?i:Object.assign({},t,i),n.forEach(n=>n(t,e))}},i=()=>t,a={setState:r,getState:i,getInitialState:()=>o,subscribe:e=>(n.add(e),()=>n.delete(e))},o=t=e(r,i,a);return a},Wi=(e=>e?Ui(e):Ui),Gi=e=>e;function Ki(e,t=Gi){let n=N.useSyncExternalStore(e.subscribe,N.useCallback(()=>t(e.getState()),[e,t]),N.useCallback(()=>t(e.getInitialState()),[e,t]));return N.useDebugValue(n),n}var qi=e=>{let t=Wi(e),n=e=>Ki(t,e);return Object.assign(n,t),n},Ji=(e=>e?qi(e):qi),Yi=`anyllm_admin_token`;function Xi(){try{let e=new URLSearchParams(window.location.search),t=e.get(`token`);if(!t)return null;e.delete(`token`);let n=e.toString(),r=window.location.pathname+(n?`?${n}`:``)+window.location.hash;return window.history.replaceState(null,``,r),t}catch{return null}}function Zi(){let e=Xi();if(e)return Qi(e),e;try{return window.sessionStorage.getItem(Yi)}catch{return null}}function Qi(e){try{window.sessionStorage.setItem(Yi,e)}catch{}}function $i(){try{window.sessionStorage.removeItem(Yi)}catch{}}var ea=Ji(e=>({token:Zi(),login(t){Qi(t),e({token:t})},logout(){$i(),e({token:null})}})),ta=Ji(e=>({status:`disconnected`,lastEvent:null,setStatus:t=>e({status:t}),pushEvent:t=>e({lastEvent:t})})),na=3e4,ra=1e3,L=null,ia=null,aa=0,oa=!1;function sa(){oa=!1,!(L&&(L.readyState===WebSocket.OPEN||L.readyState===WebSocket.CONNECTING))&&la()}function ca(){oa=!0,ia&&clearTimeout(ia),L?.close(),L=null,ta.getState().setStatus(`disconnected`)}function la(){if(oa)return;let e=ea.getState().token;if(!e)return;ta.getState().setStatus(`connecting`);let t=location.protocol===`https:`?`wss:`:`ws:`;L=new WebSocket(`${t}//${location.host}/admin/ws`),L.onopen=()=>{L.send(JSON.stringify({token:e}))},L.onmessage=e=>{let t;try{t=JSON.parse(e.data)}catch{return}if(typeof t==`object`&&t&&`status`in t&&t.status===`authenticated`){aa=0,ta.getState().setStatus(`connected`);return}typeof t==`object`&&t&&`type`in t&&ta.getState().pushEvent(t)},L.onclose=()=>{if(oa)return;ta.getState().setStatus(`disconnected`);let e=Math.min(ra*2**aa,na);aa++,ia=setTimeout(la,e)},L.onerror=()=>{L?.close()}}var ua=5,da=4e3,fa=1,pa=Ji(e=>({toasts:[],push({variant:t,message:n,ttlMs:r}){let i=fa++,a=r===void 0?t===`error`?null:da:r;return e(e=>{let r=[...e.toasts,{id:i,variant:t,message:n,ttlMs:a}];return{toasts:r.length>ua?r.slice(-5):r}}),i},dismiss(t){e(e=>({toasts:e.toasts.filter(e=>e.id!==t)}))},clear(){e({toasts:[]})}}));function ma(e){return pa.getState().push(e)}function ha(){let e=Promise.resolve();return function(t){let n=e.then(t,t);return e=n.catch(()=>void 0),n}}var ga=ha();async function _a(e,t){let n=await e(`/admin/csrf-token`,{headers:{Authorization:`Bearer ${t()}`}});if(!n.ok)throw Error(`Failed to fetch CSRF token`);let r=await n.json();if(typeof r.csrf_token!=`string`)throw Error(`Failed to fetch CSRF token`);return r.csrf_token}function va(e,t,n){return{Authorization:`Bearer ${t}`,"X-CSRF-Token":e,...n?{"Content-Type":n}:{}}}function ya(e){return e.status===204||e.headers.get(`content-length`)===`0`}async function ba(e,t,n,r,i){let a=async a=>i.fetchImpl(t,{method:e,headers:va(a,i.getToken(),r),body:n}),o=await _a(i.fetchImpl,i.getToken),s=await a(o);if(s.status===403&&(o=await _a(i.fetchImpl,i.getToken),s=await a(o)),await i.handleAuthAndErrors(s),!ya(s))return s.json()}function xa(e){"@babel/helpers - typeof";return xa=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},xa(e)}var Sa=o((()=>{}));function Ca(e,t){if(xa(e)!=`object`||!e)return e;var n=e[Symbol.toPrimitive];if(n!==void 0){var r=n.call(e,t||`default`);if(xa(r)!=`object`)return r;throw TypeError(`@@toPrimitive must return a primitive value.`)}return(t===`string`?String:Number)(e)}var wa=o((()=>{Sa()}));function Ta(e){var t=Ca(e,`string`);return xa(t)==`symbol`?t:t+``}var Ea=o((()=>{Sa(),wa()}));function Da(e,t,n){return(t=Ta(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}o((()=>{Ea()}))();function Oa(){return ea.getState().token??``}var ka=class extends Error{constructor(e){super(`Rate limited. Retry in ${e}s.`),Da(this,`retryAfterSeconds`,void 0),this.name=`RateLimitError`,this.retryAfterSeconds=e}};function Aa(e){let t=e.headers.get(`retry-after`);if(!t)return 1;let n=Number(t);if(Number.isFinite(n)&&n>0)return Math.ceil(n);let r=Date.parse(t);return Number.isNaN(r)?1:Math.max(1,Math.ceil((r-Date.now())/1e3))}async function ja(e){if(e.status===401)throw ea.getState().logout(),Error(`Unauthorized`);if(e.status===429){let t=Aa(e);throw ma({variant:`warn`,message:`Admin API rate-limited. Retry in ${t}s.`,ttlMs:t*1e3}),new ka(t)}if(!e.ok){let t=await e.text().catch(()=>e.statusText);throw Error(t||`HTTP ${e.status}`)}}async function Ma(e,t){let n=await fetch(e,{...t,headers:{Authorization:`Bearer ${Oa()}`,...t?.headers??{}}});return await ja(n),n.json()}async function Na(e,t,n,r){return ga(()=>ba(e,t,n,r,{fetchImpl:(e,t)=>fetch(e,t),getToken:Oa,handleAuthAndErrors:ja}))}function R(e,t,n){return Na(e,t,n===void 0?void 0:JSON.stringify(n),n===void 0?void 0:`application/json`)}function Pa(e,t){return Na(`POST`,e,t)}function Fa(e=!0){return Bn({queryKey:[`status`],queryFn:()=>Ma(`/admin/api/status`),enabled:e,refetchInterval:1e4})}function Ia(){return Bn({queryKey:[`metrics`],queryFn:()=>Ma(`/admin/api/metrics`),refetchInterval:5e3,staleTime:0})}function La(e,t){return Bn({queryKey:[`observability`,e,t],queryFn:()=>Ma(`/admin/api/observability/overview?window=${e}&backend=${encodeURIComponent(t)}`),refetchInterval:3e4,staleTime:0})}function Ra(e){let t=new URLSearchParams;return t.set(`limit`,String(e.page_size)),t.set(`offset`,String((e.page-1)*e.page_size)),e.backend&&t.set(`backend`,e.backend),e.status&&t.set(`status`,e.status),e.since&&t.set(`since`,e.since),e.until&&t.set(`until`,e.until),e.model&&t.set(`model`,e.model),Bn({queryKey:[`requests`,e],queryFn:()=>Ma(`/admin/api/requests?${t}`),staleTime:1/0})}function za(){return Bn({queryKey:[`keys`],queryFn:()=>Ma(`/admin/api/keys`).then(e=>e.keys),staleTime:1/0})}function Ba(){let e=Tn();return Vn({mutationFn:e=>R(`POST`,`/admin/api/keys`,e),onSuccess:()=>{e.invalidateQueries({queryKey:[`keys`]})}})}function Va(){let e=Tn();return Vn({mutationFn:({id:e,body:t})=>R(`PUT`,`/admin/api/keys/${e}`,t),onSuccess:()=>{e.invalidateQueries({queryKey:[`keys`]})}})}function Ha(){let e=Tn();return Vn({mutationFn:e=>R(`DELETE`,`/admin/api/keys/${e}`),onSuccess:()=>{e.invalidateQueries({queryKey:[`keys`]})}})}function Ua(){return Bn({queryKey:[`backends`],queryFn:()=>Ma(`/admin/api/backends`).then(e=>e.backends),staleTime:1/0})}function Wa(){return Bn({queryKey:[`config`],queryFn:()=>Promise.all([Ma(`/admin/api/config`),Ma(`/admin/api/config/overrides`)]).then(([e,t])=>({...e,entries:t.overrides??[],env:{}})),staleTime:1/0})}function Ga(){let e=Tn();return Vn({mutationFn:e=>R(`PUT`,`/admin/api/config`,e),onSuccess:()=>{e.invalidateQueries({queryKey:[`config`]}),ma({variant:`success`,message:`Setting saved — applied live, no restart needed`})}})}function Ka(){let e=Tn();return Vn({mutationFn:e=>R(`DELETE`,`/admin/api/config/overrides/${encodeURIComponent(e)}`),onSuccess:()=>{e.invalidateQueries({queryKey:[`config`]})}})}function qa(){return Bn({queryKey:[`optimizer-model`],queryFn:()=>Ma(`/admin/api/optimizer/model`),refetchInterval:e=>e.state.data?.downloading?2e3:!1})}function Ja(){let e=Tn();return Vn({mutationFn:()=>R(`POST`,`/admin/api/optimizer/model`),onSuccess:()=>{e.invalidateQueries({queryKey:[`optimizer-model`]}),ma({variant:`success`,message:`Model download started`})}})}function Ya(){return Bn({queryKey:[`env`],queryFn:()=>Ma(`/admin/api/env`),staleTime:1/0})}function Xa(){return Bn({queryKey:[`models`],queryFn:()=>Ma(`/admin/api/models`),staleTime:1/0})}function Za(){let e=Tn();return Vn({mutationFn:e=>R(`POST`,`/admin/api/models`,e),onSuccess:()=>{e.invalidateQueries({queryKey:[`models`]})}})}function Qa(){let e=Tn();return Vn({mutationFn:e=>R(`DELETE`,`/admin/api/models/${encodeURIComponent(e)}`),onSuccess:()=>{e.invalidateQueries({queryKey:[`models`]})}})}function $a(){return Vn({mutationFn:e=>R(`POST`,`/admin/api/models/discover`,e)})}function eo(e){return Bn({queryKey:[`audit`,e],queryFn:()=>Ma(`/admin/api/audit?limit=${e.page_size}&offset=${(e.page-1)*e.page_size}`),staleTime:1/0})}function to(e){return Bn({queryKey:[`traffic`,e],queryFn:()=>Ma(`/admin/api/traffic?window=${e}`),refetchInterval:3e4,staleTime:0})}function no(){return Bn({queryKey:[`uptime`],queryFn:()=>Ma(`/admin/api/uptime`),refetchInterval:3e4,staleTime:0})}function ro(){return Vn({mutationFn:e=>{let t=new FormData;return t.append(`file`,e),Pa(`/admin/api/env/import`,t)}})}function io(){return Bn({queryKey:[`catalog-providers`],queryFn:()=>Ma(`/admin/api/catalog/providers`).then(e=>e.providers),staleTime:1/0})}function ao(){return Bn({queryKey:[`favorites`],queryFn:()=>Ma(`/admin/api/favorites`).then(e=>e.favorites),staleTime:1/0})}function oo(){let e=Tn();return Vn({mutationFn:({providerId:e,on:t})=>t?R(`POST`,`/admin/api/favorites`,{provider_id:e}):R(`DELETE`,`/admin/api/favorites/${encodeURIComponent(e)}`),onSuccess:()=>{e.invalidateQueries({queryKey:[`favorites`]})}})}function so(){return Bn({queryKey:[`managed-backends`],queryFn:()=>Ma(`/admin/api/backends/managed`),staleTime:1/0})}function co(){let e=Tn();return Vn({mutationFn:e=>R(`POST`,`/admin/api/backends/managed`,e),onSuccess:()=>{e.invalidateQueries({queryKey:[`managed-backends`]}),e.invalidateQueries({queryKey:[`status`]})}})}function lo(){let e=Tn();return Vn({mutationFn:({name:e,data:t})=>R(`PUT`,`/admin/api/backends/managed/${e}`,t),onSuccess:()=>{e.invalidateQueries({queryKey:[`managed-backends`]})}})}function uo(){let e=Tn();return Vn({mutationFn:e=>R(`DELETE`,`/admin/api/backends/managed/${e}`),onSuccess:()=>{e.invalidateQueries({queryKey:[`managed-backends`]}),e.invalidateQueries({queryKey:[`status`]})}})}function fo(){return Bn({queryKey:[`routes`],queryFn:()=>Ma(`/admin/api/routes`),staleTime:1/0})}function po(){let e=Tn();return Vn({mutationFn:e=>R(`POST`,`/admin/api/routes`,e),onSuccess:()=>{e.invalidateQueries({queryKey:[`routes`]})}})}function mo(){let e=Tn();return Vn({mutationFn:({id:e,data:t})=>R(`PUT`,`/admin/api/routes/${e}`,t),onSuccess:()=>{e.invalidateQueries({queryKey:[`routes`]})}})}function z(){let e=Tn();return Vn({mutationFn:e=>R(`DELETE`,`/admin/api/routes/${e}`),onSuccess:()=>{e.invalidateQueries({queryKey:[`routes`]})}})}function B(e){return Bn({queryKey:[`route-providers`,e],queryFn:()=>Ma(`/admin/api/routes/${e}/providers`),enabled:!!e,staleTime:1/0})}function ho(){let e=Tn();return Vn({mutationFn:({routeId:e,data:t})=>R(`POST`,`/admin/api/routes/${e}/providers`,t),onSuccess:(t,{routeId:n})=>{e.invalidateQueries({queryKey:[`route-providers`,n]}),e.invalidateQueries({queryKey:[`routes`]})}})}function go(){let e=Tn();return Vn({mutationFn:({routeId:e,providerId:t,data:n})=>R(`PUT`,`/admin/api/routes/${e}/providers/${t}`,n),onSuccess:(t,{routeId:n})=>{e.invalidateQueries({queryKey:[`route-providers`,n]})}})}function _o(){let e=Tn();return Vn({mutationFn:({routeId:e,providerId:t})=>R(`DELETE`,`/admin/api/routes/${e}/providers/${t}`),onSuccess:(t,{routeId:n})=>{e.invalidateQueries({queryKey:[`route-providers`,n]}),e.invalidateQueries({queryKey:[`routes`]})}})}function vo(){let e=Tn();return Vn({mutationFn:({routeId:e,data:t})=>R(`PUT`,`/admin/api/routes/${e}/providers/reorder`,t),onSuccess:(t,{routeId:n})=>{e.invalidateQueries({queryKey:[`route-providers`,n]}),e.invalidateQueries({queryKey:[`routes`]})}})}async function yo(){let e=ea.getState().token??``,t=await fetch(`/admin/api/env/export`,{headers:{Authorization:`Bearer ${e}`}});if(!t.ok)throw Error(`Export failed: HTTP ${t.status}`);let n=await t.blob(),r=URL.createObjectURL(n),i=document.createElement(`a`);i.href=r,i.download=`.anyllm.env`,document.body.appendChild(i),i.click(),document.body.removeChild(i),URL.revokeObjectURL(r)}function V(...e){let t=[],n=e=>{e&&(typeof e==`string`||typeof e==`number`?t.push(String(e)):Array.isArray(e)&&e.forEach(n))};return e.forEach(n),t.join(` `)}var bo=(0,N.forwardRef)(({glyph:e=`✦`,solid:t,static:n,className:r,...i},a)=>(0,P.jsx)(`span`,{ref:a,"aria-hidden":`true`,className:V(`pui-sparkle`,!n&&`pui-sparkle--blink`,t&&`pui-sparkle--solid`,r),...i,children:e}));bo.displayName=`Sparkle`;var xo=(0,N.forwardRef)(({as:e,static:t,className:n,children:r,...i},a)=>(0,P.jsx)(e??`span`,{ref:a,className:V(`pui-gradient-text`,!t&&`pui-gradient-text--animate`,n),...i,children:r}));xo.displayName=`GradientText`;var So=(0,N.forwardRef)(({color:e,static:t,className:n,style:r,...i},a)=>(0,P.jsx)(`span`,{ref:a,"aria-hidden":`true`,className:V(`pui-dot`,!t&&`pui-dot--pulse`,n),style:e?{...r,background:e,color:e}:r,...i}));So.displayName=`StatusDot`;var Co=new Set([`flash1`,`flash2`,`flash3`,`glow1`,`glow2`,`glow3`]),wo=(0,N.forwardRef)(({text:e,animation:t=`wave`,color:n=`glow1`,className:r,style:i,...a},o)=>{let s=Co.has(n),c=s?`pui-quest--${n}`:void 0,l=s?void 0:{color:n};if(t===`wave`){let t=[...e];return(0,P.jsx)(`span`,{ref:o,className:V(`pui-quest`,`pui-quest--wave`,c,r),style:{...l,...i},...a,children:t.map((e,t)=>(0,P.jsx)(`span`,{className:`pui-quest__char`,style:{animationDelay:`${-(t+1)*50}ms`},children:e===` `?`\xA0`:e},t))})}let u=t===`scroll`?`pui-quest__scroll`:`pui-quest__slide`;return(0,P.jsx)(`span`,{ref:o,className:V(`pui-quest`,`pui-quest--${t}`,c,r),style:{...l,...i},...a,children:(0,P.jsx)(`span`,{className:u,children:e})})});wo.displayName=`QuestText`;function To({variant:e=`glow`,size:t=`md`,sparkle:n,loading:r,block:i,as:a,className:o,children:s,disabled:c,...l},u){let d=a??`button`;return(0,P.jsxs)(d,{ref:u,className:V(`pui-btn`,`pui-btn--${e}`,t!==`md`&&`pui-btn--${t}`,i&&`pui-btn--block`,o),disabled:d===`button`?c||r:void 0,"aria-busy":r||void 0,...l,children:[r?(0,P.jsx)(`span`,{className:`pui-btn__spinner`,"aria-hidden":!0}):null,(0,P.jsx)(`span`,{children:s}),n?(0,P.jsx)(bo,{}):null]})}var Eo=(0,N.forwardRef)(To),Do=(0,N.forwardRef)(({hideSparkle:e,trailing:t,className:n,children:r,...i},a)=>(0,P.jsxs)(`div`,{ref:a,className:V(`pui-sticky-banner`,n),...i,children:[!e&&(0,P.jsx)(bo,{}),(0,P.jsx)(`span`,{children:r}),t]}));Do.displayName=`StickyBanner`;var Oo=(0,N.forwardRef)(({icon:e,statusColor:t,className:n,children:r,...i},a)=>(0,P.jsxs)(`span`,{ref:a,className:V(`pui-eyebrow`,n),...i,children:[e===!1?null:e??(0,P.jsx)(So,{color:t}),(0,P.jsx)(`span`,{children:r})]}));Oo.displayName=`EyebrowPill`;function ko({words:e,typeMs:t=70,deleteMs:n=32,holdMs:r=1500,loop:i=!0,onWordReached:a}){let[o,s]=(0,N.useState)(``),[c,l]=(0,N.useState)(0),[u,d]=(0,N.useState)(!1),[f,p]=(0,N.useState)(!1),m=(0,N.useRef)({word:o,index:c,isDeleting:u});return m.current={word:o,index:c,isDeleting:u},(0,N.useEffect)(()=>{let o=null,c=!1;if(!e.length)return;let u=()=>{if(c)return;let{word:f,index:h,isDeleting:g}=m.current,_=e[h],v=g?_.slice(0,f.length-1):_.slice(0,f.length+1);if(s(v),!g&&v===_){if(a?.(_,h),!i&&h===e.length-1){p(!0);return}o=setTimeout(()=>{c||(d(!0),o=setTimeout(u,n))},r);return}g&&v===``&&(d(!1),l(t=>(t+1)%e.length)),o=setTimeout(u,g?n:t)};return o=setTimeout(u,t),()=>{c=!0,o&&clearTimeout(o)}},[]),{word:o,index:c,isDeleting:u,isComplete:f}}var Ao=(0,N.forwardRef)(({words:e,typeMs:t,deleteMs:n,holdMs:r,loop:i,onWordReached:a,hideCursor:o,cursor:s,renderWord:c,className:l,...u},d)=>{let{word:f,index:p}=ko({words:e,typeMs:t,deleteMs:n,holdMs:r,loop:i,onWordReached:a});return(0,P.jsxs)(`span`,{ref:d,className:V(`pui-rotator`,l),...u,children:[c?c(f,p):f,!o&&(0,P.jsx)(`span`,{"aria-hidden":`true`,className:V(`pui-rotator__cursor`,s===void 0&&`pui-rotator__cursor--block`,`pui-rotator__cursor--blink`),children:s})]})});Ao.displayName=`Rotator`;var jo=(0,N.forwardRef)(({words:e,intervalMs:t=2200,transitionMs:n=500,direction:r=`up`,gradient:i,className:a,style:o,...s},c)=>{let[l,u]=(0,N.useState)(0);(0,N.useEffect)(()=>{if(!e.length)return;let n=setInterval(()=>u(t=>(t+1)%e.length),t);return()=>clearInterval(n)},[t,e.length]);let d={...o,"--pui-roll-ms":`${n}ms`},f=(l-1+e.length)%e.length;return(0,P.jsxs)(`span`,{ref:c,className:V(`pui-roll`,r===`down`&&`pui-roll--down`,i&&`pui-roll--gradient`,a),style:d,...s,children:[(0,P.jsx)(`span`,{className:`pui-roll__sizer`,"aria-hidden":`true`,children:e[l]}),e.map((e,t)=>(0,P.jsx)(`span`,{className:V(`pui-roll__word`,t===l&&`pui-roll__word--active`,t===f&&l!==f&&`pui-roll__word--past`),"aria-hidden":t===l?void 0:`true`,children:e},t))]})});jo.displayName=`WordRoll`;var Mo=(0,N.forwardRef)(({placeholder:e=`Describe what you want to build…`,defaultValue:t,value:n,onChange:r,onSubmit:i,leading:a,ctaLabel:o=`Generate`,hideCta:s,className:c,...l},u)=>{let d=n!==void 0,[f,p]=(0,N.useState)(t??``),m=d?n:f;return(0,P.jsxs)(`form`,{ref:u,className:V(`pui-prompt`,c),onSubmit:e=>{e.preventDefault(),i?.(m)},...l,children:[a===!1?null:(0,P.jsx)(`span`,{className:`pui-prompt__icon`,children:a??(0,P.jsx)(bo,{})}),(0,P.jsx)(`input`,{className:`pui-prompt__input`,type:`text`,placeholder:e,value:m,onChange:e=>{let t=e.target.value;d||p(t),r?.(t)},autoComplete:`off`}),!s&&(0,P.jsx)(Eo,{type:`submit`,variant:`glow`,sparkle:!0,children:o})]})});Mo.displayName=`PromptHero`;var No=[`GPT-5 Turbo Vision`,`Claude Opus 4.7`,`Gemini 3 Pro`],Po=(0,N.forwardRef)(({value:e,defaultValue:t=``,onChange:n,onSubmit:r,placeholder:i=`Build me a…`,rows:a=3,models:o=No,model:s,defaultModel:c,onModelChange:l,onAddContext:u,onVoice:d,hideAddContext:f,hideModel:p,hideVoice:m,hideSend:h,submitOnCmdEnter:g=!0,toolbarExtras:_,className:v,...y},b)=>{let x=e!==void 0,[S,C]=(0,N.useState)(t),w=x?e:S,T=s!==void 0,[E,D]=(0,N.useState)(c??o[0]??``),O=T?s:E,[ee,te]=(0,N.useState)(!1),ne=(0,N.useRef)(null);(0,N.useEffect)(()=>{if(!ee)return;let e=e=>{var t;(t=ne.current)!=null&&t.contains(e.target)||te(!1)};return document.addEventListener(`mousedown`,e),()=>document.removeEventListener(`mousedown`,e)},[ee]);let re=e=>{x||C(e),n?.(e)},ie=e=>{T||D(e),l?.(e),te(!1)},ae=e=>{e?.preventDefault(),r?.(w,{model:O})},oe=e=>{g&&e.key===`Enter`&&(e.metaKey||e.ctrlKey)&&(e.preventDefault(),ae())};return(0,P.jsxs)(`form`,{ref:b,className:V(`pui-promptbox`,v),onSubmit:ae,...y,children:[(0,P.jsx)(`textarea`,{className:`pui-promptbox__textarea`,value:w,onChange:e=>re(e.target.value),onKeyDown:oe,placeholder:i,rows:a}),(0,P.jsxs)(`div`,{className:`pui-promptbox__toolbar`,children:[!f&&(0,P.jsx)(`button`,{type:`button`,className:`pui-promptbox__iconbtn`,onClick:u,title:`Add context`,"aria-label":`Add context`,children:(0,P.jsx)(Fo,{})}),!p&&o.length>0&&(0,P.jsxs)(`div`,{className:`pui-promptbox__model-wrap`,ref:ne,children:[(0,P.jsxs)(`button`,{type:`button`,className:`pui-promptbox__model`,onClick:()=>te(e=>!e),"aria-expanded":ee,"aria-haspopup":`menu`,children:[(0,P.jsx)(`span`,{children:O}),(0,P.jsx)(Io,{})]}),ee&&(0,P.jsx)(`div`,{className:`pui-promptbox__menu`,role:`menu`,children:o.map(e=>(0,P.jsxs)(`button`,{type:`button`,className:V(`pui-promptbox__menu-item`,e===O&&`pui-promptbox__menu-item--active`),onClick:()=>ie(e),role:`menuitemradio`,"aria-checked":e===O,children:[(0,P.jsx)(`span`,{children:e}),e===O&&(0,P.jsx)(zo,{})]},e))})]}),(0,P.jsx)(`div`,{className:`pui-promptbox__spacer`}),_,!m&&(0,P.jsx)(`button`,{type:`button`,className:`pui-promptbox__iconbtn`,onClick:d,title:`Voice mode`,"aria-label":`Voice mode`,children:(0,P.jsx)(Lo,{})}),!h&&(0,P.jsx)(`button`,{type:`submit`,className:`pui-promptbox__iconbtn pui-promptbox__send`,title:`Send`,"aria-label":`Send`,children:(0,P.jsx)(Ro,{})})]})]})});Po.displayName=`Prompt`;function Fo(){return(0,P.jsx)(`svg`,{viewBox:`0 0 24 24`,width:`14`,height:`14`,fill:`none`,stroke:`currentColor`,strokeWidth:`2.2`,strokeLinecap:`round`,"aria-hidden":`true`,children:(0,P.jsx)(`path`,{d:`M12 5v14M5 12h14`})})}function Io(){return(0,P.jsx)(`svg`,{viewBox:`0 0 24 24`,width:`10`,height:`10`,fill:`none`,stroke:`currentColor`,strokeWidth:`2.5`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,children:(0,P.jsx)(`polyline`,{points:`6 9 12 15 18 9`})})}function Lo(){return(0,P.jsxs)(`svg`,{viewBox:`0 0 24 24`,width:`14`,height:`14`,fill:`none`,stroke:`currentColor`,strokeWidth:`1.9`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,children:[(0,P.jsx)(`rect`,{x:`9`,y:`2`,width:`6`,height:`12`,rx:`3`}),(0,P.jsx)(`path`,{d:`M19 10a7 7 0 0 1-14 0`}),(0,P.jsx)(`line`,{x1:`12`,y1:`19`,x2:`12`,y2:`22`})]})}function Ro(){return(0,P.jsxs)(`svg`,{viewBox:`0 0 24 24`,width:`14`,height:`14`,fill:`none`,stroke:`currentColor`,strokeWidth:`2.4`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,children:[(0,P.jsx)(`line`,{x1:`12`,y1:`19`,x2:`12`,y2:`5`}),(0,P.jsx)(`polyline`,{points:`5 12 12 5 19 12`})]})}function zo(){return(0,P.jsx)(`svg`,{viewBox:`0 0 24 24`,width:`12`,height:`12`,fill:`none`,stroke:`currentColor`,strokeWidth:`2.4`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,children:(0,P.jsx)(`polyline`,{points:`20 6 9 17 4 12`})})}var Bo=` .\`'",:;Il!i><~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$`,Vo=[`#a78bfa`,`#ec4899`,`#67e8f9`,`#fbbf24`];function Ho(e,t,n={}){let{cols:r,rows:i,fontSize:a=11,fontFamily:o=`JetBrains Mono, ui-monospace, monospace`,charRamp:s=Bo,colorful:c=!1,palette:l,baseOpacity:u=1,reactive:d=!0,rippleStrength:f=1.4,rippleRadius:p=6,spotlightOpacity:m,spotlightRadius:h=8,frameMs:g=50}=n,_=(0,N.useMemo)(()=>l??(c?Vo:null),[l,c]);(0,N.useEffect)(()=>{let n=e.current,c=t.current;if(!n||!c)return;let l=n.getContext(`2d`);if(!l)return;let v=0,y=0,b=0,x=0,S=0,C=0,w=new Float32Array,T=1,E={x:-9999,y:-9999},D=()=>{w=new Float32Array(b*x);for(let e=0;e{let e=c.getBoundingClientRect();e.width===0||e.height===0||(T=Math.min(window.devicePixelRatio||1,2),n.width=Math.max(1,Math.floor(e.width*T)),n.height=Math.max(1,Math.floor(e.height*T)),l.setTransform(T,0,0,T,0,0),l.font=`${a}px ${o}`,l.textBaseline=`top`,S=l.measureText(`M`).width||a*.6,C=a*1.15,b=r??Math.max(1,Math.floor(e.width/S)),x=i??Math.max(1,Math.floor(e.height/C)),r!==void 0&&(S=e.width/b),i!==void 0&&(C=e.height/x),D())},ee=e=>{if(e-y=r.left-24&&E.x<=r.right+24&&E.y>=r.top-24&&E.y<=r.bottom+24;l.clearRect(0,0,r.width,r.height);let c=s.length-1,T=typeof m==`number`&&m!==u,D=h*h*2;for(let e=0;e1&&(te=1)}if(te<=.01)continue;let ne=`#c8c8d4`;if(_&&_.length){let r=(n*.1+e*.07+t*.12)%_.length;ne=_[Math.floor(Math.abs(r))%_.length]}l.globalAlpha=te,l.fillStyle=ne,l.fillText(ee,n*S,e*C)}l.globalAlpha=1,v=requestAnimationFrame(ee)},te=e=>{E.x=e.clientX,E.y=e.clientY},ne=new ResizeObserver(O);return ne.observe(c),O(),d&&window.addEventListener(`mousemove`,te,{passive:!0}),v=requestAnimationFrame(ee),()=>{cancelAnimationFrame(v),ne.disconnect(),d&&window.removeEventListener(`mousemove`,te)}},[e,t,r,i,a,o,s,_,u,d,f,p,m,h,g])}var Uo=(0,N.forwardRef)(({variant:e=`panel`,cols:t,rows:n,fontSize:r,fontFamily:i,charRamp:a,colorful:o,palette:s,baseOpacity:c,reactive:l,rippleStrength:u,rippleRadius:d,spotlightOpacity:f,spotlightRadius:p,frameMs:m,className:h,...g},_)=>{let v=(0,N.useRef)(null),y=(0,N.useRef)(null);return Ho(y,v,{cols:t,rows:n,fontSize:r,fontFamily:i,charRamp:a,colorful:o,palette:s,baseOpacity:c,reactive:l,rippleStrength:u,rippleRadius:d,spotlightOpacity:f,spotlightRadius:p,frameMs:m}),(0,P.jsx)(`div`,{ref:e=>{v.current=e,typeof _==`function`?_(e):_&&(_.current=e)},className:V(`pui-ascii`,e===`panel`&&`pui-ascii--panel`,h),"aria-hidden":`true`,...g,children:(0,P.jsx)(`canvas`,{ref:y})})});Uo.displayName=`AsciiHero`;var Wo=`circle(0px at -9999px -9999px)`,Go=(0,N.forwardRef)(({text_default:e,text_reveal:t,pattern:n=`0 1 0 1 `,pattern_size_default:r=14,pattern_size_reveal:i=22,scopeSize:a=320,fontSize:o,fontFamily:s,className:c,style:l,...u},d)=>{let f=(0,N.useRef)(null),p=(0,N.useRef)(null);(0,N.useEffect)(()=>{let e=f.current,t=p.current;if(!e||!t)return;let n=a/2,r=0,i=0,o=0,s=!1,c=()=>{if(r=0,!s)return;s=!1;let e=`circle(${n}px at ${i}px ${o}px)`;t.style.clipPath=e,t.style.webkitClipPath=e},l=t=>{let n=e.getBoundingClientRect();i=t.clientX-n.left,o=t.clientY-n.top,s=!0,r||(r=requestAnimationFrame(c))},u=()=>{r&&(cancelAnimationFrame(r),r=0,s=!1),t.style.clipPath=Wo,t.style.webkitClipPath=Wo};return e.addEventListener(`pointermove`,l),e.addEventListener(`pointerleave`,u),e.addEventListener(`pointercancel`,u),()=>{e.removeEventListener(`pointermove`,l),e.removeEventListener(`pointerleave`,u),e.removeEventListener(`pointercancel`,u),r&&cancelAnimationFrame(r)}},[a]);let m=e=>{f.current=e,typeof d==`function`?d(e):d&&(d.current=e)},h=(0,N.useMemo)(()=>n.repeat(40),[n]),g=(0,N.useMemo)(()=>Array.from({length:40},(e,t)=>t),[]),_=e=>(0,P.jsx)(`div`,{className:`pui-goldeneye__pattern`,style:{fontSize:`${e}px`},children:g.map(e=>(0,P.jsx)(`div`,{className:`pui-goldeneye__pattern-row`,children:h},e))}),v={...s?{"--pui-goldeneye-font":s}:{},...o==null?{}:{"--pui-goldeneye-headline-size":typeof o==`number`?`${o}px`:o}};return(0,P.jsxs)(`div`,{ref:m,className:V(`pui-goldeneye`,c),style:{...v,...l},...u,children:[(0,P.jsxs)(`div`,{className:`pui-goldeneye__base`,children:[_(r),(0,P.jsx)(`div`,{className:`pui-goldeneye__headline`,children:e})]}),(0,P.jsxs)(`div`,{ref:p,className:`pui-goldeneye__scope`,"aria-hidden":`true`,style:{clipPath:Wo,WebkitClipPath:Wo},children:[_(i),(0,P.jsx)(`div`,{className:`pui-goldeneye__headline`,children:t})]})]})});Go.displayName=`Goldeneye`;var Ko=[{color:`rgba(124,58,237,0.45)`,x:20,y:30,size:60},{color:`rgba(236,72,153,0.35)`,x:80,y:25,size:50},{color:`rgba(6,182,212,0.30)`,x:50,y:80,size:50}],qo=(0,N.forwardRef)(({blobs:e=Ko,blur:t=50,static:n,animated:r,repulsion:i=.18,className:a,style:o,...s},c)=>{let l=(0,N.useRef)([]);(0,N.useEffect)(()=>{if(!r)return;let t=e.map(e=>({x:e.x,y:e.y,homeX:e.x,homeY:e.y,size:e.size??50,vx:(Math.random()-.5)*.06,vy:(Math.random()-.5)*.06})),n=0,a=()=>{for(let e=0;e.001){let e=(l-c)/l*i;n.vx+=o/c*e,n.vy+=s/c*e}}n.vx+=(Math.random()-.5)*.012,n.vy+=(Math.random()-.5)*.012,n.x+=n.vx,n.y+=n.vy,n.x<-10&&(n.x=-10,n.vx=Math.abs(n.vx)*.6),n.x>110&&(n.x=110,n.vx=-Math.abs(n.vx)*.6),n.y<-10&&(n.y=-10,n.vy=Math.abs(n.vy)*.6),n.y>110&&(n.y=110,n.vy=-Math.abs(n.vy)*.6);let r=l.current[e];r&&(r.style.left=`${n.x}%`,r.style.top=`${n.y}%`)}n=requestAnimationFrame(a)};return n=requestAnimationFrame(a),()=>cancelAnimationFrame(n)},[r,e,i]);let u={...o,filter:`blur(${t}px) saturate(140%)`};return(0,P.jsx)(`div`,{ref:c,"aria-hidden":`true`,className:V(`pui-aurora`,!n&&!r&&`pui-aurora--drift`,a),style:u,...s,children:e.map((e,t)=>{let n=e.size??50;return(0,P.jsx)(`div`,{ref:e=>{l.current[t]=e},className:`pui-aurora__blob`,style:{position:`absolute`,left:`${e.x}%`,top:`${e.y}%`,width:`${n}%`,height:`${n}%`,background:`radial-gradient(circle at center, ${e.color} 0%, transparent 70%)`,transform:`translate(-50%, -50%)`,pointerEvents:`none`,borderRadius:`50%`}},t)})})});qo.displayName=`Aurora`;var Jo=(0,N.forwardRef)(({density:e=70,speed:t=.4,linkDistance:n=140,colors:r=[`#a78bfa`,`#f0abfc`,`#67e8f9`],linkColor:i=`#7c3aed`,hoverDistance:a=200,hoverGravity:o=.005,hoverBrighten:s=.8,baseOpacity:c=.45,overscan:l=80,className:u,...d},f)=>{let p=(0,N.useRef)(null),m=(0,N.useRef)(null);return(0,N.useEffect)(()=>{let u=p.current,d=m.current,f=d.getContext(`2d`);if(!f)return;let h=0,g=0,_=1,v={x:-9999,y:-9999},y=0,b=[],x=()=>{let n=-l,i=h+l,a=-l,o=g+l;b=Array.from({length:e},()=>({x:n+Math.random()*(i-n),y:a+Math.random()*(o-a),vx:(Math.random()-.5)*t*2,vy:(Math.random()-.5)*t*2,r:1+Math.random()*1.6,color:r[Math.floor(Math.random()*r.length)]}))},S=()=>{let e=u.getBoundingClientRect();_=Math.min(window.devicePixelRatio||1,2),h=e.width,g=e.height,d.width=h*_,d.height=g*_,d.style.width=`${h}px`,d.style.height=`${g}px`,f.setTransform(_,0,0,_,0,0),x()},C=()=>{f.clearRect(0,0,h,g);let e=-l,t=h+l,r=-l,u=g+l,d=v.x>-9e3;for(let n of b)if(n.x+=n.vx,n.y+=n.vy,(n.xt)&&(n.vx*=-1),(n.yu)&&(n.vy*=-1),a>0&&o>0&&d){let e=v.x-n.x,t=v.y-n.y,r=Math.hypot(e,t);if(r{if(!d||a<=0||s<=0)return 0;let n=Math.hypot(v.x-e,v.y-t);return n>=a?0:(1-n/a)*s};f.lineWidth=1;for(let e=0;e{let t=u.getBoundingClientRect();v.x=e.clientX-t.left,v.y=e.clientY-t.top},T=()=>{v.x=-9999,v.y=-9999},E=new ResizeObserver(S);return E.observe(u),S(),u.addEventListener(`mousemove`,w),u.addEventListener(`mouseleave`,T),y=requestAnimationFrame(C),()=>{cancelAnimationFrame(y),E.disconnect(),u.removeEventListener(`mousemove`,w),u.removeEventListener(`mouseleave`,T)}},[e,t,n,a,o,s,c,l,r,i]),(0,P.jsx)(`div`,{ref:e=>{p.current=e,typeof f==`function`?f(e):f&&(f.current=e)},"aria-hidden":`true`,className:V(`pui-node-graph`,u),...d,children:(0,P.jsx)(`canvas`,{ref:m})})});Jo.displayName=`NodeGraphBackground`;function Yo(e,t){if(e.startsWith(`#`)){let n,r,i;return e.length===4?(n=parseInt(e[1]+e[1],16),r=parseInt(e[2]+e[2],16),i=parseInt(e[3]+e[3],16)):(n=parseInt(e.slice(1,3),16),r=parseInt(e.slice(3,5),16),i=parseInt(e.slice(5,7),16)),`rgba(${n},${r},${i},${t})`}return e}var Xo=(0,N.forwardRef)(({count:e=18,glyphs:t=[`✦`,`✧`,`✶`,`✺`,`✹`,`·`],durationS:n=[8,18],sizeRange:r=[8,20],className:i,...a},o)=>{let s=(0,N.useMemo)(()=>Array.from({length:e},()=>({glyph:t[Math.floor(Math.random()*t.length)],left:Math.random()*100,duration:n[0]+Math.random()*(n[1]-n[0]),delay:Math.random()*n[1],size:r[0]+Math.random()*(r[1]-r[0]),opacity:.4+Math.random()*.5})),[e,t,n,r]);return(0,P.jsx)(`div`,{ref:o,"aria-hidden":`true`,className:V(`pui-sparkle-field`,i),...a,children:s.map((e,t)=>(0,P.jsx)(`span`,{className:`pui-sparkle-field__item`,style:{left:`${e.left}%`,fontSize:`${e.size}px`,"--pui-sparkle-peak":e.opacity.toFixed(2),animationDuration:`${e.duration}s`,animationDelay:`${e.delay}s`},children:e.glyph},t))})});Xo.displayName=`FloatingSparkles`;var Zo=(0,N.forwardRef)(({breathing:e,glowOnHover:t=!0,className:n,children:r,...i},a)=>(0,P.jsx)(`article`,{ref:a,className:V(`pui-glass-card`,e&&`pui-glass-card--breathing`,t&&`pui-glass-card--glow-hover`,n),...i,children:r}));Zo.displayName=`GlassCard`;var Qo=(0,N.forwardRef)(({className:e,...t},n)=>(0,P.jsx)(`div`,{ref:n,className:V(`pui-glass-card__icon`,e),...t}));Qo.displayName=`GlassCard.Icon`;var $o=(0,N.forwardRef)(({className:e,...t},n)=>(0,P.jsx)(`h3`,{ref:n,className:V(`pui-glass-card__title`,e),...t}));$o.displayName=`GlassCard.Title`;var es=(0,N.forwardRef)(({className:e,...t},n)=>(0,P.jsx)(`p`,{ref:n,className:V(`pui-glass-card__body`,e),...t}));es.displayName=`GlassCard.Body`;var ts=(0,N.forwardRef)(({className:e,children:t,...n},r)=>(0,P.jsxs)(`a`,{ref:r,className:V(`pui-glass-card__link`,e),...n,children:[(0,P.jsx)(`span`,{children:t}),(0,P.jsx)(`span`,{className:`pui-arrow`,children:`→`})]}));ts.displayName=`GlassCard.Link`;var ns=Object.assign(Zo,{Icon:Qo,Title:$o,Body:es,Link:ts}),rs=(0,N.forwardRef)(({filename:e,tokens:t,loop:n=!0,charMs:r=[14,42],thinkingLabel:i=`AI is writing…`,className:a,children:o,...s},c)=>(0,P.jsx)(`div`,{ref:c,"data-theme":`dark`,className:V(`pui-ide`,a),...s,children:o??(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(is,{filename:e,thinking:i}),(0,P.jsx)(as,{tokens:t??[],loop:n,charMs:r})]})}));rs.displayName=`MockIDE`;var is=(0,N.forwardRef)(({filename:e,thinking:t,className:n,children:r,...i},a)=>(0,P.jsxs)(`div`,{ref:a,className:V(`pui-ide__chrome`,n),...i,children:[(0,P.jsx)(`span`,{className:`pui-ide__dot pui-ide__dot--red`}),(0,P.jsx)(`span`,{className:`pui-ide__dot pui-ide__dot--yellow`}),(0,P.jsx)(`span`,{className:`pui-ide__dot pui-ide__dot--green`}),e&&(0,P.jsx)(`span`,{className:`pui-ide__tab`,children:e}),r,t!==!1&&(0,P.jsxs)(`span`,{className:`pui-ide__thinking`,children:[(0,P.jsx)(`span`,{className:`pui-spinner`}),(0,P.jsx)(`span`,{children:t})]})]}));is.displayName=`MockIDE.Chrome`;var as=(0,N.forwardRef)(({tokens:e,loop:t=!0,charMs:n=[14,42],className:r,...i},a)=>{let o=(0,N.useRef)(null),s=a??o,[c,l]=(0,N.useState)(0);return(0,N.useEffect)(()=>{let r=s.current;if(!r||!e.length)return;let i=!1,a=null,o=0,c=0,u=``,d=e=>e.replace(/&/g,`&`).replace(//g,`>`),f=()=>{if(i)return;if(o>=e.length){t&&(a=setTimeout(()=>{o=0,c=0,r.innerHTML=u,l(e=>e+1),f()},3e3));return}let s=e[o];if(c+=1,c>s.c.length){o+=1,c=0,f();return}let p=``;for(let t=0;t${d(n.c)}`:d(n.c)}let m=s.c.slice(0,c);p+=s.cls?`${d(m)}`:d(m),p+=u,r.innerHTML=p;let[h,g]=n,_=h+Math.random()*(g-h)+(m.endsWith(` +`)?120:0);a=setTimeout(f,_)};return r.innerHTML=u,f(),()=>{i=!0,a&&clearTimeout(a)}},[e,t,n,s,c]),(0,P.jsx)(`pre`,{ref:s,className:V(`pui-ide__body`,r),...i})});as.displayName=`MockIDE.Body`,Object.assign(rs,{Chrome:is,Body:as});var os=(0,N.forwardRef)(({role:e,agent:t,thinking:n,icon:r,className:i,children:a,...o},s)=>(0,P.jsxs)(`div`,{ref:s,className:V(`pui-bubble`,e===`user`?`pui-bubble--user`:`pui-bubble--ai`,i),...o,children:[e===`ai`&&(t||n!==!1||r!==!1)&&(0,P.jsxs)(`div`,{className:`pui-bubble__meta`,children:[r===!1?null:r??(0,P.jsx)(bo,{}),t&&(0,P.jsx)(`span`,{children:t}),n!==!1&&(0,P.jsxs)(`span`,{className:`pui-bubble__thinking-pill`,children:[(0,P.jsx)(`span`,{className:`pui-spinner pui-spinner--sm`}),(0,P.jsx)(`span`,{children:n??`thinking…`})]})]}),(0,P.jsx)(`div`,{className:`pui-bubble__stream`,children:a})]}));os.displayName=`ChatBubble`;var ss=e=>e.split(/(\s+)/);function cs({text:e,speedMs:t=[18,80],tokenize:n=ss,loop:r=!1,loopDelayMs:i=6e3,onComplete:a}){let[o,s]=(0,N.useState)(``),[c,l]=(0,N.useState)(!1),u=(0,N.useRef)(a);return u.current=a,(0,N.useEffect)(()=>{let a=!1,o=null,c=n(e),d=()=>Array.isArray(t)?t[0]+Math.random()*(t[1]-t[0]):t,f=()=>{let e=0,t=``,n=()=>{var p;if(!a){if(e>=c.length){l(!0),(p=u.current)==null||p.call(u),r&&(o=setTimeout(()=>{a||(l(!1),s(``),f())},i));return}t+=c[e],e+=1,s(t),o=setTimeout(n,d())}};n()};return f(),()=>{a=!0,o&&clearTimeout(o)}},[]),{output:o,isStreaming:!c,isComplete:c}}var ls=(0,N.forwardRef)(({text:e,speedMs:t,tokenize:n,loop:r,loopDelayMs:i,onComplete:a,hideCaret:o,className:s,...c},l)=>{let{output:u,isStreaming:d}=cs({text:e,speedMs:t,tokenize:n,loop:r,loopDelayMs:i,onComplete:a});return(0,P.jsxs)(`span`,{ref:l,className:V(s),...c,children:[u,!o&&d&&(0,P.jsx)(`span`,{className:`pui-bubble__stream-caret`})]})});ls.displayName=`TokenStream`;var us=[`·`,`✢`,`✳`,`✶`,`✻`,`✽`],ds=`Accomplishing.Actioning.Actualizing.Architecting.Baking.Beaming.Befuddling.Billowing.Blanching.Bloviating.Boogieing.Boondoggling.Booping.Bootstrapping.Brewing.Bunning.Burrowing.Calculating.Canoodling.Caramelizing.Cascading.Catapulting.Cerebrating.Channeling.Channelling.Choreographing.Churning.Clauding.Coalescing.Cogitating.Combobulating.Composing.Computing.Concocting.Considering.Contemplating.Cooking.Crafting.Creating.Crunching.Crystallizing.Cultivating.Deciphering.Deliberating.Determining.Dilly-dallying.Discombobulating.Doing.Doodling.Drizzling.Ebbing.Effecting.Elucidating.Embellishing.Enchanting.Envisioning.Evaporating.Fermenting.Fiddle-faddling.Finagling.Flambéing.Flibbertigibbeting.Flowing.Flummoxing.Fluttering.Forging.Forming.Frolicking.Frosting.Gallivanting.Galloping.Garnishing.Generating.Gesticulating.Germinating.Gitifying.Grooving.Gusting.Harmonizing.Hashing.Hatching.Herding.Honking.Hullaballooing.Hyperspacing.Ideating.Imagining.Improvising.Incubating.Inferring.Infusing.Ionizing.Jitterbugging.Julienning.Kneading.Leavening.Levitating.Lollygagging.Manifesting.Marinating.Meandering.Metamorphosing.Misting.Moonwalking.Moseying.Mulling.Mustering.Musing.Nebulizing.Nesting.Newspapering.Noodling.Nucleating.Orbiting.Orchestrating.Osmosing.Perambulating.Percolating.Perusing.Philosophising.Photosynthesizing.Pollinating.Pondering.Pontificating.Pouncing.Precipitating.Prestidigitating.Processing.Proofing.Propagating.Puttering.Puzzling.Quantumizing.Razzle-dazzling.Razzmatazzing.Recombobulating.Reticulating.Roosting.Ruminating.Sautéing.Scampering.Schlepping.Scurrying.Seasoning.Shenaniganing.Shimmying.Simmering.Skedaddling.Sketching.Slithering.Smooshing.Sock-hopping.Spelunking.Spinning.Sprouting.Stewing.Sublimating.Swirling.Swooping.Symbioting.Synthesizing.Tempering.Thinking.Thundering.Tinkering.Tomfoolering.Topsy-turvying.Transfiguring.Transmuting.Twisting.Undulating.Unfurling.Unravelling.Vibing.Waddling.Wandering.Warping.Whatchamacalliting.Whirlpooling.Whirring.Whisking.Wibbling.Working.Wrangling.Zesting.Zigzagging`.split(`.`);function fs(e){return e[Math.floor(Math.random()*e.length)]}var ps=(0,N.forwardRef)(({verbs:e=ds,glyphs:t=us,glyphInterval:n=250,verbInterval:r,ellipsis:i=`…`,info:a,glyphColor:o,className:s,...c},l)=>{let[u,d]=(0,N.useState)(0),[f,p]=(0,N.useState)(()=>e.length?fs(e):``);(0,N.useEffect)(()=>{if(!t.length)return;let e=setInterval(()=>{d(e=>(e+1)%t.length)},n);return()=>clearInterval(e)},[n,t.length]),(0,N.useEffect)(()=>{if(r==null||!e.length)return;let t=setInterval(()=>{p(fs(e))},r);return()=>clearInterval(t)},[e,r]);let m=f;return(0,P.jsxs)(`span`,{ref:l,className:V(`pui-wibble`,s),...c,children:[(0,P.jsx)(`span`,{className:`pui-wibble__glyph`,"aria-hidden":`true`,style:o?{color:o}:void 0,children:t[u]??``}),(0,P.jsxs)(`span`,{className:`pui-wibble__verb`,children:[m,i]}),a!=null&&a!==!1&&(0,P.jsxs)(`span`,{className:`pui-wibble__info`,children:[`(`,a,`)`]})]})});ps.displayName=`WibblingSpinner`;var ms=(0,N.forwardRef)(({label:e=`Ask AI`,open:t,defaultOpen:n,onOpenChange:r,popover:i,className:a,onClick:o,...s},c)=>{let l=t!==void 0,[u,d]=(0,N.useState)(n??!1),f=l?!!t:u,p=()=>{let e=!f;l||d(e),r?.(e)},m=()=>{l||d(!1),r?.(!1)};return(0,P.jsxs)(P.Fragment,{children:[(0,P.jsxs)(`button`,{ref:c,className:V(`pui-fab`,a),onClick:e=>{o?.(e),p()},"aria-expanded":f,...s,children:[(0,P.jsx)(bo,{}),(0,P.jsx)(`span`,{children:e})]}),f&&(0,P.jsx)(`div`,{role:`dialog`,className:`pui-fab-popover`,children:(0,P.jsx)(hs.Provider,{value:m,children:i})})]})});ms.displayName=`ChatFAB`;var hs=(0,N.createContext)(()=>{}),gs=(0,N.forwardRef)(({onClose:e,className:t,children:n,...r},i)=>{let a=(0,N.useContext)(hs);return(0,P.jsxs)(`div`,{ref:i,className:V(`pui-fab-popover__header`,t),...r,children:[(0,P.jsx)(bo,{}),(0,P.jsx)(`span`,{children:n}),(0,P.jsx)(`button`,{type:`button`,"aria-label":`Close`,className:`pui-fab-popover__close`,onClick:e??a,children:`×`})]})});gs.displayName=`ChatFAB.Header`;var _s=(0,N.forwardRef)(({className:e,...t},n)=>(0,P.jsx)(`div`,{ref:n,className:V(`pui-fab-popover__body`,e),...t}));_s.displayName=`ChatFAB.Body`,Object.assign(ms,{Header:gs,Body:_s});var vs=(0,N.forwardRef)(({logos:e,speed:t=40,gap:n=56,fade:r=!0,pauseOnHover:i,className:a,style:o,...s},c)=>{let l={...o??{},"--pui-marquee-speed":`${t}s`,"--pui-marquee-gap":`${n}px`},u=(e,t)=>e.kind===`img`?(0,P.jsx)(`span`,{className:`pui-marquee__item`,children:(0,P.jsx)(`img`,{src:e.src,alt:e.alt??``})},`a${t}`):(0,P.jsx)(`span`,{className:`pui-marquee__item`,children:e.node},e.key??`b${t}`);return(0,P.jsx)(`div`,{ref:c,className:V(`pui-marquee`,r&&`pui-marquee--fade`,i&&`pui-marquee--paused-on-hover`,a),style:l,"aria-label":`Trusted by`,...s,children:(0,P.jsxs)(`div`,{className:`pui-marquee__track`,children:[e.map(u),e.map((t,n)=>u(t,n+e.length))]})})});vs.displayName=`LogoMarquee`;var ys=(0,N.forwardRef)(({heading:e,logos:t,className:n,...r},i)=>(0,P.jsxs)(`div`,{ref:i,className:V(`pui-logo-row`,n),...r,children:[e&&(0,P.jsx)(`p`,{className:`pui-logo-row__heading`,children:e}),(0,P.jsx)(`div`,{className:`pui-logo-row__items`,children:t.map((e,t)=>e.kind===`img`?(0,P.jsx)(`img`,{src:e.src,alt:e.alt??``},t):(0,P.jsx)(`span`,{className:`pui-logo-row__text`,children:e.node},e.key??t))})]}));ys.displayName=`LogoRow`;var bs=(0,N.forwardRef)(({rows:e,intensity:t=240,startDirection:n=`left`,gap:r=12,fade:i=!0,gradient:a=!1,static:o,className:s,style:c,...l},u)=>{let d=(0,N.useRef)(null);(0,N.useEffect)(()=>{let e=d.current;if(!e||o||window.matchMedia?.call(window,`(prefers-reduced-motion: reduce)`).matches)return;let n=0,r=()=>{n=0;let r=e.getBoundingClientRect(),i=window.innerHeight||document.documentElement.clientHeight,a=(i-r.top)/(i+r.height),o=(Math.min(1,Math.max(0,a))-.5)*t;e.style.setProperty(`--pui-slip`,`${o}px`)},i=()=>{n||(n=requestAnimationFrame(r))};return r(),window.addEventListener(`scroll`,i,{passive:!0}),window.addEventListener(`resize`,i,{passive:!0}),()=>{n&&cancelAnimationFrame(n),window.removeEventListener(`scroll`,i),window.removeEventListener(`resize`,i)}},[t,o]);let f=e=>{d.current=e,typeof u==`function`?u(e):u&&(u.current=e)},p=n===`left`?-1:1,m={...c??{},"--pui-slip-gap":`${r}px`};return(0,P.jsx)(`div`,{ref:f,className:V(`pui-slippy`,i&&`pui-slippy--fade`,s),style:m,"aria-label":`Featured terms`,...l,children:e.map((e,t)=>(0,P.jsx)(`div`,{className:`pui-slippy__row`,style:{"--pui-slip-dir":t%2==0?p:-p},children:e.map((e,n)=>{let r=typeof e==`string`?{label:e}:e;return(0,P.jsx)(`span`,{className:V(`pui-slippy__word`,(a||typeof e==`object`&&e.gradient)&&`pui-slippy__word--gradient`),children:r.label},typeof e==`object`&&e.key||`${t}-${n}`)})},t))})});bs.displayName=`SlippyWords`;function xs({target:e,durationMs:t=1800,from:n=0,ease:r=e=>1-(1-e)**3}){let[i,a]=(0,N.useState)(n);return(0,N.useEffect)(()=>{let i=0,o=performance.now(),s=c=>{let l=Math.min(1,(c-o)/t);a(Math.floor(n+(e-n)*r(l))),l<1&&(i=requestAnimationFrame(s))};return i=requestAnimationFrame(s),()=>cancelAnimationFrame(i)},[]),i}var Ss=(0,N.forwardRef)(({target:e,durationMs:t,from:n,ease:r,format:i=e=>e.toLocaleString(),className:a,...o},s)=>{let c=xs({target:e,durationMs:t,from:n,ease:r});return(0,P.jsx)(`span`,{ref:s,className:V(`pui-stat`,a),...o,children:i(c)})});Ss.displayName=`StatCounter`;var Cs=(0,N.forwardRef)(({icon:e,iconNode:t,title:n,subtitle:r,className:i,...a},o)=>(0,P.jsxs)(`a`,{ref:o,className:V(`pui-community`,i),...a,children:[t??(e&&(0,P.jsx)(`img`,{className:`pui-community__icon`,src:e,alt:``})),(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`div`,{className:`pui-community__top`,children:n}),(0,P.jsx)(`div`,{className:`pui-community__bottom`,children:r})]})]}));Cs.displayName=`CommunityBadge`;var ws=(0,N.forwardRef)(({featured:e,className:t,...n},r)=>(0,P.jsx)(`article`,{ref:r,className:V(`pui-price`,e&&`pui-price--featured`,t),...n}));ws.displayName=`PricingCard`;var Ts=(0,N.forwardRef)(({hideSparkle:e,className:t,children:n,...r},i)=>(0,P.jsxs)(`div`,{ref:i,className:V(`pui-price__flag`,t),...r,children:[!e&&(0,P.jsx)(bo,{solid:!0}),(0,P.jsx)(`span`,{children:n})]}));Ts.displayName=`PricingCard.Flag`;var Es=(0,N.forwardRef)(({className:e,...t},n)=>(0,P.jsx)(`div`,{ref:n,className:V(`pui-price__tier`,e),...t}));Es.displayName=`PricingCard.Tier`;var Ds=(0,N.forwardRef)(({unit:e,className:t,children:n,...r},i)=>(0,P.jsxs)(`div`,{ref:i,className:V(`pui-price__amount`,t),...r,children:[n,e&&(0,P.jsx)(`span`,{className:`pui-price__amount-unit`,children:e})]}));Ds.displayName=`PricingCard.Amount`;var Os=(0,N.forwardRef)(({className:e,...t},n)=>(0,P.jsx)(`p`,{ref:n,className:V(`pui-price__blurb`,e),...t}));Os.displayName=`PricingCard.Blurb`;var ks=(0,N.forwardRef)(({className:e,...t},n)=>(0,P.jsx)(`ul`,{ref:n,className:V(`pui-price__features`,e),...t}));ks.displayName=`PricingCard.Features`;var As=(0,N.forwardRef)(({className:e,children:t,...n},r)=>(0,P.jsx)(`a`,{ref:r,className:V(`pui-btn pui-btn--glow pui-btn--block`,e),...n,children:(0,P.jsx)(`span`,{children:t})}));As.displayName=`PricingCard.CTA`,Object.assign(ws,{Flag:Ts,Tier:Es,Amount:Ds,Blurb:Os,Features:ks,CTA:As});var js=(0,N.forwardRef)(({before:e,after:t,brand:n,beforeLabel:r=`Before`,afterLabel:i=`After`,className:a,children:o,...s},c)=>(0,P.jsx)(`div`,{ref:c,className:V(`pui-ba`,a),...s,children:o??(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(Ms,{label:r,children:(0,P.jsx)(`ul`,{children:(e??[]).map((e,t)=>(0,P.jsx)(`li`,{children:e},t))})}),(0,P.jsx)(Ps,{brand:n}),(0,P.jsx)(Ns,{label:i,children:(0,P.jsx)(`ul`,{children:(t??[]).map((e,t)=>(0,P.jsx)(`li`,{children:e},t))})})]})}));js.displayName=`BeforeAfter`;var Ms=(0,N.forwardRef)(({label:e=`Before`,className:t,children:n,...r},i)=>(0,P.jsxs)(`div`,{ref:i,className:V(`pui-ba__panel pui-ba__panel--before`,t),...r,children:[(0,P.jsx)(`div`,{className:`pui-ba__tag`,children:e}),n]}));Ms.displayName=`BeforeAfter.Before`;var Ns=(0,N.forwardRef)(({label:e=`After`,className:t,children:n,...r},i)=>(0,P.jsxs)(`div`,{ref:i,className:V(`pui-ba__panel pui-ba__panel--after`,t),...r,children:[(0,P.jsx)(`div`,{className:`pui-ba__tag`,children:e}),n]}));Ns.displayName=`BeforeAfter.After`;var Ps=(0,N.forwardRef)(({brand:e,className:t,...n},r)=>(0,P.jsxs)(`div`,{ref:r,className:V(`pui-ba__arrow`,t),...n,children:[(0,P.jsx)(bo,{}),e?(0,P.jsxs)(`span`,{children:[`with `,e]}):(0,P.jsx)(`span`,{children:`after`}),(0,P.jsx)(`span`,{children:`→`})]}));Ps.displayName=`BeforeAfter.Arrow`,Object.assign(js,{Before:Ms,After:Ns,Arrow:Ps});var Fs=(0,N.forwardRef)(({placeholder:e=`you@startup.ai`,defaultValue:t=``,ctaLabel:n=`Notify me`,leading:r,footnote:i,onSubmit:a,className:o,...s},c)=>{let[l,u]=(0,N.useState)(t);return(0,P.jsxs)(`div`,{className:V(`pui-waitlist-wrap`,o),children:[(0,P.jsxs)(`form`,{ref:c,className:`pui-waitlist`,onSubmit:e=>{e.preventDefault(),a?.(l)},...s,children:[r===!1?null:(0,P.jsx)(`span`,{className:`pui-waitlist__icon`,"aria-hidden":`true`,children:r??(0,P.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`1.6`,strokeLinecap:`round`,strokeLinejoin:`round`,width:`18`,height:`18`,children:[(0,P.jsx)(`rect`,{x:`3`,y:`5`,width:`18`,height:`14`,rx:`2`}),(0,P.jsx)(`path`,{d:`M3 7l9 6 9-6`})]})}),(0,P.jsx)(`input`,{className:`pui-waitlist__input`,type:`email`,placeholder:e,value:l,onChange:e=>u(e.target.value)}),(0,P.jsx)(Eo,{type:`submit`,variant:`solid`,children:n})]}),i&&(0,P.jsx)(`div`,{className:`pui-waitlist__footnote`,children:i})]})});Fs.displayName=`WaitlistForm`;function Is({open:e,defaultOpen:t=!1,onOpenChange:n,timer:r=0,title:i,children:a,closeLabel:o=`Maybe later`,closeOnEscape:s=!1,closeOnBackdrop:c=!1,container:l,className:u}){let d=e!==void 0,[f,p]=(0,N.useState)(t),m=d?e:f,h=e=>{d||p(e),n?.(e)};if((0,N.useEffect)(()=>{if(r<=0||m)return;let e=setTimeout(()=>h(!0),r);return()=>clearTimeout(e)},[]),(0,N.useEffect)(()=>{if(!m||!s)return;let e=e=>{e.key===`Escape`&&(e.preventDefault(),h(!1))};return document.addEventListener(`keydown`,e),()=>document.removeEventListener(`keydown`,e)},[m,s]),(0,N.useEffect)(()=>{if(!m)return;let e=e=>{e.target instanceof HTMLInputElement||e.target instanceof HTMLTextAreaElement||[`[`,`]`,`j`,`k`,`ArrowLeft`,`ArrowRight`].includes(e.key)&&e.stopPropagation()};return document.addEventListener(`keydown`,e,{capture:!0}),()=>document.removeEventListener(`keydown`,e,{capture:!0})},[m]),(0,N.useEffect)(()=>{if(!m)return;let e=document.body.style.overflow;return document.body.style.overflow=`hidden`,()=>{document.body.style.overflow=e}},[m]),!m)return null;let g=l??(typeof document<`u`?document.body:null);return g?(0,Hn.createPortal)((0,P.jsxs)(`div`,{className:`pui-popover-overlay`,role:`dialog`,"aria-modal":`true`,children:[(0,P.jsx)(`div`,{className:`pui-popover-backdrop`,"aria-hidden":`true`,onClick:c?()=>h(!1):void 0}),(0,P.jsxs)(`div`,{className:V(`pui-popover`,u),children:[i&&(0,P.jsx)(`div`,{className:`pui-popover__title`,children:i}),(0,P.jsx)(`div`,{className:`pui-popover__body`,children:a}),o!==!1&&(0,P.jsx)(`button`,{type:`button`,className:`pui-popover__dismiss`,onClick:()=>h(!1),children:o})]})]}),g):null}Is.displayName=`Popover`;var Ls={primary:`wave`,secondary:`ghost`,danger:`ghost`,icon:`ghost`},Rs={sm:`sm`,md:`md`};function H({tone:e=`secondary`,size:t=`md`,loading:n=!1,block:r=!1,className:i,children:a,disabled:o,...s}){return(0,P.jsx)(Eo,{variant:Ls[e],size:Rs[t],loading:n,block:r,className:[`admin-button`,`admin-button-${e}`,i].filter(Boolean).join(` `),disabled:o||n,...s,children:a})}var zs={ok:`var(--ok)`,warn:`var(--warn)`,err:`var(--err)`,dim:`var(--text-3)`};function Bs({status:e,pulse:t}){return(0,P.jsx)(So,{color:zs[e],static:!t,className:`admin-status-dot`})}function U({children:e,className:t,breathing:n=!1,glowOnHover:r=!1,...i}){return(0,P.jsx)(ns,{breathing:n,glowOnHover:r,className:[`admin-surface`,t].filter(Boolean).join(` `),...i,children:e})}function Vs({label:e=`Loading`,info:t,className:n}){return(0,P.jsx)(ps,{verbs:[e],glyphs:[`.`,`o`,`O`,`o`],glyphInterval:220,ellipsis:`...`,info:t,glyphColor:`var(--accent)`,className:[`admin-loading`,n].filter(Boolean).join(` `)})}function Hs({value:e,precision:t=0,durationMs:n=450,className:r,format:i}){let a=10**t,o=Math.round(e*a),s=(0,N.useRef)(o),c=s.current;return(0,N.useEffect)(()=>{s.current=o},[o]),(0,P.jsx)(Ss,{target:o,from:c,durationMs:n,className:r,format:e=>{let t=e/a;return i?i(t):t.toLocaleString()}},o)}function Us(){let e=ea(e=>e.login),[t,n]=(0,N.useState)(``),[r,i]=(0,N.useState)(!1);async function a(t){t.preventDefault();let r=t.currentTarget.elements.namedItem(`token`).value.trim();if(r){i(!0),n(``);try{if(!(await fetch(`/admin/api/metrics`,{headers:{Authorization:`Bearer ${r}`}})).ok)throw Error(`Invalid token`);e(r)}catch{n(`Invalid token`)}finally{i(!1)}}}return(0,P.jsxs)(`div`,{className:`login-overlay`,children:[(0,P.jsx)(Jo,{density:24,speed:.16,linkDistance:110,hoverDistance:120,hoverGravity:.002,baseOpacity:.18,colors:[`#e8a030`,`#4caf6e`,`#5aa9e6`],linkColor:`#e8a030`,className:`login-node-bg`}),(0,P.jsxs)(U,{className:`login-card`,glowOnHover:!0,breathing:!0,children:[(0,P.jsxs)(`div`,{className:`login-title`,children:[(0,P.jsx)(`span`,{className:`prompt`,children:`>\xA0`}),`proxy admin`]}),(0,P.jsxs)(`form`,{onSubmit:a,children:[(0,P.jsx)(`input`,{type:`password`,name:`token`,placeholder:`Admin token`,autoComplete:`current-password`,autoFocus:!0}),(0,P.jsx)(H,{type:`submit`,tone:`primary`,loading:r,block:!0,children:`Sign in`})]}),(0,P.jsx)(`div`,{className:`login-error`,children:t})]})]})}var Ws=[{label:`Overview`,items:[{to:`/dashboard`,label:`Dashboard`},{to:`/requests`,label:`Request Log`},{to:`/traffic`,label:`Traffic`}]},{label:`Configure`,items:[{to:`/providers`,label:`Providers`},{to:`/routes`,label:`Routes`},{to:`/models`,label:`Models`},{to:`/backends`,label:`Backends`}]},{label:`Access`,items:[{to:`/keys`,label:`API Keys`},{to:`/audit`,label:`Audit Log`}]},{label:`System`,items:[{to:`/settings`,label:`Settings`},{to:`/uptime`,label:`Uptime`}]}];function Gs(){let e=ea(e=>e.logout),t=ta(e=>e.status);return(0,P.jsxs)(`aside`,{className:`sidebar`,children:[(0,P.jsx)(`div`,{className:`sidebar-brand`,children:`anyllm`}),(0,P.jsx)(`div`,{className:`sidebar-scroll`,children:Ws.map(e=>(0,P.jsxs)(`div`,{className:`sidebar-group`,children:[(0,P.jsx)(`div`,{className:`sidebar-group-label`,children:e.label}),e.items.map(e=>(0,P.jsx)(F,{to:e.to,className:({isActive:e})=>`sidebar-item${e?` active`:``}`,children:e.label},e.to))]},e.label))}),(0,P.jsxs)(`div`,{className:`sidebar-footer`,children:[(0,P.jsx)(`span`,{className:`ws-status ${t===`connected`?`connected`:`disconnected`}`,children:t===`connected`?`Live`:`Offline`}),(0,P.jsx)(H,{size:`sm`,onClick:e,children:`Sign out`})]})]})}function Ks(){let e=pa(e=>e.toasts);return e.length===0?null:(0,P.jsx)(`div`,{className:`toast-stack`,role:`region`,"aria-label":`Notifications`,children:e.map(e=>(0,P.jsx)(qs,{toast:e},e.id))})}function qs({toast:e}){let t=pa(e=>e.dismiss);return(0,N.useEffect)(()=>{if(e.ttlMs==null)return;let n=window.setTimeout(()=>t(e.id),e.ttlMs);return()=>window.clearTimeout(n)},[e.id,e.ttlMs,t]),(0,P.jsxs)(`div`,{className:`toast toast-${e.variant}`,role:`status`,children:[(0,P.jsx)(`div`,{className:`toast-message`,children:e.message}),(0,P.jsx)(`button`,{type:`button`,className:`toast-close`,"aria-label":`Dismiss`,onClick:()=>t(e.id),children:`×`})]})}function Js({req:e}){return(0,P.jsxs)(`div`,{className:`feed-detail`,children:[(0,P.jsx)(`span`,{className:`label`,children:`Request ID`}),(0,P.jsx)(`span`,{className:`val`,children:e.request_id}),(0,P.jsx)(`span`,{className:`label`,children:`Backend`}),(0,P.jsx)(`span`,{className:`val`,children:e.backend}),(0,P.jsx)(`span`,{className:`label`,children:`Model (req)`}),(0,P.jsx)(`span`,{className:`val`,children:e.model_requested??`—`}),(0,P.jsx)(`span`,{className:`label`,children:`Model (mapped)`}),(0,P.jsx)(`span`,{className:`val`,children:e.model_mapped??`—`}),(0,P.jsx)(`span`,{className:`label`,children:`Latency`}),(0,P.jsxs)(`span`,{className:`val`,children:[e.latency_ms,` ms`]}),(0,P.jsx)(`span`,{className:`label`,children:`Tokens in/out`}),(0,P.jsxs)(`span`,{className:`val`,children:[e.input_tokens??`—`,` / `,e.output_tokens??`—`]}),(0,P.jsx)(`span`,{className:`label`,children:`Cost`}),(0,P.jsx)(`span`,{className:`val`,children:e.cost_usd==null?`—`:`$${e.cost_usd.toFixed(6)}`}),e.error_message&&(0,P.jsx)(`div`,{className:`error-msg`,children:e.error_message})]})}function Ys(e){return e<300?`status-2xx`:e<500?`status-4xx`:`status-5xx`}function Xs({req:e}){let[t,n]=(0,N.useState)(!1);return(0,P.jsxs)(P.Fragment,{children:[(0,P.jsxs)(`div`,{className:`feed-row`,onClick:()=>n(e=>!e),children:[(0,P.jsx)(`span`,{className:`mono dim`,children:e.timestamp.slice(11,19)}),(0,P.jsx)(`span`,{className:`mono ${Ys(e.status_code)}`,children:e.status_code}),(0,P.jsxs)(`span`,{className:`mono`,children:[e.latency_ms,`ms`]}),(0,P.jsxs)(`span`,{className:`mono`,style:{overflow:`hidden`,textOverflow:`ellipsis`,whiteSpace:`nowrap`},children:[e.model_requested??e.backend,e.is_streaming&&(0,P.jsx)(`span`,{className:`streaming-badge`,children:`stream`})]}),(0,P.jsx)(`span`,{className:`mono dim`,children:e.input_tokens??`—`}),(0,P.jsx)(`span`,{className:`mono dim`,children:e.output_tokens??`—`}),(0,P.jsx)(`span`,{className:`mono dim`,children:e.cost_usd==null?`—`:`$${e.cost_usd.toFixed(5)}`})]}),t&&(0,P.jsx)(Js,{req:e})]})}var Zs=200;function Qs({initial:e}){let[t,n]=(0,N.useState)(e??[]),[r,i]=(0,N.useState)(!1),a=(0,N.useRef)(r);a.current=r;let o=ta(e=>e.lastEvent);return(0,N.useEffect)(()=>{!o||o.type!==`request_completed`||a.current||n(e=>[o.data,...e].slice(0,Zs))},[o]),(0,P.jsxs)(`div`,{children:[(0,P.jsxs)(`div`,{className:`section-header`,children:[(0,P.jsx)(`span`,{className:`section-label`,children:`Live Feed`}),(0,P.jsx)(H,{size:`sm`,tone:r?`primary`:`secondary`,onClick:()=>i(e=>!e),children:r?`Resume`:`Pause`})]}),(0,P.jsxs)(`div`,{className:`feed`,children:[(0,P.jsxs)(`div`,{className:`feed-header`,children:[(0,P.jsx)(`span`,{children:`Time`}),(0,P.jsx)(`span`,{children:`Status`}),(0,P.jsx)(`span`,{children:`Latency`}),(0,P.jsx)(`span`,{children:`Model`}),(0,P.jsx)(`span`,{children:`In`}),(0,P.jsx)(`span`,{children:`Out`}),(0,P.jsx)(`span`,{children:`Cost`})]}),t.length===0?(0,P.jsx)(`div`,{className:`empty`,children:`Waiting for requests…`}):t.map(e=>(0,P.jsx)(Xs,{req:e},e.request_id))]})]})}function $s({text:e}){return(0,P.jsx)(`span`,{className:`info-tip`,title:e,"aria-label":e,role:`img`,children:`?`})}function ec({series:e,gridColor:t=`var(--border-sub)`,height:n=130}){let r=n,i={top:8,right:8,bottom:0,left:0},a=600-i.left-i.right,o=r-i.top-i.bottom,s=e.flatMap(e=>e.data),c=Math.max(...s,1),l=Math.max(...e.map(e=>e.data.length),2);function u(e){return i.left+e/(l-1)*a}function d(e){return i.top+o-e/c*o}let f=Array.from({length:4},(e,t)=>i.top+t/3*o);return(0,P.jsxs)(`svg`,{className:`chart-svg`,viewBox:`0 0 600 ${r}`,preserveAspectRatio:`none`,style:{height:n},children:[f.map((e,n)=>(0,P.jsx)(`line`,{className:`chart-grid-line`,x1:i.left,y1:e,x2:600-i.right,y2:e,stroke:t},n)),e.map((e,t)=>{if(e.data.length<2)return null;let n=e.data.map((e,t)=>`${u(t)},${d(e)}`).join(` `);return(0,P.jsxs)(`g`,{children:[(0,P.jsx)(`polygon`,{className:`chart-area`,points:[`${u(0)},${i.top+o}`,...e.data.map((e,t)=>`${u(t)},${d(e)}`),`${u(e.data.length-1)},${i.top+o}`].join(` `),fill:e.color}),(0,P.jsx)(`polyline`,{className:`chart-line${e.secondary?` secondary`:``}`,points:n,stroke:e.color})]},t)})]})}function tc({loading:e,error:t,empty:n,message:r}){return e?(0,P.jsx)(`div`,{className:`empty`,children:(0,P.jsx)(Vs,{})}):t?(0,P.jsx)(`div`,{className:`empty error`,children:t}):n?(0,P.jsx)(`div`,{className:`empty`,children:r??`No data`}):null}function nc(){let[e,t]=(0,N.useState)(6),[n,r]=(0,N.useState)(``),{data:i}=Ua(),{data:a,isLoading:o,error:s}=La(e,n),c=a?[{label:`Requests`,color:`#e8a030`,data:a.series.map(e=>e.requests)},{label:`Errors`,color:`#e05252`,data:a.series.map(e=>e.errors),secondary:!0}]:[],l=a?[{label:`Input`,color:`#4caf6e`,data:a.series.map(e=>e.input_tokens)},{label:`Output`,color:`#6eb5c0`,data:a.series.map(e=>e.output_tokens),secondary:!0}]:[],u=a?[{label:`Cost`,color:`#c87dd4`,data:a.series.map(e=>e.cost_usd)}]:[];return(0,P.jsxs)(`div`,{children:[(0,P.jsxs)(`div`,{className:`operator-controls`,children:[(0,P.jsx)(`span`,{className:`section-label`,style:{marginBottom:0},children:`Operator View`}),(0,P.jsxs)(`div`,{className:`form-row`,style:{flexWrap:`wrap`,gap:6,marginTop:0},children:[(0,P.jsxs)(`select`,{value:e,onChange:e=>t(Number(e.target.value)),children:[(0,P.jsx)(`option`,{value:1,children:`Last 1 hour`}),(0,P.jsx)(`option`,{value:6,children:`Last 6 hours`}),(0,P.jsx)(`option`,{value:24,children:`Last 24 hours`})]}),(0,P.jsxs)(`select`,{value:n,onChange:e=>r(e.target.value),children:[(0,P.jsx)(`option`,{value:``,children:`All backends`}),i?.map(e=>(0,P.jsx)(`option`,{value:e.name,children:e.name},e.name))]})]})]}),a&&(0,P.jsxs)(`div`,{className:`stats-row`,children:[(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsx)(`div`,{className:`stat-label`,children:`Input Tokens`}),(0,P.jsx)(`div`,{className:`stat-value`,children:(0,P.jsx)(Hs,{value:a.total_input_tokens})})]}),(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsx)(`div`,{className:`stat-label`,children:`Output Tokens`}),(0,P.jsx)(`div`,{className:`stat-value`,children:(0,P.jsx)(Hs,{value:a.total_output_tokens})})]}),(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsxs)(`div`,{className:`stat-label`,children:[`Window Failures`,(0,P.jsx)($s,{text:`Failed (error) requests within the selected time window and backend filter.`})]}),(0,P.jsx)(`div`,{className:`stat-value`,children:(0,P.jsx)(Hs,{value:a.total_errors})})]}),(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsxs)(`div`,{className:`stat-label`,children:[`Window Cost`,(0,P.jsx)($s,{text:`Estimated USD spend within the selected time window and backend filter, from model pricing.`})]}),(0,P.jsx)(`div`,{className:`stat-value`,children:(0,P.jsx)(Hs,{value:a.total_cost_usd,precision:2,format:e=>`$${e.toFixed(2)}`})})]})]}),(0,P.jsx)(tc,{loading:o,error:s?.message}),a&&(0,P.jsxs)(`div`,{className:`operator-grid`,children:[(0,P.jsxs)(U,{className:`chart-card`,children:[(0,P.jsxs)(`div`,{className:`chart-header`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`div`,{className:`chart-title`,children:`Request Volume`}),(0,P.jsx)(`div`,{className:`chart-subtitle`,children:`Rolling request count and errors`})]}),(0,P.jsx)(`div`,{className:`chart-value`,children:a.total_requests})]}),(0,P.jsx)(ec,{series:c})]}),(0,P.jsxs)(U,{className:`chart-card`,children:[(0,P.jsxs)(`div`,{className:`chart-header`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`div`,{className:`chart-title`,children:`Tokens`}),(0,P.jsx)(`div`,{className:`chart-subtitle`,children:`Input and output usage`})]}),(0,P.jsx)(`div`,{className:`chart-value`,children:(a.total_input_tokens+a.total_output_tokens).toLocaleString()})]}),(0,P.jsx)(ec,{series:l})]}),(0,P.jsxs)(U,{className:`chart-card`,children:[(0,P.jsxs)(`div`,{className:`chart-header`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`div`,{className:`chart-title`,children:`Estimated Cost`}),(0,P.jsx)(`div`,{className:`chart-subtitle`,children:`USD by minute bucket`})]}),(0,P.jsxs)(`div`,{className:`chart-value`,children:[`$`,a.total_cost_usd.toFixed(4)]})]}),(0,P.jsx)(ec,{series:u})]})]})]})}function rc(){let{data:e}=Ia();return(0,P.jsxs)(`div`,{children:[(0,P.jsxs)(`div`,{className:`stats-row`,children:[(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsx)(`div`,{className:`stat-label`,children:`Requests/min`}),(0,P.jsx)(`div`,{className:`stat-value`,children:e?(0,P.jsx)(Hs,{value:e.requests_per_minute,precision:1,format:e=>e.toFixed(1)}):`—`})]}),(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsx)(`div`,{className:`stat-label`,children:`Error Rate`}),(0,P.jsx)(`div`,{className:`stat-value`,children:e?(0,P.jsx)(Hs,{value:e.error_rate*100,precision:1,format:e=>`${e.toFixed(1)}%`}):`—`})]}),(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsxs)(`div`,{className:`stat-label`,children:[`P50 Latency`,(0,P.jsx)($s,{text:`Median response latency — half of requests were faster than this.`})]}),(0,P.jsx)(`div`,{className:`stat-value`,children:e?(0,P.jsx)(Hs,{value:e.p50_latency_ms??0,format:e=>`${Math.round(e)}ms`}):`—`})]}),(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsxs)(`div`,{className:`stat-label`,children:[`P95 Latency`,(0,P.jsx)($s,{text:`95th-percentile latency — 95% of requests were faster than this. Captures tail slowness.`})]}),(0,P.jsx)(`div`,{className:`stat-value`,children:e?(0,P.jsx)(Hs,{value:e.p95_latency_ms??0,format:e=>`${Math.round(e)}ms`}):`—`})]}),(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsx)(`div`,{className:`stat-label`,children:`Total Requests`}),(0,P.jsx)(`div`,{className:`stat-value`,children:(0,P.jsx)(Hs,{value:e?.total_requests??0})})]})]}),(0,P.jsxs)(`div`,{className:`stats-row`,style:{marginBottom:16},children:[(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsx)(`div`,{className:`stat-label`,children:`Streams Started`}),(0,P.jsx)(`div`,{className:`stat-value`,children:(0,P.jsx)(Hs,{value:e?.streams_started??0})})]}),(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsx)(`div`,{className:`stat-label`,children:`Completed`}),(0,P.jsx)(`div`,{className:`stat-value ok`,children:(0,P.jsx)(Hs,{value:e?.streams_completed??0})})]}),(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsx)(`div`,{className:`stat-label`,children:`Failed`}),(0,P.jsx)(`div`,{className:`stat-value`,style:{color:`var(--err)`},children:(0,P.jsx)(Hs,{value:e?.streams_failed??0})})]}),(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsx)(`div`,{className:`stat-label`,children:`Client Disconnects`}),(0,P.jsx)(`div`,{className:`stat-value`,style:{color:`var(--warn)`},children:(0,P.jsx)(Hs,{value:e?.streams_client_disconnected??0})})]})]}),(e?.pxpipe_compressed_total??0)>0&&(0,P.jsxs)(`div`,{className:`stats-row`,style:{marginBottom:16},children:[(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsx)(`div`,{className:`stat-label`,children:`Image-Compressed Requests`}),(0,P.jsx)(`div`,{className:`stat-value`,children:(0,P.jsx)(Hs,{value:e?.pxpipe_compressed_total??0})})]}),(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsx)(`div`,{className:`stat-label`,children:`Images Emitted`}),(0,P.jsx)(`div`,{className:`stat-value`,children:(0,P.jsx)(Hs,{value:e?.pxpipe_images_total??0})})]}),(0,P.jsxs)(U,{className:`stat`,children:[(0,P.jsx)(`div`,{className:`stat-label`,children:`Chars Imaged`}),(0,P.jsx)(`div`,{className:`stat-value`,children:(0,P.jsx)(Hs,{value:e?.pxpipe_imaged_chars_total??0})})]})]}),(0,P.jsx)(nc,{}),(0,P.jsx)(`div`,{style:{marginTop:16},children:(0,P.jsx)(Qs,{})})]})}function ic({page:e,hasMore:t,onPrev:n,onNext:r}){return(0,P.jsxs)(`div`,{className:`pagination`,children:[(0,P.jsx)(H,{size:`sm`,onClick:n,disabled:e<=1,children:`Prev`}),(0,P.jsxs)(`span`,{children:[`Page `,e]}),(0,P.jsx)(H,{size:`sm`,onClick:r,disabled:!t,children:`Next`})]})}function ac({query:e,children:t,empty:n,loading:r,errorTitle:i=`Failed to load`,skeletonRows:a=3}){return e.isLoading&&e.data===void 0?(0,P.jsx)(P.Fragment,{children:r??(0,P.jsx)(oc,{count:a})}):e.isError?(0,P.jsxs)(`div`,{className:`async-error`,role:`alert`,children:[(0,P.jsx)(`div`,{className:`async-error-title`,children:i}),(0,P.jsx)(`div`,{className:`async-error-message`,children:e.error instanceof Error?e.error.message:String(e.error)}),(0,P.jsx)(H,{type:`button`,onClick:()=>{e.refetch()},disabled:e.isFetching,loading:e.isFetching,children:`Retry`})]}):e.data===void 0?null:n&&n.when(e.data)?(0,P.jsx)(P.Fragment,{children:n.render()}):(0,P.jsx)(P.Fragment,{children:t(e.data)})}function oc({count:e}){return(0,P.jsx)(`div`,{className:`skeleton-stack`,"aria-hidden":`true`,children:Array.from({length:e},(e,t)=>(0,P.jsx)(`div`,{className:`skeleton skeleton-row`},t))})}function sc(){let[e,t]=Vi(),n=Math.max(1,Number(e.get(`page`)??`1`)||1),r=e.get(`backend`)??``,i=e.get(`status`)??``;function a(n,r,i){let a=new URLSearchParams(e);r?a.set(n,r):a.delete(n),i?.resetPage&&a.delete(`page`),t(a,{replace:!0})}function o(r){let i=new URLSearchParams(e),a=r(n);a<=1?i.delete(`page`):i.set(`page`,String(a)),t(i,{replace:!0})}let s=Ra({page:n,page_size:50,backend:r,status:i}),{data:c}=Ua();return(0,P.jsxs)(`div`,{children:[(0,P.jsxs)(`div`,{className:`section-header`,children:[(0,P.jsx)(`span`,{className:`section-label`,children:`Request Log`}),(0,P.jsxs)(`div`,{className:`form-row`,style:{marginTop:0},children:[(0,P.jsxs)(`select`,{name:`requestlog-backend`,value:r,onChange:e=>a(`backend`,e.target.value,{resetPage:!0}),children:[(0,P.jsx)(`option`,{value:``,children:`All backends`}),c?.map(e=>(0,P.jsx)(`option`,{value:e.name,children:e.name},e.name))]}),(0,P.jsxs)(`select`,{name:`requestlog-status`,value:i,onChange:e=>a(`status`,e.target.value,{resetPage:!0}),children:[(0,P.jsx)(`option`,{value:``,children:`All status`}),(0,P.jsx)(`option`,{value:`ok`,children:`2xx`}),(0,P.jsx)(`option`,{value:`error`,children:`4xx/5xx`})]})]})]}),(0,P.jsx)(ac,{query:s,errorTitle:`Failed to load request log`,empty:{when:e=>e.requests.length===0&&n===1,render:()=>(0,P.jsxs)(`div`,{className:`empty-cta`,children:[(0,P.jsx)(`div`,{className:`empty-cta-title`,children:`No requests logged`}),(0,P.jsx)(`div`,{className:`empty-cta-body`,children:`Send a request through the proxy and it will appear here. Only proxied traffic is logged; admin API calls are in the Audit tab.`})]})},children:e=>(0,P.jsxs)(P.Fragment,{children:[(0,P.jsxs)(`div`,{className:`feed`,children:[(0,P.jsxs)(`div`,{className:`feed-header`,children:[(0,P.jsx)(`span`,{children:`Time`}),(0,P.jsx)(`span`,{children:`Status`}),(0,P.jsx)(`span`,{children:`Latency`}),(0,P.jsx)(`span`,{children:`Model`}),(0,P.jsx)(`span`,{children:`In`}),(0,P.jsx)(`span`,{children:`Out`}),(0,P.jsx)(`span`,{children:`Cost`})]}),e.requests.map(e=>(0,P.jsx)(Xs,{req:e},e.request_id))]}),(0,P.jsx)(ic,{page:n,hasMore:e.has_more,onPrev:()=>o(e=>Math.max(1,e-1)),onNext:()=>o(e=>e+1)})]})})]})}function cc({configured:e}){return e?null:(0,P.jsxs)(`div`,{style:{marginBottom:20,padding:`12px 16px`,border:`1px solid var(--border)`,borderLeft:`3px solid var(--warn)`,borderRadius:`var(--r)`,fontSize:13},children:[(0,P.jsx)(`div`,{style:{fontWeight:600,marginBottom:8},children:`No backend configured — nothing to forward requests to.`}),(0,P.jsxs)(`div`,{style:{marginBottom:10},children:[`Add a backend on the `,(0,P.jsx)(`span`,{className:`mono`,children:`Backends`}),` tab, or configure one via env. The proxy needs a backend endpoint (where to forward) and a listen port (where to accept). LISTEN_PORT defaults to 3000. Create a `,(0,P.jsx)(`span`,{className:`mono`,children:`.anyllm.env`}),` and import it below, or pass it at startup: `,(0,P.jsx)(`span`,{className:`mono`,children:`anyllm-proxy --webui --env-file .anyllm.env`})]}),(0,P.jsxs)(`div`,{style:{display:`grid`,gridTemplateColumns:`1fr 1fr 1fr`,gap:10},children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`div`,{style:{fontWeight:600,marginBottom:4,fontSize:12},children:`OpenAI`}),(0,P.jsx)(`pre`,{style:{margin:0,padding:`6px 10px`,background:`var(--surface-2)`,borderRadius:`var(--r)`,fontSize:11,overflowX:`auto`},children:`OPENAI_API_KEY=sk-... PROXY_API_KEYS=my-key`})]}),(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`div`,{style:{fontWeight:600,marginBottom:4,fontSize:12},children:`Ollama / local LLM`}),(0,P.jsx)(`pre`,{style:{margin:0,padding:`6px 10px`,background:`var(--surface-2)`,borderRadius:`var(--r)`,fontSize:11,overflowX:`auto`},children:`OPENAI_BASE_URL=http://localhost:11434/v1 PROXY_OPEN_RELAY=true`})]}),(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`div`,{style:{fontWeight:600,marginBottom:4,fontSize:12},children:`OpenRouter / custom`}),(0,P.jsx)(`pre`,{style:{margin:0,padding:`6px 10px`,background:`var(--surface-2)`,borderRadius:`var(--r)`,fontSize:11,overflowX:`auto`},children:`OPENAI_BASE_URL=https://openrouter.ai/api/v1 OPENAI_API_KEY=sk-or-... -PROXY_API_KEYS=my-key`})]})]})]}),b&&(0,P.jsxs)(U,{className:`settings-restart-banner`,children:[(0,P.jsx)(`span`,{children:`Restart the proxy for imported env vars to take effect.`}),(0,P.jsx)(H,{size:`sm`,onClick:re,children:`Dismiss`})]}),(0,P.jsxs)(`div`,{style:{marginBottom:24},children:[(0,P.jsx)(`div`,{className:`section-label`,style:{marginBottom:8},children:`Env File`}),(0,P.jsxs)(`div`,{style:{display:`flex`,gap:8,alignItems:`center`},children:[(0,P.jsx)(`input`,{ref:d,type:`file`,accept:`.env,.anyllm.env,text/plain`,style:{display:`none`},onChange:te}),(0,P.jsx)(H,{size:`sm`,onClick:()=>d.current?.click(),disabled:u.isPending,loading:u.isPending,children:`Import .anyllm.env`}),(0,P.jsx)(H,{size:`sm`,onClick:ne,children:`Export .anyllm.env`})]}),m&&(0,P.jsxs)(`div`,{style:{marginTop:10},children:[(0,P.jsxs)(`div`,{className:`dim`,style:{marginBottom:4},children:[m.applied,` variable`,m.applied===1?``:`s`,` imported.`,m.warnings.length===0&&` No issues.`]}),m.warnings.length>0&&(0,P.jsxs)(`div`,{style:{marginTop:8,padding:`8px 12px`,background:`var(--warn-dim)`,borderLeft:`3px solid var(--warn)`,borderRadius:`var(--r)`,fontSize:12},children:[(0,P.jsx)(`div`,{style:{fontWeight:600,marginBottom:4},children:`Warnings`}),m.warnings.map((e,t)=>(0,P.jsxs)(`div`,{className:`mono`,style:{fontSize:12},children:[e.line!=null&&(0,P.jsxs)(`span`,{className:`dim`,children:[`[line `,e.line,`] `]}),e.key&&(0,P.jsxs)(`span`,{children:[e.key,`: `]}),e.message]},t))]})]}),g&&(0,P.jsxs)(`div`,{style:{marginTop:10,padding:`8px 12px`,background:`var(--err-dim)`,borderLeft:`3px solid var(--err)`,borderRadius:`var(--r)`,fontSize:12},children:[(0,P.jsx)(`div`,{style:{fontWeight:600,marginBottom:4},children:`Import rejected`}),g.hard_errors.map((e,t)=>(0,P.jsx)(`div`,{className:`mono`,style:{fontSize:12},children:e},t)),g.warnings.length>0&&(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(`div`,{style:{fontWeight:600,marginTop:8,marginBottom:4},children:`Warnings (from partial parse)`}),g.warnings.map((e,t)=>(0,P.jsxs)(`div`,{className:`mono`,style:{fontSize:12},children:[e.line!=null&&(0,P.jsxs)(`span`,{className:`dim`,children:[`[line `,e.line,`] `]}),e.message]},t))]})]}),v&&(0,P.jsxs)(`div`,{style:{marginTop:10,padding:`8px 12px`,background:`var(--err-dim)`,borderLeft:`3px solid var(--err)`,borderRadius:`var(--r)`,fontSize:12},children:[`Export failed: `,v]})]}),(0,P.jsx)(tc,{loading:n,error:r?.message}),t&&(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`div`,{className:`section-label`,style:{marginBottom:8},children:`Runtime`}),(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsxs)(`label`,{className:`form-label`,htmlFor:`cfg-redact-secrets`,style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,P.jsx)(`input`,{id:`cfg-redact-secrets`,type:`checkbox`,checked:t.redact_secrets,disabled:c.isPending,onChange:e=>E(`redact_secrets`,e.target.checked)}),`Redact secrets`]}),t.overridden_keys.includes(`redact_secrets`)&&(0,P.jsx)(`div`,{className:`form-row`,children:(0,P.jsx)(H,{size:`sm`,onClick:()=>C(`redact_secrets`),children:`Reset`})})]}),(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsxs)(`label`,{className:`form-label`,htmlFor:`cfg-log-bodies`,style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,P.jsx)(`input`,{id:`cfg-log-bodies`,type:`checkbox`,checked:t.log_bodies,disabled:c.isPending,onChange:e=>E(`log_bodies`,e.target.checked)}),`Log bodies`]}),t.overridden_keys.includes(`log_bodies`)&&(0,P.jsx)(`div`,{className:`form-row`,children:(0,P.jsx)(H,{size:`sm`,onClick:()=>C(`log_bodies`),children:`Reset`})})]}),(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsxs)(`label`,{className:`form-label`,htmlFor:`cfg-thinking-repair`,style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,P.jsx)(`input`,{id:`cfg-thinking-repair`,type:`checkbox`,checked:t.anthropic_thinking_repair,disabled:c.isPending,onChange:e=>E(`anthropic_thinking_repair`,e.target.checked)}),`Anthropic thinking-block repair`]}),(0,P.jsx)(`div`,{className:`dim`,style:{fontSize:12},children:`Repairs corrupted thinking/redacted_thinking blocks in Anthropic passthrough requests (applies to any backend running in BACKEND=anthropic passthrough mode, including a named backend in a multi-backend config). Off by default.`}),t.overridden_keys.includes(`anthropic_thinking_repair`)&&(0,P.jsx)(`div`,{className:`form-row`,children:(0,P.jsx)(H,{size:`sm`,onClick:()=>C(`anthropic_thinking_repair`),children:`Reset`})})]}),(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsxs)(`label`,{className:`form-label`,htmlFor:`cfg-pxpipe-compress`,style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,P.jsx)(`input`,{id:`cfg-pxpipe-compress`,type:`checkbox`,checked:t.pxpipe_compress,disabled:c.isPending,onChange:e=>E(`pxpipe_compress`,e.target.checked)}),`Image context compression (pxpipe)`]}),(0,P.jsx)(`div`,{className:`dim`,style:{fontSize:12},children:`Renders the stable system + tool-definition slab of Anthropic passthrough requests to a PNG image block to save input tokens on vision models. Off by default. Enable per-model below — only models that read imaged text reliably are offered.`}),t.overridden_keys.includes(`pxpipe_compress`)&&(0,P.jsx)(`div`,{className:`form-row`,children:(0,P.jsx)(H,{size:`sm`,onClick:()=>C(`pxpipe_compress`),children:`Reset`})}),t.pxpipe_compress&&(0,P.jsxs)(`div`,{style:{marginTop:8},children:[(0,P.jsx)(`div`,{className:`form-label`,style:{fontSize:13},children:`Models in scope (vision-capable)`}),t.pxpipe_available_models.length===0?(0,P.jsx)(`div`,{className:`dim`,style:{fontSize:12},children:`No vision-capable models in the catalog.`}):(0,P.jsx)(`div`,{style:{display:`flex`,flexWrap:`wrap`,gap:`4px 16px`},children:t.pxpipe_available_models.map(e=>(0,P.jsxs)(`label`,{style:{display:`flex`,alignItems:`center`,gap:6,fontSize:12},children:[(0,P.jsx)(`input`,{type:`checkbox`,checked:O(e),disabled:c.isPending,onChange:t=>ee(e,t.target.checked)}),e]},e))}),t.overridden_keys.includes(`pxpipe_models`)&&(0,P.jsx)(`div`,{className:`form-row`,style:{marginTop:6},children:(0,P.jsx)(H,{size:`sm`,onClick:()=>C(`pxpipe_models`),children:`Reset scope`})})]})]}),(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsxs)(`label`,{className:`form-label`,htmlFor:`cfg-rtk-compress`,style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,P.jsx)(`input`,{id:`cfg-rtk-compress`,type:`checkbox`,checked:t.rtk_compress,disabled:c.isPending,onChange:e=>E(`rtk_compress`,e.target.checked)}),`Tool-output compression (RTK)`]}),(0,P.jsx)(`div`,{className:`dim`,style:{fontSize:12},children:`Command-aware filtering of tool-result text (test/build/git/log output) using the RTK filter catalog. Shrinks noisy machine output before it reaches the backend; deterministic and cache-safe. Off by default. Applies to Anthropic passthrough and translate paths.`}),t.overridden_keys.includes(`rtk_compress`)&&(0,P.jsx)(`div`,{className:`form-row`,children:(0,P.jsx)(H,{size:`sm`,onClick:()=>C(`rtk_compress`),children:`Reset`})}),t.rtk_compress&&(0,P.jsxs)(`div`,{style:{marginTop:8},children:[(0,P.jsx)(`div`,{className:`form-label`,style:{fontSize:13},children:`Models in scope (CSV, empty = all)`}),(0,P.jsx)(`input`,{type:`text`,className:`form-input`,defaultValue:t.rtk_models,placeholder:`empty = all models; e.g. claude, gpt-5`,disabled:c.isPending,onBlur:e=>{let n=e.target.value.trim();n!==(t.rtk_models??``)&&c.mutate({rtk_models:n})}},t.rtk_models),t.overridden_keys.includes(`rtk_models`)&&(0,P.jsx)(`div`,{className:`form-row`,style:{marginTop:6},children:(0,P.jsx)(H,{size:`sm`,onClick:()=>C(`rtk_models`),children:`Reset scope`})})]})]}),(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsxs)(`label`,{className:`form-label`,htmlFor:`cfg-forward-client-auth`,style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,P.jsx)(`input`,{id:`cfg-forward-client-auth`,type:`checkbox`,checked:t.forward_client_auth,disabled:c.isPending,onChange:e=>E(`forward_client_auth`,e.target.checked)}),`Forward client credential (Anthropic passthrough)`]}),(0,P.jsx)(`div`,{className:`dim`,style:{fontSize:12},children:`Forwards the client's own x-api-key/Authorization header upstream instead of the operator's configured credential (BACKEND=anthropic passthrough only, single-key/BYOK deployments). The proxy refuses to enable this with 2+ PROXY_API_KEYS entries and no PROXY_OPEN_RELAY. Off by default.`}),t.overridden_keys.includes(`forward_client_auth`)&&(0,P.jsx)(`div`,{className:`form-row`,children:(0,P.jsx)(H,{size:`sm`,onClick:()=>C(`forward_client_auth`),children:`Reset`})})]}),(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsx)(`label`,{className:`form-label`,htmlFor:`cfg-tool-guardrail-mode`,children:`Tool guardrail mode`}),(0,P.jsxs)(`div`,{className:`form-row`,children:[(0,P.jsxs)(`select`,{id:`cfg-tool-guardrail-mode`,value:t.tool_guardrail_mode,disabled:c.isPending,onChange:e=>c.mutate({tool_guardrail_mode:e.target.value}),children:[(0,P.jsx)(`option`,{value:`disabled`,children:`Disabled`}),(0,P.jsx)(`option`,{value:`standard`,children:`Standard`})]}),t.overridden_keys.includes(`tool_guardrail_mode`)&&(0,P.jsx)(H,{size:`sm`,onClick:()=>C(`tool_guardrail_mode`),children:`Reset`})]}),(0,P.jsx)(`div`,{className:`dim`,style:{fontSize:12},children:`Applies advisory guardrails to tool calls the proxy auto-executes. Disabled by default.`})]}),(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsx)(`label`,{className:`form-label`,htmlFor:`cfg-optimizer-mode`,children:`Prompt compression (optimizer)`}),(0,P.jsxs)(`div`,{className:`form-row`,children:[(0,P.jsxs)(`select`,{id:`cfg-optimizer-mode`,value:t.optimizer_mode,disabled:c.isPending||!!i?.compiled_in&&!i?.present,onChange:e=>c.mutate({optimizer_mode:e.target.value}),children:[(0,P.jsx)(`option`,{value:`off`,children:`Off`}),(0,P.jsx)(`option`,{value:`shadow`,children:`Shadow (report only)`}),(0,P.jsx)(`option`,{value:`live`,children:`Live (compress)`})]}),t.overridden_keys.includes(`optimizer_mode`)&&(0,P.jsx)(H,{size:`sm`,onClick:()=>C(`optimizer_mode`),children:`Reset`})]}),(0,P.jsx)(`div`,{className:`dim`,style:{fontSize:12},children:`Frozen-Frontier compression of long conversation history (latest turn untouched). Off by default.`}),i&&!i.compiled_in&&(0,P.jsxs)(`div`,{className:`dim`,style:{fontSize:12,marginTop:8},children:[`Heuristic scorer only. Rebuild the proxy with `,(0,P.jsx)(`code`,{children:`--features optimizer-onnx`}),` to enable the LLMLingua-2 ONNX scorer.`]}),i?.compiled_in&&!i.present&&!i.downloading&&(0,P.jsxs)(`div`,{className:`form-row`,style:{marginTop:8},children:[(0,P.jsxs)(H,{size:`sm`,disabled:a.isPending,onClick:()=>a.mutate(),children:[`Download model (`,pc(i.size_bytes),`)`]}),(0,P.jsx)(`span`,{className:`dim`,style:{fontSize:12},children:`Required before enabling. Verified against a pinned sha256.`})]}),i?.downloading&&(0,P.jsxs)(`div`,{className:`dim`,style:{fontSize:12,marginTop:8},children:[`Downloading and verifying model (`,pc(i.size_bytes),`)…`]}),i?.error&&!i.downloading&&(0,P.jsxs)(`div`,{style:{fontSize:12,marginTop:8,color:`var(--danger, #c0392b)`},children:[`Download failed: `,i.error]}),i?.compiled_in&&i.present&&(0,P.jsx)(`div`,{className:`dim`,style:{fontSize:12,marginTop:8},children:`ONNX scorer ready — live mode uses LLMLingua-2 (loaded on the next request).`})]}),t.entries.filter(e=>![`redact_secrets`,`log_bodies`,`anthropic_thinking_repair`,`pxpipe_compress`,`pxpipe_models`,`rtk_compress`,`rtk_models`,`forward_client_auth`,`tool_guardrail_mode`,`optimizer_mode`].includes(e.key)).map(e=>{let t=`cfg-${e.key}`;return(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsx)(`label`,{className:`form-label`,htmlFor:t,children:e.key}),(0,P.jsxs)(`div`,{className:`form-row`,children:[(0,P.jsx)(`input`,{id:t,name:e.key,value:f[e.key]??e.value,onChange:t=>p(n=>({...n,[e.key]:t.target.value}))}),(0,P.jsx)(H,{tone:`primary`,size:`sm`,onClick:()=>T(e.key,e.value),children:`Save`}),(0,P.jsx)(H,{size:`sm`,onClick:()=>C(e.key),children:`Reset`})]})]},e.key)})]}),o&&(0,P.jsxs)(`div`,{className:`readonly-section`,style:{marginTop:16},children:[(0,P.jsx)(`div`,{className:`section-label`,children:`Environment`}),(0,P.jsx)(`div`,{style:{display:`grid`,gridTemplateColumns:`220px 1fr`,gap:`4px 12px`,marginTop:8,fontSize:12},children:Object.entries(o).map(([e,t])=>(0,P.jsxs)(N.Fragment,{children:[(0,P.jsx)(`span`,{className:`dim`,children:e}),(0,P.jsx)(`span`,{className:`mono`,children:t})]},e))})]}),(0,P.jsx)(uc,{open:S!==null,onClose:()=>C(null),onConfirm:w,title:`Reset override?`,message:(0,P.jsxs)(P.Fragment,{children:[`Reset override for `,(0,P.jsx)(`span`,{className:`mono`,children:S}),`? The runtime value will revert to the env-file or default. Active connections are not affected.`]}),confirmLabel:`Reset`,variant:`primary`})]})}function hc({variant:e}){return(0,P.jsx)(`span`,{className:`badge badge-${e}`,children:e})}function gc({spent:e,limit:t}){if(!t)return(0,P.jsx)(`span`,{className:`dim`,children:`—`});let n=Math.min(e/t*100,100),r=n>=95?`danger`:n>=80?`warn`:``;return(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`div`,{className:`budget-bar`,children:(0,P.jsx)(`div`,{className:`budget-bar-fill${r?` ${r}`:``}`,style:{width:`${n}%`}})}),(0,P.jsxs)(`span`,{className:`dim`,style:{fontSize:10},children:[`$`,e.toFixed(4),` / $`,t.toFixed(2)]})]})}function _c(e){let t=e.trim();return t?Number(t):null}function vc(e){return{description:e.description.trim()||null,max_budget_usd:_c(e.spendLimit),rpm_limit:_c(e.rpmLimit)}}function yc({onCreated:e}){let t=Ba(),[n,r]=(0,N.useState)(``),[i,a]=(0,N.useState)(``),[o,s]=(0,N.useState)(``);function c(){t.mutate(vc({description:n,spendLimit:i,rpmLimit:o}),{onSuccess:t=>{r(``),a(``),s(``),e(t.key)}})}return(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsx)(`div`,{className:`form-label`,children:`Create Key`}),(0,P.jsxs)(`form`,{onSubmit:e=>{e.preventDefault(),c()},children:[(0,P.jsxs)(`div`,{className:`form-row`,style:{flexWrap:`wrap`},children:[(0,P.jsx)(`input`,{name:`description`,placeholder:`Description`,value:n,onChange:e=>r(e.target.value)}),(0,P.jsx)(`input`,{name:`max_budget_usd`,placeholder:`Spend limit USD`,type:`number`,value:i,onChange:e=>a(e.target.value),style:{width:160}}),(0,P.jsx)(`input`,{name:`rpm_limit`,placeholder:`RPM limit`,type:`number`,value:o,onChange:e=>s(e.target.value),style:{width:100}}),(0,P.jsx)(H,{type:`submit`,tone:`primary`,loading:t.isPending,children:`Create`})]}),!i&&!o&&(0,P.jsx)(`div`,{className:`form-hint`,style:{color:`var(--warn)`,marginTop:6},children:`No limits set — this key will be unrestricted (unlimited spend and requests).`})]})]})}function bc({vk:e,onClose:t}){let n=Va(),r=Ha(),[i,a]=(0,N.useState)(e.description??``),[o,s]=(0,N.useState)(e.max_budget_usd?.toString()??``),[c,l]=(0,N.useState)(e.rpm_limit?.toString()??``),[u,d]=(0,N.useState)(new Set(e.allowed_routes??[])),[f,p]=(0,N.useState)(!1),{data:m}=fo();function h(){n.mutate({id:e.id,body:{description:i||null,max_budget_usd:o?Number(o):null,rpm_limit:c?Number(c):null,allowed_routes:u.size>0?[...u]:null,expires_at:e.expires_at,tpm_limit:e.tpm_limit,budget_duration:e.budget_duration,allowed_models:e.allowed_models}},{onSuccess:t})}function g(){return r.mutateAsync(e.id)}return(0,N.useEffect)(()=>{r.isSuccess&&t()},[r.isSuccess]),(0,P.jsxs)(P.Fragment,{children:[(0,P.jsxs)(lc,{open:!0,onClose:t,title:`Edit Key — ${e.key_prefix}…`,dismissable:!n.isPending,footer:(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(H,{tone:`danger`,onClick:()=>p(!0),disabled:n.isPending,style:{marginRight:`auto`},children:`Revoke`}),(0,P.jsx)(H,{onClick:t,disabled:n.isPending,children:`Cancel`}),(0,P.jsx)(H,{tone:`primary`,onClick:h,loading:n.isPending,children:`Save`})]}),children:[(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsx)(`label`,{className:`form-label`,htmlFor:`vk-desc`,children:`Description`}),(0,P.jsx)(`input`,{id:`vk-desc`,name:`description`,value:i,onChange:e=>a(e.target.value),style:{width:`100%`}})]}),(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsx)(`label`,{className:`form-label`,htmlFor:`vk-spend`,children:`Spend limit (USD)`}),(0,P.jsx)(`input`,{id:`vk-spend`,name:`spend_limit`,value:o,onChange:e=>s(e.target.value),type:`number`,min:`0`,step:`0.01`})]}),(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsx)(`label`,{className:`form-label`,htmlFor:`vk-rpm`,children:`RPM limit`}),(0,P.jsx)(`input`,{id:`vk-rpm`,name:`rpm_limit`,value:c,onChange:e=>l(e.target.value),type:`number`,min:`0`})]}),(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsxs)(`div`,{className:`form-label`,children:[`Allowed routes `,u.size===0&&(0,P.jsx)(`span`,{className:`hint`,children:`(all routes)`})]}),(0,P.jsxs)(`div`,{className:`route-scope-list`,children:[m?.routes.map(e=>(0,P.jsxs)(`label`,{className:`route-scope-item`,children:[(0,P.jsx)(`input`,{type:`checkbox`,name:`route-${e.id}`,checked:u.has(e.id),onChange:()=>{d(t=>{let n=new Set(t);return n.has(e.id)?n.delete(e.id):n.add(e.id),n})}}),(0,P.jsx)(`span`,{children:e.name})]},e.id)),!m?.routes.length&&(0,P.jsx)(`span`,{className:`hint`,children:`No routes configured`})]})]})]}),(0,P.jsx)(uc,{open:f,onClose:()=>p(!1),onConfirm:g,title:`Revoke key?`,message:(0,P.jsxs)(P.Fragment,{children:[`Revoking `,(0,P.jsxs)(`span`,{className:`mono`,children:[e.key_prefix,`…`]}),` will immediately reject any request using it. This cannot be undone.`]}),confirmLabel:`Revoke`})]})}async function xc(e){try{return await navigator.clipboard.writeText(e),!0}catch{return!1}}function Sc(){let e=za(),[t,n]=Vi(),r=t.get(`q`)??``,i=t.get(`edit`),[a,o]=(0,N.useState)(null),[s,c]=(0,N.useState)(null);(0,N.useEffect)(()=>{if(!i||!e.data)return;let t=e.data.find(e=>String(e.id)===i);t&&c(t)},[i,e.data]);function l(){if(c(null),t.has(`edit`)){let e=new URLSearchParams(t);e.delete(`edit`),n(e,{replace:!0})}}function u(e){c(e);let r=new URLSearchParams(t);r.set(`edit`,String(e.id)),n(r,{replace:!0})}function d(e){let r=new URLSearchParams(t);e?r.set(`q`,e):r.delete(`q`),n(r,{replace:!0})}let f=r.trim().toLowerCase(),p=(0,N.useMemo)(()=>f?(e.data??[]).filter(e=>{let t=(e.description??``).toLowerCase(),n=e.key_prefix.toLowerCase();return t.includes(f)||n.includes(f)}):e.data??[],[e.data,f]);return(0,P.jsxs)(`div`,{children:[(0,P.jsx)(yc,{onCreated:o}),a&&(0,P.jsxs)(`div`,{className:`key-result`,children:[(0,P.jsx)(`div`,{className:`key-result-label`,children:`New key (copy now — not shown again)`}),(0,P.jsxs)(`div`,{className:`key-result-value`,children:[(0,P.jsx)(`span`,{className:`mono`,children:a}),(0,P.jsxs)(`div`,{className:`key-result-actions`,children:[(0,P.jsx)(H,{size:`sm`,onClick:async()=>{ma(await xc(a)?{variant:`success`,message:`Key copied to clipboard`}:{variant:`error`,message:`Copy failed — select and copy manually`})},children:`Copy`}),(0,P.jsx)(`button`,{type:`button`,className:`key-result-dismiss`,"aria-label":`Dismiss`,onClick:()=>o(null),children:`×`})]})]})]}),(0,P.jsxs)(`div`,{className:`toolbar`,children:[(0,P.jsx)(`input`,{type:`search`,name:`keys-search`,placeholder:`Search by description or prefix…`,value:r,onChange:e=>d(e.target.value),className:`toolbar-search`}),e.data&&(0,P.jsxs)(`span`,{className:`dim toolbar-count`,children:[p.length,` of `,e.data.length]})]}),(0,P.jsx)(ac,{query:e,errorTitle:`Failed to load keys`,empty:{when:e=>e.length===0,render:()=>(0,P.jsxs)(`div`,{className:`empty-cta`,children:[(0,P.jsx)(`div`,{className:`empty-cta-title`,children:`No virtual keys yet`}),(0,P.jsx)(`div`,{className:`empty-cta-body`,children:`Use the form above to create one. Keys are shown once at creation and hashed in storage.`})]})},children:()=>p.length===0?(0,P.jsxs)(`div`,{className:`empty`,children:[`No keys match "`,r,`".`]}):(0,P.jsxs)(`table`,{className:`keys-grid`,children:[(0,P.jsx)(`thead`,{children:(0,P.jsxs)(`tr`,{children:[(0,P.jsx)(`th`,{children:`Prefix`}),(0,P.jsx)(`th`,{children:`Description`}),(0,P.jsx)(`th`,{children:`Status`}),(0,P.jsx)(`th`,{children:`Spend`}),(0,P.jsx)(`th`,{children:`Requests`}),(0,P.jsx)(`th`,{children:`Created`})]})}),(0,P.jsx)(`tbody`,{children:p.map(e=>(0,P.jsxs)(`tr`,{style:{cursor:`pointer`},onClick:()=>u(e),children:[(0,P.jsxs)(`td`,{className:`mono`,children:[e.key_prefix,`…`]}),(0,P.jsx)(`td`,{className:`dim`,children:e.description??`—`}),(0,P.jsx)(`td`,{children:(0,P.jsx)(hc,{variant:e.status})}),(0,P.jsx)(`td`,{children:(0,P.jsx)(gc,{spent:e.period_spend_usd,limit:e.max_budget_usd})}),(0,P.jsx)(`td`,{className:`mono`,children:e.total_requests.toLocaleString()}),(0,P.jsx)(`td`,{className:`mono dim`,children:e.created_at.slice(0,10)})]},e.id))})]})}),s&&(0,P.jsx)(bc,{vk:s,onClose:l},s.id)]})}var Cc={openrouter:{text:`Public, no key needed`,needsKey:!1},deepinfra:{text:`Public, no key needed`,needsKey:!1},ollama:{text:`No key needed (local)`,needsKey:!1},configured:{text:`API key required`,needsKey:!0},custom:{text:`API key may be required`,needsKey:!0}};function wc(){return(0,P.jsx)(`svg`,{width:`12`,height:`12`,viewBox:`0 0 16 16`,fill:`none`,className:`key-icon-inline`,children:(0,P.jsx)(`path`,{d:`M10.5 1a4.5 4.5 0 0 0-4.1 6.35L2 11.75V15h3.25v-2H7v-1.75h1.75L9.65 10.4A4.5 4.5 0 1 0 10.5 1zm1 3a1 1 0 1 1 0-2 1 1 0 0 1 0 2z`,fill:`currentColor`})})}function Tc(){let e=Xa(),t=Za(),n=Qa(),r=$a(),{data:i}=Ua(),{data:a}=so(),[o,s]=(0,N.useState)(``),[c,l]=(0,N.useState)(``),[u,d]=(0,N.useState)(``),[f,p]=(0,N.useState)(``),[m,h]=(0,N.useState)(`openrouter`),[g,_]=(0,N.useState)(``),[v,y]=(0,N.useState)(``),[b,x]=(0,N.useState)(null),S=Cc[m]??Cc.custom;function C(){r.mutate({source:m,...m===`custom`?{url:g}:{}})}function w(){t.mutate({model_name:o,actual_model:u,backend_name:f},{onSuccess:()=>{s(``),l(``),d(``),p(``)}})}function T(){return b?n.mutateAsync(b).then(()=>void 0):Promise.resolve()}let E=v.trim().toLowerCase(),D=(0,N.useMemo)(()=>{let t=e.data?.models??[];return E?t.filter(e=>e.model_name.toLowerCase().includes(E)):t},[e.data,E]);return(0,P.jsxs)(`div`,{children:[(0,P.jsxs)(`div`,{className:`models-discover`,children:[(0,P.jsx)(`div`,{className:`section-label`,children:`Discover Models`}),(0,P.jsxs)(`div`,{className:`models-discover-row`,children:[(0,P.jsxs)(`select`,{value:m,onChange:e=>{h(e.target.value),r.reset()},children:[(0,P.jsx)(`option`,{value:`openrouter`,children:`OpenRouter`}),(0,P.jsx)(`option`,{value:`deepinfra`,children:`DeepInfra`}),(0,P.jsx)(`option`,{value:`ollama`,children:`Ollama (local)`}),(0,P.jsx)(`option`,{value:`configured`,children:`Configured backend`}),(0,P.jsx)(`option`,{value:`custom`,children:`Custom URL`})]}),m===`custom`&&(0,P.jsx)(`input`,{name:`discover-url`,placeholder:`https://api.example.com`,value:g,onChange:e=>_(e.target.value),style:{minWidth:220}}),(0,P.jsx)(H,{onClick:C,disabled:r.isPending||m===`custom`&&!g,loading:r.isPending,children:`Fetch`}),(0,P.jsxs)(`span`,{className:`dim models-discover-hint`,children:[S.needsKey&&(0,P.jsx)(wc,{}),S.text]})]}),r.isError&&(0,P.jsx)(`div`,{className:`inline-error`,children:r.error.message}),r.data&&r.data.models.length>0&&(0,P.jsxs)(`div`,{className:`models-discover-results`,children:[(0,P.jsxs)(`div`,{className:`dim models-discover-count`,children:[r.data.models.length,` model`,r.data.models.length===1?``:`s`,` found. Click to populate the form below.`]}),(0,P.jsx)(`div`,{className:`models-discover-list`,children:r.data.models.map(e=>(0,P.jsxs)(`div`,{onClick:()=>{if(d(e.id),!o||o===c){let t=e.name&&e.name!==e.id?e.name:e.id;s(t),l(t)}},className:`models-discover-item${u===e.id?` is-selected`:``}`,children:[(0,P.jsx)(`span`,{className:`mono`,children:e.id}),e.name&&e.name!==e.id&&(0,P.jsx)(`span`,{className:`dim models-discover-item-name`,children:e.name})]},e.id))})]}),r.data&&r.data.models.length===0&&(0,P.jsx)(`div`,{className:`dim models-discover-count`,children:`No models returned.`})]}),(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsx)(`div`,{className:`form-label`,children:`Add Model`}),(0,P.jsxs)(`div`,{className:`form-row`,style:{flexWrap:`wrap`},children:[(0,P.jsx)(`input`,{name:`model-name`,placeholder:`Virtual name`,value:o,onChange:e=>s(e.target.value)}),(0,P.jsx)(`input`,{name:`model-id`,placeholder:`Model ID`,value:u,onChange:e=>d(e.target.value)}),(0,P.jsxs)(`select`,{name:`backend`,value:f,onChange:e=>p(e.target.value),children:[(0,P.jsx)(`option`,{value:``,children:`Backend…`}),i?.map(e=>(0,P.jsx)(`option`,{value:e.name,children:e.name},e.name)),a?.backends.map(e=>(0,P.jsxs)(`option`,{value:e.name,children:[e.name,` (managed)`]},`managed-${e.name}`))]}),(0,P.jsx)(H,{tone:`primary`,onClick:w,disabled:!o||!u||!f||t.isPending,loading:t.isPending,children:`Add`})]}),t.isError&&(0,P.jsx)(`div`,{className:`inline-error`,children:t.error.message})]}),(0,P.jsxs)(`div`,{className:`toolbar`,children:[(0,P.jsx)(`input`,{type:`search`,name:`models-search`,placeholder:`Search models…`,value:v,onChange:e=>y(e.target.value),className:`toolbar-search`}),e.data&&(0,P.jsxs)(`span`,{className:`dim toolbar-count`,children:[D.length,` of `,e.data.models.length]})]}),(0,P.jsx)(ac,{query:e,errorTitle:`Failed to load models`,empty:{when:e=>(e.models?.length??0)===0,render:()=>(0,P.jsxs)(U,{className:`empty-cta`,children:[(0,P.jsx)(`div`,{className:`empty-cta-title`,children:`No models configured`}),(0,P.jsx)(`div`,{className:`empty-cta-body`,children:`Add a model above, or use Discover to pull a catalog from OpenRouter, DeepInfra, Ollama, or a custom endpoint.`})]})},children:e=>D.length===0?(0,P.jsxs)(`div`,{className:`empty`,children:[`No models match "`,v,`".`]}):(0,P.jsxs)(`table`,{className:`route-table`,children:[(0,P.jsx)(`thead`,{children:(0,P.jsxs)(`tr`,{children:[(0,P.jsx)(`th`,{children:`Virtual Name`}),(0,P.jsx)(`th`,{children:`Deployments`}),(0,P.jsx)(`th`,{children:`Strategy`}),(0,P.jsx)(`th`,{})]})}),(0,P.jsx)(`tbody`,{children:D.map(t=>(0,P.jsxs)(`tr`,{children:[(0,P.jsx)(`td`,{className:`mono`,children:t.model_name}),(0,P.jsx)(`td`,{className:`mono`,children:t.deployments}),(0,P.jsx)(`td`,{className:`dim`,children:e.strategy??`—`}),(0,P.jsx)(`td`,{children:(0,P.jsx)(H,{tone:`danger`,size:`sm`,onClick:()=>x(t.model_name),children:`Remove`})})]},t.model_name))})]})}),(0,P.jsx)(uc,{open:b!==null,onClose:()=>x(null),onConfirm:T,title:`Remove model?`,message:(0,P.jsxs)(P.Fragment,{children:[`Remove model `,(0,P.jsx)(`span`,{className:`mono`,children:b}),`? Requests using this virtual name will fail until another model with the same name is added.`]}),confirmLabel:`Remove`})]})}function Ec(){let[e,t]=Vi(),n=Math.max(1,Number(e.get(`page`)??`1`)||1);function r(r){let i=new URLSearchParams(e),a=r(n);a<=1?i.delete(`page`):i.set(`page`,String(a)),t(i,{replace:!0})}return(0,P.jsx)(`div`,{children:(0,P.jsx)(ac,{query:eo({page:n,page_size:50}),errorTitle:`Failed to load audit log`,empty:{when:e=>e.entries.length===0&&n===1,render:()=>(0,P.jsxs)(`div`,{className:`empty-cta`,children:[(0,P.jsx)(`div`,{className:`empty-cta-title`,children:`No audit entries yet`}),(0,P.jsx)(`div`,{className:`empty-cta-body`,children:`Admin actions (creating keys, editing routes, managing backends) are recorded here.`})]})},children:e=>(0,P.jsxs)(P.Fragment,{children:[(0,P.jsxs)(`table`,{className:`route-table`,children:[(0,P.jsx)(`thead`,{children:(0,P.jsxs)(`tr`,{children:[(0,P.jsx)(`th`,{children:`Time`}),(0,P.jsx)(`th`,{children:`Action`}),(0,P.jsx)(`th`,{children:`Target`}),(0,P.jsx)(`th`,{children:`Detail`}),(0,P.jsx)(`th`,{children:`IP`})]})}),(0,P.jsx)(`tbody`,{children:e.entries.map(e=>(0,P.jsxs)(`tr`,{children:[(0,P.jsx)(`td`,{className:`mono dim`,children:e.timestamp.slice(0,19)}),(0,P.jsx)(`td`,{className:`mono`,children:e.action}),(0,P.jsxs)(`td`,{className:`dim`,children:[e.target_type,e.target_id?` #${e.target_id}`:``]}),(0,P.jsx)(`td`,{className:`dim audit-detail`,children:e.detail??`—`}),(0,P.jsx)(`td`,{className:`mono dim`,children:e.source_ip??`—`})]},e.id))})]}),(0,P.jsx)(ic,{page:n,hasMore:e.has_more,onPrev:()=>r(e=>Math.max(1,e-1)),onNext:()=>r(e=>e+1)})]})})})}function Dc({routes:e}){let t=[...e].sort((e,t)=>t.requests_per_min-e.requests_per_min);return(0,P.jsxs)(`table`,{className:`route-table`,children:[(0,P.jsx)(`thead`,{children:(0,P.jsxs)(`tr`,{children:[(0,P.jsx)(`th`,{children:`Route`}),(0,P.jsx)(`th`,{children:`Req/min`}),(0,P.jsx)(`th`,{children:`Error rate`}),(0,P.jsx)(`th`,{children:`Avg latency`}),(0,P.jsx)(`th`,{children:`P95 latency`}),(0,P.jsx)(`th`,{children:`Total`})]})}),(0,P.jsx)(`tbody`,{children:t.map(e=>(0,P.jsxs)(`tr`,{children:[(0,P.jsx)(`td`,{className:`mono`,children:e.path}),(0,P.jsx)(`td`,{className:`mono`,children:e.requests_per_min.toFixed(2)}),(0,P.jsxs)(`td`,{className:`mono`,style:{color:e.error_rate>.05?`var(--err)`:e.error_rate>.01?`var(--warn)`:void 0},children:[(e.error_rate*100).toFixed(1),`%`]}),(0,P.jsxs)(`td`,{className:`mono`,children:[e.avg_latency_ms.toFixed(0),`ms`]}),(0,P.jsxs)(`td`,{className:`mono`,children:[e.p95_latency_ms,`ms`]}),(0,P.jsx)(`td`,{className:`mono`,children:e.total_requests.toLocaleString()})]},e.path))})]})}var Oc=[`#e8a030`,`#d4922b`,`#c07820`,`#a86015`,`#8c500a`],kc=[`#6eb5c0`,`#5aa0ab`,`#468b96`,`#327681`,`#1e616c`];function Ac(){let[e,t]=(0,N.useState)(6),{data:n,isLoading:r,error:i}=to(e),a=n?.routes??[],o=a.slice(0,5).map((e,t)=>{let r=(n?.series??[]).filter(t=>t.path===e.path).map(e=>e.requests);return{label:e.path,color:Oc[t%Oc.length],data:r}});return(0,P.jsxs)(`div`,{children:[(0,P.jsxs)(`div`,{className:`section-header`,children:[(0,P.jsx)(`span`,{className:`section-label`,children:`Traffic`}),(0,P.jsxs)(`select`,{value:e,onChange:e=>t(Number(e.target.value)),children:[(0,P.jsx)(`option`,{value:1,children:`Last 1 hour`}),(0,P.jsx)(`option`,{value:6,children:`Last 6 hours`}),(0,P.jsx)(`option`,{value:24,children:`Last 24 hours`})]})]}),(0,P.jsx)(tc,{loading:r,error:i?.message}),n&&(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(Dc,{routes:n.routes}),(0,P.jsxs)(`div`,{className:`operator-grid`,style:{marginTop:16},children:[(0,P.jsxs)(U,{className:`chart-card`,children:[(0,P.jsx)(`div`,{className:`chart-header`,children:(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`div`,{className:`chart-title`,children:`Requests / min by route`}),(0,P.jsx)(`div`,{className:`chart-subtitle`,children:`Stacked over time window`})]})}),a.length===0?(0,P.jsx)(`div`,{className:`empty`,children:`No routes`}):(0,P.jsx)(ec,{series:o})]}),(0,P.jsxs)(U,{className:`chart-card`,children:[(0,P.jsx)(`div`,{className:`chart-header`,children:(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`div`,{className:`chart-title`,children:`Avg latency per route`}),(0,P.jsx)(`div`,{className:`chart-subtitle`,children:`ms`})]})}),a.length===0?(0,P.jsx)(`div`,{className:`empty`,children:`No routes`}):(0,P.jsx)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:8,paddingTop:8},children:a.slice(0,5).map((e,t)=>{let n=Math.max(...a.slice(0,5).map(e=>e.avg_latency_ms),1),r=e.avg_latency_ms/n*100;return(0,P.jsxs)(`div`,{children:[(0,P.jsxs)(`div`,{style:{display:`flex`,justifyContent:`space-between`,fontSize:11,marginBottom:2},children:[(0,P.jsx)(`span`,{className:`mono dim`,style:{overflow:`hidden`,textOverflow:`ellipsis`,whiteSpace:`nowrap`,maxWidth:`70%`},children:e.path}),(0,P.jsxs)(`span`,{className:`mono`,children:[e.avg_latency_ms.toFixed(0),`ms`]})]}),(0,P.jsx)(`div`,{style:{height:6,background:`var(--border)`,borderRadius:0},children:(0,P.jsx)(`div`,{style:{height:`100%`,width:`${r}%`,background:kc[t%kc.length],borderRadius:0}})})]},e.path)})})]})]})]})]})}function jc(e){let t=Math.floor(Date.now()/1e3-e),n=Math.floor(t/86400),r=Math.floor(t%86400/3600),i=Math.floor(t%3600/60);return n>0?`${n}d ${r}h ${i}m`:r>0?`${r}h ${i}m`:`${i}m`}function Mc({proxy:e}){return(0,P.jsxs)(`div`,{className:`uptime-proxy`,children:[(0,P.jsxs)(`div`,{className:`uptime-proxy-stats`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`div`,{className:`section-label`,children:`Uptime (30d)`}),(0,P.jsxs)(`div`,{className:`uptime-pct`,children:[e.uptime_pct_30d.toFixed(2),`%`]})]}),(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`div`,{className:`section-label`,children:`Running`}),(0,P.jsx)(`div`,{className:`stat-value`,style:{fontSize:16},children:jc(e.started_at)})]})]}),(0,P.jsx)(`div`,{className:`section-label`,style:{marginBottom:4},children:`30-day history`}),(0,P.jsx)(`div`,{className:`history-bar`,children:e.history.map(e=>(0,P.jsx)(`div`,{className:`history-day ${e.status}`,title:`${e.date}: ${e.status}`},e.date))})]})}var Nc=Bs;function Pc({b:e}){let t=e.status===`up`?`ok`:e.status===`down`?`err`:`dim`,n=e.last_checked_at?new Date(e.last_checked_at*1e3).toLocaleTimeString():`—`;return(0,P.jsxs)(`tr`,{children:[(0,P.jsx)(`td`,{className:`mono`,children:e.name}),(0,P.jsxs)(`td`,{children:[(0,P.jsx)(Nc,{status:t,pulse:e.status===`up`}),e.status]}),(0,P.jsxs)(`td`,{className:`mono`,children:[e.uptime_pct_30d.toFixed(2),`%`]}),(0,P.jsx)(`td`,{className:`mono dim`,children:n}),(0,P.jsx)(`td`,{className:`mono dim`,children:e.last_latency_ms==null?`—`:`${e.last_latency_ms}ms`}),(0,P.jsx)(`td`,{children:(0,P.jsx)(`div`,{className:`history-bar`,style:{height:12},children:e.history.map(e=>(0,P.jsx)(`div`,{className:`history-day ${e.status}`,title:`${e.date}: ${e.status}`},e.date))})})]})}function Fc(){let{data:e,isLoading:t,error:n}=no();return(0,P.jsxs)(`div`,{children:[(0,P.jsx)(tc,{loading:t,error:n?.message}),e&&(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(Mc,{proxy:e.proxy}),(0,P.jsx)(`div`,{className:`section-label`,style:{marginTop:16,marginBottom:8},children:`Backend Availability`}),(0,P.jsxs)(`table`,{className:`backend-health-table`,children:[(0,P.jsx)(`thead`,{children:(0,P.jsxs)(`tr`,{children:[(0,P.jsx)(`th`,{children:`Backend`}),(0,P.jsx)(`th`,{children:`Status`}),(0,P.jsx)(`th`,{children:`Uptime (30d)`}),(0,P.jsx)(`th`,{children:`Last checked`}),(0,P.jsx)(`th`,{children:`Latency`}),(0,P.jsx)(`th`,{children:`History`})]})}),(0,P.jsx)(`tbody`,{children:e.backends.slice().sort((e,t)=>e.name.localeCompare(t.name)).map(e=>(0,P.jsx)(Pc,{b:e},e.name))})]})]})]})}function Ic(e){let t=e.trim().replace(/\/+$/,``);return t?t.endsWith(`/models`)?t:t.endsWith(`/v1`)?`${t}/models`:`${t}/v1/models`:``}function Lc(e){let t=[],n=e.env_vars.length>0?e.env_vars[0]:null,{protocol:r,auth:i,default_base_url:a}=e;return r===`openai_compat`&&i===`bearer`?(t.push({name:`api_key`,label:`API Key`,type:`password`,required:!0,group:`auth`,...n?{hint:`or set ${n} env var`}:{}}),t.push({name:`api_base`,label:`API Base URL`,type:`url`,required:!a,group:`endpoint`,...a?{placeholder:a}:{}})):r===`openai_compat`&&i===`none`?(t.push({name:`api_base`,label:`API Base URL`,type:`url`,required:!a,group:`endpoint`,...a?{placeholder:a}:{}}),t.push({name:`api_key`,label:`API Key (optional)`,type:`password`,required:!1,group:`auth`,hint:`Only if your local server enforces a key`})):r===`azure_openai`&&i===`azure_api_key`?(t.push({name:`api_key`,label:`Azure API Key`,type:`password`,required:!0,group:`auth`}),t.push({name:`api_base`,label:`Endpoint URL`,type:`url`,required:!0,placeholder:`https://.openai.azure.com`,group:`endpoint`}),t.push({name:`deployment`,label:`Deployment Name`,type:`text`,required:!0,group:`endpoint`}),t.push({name:`api_version`,label:`API Version`,type:`text`,required:!0,placeholder:`2024-08-01-preview`,group:`endpoint`})):r===`vertex_ai`&&i===`google_api_key`?(t.push({name:`api_key`,label:`API Key`,type:`password`,required:!0,group:`auth`}),t.push({name:`project`,label:`GCP Project ID`,type:`text`,required:!0,group:`endpoint`}),t.push({name:`region`,label:`GCP Region`,type:`text`,required:!0,placeholder:`us-central1`,group:`endpoint`})):r===`bedrock_native`&&i===`aws_sigv4`?(t.push({name:`aws_access_key_id`,label:`AWS Access Key ID`,type:`text`,required:!0,group:`auth`}),t.push({name:`aws_secret_access_key`,label:`AWS Secret Access Key`,type:`password`,required:!0,group:`auth`}),t.push({name:`aws_session_token`,label:`AWS Session Token`,type:`password`,required:!1,group:`auth`}),t.push({name:`region`,label:`AWS Region`,type:`text`,required:!0,placeholder:`us-east-1`,group:`endpoint`})):(r===`gemini_openai`||r===`gemini_native`)&&i===`google_api_key`||r===`anthropic_native`&&i===`bearer`?t.push({name:`api_key`,label:`API Key`,type:`password`,required:!0,group:`auth`,...n?{hint:`or set ${n} env var`}:{}}):(i.includes(`bearer`)&&t.push({name:`api_key`,label:`API Key`,type:`password`,required:!0,group:`auth`}),(i===`none`||i.includes(`bearer`))&&t.push({name:`api_base`,label:`API Base URL`,type:`url`,required:!a,group:`endpoint`,...a?{placeholder:a}:{}}),i===`none`&&t.push({name:`api_key`,label:`API Key (optional)`,type:`password`,required:!1,group:`auth`,hint:`Only if your server enforces a key`})),t.push({name:`rpm`,label:`Rate Limit (req/min)`,type:`number`,required:!1,group:`limits`,hint:`Stored for reference; not enforced on managed backends`}),t.push({name:`tpm`,label:`Token Limit (tokens/min)`,type:`number`,required:!1,group:`limits`,hint:`Stored for reference; not enforced on managed backends`}),t}var Rc={openai:0,anthropic:0,gemini:0,vertex:0,azure:1,bedrock:1,mistral:1,groq:1,deepseek:1,xai:1,together_ai:2,openrouter:2,fireworks_ai:2,perplexity:2,cohere_chat:2,cerebras:2,sambanova:2,ollama:2,deepinfra:2,replicate:2,nvidia_nim:2},zc={0:`Top providers`,1:`Popular`,2:`Notable`,3:`More providers`},Bc=new Set([`gemini`,`groq`,`openrouter`,`mistral`,`deepseek`,`cohere_chat`,`cohere`]);function Vc(e){let t=e.default_base_url??``;return/localhost|127\.0\.0\.1|0\.0\.0\.0/.test(t)}function Hc(e,t){let n=[],r=[],i=[],a=new Map;for(let o of e)if(t.has(o.id))n.push(o);else if(Vc(o))r.push(o);else if(Bc.has(o.id))i.push(o);else{let e=Rc[o.id]??3;a.has(e)||a.set(e,[]),a.get(e).push(o)}let o=(e,t)=>e.display_name.localeCompare(t.display_name);n.sort(o),r.sort(o),i.sort(o);let s=[];n.length&&s.push({key:`favorites`,label:`Favorites`,top:!0,providers:n}),r.length&&s.push({key:`local`,label:`Local LLMs`,top:!1,providers:r}),i.length&&s.push({key:`free`,label:`Free`,top:!1,providers:i});for(let[e,t]of[...a.entries()].sort(([e],[t])=>e-t))t.sort(o),s.push({key:`tier-${e}`,label:zc[e]??`Other`,top:!1,providers:t});return s}var Uc=function(e){return typeof window<`u`?matchMedia&&matchMedia(`(prefers-color-scheme: ${e})`):{matches:!1}},Wc,Gc=(0,N.createContext)({appearance:`light`,setAppearance:function(){},isDarkMode:!1,themeMode:`light`,setThemeMode:function(){},browserPrefers:(Wc=Uc(`dark`))!=null&&Wc.matches?`dark`:`light`}),Kc=function(){return(0,N.useContext)(Gc)},qc=(e,t)=>{if(t)return`row`;switch(e){case`horizontal`:return`row`;case`horizontal-reverse`:return`row-reverse`;case`vertical`:default:return`column`;case`vertical-reverse`:return`column-reverse`}},Jc=e=>{if(e)return[`space-between`,`space-around`,`space-evenly`].includes(e)},Yc=(e,t)=>qc(e,t)===`row`,Xc=e=>typeof e==`number`?`${e}px`:e,Zc=(0,N.memo)(({visible:e,flex:t,gap:n,direction:r,horizontal:i,align:a,justify:o,distribution:s,height:c,width:l,allowShrink:u,padding:d,paddingInline:f,paddingBlock:p,prefixCls:m,as:h=`div`,className:g,style:_,children:v,wrap:y,ref:b,...x})=>{let S=o||s,C=Yc(r,i)&&!l&&Jc(S)?`100%`:Xc(l),w={...t===void 0?{}:{"--lobe-flex":String(t)},...r||i?{"--lobe-flex-direction":qc(r,i)}:{},...y===void 0?{}:{"--lobe-flex-wrap":y},...S===void 0?{}:{"--lobe-flex-justify":S},...a===void 0?{}:{"--lobe-flex-align":a},...C===void 0?{}:{"--lobe-flex-width":C},...c===void 0?{}:{"--lobe-flex-height":Xc(c)},...d===void 0?{}:{"--lobe-flex-padding":Xc(d)},...f===void 0?{}:{"--lobe-flex-padding-inline":Xc(f)},...p===void 0?{}:{"--lobe-flex-padding-block":Xc(p)},...n===void 0?{}:{"--lobe-flex-gap":Xc(n)},...u?{minWidth:0}:{},..._},T=`lobe-flex`,E=[T,e===!1?`${T}--hidden`:void 0,m?`${m}-flex`:void 0,g].filter(Boolean).join(` `);return(0,P.jsx)(h,{ref:b,...x,className:E,style:w,children:v})}),Qc=({children:e,ref:t,...n})=>(0,P.jsx)(Zc,{...n,align:`center`,justify:`center`,ref:t,children:e});function $c(e){return Array.from(e.match(el)??[])}var el,tl=o((()=>{el=/\p{Lu}?\p{Ll}+|[0-9]+|\p{Lu}+(?!\p{Ll})|\p{Emoji_Presentation}|\p{Extended_Pictographic}|\p{L}+/gu}));function nl(e){return $c(e).map(e=>e.toLowerCase()).join(`-`)}o((()=>{tl()}))();var rl=function(e){var t=(0,N.useId)(),n=`lobe-icons-${nl(e)}-${t}`;return(0,N.useMemo)(function(){return{fill:`url(#${n})`,id:n}},[e])},il=function(e,t){var n=(0,N.useId)();return(0,N.useMemo)(function(){return Array.from({length:t},function(t,r){var i=`lobe-icons-${nl(e)}-${r}-${n}`;return{fill:`url(#${i})`,id:i}})},[e,t,n])},al=`Gemini`,ol=`#fff`,sl=`#fff`,cl=.8;function W(e){"@babel/helpers - typeof";return W=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},W(e)}var ll=[`size`,`style`];function ul(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function dl(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=Array(t);n=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Sl(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Cl=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=xl(e,ll),a=hl(il(al,3),3),o=a[0],s=a[1],c=a[2];return(0,P.jsxs)(`svg`,dl(dl({height:n,style:dl({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:al}),(0,P.jsx)(`path`,{d:`M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z`,fill:`#3186FF`}),(0,P.jsx)(`path`,{d:`M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z`,fill:o.fill}),(0,P.jsx)(`path`,{d:`M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z`,fill:s.fill}),(0,P.jsx)(`path`,{d:`M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z`,fill:c.fill}),(0,P.jsxs)(`defs`,{children:[(0,P.jsxs)(`linearGradient`,{gradientUnits:`userSpaceOnUse`,id:o.id,x1:`7`,x2:`11`,y1:`15.5`,y2:`12`,children:[(0,P.jsx)(`stop`,{stopColor:`#08B962`}),(0,P.jsx)(`stop`,{offset:`1`,stopColor:`#08B962`,stopOpacity:`0`})]}),(0,P.jsxs)(`linearGradient`,{gradientUnits:`userSpaceOnUse`,id:s.id,x1:`8`,x2:`11.5`,y1:`5.5`,y2:`11`,children:[(0,P.jsx)(`stop`,{stopColor:`#F94543`}),(0,P.jsx)(`stop`,{offset:`1`,stopColor:`#F94543`,stopOpacity:`0`})]}),(0,P.jsxs)(`linearGradient`,{gradientUnits:`userSpaceOnUse`,id:c.id,x1:`3.5`,x2:`17.5`,y1:`13.5`,y2:`12`,children:[(0,P.jsx)(`stop`,{stopColor:`#FABC12`}),(0,P.jsx)(`stop`,{offset:`.46`,stopColor:`#FABC12`,stopOpacity:`0`})]})]})]}))});function wl(e){"@babel/helpers - typeof";return wl=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},wl(e)}function Tl(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function El(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Kl(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var ql=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Gl(e,Y);return(0,P.jsxs)(`svg`,Vl(Vl({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Vl({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Ml}),(0,P.jsx)(`path`,{d:`M9.205 8.658v-2.26c0-.19.072-.333.238-.428l4.543-2.616c.619-.357 1.356-.523 2.117-.523 2.854 0 4.662 2.212 4.662 4.566 0 .167 0 .357-.024.547l-4.71-2.759a.797.797 0 00-.856 0l-5.97 3.473zm10.609 8.8V12.06c0-.333-.143-.57-.429-.737l-5.97-3.473 1.95-1.118a.433.433 0 01.476 0l4.543 2.617c1.309.76 2.189 2.378 2.189 3.948 0 1.808-1.07 3.473-2.76 4.163zM7.802 12.703l-1.95-1.142c-.167-.095-.239-.238-.239-.428V5.899c0-2.545 1.95-4.472 4.591-4.472 1 0 1.927.333 2.712.928L8.23 5.067c-.285.166-.428.404-.428.737v6.898zM12 15.128l-2.795-1.57v-3.33L12 8.658l2.795 1.57v3.33L12 15.128zm1.796 7.23c-1 0-1.927-.332-2.712-.927l4.686-2.712c.285-.166.428-.404.428-.737v-6.898l1.974 1.142c.167.095.238.238.238.428v5.233c0 2.545-1.974 4.472-4.614 4.472zm-5.637-5.303l-4.544-2.617c-1.308-.761-2.188-2.378-2.188-3.948A4.482 4.482 0 014.21 6.327v5.423c0 .333.143.571.428.738l5.947 3.449-1.95 1.118a.432.432 0 01-.476 0zm-.262 3.9c-2.688 0-4.662-2.021-4.662-4.519 0-.19.024-.38.047-.57l4.686 2.71c.286.167.571.167.856 0l5.97-3.448v2.26c0 .19-.07.333-.237.428l-4.543 2.616c-.619.357-1.356.523-2.117.523zm5.899 2.83a5.947 5.947 0 005.827-4.756C22.287 18.339 24 15.84 24 13.296c0-1.665-.713-3.282-1.998-4.448.119-.5.19-.999.19-1.498 0-3.401-2.759-5.947-5.946-5.947-.642 0-1.26.095-1.88.31A5.962 5.962 0 0010.205 0a5.947 5.947 0 00-5.827 4.757C1.713 5.447 0 7.945 0 10.49c0 1.666.713 3.283 1.998 4.448-.119.5-.19 1-.19 1.499 0 3.401 2.759 5.946 5.946 5.946.642 0 1.26-.095 1.88-.309a5.96 5.96 0 004.162 1.713z`})]}))});function Jl(e){"@babel/helpers - typeof";return Jl=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Jl(e)}var Yl=[`type`];function Xl(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Zl(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function nu(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var ru=(0,N.memo)(function(e){var t=e.type,n=t===void 0?`normal`:t,r=tu(e,Yl);return(0,P.jsx)($,Zl({Icon:ql,"aria-label":Ml,background:(0,N.useMemo)(function(){switch(n){case`gpt3`:return Pl;case`gpt4`:return Fl;case`gpt5`:return Il;case`o3`:case`o1`:return Ll;case`oss`:return Rl;case`platform`:return zl;default:return G}},[n]),color:K,iconMultiple:q},r))}),iu=`Qwen`,au=`#615ced`,ou=`#fff`,su=.75;function cu(e){"@babel/helpers - typeof";return cu=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},cu(e)}var lu=[`size`,`style`];function uu(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function du(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function gu(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var _u=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=hu(e,lu);return(0,P.jsxs)(`svg`,du(du({fill:`currentColor`,fillRule:`evenodd`,height:n,style:du({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:iu}),(0,P.jsx)(`path`,{d:`M12.604 1.34c.393.69.784 1.382 1.174 2.075a.18.18 0 00.157.091h5.552c.174 0 .322.11.446.327l1.454 2.57c.19.337.24.478.024.837-.26.43-.513.864-.76 1.3l-.367.658c-.106.196-.223.28-.04.512l2.652 4.637c.172.301.111.494-.043.77-.437.785-.882 1.564-1.335 2.34-.159.272-.352.375-.68.37-.777-.016-1.552-.01-2.327.016a.099.099 0 00-.081.05 575.097 575.097 0 01-2.705 4.74c-.169.293-.38.363-.725.364-.997.003-2.002.004-3.017.002a.537.537 0 01-.465-.271l-1.335-2.323a.09.09 0 00-.083-.049H4.982c-.285.03-.553-.001-.805-.092l-1.603-2.77a.543.543 0 01-.002-.54l1.207-2.12a.198.198 0 000-.197 550.951 550.951 0 01-1.875-3.272l-.79-1.395c-.16-.31-.173-.496.095-.965.465-.813.927-1.625 1.387-2.436.132-.234.304-.334.584-.335a338.3 338.3 0 012.589-.001.124.124 0 00.107-.063l2.806-4.895a.488.488 0 01.422-.246c.524-.001 1.053 0 1.583-.006L11.704 1c.341-.003.724.032.9.34zm-3.432.403a.06.06 0 00-.052.03L6.254 6.788a.157.157 0 01-.135.078H3.253c-.056 0-.07.025-.041.074l5.81 10.156c.025.042.013.062-.034.063l-2.795.015a.218.218 0 00-.2.116l-1.32 2.31c-.044.078-.021.118.068.118l5.716.008c.046 0 .08.02.104.061l1.403 2.454c.046.081.092.082.139 0l5.006-8.76.783-1.382a.055.055 0 01.096 0l1.424 2.53a.122.122 0 00.107.062l2.763-.02a.04.04 0 00.035-.02.041.041 0 000-.04l-2.9-5.086a.108.108 0 010-.113l.293-.507 1.12-1.977c.024-.041.012-.062-.035-.062H9.2c-.059 0-.073-.026-.043-.077l1.434-2.505a.107.107 0 000-.114L9.225 1.774a.06.06 0 00-.053-.031zm6.29 8.02c.046 0 .058.02.034.06l-.832 1.465-2.613 4.585a.056.056 0 01-.05.029.058.058 0 01-.05-.029L8.498 9.841c-.02-.034-.01-.052.028-.054l.216-.012 6.722-.012z`})]}))});function vu(e){"@babel/helpers - typeof";return vu=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},vu(e)}function yu(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function bu(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Ru(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var zu=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Lu(e,ju);return(0,P.jsxs)(`svg`,Nu(Nu({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Nu({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Eu}),(0,P.jsx)(`path`,{d:`M6.47 17l-.367-1.189H2.718L2.35 17H0l3.398-9.789h2.026L8.864 17H6.47zm-2.052-6.993l-1.17 4.028H5.56l-1.142-4.028zm4.707-2.796h2.23V17h-2.23V7.211zM11.955 15c.1-.483.277-.946.524-1.37.214-.359.482-.68.795-.951.32-.273.658-.52 1.013-.741.28-.168.54-.33.781-.483.222-.14.433-.296.632-.468.172-.148.317-.325.428-.525.107-.199.16-.423.157-.65 0-.392-.104-.674-.313-.846a1.176 1.176 0 00-.775-.259 1.207 1.207 0 00-.863.329c-.231.219-.347.585-.347 1.098H11.8a3.387 3.387 0 01.224-1.245c.146-.377.371-.716.66-.993.306-.29.667-.514 1.06-.657A4.04 4.04 0 0115.183 7c.42-.002.84.057 1.244.175.376.107.73.287 1.04.531.305.246.55.562.714.923.185.419.275.875.265 1.335.005.39-.084.774-.259 1.12-.167.328-.38.63-.632.894-.246.259-.517.49-.808.693-.29.2-.554.37-.789.51-.326.224-.596.417-.809.58a3.872 3.872 0 00-.51.455 1.229 1.229 0 00-.265.434 1.633 1.633 0 00-.074.517h4.078V17h-6.606a9.24 9.24 0 01.183-2zM18.8 8.93a5.05 5.05 0 001.135-.105c.25-.049.484-.156.686-.314.163-.139.28-.324.34-.532.068-.25.1-.51.095-.77H23V17h-2.243v-6.475H18.8V8.93z`})]}))}),Bu=`Anthropic`,Vu=`#F1F0E8`,Hu=`#141413`,Uu=.75;function Wu(e){"@babel/helpers - typeof";return Wu=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Wu(e)}var Gu=[`size`,`style`];function X(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Ku(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Zu(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Qu=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Xu(e,Gu);return(0,P.jsxs)(`svg`,Ku(Ku({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Ku({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Bu}),(0,P.jsx)(`path`,{d:`M13.827 3.52h3.603L24 20h-3.603l-6.57-16.48zm-7.258 0h3.767L16.906 20h-3.674l-1.343-3.461H5.017l-1.344 3.46H0L6.57 3.522zm4.132 9.959L8.453 7.687 6.205 13.48H10.7z`})]}))});function $u(e){"@babel/helpers - typeof";return $u=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},$u(e)}function ed(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function td(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function yd(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var bd=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=vd(e,fd);return(0,P.jsxs)(`svg`,md(md({fill:`currentColor`,fillRule:`evenodd`,height:n,style:md({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:sd}),(0,P.jsx)(`path`,{d:`M10.595 1.5a3.695 3.695 0 00-3.444 2.355L0 22.26h5.432l5.629-14.486h.002a.96.96 0 011.782 0h.75V4.835h-1.393L13.498 1.5h-2.902z`}),(0,P.jsx)(`path`,{d:`M7.151 3.855a3.695 3.695 0 013.26-2.35l-.002-.005H13.405c1.524 0 2.893.936 3.444 2.355L24 22.26h-5.525L11.54 4.413a2.528 2.528 0 00-4.609.006l.22-.564z`})]}))});function Z(e){"@babel/helpers - typeof";return Z=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Z(e)}function xd(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Sd(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Rd(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var zd=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Ld(e,Md);return(0,P.jsxs)(`svg`,Q(Q({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Q({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:`AWS`}),(0,P.jsx)(`path`,{d:`M6.763 11.212c0 .296.032.535.088.71.064.176.144.368.256.576.04.063.056.127.056.183 0 .08-.048.16-.152.24l-.503.335a.383.383 0 01-.208.072c-.08 0-.16-.04-.239-.112a2.47 2.47 0 01-.287-.375 6.18 6.18 0 01-.248-.471c-.622.734-1.405 1.101-2.347 1.101-.67 0-1.205-.191-1.596-.574-.39-.384-.59-.894-.59-1.533 0-.678.24-1.23.726-1.644.487-.415 1.133-.623 1.955-.623.272 0 .551.024.846.064.296.04.6.104.918.176v-.583c0-.607-.127-1.03-.375-1.277-.255-.248-.686-.367-1.3-.367-.28 0-.568.031-.863.103-.295.072-.583.16-.862.272a2.4 2.4 0 01-.28.104.488.488 0 01-.127.023c-.112 0-.168-.08-.168-.247v-.391c0-.128.016-.224.056-.28a.597.597 0 01.224-.167 4.577 4.577 0 011.005-.36 4.84 4.84 0 011.246-.151c.95 0 1.644.216 2.091.647.44.43.662 1.085.662 1.963v2.586h.016zm-3.24 1.214c.263 0 .534-.048.822-.144a1.78 1.78 0 00.758-.51 1.27 1.27 0 00.272-.512c.047-.191.08-.423.08-.694v-.335a6.66 6.66 0 00-.735-.136 6.02 6.02 0 00-.75-.048c-.535 0-.926.104-1.19.32-.263.215-.39.518-.39.917 0 .375.095.655.295.846.191.2.47.296.838.296zm6.41.862c-.144 0-.24-.024-.304-.08-.064-.048-.12-.16-.168-.311L7.586 6.726a1.398 1.398 0 01-.072-.32c0-.128.064-.2.191-.2h.783c.151 0 .255.025.31.08.065.048.113.16.16.312l1.342 5.284 1.245-5.284c.04-.16.088-.264.151-.312a.549.549 0 01.32-.08h.638c.152 0 .256.025.32.08.063.048.12.16.151.312l1.261 5.348 1.381-5.348c.048-.16.104-.264.16-.312a.52.52 0 01.311-.08h.743c.127 0 .2.065.2.2 0 .04-.009.08-.017.128a1.137 1.137 0 01-.056.2l-1.923 6.17c-.048.16-.104.263-.168.311a.51.51 0 01-.303.08h-.687c-.15 0-.255-.024-.32-.08-.063-.056-.119-.16-.15-.32L12.32 7.747l-1.23 5.14c-.04.16-.087.264-.15.32-.065.056-.177.08-.32.08l-.686.001zm10.256.215c-.415 0-.83-.048-1.229-.143-.399-.096-.71-.2-.918-.32-.128-.071-.215-.151-.247-.223a.563.563 0 01-.048-.224v-.407c0-.167.064-.247.183-.247.048 0 .096.008.144.024.048.016.12.048.2.08.271.12.566.215.878.279.32.064.63.096.95.096.502 0 .894-.088 1.165-.264a.86.86 0 00.415-.758.777.777 0 00-.215-.559c-.144-.151-.416-.287-.807-.415l-1.157-.36c-.583-.183-1.014-.454-1.277-.813a1.902 1.902 0 01-.4-1.158c0-.335.073-.63.216-.886.144-.255.335-.479.575-.654.24-.184.51-.32.83-.415.32-.096.655-.136 1.006-.136.175 0 .36.008.535.032.183.024.35.056.518.088.16.04.312.08.455.127.144.048.256.096.336.144a.69.69 0 01.24.2.43.43 0 01.071.263v.375c0 .168-.064.256-.184.256a.83.83 0 01-.303-.096 3.652 3.652 0 00-1.532-.311c-.455 0-.815.071-1.062.223-.248.152-.375.383-.375.71 0 .224.08.416.24.567.16.152.454.304.877.44l1.134.358c.574.184.99.44 1.237.767.247.327.367.702.367 1.117 0 .343-.072.655-.207.926a2.157 2.157 0 01-.583.703c-.248.2-.543.343-.886.447-.36.111-.734.167-1.142.167z`}),(0,P.jsx)(`path`,{d:`M.378 15.475c3.384 1.963 7.56 3.153 11.877 3.153 2.914 0 6.114-.607 9.06-1.852.44-.2.814.287.383.607-2.626 1.94-6.442 2.969-9.722 2.969-4.598 0-8.74-1.7-11.87-4.526-.247-.223-.024-.527.272-.351zm23.531-.2c.287.36-.08 2.826-1.485 4.007-.215.184-.423.088-.327-.151l.175-.439c.343-.88.802-2.198.52-2.555-.336-.43-2.22-.207-3.074-.103-.255.032-.295-.192-.063-.36 1.5-1.053 3.967-.75 4.254-.399z`,fill:`#F90`})]}))});function Bd(e){"@babel/helpers - typeof";return Bd=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Bd(e)}function Vd(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Hd(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function sf(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var cf=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=of(e,$d);return(0,P.jsxs)(`svg`,tf(tf({fill:`currentColor`,fillRule:`evenodd`,height:n,style:tf({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Jd}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M8.128 14.099c.592 0 1.77-.033 3.398-.703 1.897-.781 5.672-2.2 8.395-3.656 1.905-1.018 2.74-2.366 2.74-4.18A4.56 4.56 0 0018.1 1H7.549A6.55 6.55 0 001 7.55c0 3.617 2.745 6.549 7.128 6.549z`}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M9.912 18.61a4.387 4.387 0 012.705-4.052l3.323-1.38c3.361-1.394 7.06 1.076 7.06 4.715a5.104 5.104 0 01-5.105 5.104l-3.597-.001a4.386 4.386 0 01-4.386-4.387z`}),(0,P.jsx)(`path`,{d:`M4.776 14.962A3.775 3.775 0 001 18.738v.489a3.776 3.776 0 007.551 0v-.49a3.775 3.775 0 00-3.775-3.775z`})]}))});function lf(e){"@babel/helpers - typeof";return lf=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},lf(e)}function uf(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function df(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function kf(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Af=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Of(e,Sf);return(0,P.jsxs)(`svg`,wf(wf({fill:`currentColor`,fillRule:`evenodd`,height:n,style:wf({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:_f}),(0,P.jsx)(`path`,{d:`M21.821 9.894l-9.81 5.595L1.505 9.511 1 9.787v4.34l11.01 6.256 9.811-5.574v2.297l-9.81 5.596-10.506-5.979L1 17v.745L12.01 24 23 17.745v-4.34l-.505-.277-10.484 5.957-9.832-5.574v-2.298l9.832 5.574L23 10.532V6.255l-.547-.319-10.442 5.936-9.327-5.276 9.327-5.298 7.663 4.362.673-.383v-.532L12.011 0 1 6.255v.681l11.01 6.255 9.811-5.595z`})]}))});function jf(e){"@babel/helpers - typeof";return jf=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},jf(e)}function Mf(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Nf(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Zf(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Qf=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Xf(e,Wf);return(0,P.jsxs)(`svg`,Kf(Kf({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Kf({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:zf}),(0,P.jsx)(`path`,{d:`M23.748 4.482c-.254-.124-.364.113-.512.234-.051.039-.094.09-.137.136-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.156-.708-.311-.955-.65-.172-.241-.219-.51-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.093.172.187.129.323-.082.28-.18.552-.266.833-.055.179-.137.217-.329.14a5.526 5.526 0 01-1.736-1.18c-.857-.828-1.631-1.742-2.597-2.458a11.365 11.365 0 00-.689-.471c-.985-.957.13-1.743.388-1.836.27-.098.093-.432-.779-.428-.872.004-1.67.295-2.687.684a3.055 3.055 0 01-.465.137 9.597 9.597 0 00-2.883-.102c-1.885.21-3.39 1.102-4.497 2.623C.082 8.606-.231 10.684.152 12.85c.403 2.284 1.569 4.175 3.36 5.653 1.858 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.133-.284 4.994-1.86.47.234.962.327 1.78.397.63.059 1.236-.03 1.705-.128.735-.156.684-.837.419-.961-2.155-1.004-1.682-.595-2.113-.926 1.096-1.296 2.746-2.642 3.392-7.003.05-.347.007-.565 0-.845-.004-.17.035-.237.23-.256a4.173 4.173 0 001.545-.475c1.396-.763 1.96-2.015 2.093-3.517.02-.23-.004-.467-.247-.588zM11.581 18c-2.089-1.642-3.102-2.183-3.52-2.16-.392.024-.321.471-.235.763.09.288.207.486.371.739.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.167-1.361-.802-2.5-1.86-3.301-3.307-.774-1.393-1.224-2.887-1.298-4.482-.02-.386.093-.522.477-.592a4.696 4.696 0 011.529-.039c2.132.312 3.946 1.265 5.468 2.774.868.86 1.525 1.887 2.202 2.891.72 1.066 1.494 2.082 2.48 2.914.348.292.625.514.891.677-.802.09-2.14.11-3.054-.614zm1-6.44a.306.306 0 01.415-.287.302.302 0 01.2.288.306.306 0 01-.31.307.303.303 0 01-.304-.308zm3.11 1.596c-.2.081-.399.151-.59.16a1.245 1.245 0 01-.798-.254c-.274-.23-.47-.358-.552-.758a1.73 1.73 0 01.016-.588c.07-.327-.008-.537-.239-.727-.187-.156-.426-.199-.688-.199a.559.559 0 01-.254-.078c-.11-.054-.2-.19-.114-.358.028-.054.16-.186.192-.21.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.391.451.462.576.685.914.176.265.336.537.445.848.067.195-.019.354-.25.452z`})]}))});function $f(e){"@babel/helpers - typeof";return $f=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},$f(e)}function ep(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function tp(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function yp(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var bp=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=vp(e,fp);return(0,P.jsxs)(`svg`,mp(mp({fill:`currentColor`,fillRule:`evenodd`,height:n,style:mp({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:sp}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M14.8 5l-2.801 6.795L9.195 5H7.397l3.072 7.428a1.64 1.64 0 003.038.002L16.598 5H14.8zm1.196 10.352l5.124-5.244-.699-1.669-5.596 5.739a1.664 1.664 0 00-.343 1.807 1.642 1.642 0 001.516 1.012L16 17l8-.02-.699-1.669-7.303.041h-.002zM2.88 10.104l.699-1.669 5.596 5.739c.468.479.603 1.189.343 1.807a1.643 1.643 0 01-1.516 1.012l-8-.018-.002.002.699-1.669 7.303.042-5.122-5.246z`})]}))});function xp(e){"@babel/helpers - typeof";return xp=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},xp(e)}function Sp(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Cp(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Vp(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Hp=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Bp(e,Pp);return(0,P.jsxs)(`svg`,Ip(Ip({height:n,style:Ip({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:kp}),(0,P.jsx)(`path`,{d:`M23 12.245c0-.905-.075-1.565-.236-2.25h-10.54v4.083h6.186c-.124 1.014-.797 2.542-2.294 3.569l-.021.136 3.332 2.53.23.022C21.779 18.417 23 15.593 23 12.245z`,fill:`#4285F4`}),(0,P.jsx)(`path`,{d:`M12.225 23c3.03 0 5.574-.978 7.433-2.665l-3.542-2.688c-.948.648-2.22 1.1-3.891 1.1a6.745 6.745 0 01-6.386-4.572l-.132.011-3.465 2.628-.045.124C4.043 20.531 7.835 23 12.225 23z`,fill:`#34A853`}),(0,P.jsx)(`path`,{d:`M5.84 14.175A6.65 6.65 0 015.463 12c0-.758.138-1.491.361-2.175l-.006-.147-3.508-2.67-.115.054A10.831 10.831 0 001 12c0 1.772.436 3.447 1.197 4.938l3.642-2.763z`,fill:`#FBBC05`}),(0,P.jsx)(`path`,{d:`M12.225 5.253c2.108 0 3.529.892 4.34 1.638l3.167-3.031C17.787 2.088 15.255 1 12.225 1 7.834 1 4.043 3.469 2.197 7.062l3.63 2.763a6.77 6.77 0 016.398-4.572z`,fill:`#EB4335`})]}))});function Up(e){"@babel/helpers - typeof";return Up=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Up(e)}function Wp(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Gp(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function cm(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var lm=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=sm(e,tm);return(0,P.jsxs)(`svg`,rm(rm({fill:`currentColor`,fillRule:`evenodd`,height:n,style:rm({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:`IBM`}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M24 16.333V17h-3.158v-.667H24zm-7.579 0V17h-3.158v-.667h3.158zm2.464 0L18.63 17l-.25-.667h.504zm-7.075 0a2.528 2.528 0 01-1.717.667h-5.04v-.667h6.757zm-7.389 0V17H0v-.667h4.421zm12-1.333v.667h-3.158V15h3.158zm2.958 0l-.246.667h-1L17.885 15h1.494zm-6.937 0c-.057.237-.148.46-.265.667H5.053V15h7.39zm-8.02 0v.667H0V15h4.421zM24 15v.667h-3.158V15H24zm-1.263-1.333v.666h-1.895v-.666h1.895zm-6.316 0v.666h-1.895v-.666h1.895zm3.453 0l-.248.666h-1.989l-.25-.666h2.487zm-7.52 0c.056.212.088.435.088.666h-2.337v-.666h2.249zm-4.143 0v.666H6.316v-.666H8.21zm-5.053 0v.666H1.263v-.666h1.895zm19.579-1.334V13h-1.895v-.667h1.895zm-6.316 0V13h-1.895v-.667h1.895zm3.948 0l-.247.667h-2.987l-.245-.667h3.48zm-8.792 0c.218.188.405.414.55.667H6.315v-.667h5.26zm-8.42 0V13H1.264v-.667h1.895zM18.456 11l.177.539.176-.539h3.929v.667h-1.895v-.613l-.215.613H16.63l-.209-.613v.613h-1.895V11h3.929zM3.158 11v.667H1.263V11h1.895zm8.968 0a2.555 2.555 0 01-.55.667h-5.26V11h5.81zm10.61-1.333v.666h-3.709l.224-.666h3.486zm-4.722 0l.224.666h-3.712v-.666h3.488zm-5.572 0c0 .23-.032.454-.088.666h-2.249v-.666h2.337zm-4.231 0v.666H6.316v-.666H8.21zm-5.053 0v.666H1.263v-.666h1.895zm14.419-1.334l.22.667h-4.534v-.667h4.314zm6.423 0V9h-4.536l.229-.667H24zm-11.823 0c.117.206.208.43.265.667h-7.39v-.667h7.125zm-7.756 0V9H0v-.667h4.421zM17.133 7l.224.667h-4.094V7h3.87zM24 7v.667h-4.089L20.13 7H24zM10.093 7c.662 0 1.264.253 1.717.667H5.053V7h5.04zM4.42 7v.667H0V7h4.421z`})]}))});function um(e){"@babel/helpers - typeof";return um=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},um(e)}function dm(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function fm(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Am(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var jm=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=km(e,Cm);return(0,P.jsxs)(`svg`,Tm(Tm({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Tm({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:vm}),(0,P.jsx)(`path`,{d:`M6.608 21.416a4.608 4.608 0 100-9.217 4.608 4.608 0 000 9.217zM20.894 2.015c.614 0 1.106.492 1.106 1.106v9.002c0 5.13-4.148 9.309-9.217 9.37v-9.355l-.03-9.032c0-.614.491-1.106 1.106-1.106h7.158l-.123.015z`})]}))});function Mm(e){"@babel/helpers - typeof";return Mm=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Mm(e)}function Nm(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Pm(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Qm(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var $m=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Zm(e,Gm);return(0,P.jsxs)(`svg`,qm(qm({fill:`currentColor`,fillRule:`evenodd`,height:n,style:qm({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Bm}),(0,P.jsx)(`path`,{d:`M6.897 4c1.915 0 3.516.932 5.43 3.376l.282-.373c.19-.246.383-.484.58-.71l.313-.35C14.588 4.788 15.792 4 17.225 4c1.273 0 2.469.557 3.491 1.516l.218.213c1.73 1.765 2.917 4.71 3.053 8.026l.011.392.002.25c0 1.501-.28 2.759-.818 3.7l-.14.23-.108.153c-.301.42-.664.758-1.086 1.009l-.265.142-.087.04a3.493 3.493 0 01-.302.118 4.117 4.117 0 01-1.33.208c-.524 0-.996-.067-1.438-.215-.614-.204-1.163-.56-1.726-1.116l-.227-.235c-.753-.812-1.534-1.976-2.493-3.586l-1.43-2.41-.544-.895-1.766 3.13-.343.592C7.597 19.156 6.227 20 4.356 20c-1.21 0-2.205-.42-2.936-1.182l-.168-.184c-.484-.573-.837-1.311-1.043-2.189l-.067-.32a8.69 8.69 0 01-.136-1.288L0 14.468c.002-.745.06-1.49.174-2.23l.1-.573c.298-1.53.828-2.958 1.536-4.157l.209-.34c1.177-1.83 2.789-3.053 4.615-3.16L6.897 4zm-.033 2.615l-.201.01c-.83.083-1.606.673-2.252 1.577l-.138.199-.01.018c-.67 1.017-1.185 2.378-1.456 3.845l-.004.022a12.591 12.591 0 00-.207 2.254l.002.188c.004.18.017.36.04.54l.043.291c.092.503.257.908.486 1.208l.117.137c.303.323.698.492 1.17.492 1.1 0 1.796-.676 3.696-3.641l2.175-3.4.454-.701-.139-.198C9.11 7.3 8.084 6.616 6.864 6.616zm10.196-.552l-.176.007c-.635.048-1.223.359-1.82.933l-.196.198c-.439.462-.887 1.064-1.367 1.807l.266.398c.18.274.362.56.55.858l.293.475 1.396 2.335.695 1.114c.583.926 1.03 1.6 1.408 2.082l.213.262c.282.326.529.54.777.673l.102.05c.227.1.457.138.718.138.176.002.35-.023.518-.073.338-.104.61-.32.813-.637l.095-.163.077-.162c.194-.459.29-1.06.29-1.785l-.006-.449c-.08-2.871-.938-5.372-2.2-6.798l-.176-.189c-.67-.683-1.444-1.074-2.27-1.074z`})]}))});function eh(e){"@babel/helpers - typeof";return eh=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},eh(e)}function th(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function nh(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function bh(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var xh=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=yh(e,ph);return(0,P.jsxs)(`svg`,hh(hh({fill:`currentColor`,fillRule:`evenodd`,height:n,style:hh({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:ch}),(0,P.jsx)(`path`,{d:`M16.278 2c1.156 0 2.093.927 2.093 2.07v12.501a.74.74 0 00.744.709.74.74 0 00.743-.709V9.099a2.06 2.06 0 012.071-2.049A2.06 2.06 0 0124 9.1v6.561a.649.649 0 01-.652.645.649.649 0 01-.653-.645V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v7.472a2.037 2.037 0 01-2.048 2.026 2.037 2.037 0 01-2.048-2.026v-12.5a.785.785 0 00-.788-.753.785.785 0 00-.789.752l-.001 15.904A2.037 2.037 0 0113.441 22a2.037 2.037 0 01-2.048-2.026V18.04c0-.356.292-.645.652-.645.36 0 .652.289.652.645v1.934c0 .263.142.506.372.638.23.131.514.131.744 0a.734.734 0 00.372-.638V4.07c0-1.143.937-2.07 2.093-2.07zm-5.674 0c1.156 0 2.093.927 2.093 2.07v11.523a.648.648 0 01-.652.645.648.648 0 01-.652-.645V4.07a.785.785 0 00-.789-.78.785.785 0 00-.789.78v14.013a2.06 2.06 0 01-2.07 2.048 2.06 2.06 0 01-2.071-2.048V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v3.8a2.06 2.06 0 01-2.071 2.049A2.06 2.06 0 010 12.9v-1.378c0-.357.292-.646.652-.646.36 0 .653.29.653.646V12.9c0 .418.343.757.766.757s.766-.339.766-.757V9.099a2.06 2.06 0 012.07-2.048 2.06 2.06 0 012.071 2.048v8.984c0 .419.343.758.767.758.423 0 .766-.339.766-.758V4.07c0-1.143.937-2.07 2.093-2.07z`})]}))});function Sh(e){"@babel/helpers - typeof";return Sh=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Sh(e)}function Ch(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function wh(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Hh(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Uh=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Vh(e,Fh);return(0,P.jsxs)(`svg`,Lh(Lh({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Lh({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Ah}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M3.428 3.4h3.429v3.428h3.429v3.429h-.002 3.431V6.828h3.427V3.4h3.43v13.714H24v3.429H13.714v-3.428h-3.428v-3.429h-3.43v3.428h3.43v3.429H0v-3.429h3.428V3.4zm10.286 13.715h3.428v-3.429h-3.427v3.429z`})]}))});function Wh(e){"@babel/helpers - typeof";return Wh=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Wh(e)}function Gh(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Kh(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function ug(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var dg=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=lg(e,rg);return(0,P.jsxs)(`svg`,ag(ag({fill:`currentColor`,fillRule:`evenodd`,height:n,style:ag({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Qh}),(0,P.jsx)(`path`,{d:`M1.052 16.916l9.539 2.552a21.007 21.007 0 00.06 2.033l5.956 1.593a11.997 11.997 0 01-5.586.865l-.18-.016-.044-.004-.084-.009-.094-.01a11.605 11.605 0 01-.157-.02l-.107-.014-.11-.016a11.962 11.962 0 01-.32-.051l-.042-.008-.075-.013-.107-.02-.07-.015-.093-.019-.075-.016-.095-.02-.097-.023-.094-.022-.068-.017-.088-.022-.09-.024-.095-.025-.082-.023-.109-.03-.062-.02-.084-.025-.093-.028-.105-.034-.058-.019-.08-.026-.09-.031-.066-.024a6.293 6.293 0 01-.044-.015l-.068-.025-.101-.037-.057-.022-.08-.03-.087-.035-.088-.035-.079-.032-.095-.04-.063-.028-.063-.027a5.655 5.655 0 01-.041-.018l-.066-.03-.103-.047-.052-.024-.096-.046-.062-.03-.084-.04-.086-.044-.093-.047-.052-.027-.103-.055-.057-.03-.058-.032a6.49 6.49 0 01-.046-.026l-.094-.053-.06-.034-.051-.03-.072-.041-.082-.05-.093-.056-.052-.032-.084-.053-.061-.039-.079-.05-.07-.047-.053-.035a7.785 7.785 0 01-.054-.036l-.044-.03-.044-.03a6.066 6.066 0 01-.04-.028l-.057-.04-.076-.054-.069-.05-.074-.054-.056-.042-.076-.057-.076-.059-.086-.067-.045-.035-.064-.052-.074-.06-.089-.073-.046-.039-.046-.039a7.516 7.516 0 01-.043-.037l-.045-.04-.061-.053-.07-.062-.068-.06-.062-.058-.067-.062-.053-.05-.088-.084a13.28 13.28 0 01-.099-.097l-.029-.028-.041-.042-.069-.07-.05-.051-.05-.053a6.457 6.457 0 01-.168-.179l-.08-.088-.062-.07-.071-.08-.042-.049-.053-.062-.058-.068-.046-.056a7.175 7.175 0 01-.027-.033l-.045-.055-.066-.082-.041-.052-.05-.064-.02-.025a11.99 11.99 0 01-1.44-2.402zm-1.02-5.794l11.353 3.037a20.468 20.468 0 00-.469 2.011l10.817 2.894a12.076 12.076 0 01-1.845 2.005L.657 15.923l-.016-.046-.035-.104a11.965 11.965 0 01-.05-.153l-.007-.023a11.896 11.896 0 01-.207-.741l-.03-.126-.018-.08-.021-.097-.018-.081-.018-.09-.017-.084-.018-.094c-.026-.141-.05-.283-.071-.426l-.017-.118-.011-.083-.013-.102a12.01 12.01 0 01-.019-.161l-.005-.047a12.12 12.12 0 01-.034-2.145zm1.593-5.15l11.948 3.196c-.368.605-.705 1.231-1.01 1.875l11.295 3.022c-.142.82-.368 1.612-.668 2.365l-11.55-3.09L.124 10.26l.015-.1.008-.049.01-.067.015-.087.018-.098c.026-.148.056-.295.088-.442l.028-.124.02-.085.024-.097c.022-.09.045-.18.07-.268l.028-.102.023-.083.03-.1.025-.082.03-.096.026-.082.031-.095a11.896 11.896 0 011.01-2.232zm4.442-4.4L17.352 4.59a20.77 20.77 0 00-1.688 1.721l7.823 2.093c.267.852.442 1.744.513 2.665L2.106 5.213l.045-.065.027-.04.04-.055.046-.065.055-.076.054-.072.064-.086.05-.065.057-.073.055-.07.06-.074.055-.069.065-.077.054-.066.066-.077.053-.06.072-.082.053-.06.067-.074.054-.058.073-.078.058-.06.063-.067.168-.17.1-.098.059-.056.076-.071a12.084 12.084 0 012.272-1.677zM12.017 0h.097l.082.001.069.001.054.002.068.002.046.001.076.003.047.002.06.003.054.002.087.005.105.007.144.011.088.007.044.004.077.008.082.008.047.005.102.012.05.006.108.014.081.01.042.006.065.01.207.032.07.012.065.011.14.026.092.018.11.022.046.01.075.016.041.01L14.7.3l.042.01.065.015.049.012.071.017.096.024.112.03.113.03.113.032.05.015.07.02.078.024.073.023.05.016.05.016.076.025.099.033.102.036.048.017.064.023.093.034.11.041.116.045.1.04.047.02.06.024.041.018.063.026.04.018.057.025.11.048.1.046.074.035.075.036.06.028.092.046.091.045.102.052.053.028.049.026.046.024.06.033.041.022.052.029.088.05.106.06.087.051.057.034.053.032.096.059.088.055.098.062.036.024.064.041.084.056.04.027.062.042.062.043.023.017c.054.037.108.075.161.114l.083.06.065.048.056.043.086.065.082.064.04.03.05.041.086.069.079.065.085.071c.712.6 1.353 1.283 1.909 2.031L7.222.994l.062-.027.065-.028.081-.034.086-.035c.113-.045.227-.09.341-.131l.096-.035.093-.033.084-.03.096-.031c.087-.03.176-.058.264-.085l.091-.027.086-.025.102-.03.085-.023.1-.026L9.04.37l.09-.023.091-.022.095-.022.09-.02.098-.021.091-.02.095-.018.092-.018.1-.018.091-.016.098-.017.092-.014.097-.015.092-.013.102-.013.091-.012.105-.012.09-.01.105-.01c.093-.01.186-.018.28-.024l.106-.008.09-.005.11-.006.093-.004.1-.004.097-.002.099-.002.197-.002z`})]}))});function fg(e){"@babel/helpers - typeof";return fg=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},fg(e)}function pg(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function mg(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Mg(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Ng=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=jg(e,Tg);return(0,P.jsxs)(`svg`,Dg(Dg({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Dg({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:bg}),(0,P.jsx)(`path`,{d:`M7.941 2c.23 0 .452.073.638.21.186.136.325.328.397.55l.593 1.814c.073.221.212.413.397.55.186.136.409.21.638.21h2.791c.23 0 .452-.074.638-.21a1.11 1.11 0 00.397-.55l.594-1.815a1.11 1.11 0 01.397-.55c.185-.136.408-.209.637-.209h1.7c.23 0 .453.073.639.21.185.136.324.328.397.55l.652 1.994c.118.361.41.635.77.728l2.957.752c.236.06.446.199.596.394.15.195.23.436.231.684v9.376c0 .248-.081.488-.231.684a1.09 1.09 0 01-.595.394l-2.957.752a1.086 1.086 0 00-.477.263 1.114 1.114 0 00-.293.465l-.653 1.994a1.11 1.11 0 01-.396.55c-.186.136-.41.21-.638.21h-1.702c-.229 0-.452-.073-.637-.21a1.11 1.11 0 01-.397-.55l-.364-1.11a1.131 1.131 0 01.15-1.002 1.074 1.074 0 01.885-.462h2.85c.29 0 .567-.116.772-.325.204-.208.32-.49.32-.785V6.444c0-.294-.116-.577-.32-.785a1.08 1.08 0 00-.771-.326h-3.273c-.29 0-.567.117-.772.326-.204.208-.32.49-.32.785v7.778c0 .295-.114.578-.319.786a1.08 1.08 0 01-.771.325h-2.182a1.08 1.08 0 01-.771-.325 1.122 1.122 0 01-.32-.786V6.444c0-.294-.115-.577-.32-.785a1.081 1.081 0 00-.77-.326H5.454c-.29 0-.567.117-.772.326-.204.208-.32.49-.32.785v11.112c0 .294.116.577.32.785.205.209.482.326.772.326h2.85a1.075 1.075 0 01.885.461 1.122 1.122 0 01.15 1.001l-.364 1.112a1.11 1.11 0 01-.397.55c-.185.136-.408.209-.637.209H6.24c-.229 0-.452-.073-.638-.21a1.11 1.11 0 01-.397-.55l-.652-1.994a1.114 1.114 0 00-.294-.465 1.086 1.086 0 00-.477-.263l-2.956-.752a1.09 1.09 0 01-.595-.394A1.124 1.124 0 010 16.688V7.312c0-.248.081-.489.231-.684.15-.195.36-.334.595-.394l2.957-.753c.178-.045.342-.136.477-.263.134-.127.235-.287.293-.464l.653-1.995a1.11 1.11 0 01.397-.55C5.788 2.075 6.01 2 6.24 2h1.701z`})]}))});function Pg(e){"@babel/helpers - typeof";return Pg=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Pg(e)}function Fg(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Ig(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function e_(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var t_=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=$g(e,qg);return(0,P.jsxs)(`svg`,Yg(Yg({fill:`none`,height:n,style:Yg({flex:`none`,lineHeight:1},r),viewBox:`0 0 33 32`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Hg}),(0,P.jsx)(`path`,{d:`m17.865 23.28 1.533 1.543c.07.07.092.175.055.267l-2.398 6.118A1.24 1.24 0 0 1 15.9 32c-.51 0-.969-.315-1.155-.793l-3.451-8.804-5.582 5.617a.246.246 0 0 1-.35 0l-1.407-1.415a.25.25 0 0 1 0-.352l6.89-6.932a1.3 1.3 0 0 1 .834-.398 1.25 1.25 0 0 1 1.232.79l2.992 7.63 1.557-3.977a.248.248 0 0 1 .408-.085zm8.224-19.3-5.583 5.617-3.45-8.805a1.24 1.24 0 0 0-1.43-.762c-.414.092-.744.407-.899.805l-2.38 6.072a.25.25 0 0 0 .055.267l1.533 1.543c.127.127.34.082.407-.085L15.9 4.655l2.991 7.629a1.24 1.24 0 0 0 2.035.425l6.922-6.965a.25.25 0 0 0 0-.352L26.44 3.977a.246.246 0 0 0-.35 0zM8.578 17.566l-3.953-1.567 7.582-3.01c.49-.195.815-.685.785-1.24a1.3 1.3 0 0 0-.395-.84l-6.886-6.93a.246.246 0 0 0-.35 0L3.954 5.395a.25.25 0 0 0 0 .353l5.583 5.617-8.75 3.472a1.25 1.25 0 0 0 0 2.325l6.079 2.412a.24.24 0 0 0 .266-.055l1.533-1.542a.25.25 0 0 0-.085-.41zm22.434-2.73-6.08-2.412a.24.24 0 0 0-.265.055l-1.533 1.542a.25.25 0 0 0 .084.41L27.172 16l-7.583 3.01a1.255 1.255 0 0 0-.785 1.24c.018.317.172.614.395.84l6.89 6.931a.246.246 0 0 0 .35 0l1.406-1.415a.25.25 0 0 0 0-.352l-5.582-5.617 8.75-3.472a1.25 1.25 0 0 0 0-2.325z`,fill:`currentColor`})]}))});function n_(e){"@babel/helpers - typeof";return n_=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},n_(e)}function r_(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function i_(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function S_(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var C_=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=x_(e,h_);return(0,P.jsxs)(`svg`,__(__({fill:`currentColor`,fillRule:`evenodd`,height:n,style:__({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:u_}),(0,P.jsx)(`path`,{d:`M10.212 8.976V7.62c.127-.01.256-.017.388-.021 3.596-.117 5.957 3.184 5.957 3.184s-2.548 3.647-5.282 3.647a3.227 3.227 0 01-1.063-.175v-4.109c1.4.174 1.681.812 2.523 2.258l1.873-1.627a4.905 4.905 0 00-3.67-1.846 6.594 6.594 0 00-.729.044m0-4.476v2.025c.13-.01.259-.019.388-.024 5.002-.174 8.261 4.226 8.261 4.226s-3.743 4.69-7.643 4.69c-.338 0-.675-.031-1.007-.092v1.25c.278.038.558.057.838.057 3.629 0 6.253-1.91 8.794-4.169.421.347 2.146 1.193 2.501 1.564-2.416 2.083-8.048 3.763-11.24 3.763-.308 0-.603-.02-.894-.048V19.5H24v-15H10.21zm0 9.756v1.068c-3.356-.616-4.287-4.21-4.287-4.21a7.173 7.173 0 014.287-2.138v1.172h-.005a3.182 3.182 0 00-2.502 1.178s.615 2.276 2.507 2.931m-5.961-3.3c1.436-1.935 3.604-3.148 5.961-3.336V6.523C5.81 6.887 2 10.723 2 10.723s2.158 6.427 8.21 7.015v-1.166C5.77 16 4.25 10.958 4.25 10.958h-.002z`})]}))});function w_(e){"@babel/helpers - typeof";return w_=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},w_(e)}function T_(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function E_(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function W_(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var G_=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=U_(e,L_);return(0,P.jsxs)(`svg`,z_(z_({fill:`currentColor`,fillRule:`evenodd`,height:n,style:z_({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:M_}),(0,P.jsx)(`path`,{d:`M16.804 1.957l7.22 4.105v.087L16.73 10.21l.017-2.117-.821-.03c-1.059-.028-1.611.002-2.268.11-1.064.175-2.038.577-3.147 1.352L8.345 11.03c-.284.195-.495.336-.68.455l-.515.322-.397.234.385.23.53.338c.476.314 1.17.796 2.701 1.866 1.11.775 2.083 1.177 3.147 1.352l.3.045c.694.091 1.375.094 2.825.033l.022-2.159 7.22 4.105v.087L16.589 22l.014-1.862-.635.022c-1.386.042-2.137.002-3.138-.162-1.694-.28-3.26-.926-4.881-2.059l-2.158-1.5a21.997 21.997 0 00-.755-.498l-.467-.28a55.927 55.927 0 00-.76-.43C2.908 14.73.563 14.116 0 14.116V9.888l.14.004c.564-.007 2.91-.622 3.809-1.124l1.016-.58.438-.274c.428-.28 1.072-.726 2.686-1.853 1.621-1.133 3.186-1.78 4.881-2.059 1.152-.19 1.974-.213 3.814-.138l.02-1.907z`})]}))});function K_(e){"@babel/helpers - typeof";return K_=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},K_(e)}function q_(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function J_(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function fv(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var pv=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=dv(e,av);return(0,P.jsxs)(`svg`,sv(sv({height:n,style:sv({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:ev}),(0,P.jsx)(`path`,{d:`M12 22.926c.928 0 1.679-.752 1.679-1.68V6.696h-3.358v14.552c0 .927.751 1.679 1.679 1.679z`,fill:`#F9AB00`}),(0,P.jsx)(`path`,{d:`M18.69 12.005A5.819 5.819 0 0012 10.904l7.188 7.188c.296.296.807.179.933-.22a5.815 5.815 0 00-1.431-5.867z`,fill:`#5BB974`}),(0,P.jsx)(`path`,{d:`M5.31 12.005A5.819 5.819 0 0112 10.904l-7.188 7.188a.562.562 0 01-.933-.22 5.815 5.815 0 011.431-5.867z`,fill:`#129EAF`}),(0,P.jsx)(`path`,{d:`M18.157 6.426c-2.86 0-5.288 1.875-6.157 4.478h11.367a.629.629 0 00.565-.908c-1.08-2.12-3.26-3.57-5.775-3.57z`,fill:`#AF5CF7`}),(0,P.jsx)(`path`,{d:`M13.188 3.384c-2.023 2.024-2.414 5.064-1.188 7.52l8.038-8.039a.629.629 0 00-.242-1.042c-2.264-.735-4.83-.217-6.608 1.561z`,fill:`#FF8BCB`}),(0,P.jsx)(`path`,{d:`M10.812 3.384c2.023 2.024 2.414 5.064 1.188 7.52L3.962 2.865a.629.629 0 01.242-1.042c2.264-.735 4.83-.217 6.608 1.561z`,fill:`#FA7B17`}),(0,P.jsx)(`path`,{d:`M5.843 6.426c2.86 0 5.288 1.875 6.157 4.478H.633a.629.629 0 01-.565-.908c1.08-2.12 3.26-3.57 5.775-3.57z`,fill:`#4285F4`})]}))});function mv(e){"@babel/helpers - typeof";return mv=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},mv(e)}function hv(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function gv(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Pv(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Fv=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Nv(e,Dv);return(0,P.jsxs)(`svg`,kv(kv({fill:`currentColor`,fillRule:`evenodd`,height:n,style:kv({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Sv}),(0,P.jsx)(`path`,{d:`M19.785 0v7.272H22.5V17.62h-2.935V24l-7.037-6.194v6.145h-1.091v-6.152L4.392 24v-6.465H1.5V7.188h2.884V0l7.053 6.494V.19h1.09v6.49L19.786 0zm-7.257 9.044v7.319l5.946 5.234V14.44l-5.946-5.397zm-1.099-.08l-5.946 5.398v7.235l5.946-5.234V8.965zm8.136 7.58h1.844V8.349H13.46l6.105 5.54v2.655zm-8.982-8.28H2.59v8.195h1.8v-2.576l6.192-5.62zM5.475 2.476v4.71h5.115l-5.115-4.71zm13.219 0l-5.115 4.71h5.115v-4.71z`})]}))});function Iv(e){"@babel/helpers - typeof";return Iv=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Iv(e)}function Lv(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Rv(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function ny(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var ry=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=ty(e,Yv);return(0,P.jsxs)(`svg`,Zv(Zv({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Zv({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Wv}),(0,P.jsx)(`path`,{d:`M11.615 0l6.237 6.107c2.382 2.338 2.823 3.743 3.161 6.15-1.197-1.732-1.776-2.02-4.504-2.772C12.48 8.374 11.095 5.933 11.615 0z`}),(0,P.jsx)(`path`,{d:`M9.32 2.122C4.771 6.367 2 9.182 2 13.08c0 5.76 4.288 9.788 9.745 9.918 5.457.13 9.441-5.284 9.095-8.403-.347-3.118-4.418-3.81-4.418-3.81 1.69 3.16-.13 8.098-4.894 8.098-5.154 0-6.8-6.02-4.2-9.008.82 1.617 1.879 2.563 2.674 3.273.717.64 1.219 1.09 1.136 1.664-.173 1.213-1.385.866-1.385.866.346.607 3.6 1.473 4.59-1.342.613-1.741-.423-2.789-1.714-4.096-1.632-1.651-3.672-3.717-3.31-8.118z`})]}))});function iy(e){"@babel/helpers - typeof";return iy=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},iy(e)}function ay(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function oy(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function wy(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Ty=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Cy(e,_y);return(0,P.jsxs)(`svg`,yy(yy({fill:`currentColor`,fillRule:`evenodd`,height:n,style:yy({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:fy}),(0,P.jsx)(`path`,{d:`M7.223 21c4.252 0 7.018-2.22 7.018-5.56 0-2.59-1.682-4.236-4.69-4.918l-1.93-.571c-1.694-.375-2.683-.825-2.45-1.975.194-.957.773-1.497 2.122-1.497 4.285 0 5.873 1.497 5.873 1.497v-3.6S11.62 3 7.293 3C3.213 3 1 5.07 1 8.273c0 2.59 1.534 4.097 4.645 4.812l.334.083c.473.144 1.112.335 1.916.572 1.59.375 1.999.773 1.999 1.966 0 1.09-1.15 1.71-2.67 1.71C2.841 17.416 1 15.231 1 15.231v3.989S2.152 21 7.223 21z`}),(0,P.jsx)(`path`,{d:`M20.374 20.73c1.505 0 2.626-1.073 2.626-2.526 0-1.484-1.089-2.526-2.626-2.526-1.505 0-2.594 1.042-2.594 2.526 0 1.484 1.089 2.526 2.594 2.526z`})]}))});function Ey(e){"@babel/helpers - typeof";return Ey=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Ey(e)}function Dy(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Oy(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Gy(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Ky=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Wy(e,Ry);return(0,P.jsxs)(`svg`,By(By({fill:`currentColor`,fillRule:`evenodd`,height:n,style:By({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:`V0`}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M14.252 8.25h5.624c.088 0 .176.006.26.018l-5.87 5.87a1.889 1.889 0 01-.019-.265V8.25h-2.25v5.623a4.124 4.124 0 004.125 4.125h5.624v-2.25h-5.624c-.09 0-.179-.006-.265-.018l5.874-5.875a1.9 1.9 0 01.02.27v5.623H24v-5.624A4.124 4.124 0 0019.876 6h-5.624v2.25zM0 7.5v.006l7.686 9.788c.924 1.176 2.813.523 2.813-.973V7.5H8.25v6.87L2.856 7.5H0z`})]}))});function qy(e){"@babel/helpers - typeof";return qy=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},qy(e)}function Jy(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Yy(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function pb(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var mb=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=fb(e,ob);return(0,P.jsxs)(`svg`,cb(cb({fill:`currentColor`,fillRule:`evenodd`,height:n,style:cb({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:tb}),(0,P.jsx)(`path`,{d:`M11.995 20.216a1.892 1.892 0 100 3.785 1.892 1.892 0 000-3.785zm0 2.806a.927.927 0 11.927-.914.914.914 0 01-.927.914z`}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M21.687 14.144c.237.038.452.16.605.344a.978.978 0 01-.18 1.3l-8.24 6.082a1.892 1.892 0 00-1.147-1.508l8.28-6.08a.991.991 0 01.682-.138z`}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M10.122 21.842l-8.217-6.066a.952.952 0 01-.206-1.287.978.978 0 011.287-.206l8.28 6.08a1.893 1.893 0 00-1.144 1.479z`}),(0,P.jsx)(`path`,{d:`M4.273 4.475a.978.978 0 01-.965-.965V1.09a.978.978 0 111.943 0v2.42a.978.978 0 01-.978.965zM4.247 13.034a.978.978 0 100-1.956.978.978 0 000 1.956zM4.247 10.19a.978.978 0 100-1.956.978.978 0 000 1.956zM4.247 7.332a.978.978 0 100-1.956.978.978 0 000 1.956z`}),(0,P.jsx)(`path`,{d:`M19.718 7.307a.978.978 0 01-.965-.979v-2.42a.965.965 0 011.93 0v2.42a.964.964 0 01-.965.979zM19.743 13.047a.978.978 0 100-1.956.978.978 0 000 1.956zM19.743 10.151a.978.978 0 100-1.956.978.978 0 000 1.956zM19.743 2.068a.978.978 0 100-1.956.978.978 0 000 1.956z`}),(0,P.jsx)(`path`,{d:`M11.995 15.917a.978.978 0 01-.965-.965v-2.459a.978.978 0 011.943 0v2.433a.976.976 0 01-.978.991zM11.995 18.762a.978.978 0 100-1.956.978.978 0 000 1.956zM11.995 10.64a.978.978 0 100-1.956.978.978 0 000 1.956zM11.995 7.783a.978.978 0 100-1.956.978.978 0 000 1.956z`}),(0,P.jsx)(`path`,{d:`M15.856 10.177a.978.978 0 01-.965-.965v-2.42a.977.977 0 011.702-.763.979.979 0 01.241.763v2.42a.978.978 0 01-.978.965zM15.869 4.913a.978.978 0 100-1.956.978.978 0 000 1.956zM15.869 15.853a.978.978 0 100-1.956.978.978 0 000 1.956zM15.869 12.996a.978.978 0 100-1.956.978.978 0 000 1.956z`}),(0,P.jsx)(`path`,{d:`M8.121 15.853a.978.978 0 100-1.956.978.978 0 000 1.956zM8.121 7.783a.978.978 0 100-1.956.978.978 0 000 1.956zM8.121 4.913a.978.978 0 100-1.957.978.978 0 000 1.957zM8.134 12.996a.978.978 0 01-.978-.94V9.611a.965.965 0 011.93 0v2.445a.966.966 0 01-.952.94z`})]}))});function hb(e){"@babel/helpers - typeof";return hb=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},hb(e)}function gb(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function _b(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Fb(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Ib=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Pb(e,Ob);return(0,P.jsxs)(`svg`,Ab(Ab({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Ab({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Cb}),(0,P.jsx)(`path`,{d:`M5.407 0v.066a.974.974 0 00-.048.245c-.011.11-.016.208-.016.295 0 .339.043.715.128 1.13.097.405.274.912.531 1.524l7.125 16.366L20.011 3.39c.161-.404.333-.846.515-1.327.182-.48.273-.966.273-1.458a1.406 1.406 0 00-.096-.54V0H24v.066c-.204.207-.45.578-.74 1.114-.29.535-.606 1.195-.949 1.982L13.095 24h-1.287L3.075 3.965c-.204-.47-.418-.923-.644-1.36-.214-.437-.418-.83-.61-1.18-.194-.36-.365-.66-.515-.9A5.666 5.666 0 001 .064V0h4.407z`})]}))});function Lb(e){"@babel/helpers - typeof";return Lb=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Lb(e)}function Rb(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function zb(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function rx(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var ix=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=nx(e,Xb);return(0,P.jsxs)(`svg`,Qb(Qb({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Qb({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Gb}),(0,P.jsx)(`path`,{d:`M.958 15.936a.459.459 0 01.459.44v2.729a.46.46 0 01-.918 0v-2.729a.459.459 0 01.459-.44zm4.814-2.035a.46.46 0 01.553.45v4.754a.458.458 0 11-.918 0V15.48L3.74 17.202a.462.462 0 01-.655.016.462.462 0 01-.065-.082L.628 14.67a.459.459 0 01.658-.637l2.124 2.187 2.127-2.188a.46.46 0 01.235-.13zm2.068.004a.46.46 0 01.458.445v4.755a.46.46 0 01-.458.458.459.459 0 01-.458-.458V14.35a.459.459 0 01.458-.445zm1.973 2.014a.46.46 0 01.46.457v2.729a.46.46 0 01-.784.324.46.46 0 01-.134-.324v-2.729a.46.46 0 01.458-.458zm.002-2.045a.458.458 0 01.328.157l2.127 2.19 2.125-2.19a.459.459 0 01.784.318v4.756a.46.46 0 01-.455.458.46.46 0 01-.458-.458V15.48l-1.667 1.723a.46.46 0 01-.65.008l-.005-.005c0-.002-.002-.002-.004-.003l-2.455-2.534a.46.46 0 01-.008-.667.461.461 0 01.338-.128zm6.797 1.206a.46.46 0 01.53.651A1.966 1.966 0 0019.81 18.4a.462.462 0 01.623.18.46.46 0 01-.181.624 2.863 2.863 0 01-1.38.353l-.142-.004a2.88 2.88 0 01-2.393-4.263.461.461 0 01.274-.21zm.864-.931a2.884 2.884 0 013.915 3.914.46.46 0 01-.402.24l-.057-.004a.458.458 0 01-.164-.055.46.46 0 01-.182-.622 1.967 1.967 0 00-2.669-2.67.459.459 0 11-.441-.803zM9.59 6.368c1.481 0 1.696 1.202 1.696 1.654v2.648h-.917v-.432c-.26.346-.792.535-1.36.535-.133 0-1.289-.03-1.384-1.136-.082-.932.675-1.61 2.053-1.61h.691c0-.563-.367-.886-.983-.886-.44.013-.864.174-1.2.458l-.36-.664c.484-.379 1.012-.567 1.764-.567zm4.427.1c1.263 0 2.082.97 2.083 2.15 0 1.181-.824 2.154-2.083 2.154-1.26 0-2.084-.972-2.084-2.152 0-1.18.82-2.153 2.084-2.153zm6.801.015c.68 0 1.202.465 1.197 1.548v2.642H21.1V8.29c0-.312-.002-.98-.63-.98s-.628.667-.628.838v2.524h-.89V8.148c0-.17-.001-.838-.63-.838-.628 0-.628.668-.628.98v2.383h-.917v-4.03h.917V7a1.22 1.22 0 01.947-.516c.398 0 .76.193.982.686a1.321 1.321 0 011.195-.686zm-18.093.872l1.457-1.772H5.32L3.311 8.07l2.14 2.602H4.24L2.725 8.796 1.21 10.672H0L2.138 8.07.13 5.583h1.138l1.458 1.772zm4.149 3.317h-.916V6.644h.916v4.028zm16.99 0h-.916V6.644h.916v4.028zM9.925 8.71c-1.055 0-1.359.412-1.326.742.032.329.324.537.757.537a1.013 1.013 0 001.014-.968l.002-.31h-.447zM14.018 7.3c-.663 0-1.184.487-1.184 1.32 0 .832.52 1.32 1.184 1.32.662 0 1.182-.49 1.182-1.32 0-.832-.52-1.32-1.182-1.32zM6.417 5.001a.568.568 0 01.587.582.588.588 0 01-1.175 0A.57.57 0 016.417 5zm16.991 0a.57.57 0 01.592.582.588.588 0 01-1.174 0 .57.57 0 01.357-.542.572.572 0 01.225-.04z`})]}))});function ax(e){"@babel/helpers - typeof";return ax=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},ax(e)}function ox(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function sx(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Tx(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Ex=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=wx(e,vx);return(0,P.jsxs)(`svg`,bx(bx({fill:`currentColor`,fillRule:`evenodd`,height:n,style:bx({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:px}),(0,P.jsx)(`path`,{d:`M12.105 2L9.927 4.953H.653L2.83 2h9.276zM23.254 19.048L21.078 22h-9.242l2.174-2.952h9.244zM24 2L9.264 22H0L14.736 2H24z`})]}))});function Dx(e){"@babel/helpers - typeof";return Dx=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Dx(e)}function Ox(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function kx(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function qx(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Jx=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Kx(e,Bx);return(0,P.jsxs)(`svg`,Hx(Hx({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Hx({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Fx}),(0,P.jsx)(`path`,{d:`M2.373 4.301L1 7.663a4.608 4.608 0 012.602 2.602c.976 2.422-.18 5.169-2.566 6.145L2.41 19.77c2.096-.867 3.723-2.494 4.554-4.59A8.346 8.346 0 002.374 4.3zM5.916 21.072L8.084 24c1.049-.759 1.988-1.699 2.783-2.71l-2.819-2.242a11.324 11.324 0 01-2.132 2.024zM14.157 12.036c0-4.699-2.277-9.144-6.073-11.928L5.916 3.036c2.891 2.096 4.59 5.458 4.626 9.036A14.81 14.81 0 0016.578 24l2.133-2.928c-2.856-2.132-4.554-5.458-4.554-9.036zM18.82 2.964L16.722 0a14.601 14.601 0 00-2.964 2.82l2.82 2.24a11.256 11.256 0 012.24-2.096zM21.277 14.06c-1.12-2.421-.036-5.313 2.386-6.433l-1.518-3.29a8.457 8.457 0 00-4.193 4.265c-1.916 4.265 0 9.29 4.301 11.17l1.482-3.29a4.862 4.862 0 01-2.458-2.422z`})]}))});function Yx(e){"@babel/helpers - typeof";return Yx=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Yx(e)}function Xx(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Zx(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function hS(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var gS=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=mS(e,cS);return(0,P.jsxs)(`svg`,uS(uS({fill:`currentColor`,fillRule:`evenodd`,height:n,style:uS({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:rS}),(0,P.jsx)(`path`,{d:`M17.583 12.344L14.606 17.5H20.6c.22 0 .424-.117.535-.308l2.799-4.848h-6.351zM23.934 11.656l-2.799-4.848A.616.616 0 0020.6 6.5h-5.994l2.977 5.156h6.35zM8.653 6.5h5.953l-2.997-5.191A.616.616 0 0011.074 1H5.476l3.176 5.5zM4.881 1.343L2.083 6.191a.618.618 0 000 .617l2.997 5.191 2.976-5.156-3.175-5.5zM8.057 17.155L5.081 12l-2.998 5.192a.618.618 0 000 .617l2.798 4.848 3.175-5.5h.001zM5.476 23h5.598c.22 0 .424-.117.535-.308l2.997-5.192H8.653L5.477 23z`})]}))});function _S(e){"@babel/helpers - typeof";return _S=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},_S(e)}function vS(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function yS(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=Array(t);n=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function US(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var WS=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=HS(e,AS),a=IS(il(TS,3),3),o=a[0],s=a[1],c=a[2];return(0,P.jsxs)(`svg`,MS(MS({height:n,style:MS({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:TS}),(0,P.jsx)(`path`,{d:`M7.242 1.613A1.11 1.11 0 018.295.857h6.977L8.03 22.316a1.11 1.11 0 01-1.052.755h-5.43a1.11 1.11 0 01-1.053-1.466L7.242 1.613z`,fill:o.fill}),(0,P.jsx)(`path`,{d:`M18.397 15.296H7.4a.51.51 0 00-.347.882l7.066 6.595c.206.192.477.298.758.298h6.226l-2.706-7.775z`,fill:`#0078D4`}),(0,P.jsx)(`path`,{d:`M15.272.857H7.497L0 23.071h7.775l1.596-4.73 5.068 4.73h6.665l-2.707-7.775h-7.998L15.272.857z`,fill:s.fill}),(0,P.jsx)(`path`,{d:`M17.193 1.613a1.11 1.11 0 00-1.052-.756h-7.81.035c.477 0 .9.304 1.052.756l6.748 19.992a1.11 1.11 0 01-1.052 1.466h-.12 7.895a1.11 1.11 0 001.052-1.466L17.193 1.613z`,fill:c.fill}),(0,P.jsxs)(`defs`,{children:[(0,P.jsxs)(`linearGradient`,{gradientUnits:`userSpaceOnUse`,id:o.id,x1:`8.247`,x2:`1.002`,y1:`1.626`,y2:`23.03`,children:[(0,P.jsx)(`stop`,{stopColor:`#114A8B`}),(0,P.jsx)(`stop`,{offset:`1`,stopColor:`#0669BC`})]}),(0,P.jsxs)(`linearGradient`,{gradientUnits:`userSpaceOnUse`,id:s.id,x1:`14.042`,x2:`12.324`,y1:`15.302`,y2:`15.888`,children:[(0,P.jsx)(`stop`,{stopOpacity:`.3`}),(0,P.jsx)(`stop`,{offset:`.071`,stopOpacity:`.2`}),(0,P.jsx)(`stop`,{offset:`.321`,stopOpacity:`.1`}),(0,P.jsx)(`stop`,{offset:`.623`,stopOpacity:`.05`}),(0,P.jsx)(`stop`,{offset:`1`,stopOpacity:`0`})]}),(0,P.jsxs)(`linearGradient`,{gradientUnits:`userSpaceOnUse`,id:c.id,x1:`12.841`,x2:`20.793`,y1:`1.626`,y2:`22.814`,children:[(0,P.jsx)(`stop`,{stopColor:`#3CCBF4`}),(0,P.jsx)(`stop`,{offset:`1`,stopColor:`#2892DF`})]})]})]}))});function GS(e){"@babel/helpers - typeof";return GS=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},GS(e)}function KS(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function qS(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=Array(t);n=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function _C(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var vC=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=gC(e,iC),a=uC(il($S,3),3),o=a[0],s=a[1],c=a[2];return(0,P.jsxs)(`svg`,oC(oC({height:n,style:oC({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:$S}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M16.233 0c.713 0 1.345.551 1.572 1.329.227.778 1.555 5.59 1.555 5.59v9.562h-4.813L14.645 0h1.588z`,fill:o.fill,fillRule:`evenodd`}),(0,P.jsx)(`path`,{d:`M23.298 7.47c0-.34-.275-.6-.6-.6h-2.835a3.617 3.617 0 00-3.614 3.615v5.996h3.436a3.617 3.617 0 003.613-3.614V7.47z`,fill:s.fill}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M16.233 0a.982.982 0 00-.989.989l-.097 18.198A4.814 4.814 0 0110.334 24H1.6a.597.597 0 01-.567-.794l7-19.981A4.819 4.819 0 0112.57 0h3.679-.016z`,fill:c.fill,fillRule:`evenodd`}),(0,P.jsxs)(`defs`,{children:[(0,P.jsxs)(`linearGradient`,{gradientUnits:`userSpaceOnUse`,id:o.id,x1:`18.242`,x2:`14.191`,y1:`16.837`,y2:`.616`,children:[(0,P.jsx)(`stop`,{stopColor:`#712575`}),(0,P.jsx)(`stop`,{offset:`.09`,stopColor:`#9A2884`}),(0,P.jsx)(`stop`,{offset:`.18`,stopColor:`#BF2C92`}),(0,P.jsx)(`stop`,{offset:`.27`,stopColor:`#DA2E9C`}),(0,P.jsx)(`stop`,{offset:`.34`,stopColor:`#EB30A2`}),(0,P.jsx)(`stop`,{offset:`.4`,stopColor:`#F131A5`}),(0,P.jsx)(`stop`,{offset:`.5`,stopColor:`#EC30A3`}),(0,P.jsx)(`stop`,{offset:`.61`,stopColor:`#DF2F9E`}),(0,P.jsx)(`stop`,{offset:`.72`,stopColor:`#C92D96`}),(0,P.jsx)(`stop`,{offset:`.83`,stopColor:`#AA2A8A`}),(0,P.jsx)(`stop`,{offset:`.95`,stopColor:`#83267C`}),(0,P.jsx)(`stop`,{offset:`1`,stopColor:`#712575`})]}),(0,P.jsxs)(`linearGradient`,{gradientUnits:`userSpaceOnUse`,id:s.id,x1:`19.782`,x2:`19.782`,y1:`.34`,y2:`23.222`,children:[(0,P.jsx)(`stop`,{stopColor:`#DA7ED0`}),(0,P.jsx)(`stop`,{offset:`.08`,stopColor:`#B17BD5`}),(0,P.jsx)(`stop`,{offset:`.19`,stopColor:`#8778DB`}),(0,P.jsx)(`stop`,{offset:`.3`,stopColor:`#6276E1`}),(0,P.jsx)(`stop`,{offset:`.41`,stopColor:`#4574E5`}),(0,P.jsx)(`stop`,{offset:`.54`,stopColor:`#2E72E8`}),(0,P.jsx)(`stop`,{offset:`.67`,stopColor:`#1D71EB`}),(0,P.jsx)(`stop`,{offset:`.81`,stopColor:`#1471EC`}),(0,P.jsx)(`stop`,{offset:`1`,stopColor:`#1171ED`})]}),(0,P.jsxs)(`linearGradient`,{gradientUnits:`userSpaceOnUse`,id:c.id,x1:`18.404`,x2:`3.236`,y1:`.859`,y2:`25.183`,children:[(0,P.jsx)(`stop`,{stopColor:`#DA7ED0`}),(0,P.jsx)(`stop`,{offset:`.05`,stopColor:`#B77BD4`}),(0,P.jsx)(`stop`,{offset:`.11`,stopColor:`#9079DA`}),(0,P.jsx)(`stop`,{offset:`.18`,stopColor:`#6E77DF`}),(0,P.jsx)(`stop`,{offset:`.25`,stopColor:`#5175E3`}),(0,P.jsx)(`stop`,{offset:`.33`,stopColor:`#3973E7`}),(0,P.jsx)(`stop`,{offset:`.42`,stopColor:`#2772E9`}),(0,P.jsx)(`stop`,{offset:`.54`,stopColor:`#1A71EB`}),(0,P.jsx)(`stop`,{offset:`.68`,stopColor:`#1371EC`}),(0,P.jsx)(`stop`,{offset:`1`,stopColor:`#1171ED`})]})]})]}))});function yC(e){"@babel/helpers - typeof";return yC=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},yC(e)}function bC(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function xC(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function zC(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var BC=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=RC(e,MC);return(0,P.jsxs)(`svg`,PC(PC({fill:`currentColor`,fillRule:`evenodd`,height:n,style:PC({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:DC}),(0,P.jsx)(`path`,{d:`M8.859 11.735c1.017-1.71 4.059-3.083 6.202.286 1.579 2.284 4.284 4.397 4.284 4.397s2.027 1.601.73 4.684c-1.24 2.956-5.64 1.607-6.005 1.49l-.024-.009s-1.746-.568-3.776-.112c-2.026.458-3.773.286-3.773.286l-.045-.001c-.328-.01-2.38-.187-3.001-2.968-.675-3.028 2.365-4.687 2.592-4.968.226-.288 1.802-1.37 2.816-3.085zm.986 1.738v2.032h-1.64s-1.64.138-2.213 2.014c-.2 1.252.177 1.99.242 2.148.067.157.596 1.073 1.927 1.342h3.078v-7.514l-1.394-.022zm3.588 2.191l-1.44.024v3.956s.064.985 1.44 1.344h3.541v-5.3h-1.528v3.979h-1.46s-.466-.068-.553-.447v-3.556zM9.82 16.715v3.06H8.58s-.863-.045-1.126-1.049c-.136-.445.02-.959.088-1.16.063-.203.353-.671.951-.85H9.82zm9.525-9.036c2.086 0 2.646 2.06 2.646 2.742 0 .688.284 3.597-2.309 3.655-2.595.057-2.704-1.77-2.704-3.08 0-1.374.277-3.317 2.367-3.317zM4.24 6.08c1.523-.135 2.645 1.55 2.762 2.513.07.625.393 3.486-1.975 4-2.364.515-3.244-2.249-2.984-3.544 0 0 .28-2.797 2.197-2.969zm8.847-1.483c.14-1.31 1.69-3.316 2.931-3.028 1.236.285 2.367 1.944 2.137 3.37-.224 1.428-1.345 3.313-3.095 3.082-1.748-.226-2.143-1.823-1.973-3.424zM9.425 1c1.307 0 2.364 1.519 2.364 3.398 0 1.879-1.057 3.4-2.364 3.4s-2.367-1.521-2.367-3.4C7.058 2.518 8.118 1 9.425 1z`})]}))});function VC(e){"@babel/helpers - typeof";return VC=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},VC(e)}function HC(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function UC(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function sw(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var cw=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=ow(e,ew);return(0,P.jsxs)(`svg`,nw(nw({fill:`currentColor`,fillRule:`evenodd`,height:n,style:nw({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:YC}),(0,P.jsx)(`path`,{d:`M2.316 4.8h14.682v4.8H7.31a.302.302 0 00-.308.3v4.2c0 .171.14.3.308.3h9.688v4.8h-4.686a.302.302 0 00-.308.3v4.2c0 .171.141.3.308.3h4.378a.297.297 0 00.308-.3v-4.5h4.694a.302.302 0 00.308-.3v-4.2c0-.171-.14-.3-.308-.3h-4.694V9.6h4.694A.302.302 0 0022 9.3V5.1c0-.171-.14-.3-.308-.3h-4.694V.3c0-.171-.14-.3-.308-.3H2.316A.31.31 0 002 .3v4.2c0 .171.14.3.316.3z`})]}))});function lw(e){"@babel/helpers - typeof";return lw=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},lw(e)}function uw(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function dw(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function kw(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Aw=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Ow(e,Sw);return(0,P.jsxs)(`svg`,ww(ww({fill:`currentColor`,fillRule:`evenodd`,height:n,style:ww({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:_w}),(0,P.jsx)(`path`,{d:`M13.05 15.513h3.08c.214 0 .389.177.389.394v1.82a1.704 1.704 0 011.296 1.661c0 .943-.755 1.708-1.685 1.708-.931 0-1.686-.765-1.686-1.708 0-.807.554-1.484 1.297-1.662v-1.425h-2.69v4.663a.395.395 0 01-.188.338l-2.69 1.641a.385.385 0 01-.405-.002l-4.926-3.086a.395.395 0 01-.185-.336V16.3L2.196 14.87A.395.395 0 012 14.555L2 14.528V9.406c0-.14.073-.27.192-.34l2.465-1.462V4.448c0-.129.062-.249.165-.322l.021-.014L9.77 1.058a.385.385 0 01.407 0l2.69 1.675a.395.395 0 01.185.336V7.6h3.856V5.683a1.704 1.704 0 01-1.296-1.662c0-.943.755-1.708 1.685-1.708.931 0 1.685.765 1.685 1.708 0 .807-.553 1.484-1.296 1.662v2.311a.391.391 0 01-.389.394h-4.245v1.806h6.624a1.69 1.69 0 011.64-1.313c.93 0 1.685.764 1.685 1.707 0 .943-.754 1.708-1.685 1.708a1.69 1.69 0 01-1.64-1.314H13.05v1.937h4.953l.915 1.18a1.66 1.66 0 01.84-.227c.931 0 1.685.764 1.685 1.707 0 .943-.754 1.708-1.685 1.708-.93 0-1.685-.765-1.685-1.708 0-.346.102-.668.276-.937l-.724-.935H13.05v1.806zM9.973 1.856L7.93 3.122V6.09h-.778V3.604L5.435 4.669v2.945l2.11 1.36L9.712 7.61V5.334h.778V7.83c0 .136-.07.263-.184.335L7.963 9.638v2.081l1.422 1.009-.446.646-1.406-.998-1.53 1.005-.423-.66 1.605-1.055v-1.99L5.038 8.29l-2.26 1.34v1.676l1.972-1.189.398.677-2.37 1.429V14.3l2.166 1.258 2.27-1.368.397.677-2.176 1.311V19.3l1.876 1.175 2.365-1.426.398.678-2.017 1.216 1.918 1.201 2.298-1.403v-5.78l-4.758 2.893-.4-.675 5.158-3.136V3.289L9.972 1.856zM16.13 18.47a.913.913 0 00-.908.92c0 .507.406.918.908.918a.913.913 0 00.907-.919.913.913 0 00-.907-.92zm3.63-3.81a.913.913 0 00-.908.92c0 .508.406.92.907.92a.913.913 0 00.908-.92.913.913 0 00-.908-.92zm1.555-4.99a.913.913 0 00-.908.92c0 .507.407.918.908.918a.913.913 0 00.907-.919.913.913 0 00-.907-.92zM17.296 3.1a.913.913 0 00-.907.92c0 .508.406.92.907.92a.913.913 0 00.908-.92.913.913 0 00-.908-.92z`})]}))});function jw(e){"@babel/helpers - typeof";return jw=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},jw(e)}function Mw(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Nw(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Zw(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Qw=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Xw(e,Ww);return(0,P.jsxs)(`svg`,Kw(Kw({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Kw({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:zw}),(0,P.jsx)(`path`,{d:`M17.113 10.248H14.56l-2.553-3.616-7.963 11.27h2.558l5.405-7.654h2.552l-5.404 7.653h2.565l5.392-7.653L24 20 19.97 20v-2.091l-2.857-4.044-2.842 4.037V20H0L12.008 3l5.105 7.249z`})]}))});function $w(e){"@babel/helpers - typeof";return $w=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},$w(e)}function eT(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function tT(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function yT(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var bT=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=vT(e,fT);return(0,P.jsxs)(`svg`,mT(mT({fill:`currentColor`,fillRule:`evenodd`,height:n,style:mT({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:sT}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M14.121 2.701a9.299 9.299 0 000 18.598V22.7c-5.91 0-10.7-4.791-10.7-10.701S8.21 1.299 14.12 1.299V2.7zm4.752 3.677A7.353 7.353 0 109.42 17.643l-.901 1.074a8.754 8.754 0 01-1.08-12.334 8.755 8.755 0 0112.335-1.08l-.901 1.075zm-2.255.844a5.407 5.407 0 00-5.048 9.563l-.656 1.24a6.81 6.81 0 016.358-12.043l-.654 1.24zM14.12 8.539a3.46 3.46 0 100 6.922v1.402a4.863 4.863 0 010-9.726v1.402z`}),(0,P.jsx)(`path`,{d:`M15.407 10.836a2.24 2.24 0 00-.51-.409 1.084 1.084 0 00-.544-.152c-.255 0-.483.047-.684.14a1.58 1.58 0 00-.84.912c-.074.203-.11.416-.11.631 0 .218.036.43.11.631a1.594 1.594 0 00.84.913c.2.093.43.14.684.14.216 0 .417-.046.602-.135.188-.09.35-.225.475-.392l.928 1.006c-.14.14-.3.261-.482.363a3.367 3.367 0 01-1.083.38c-.17.026-.317.04-.44.04a3.315 3.315 0 01-1.182-.21 2.825 2.825 0 01-.961-.597 2.816 2.816 0 01-.644-.929 2.987 2.987 0 01-.238-1.21c0-.444.08-.847.238-1.21.15-.35.368-.666.643-.929.278-.261.605-.464.962-.596a3.315 3.315 0 011.182-.21c.355 0 .712.068 1.072.204.361.138.685.36.944.649l-.962.97z`})]}))});function xT(e){"@babel/helpers - typeof";return xT=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},xT(e)}function ST(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function CT(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function VT(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var HT=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=BT(e,PT);return(0,P.jsxs)(`svg`,IT(IT({fill:`currentColor`,fillRule:`evenodd`,height:n,style:IT({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:kT}),(0,P.jsx)(`path`,{d:`M16.493 17.4c.135-.52.08-.983-.161-1.338-.215-.328-.592-.519-1.05-.519l-8.663-.109a.148.148 0 01-.135-.082c-.027-.054-.027-.109-.027-.163.027-.082.108-.164.189-.164l8.744-.11c1.05-.054 2.153-.9 2.556-1.937l.511-1.31c.027-.055.027-.11.027-.164C17.92 8.91 15.66 7 12.942 7c-2.503 0-4.628 1.638-5.381 3.903a2.432 2.432 0 00-1.803-.491c-1.21.109-2.153 1.092-2.287 2.32-.027.328 0 .628.054.9C1.56 13.688 0 15.326 0 17.319c0 .19.027.355.027.545 0 .082.08.137.161.137h15.983c.08 0 .188-.055.215-.164l.107-.437`}),(0,P.jsx)(`path`,{d:`M19.238 11.75h-.242c-.054 0-.108.054-.135.109l-.35 1.2c-.134.52-.08.983.162 1.338.215.328.592.518 1.05.518l1.855.11c.054 0 .108.027.135.082.027.054.027.109.027.163-.027.082-.108.164-.188.164l-1.91.11c-1.05.054-2.153.9-2.557 1.937l-.134.355c-.027.055.026.137.107.137h6.592c.081 0 .162-.055.162-.137.107-.41.188-.846.188-1.31-.027-2.62-2.153-4.777-4.762-4.777`})]}))});function UT(e){"@babel/helpers - typeof";return UT=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},UT(e)}function WT(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function GT(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function lE(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var uE=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=cE(e,nE),a=rl(ZT),o=a.id,s=a.fill;return(0,P.jsxs)(`svg`,iE(iE({height:n,style:iE({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:ZT}),(0,P.jsx)(`path`,{d:`M12 0L4.583 6.583c-3.23 2.869-3.23 7.965 0 10.834L12 24l7.417-6.583c3.23-2.869 3.23-7.965 0-10.834L12 0z`,fill:s}),(0,P.jsx)(`defs`,{children:(0,P.jsxs)(`linearGradient`,{gradientUnits:`userSpaceOnUse`,id:o,x1:`18.919`,x2:`4.853`,y1:`5.595`,y2:`18.301`,children:[(0,P.jsx)(`stop`,{stopColor:`#F4BF45`}),(0,P.jsx)(`stop`,{offset:`.35`,stopColor:`#E48047`}),(0,P.jsx)(`stop`,{offset:`.69`,stopColor:`#C73361`}),(0,P.jsx)(`stop`,{offset:`1`,stopColor:`#A42F5F`})]})})]}))});function dE(e){"@babel/helpers - typeof";return dE=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},dE(e)}function fE(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function pE(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function jE(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var ME=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=AE(e,wE);return(0,P.jsxs)(`svg`,EE(EE({height:n,style:EE({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:yE}),(0,P.jsx)(`path`,{d:`M3.294 7.821A2.297 2.297 0 011 5.527a2.297 2.297 0 012.294-2.295A2.297 2.297 0 015.59 5.527 2.297 2.297 0 013.294 7.82zm0-3.688a1.396 1.396 0 000 2.79 1.396 1.396 0 000-2.79zM3.294 14.293A2.297 2.297 0 011 11.998a2.297 2.297 0 012.294-2.294 2.297 2.297 0 012.295 2.294 2.297 2.297 0 01-2.295 2.295zm0-3.688a1.395 1.395 0 000 2.788 1.395 1.395 0 100-2.788zM3.294 20.761A2.297 2.297 0 011 18.467a2.297 2.297 0 012.294-2.295 2.297 2.297 0 012.295 2.295 2.297 2.297 0 01-2.295 2.294zm0-3.688a1.396 1.396 0 000 2.79 1.396 1.396 0 000-2.79zM20.738 7.821a2.297 2.297 0 01-2.295-2.294 2.297 2.297 0 012.294-2.295 2.297 2.297 0 012.295 2.295 2.297 2.297 0 01-2.294 2.294zm0-3.688a1.396 1.396 0 101.395 1.395c0-.77-.626-1.395-1.395-1.395zM20.738 14.293a2.297 2.297 0 01-2.295-2.295 2.297 2.297 0 012.294-2.294 2.297 2.297 0 012.295 2.294 2.297 2.297 0 01-2.294 2.295zm0-3.688c-.769 0-1.395.625-1.395 1.393a1.396 1.396 0 002.79 0c0-.77-.626-1.393-1.395-1.393zM20.738 20.761a2.297 2.297 0 01-2.295-2.294 2.297 2.297 0 012.294-2.295 2.297 2.297 0 012.295 2.295 2.297 2.297 0 01-2.294 2.294zm0-3.688a1.396 1.396 0 101.395 1.395c0-.77-.626-1.395-1.395-1.395zM12.016 11.057a2.297 2.297 0 01-2.294-2.294 2.297 2.297 0 012.294-2.295 2.297 2.297 0 012.295 2.295 2.297 2.297 0 01-2.295 2.294zm0-3.688a1.396 1.396 0 101.395 1.395c0-.77-.625-1.395-1.395-1.395zM12.017 4.589a2.297 2.297 0 01-2.295-2.295A2.297 2.297 0 0112.017 0a2.297 2.297 0 012.294 2.294 2.297 2.297 0 01-2.294 2.295zm0-3.688a1.396 1.396 0 101.395 1.395c0-.77-.626-1.395-1.395-1.395zM12.017 17.529a2.297 2.297 0 01-2.295-2.295 2.297 2.297 0 012.295-2.294 2.297 2.297 0 012.294 2.294 2.297 2.297 0 01-2.294 2.295zm0-3.688a1.396 1.396 0 101.395 1.395c0-.77-.626-1.395-1.395-1.395zM12.016 24a2.297 2.297 0 01-2.294-2.295 2.297 2.297 0 012.294-2.294 2.297 2.297 0 012.295 2.294A2.297 2.297 0 0112.016 24zm0-3.688a1.396 1.396 0 101.395 1.395c0-.77-.625-1.395-1.395-1.395z`,fill:`#2A3275`}),(0,P.jsx)(`path`,{d:`M8.363 8.222a.742.742 0 01-.277-.053l-1.494-.596a.75.75 0 11.557-1.392l1.493.595a.75.75 0 01-.278 1.446h-.001zM8.363 14.566a.743.743 0 01-.277-.053l-1.494-.595a.75.75 0 11.557-1.393l1.493.596a.75.75 0 01-.278 1.445h-.001zM17.124 11.397a.741.741 0 01-.277-.054l-1.493-.595a.75.75 0 11.555-1.392l1.493.595a.75.75 0 01-.278 1.446zM17.124 5.05a.744.744 0 01-.277-.054L15.354 4.4a.75.75 0 01.555-1.392l1.493.596a.75.75 0 01-.278 1.445zM17.124 17.739a.743.743 0 01-.277-.053l-1.494-.596a.75.75 0 11.556-1.392l1.493.596a.75.75 0 01-.278 1.445zM6.91 17.966a.75.75 0 01-.279-1.445l1.494-.595a.749.749 0 11.556 1.392l-1.493.595a.743.743 0 01-.277.053H6.91zM6.91 11.66a.75.75 0 01-.279-1.446l1.494-.595a.75.75 0 01.556 1.392l-1.493.595a.743.743 0 01-.277.053H6.91zM6.91 5.033a.75.75 0 01-.279-1.446l1.494-.595a.75.75 0 01.556 1.392l-1.493.596a.744.744 0 01-.277.053H6.91zM8.363 21.364a.743.743 0 01-.277-.053l-1.494-.596a.75.75 0 01.555-1.392l1.494.595a.75.75 0 01-.278 1.446zM15.63 8.223a.75.75 0 01-.278-1.447l1.494-.595a.75.75 0 01.556 1.393l-1.494.595a.744.744 0 01-.276.054h-.002zM15.63 14.567a.75.75 0 01-.278-1.446l1.494-.596a.75.75 0 01.556 1.394l-1.494.595a.743.743 0 01-.276.053h-.002zM15.63 21.363a.749.749 0 01-.278-1.445l1.494-.595a.75.75 0 11.555 1.392l-1.494.595a.741.741 0 01-.277.053z`,fill:`#5699DB`})]}))});function NE(e){"@babel/helpers - typeof";return NE=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},NE(e)}function PE(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function FE(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function QE(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var $E=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=ZE(e,GE);return(0,P.jsxs)(`svg`,qE(qE({fill:`currentColor`,fillRule:`evenodd`,height:n,style:qE({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:`Exa`}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M3 0h19v1.791L13.892 12 22 22.209V24H3V0zm9.62 10.348l6.589-8.557H6.03l6.59 8.557zM5.138 3.935v7.17h5.52l-5.52-7.17zm5.52 8.96h-5.52v7.17l5.52-7.17zM6.03 22.21l6.59-8.557 6.589 8.557H6.03z`})]}))});function eD(e){"@babel/helpers - typeof";return eD=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},eD(e)}function tD(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function nD(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function yD(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var bD=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=vD(e,fD);return(0,P.jsxs)(`svg`,mD(mD({fill:`currentColor`,fillRule:`evenodd`,height:n,style:mD({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:`Fal`}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M15.477 0c.415 0 .749.338.788.752a7.775 7.775 0 006.985 6.984c.413.04.752.373.752.788v6.952c0 .415-.338.748-.752.788a7.775 7.775 0 00-6.985 6.984c-.04.414-.373.752-.788.752H8.525c-.416 0-.749-.338-.789-.752a7.775 7.775 0 00-6.984-6.984c-.414-.04-.752-.373-.752-.788V8.524c0-.415.338-.748.752-.788A7.775 7.775 0 007.736.752C7.776.338 8.11 0 8.526 0h6.95zM4.819 11.98a7.226 7.226 0 007.223 7.23 7.226 7.226 0 007.223-7.23c0-3.994-3.234-7.23-7.223-7.23a7.227 7.227 0 00-7.223 7.23z`})]}))});function xD(e){"@babel/helpers - typeof";return xD=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},xD(e)}function SD(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function CD(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function VD(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var HD=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=BD(e,PD);return(0,P.jsxs)(`svg`,ID(ID({fill:`currentColor`,fillRule:`evenodd`,height:n,style:ID({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:kD}),(0,P.jsx)(`path`,{d:`M22.724 3.088C21.527 2.376 19.91 2 18.044 2c-2.854 0-6 .877-8.826 2.403l-.02-.007-.004.021c-.855.464-1.684.981-2.462 1.558C2.147 9.376.863 13.412 1.947 15.57.76 17.542.03 19.583 0 22c2.28-4.233 3.648-7.663 11.076-13.438-2.122.443-5.79 2.545-8.258 5.735-.233-1.866 1.28-4.879 4.65-7.379.428-.316.871-.612 1.324-.893-.354 1.071-.24.805-.975 2.307 1.086-1.001 1.8-1.62 2.873-3.335a18.995 18.995 0 014.276-1.465c-.238.767-.69 2.067-1.302 3.095 0 0 1.553-.324 2.837-.25-.701.753-1.333 1.569-1.973 2.403-.876 1.142-1.782 2.322-2.943 3.421-.14.133-.273.253-.408.377-1.784-.167-2.961.483-4.065 1.63.87-.395 2.04-.72 2.772-.524-1.35 1.073-3.477 2.487-5.224 2.37-.332.492-.353.507-.717 1.1 2.835.688 6.395-2.118 8.49-4.103 1.229-1.164 2.165-2.383 3.07-3.56 1.862-2.427 3.471-4.523 7.04-5.32L24 3.846l-1.276-.758z`})]}))});function UD(e){"@babel/helpers - typeof";return UD=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},UD(e)}function WD(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function GD(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function uO(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var dO=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=lO(e,rO);return(0,P.jsxs)(`svg`,aO(aO({fill:`currentColor`,fillRule:`evenodd`,height:n,style:aO({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:ZD}),(0,P.jsx)(`path`,{d:`M3.675 7.386A3.684 3.684 0 007.35 3.693 3.684 3.684 0 003.675 0 3.684 3.684 0 000 3.693a3.684 3.684 0 003.675 3.693zm0 16.614a3.683 3.683 0 003.675-3.693 3.684 3.684 0 00-3.675-3.693A3.683 3.683 0 000 20.307 3.684 3.684 0 003.675 24z`}),(0,P.jsx)(`path`,{d:`M10.338 7.2a8.002 8.002 0 011.146-.114h2.037a2.14 2.14 0 002.136-2.139V2.44c0-1.179-.96-2.139-2.136-2.139h-2.484a2.14 2.14 0 00-2.136 2.14l-.08 1.487a8.001 8.001 0 01-.12.9 5.2 5.2 0 01-.487 1.38s-.327.627-.753 1.068a5 5 0 01-.327.306l-.219.18a4.4 4.4 0 01-1.779.786c-.285.06-.939.066-1.206.072H2.433c-1.179 0-2.136.96-2.136 2.148v2.5c0 1.187.96 2.147 2.136 2.147h2.544a2.15 2.15 0 002.136-2.148v-1.794c-.02-.62.021-1.773.567-2.547.34-.48.88-.906.972-.98a3.58 3.58 0 01.798-.487c.087-.039.36-.147.885-.246V7.2h.003z`}),(0,P.jsx)(`path`,{d:`M21.897.3H19.28c-1.146 0-2.07.927-2.07 2.073V4.14s0 1.227-.3 2.14c-.321.905-1.131 1.727-1.944 2.027-.951.348-2.064.3-2.631.3h-1.59a2.07 2.07 0 00-2.064 2.073v2.634c0 1.146.924 2.073 2.064 2.073h2.622a2.07 2.07 0 002.064-2.073l.02-1.1c-.011-.409.028-1.249.226-1.86.072-.229.219-.649.552-1.108.24-.327.474-.534.71-.753.433-.387.799-.612.9-.666.22-.132.6-.36 1.138-.528.48-.147.84-.174 1.452-.213.36-.027.858-.039 1.458-.006 1.146 0 2.07-.927 2.07-2.073V2.373A2.07 2.07 0 0021.888.3h.009z`})]}))});function fO(e){"@babel/helpers - typeof";return fO=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},fO(e)}function pO(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function mO(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function MO(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var NO=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=jO(e,TO);return(0,P.jsxs)(`svg`,DO(DO({fill:`currentColor`,fillRule:`evenodd`,height:n,style:DO({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:bO}),(0,P.jsx)(`path`,{d:`M12 0c6.63 0 12 5.276 12 11.79-.001 5.067-3.29 9.567-8.175 11.187-.6.118-.825-.25-.825-.56 0-.398.015-1.665.015-3.242 0-1.105-.375-1.813-.81-2.181 2.67-.295 5.475-1.297 5.475-5.822 0-1.297-.465-2.344-1.23-3.169.12-.295.54-1.503-.12-3.125 0 0-1.005-.324-3.3 1.209a11.32 11.32 0 00-3-.398c-1.02 0-2.04.133-3 .398-2.295-1.518-3.3-1.209-3.3-1.209-.66 1.622-.24 2.83-.12 3.125-.765.825-1.23 1.887-1.23 3.169 0 4.51 2.79 5.527 5.46 5.822-.345.294-.66.81-.765 1.577-.69.31-2.415.81-3.495-.973-.225-.354-.9-1.223-1.845-1.209-1.005.015-.405.56.015.781.51.28 1.095 1.327 1.23 1.666.24.663 1.02 1.93 4.035 1.385 0 .988.015 1.916.015 2.196 0 .31-.225.664-.825.56C3.303 21.374-.003 16.867 0 11.791 0 5.276 5.37 0 12 0z`})]}))});function PO(e){"@babel/helpers - typeof";return PO=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},PO(e)}function FO(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function IO(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function ek(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var tk=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=$O(e,qO);return(0,P.jsxs)(`svg`,YO(YO({fill:`currentColor`,fillRule:`evenodd`,height:n,style:YO({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:HO}),(0,P.jsx)(`path`,{d:`M12.036 2c-3.853-.035-7 3-7.036 6.781-.035 3.782 3.055 6.872 6.908 6.907h2.42v-2.566h-2.292c-2.407.028-4.38-1.866-4.408-4.23-.029-2.362 1.901-4.298 4.308-4.326h.1c2.407 0 4.358 1.915 4.365 4.278v6.305c0 2.342-1.944 4.25-4.323 4.279a4.375 4.375 0 01-3.033-1.252l-1.851 1.818A7 7 0 0012.029 22h.092c3.803-.056 6.858-3.083 6.879-6.816v-6.5C18.907 4.963 15.817 2 12.036 2z`})]}))});function nk(e){"@babel/helpers - typeof";return nk=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},nk(e)}function rk(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function ik(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Sk(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Ck=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=xk(e,hk);return(0,P.jsxs)(`svg`,_k(_k({height:n,style:_k({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:uk}),(0,P.jsx)(`path`,{d:`M2.25 11.535c0-3.407 1.847-6.554 4.844-8.258a9.822 9.822 0 019.687 0c2.997 1.704 4.844 4.851 4.844 8.258 0 5.266-4.337 9.535-9.687 9.535S2.25 16.8 2.25 11.535z`,fill:`#FF9D0B`}),(0,P.jsx)(`path`,{d:`M11.938 20.086c4.797 0 8.687-3.829 8.687-8.551 0-4.722-3.89-8.55-8.687-8.55-4.798 0-8.688 3.828-8.688 8.55 0 4.722 3.89 8.55 8.688 8.55z`,fill:`#FFD21E`}),(0,P.jsx)(`path`,{d:`M11.875 15.113c2.457 0 3.25-2.156 3.25-3.263 0-.576-.393-.394-1.023-.089-.582.283-1.365.675-2.224.675-1.798 0-3.25-1.693-3.25-.586 0 1.107.79 3.263 3.25 3.263h-.003z`,fill:`#FF323D`}),(0,P.jsx)(`path`,{d:`M14.76 9.21c.32.108.445.753.767.585.447-.233.707-.708.659-1.204a1.235 1.235 0 00-.879-1.059 1.262 1.262 0 00-1.33.394c-.322.384-.377.92-.14 1.36.153.283.638-.177.925-.079l-.002.003zm-5.887 0c-.32.108-.448.753-.768.585a1.226 1.226 0 01-.658-1.204c.048-.495.395-.913.878-1.059a1.262 1.262 0 011.33.394c.322.384.377.92.14 1.36-.152.283-.64-.177-.925-.079l.003.003zm1.12 5.34a2.166 2.166 0 011.325-1.106c.07-.02.144.06.219.171l.192.306c.069.1.139.175.209.175.074 0 .15-.074.223-.172l.205-.302c.08-.11.157-.188.234-.165.537.168.986.536 1.25 1.026.932-.724 1.275-1.905 1.275-2.633 0-.508-.306-.426-.81-.19l-.616.296c-.52.24-1.148.48-1.824.48-.676 0-1.302-.24-1.823-.48l-.589-.283c-.52-.248-.838-.342-.838.177 0 .703.32 1.831 1.187 2.56l.18.14z`,fill:`#3A3B45`}),(0,P.jsx)(`path`,{d:`M17.812 10.366a.806.806 0 00.813-.8c0-.441-.364-.8-.813-.8a.806.806 0 00-.812.8c0 .442.364.8.812.8zm-11.624 0a.806.806 0 00.812-.8c0-.441-.364-.8-.812-.8a.806.806 0 00-.813.8c0 .442.364.8.813.8zM4.515 13.073c-.405 0-.765.162-1.017.46a1.455 1.455 0 00-.333.925 1.801 1.801 0 00-.485-.074c-.387 0-.737.146-.985.409a1.41 1.41 0 00-.2 1.722 1.302 1.302 0 00-.447.694c-.06.222-.12.69.2 1.166a1.267 1.267 0 00-.093 1.236c.238.533.81.958 1.89 1.405l.24.096c.768.3 1.473.492 1.478.494.89.243 1.808.375 2.732.394 1.465 0 2.513-.443 3.115-1.314.93-1.342.842-2.575-.274-3.763l-.151-.154c-.692-.684-1.155-1.69-1.25-1.912-.195-.655-.71-1.383-1.562-1.383-.46.007-.889.233-1.15.605-.25-.31-.495-.553-.715-.694a1.87 1.87 0 00-.993-.312zm14.97 0c.405 0 .767.162 1.017.46.216.262.333.588.333.925.158-.047.322-.071.487-.074.388 0 .738.146.985.409a1.41 1.41 0 01.2 1.722c.22.178.377.422.445.694.06.222.12.69-.2 1.166.244.37.279.836.093 1.236-.238.533-.81.958-1.889 1.405l-.239.096c-.77.3-1.475.492-1.48.494-.89.243-1.808.375-2.732.394-1.465 0-2.513-.443-3.115-1.314-.93-1.342-.842-2.575.274-3.763l.151-.154c.695-.684 1.157-1.69 1.252-1.912.195-.655.708-1.383 1.56-1.383.46.007.889.233 1.15.605.25-.31.495-.553.718-.694.244-.162.523-.265.814-.3l.176-.012z`,fill:`#FF9D0B`}),(0,P.jsx)(`path`,{d:`M9.785 20.132c.688-.994.638-1.74-.305-2.667-.945-.928-1.495-2.288-1.495-2.288s-.205-.788-.672-.714c-.468.074-.81 1.25.17 1.971.977.721-.195 1.21-.573.534-.375-.677-1.405-2.416-1.94-2.751-.532-.332-.907-.148-.782.541.125.687 2.357 2.35 2.14 2.707-.218.362-.983-.42-.983-.42S2.953 14.9 2.43 15.46c-.52.558.398 1.026 1.7 1.803 1.308.778 1.41.985 1.225 1.28-.187.295-3.07-2.1-3.34-1.083-.27 1.011 2.943 1.304 2.745 2.006-.2.7-2.265-1.324-2.685-.537-.425.79 2.913 1.718 2.94 1.725 1.075.276 3.813.859 4.77-.522zm4.432 0c-.687-.994-.64-1.74.305-2.667.943-.928 1.493-2.288 1.493-2.288s.205-.788.675-.714c.465.074.807 1.25-.17 1.971-.98.721.195 1.21.57.534.377-.677 1.407-2.416 1.94-2.751.532-.332.91-.148.782.541-.125.687-2.355 2.35-2.137 2.707.215.362.98-.42.98-.42S21.05 14.9 21.57 15.46c.52.558-.395 1.026-1.7 1.803-1.308.778-1.408.985-1.225 1.28.187.295 3.07-2.1 3.34-1.083.27 1.011-2.94 1.304-2.743 2.006.2.7 2.263-1.324 2.685-.537.423.79-2.912 1.718-2.94 1.725-1.077.276-3.815.859-4.77-.522z`,fill:`#FFD21E`})]}))});function wk(e){"@babel/helpers - typeof";return wk=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},wk(e)}function Tk(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Ek(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Wk(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Gk=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Uk(e,Lk);return(0,P.jsxs)(`svg`,zk(zk({fill:`currentColor`,fillRule:`evenodd`,height:n,style:zk({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Mk}),(0,P.jsx)(`path`,{d:`M.193 19.503a2.413 2.413 0 00-.186.925c0 1.317 1.112 2.518 2.95 3.437a1.337 1.337 0 001.838-.738l2.049-4.93c.359-.857.642-1.745.846-2.652-3.795.637-6.656 2.092-7.448 3.872l-.032.076-.017.01zm7.49-11.047a15.981 15.981 0 00-.846-2.653L4.79.873a1.34 1.34 0 00-1.84-.738C1.112 1.054 0 2.256 0 3.573c0 .317.064.631.186.924v.01l.032.077c.81 1.78 3.67 3.234 7.466 3.872zM21.049.136c1.838.918 2.95 2.12 2.95 3.436a2.454 2.454 0 01-.196.925l-.027.063c-.785 1.792-3.653 3.254-7.46 3.896.204-.907.487-1.795.846-2.653L19.21.873a1.337 1.337 0 011.839-.738zm-4.722 15.409c.201.906.48 1.793.837 2.65l2.048 4.932a1.338 1.338 0 001.838.738c1.839-.92 2.951-2.12 2.951-3.437a2.446 2.446 0 00-.186-.925l-.027-.062c-.782-1.792-3.66-3.256-7.46-3.896zm-.129-6.04c2.695-.415 4.935-1.223 6.48-2.278L22.24 8.28a9.755 9.755 0 000 7.437l.435 1.048c-1.547-1.055-3.787-1.855-6.479-2.275l-.07-.01A27.196 27.196 0 0012 14.172c-1.377-.002-2.752.1-4.114.307l-.071.01c-2.693.413-4.933 1.222-6.48 2.277l.437-1.05a9.755 9.755 0 000-7.437l-.437-1.052c1.54 1.06 3.78 1.863 6.473 2.278l.071.01c2.734.407 5.513.407 8.246 0l.071-.01z`})]}))});function Kk(e){"@babel/helpers - typeof";return Kk=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Kk(e)}function qk(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Jk(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function fA(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var pA=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=dA(e,aA);return(0,P.jsxs)(`svg`,sA(sA({fill:`currentColor`,fillRule:`evenodd`,height:n,style:sA({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:eA}),(0,P.jsx)(`path`,{d:`M20.713 6.655c-.414-1.426-1.748-2.472-3.357-2.472a3.62 3.62 0 00-1.7.423C14.62 3.046 12.804 2 10.735 2 7.77 2 5.31 4.16 4.943 6.944 2.138 7.39 0 9.728 0 12.58c0 3.14 2.62 5.68 5.862 5.68 1.61 0 3.08-.646 4.138-1.671.276-.267.529-.557.736-.89a5.02 5.02 0 01-.713.845 8.998 8.998 0 00-1.77 5.39V22a16.682 16.682 0 018.666-2.717h.046c3.035 0 5.633-1.871 6.621-4.499A6.599 6.599 0 0024 12.445c0-2.427-1.31-4.565-3.287-5.79zM6.966 12.869a.836.836 0 01-.851.824.81.81 0 01-.805-.824v-2.183a.81.81 0 01.805-.824c.46 0 .85.379.85.824v2.183zm3.011 1.069a.86.86 0 01-.874.846.86.86 0 01-.873-.846v-4.9a.86.86 0 01.873-.846.86.86 0 01.874.846v4.9zm3.104-1.047c0 .445-.414.824-.874.824s-.85-.379-.85-.824v-2.227c0-.446.367-.824.85-.824.46 0 .873.378.873.824v2.227zm3.149 1.069a.86.86 0 01-.874.846.86.86 0 01-.873-.846v-4.9a.86.86 0 01.873-.846.86.86 0 01.874.846v4.9zm3.08-1.091a.836.836 0 01-.85.824.81.81 0 01-.805-.824v-2.183a.81.81 0 01.805-.824c.46 0 .85.379.85.824v2.183z`})]}))});function mA(e){"@babel/helpers - typeof";return mA=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},mA(e)}function hA(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function gA(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function PA(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var FA=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=NA(e,DA);return(0,P.jsxs)(`svg`,kA(kA({fill:`currentColor`,fillRule:`evenodd`,height:n,style:kA({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:SA}),(0,P.jsx)(`path`,{d:`M2 2h20v20H2V2zm1.768 18.237h16.459V3.761H3.768v16.476zm3.515-14.91l3.479 6.176-3.871 7.154h2.493l2.58-4.883 2.747 4.883h2.54L9.82 5.324l-2.538.002z`})]}))});function IA(e){"@babel/helpers - typeof";return IA=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},IA(e)}function LA(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function RA(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function nj(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var rj=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=tj(e,YA);return(0,P.jsxs)(`svg`,ZA(ZA({fill:`currentColor`,fillRule:`evenodd`,height:n,style:ZA({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:WA}),(0,P.jsx)(`path`,{d:`M2.84 2a1.273 1.273 0 100 2.547h14.107a1.273 1.273 0 100-2.547H2.84zM7.935 5.33a1.273 1.273 0 000 2.548H22.04a1.274 1.274 0 000-2.547H7.935zM3.624 9.935c0-.704.57-1.274 1.274-1.274h14.106a1.274 1.274 0 010 2.547H4.898c-.703 0-1.274-.57-1.274-1.273zM1.273 12.188a1.273 1.273 0 100 2.547H15.38a1.274 1.274 0 000-2.547H1.273zM3.624 16.792c0-.704.57-1.274 1.274-1.274h14.106a1.273 1.273 0 110 2.547H4.898c-.703 0-1.274-.57-1.274-1.273zM13.029 18.849a1.273 1.273 0 100 2.547h9.698a1.273 1.273 0 100-2.547h-9.698z`,fillOpacity:`.3`}),(0,P.jsx)(`path`,{d:`M2.84 2a1.273 1.273 0 100 2.547h10.287a1.274 1.274 0 000-2.547H2.84zM7.935 5.33a1.273 1.273 0 000 2.548H18.22a1.274 1.274 0 000-2.547H7.935zM3.624 9.935c0-.704.57-1.274 1.274-1.274h10.286a1.273 1.273 0 010 2.547H4.898c-.703 0-1.274-.57-1.274-1.273zM1.273 12.188a1.273 1.273 0 100 2.547H11.56a1.274 1.274 0 000-2.547H1.273zM3.624 16.792c0-.704.57-1.274 1.274-1.274h10.286a1.273 1.273 0 110 2.547H4.898c-.703 0-1.274-.57-1.274-1.273zM13.029 18.849a1.273 1.273 0 100 2.547h5.78a1.273 1.273 0 100-2.547h-5.78z`})]}))});function ij(e){"@babel/helpers - typeof";return ij=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},ij(e)}function aj(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function oj(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function wj(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Tj=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Cj(e,_j);return(0,P.jsxs)(`svg`,yj(yj({fill:`currentColor`,fillRule:`evenodd`,height:n,style:yj({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:fj}),(0,P.jsx)(`path`,{d:`M10.033 5.807L0 18.193h10.792l4.636-5.724-5.395-6.662zm.651 5.421l-.651 2.553-.652-2.553-2.538.926 3.19-3.938 3.19 3.938-2.539-.926zM18.107 10.918l-5.893 7.275H24l-5.893-7.275zm0 4.683l-.383-1.499-1.49.544 1.873-2.313 1.873 2.313-1.49-.544-.383 1.5z`})]}))});function Ej(e){"@babel/helpers - typeof";return Ej=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Ej(e)}function Dj(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Oj(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Kj(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var qj=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Gj(e,zj);return(0,P.jsxs)(`svg`,Vj(Vj({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Vj({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Pj}),(0,P.jsx)(`path`,{d:`M20 2.306v16.797s4-.242 4-4.815V2.306h-4zM4 22.001V5.204s-4 .242-4 4.816V22h4z`}),(0,P.jsx)(`path`,{d:`M16.318 16.51L11.286 4.94c-.824-1.872-2.168-2.926-4.077-2.926-1.908 0-3.211 1.54-3.211 3.19 0 0 2.405-.333 3.68 2.593l5.036 11.57c.821 1.87 2.168 2.926 4.075 2.926 1.905 0 3.211-1.541 3.211-3.19 0 0-2.406.333-3.682-2.594z`})]}))});function Jj(e){"@babel/helpers - typeof";return Jj=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Jj(e)}function Yj(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Xj(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function mM(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var hM=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=pM(e,sM);return(0,P.jsxs)(`svg`,lM(lM({fill:`currentColor`,fillRule:`evenodd`,height:n,style:lM({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:nM}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M9.167 4.17v5.665L0 19.003h9.167v-5.666l5.666 5.666H24L9.167 4.17z`})]}))});function gM(e){"@babel/helpers - typeof";return gM=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},gM(e)}function _M(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function vM(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function IM(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var LM=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=FM(e,kM);return(0,P.jsxs)(`svg`,jM(jM({fill:`currentColor`,fillRule:`evenodd`,height:n,style:jM({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:wM}),(0,P.jsx)(`path`,{d:`M7.905 1.09c.216.085.411.225.588.41.295.306.544.744.734 1.263.191.522.315 1.1.362 1.68a5.054 5.054 0 012.049-.636l.051-.004c.87-.07 1.73.087 2.48.474.101.053.2.11.297.17.05-.569.172-1.134.36-1.644.19-.52.439-.957.733-1.264a1.67 1.67 0 01.589-.41c.257-.1.53-.118.796-.042.401.114.745.368 1.016.737.248.337.434.769.561 1.287.23.934.27 2.163.115 3.645l.053.04.026.019c.757.576 1.284 1.397 1.563 2.35.435 1.487.216 3.155-.534 4.088l-.018.021.002.003c.417.762.67 1.567.724 2.4l.002.03c.064 1.065-.2 2.137-.814 3.19l-.007.01.01.024c.472 1.157.62 2.322.438 3.486l-.006.039a.651.651 0 01-.747.536.648.648 0 01-.54-.742c.167-1.033.01-2.069-.48-3.123a.643.643 0 01.04-.617l.004-.006c.604-.924.854-1.83.8-2.72-.046-.779-.325-1.544-.8-2.273a.644.644 0 01.18-.886l.009-.006c.243-.159.467-.565.58-1.12a4.229 4.229 0 00-.095-1.974c-.205-.7-.58-1.284-1.105-1.683-.595-.454-1.383-.673-2.38-.61a.653.653 0 01-.632-.371c-.314-.665-.772-1.141-1.343-1.436a3.288 3.288 0 00-1.772-.332c-1.245.099-2.343.801-2.67 1.686a.652.652 0 01-.61.425c-1.067.002-1.893.252-2.497.703-.522.39-.878.935-1.066 1.588a4.07 4.07 0 00-.068 1.886c.112.558.331 1.02.582 1.269l.008.007c.212.207.257.53.109.785-.36.622-.629 1.549-.673 2.44-.05 1.018.186 1.902.719 2.536l.016.019a.643.643 0 01.095.69c-.576 1.236-.753 2.252-.562 3.052a.652.652 0 01-1.269.298c-.243-1.018-.078-2.184.473-3.498l.014-.035-.008-.012a4.339 4.339 0 01-.598-1.309l-.005-.019a5.764 5.764 0 01-.177-1.785c.044-.91.278-1.842.622-2.59l.012-.026-.002-.002c-.293-.418-.51-.953-.63-1.545l-.005-.024a5.352 5.352 0 01.093-2.49c.262-.915.777-1.701 1.536-2.269.06-.045.123-.09.186-.132-.159-1.493-.119-2.73.112-3.67.127-.518.314-.95.562-1.287.27-.368.614-.622 1.015-.737.266-.076.54-.059.797.042zm4.116 9.09c.936 0 1.8.313 2.446.855.63.527 1.005 1.235 1.005 1.94 0 .888-.406 1.58-1.133 2.022-.62.375-1.451.557-2.403.557-1.009 0-1.871-.259-2.493-.734-.617-.47-.963-1.13-.963-1.845 0-.707.398-1.417 1.056-1.946.668-.537 1.55-.849 2.485-.849zm0 .896a3.07 3.07 0 00-1.916.65c-.461.37-.722.835-.722 1.25 0 .428.21.829.61 1.134.455.347 1.124.548 1.943.548.799 0 1.473-.147 1.932-.426.463-.28.7-.686.7-1.257 0-.423-.246-.89-.683-1.256-.484-.405-1.14-.643-1.864-.643zm.662 1.21l.004.004c.12.151.095.37-.056.49l-.292.23v.446a.375.375 0 01-.376.373.375.375 0 01-.376-.373v-.46l-.271-.218a.347.347 0 01-.052-.49.353.353 0 01.494-.051l.215.172.22-.174a.353.353 0 01.49.051zm-5.04-1.919c.478 0 .867.39.867.871a.87.87 0 01-.868.871.87.87 0 01-.867-.87.87.87 0 01.867-.872zm8.706 0c.48 0 .868.39.868.871a.87.87 0 01-.868.871.87.87 0 01-.867-.87.87.87 0 01.867-.872zM7.44 2.3l-.003.002a.659.659 0 00-.285.238l-.005.006c-.138.189-.258.467-.348.832-.17.692-.216 1.631-.124 2.782.43-.128.899-.208 1.404-.237l.01-.001.019-.034c.046-.082.095-.161.148-.239.123-.771.022-1.692-.253-2.444-.134-.364-.297-.65-.453-.813a.628.628 0 00-.107-.09L7.44 2.3zm9.174.04l-.002.001a.628.628 0 00-.107.09c-.156.163-.32.45-.453.814-.29.794-.387 1.776-.23 2.572l.058.097.008.014h.03a5.184 5.184 0 011.466.212c.086-1.124.038-2.043-.128-2.722-.09-.365-.21-.643-.349-.832l-.004-.006a.659.659 0 00-.285-.239h-.004z`})]}))});function RM(e){"@babel/helpers - typeof";return RM=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},RM(e)}function zM(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function BM(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function iN(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var aN=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=rN(e,ZM);return(0,P.jsxs)(`svg`,$M($M({fill:`currentColor`,fillRule:`evenodd`,height:n,style:$M({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:KM}),(0,P.jsx)(`path`,{d:`M22 10.552v2.26h-7.932V22H11.54V10.552H22zM22 2v2.264H4.528V22H2V2h20zm0 4.276V8.54H9.296V22H6.768V6.276H22z`})]}))});function oN(e){"@babel/helpers - typeof";return oN=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},oN(e)}function sN(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function cN(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function EN(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var DN=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=TN(e,yN);return(0,P.jsxs)(`svg`,xN(xN({fill:`currentColor`,fillRule:`evenodd`,height:n,style:xN({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:mN}),(0,P.jsx)(`path`,{d:`M23 23h-1.223V8.028c0-3.118-2.568-5.806-5.744-5.806H8.027c-3.176 0-5.744 2.565-5.744 5.686 0 3.119 2.568 5.684 5.744 5.684h.794c1.346 0 2.445 1.1 2.445 2.444 0 1.346-1.1 2.446-2.445 2.446H1v-1.223h7.761c.671 0 1.223-.551 1.223-1.16 0-.67-.552-1.16-1.223-1.16h-.794C4.177 14.872 1 11.756 1 7.909 1 4.058 4.176 1 8.027 1h8.066C19.88 1 23 4.239 23 8.028V23z`}),(0,P.jsx)(`path`,{d:`M8.884 12.672c1.71.06 3.361 1.588 3.361 3.422 0 1.833-1.528 3.421-3.421 3.421H1v1.223h7.761c2.568 0 4.705-2.077 4.705-4.644 0-.672-.123-1.283-.43-1.894-.245-.551-.67-1.1-1.099-1.528-.489-.429-1.039-.734-1.65-.977-.525-.175-1.048-.193-1.594-.212-.218-.008-.441-.016-.669-.034-.428 0-1.406-.245-1.956-.61a3.369 3.369 0 01-1.223-1.406c-.183-.489-.305-.977-.305-1.528A3.417 3.417 0 017.96 4.482h8.066c1.895 0 3.422 1.65 3.422 3.483v15.032h1.223V8.027c0-2.568-2.077-4.768-4.645-4.768h-8c-2.568 0-4.705 2.077-4.705 4.646 0 .67.123 1.282.43 1.894a4.45 4.45 0 001.099 1.528c.429.428 1.039.734 1.588.976.306.123.611.183.976.246.857.06 1.406.123 1.466.123h.003z`}),(0,P.jsx)(`path`,{d:`M1 23h7.761v-.003c3.85 0 7.03-3.116 7.09-7.026 0-3.79-3.117-6.906-6.967-6.906H8.09c-.672 0-1.222-.552-1.222-1.16 0-.608.487-1.16 1.159-1.16h8.069c.608 0 1.159.611 1.159 1.283v14.97h1.223V8.024c0-1.345-1.1-2.505-2.445-2.505H7.967a2.451 2.451 0 00-2.445 2.445 2.45 2.45 0 002.445 2.445h.794c3.176 0 5.744 2.568 5.744 5.684s-2.568 5.684-5.744 5.684H1V23z`})]}))});function ON(e){"@babel/helpers - typeof";return ON=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},ON(e)}function kN(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function AN(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function JN(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var YN=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=qN(e,VN);return(0,P.jsxs)(`svg`,UN(UN({fill:`currentColor`,fillRule:`evenodd`,height:n,style:UN({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:IN}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M22.956 6.521H12.522c-.577 0-1.044.468-1.044 1.044v3.13c0 .577-.466 1.044-1.043 1.044H1.044c-.577 0-1.044.467-1.044 1.044v4.174C0 17.533.467 18 1.044 18h10.434c.577 0 1.044-.467 1.044-1.043v-3.13c0-.578.466-1.044 1.043-1.044h9.391c.577 0 1.044-.467 1.044-1.044V7.565c0-.576-.467-1.044-1.044-1.044z`})]}))});function XN(e){"@babel/helpers - typeof";return XN=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},XN(e)}function ZN(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function QN(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function gP(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var _P=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=hP(e,lP);return(0,P.jsxs)(`svg`,dP(dP({fill:`currentColor`,fillRule:`evenodd`,height:n,style:dP({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:iP}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M23.252 10.365l-2.843 1.636 2.843 1.631a1.47 1.47 0 01.697.903 1.492 1.492 0 01-.15 1.135c-.202.342-.53.591-.912.693a1.498 1.498 0 01-1.132-.15l-5.09-2.924a1.473 1.473 0 01-.68-.851 1.446 1.446 0 01-.068-.485 1.5 1.5 0 01.745-1.248l5.09-2.921a1.496 1.496 0 012.044.547 1.479 1.479 0 01-.544 2.034zm-2.692 7.927l-5.087-2.92a1.477 1.477 0 00-.867-.195 1.478 1.478 0 00-.982.468c-.257.276-.4.639-.403 1.017v5.847A1.49 1.49 0 0014.718 24c.828 0 1.497-.668 1.497-1.491v-3.27l2.849 1.636a1.493 1.493 0 002.044-.544 1.49 1.49 0 00-.548-2.04zm-5.87-5.719l-2.116 2.102a.42.42 0 01-.265.112h-.621a.427.427 0 01-.265-.112l-2.115-2.102a.42.42 0 01-.11-.262v-.62a.43.43 0 01.11-.265l2.114-2.102a.426.426 0 01.264-.11h.623a.422.422 0 01.265.11l2.116 2.102a.43.43 0 01.109.265v.62a.428.428 0 01-.11.262zM13 11.99a.442.442 0 00-.113-.266l-.612-.607a.431.431 0 00-.266-.11h-.024a.426.426 0 00-.264.11l-.612.607a.436.436 0 00-.107.266v.024c0 .085.047.202.107.262l.612.61c.061.06.179.11.264.11h.024a.434.434 0 00.266-.11l.612-.61a.429.429 0 00.112-.262v-.024zM3.436 5.704l5.089 2.924c.274.157.578.219.868.195.375-.026.726-.194.983-.47.256-.275.4-.64.403-1.017V1.489C10.78.667 10.11 0 9.284 0c-.829 0-1.498.667-1.498 1.49v3.27l-2.85-1.639a1.496 1.496 0 00-2.045.546 1.489 1.489 0 00.546 2.037zm11.17 3.119c.29.024.594-.038.866-.195l5.087-2.923a1.474 1.474 0 00.697-.903 1.496 1.496 0 00-.149-1.135 1.496 1.496 0 00-2.044-.545L16.215 4.76V1.489C16.215.667 15.546 0 14.718 0c-.83 0-1.497.667-1.497 1.49v5.845a1.491 1.491 0 001.385 1.487zm-5.213 6.354a1.479 1.479 0 00-.868.194l-5.089 2.92a1.476 1.476 0 00-.696.905 1.498 1.498 0 00.148 1.135 1.496 1.496 0 002.044.543l2.851-1.636v3.27c0 .825.67 1.491 1.498 1.491.826 0 1.496-.667 1.496-1.49v-5.847a1.5 1.5 0 00-.401-1.017 1.477 1.477 0 00-.982-.468zm-1.38-2.74c.05-.156.072-.32.068-.484a1.497 1.497 0 00-.751-1.248l-5.084-2.92a1.499 1.499 0 00-2.045.547 1.481 1.481 0 00.549 2.034l2.841 1.636L.75 13.633a1.47 1.47 0 00-.698.903 1.492 1.492 0 00.15 1.135c.202.343.53.592.912.693.382.102.789.048 1.132-.15l5.086-2.924c.345-.195.577-.505.684-.852z`})]}))});function vP(e){"@babel/helpers - typeof";return vP=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},vP(e)}function yP(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function bP(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function RP(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var zP=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=LP(e,jP);return(0,P.jsxs)(`svg`,NP(NP({height:n,style:NP({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:EP}),(0,P.jsx)(`path`,{d:`M23.197 4.503A6 6 0 0015 2.307a5.973 5.973 0 00-2.995 4.933l5.996.008v.515h-5.996c.039.937.298 1.87.8 2.74a6 6 0 1010.39-6z`,fill:`#EF2CC1`}),(0,P.jsx)(`path`,{d:`M.805 4.5A6 6 0 003 12.697a5.972 5.972 0 005.77.127L5.779 7.627l.446-.257 2.997 5.192A6 6 0 10.804 4.5z`,fill:`#CAAEF5`}),(0,P.jsx)(`path`,{d:`M12 23.894a6 6 0 005.999-6c0-2.13-1.1-3.996-2.775-5.06l-3.005 5.189-.444-.258 2.997-5.192A6 6 0 1012 23.894z`,fill:`#FC4C02`})]}))});function BP(e){"@babel/helpers - typeof";return BP=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},BP(e)}function VP(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function HP(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function oF(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var sF=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=aF(e,$P);return(0,P.jsxs)(`svg`,tF(tF({fill:`currentColor`,fillRule:`evenodd`,height:n,style:tF({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:JP}),(0,P.jsx)(`path`,{d:`M12 0l12 20.785H0L12 0z`})]}))});function cF(e){"@babel/helpers - typeof";return cF=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},cF(e)}function lF(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function uF(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function OF(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var kF=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=DF(e,xF);return(0,P.jsxs)(`svg`,CF(CF({height:n,style:CF({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:gF}),(0,P.jsx)(`path`,{d:`M0 4.973h9.324V23L0 4.973z`,fill:`#FDB515`}),(0,P.jsx)(`path`,{d:`M13.986 4.351L22.378 0l-6.216 23H9.324l4.662-18.649z`,fill:`#30A2FF`})]}))});function AF(e){"@babel/helpers - typeof";return AF=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},AF(e)}function jF(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function MF(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function XF(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var ZF=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=YF(e,UF);return(0,P.jsxs)(`svg`,GF(GF({height:n,style:GF({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:RF}),(0,P.jsx)(`path`,{d:`M19.44 10.153l-2.936 11.586a.215.215 0 00.214.261h5.87a.215.215 0 00.214-.261l-2.95-11.586a.214.214 0 00-.412 0zM3.28 12.778l-2.275 8.96A.214.214 0 001.22 22h4.532a.212.212 0 00.214-.165.214.214 0 000-.097l-2.276-8.96a.214.214 0 00-.41 0z`,fill:`#00E5E5`}),(0,P.jsx)(`path`,{d:`M7.29 5.359L3.148 21.738a.215.215 0 00.203.261h8.29a.214.214 0 00.215-.261L7.7 5.358a.214.214 0 00-.41 0z`,fill:`#006EFF`}),(0,P.jsx)(`path`,{d:`M14.44.15a.214.214 0 00-.41 0L8.366 21.739a.214.214 0 00.214.261H19.9a.216.216 0 00.171-.078.214.214 0 00.044-.183L14.439.15z`,fill:`#006EFF`}),(0,P.jsx)(`path`,{d:`M10.278 7.741L6.685 21.736a.214.214 0 00.214.264h7.17a.215.215 0 00.214-.264L10.688 7.741a.214.214 0 00-.41 0z`,fill:`#00E5E5`})]}))});function QF(e){"@babel/helpers - typeof";return QF=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},QF(e)}function $F(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function eI(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function vI(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var yI=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=_I(e,dI);return(0,P.jsxs)(`svg`,pI(pI({fill:`currentColor`,fillRule:`evenodd`,height:n,style:pI({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:oI}),(0,P.jsx)(`path`,{d:`M6.469 8.776L16.512 23h-4.464L2.005 8.776H6.47zm-.004 7.9l2.233 3.164L6.467 23H2l4.465-6.324zM22 2.582V23h-3.659V7.764L22 2.582zM22 1l-9.952 14.095-2.233-3.163L17.533 1H22z`})]}))});function bI(e){"@babel/helpers - typeof";return bI=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},bI(e)}function xI(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function SI(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function BI(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var VI=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=zI(e,NI);return(0,P.jsxs)(`svg`,FI(FI({fill:`currentColor`,fillRule:`evenodd`,height:n,style:FI({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:OI}),(0,P.jsx)(`path`,{d:`M5.223 9.692c.652 1.795 1.925 3.376 3.396 4.573 1.482 1.229 3.254 2.17 5.122 2.653a9.99 9.99 0 002.033.302c1.302.05 2.713-.206 3.758-1.04 1.297-1.036 1.651-2.625 1.318-4.21-.209-.993-.641-1.93-1.205-2.787a10.284 10.284 0 00-.366-.525.008.008 0 01.005-.007h.004c.002 0 .004 0 .006.002l.394.405a17.227 17.227 0 012.484 3.262c.579.993 1.023 2.046 1.255 3.144.369 1.747.07 3.546-1.306 4.777-.724.648-1.655 1.041-2.59 1.235-1.297.267-2.649.228-3.965.007-.669-.112-1.315-.26-1.937-.443-2.576-.756-5.012-2.051-7.143-3.677a20.968 20.968 0 01-3.484-3.296C1.949 12.813 1.046 11.396.487 9.853.12 8.845-.087 7.725.035 6.663c.267-2.306 1.98-3.654 4.174-4.06 1.265-.234 2.594-.186 3.879.037a17.71 17.71 0 013.978 1.192v.004a.006.006 0 01-.004.004h-.004a8.907 8.907 0 00-2.869-.29c-.807.048-1.666.263-2.357.656-1.034.588-1.67 1.463-1.907 2.625a4.567 4.567 0 00-.069 1.1c.025.58.163 1.198.367 1.761z`}),(0,P.jsx)(`path`,{d:`M18.02 7.235a.05.05 0 01-.007.03c-.461.916-.923 1.832-1.386 2.747-.424.837-.745 1.437-.965 1.8a17.877 17.877 0 01-2.98 3.707.027.027 0 01-.03.005 12.678 12.678 0 01-4.205-2.777c-.14-.14-.28-.288-.42-.447a.024.024 0 01-.005-.013c0-.005 0-.01.003-.014a17.718 17.718 0 011.68-2.379 18.27 18.27 0 012.7-2.606c.408-.32 1.39-1.094 2.95-2.323L21.652.002a.008.008 0 01.01 0 .01.01 0 01.004.005.01.01 0 010 .006l-3.648 7.222z`}),(0,P.jsx)(`path`,{d:`M2.027 24c.002 0 .004 0 .005-.002l5.843-4.58a.02.02 0 00.008-.017.02.02 0 00-.01-.016 26.743 26.743 0 01-2.584-1.842h-.006a.014.014 0 00-.005.002.012.012 0 00-.004.005L2.02 23.987a.01.01 0 000 .006c0 .002 0 .004.002.005a.009.009 0 00.006.002z`})]}))});function HI(e){"@babel/helpers - typeof";return HI=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},HI(e)}function UI(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function WI(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function cL(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var lL=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=sL(e,tL);return(0,P.jsxs)(`svg`,rL(rL({fill:`currentColor`,fillRule:`evenodd`,height:n,style:rL({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:XI}),(0,P.jsx)(`path`,{d:`M11.991 23.503a.24.24 0 00-.244.248.24.24 0 00.244.249.24.24 0 00.245-.249.24.24 0 00-.22-.247l-.025-.001zM9.671 5.365a1.697 1.697 0 011.099 2.132l-.071.172-.016.04-.018.054c-.07.16-.104.32-.104.498-.035.71.47 1.279 1.186 1.314h.366c1.309.053 2.338 1.173 2.286 2.523-.052 1.332-1.152 2.38-2.478 2.327h-.174c-.715.018-1.274.64-1.239 1.368 0 .124.018.23.053.337.209.373.54.658.96.8.75.23 1.517-.125 1.9-.782l.018-.035c.402-.64 1.17-.96 1.92-.711.854.284 1.378 1.226 1.099 2.167a1.661 1.661 0 01-2.077 1.102 1.711 1.711 0 01-.907-.711l-.017-.035c-.2-.323-.463-.58-.851-.711l-.056-.018a1.646 1.646 0 00-1.954.746 1.66 1.66 0 01-1.065.764 1.677 1.677 0 01-1.989-1.279c-.209-.906.332-1.83 1.257-2.043a1.51 1.51 0 01.296-.035h.018c.68-.071 1.151-.622 1.116-1.333a1.307 1.307 0 00-.227-.693 2.515 2.515 0 01-.366-1.403 2.39 2.39 0 01.366-1.208c.14-.195.21-.444.227-.693.018-.71-.506-1.261-1.186-1.332l-.07-.018a1.43 1.43 0 01-.299-.07l-.05-.019a1.7 1.7 0 01-1.047-2.114 1.68 1.68 0 012.094-1.101zm-5.575 10.11c.26-.264.639-.367.994-.27.355.096.633.379.728.74.095.362-.007.748-.267 1.013-.402.41-1.053.41-1.455 0a1.062 1.062 0 010-1.482zm14.845-.294c.359-.09.738.024.992.297.254.274.344.665.237 1.025-.107.36-.396.634-.756.718-.551.128-1.1-.22-1.23-.781a1.05 1.05 0 01.757-1.26zm-.064-4.39c.314.32.49.753.49 1.206 0 .452-.176.886-.49 1.206-.315.32-.74.5-1.185.5-.444 0-.87-.18-1.184-.5a1.727 1.727 0 010-2.412 1.654 1.654 0 012.369 0zm-11.243.163c.364.484.447 1.128.218 1.691a1.665 1.665 0 01-2.188.923c-.855-.36-1.26-1.358-.907-2.228a1.68 1.68 0 011.33-1.038c.593-.08 1.183.169 1.547.652zm11.545-4.221c.368 0 .708.2.892.524.184.324.184.724 0 1.048a1.026 1.026 0 01-.892.524c-.568 0-1.03-.47-1.03-1.048 0-.579.462-1.048 1.03-1.048zm-14.358 0c.368 0 .707.2.891.524.184.324.184.724 0 1.048a1.026 1.026 0 01-.891.524c-.569 0-1.03-.47-1.03-1.048 0-.579.461-1.048 1.03-1.048zm10.031-1.475c.925 0 1.675.764 1.675 1.706s-.75 1.705-1.675 1.705-1.674-.763-1.674-1.705c0-.942.75-1.706 1.674-1.706zm-2.626-.684c.362-.082.653-.356.761-.718a1.062 1.062 0 00-.238-1.028 1.017 1.017 0 00-.996-.294c-.547.14-.881.7-.752 1.257.13.558.675.907 1.225.783zm0 16.876c.359-.087.644-.36.75-.72a1.062 1.062 0 00-.237-1.019 1.018 1.018 0 00-.985-.301 1.037 1.037 0 00-.762.717c-.108.361-.017.754.239 1.028.245.263.606.377.953.305l.043-.01zM17.19 3.5a.631.631 0 00.628-.64c0-.355-.279-.64-.628-.64a.631.631 0 00-.628.64c0 .355.28.64.628.64zm-10.38 0a.631.631 0 00.628-.64c0-.355-.28-.64-.628-.64a.631.631 0 00-.628.64c0 .355.279.64.628.64zm-5.182 7.852a.631.631 0 00-.628.64c0 .354.28.639.628.639a.63.63 0 00.627-.606l.001-.034a.62.62 0 00-.628-.64zm5.182 9.13a.631.631 0 00-.628.64c0 .355.279.64.628.64a.631.631 0 00.628-.64c0-.355-.28-.64-.628-.64zm10.38.018a.631.631 0 00-.628.64c0 .355.28.64.628.64a.631.631 0 00.628-.64c0-.355-.279-.64-.628-.64zm5.182-9.148a.631.631 0 00-.628.64c0 .354.279.639.628.639a.631.631 0 00.628-.64c0-.355-.28-.64-.628-.64zm-.384-4.992a.24.24 0 00.244-.249.24.24 0 00-.244-.249.24.24 0 00-.244.249c0 .142.122.249.244.249zM11.991.497a.24.24 0 00.245-.248A.24.24 0 0011.99 0a.24.24 0 00-.244.249c0 .133.108.236.223.247l.021.001zM2.011 6.36a.24.24 0 00.245-.249.24.24 0 00-.244-.249.24.24 0 00-.244.249.24.24 0 00.244.249zm0 11.263a.24.24 0 00-.243.248.24.24 0 00.244.249.24.24 0 00.244-.249.252.252 0 00-.244-.248zm19.995-.018a.24.24 0 00-.245.248.24.24 0 00.245.25.24.24 0 00.244-.25.252.252 0 00-.244-.248z`})]}))});function uL(e){"@babel/helpers - typeof";return uL=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},uL(e)}function dL(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function fL(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function AL(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var jL=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=kL(e,CL);return(0,P.jsxs)(`svg`,TL(TL({fill:`currentColor`,fillRule:`evenodd`,height:n,style:TL({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:vL}),(0,P.jsx)(`path`,{d:`M5 0h5v24H5V0zM14 0h5v24h-5V0z`})]}))});function ML(e){"@babel/helpers - typeof";return ML=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},ML(e)}function NL(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function PL(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function QL(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var $L=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=ZL(e,GL);return(0,P.jsxs)(`svg`,qL(qL({fill:`currentColor`,fillRule:`evenodd`,height:n,style:qL({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:BL}),(0,P.jsx)(`path`,{d:`M11.823 22.145c.127.192.217.402.276.627.099.336.037.713-.377.725-.279.028-.506-.126-.54-.397-.066-.39.105-.75.299-1.083l.011-.018c.025-.037.057-.073.095-.078h.008c.074-.007.159.122.228.224zm1.596-1.247c.75.31.95 1.246-.084 1.256h-.015c-.52.017-.906-.472-.587-.92.15-.212.417-.42.686-.336zm-3.989-.594c.256.033.512.1.762.16l.037.011c.21.058.458.11.55.297.26.585-.47 1.093-.976 1.103h-.028v-.001c-.413.01-.809-.126-.942-.535-.11-.28-.085-.603.128-.827l.014-.015c.117-.124.282-.212.455-.193zm6.094.237v.014c-.004.302-.148.608-.42.751-.186.093-.417-.01-.588-.15-.171-.147-.283-.356-.382-.568l-.04-.087c-.193-.405.088-.552.405-.627l.04-.01c.058-.012.116-.022.173-.032l.042-.008c.578-.105.792.15.77.717zM2.827 2.991c1.15.86 2.286 1.734 3.376 2.67 2.476 2.166 3.346 5.568 3.452 8.766.048 1.103.108 2.205.199 3.306l.004.04c.019.182.078.563.25.522l.007-.003c.057-.04.086-.123.109-.192.114-.336.194-.693.323-1.026.09-.216.204-.344.377-.411.3-.12.702-.062.87.246.037.05.073.123.11.202l.036.078.02.044.015.03.013.026.013.026c.1.189.223.344.42.264.117-.059.221-.19.319-.283.2-.22.505-.292.713-.053.524.616.5 1.486 0 2.106-.451.585-1.182.7-1.862.586l-.054-.01c-.972-.162-1.555-.96-2.015-1.779l-.03-.055a141.48 141.48 0 00-1.583-2.577l-.03-.045c-.116-.165-.264-.35-.43-.445-.07-.015-.006.127.009.173.467 1.253 1.07 2.425 1.961 3.427l.011.02c.086.168-.045.122-.19.041l-.016-.01-.027-.015-.019-.011a3.561 3.561 0 01-.126-.082l-.075-.05a4.532 4.532 0 01-.78-.67c-.772-.87-1.42-1.838-2.054-2.816l-.375-.58-.165-.255a32.793 32.793 0 00-1.11-1.623c-.415-.567-.89-1.092-1.298-1.666-.774-1.071-1.251-2.312-1.519-3.61-.137-.56-.297-1.114-.438-1.672l-.06-.24c-.094-.38-.166-.784-.129-1.173.083-.894.815-1.97 1.778-1.222zm18.27 14.434c-1.048 1.176-3.299 2.527-4.609 2.154l.002-.002c.014-.019.125-.043.177-.055.838-.192 1.555-.639 2.285-1.058.556-.295 1.048-.682 1.525-1.1l.04-.032c.106-.089.223-.18.352-.214.226-.053.428.11.228.307zM2.012 11.4c1.647.403 2.589 1.718 3.455 3.08l.108.17.226.358c.25.398.502.79.77 1.155.213.305.442.598.666.894.048.058.115.21.05.216h-.011c-.051 0-.127-.062-.172-.106a41.465 41.465 0 01-.727-.698l-.24-.236a26.247 26.247 0 00-1.103-1.032c-.165-.079-.093.137-.027.238.187.286.463.497.695.747.353.352.711.7 1.094 1.018.805.66 1.68 1.232 2.534 1.832l.056.042c.099.074.211.172.205.28-.005.288-1.397.287-1.663.275H7.92c-1.797-.073-3.27-1.012-4.66-2.055l-.138-.105-.097-.073a68.65 68.65 0 01-.287-.22l-.501-.387c-.286-.224-.573-.443-.848-.681C.464 15.3.032 14.098.01 12.882c-.117-1.32.826-1.809 2.003-1.482zm20.708.695l.064.014c.369.078.673.288.899.618.253.37.387.827.28 1.26-.266.87-.806 1.621-1.446 2.26-.15.147-.309.283-.47.418-.178.135-.345.331-.571.376-.19.034-.402-.135-.597-.129-.158 0-.29.092-.41.193-.806.767-1.78 1.286-2.772 1.765l-.186.089-.186.089-.248.117-.017.007c-.189.076-.786.402-.87.234l-.004-.007c-.034-.07.03-.211.091-.298.968-1.244 1.618-2.666 2.246-4.097l.213-.486.255-.576.123-.26c.19-.4.397-.807.726-1.098.77-.65 1.924-.691 2.88-.49zM21.197 4.78c.161.335.22.69.327 1.054.022.084.04.171.04.257v.012a.977.977 0 01-.027.217l-.073.3a7.983 7.983 0 01-.331 1.104c-.32.797-.632 1.597-.942 2.399l-.373.962c-.688 1.776-1.381 3.551-2.15 5.292-.453.953-1.084 1.814-1.791 2.59-.197.227-.453.404-.777.323-.51-.124-.59-.456-.416-.918.187-.546.42-1.08.567-1.636.286-1.186.382-2.408.2-3.628-.094-.72-.458-.813-.943-1.199a.586.586 0 01-.207-.52c.034-.337.158-.684.264-1.01l.015-.046c.353-1.092 1.013-2.015 1.707-2.912l.08-.103.08-.103.162-.206c.12-.154.241-.308.36-.463.222-.296.529-.5.84-.688.48-.32.792-.816 1.258-1.146.566-.437 1.708-.666 2.13.068zM11.21 18.728c.007.54.965.728.996.052v-.013c.037-.614-.95-.599-.996-.04zm-5.78-.884h-.002c.8.623 1.737 1.182 2.757 1.33 0-.02-.09-.059-.123-.076-.876-.411-1.752-.862-2.632-1.254zM.76 14.145l.009.02c.88 1.917 2.523 3.067 4.354 3.903l.055.026.001-.001-.253-.159-.152-.095-.1-.064-.096-.06c-1.466-.93-2.883-1.934-3.767-3.51l-.049-.088c-.048-.083-.047-.07-.002.028zm14.154-1.55c.293.401.325.933.331 1.411v.013a7.8 7.8 0 01-.267 1.831c-.028.081-.05.171-.074.261l-.011.042-.012.04c-.052.18-.122.351-.274.444-.317.166-.444-.222-.414-.482.028-1 .078-2.002.128-3 .016-.199.013-.403.06-.6.02-.077.055-.143.106-.173.163-.077.33.093.427.212zm1.373-9.036c.609.393.956 1.003 1.052 1.716l.005.034a.666.666 0 01-.184.569c-2.37 2.286-3.416 5.563-3.605 8.805l-.026.204c-.045.34-.094.68-.165 1.014-.09.377-.438.46-.66.124-.112-.17-.122-.388-.138-.587a98.335 98.335 0 01-.098-1.872c-.044-.646.108-.898.588-1.298.259-.263.3-.595.232-.955l-.022-.102c-.077-.352-.167-.715-.4-.996-.362-.432-1.06-.554-1.495-.158-.556.515-.644 1.344-.361 2.021l.01.025c.232.546.445 1.108.533 1.695l.009.061c.064.427.05.867-.01 1.293l-.013.096c-.026.193-.042.423-.163.554-.089.1-.257.129-.389.082-.173-.055-.201-.263-.203-.427-.002-.637-.154-1.246-.32-1.852l-.084-.304a17.057 17.057 0 01-.174-.669l-.22-.949c-.2-.87-.396-1.742-.565-2.62-.504-1.985.138-3.798 1.852-4.934.37-.228.792-.434 1.236-.434.573-.004 1.156.34 1.4.88.082.188.093.394.086.597V5.2c-.031.816-.071 1.633-.11 2.45l-.02.412-.013.274c-.023.458-.042.915-.036 1.374.003.113-.005.24.016.347.01.047.023.07.037.06l.003-.001-.002-.002c.028-.024.05-.149.054-.208.019-.184.038-.369.054-.553.102-1.29.162-2.582.28-3.87.075-.899.953-2.621 2.03-1.925zM1.84 3.237c-.33.095-.365.509-.32.804l.02.167c.045.39.099.78.187 1.16l.054.224c.05.203.093.407.078.617-.03.253-.088.499-.007.746.177.7.449 1.363.78 1.999l.014.02c.107.15.073-.046.033-.135l-.195-.482c-.065-.16-.13-.321-.19-.483l-.082-.217c-.165-.446-.307-.895-.203-1.38.082-.268.08-.537-.037-.796-.228-.605-.363-1.25-.23-1.89.23-.935 1.874.88 2.082 1.23.205.29.379.614.512.948.257.655.505 1.304.743 1.964.849 2.276 1.312 4.665 1.713 7.065l.001.001a1.37 1.37 0 00-.011-.274l-.002-.012c-.07-.573-.135-1.15-.209-1.722-.245-2.102-.837-4.132-1.562-6.113l-.037-.103a6.717 6.717 0 00-1.063-1.937l-.023-.027C3.506 4.15 2.44 3.075 1.84 3.237zm5.688 10.665c.119.666.373.617.964.656h.013c.317.03.503-.115.559-.422l.003-.019c.146-.84-.385-1.915-1.262-2.068-.781-.079-.347 1.417-.277 1.853zm.67-1.185c.214.21.4.555.353.899l-.003.011c-.066.216-.397.167-.52.054a.392.392 0 01-.088-.172c-.07-.245-.134-.507-.127-.764.023-.264.264-.158.385-.028zm4.026-2.251c.4.147.491.734.273 1.078-.328.499-1.009.373-.966-.299v-.015c.006-.369.274-.886.693-.764zm7.163-5.23v.01c-.014.173.01.355.028.529.148 1.259.17 2.518-.015 3.775-.04.28-.093.555-.15.832-.024.175-.108.41-.094.571l.002.002c.01.009.036-.032.063-.101.054-.14.092-.3.126-.448.364-1.723.499-3.504.304-5.255-.075-.246-.25-.12-.264.085zm-.962.684c.271.565.321 1.175.35 1.784l.011.278c.008.172.016.342.03.511h.001c.14-.862.114-1.766-.35-2.544l-.02-.035c-.053-.084-.064-.09-.022.006zm2.225-.994c-.021.01-.02.095-.024.133-.027.705-.073 1.37-.086 2.033h.001l.054-.243c.124-.57.231-1.16.103-1.79l-.009-.036c-.01-.04-.023-.09-.039-.097zm-12.524-2.2c.281.257.521.567.68.917.282.646-.196 1.064-.811 1.063h-.032c-.55.015-1.096-.403-1.18-.955l-.002-.02c-.11-.691.705-1.576 1.345-1.005zm5.76-1.494l.007.012c.094.15.17.315.247.475l.017.036c.064.136.131.253.098.395l-.002.01v.001l-.017.064c-.037.139-.087.302-.187.404-.227.264-.684.22-.867-.073l-.009-.015c-.239-.394-.148-1.039.19-1.344.147-.133.41-.141.523.035zM20.337.5c.96.022.681 1.21-.15 1.16a.568.568 0 01-.493-.65c.035-.33.279-.505.61-.51h.033z`})]}))});function eR(e){"@babel/helpers - typeof";return eR=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},eR(e)}function tR(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function nR(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function bR(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var xR=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=yR(e,pR);return(0,P.jsxs)(`svg`,hR(hR({fill:`currentColor`,fillRule:`evenodd`,height:n,style:hR({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:cR}),(0,P.jsx)(`path`,{d:`M19.667 8.275c0-4.57-4.15-8.275-9.27-8.275-1.774 0-3.213 3.705-3.213 8.275 0 1.143.09 2.233.253 3.224H4.29L1 23h9.4v-6.447c5.117 0 9.266-3.707 9.266-8.275l.001-.002zm-9.27-6.76c.93 0 1.682 3.028 1.682 6.76 0 3.733-.752 6.76-1.681 6.76-.93 0-1.681-3.027-1.681-6.76 0-3.732.752-6.76 1.68-6.76z`}),(0,P.jsx)(`path`,{d:`M19.848 16.552h-9.44L14.028 23h9.438l-3.618-6.448z`})]}))});function SR(e){"@babel/helpers - typeof";return SR=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},SR(e)}function CR(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function wR(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function HR(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var UR=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=VR(e,FR);return(0,P.jsxs)(`svg`,LR(LR({fill:`currentColor`,fillRule:`evenodd`,height:n,style:LR({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:AR}),(0,P.jsx)(`path`,{d:`M17.86 22.992c-2.669.245-4.887-2.876-6.597-4.454C10.398 24.759 1 24.177 1 17.86V6.15c0-.921.244-1.861.733-2.65C2.635 1.977 4.383.98 6.15 1h11.71c6.316 0 6.918 9.398.677 10.243l2.97 2.951c3.252 3.064.808 8.929-3.646 8.797zm-1.428-3.721c1.842 1.898 4.774-1.034 2.876-2.876l-5.132-5.132H11.3v2.876l4.436 4.436.696.696zM4.12 17.842c-.037 2.632 4.117 2.632 4.06 0V6.132c.038-1.316-1.353-2.35-2.612-1.955-.057.019-.113.037-.15.056-.79.301-1.335 1.09-1.317 1.936v11.673h.02zm13.74-9.68c2.632.037 2.632-4.098 0-4.06h-6.973c.526 1.109.395 2.857.413 4.06h6.56z`})]}))});function WR(e){"@babel/helpers - typeof";return WR=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},WR(e)}function GR(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function KR(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function uz(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var dz=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=lz(e,rz);return(0,P.jsxs)(`svg`,az(az({height:n,style:az({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:QR}),(0,P.jsx)(`path`,{d:`M9.1.503l2.824 4.47a1.078 1.078 0 01-.911 1.655H9.858v6.692h-1.67V0c.35 0 .7.168.912.503z`,fill:`#8FBCFA`}),(0,P.jsx)(`path`,{d:`M4.453 4.974L7.277.503A1.07 1.07 0 018.189 0v13.32a2.633 2.633 0 00-1.67.48V6.628H5.364c-.85 0-1.366-.936-.912-1.654z`,fill:`#468BFF`}),(0,P.jsx)(`path`,{d:`M17.041 17.74h-7.028c.423-.457.67-1.049.7-1.67h12.956c0 .35-.168.7-.502.912l-4.472 2.823a1.078 1.078 0 01-1.654-.911v-1.155z`,fill:`#FDBB11`}),(0,P.jsx)(`path`,{d:`M18.695 12.334l4.47 2.824c.336.212.503.562.503.912H10.713a2.65 2.65 0 00-.493-1.67h6.822v-1.154c0-.85.935-1.366 1.653-.912z`,fill:`#F6D785`}),(0,P.jsx)(`path`,{d:`M4.394 19.605L.316 23.683a1.07 1.07 0 001 .29l5.158-1.165A1.078 1.078 0 007 20.994l-.816-.816 3.073-3.074a1.61 1.61 0 000-2.276l-.042-.043-4.82 4.82z`,fill:`#FF9A9D`}),(0,P.jsx)(`path`,{d:`M3.822 17.817l3.073-3.074a1.61 1.61 0 012.277 0l.042.043-4.818 4.819-4.08 4.079a1.07 1.07 0 01-.289-1l1.165-5.158A1.078 1.078 0 013.006 17l.816.817z`,fill:`#FE363B`})]}))});function fz(e){"@babel/helpers - typeof";return fz=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},fz(e)}function pz(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function mz(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function kz(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var $=(0,N.memo)(function(e){var t=e.shape,n=t===void 0?`circle`:t,r=e.color,i=r===void 0?`#fff`:r,a=e.background,o=e.size,s=e.style,c=e.iconMultiple,l=c===void 0?.75:c,u=e.Icon,d=e.iconStyle,f=e.iconClassName,p=Oz(e,Sz),m=Kc().isDarkMode;return(0,P.jsx)(Qc,wz(wz({flex:`none`,style:wz({background:a,borderRadius:n===`circle`?`50%`:Math.floor(o*.1),boxShadow:bz(m,a),color:i,height:o,width:o},s)},p),{},{children:u&&(0,P.jsx)(u,{className:f,color:i,size:o,style:wz({transform:`scale(${l})`},d)})}))});function Az(e){"@babel/helpers - typeof";return Az=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Az(e)}function jz(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Mz(e){for(var t=1;t0?` has-backends`:``}`,onClick:i,children:[(0,P.jsx)(Bz,{id:e.id,size:28}),(0,P.jsx)(`span`,{className:`provider-tile-name`,children:e.display_name}),(0,P.jsx)(`span`,{className:`provider-tile-id`,children:e.id}),t>0&&(0,P.jsxs)(`span`,{className:`provider-tile-count`,children:[t,` key`,t===1?``:`s`]})]}),(0,P.jsx)(`button`,{type:`button`,className:`provider-fav-btn${n?` active`:``}`,"aria-label":n?`Remove from favorites`:`Add to favorites`,"aria-pressed":n,title:n?`Unfavorite`:`Favorite`,onClick:e=>{e.stopPropagation(),r()},children:n?`♥`:`♡`})]})}function Wz({backend:e,healthStatus:t,onDelete:n}){return(0,P.jsxs)(`div`,{className:`provider-backend-row`,children:[(0,P.jsx)(Nc,{status:t===`up`?`ok`:t===`down`?`err`:`dim`,pulse:t===`up`}),(0,P.jsx)(`span`,{className:`backend-name`,children:e.name}),(0,P.jsxs)(`span`,{className:`backend-status`,children:[e.api_key_set?`key set`:`no key`,e.rpm!=null&&(0,P.jsxs)(P.Fragment,{children:[` · RPM `,e.rpm]})]}),(0,P.jsx)(H,{tone:`danger`,size:`sm`,onClick:n,children:`Delete`})]})}function Gz({provider:e,existingCount:t}){let n=co(),r=$a(),i=()=>{let n={name:`${e.id}-${t+1}`,provider_id:e.id};return Vz(e)&&e.default_base_url&&(n.api_base=e.default_base_url),n},[a,o]=(0,N.useState)(i);(0,N.useEffect)(()=>{o(i())},[e.id,t]);function s(){n.mutate({name:a.name,provider_id:e.id,api_key:a.api_key||void 0,api_base:a.api_base?a.api_base.trim().replace(/\/+$/,``):void 0,deployment:a.deployment||void 0,api_version:a.api_version||void 0,project:a.project||void 0,region:a.region||void 0,aws_access_key_id:a.aws_access_key_id||void 0,aws_secret_access_key:a.aws_secret_access_key||void 0,aws_session_token:a.aws_session_token||void 0,rpm:a.rpm?Number(a.rpm):void 0,tpm:a.tpm?Number(a.tpm):void 0},{onSuccess:()=>o(i())})}function c(){r.mutate({source:`custom`,url:a.api_base||e.default_base_url,provider_id:e.id,api_key:a.api_key||void 0})}let l=Lc(e);return(0,P.jsxs)(`div`,{className:`provider-add-form`,children:[(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsx)(`label`,{className:`form-label`,htmlFor:`add-backend-name`,children:`Name`}),(0,P.jsx)(`input`,{id:`add-backend-name`,name:`name`,type:`text`,value:a.name,onChange:e=>o(t=>({...t,name:e.target.value})),style:{width:`100%`}})]}),l.map(t=>(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsx)(`label`,{className:`form-label`,htmlFor:`add-${t.name}`,children:t.label}),t.hint&&(0,P.jsx)(`div`,{className:`form-hint`,children:t.hint}),(0,P.jsx)(`input`,{id:`add-${t.name}`,name:t.name,type:t.type,placeholder:t.placeholder,value:a[t.name]??``,onChange:e=>o(n=>({...n,[t.name]:e.target.value})),style:{width:`100%`}}),t.name===`api_base`&&(()=>{let t=Ic(a.api_base||e.default_base_url||``);if(!t)return null;let n=[`vertex_ai`,`gemini_native`,`bedrock_native`].includes(e.protocol);return(0,P.jsxs)(`div`,{className:`form-hint`,children:[`Query models will request: `,(0,P.jsx)(`span`,{className:`mono`,children:t}),n&&` — model discovery may not work for this provider.`]})})()]},t.name)),r.isError&&(0,P.jsx)(`div`,{className:`inline-error`,children:Hz(r.error,`Failed to query models`)}),r.isSuccess&&(0,P.jsx)(`div`,{className:`form-hint`,children:r.data.models.length>0?`Found ${r.data.models.length} model(s): ${r.data.models.slice(0,8).map(e=>e.id).join(`, `)}${r.data.models.length>8?`, …`:``}`:`No models returned by the server.`}),n.isError&&(0,P.jsx)(`div`,{className:`inline-error`,children:Hz(n.error,`Failed to create backend`)}),(0,P.jsxs)(`div`,{className:`provider-add-actions`,children:[(0,P.jsx)(H,{size:`sm`,onClick:()=>o(i()),disabled:n.isPending,children:`Reset`}),(0,P.jsx)(H,{size:`sm`,onClick:c,disabled:r.isPending||!a.api_base&&!e.default_base_url,loading:r.isPending,children:`Query models`}),(0,P.jsx)(H,{tone:`primary`,size:`sm`,onClick:s,disabled:!a.name||n.isPending,loading:n.isPending,children:`Create`})]})]})}function Kz({provider:e,backends:t,healthMap:n,onClose:r,onDeleteBackend:i}){let a=e.capabilities,o=[[`chat`,a.chat_completions],[`streaming`,a.streaming],[`tool use`,a.tool_use],[`vision`,a.vision],[`embeddings`,a.embeddings],[`batch`,a.batch]];return(0,P.jsxs)(lc,{open:!0,onClose:r,title:`${e.display_name} (${e.id})`,size:`md`,children:[(0,P.jsxs)(`div`,{className:`provider-panel-caps`,children:[o.map(([e,t])=>(0,P.jsx)(`span`,{className:`badge-cap${t?` active`:``}`,children:e},e)),(0,P.jsxs)(`span`,{style:{marginLeft:`auto`},className:`badge-cap active`,children:[e.model_count,` models`]})]}),(0,P.jsxs)(`div`,{className:`provider-panel-meta`,children:[(0,P.jsxs)(`span`,{children:[`Protocol: `,(0,P.jsx)(`span`,{className:`mono`,children:e.protocol.replace(/_/g,` `)})]}),(0,P.jsxs)(`span`,{children:[`Auth: `,(0,P.jsx)(`span`,{className:`mono`,children:e.auth.replace(/_/g,` `)})]}),(0,P.jsxs)(`span`,{children:[`Status: `,(0,P.jsx)(`span`,{className:`mono`,children:e.status})]}),e.env_vars.length>0&&(0,P.jsxs)(`span`,{children:[`Env: `,(0,P.jsx)(`span`,{className:`mono`,children:e.env_vars[0]})]})]}),(0,P.jsxs)(`div`,{className:`provider-panel-section`,children:[(0,P.jsxs)(`div`,{className:`provider-panel-section-label`,children:[`Configured keys (`,t.length,`)`]}),t.length===0&&(0,P.jsx)(`div`,{className:`provider-empty-hint`,children:`No keys configured. Add one below to start forwarding requests.`}),t.map(e=>(0,P.jsx)(Wz,{backend:e,healthStatus:n.get(e.name),onDelete:()=>i(e)},e.id)),(0,P.jsx)(Gz,{provider:e,existingCount:t.length})]})]})}function qz(){let e=io(),t=so(),{data:n}=no(),{data:r}=ao(),i=oo(),a=uo(),o=(0,N.useMemo)(()=>new Set(r??[]),[r]),[s,c]=(0,N.useState)(null),[l,u]=(0,N.useState)(``),[d,f]=(0,N.useState)(null),p=(0,N.useMemo)(()=>e.data??[],[e.data]),m=(0,N.useMemo)(()=>t.data?.backends??[],[t.data]),h=(0,N.useMemo)(()=>{let e=new Map;for(let t of m)e.has(t.provider_id)||e.set(t.provider_id,[]),e.get(t.provider_id).push(t);return e},[m]),g=(0,N.useMemo)(()=>{let e=new Map;for(let t of n?.backends??[])e.set(t.name,t.status);return e},[n]),_=(0,N.useMemo)(()=>{let e=l.toLowerCase();return Hc(e?p.filter(t=>t.display_name.toLowerCase().includes(e)||t.id.includes(e)):p,o)},[p,l,o]),v=s?p.find(e=>e.id===s):null,y=s?h.get(s)??[]:[];function b(){return d?a.mutateAsync(d.name).then(()=>void 0):Promise.resolve()}return(0,P.jsxs)(`div`,{children:[(0,P.jsxs)(`div`,{className:`section-header`,children:[(0,P.jsx)(`h2`,{children:`Providers`}),(0,P.jsx)(`input`,{type:`search`,name:`provider-search`,placeholder:`Search providers...`,value:l,onChange:e=>u(e.target.value),style:{width:260}})]}),(0,P.jsx)(ac,{query:e,errorTitle:`Failed to load provider catalog`,empty:{when:()=>p.length===0,render:()=>(0,P.jsxs)(U,{className:`empty-cta`,children:[(0,P.jsx)(`div`,{className:`empty-cta-title`,children:`No providers available`}),(0,P.jsx)(`div`,{className:`empty-cta-body`,children:`The provider catalog is empty. Check that the providers crate is loaded.`})]})},children:()=>(0,P.jsxs)(`div`,{className:`provider-catalog`,children:[_.map(e=>(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`div`,{className:`provider-tier-label`,children:e.label}),(0,P.jsx)(`div`,{className:`provider-tile-grid${e.top?` tier-top`:``}`,children:e.providers.map(e=>(0,P.jsx)(Uz,{provider:e,backendCount:h.get(e.id)?.length??0,favorited:o.has(e.id),onToggleFavorite:()=>i.mutate({providerId:e.id,on:!o.has(e.id)}),onClick:()=>c(e.id)},e.id))})]},e.key)),_.length===0&&l&&(0,P.jsxs)(`div`,{className:`dim`,style:{padding:20},children:[`No providers match "`,l,`".`]})]})}),v&&(0,P.jsx)(Kz,{provider:v,backends:y,healthMap:g,onClose:()=>c(null),onDeleteBackend:f},s),(0,P.jsx)(uc,{open:d!==null,onClose:()=>f(null),onConfirm:b,title:`Delete backend?`,message:(0,P.jsxs)(P.Fragment,{children:[`Delete backend `,(0,P.jsx)(`span`,{className:`mono`,children:d?.name}),`? Routes referencing this backend will lose it from their provider list.`]})})]})}function Jz({initial:e,onSuccess:t,onCancel:n}){let r=!!e,{data:i=[]}=io(),a=co(),o=lo(),[s,c]=(0,N.useState)(e?.name??``),[l,u]=(0,N.useState)(e?.provider_id??``),[d,f]=(0,N.useState)(()=>{if(!e)return{};let t={};for(let n of[`api_base`,`deployment`,`api_version`,`project`,`region`])e[n]!=null&&(t[n]=e[n]);return e.rpm!=null&&(t.rpm=String(e.rpm)),e.tpm!=null&&(t.tpm=String(e.tpm)),t}),[p,m]=(0,N.useState)(null);(0,N.useEffect)(()=>{i.length>0&&!l&&u(e?.provider_id??i[0].id)},[i.length]);let h=i.find(e=>e.id===l)??(i.length>0?i[0]:void 0),g=h?Lc(h):[],_=g.filter(e=>e.group===`auth`),v=g.filter(e=>e.group===`endpoint`),y=g.filter(e=>e.group===`limits`);function b(e){return d[e]??``}function x(e,t){f(n=>({...n,[e]:t}))}function S(t){return!r||!e?!1:t===`api_key`?e.api_key_set:t===`aws_secret_access_key`||t===`aws_access_key_id`?e.aws_creds_set:!1}function C(e){return Object.fromEntries(Object.entries(e).filter(([,e])=>e!==``))}function w(n){n.preventDefault(),m(null);let i=C(d);if(r&&e)o.mutate({name:e.name,data:i},{onSuccess:()=>t(),onError:e=>m(e.message)});else{if(!s){m(`Name is required`);return}a.mutate({name:s,provider_id:l,...i},{onSuccess:()=>t(),onError:e=>m(e.message)})}}let T=a.isPending||o.isPending;function E(e){let t=b(e.name),n=S(e.name),r=e.type===`url`?`text`:e.type;return(0,P.jsxs)(`div`,{style:{marginBottom:10},children:[(0,P.jsxs)(`div`,{style:{fontSize:12,color:`var(--text-2)`,marginBottom:3},children:[e.label,e.required&&(0,P.jsx)(`span`,{style:{color:`var(--err)`,marginLeft:2},children:`*`})]}),(0,P.jsx)(`input`,{type:r,value:t,placeholder:n?`••••••••`:e.placeholder,onChange:t=>x(e.name,t.target.value),style:{width:`100%`}}),e.hint&&(0,P.jsx)(`div`,{style:{fontSize:11,color:`var(--text-2)`,marginTop:3},children:e.hint})]},e.name)}return(0,P.jsxs)(`form`,{onSubmit:w,style:{padding:`12px`,background:`var(--bg-raised)`,border:`1px solid var(--border)`,borderRadius:`var(--rm)`,marginBottom:14},children:[(0,P.jsx)(`div`,{style:{fontWeight:600,marginBottom:12,fontSize:13},children:r?`Edit backend: ${e.name}`:`Add managed backend`}),(0,P.jsxs)(`div`,{style:{marginBottom:10},children:[(0,P.jsxs)(`div`,{style:{fontSize:12,color:`var(--text-2)`,marginBottom:3},children:[`Provider`,(0,P.jsx)(`span`,{style:{color:`var(--err)`,marginLeft:2},children:`*`})]}),(0,P.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:8},children:[l&&(0,P.jsx)(Bz,{id:l,size:18,style:{flexShrink:0}}),(0,P.jsxs)(`select`,{value:l,onChange:e=>{u(e.target.value),f({})},disabled:r,style:{flex:1},children:[i.length===0&&(0,P.jsx)(`option`,{value:``,children:`Loading providers…`}),[`implemented`,`wired`,`stub`].map(e=>{let t=i.filter(t=>t.status===e);return t.length===0?null:(0,P.jsx)(`optgroup`,{label:e.charAt(0).toUpperCase()+e.slice(1),children:t.map(e=>(0,P.jsxs)(`option`,{value:e.id,children:[e.display_name,` (`,e.id,`)`]},e.id))},e)})]})]})]}),(0,P.jsxs)(`div`,{style:{marginBottom:10},children:[(0,P.jsxs)(`div`,{style:{fontSize:12,color:`var(--text-2)`,marginBottom:3},children:[`Name`,(0,P.jsx)(`span`,{style:{color:`var(--err)`,marginLeft:2},children:`*`})]}),(0,P.jsx)(`input`,{type:`text`,value:s,onChange:e=>c(e.target.value),required:!0,pattern:`[a-zA-Z0-9_\\-]+`,placeholder:`my-backend`,disabled:r,style:{width:`100%`}}),!r&&(0,P.jsx)(`div`,{style:{fontSize:11,color:`var(--text-2)`,marginTop:3},children:`Letters, numbers, underscores, hyphens only`})]}),_.length>0&&(0,P.jsxs)(`div`,{style:{marginBottom:4},children:[(0,P.jsx)(`div`,{className:`section-label`,style:{marginBottom:6},children:`Authentication`}),_.map(E)]}),v.length>0&&(0,P.jsxs)(`div`,{style:{marginBottom:4},children:[(0,P.jsx)(`div`,{className:`section-label`,style:{marginBottom:6},children:`Endpoint`}),v.map(E)]}),y.length>0&&(0,P.jsxs)(`details`,{style:{marginBottom:10},children:[(0,P.jsx)(`summary`,{style:{fontSize:11,color:`var(--text-2)`,cursor:`pointer`,textTransform:`uppercase`,letterSpacing:`0.07em`,fontWeight:500,marginBottom:6},children:`Rate Limits`}),(0,P.jsx)(`div`,{style:{marginTop:8},children:y.map(E)})]}),p&&(0,P.jsx)(`div`,{style:{marginBottom:10,padding:`6px 10px`,background:`var(--err-dim)`,borderLeft:`3px solid var(--err)`,borderRadius:`var(--r)`,fontSize:12},children:p}),(0,P.jsxs)(`div`,{style:{display:`flex`,gap:8},children:[(0,P.jsx)(H,{type:`submit`,tone:`primary`,size:`sm`,loading:T,children:r?`Save changes`:`Create backend`}),(0,P.jsx)(H,{type:`button`,size:`sm`,onClick:n,disabled:T,children:`Cancel`})]})]})}function Yz(){let{data:e,isLoading:t,error:n}=so(),{data:r=[]}=io(),i=uo(),[a,o]=(0,N.useState)({mode:`none`}),[s,c]=(0,N.useState)(null),l=(0,N.useMemo)(()=>Object.fromEntries(r.map(e=>[e.id,e.display_name])),[r]);function u(e){return l[e]??e}function d(){return s?i.mutateAsync(s.name).then(()=>void 0):Promise.resolve()}return(0,P.jsxs)(`div`,{style:{marginBottom:24},children:[(0,P.jsxs)(`div`,{className:`section-header`,children:[(0,P.jsx)(`div`,{className:`section-label`,style:{margin:0},children:`Managed Backends`}),(0,P.jsx)(H,{tone:`primary`,size:`sm`,onClick:()=>o({mode:`create`}),children:`Add Backend`})]}),(0,P.jsx)(`div`,{style:{fontSize:12,color:`var(--text-2)`,marginBottom:10},children:`Configure provider credentials and backend settings at runtime.`}),a.mode===`create`&&(0,P.jsx)(Jz,{onSuccess:()=>o({mode:`none`}),onCancel:()=>o({mode:`none`})}),a.mode===`edit`&&(0,P.jsx)(Jz,{initial:a.backend,onSuccess:()=>o({mode:`none`}),onCancel:()=>o({mode:`none`})}),(0,P.jsx)(tc,{loading:t,error:n?.message}),e&&e.backends.length===0&&(0,P.jsx)(`div`,{style:{padding:`20px 0`,color:`var(--text-2)`,fontSize:13},children:`No managed backends yet. Add one to configure provider credentials at runtime.`}),e&&e.backends.length>0&&(0,P.jsxs)(`table`,{className:`route-table`,children:[(0,P.jsx)(`thead`,{children:(0,P.jsxs)(`tr`,{children:[(0,P.jsx)(`th`,{children:`Name`}),(0,P.jsx)(`th`,{children:`Provider`}),(0,P.jsx)(`th`,{children:`Credentials`}),(0,P.jsx)(`th`,{children:`Base URL`}),(0,P.jsx)(`th`,{})]})}),(0,P.jsx)(`tbody`,{children:e.backends.map(e=>(0,P.jsxs)(`tr`,{children:[(0,P.jsx)(`td`,{className:`mono`,children:e.name}),(0,P.jsxs)(`td`,{className:`dim`,style:{whiteSpace:`nowrap`},children:[(0,P.jsx)(Bz,{id:e.provider_id,size:16,style:{marginRight:6,verticalAlign:`middle`,opacity:.8}}),u(e.provider_id)]}),(0,P.jsxs)(`td`,{children:[e.api_key_set&&(0,P.jsx)(`span`,{className:`badge badge-active`,style:{marginRight:4},children:`Key set`}),!e.api_key_set&&!e.aws_creds_set&&(0,P.jsx)(`span`,{className:`badge badge-revoked`,children:`No key`}),e.aws_creds_set&&(0,P.jsx)(`span`,{className:`badge badge-active`,children:`AWS creds set`})]}),(0,P.jsx)(`td`,{className:`dim mono`,style:{fontSize:11},children:e.api_base??`—`}),(0,P.jsx)(`td`,{children:(0,P.jsxs)(`div`,{style:{display:`flex`,gap:6},children:[(0,P.jsx)(H,{size:`sm`,onClick:()=>{a.mode===`edit`&&a.backend.id===e.id?o({mode:`none`}):o({mode:`edit`,backend:e})},children:`Edit`}),(0,P.jsx)(H,{tone:`danger`,size:`sm`,onClick:()=>c(e),disabled:i.isPending&&i.variables===e.name,children:`Delete`})]})})]},e.id))})]}),(0,P.jsx)(uc,{open:s!==null,onClose:()=>c(null),onConfirm:d,title:`Delete managed backend?`,message:(0,P.jsxs)(P.Fragment,{children:[`Delete backend `,(0,P.jsx)(`span`,{className:`mono`,children:s?.name}),`? Stored credentials will be removed. Routes still referencing it will fail until reconfigured.`]})})]})}function Xz(){let{data:e,isLoading:t,error:n}=Ua(),{data:r}=no(),i=(0,N.useMemo)(()=>{let e=new Map;for(let t of r?.backends??[])e.set(t.name,t);return e},[r]);return(0,P.jsxs)(`div`,{children:[(0,P.jsx)(Yz,{}),(0,P.jsx)(`div`,{className:`section-label`,style:{marginTop:8},children:`Backend Status`}),(0,P.jsx)(tc,{loading:t,error:n?.message,empty:e?.length===0}),(0,P.jsx)(`div`,{className:`backend-cards`,children:e?.map(e=>{let t=i.get(e.name),n=t?.status===`up`?`ok`:t?.status===`down`?`err`:`dim`;return(0,P.jsxs)(U,{className:`card`,children:[(0,P.jsxs)(`div`,{className:`card-header`,children:[(0,P.jsx)(`span`,{className:`card-name`,children:e.name}),(0,P.jsx)(Nc,{status:n,pulse:n===`ok`})]}),(0,P.jsxs)(`div`,{className:`card-body`,children:[(0,P.jsxs)(`div`,{className:`mono`,children:[e.big_model,` / `,e.small_model]}),(0,P.jsxs)(`div`,{style:{marginTop:6,display:`grid`,gridTemplateColumns:`1fr 1fr`,gap:4},children:[(0,P.jsx)(`span`,{className:`dim`,children:`Requests`}),(0,P.jsx)(`span`,{className:`mono`,children:e.metrics.requests_total}),(0,P.jsx)(`span`,{className:`dim`,children:`Errors`}),(0,P.jsx)(`span`,{className:`mono`,style:{color:e.metrics.requests_error>0?`var(--err)`:void 0},children:e.metrics.requests_error}),t?.last_latency_ms!=null&&(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(`span`,{className:`dim`,children:`Last latency`}),(0,P.jsxs)(`span`,{className:`mono`,children:[t.last_latency_ms,`ms`]})]}),t&&(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(`span`,{className:`dim`,children:`30d uptime`}),(0,P.jsxs)(`span`,{className:`mono`,children:[t.uptime_pct_30d,`%`]})]})]})]})]},e.name)})})]})}var Zz=[`failover`,`round-robin`,`least-busy`,`latency`,`weighted`,`cost`];function Qz({value:e,onChange:t}){return(0,P.jsxs)(`select`,{value:e===null?`inherit`:e?`on`:`off`,onChange:e=>{let n=e.target.value;t(n===`inherit`?null:n===`on`)},children:[(0,P.jsx)(`option`,{value:`inherit`,children:`inherit (global)`}),(0,P.jsx)(`option`,{value:`on`,children:`on`}),(0,P.jsx)(`option`,{value:`off`,children:`off`})]})}function $z({route:e,onClose:t}){let{data:n,isLoading:r}=B(e.id),i=ho(),a=go(),o=_o(),s=vo(),c=mo(),l=lo(),{data:u}=so(),{data:d}=Fa(),[f,p]=(0,N.useState)(!1),[m,h]=(0,N.useState)(``),[g,_]=(0,N.useState)(`*`),v=n?.providers??[],y=u?.backends??[],b=[`curl ${`http://${window.location.hostname}:${d?.proxy_port??3e3}/v1/chat/completions`} \\`,` -H 'Authorization: Bearer ' \\`,` -H 'Content-Type: application/json' \\`,` -d '${JSON.stringify({model:e.name,messages:[{role:`user`,content:`hi`}]})}'`].join(` -`);async function x(){ma(await xc(b)?{variant:`success`,message:`curl snippet copied`}:{variant:`error`,message:`Copy failed (clipboard blocked)`})}let S=new Set(v.map(e=>e.backend_id)),C=y.filter(e=>!S.has(e.id));function w(){if(!m)return;let t=g.trim()===`*`?[`*`]:g.split(`,`).map(e=>e.trim()).filter(Boolean);i.mutate({routeId:e.id,data:{backend_id:m,models:t,priority:v.length,enabled:!0}},{onSuccess:()=>{p(!1),h(``),_(`*`)}})}function T(t,n){let r=t+n;if(r<0||r>=v.length)return;let i=v.slice(),[a]=i.splice(t,1);i.splice(r,0,a),s.mutate({routeId:e.id,data:{provider_ids:i.map(e=>e.id)}})}return(0,P.jsxs)(`div`,{className:`route-detail`,children:[(0,P.jsxs)(`div`,{className:`route-detail-header`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`span`,{className:`route-detail-title`,children:e.name}),e.description&&(0,P.jsx)(`span`,{className:`dim route-detail-desc`,children:e.description})]}),(0,P.jsxs)(`div`,{className:`route-detail-meta`,children:[e.rpm&&(0,P.jsxs)(`span`,{className:`dim mono`,children:[`RPM `,e.rpm]}),(0,P.jsx)(H,{size:`sm`,tone:e.enabled?`primary`:`secondary`,title:`Route on/off. Disabled routes stop dispatching and lose virtual-key scope.`,onClick:()=>c.mutate({id:e.id,data:{enabled:!e.enabled}}),children:e.enabled?`route on`:`route off`}),(0,P.jsx)(H,{size:`sm`,onClick:t,children:`Close`})]})]}),e.enabled&&(0,P.jsxs)(`div`,{className:`route-detail-curl`,children:[(0,P.jsxs)(`div`,{className:`route-detail-curl-head`,children:[(0,P.jsx)(`span`,{className:`section-label`,children:`Call this route`}),(0,P.jsx)(H,{size:`sm`,onClick:x,children:`Copy curl`})]}),(0,P.jsx)(`pre`,{className:`route-detail-curl-body mono`,children:b}),(0,P.jsxs)(`div`,{className:`dim route-detail-curl-hint`,children:[`The route is selected by the `,(0,P.jsx)(`code`,{children:`model`}),` field (= route name). Replace`,` `,(0,P.jsx)(`code`,{children:``}),` with a proxy or virtual key.`]})]}),(0,P.jsxs)(`div`,{className:`route-detail-options`,children:[(0,P.jsx)(`span`,{className:`section-label route-detail-subhead-label`,children:`Route options`}),(0,P.jsxs)(`div`,{className:`route-options-grid`,children:[(0,P.jsxs)(`label`,{className:`route-option`,children:[(0,P.jsx)(`span`,{className:`dim`,children:`Strategy`}),(0,P.jsx)(`select`,{value:e.strategy,onChange:t=>c.mutate({id:e.id,data:{strategy:t.target.value}}),children:Zz.map(e=>(0,P.jsx)(`option`,{value:e,children:e},e))})]}),(0,P.jsxs)(`label`,{className:`route-option`,children:[(0,P.jsx)(`span`,{className:`dim`,children:`Position (lower wins across routes)`}),(0,P.jsx)(`input`,{type:`number`,name:`route-position`,defaultValue:e.position,onBlur:t=>{let n=Number.parseInt(t.target.value,10);Number.isNaN(n)||n===e.position||c.mutate({id:e.id,data:{position:n}})}})]}),(0,P.jsxs)(`label`,{className:`route-option`,children:[(0,P.jsx)(`span`,{className:`dim`,children:`Guardrails`}),(0,P.jsxs)(`select`,{value:e.guardrail_mode??`inherit`,onChange:t=>c.mutate({id:e.id,data:{guardrail_mode:t.target.value===`inherit`?null:t.target.value}}),children:[(0,P.jsx)(`option`,{value:`inherit`,children:`inherit (global)`}),(0,P.jsx)(`option`,{value:`disabled`,children:`disabled`}),(0,P.jsx)(`option`,{value:`standard`,children:`standard`})]})]}),(0,P.jsxs)(`label`,{className:`route-option`,children:[(0,P.jsx)(`span`,{className:`dim`,children:`Secret redaction`}),(0,P.jsx)(Qz,{value:e.redact_secrets,onChange:t=>c.mutate({id:e.id,data:{redact_secrets:t}})})]}),(0,P.jsxs)(`label`,{className:`route-option`,children:[(0,P.jsx)(`span`,{className:`dim`,children:`Image compression`}),(0,P.jsx)(Qz,{value:e.pxpipe_compress,onChange:t=>c.mutate({id:e.id,data:{pxpipe_compress:t}})})]}),(0,P.jsxs)(`label`,{className:`route-option route-option-wide`,children:[(0,P.jsx)(`span`,{className:`dim`,children:`Compression model scope (CSV, blank = inherit)`}),(0,P.jsx)(`input`,{type:`text`,name:`route-pxpipe-models`,defaultValue:e.pxpipe_models??``,placeholder:`inherit global`,onBlur:t=>{let n=t.target.value.trim();(e.pxpipe_models??``)!==n&&c.mutate({id:e.id,data:{pxpipe_models:n===``?null:n}})}})]})]}),(0,P.jsx)(`div`,{className:`dim route-options-note`,children:`Overrides apply only where the feature already runs (image compression: Anthropic passthrough backends only). "inherit" / blank uses the global value from Settings.`})]}),(0,P.jsxs)(`div`,{className:`route-detail-subhead`,children:[(0,P.jsx)(`span`,{className:`section-label route-detail-subhead-label`,children:`Providers (priority order)`}),(0,P.jsx)(H,{tone:`primary`,size:`sm`,onClick:()=>p(!f),children:f?`Cancel`:`+ Add Provider`})]}),f&&(0,P.jsxs)(`div`,{className:`route-detail-add`,children:[(0,P.jsxs)(`select`,{value:m,onChange:e=>h(e.target.value),className:`route-detail-add-select`,children:[(0,P.jsx)(`option`,{value:``,children:`Select provider...`}),C.map(e=>(0,P.jsxs)(`option`,{value:e.id,children:[e.name,` (`,e.provider_id,`)`]},e.id))]}),(0,P.jsx)(`input`,{type:`text`,name:`route-provider-models`,placeholder:`models (* for all)`,value:g,onChange:e=>_(e.target.value),className:`route-detail-add-models`}),(0,P.jsx)(H,{tone:`primary`,size:`sm`,onClick:w,disabled:!m||i.isPending,loading:i.isPending,children:`Add`})]}),r&&(0,P.jsx)(`div`,{className:`dim`,children:(0,P.jsx)(Vs,{label:`Loading providers`})}),!r&&v.length===0&&(0,P.jsx)(`div`,{className:`dim route-detail-empty`,children:`No providers assigned. Click "+ Add Provider" above.`}),!r&&v.map((t,n)=>(0,P.jsxs)(`div`,{className:`route-provider-row`,children:[(0,P.jsxs)(`span`,{className:`dim mono`,children:[n+1,`.`]}),(0,P.jsxs)(`span`,{children:[(0,P.jsx)(`span`,{className:`route-provider-name`,children:t.backend_name}),(0,P.jsxs)(`span`,{className:`dim route-provider-id`,children:[`(`,t.provider_id,`)`]})]}),(0,P.jsxs)(`span`,{className:`mono dim route-provider-models`,children:[`[`,t.models.join(`, `),`]`]}),(0,P.jsxs)(`span`,{className:`route-provider-reorder`,children:[(0,P.jsx)(H,{tone:`icon`,size:`sm`,className:`btn-icon`,onClick:()=>T(n,-1),disabled:n===0||s.isPending,"aria-label":`Move up`,children:`↑`}),(0,P.jsx)(H,{tone:`icon`,size:`sm`,className:`btn-icon`,onClick:()=>T(n,1),disabled:n>=v.length-1||s.isPending,"aria-label":`Move down`,children:`↓`})]}),(0,P.jsx)(H,{size:`sm`,tone:t.enabled?`primary`:`secondary`,className:`route-provider-toggle`,title:`In-route membership: whether this backend is active within this route.`,onClick:()=>a.mutate({routeId:e.id,providerId:t.id,data:{enabled:!t.enabled}}),children:t.enabled?`in route`:`excluded`}),(()=>{let e=y.find(e=>e.id===t.backend_id);return e?(0,P.jsx)(H,{size:`sm`,tone:e.enabled?`primary`:`secondary`,className:`route-provider-toggle`,title:`Backend online (global). Disables this backend everywhere, not just this route.`,onClick:()=>l.mutate({name:e.name,data:{enabled:!e.enabled}}),children:e.enabled?`backend on`:`backend off`}):null})(),(0,P.jsx)(H,{tone:`danger`,size:`sm`,className:`route-provider-remove`,onClick:()=>o.mutate({routeId:e.id,providerId:t.id}),children:`Remove`})]},t.id))]})}function eB({onClose:e}){let t=po(),[n,r]=(0,N.useState)(``),[i,a]=(0,N.useState)(``),[o,s]=(0,N.useState)(`failover`);function c(){t.mutate({name:n,description:i||void 0,strategy:o},{onSuccess:e})}return(0,P.jsxs)(lc,{open:!0,onClose:e,title:`New Route`,size:`sm`,dismissable:!t.isPending,footer:(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(H,{onClick:e,disabled:t.isPending,children:`Cancel`}),(0,P.jsx)(H,{tone:`primary`,onClick:c,disabled:!n.trim()||t.isPending,loading:t.isPending,children:`Create`})]}),children:[(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsx)(`label`,{className:`form-label`,htmlFor:`route-name`,children:`Name`}),(0,P.jsx)(`input`,{id:`route-name`,name:`name`,type:`text`,value:n,onChange:e=>r(e.target.value),placeholder:`e.g. default, cheap`,style:{width:`100%`}})]}),(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsx)(`label`,{className:`form-label`,htmlFor:`route-desc`,children:`Description`}),(0,P.jsx)(`input`,{id:`route-desc`,name:`description`,type:`text`,value:i,onChange:e=>a(e.target.value),placeholder:`optional`,style:{width:`100%`}})]}),(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsx)(`label`,{className:`form-label`,htmlFor:`route-strategy`,children:`Strategy`}),(0,P.jsx)(`select`,{id:`route-strategy`,name:`strategy`,value:o,onChange:e=>s(e.target.value),style:{width:`100%`},children:Zz.map(e=>(0,P.jsx)(`option`,{value:e,children:e},e))})]}),(0,P.jsx)(`div`,{className:`dim`,style:{fontSize:`0.85em`},children:`Per-route options (guardrails, compression, secret redaction) and on/off are set after creation from the route's detail panel.`}),t.isError&&(0,P.jsx)(`div`,{className:`error`,children:`Failed to create route`})]})}function tB(){let e=fo(),t=z(),[n,r]=(0,N.useState)(null),[i,a]=(0,N.useState)(!1),[o,s]=(0,N.useState)(null);function c(){return o?t.mutateAsync(o.id).then(()=>void 0):Promise.resolve()}return(0,P.jsxs)(`div`,{children:[(0,P.jsxs)(`div`,{className:`section-header`,children:[(0,P.jsx)(`h2`,{children:`Routes`}),(0,P.jsx)(H,{tone:`primary`,onClick:()=>a(!0),children:`+ New Route`})]}),(0,P.jsx)(ac,{query:e,errorTitle:`Failed to load routes`,empty:{when:e=>(e.routes?.length??0)===0,render:()=>(0,P.jsxs)(U,{className:`empty-cta`,children:[(0,P.jsx)(`div`,{className:`empty-cta-title`,children:`No routes yet`}),(0,P.jsx)(`div`,{className:`empty-cta-body`,children:`Create a route to fan requests out across multiple backends with priority-based failover.`}),(0,P.jsx)(H,{tone:`primary`,onClick:()=>a(!0),children:`+ New Route`})]})},children:e=>(0,P.jsxs)(`table`,{className:`route-table`,children:[(0,P.jsx)(`thead`,{children:(0,P.jsxs)(`tr`,{children:[(0,P.jsx)(`th`,{children:`Name`}),(0,P.jsx)(`th`,{children:`Strategy`}),(0,P.jsx)(`th`,{children:`Providers`}),(0,P.jsx)(`th`,{children:`Limits`}),(0,P.jsx)(`th`,{})]})}),(0,P.jsx)(`tbody`,{children:e.routes.map(e=>(0,P.jsx)(nB,{route:e,expanded:n===e.id,onToggle:()=>r(n===e.id?null:e.id),onDelete:()=>s(e)},e.id))})]})}),i&&(0,P.jsx)(eB,{onClose:()=>a(!1)}),(0,P.jsx)(uc,{open:o!==null,onClose:()=>s(null),onConfirm:c,title:`Delete route?`,message:(0,P.jsxs)(P.Fragment,{children:[`Delete route `,(0,P.jsx)(`span`,{className:`mono`,children:o?.name}),`? Virtual keys scoped to this route will lose access. This cannot be undone.`]})})]})}function nB({route:e,expanded:t,onToggle:n,onDelete:r}){let i=[e.rpm&&`RPM ${e.rpm}`,e.tpm&&`TPM ${e.tpm}`].filter(Boolean).join(`, `)||`—`;return(0,P.jsxs)(P.Fragment,{children:[(0,P.jsxs)(`tr`,{className:`route-row`,onClick:n,children:[(0,P.jsxs)(`td`,{className:`route-row-name`,children:[t?`▾ `:`▸ `,e.name,!e.enabled&&(0,P.jsx)(`span`,{className:`dim route-row-desc`,children:`(disabled)`}),e.description&&(0,P.jsx)(`span`,{className:`dim route-row-desc`,children:e.description})]}),(0,P.jsx)(`td`,{className:`dim`,children:e.strategy}),(0,P.jsx)(`td`,{children:e.provider_count}),(0,P.jsx)(`td`,{className:`mono dim`,children:i}),(0,P.jsx)(`td`,{className:`route-row-actions`,children:(0,P.jsx)(H,{tone:`danger`,size:`sm`,onClick:e=>{e.stopPropagation(),r()},children:`Delete`})})]}),t&&(0,P.jsx)(`tr`,{children:(0,P.jsx)(`td`,{colSpan:5,className:`route-row-detail-cell`,children:(0,P.jsx)($z,{route:e,onClose:n})})})]})}function rB(){let e=ea(e=>e.token),t=ta(e=>e.lastEvent),n=F(),{data:r}=Fa(!!e);if((0,N.useEffect)(()=>{e?sa():ca()},[e]),(0,N.useEffect)(()=>{t&&(t.type===`metrics_snapshot`?n.setQueryData([`metrics`],t.data):t.type===`backend_health_changed`?n.invalidateQueries({queryKey:[`uptime`]}):t.type===`config_changed`&&(n.invalidateQueries({queryKey:[`config`]}),n.invalidateQueries({queryKey:[`env`]})))},[t,n]),!e)return(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(Us,{}),(0,P.jsx)(Ks,{})]});let i=r?.configured??!0;return(0,P.jsx)(Ni,{children:(0,P.jsxs)(`div`,{className:`app-layout`,children:[(0,P.jsx)(Gs,{}),(0,P.jsx)(`div`,{className:`tab-content`,children:(0,P.jsxs)(yi,{children:[(0,P.jsx)(_i,{path:`/`,element:(0,P.jsx)(gi,{to:i?`/dashboard`:`/settings`,replace:!0})}),(0,P.jsx)(_i,{path:`/dashboard`,element:(0,P.jsx)(rc,{})}),(0,P.jsx)(_i,{path:`/requests`,element:(0,P.jsx)(sc,{})}),(0,P.jsx)(_i,{path:`/traffic`,element:(0,P.jsx)(Ac,{})}),(0,P.jsx)(_i,{path:`/providers`,element:(0,P.jsx)(qz,{})}),(0,P.jsx)(_i,{path:`/routes`,element:(0,P.jsx)(tB,{})}),(0,P.jsx)(_i,{path:`/models`,element:(0,P.jsx)(Tc,{})}),(0,P.jsx)(_i,{path:`/backends`,element:(0,P.jsx)(Xz,{})}),(0,P.jsx)(_i,{path:`/keys`,element:(0,P.jsx)(Sc,{})}),(0,P.jsx)(_i,{path:`/audit`,element:(0,P.jsx)(Ec,{})}),(0,P.jsx)(_i,{path:`/settings`,element:(0,P.jsx)(mc,{configured:i})}),(0,P.jsx)(_i,{path:`/uptime`,element:(0,P.jsx)(Fc,{})}),(0,P.jsx)(_i,{path:`*`,element:(0,P.jsx)(gi,{to:`/dashboard`,replace:!0})})]})}),(0,P.jsx)(Ks,{})]})})}var iB=new xn({defaultOptions:{queries:{retry:(e,t)=>t instanceof ka?!1:e<1,refetchOnWindowFocus:!1,staleTime:3e4},mutations:{retry:(e,t)=>t instanceof ka?!1:e<1}}});(0,Hn.createRoot)(document.getElementById(`root`)).render((0,P.jsx)(N.StrictMode,{children:(0,P.jsx)(Tn,{client:iB,children:(0,P.jsx)(rB,{})})})); +PROXY_API_KEYS=my-key`})]})]})]})}var lc=`env_import_pending_restart`;function uc(){return sessionStorage.getItem(lc)===`1`}function dc(){let e=ro(),t=(0,N.useRef)(null),[n,r]=(0,N.useState)(null),[i,a]=(0,N.useState)(null),[o,s]=(0,N.useState)(null),[c,l]=(0,N.useState)(uc);function u(n){let i=n.target.files?.[0];i&&(r(null),a(null),e.mutate(i,{onSuccess(e){r(e),sessionStorage.setItem(lc,`1`),l(!0)},onError(e){try{let t=JSON.parse(e.message);if(t.hard_errors){a(t);return}}catch{}a({hard_errors:[e.message],warnings:[]})}}),t.current&&(t.current.value=``))}async function d(){s(null);try{await yo()}catch(e){s(e instanceof Error?e.message:String(e))}}function f(){sessionStorage.removeItem(lc),l(!1)}return(0,P.jsxs)(`div`,{style:{marginBottom:24},children:[c&&(0,P.jsxs)(U,{className:`settings-restart-banner`,style:{marginBottom:16},children:[(0,P.jsx)(`span`,{children:`Restart the proxy for imported env vars to take effect.`}),(0,P.jsx)(H,{size:`sm`,onClick:f,children:`Dismiss`})]}),(0,P.jsx)(`div`,{className:`section-label`,style:{marginBottom:8},children:`Env File`}),(0,P.jsxs)(`div`,{style:{display:`flex`,gap:8,alignItems:`center`},children:[(0,P.jsx)(`input`,{ref:t,type:`file`,accept:`.env,.anyllm.env,text/plain`,style:{display:`none`},onChange:u}),(0,P.jsx)(H,{size:`sm`,onClick:()=>t.current?.click(),disabled:e.isPending,loading:e.isPending,children:`Import .anyllm.env`}),(0,P.jsx)(H,{size:`sm`,onClick:d,children:`Export .anyllm.env`})]}),n&&(0,P.jsxs)(`div`,{style:{marginTop:10},children:[(0,P.jsxs)(`div`,{className:`dim`,style:{marginBottom:4},children:[n.applied,` variable`,n.applied===1?``:`s`,` imported.`,n.warnings.length===0&&` No issues.`]}),n.warnings.length>0&&(0,P.jsxs)(`div`,{style:{marginTop:8,padding:`8px 12px`,background:`var(--warn-dim)`,borderLeft:`3px solid var(--warn)`,borderRadius:`var(--r)`,fontSize:12},children:[(0,P.jsx)(`div`,{style:{fontWeight:600,marginBottom:4},children:`Warnings`}),n.warnings.map((e,t)=>(0,P.jsxs)(`div`,{className:`mono`,style:{fontSize:12},children:[e.line!=null&&(0,P.jsxs)(`span`,{className:`dim`,children:[`[line `,e.line,`] `]}),e.key&&(0,P.jsxs)(`span`,{children:[e.key,`: `]}),e.message]},t))]})]}),i&&(0,P.jsxs)(`div`,{style:{marginTop:10,padding:`8px 12px`,background:`var(--err-dim)`,borderLeft:`3px solid var(--err)`,borderRadius:`var(--r)`,fontSize:12},children:[(0,P.jsx)(`div`,{style:{fontWeight:600,marginBottom:4},children:`Import rejected`}),i.hard_errors.map((e,t)=>(0,P.jsx)(`div`,{className:`mono`,style:{fontSize:12},children:e},t)),i.warnings.length>0&&(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(`div`,{style:{fontWeight:600,marginTop:8,marginBottom:4},children:`Warnings (from partial parse)`}),i.warnings.map((e,t)=>(0,P.jsxs)(`div`,{className:`mono`,style:{fontSize:12},children:[e.line!=null&&(0,P.jsxs)(`span`,{className:`dim`,children:[`[line `,e.line,`] `]}),e.message]},t))]})]}),o&&(0,P.jsxs)(`div`,{style:{marginTop:10,padding:`8px 12px`,background:`var(--err-dim)`,borderLeft:`3px solid var(--err)`,borderRadius:`var(--r)`,fontSize:12},children:[`Export failed: `,o]})]})}var fc=[`a[href]`,`button:not([disabled])`,`textarea:not([disabled])`,`input:not([disabled])`,`select:not([disabled])`,`[tabindex]:not([tabindex="-1"])`].join(`,`);function pc({open:e,onClose:t,title:n,size:r=`md`,children:i,footer:a,dismissable:o=!0}){let s=(0,N.useRef)(null),c=(0,N.useRef)(null);return(0,N.useEffect)(()=>{if(!e)return;c.current=document.activeElement??null;let t=s.current;return t&&(t.querySelector(fc)??t).focus(),()=>{c.current?.focus?.()}},[e]),(0,N.useEffect)(()=>{if(!e)return;let t=document.body.style.overflow;return document.body.style.overflow=`hidden`,()=>{document.body.style.overflow=t}},[e]),(0,N.useEffect)(()=>{if(!e)return;let n=e=>{if(e.key===`Escape`&&o){e.stopPropagation(),t();return}if(e.key!==`Tab`)return;let n=s.current;if(!n)return;let r=Array.from(n.querySelectorAll(fc)).filter(e=>!e.hasAttribute(`data-focus-skip`));if(r.length===0){e.preventDefault();return}let i=r[0],a=r[r.length-1],c=document.activeElement;e.shiftKey&&c===i?(e.preventDefault(),a.focus()):!e.shiftKey&&c===a&&(e.preventDefault(),i.focus())};return document.addEventListener(`keydown`,n),()=>document.removeEventListener(`keydown`,n)},[e,o,t]),e?(0,Hn.createPortal)((0,P.jsx)(`div`,{className:`modal-backdrop-v2`,onClick:()=>{o&&t()},children:(0,P.jsxs)(`div`,{ref:s,className:`modal-v2 modal-${r}`,role:`dialog`,"aria-modal":`true`,"aria-label":n,tabIndex:-1,onClick:e=>e.stopPropagation(),children:[(0,P.jsxs)(`div`,{className:`modal-header`,children:[(0,P.jsx)(`div`,{className:`modal-title`,children:n}),(0,P.jsx)(`button`,{type:`button`,className:`modal-close`,"aria-label":`Close`,onClick:t,disabled:!o,children:`×`})]}),(0,P.jsx)(`div`,{className:`modal-body`,children:i}),a!=null&&(0,P.jsx)(`div`,{className:`modal-footer`,children:a})]})}),document.body):null}function mc({open:e,onClose:t,onConfirm:n,title:r,message:i,confirmLabel:a=`Delete`,cancelLabel:o=`Cancel`,variant:s=`danger`}){let[c,l]=(0,N.useState)(!1),[u,d]=(0,N.useState)(null),f=async()=>{l(!0),d(null);try{await n(),t()}catch(e){d(e instanceof Error?e.message:String(e))}finally{l(!1)}},p=()=>{c||(d(null),t())};return(0,P.jsxs)(pc,{open:e,onClose:p,title:r,size:`sm`,dismissable:!c,footer:(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(H,{type:`button`,onClick:p,disabled:c,children:o}),(0,P.jsx)(H,{type:`button`,tone:s===`danger`?`danger`:`primary`,onClick:f,disabled:c,loading:c,children:a})]}),children:[(0,P.jsx)(`div`,{className:`confirm-message`,children:i}),u&&(0,P.jsx)(`div`,{className:`confirm-error`,role:`alert`,children:u})]})}function hc(e){return`${Math.round(e/1e6)} MB`}function gc({cfg:e}){let t=Ga(),n=Ka(),{data:r}=qa(),i=Ja(),[a,o]=(0,N.useState)({}),[s,c]=(0,N.useState)(null);function l(){if(!s)return Promise.resolve();let e=s;return n.mutateAsync(e).then(()=>void 0)}function u(e,n){t.mutate({[e]:a[e]??n})}function d(e,n){t.mutate({[e]:n})}function f(){return(e.pxpipe_models??``).split(`,`).map(e=>e.trim()).filter(Boolean)}function p(e){let t=e.toLowerCase();return f().some(e=>t.includes(e.toLowerCase()))}function m(e,n){let r=f(),i=n?p(e)?r:[...r,e]:r.filter(t=>!e.toLowerCase().includes(t.toLowerCase()));t.mutate({pxpipe_models:i.join(`,`)})}return(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`div`,{className:`section-label`,style:{marginBottom:8},children:`Runtime`}),(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsxs)(`label`,{className:`form-label`,htmlFor:`cfg-redact-secrets`,style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,P.jsx)(`input`,{id:`cfg-redact-secrets`,type:`checkbox`,checked:e.redact_secrets,disabled:t.isPending,onChange:e=>d(`redact_secrets`,e.target.checked)}),`Redact secrets`]}),e.overridden_keys.includes(`redact_secrets`)&&(0,P.jsx)(`div`,{className:`form-row`,children:(0,P.jsx)(H,{size:`sm`,onClick:()=>c(`redact_secrets`),children:`Reset`})})]}),(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsxs)(`label`,{className:`form-label`,htmlFor:`cfg-log-bodies`,style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,P.jsx)(`input`,{id:`cfg-log-bodies`,type:`checkbox`,checked:e.log_bodies,disabled:t.isPending,onChange:e=>d(`log_bodies`,e.target.checked)}),`Log bodies`]}),e.overridden_keys.includes(`log_bodies`)&&(0,P.jsx)(`div`,{className:`form-row`,children:(0,P.jsx)(H,{size:`sm`,onClick:()=>c(`log_bodies`),children:`Reset`})})]}),(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsxs)(`label`,{className:`form-label`,htmlFor:`cfg-thinking-repair`,style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,P.jsx)(`input`,{id:`cfg-thinking-repair`,type:`checkbox`,checked:e.anthropic_thinking_repair,disabled:t.isPending,onChange:e=>d(`anthropic_thinking_repair`,e.target.checked)}),`Anthropic thinking-block repair`]}),(0,P.jsx)(`div`,{className:`dim`,style:{fontSize:12},children:`Repairs corrupted thinking/redacted_thinking blocks in Anthropic passthrough requests (applies to any backend running in BACKEND=anthropic passthrough mode, including a named backend in a multi-backend config). Off by default.`}),e.overridden_keys.includes(`anthropic_thinking_repair`)&&(0,P.jsx)(`div`,{className:`form-row`,children:(0,P.jsx)(H,{size:`sm`,onClick:()=>c(`anthropic_thinking_repair`),children:`Reset`})})]}),(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsxs)(`label`,{className:`form-label`,htmlFor:`cfg-pxpipe-compress`,style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,P.jsx)(`input`,{id:`cfg-pxpipe-compress`,type:`checkbox`,checked:e.pxpipe_compress,disabled:t.isPending,onChange:e=>d(`pxpipe_compress`,e.target.checked)}),`Image context compression (pxpipe)`]}),(0,P.jsx)(`div`,{className:`dim`,style:{fontSize:12},children:`Renders the stable system + tool-definition slab of Anthropic passthrough requests to a PNG image block to save input tokens on vision models. Off by default. Enable per-model below — only models that read imaged text reliably are offered.`}),e.overridden_keys.includes(`pxpipe_compress`)&&(0,P.jsx)(`div`,{className:`form-row`,children:(0,P.jsx)(H,{size:`sm`,onClick:()=>c(`pxpipe_compress`),children:`Reset`})}),e.pxpipe_compress&&(0,P.jsxs)(`div`,{style:{marginTop:8},children:[(0,P.jsx)(`div`,{className:`form-label`,style:{fontSize:13},children:`Models in scope (vision-capable)`}),e.pxpipe_available_models.length===0?(0,P.jsx)(`div`,{className:`dim`,style:{fontSize:12},children:`No vision-capable models in the catalog.`}):(0,P.jsx)(`div`,{style:{display:`flex`,flexWrap:`wrap`,gap:`4px 16px`},children:e.pxpipe_available_models.map(e=>(0,P.jsxs)(`label`,{style:{display:`flex`,alignItems:`center`,gap:6,fontSize:12},children:[(0,P.jsx)(`input`,{type:`checkbox`,checked:p(e),disabled:t.isPending,onChange:t=>m(e,t.target.checked)}),e]},e))}),e.overridden_keys.includes(`pxpipe_models`)&&(0,P.jsx)(`div`,{className:`form-row`,style:{marginTop:6},children:(0,P.jsx)(H,{size:`sm`,onClick:()=>c(`pxpipe_models`),children:`Reset scope`})})]})]}),(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsxs)(`label`,{className:`form-label`,htmlFor:`cfg-rtk-compress`,style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,P.jsx)(`input`,{id:`cfg-rtk-compress`,type:`checkbox`,checked:e.rtk_compress,disabled:t.isPending,onChange:e=>d(`rtk_compress`,e.target.checked)}),`Tool-output compression (RTK)`]}),(0,P.jsx)(`div`,{className:`dim`,style:{fontSize:12},children:`Command-aware filtering of tool-result text (test/build/git/log output) using the RTK filter catalog. Shrinks noisy machine output before it reaches the backend; deterministic and cache-safe. Off by default. Applies to Anthropic passthrough and translate paths.`}),e.overridden_keys.includes(`rtk_compress`)&&(0,P.jsx)(`div`,{className:`form-row`,children:(0,P.jsx)(H,{size:`sm`,onClick:()=>c(`rtk_compress`),children:`Reset`})}),e.rtk_compress&&(0,P.jsxs)(`div`,{style:{marginTop:8},children:[(0,P.jsx)(`div`,{className:`form-label`,style:{fontSize:13},children:`Models in scope (CSV, empty = all)`}),(0,P.jsx)(`input`,{type:`text`,className:`form-input`,defaultValue:e.rtk_models,placeholder:`empty = all models; e.g. claude, gpt-5`,disabled:t.isPending,onBlur:n=>{let r=n.target.value.trim();r!==(e.rtk_models??``)&&t.mutate({rtk_models:r})}},e.rtk_models),e.overridden_keys.includes(`rtk_models`)&&(0,P.jsx)(`div`,{className:`form-row`,style:{marginTop:6},children:(0,P.jsx)(H,{size:`sm`,onClick:()=>c(`rtk_models`),children:`Reset scope`})})]})]}),(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsxs)(`label`,{className:`form-label`,htmlFor:`cfg-forward-client-auth`,style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,P.jsx)(`input`,{id:`cfg-forward-client-auth`,type:`checkbox`,checked:e.forward_client_auth,disabled:t.isPending,onChange:e=>d(`forward_client_auth`,e.target.checked)}),`Forward client credential (Anthropic passthrough)`]}),(0,P.jsx)(`div`,{className:`dim`,style:{fontSize:12},children:`Forwards the client's own x-api-key/Authorization header upstream instead of the operator's configured credential (BACKEND=anthropic passthrough only, single-key/BYOK deployments). The proxy refuses to enable this with 2+ PROXY_API_KEYS entries and no PROXY_OPEN_RELAY. Off by default.`}),e.overridden_keys.includes(`forward_client_auth`)&&(0,P.jsx)(`div`,{className:`form-row`,children:(0,P.jsx)(H,{size:`sm`,onClick:()=>c(`forward_client_auth`),children:`Reset`})})]}),(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsx)(`label`,{className:`form-label`,htmlFor:`cfg-tool-guardrail-mode`,children:`Tool guardrail mode`}),(0,P.jsxs)(`div`,{className:`form-row`,children:[(0,P.jsxs)(`select`,{id:`cfg-tool-guardrail-mode`,value:e.tool_guardrail_mode,disabled:t.isPending,onChange:e=>t.mutate({tool_guardrail_mode:e.target.value}),children:[(0,P.jsx)(`option`,{value:`disabled`,children:`Disabled`}),(0,P.jsx)(`option`,{value:`standard`,children:`Standard`})]}),e.overridden_keys.includes(`tool_guardrail_mode`)&&(0,P.jsx)(H,{size:`sm`,onClick:()=>c(`tool_guardrail_mode`),children:`Reset`})]}),(0,P.jsx)(`div`,{className:`dim`,style:{fontSize:12},children:`Applies advisory guardrails to tool calls the proxy auto-executes. Disabled by default.`})]}),(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsx)(`label`,{className:`form-label`,htmlFor:`cfg-optimizer-mode`,children:`Prompt compression (optimizer)`}),(0,P.jsxs)(`div`,{className:`form-row`,children:[(0,P.jsxs)(`select`,{id:`cfg-optimizer-mode`,value:e.optimizer_mode,disabled:t.isPending||!!r?.compiled_in&&!r?.present,onChange:e=>t.mutate({optimizer_mode:e.target.value}),children:[(0,P.jsx)(`option`,{value:`off`,children:`Off`}),(0,P.jsx)(`option`,{value:`shadow`,children:`Shadow (report only)`}),(0,P.jsx)(`option`,{value:`live`,children:`Live (compress)`})]}),e.overridden_keys.includes(`optimizer_mode`)&&(0,P.jsx)(H,{size:`sm`,onClick:()=>c(`optimizer_mode`),children:`Reset`})]}),(0,P.jsx)(`div`,{className:`dim`,style:{fontSize:12},children:`Frozen-Frontier compression of long conversation history (latest turn untouched). Off by default.`}),r&&!r.compiled_in&&(0,P.jsxs)(`div`,{className:`dim`,style:{fontSize:12,marginTop:8},children:[`Heuristic scorer only. Rebuild the proxy with `,(0,P.jsx)(`code`,{children:`--features optimizer-onnx`}),` to enable the LLMLingua-2 ONNX scorer.`]}),r?.compiled_in&&!r.present&&!r.downloading&&(0,P.jsxs)(`div`,{className:`form-row`,style:{marginTop:8},children:[(0,P.jsxs)(H,{size:`sm`,disabled:i.isPending,onClick:()=>i.mutate(),children:[`Download model (`,hc(r.size_bytes),`)`]}),(0,P.jsx)(`span`,{className:`dim`,style:{fontSize:12},children:`Required before enabling. Verified against a pinned sha256.`})]}),r?.downloading&&(0,P.jsxs)(`div`,{className:`dim`,style:{fontSize:12,marginTop:8},children:[`Downloading and verifying model (`,hc(r.size_bytes),`)…`]}),r?.error&&!r.downloading&&(0,P.jsxs)(`div`,{style:{fontSize:12,marginTop:8,color:`var(--danger, #c0392b)`},children:[`Download failed: `,r.error]}),r?.compiled_in&&r.present&&(0,P.jsx)(`div`,{className:`dim`,style:{fontSize:12,marginTop:8},children:`ONNX scorer ready — live mode uses LLMLingua-2 (loaded on the next request).`})]}),e.entries.filter(e=>![`redact_secrets`,`log_bodies`,`anthropic_thinking_repair`,`pxpipe_compress`,`pxpipe_models`,`rtk_compress`,`rtk_models`,`forward_client_auth`,`tool_guardrail_mode`,`optimizer_mode`].includes(e.key)).map(e=>{let t=`cfg-${e.key}`;return(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsx)(`label`,{className:`form-label`,htmlFor:t,children:e.key}),(0,P.jsxs)(`div`,{className:`form-row`,children:[(0,P.jsx)(`input`,{id:t,name:e.key,value:a[e.key]??e.value,onChange:t=>o(n=>({...n,[e.key]:t.target.value}))}),(0,P.jsx)(H,{tone:`primary`,size:`sm`,onClick:()=>u(e.key,e.value),children:`Save`}),(0,P.jsx)(H,{size:`sm`,onClick:()=>c(e.key),children:`Reset`})]})]},e.key)}),(0,P.jsx)(mc,{open:s!==null,onClose:()=>c(null),onConfirm:l,title:`Reset override?`,message:(0,P.jsxs)(P.Fragment,{children:[`Reset override for `,(0,P.jsx)(`span`,{className:`mono`,children:s}),`? The runtime value will revert to the env-file or default. Active connections are not affected.`]}),confirmLabel:`Reset`,variant:`primary`})]})}function _c({envData:e}){return e?(0,P.jsxs)(`div`,{className:`readonly-section`,style:{marginTop:16},children:[(0,P.jsx)(`div`,{className:`section-label`,children:`Environment`}),(0,P.jsx)(`div`,{style:{display:`grid`,gridTemplateColumns:`220px 1fr`,gap:`4px 12px`,marginTop:8,fontSize:12},children:Object.entries(e).map(([e,t])=>(0,P.jsxs)(N.Fragment,{children:[(0,P.jsx)(`span`,{className:`dim`,children:e}),(0,P.jsx)(`span`,{className:`mono`,children:t})]},e))})]}):null}function vc({configured:e=!0}){let{data:t,isLoading:n,error:r}=Wa(),{data:i}=Ya(),{data:a}=Fa(),o=a?`http://${window.location.hostname}:${a.proxy_port}`:``;return(0,P.jsxs)(`div`,{children:[a&&(0,P.jsxs)(`div`,{className:`proxy-status-badge`,style:{marginBottom:16,fontSize:13,display:`flex`,alignItems:`center`,gap:8},children:[(0,P.jsx)(`span`,{style:{color:a.proxy_running?`var(--ok, green)`:`var(--warn, orange)`},children:a.proxy_running?`●`:`○`}),a.proxy_running?(0,P.jsxs)(`span`,{children:[`Proxy running — `,(0,P.jsx)(`span`,{className:`mono`,children:o})]}):(0,P.jsxs)(`span`,{children:[`Proxy unreachable on `,(0,P.jsx)(`span`,{className:`mono`,children:o})]})]}),(0,P.jsx)(cc,{configured:e}),(0,P.jsx)(dc,{}),(0,P.jsx)(tc,{loading:n,error:r?.message}),t&&(0,P.jsx)(gc,{cfg:t}),(0,P.jsx)(_c,{envData:i})]})}function yc({variant:e}){return(0,P.jsx)(`span`,{className:`badge badge-${e}`,children:e})}function bc({spent:e,limit:t}){if(!t)return(0,P.jsx)(`span`,{className:`dim`,children:`—`});let n=Math.min(e/t*100,100),r=n>=95?`danger`:n>=80?`warn`:``;return(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`div`,{className:`budget-bar`,children:(0,P.jsx)(`div`,{className:`budget-bar-fill${r?` ${r}`:``}`,style:{width:`${n}%`}})}),(0,P.jsxs)(`span`,{className:`dim`,style:{fontSize:10},children:[`$`,e.toFixed(4),` / $`,t.toFixed(2)]})]})}function xc(e){let t=e.trim();return t?Number(t):null}function Sc(e){return{description:e.description.trim()||null,max_budget_usd:xc(e.spendLimit),rpm_limit:xc(e.rpmLimit)}}function Cc({onCreated:e}){let t=Ba(),[n,r]=(0,N.useState)(``),[i,a]=(0,N.useState)(``),[o,s]=(0,N.useState)(``);function c(){t.mutate(Sc({description:n,spendLimit:i,rpmLimit:o}),{onSuccess:t=>{r(``),a(``),s(``),e(t.key)}})}return(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsx)(`div`,{className:`form-label`,children:`Create Key`}),(0,P.jsxs)(`form`,{onSubmit:e=>{e.preventDefault(),c()},children:[(0,P.jsxs)(`div`,{className:`form-row`,style:{flexWrap:`wrap`},children:[(0,P.jsx)(`input`,{name:`description`,placeholder:`Description`,value:n,onChange:e=>r(e.target.value)}),(0,P.jsx)(`input`,{name:`max_budget_usd`,placeholder:`Spend limit USD`,type:`number`,value:i,onChange:e=>a(e.target.value),style:{width:160}}),(0,P.jsx)(`input`,{name:`rpm_limit`,placeholder:`RPM limit`,type:`number`,value:o,onChange:e=>s(e.target.value),style:{width:100}}),(0,P.jsx)(H,{type:`submit`,tone:`primary`,loading:t.isPending,children:`Create`})]}),!i&&!o&&(0,P.jsx)(`div`,{className:`form-hint`,style:{color:`var(--warn)`,marginTop:6},children:`No limits set — this key will be unrestricted (unlimited spend and requests).`})]})]})}function wc({vk:e,onClose:t}){let n=Va(),r=Ha(),[i,a]=(0,N.useState)(e.description??``),[o,s]=(0,N.useState)(e.max_budget_usd?.toString()??``),[c,l]=(0,N.useState)(e.rpm_limit?.toString()??``),[u,d]=(0,N.useState)(new Set(e.allowed_routes??[])),[f,p]=(0,N.useState)(!1),{data:m}=fo();function h(){n.mutate({id:e.id,body:{description:i||null,max_budget_usd:o?Number(o):null,rpm_limit:c?Number(c):null,allowed_routes:u.size>0?[...u]:null,expires_at:e.expires_at,tpm_limit:e.tpm_limit,budget_duration:e.budget_duration,allowed_models:e.allowed_models}},{onSuccess:t})}function g(){return r.mutateAsync(e.id)}return(0,N.useEffect)(()=>{r.isSuccess&&t()},[r.isSuccess]),(0,P.jsxs)(P.Fragment,{children:[(0,P.jsxs)(pc,{open:!0,onClose:t,title:`Edit Key — ${e.key_prefix}…`,dismissable:!n.isPending,footer:(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(H,{tone:`danger`,onClick:()=>p(!0),disabled:n.isPending,style:{marginRight:`auto`},children:`Revoke`}),(0,P.jsx)(H,{onClick:t,disabled:n.isPending,children:`Cancel`}),(0,P.jsx)(H,{tone:`primary`,onClick:h,loading:n.isPending,children:`Save`})]}),children:[(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsx)(`label`,{className:`form-label`,htmlFor:`vk-desc`,children:`Description`}),(0,P.jsx)(`input`,{id:`vk-desc`,name:`description`,value:i,onChange:e=>a(e.target.value),style:{width:`100%`}})]}),(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsx)(`label`,{className:`form-label`,htmlFor:`vk-spend`,children:`Spend limit (USD)`}),(0,P.jsx)(`input`,{id:`vk-spend`,name:`spend_limit`,value:o,onChange:e=>s(e.target.value),type:`number`,min:`0`,step:`0.01`})]}),(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsx)(`label`,{className:`form-label`,htmlFor:`vk-rpm`,children:`RPM limit`}),(0,P.jsx)(`input`,{id:`vk-rpm`,name:`rpm_limit`,value:c,onChange:e=>l(e.target.value),type:`number`,min:`0`})]}),(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsxs)(`div`,{className:`form-label`,children:[`Allowed routes `,u.size===0&&(0,P.jsx)(`span`,{className:`hint`,children:`(all routes)`})]}),(0,P.jsxs)(`div`,{className:`route-scope-list`,children:[m?.routes.map(e=>(0,P.jsxs)(`label`,{className:`route-scope-item`,children:[(0,P.jsx)(`input`,{type:`checkbox`,name:`route-${e.id}`,checked:u.has(e.id),onChange:()=>{d(t=>{let n=new Set(t);return n.has(e.id)?n.delete(e.id):n.add(e.id),n})}}),(0,P.jsx)(`span`,{children:e.name})]},e.id)),!m?.routes.length&&(0,P.jsx)(`span`,{className:`hint`,children:`No routes configured`})]})]})]}),(0,P.jsx)(mc,{open:f,onClose:()=>p(!1),onConfirm:g,title:`Revoke key?`,message:(0,P.jsxs)(P.Fragment,{children:[`Revoking `,(0,P.jsxs)(`span`,{className:`mono`,children:[e.key_prefix,`…`]}),` will immediately reject any request using it. This cannot be undone.`]}),confirmLabel:`Revoke`})]})}async function Tc(e){try{return await navigator.clipboard.writeText(e),!0}catch{return!1}}function Ec(){let e=za(),[t,n]=Vi(),r=t.get(`q`)??``,i=t.get(`edit`),[a,o]=(0,N.useState)(null),[s,c]=(0,N.useState)(null);(0,N.useEffect)(()=>{if(!i||!e.data)return;let t=e.data.find(e=>String(e.id)===i);t&&c(t)},[i,e.data]);function l(){if(c(null),t.has(`edit`)){let e=new URLSearchParams(t);e.delete(`edit`),n(e,{replace:!0})}}function u(e){c(e);let r=new URLSearchParams(t);r.set(`edit`,String(e.id)),n(r,{replace:!0})}function d(e){let r=new URLSearchParams(t);e?r.set(`q`,e):r.delete(`q`),n(r,{replace:!0})}let f=r.trim().toLowerCase(),p=(0,N.useMemo)(()=>f?(e.data??[]).filter(e=>{let t=(e.description??``).toLowerCase(),n=e.key_prefix.toLowerCase();return t.includes(f)||n.includes(f)}):e.data??[],[e.data,f]);return(0,P.jsxs)(`div`,{children:[(0,P.jsx)(Cc,{onCreated:o}),a&&(0,P.jsxs)(`div`,{className:`key-result`,children:[(0,P.jsx)(`div`,{className:`key-result-label`,children:`New key (copy now — not shown again)`}),(0,P.jsxs)(`div`,{className:`key-result-value`,children:[(0,P.jsx)(`span`,{className:`mono`,children:a}),(0,P.jsxs)(`div`,{className:`key-result-actions`,children:[(0,P.jsx)(H,{size:`sm`,onClick:async()=>{ma(await Tc(a)?{variant:`success`,message:`Key copied to clipboard`}:{variant:`error`,message:`Copy failed — select and copy manually`})},children:`Copy`}),(0,P.jsx)(`button`,{type:`button`,className:`key-result-dismiss`,"aria-label":`Dismiss`,onClick:()=>o(null),children:`×`})]})]})]}),(0,P.jsxs)(`div`,{className:`toolbar`,children:[(0,P.jsx)(`input`,{type:`search`,name:`keys-search`,placeholder:`Search by description or prefix…`,value:r,onChange:e=>d(e.target.value),className:`toolbar-search`}),e.data&&(0,P.jsxs)(`span`,{className:`dim toolbar-count`,children:[p.length,` of `,e.data.length]})]}),(0,P.jsx)(ac,{query:e,errorTitle:`Failed to load keys`,empty:{when:e=>e.length===0,render:()=>(0,P.jsxs)(`div`,{className:`empty-cta`,children:[(0,P.jsx)(`div`,{className:`empty-cta-title`,children:`No virtual keys yet`}),(0,P.jsx)(`div`,{className:`empty-cta-body`,children:`Use the form above to create one. Keys are shown once at creation and hashed in storage.`})]})},children:()=>p.length===0?(0,P.jsxs)(`div`,{className:`empty`,children:[`No keys match "`,r,`".`]}):(0,P.jsxs)(`table`,{className:`keys-grid`,children:[(0,P.jsx)(`thead`,{children:(0,P.jsxs)(`tr`,{children:[(0,P.jsx)(`th`,{children:`Prefix`}),(0,P.jsx)(`th`,{children:`Description`}),(0,P.jsx)(`th`,{children:`Status`}),(0,P.jsx)(`th`,{children:`Spend`}),(0,P.jsx)(`th`,{children:`Requests`}),(0,P.jsx)(`th`,{children:`Created`})]})}),(0,P.jsx)(`tbody`,{children:p.map(e=>(0,P.jsxs)(`tr`,{style:{cursor:`pointer`},onClick:()=>u(e),children:[(0,P.jsxs)(`td`,{className:`mono`,children:[e.key_prefix,`…`]}),(0,P.jsx)(`td`,{className:`dim`,children:e.description??`—`}),(0,P.jsx)(`td`,{children:(0,P.jsx)(yc,{variant:e.status})}),(0,P.jsx)(`td`,{children:(0,P.jsx)(bc,{spent:e.period_spend_usd,limit:e.max_budget_usd})}),(0,P.jsx)(`td`,{className:`mono`,children:e.total_requests.toLocaleString()}),(0,P.jsx)(`td`,{className:`mono dim`,children:e.created_at.slice(0,10)})]},e.id))})]})}),s&&(0,P.jsx)(wc,{vk:s,onClose:l},s.id)]})}var Dc={openrouter:{text:`Public, no key needed`,needsKey:!1},deepinfra:{text:`Public, no key needed`,needsKey:!1},ollama:{text:`No key needed (local)`,needsKey:!1},configured:{text:`API key required`,needsKey:!0},custom:{text:`API key may be required`,needsKey:!0}};function Oc(){return(0,P.jsx)(`svg`,{width:`12`,height:`12`,viewBox:`0 0 16 16`,fill:`none`,className:`key-icon-inline`,children:(0,P.jsx)(`path`,{d:`M10.5 1a4.5 4.5 0 0 0-4.1 6.35L2 11.75V15h3.25v-2H7v-1.75h1.75L9.65 10.4A4.5 4.5 0 1 0 10.5 1zm1 3a1 1 0 1 1 0-2 1 1 0 0 1 0 2z`,fill:`currentColor`})})}function kc(){let e=Xa(),t=Za(),n=Qa(),r=$a(),{data:i}=Ua(),{data:a}=so(),[o,s]=(0,N.useState)(``),[c,l]=(0,N.useState)(``),[u,d]=(0,N.useState)(``),[f,p]=(0,N.useState)(``),[m,h]=(0,N.useState)(`openrouter`),[g,_]=(0,N.useState)(``),[v,y]=(0,N.useState)(``),[b,x]=(0,N.useState)(null),S=Dc[m]??Dc.custom;function C(){r.mutate({source:m,...m===`custom`?{url:g}:{}})}function w(){t.mutate({model_name:o,actual_model:u,backend_name:f},{onSuccess:()=>{s(``),l(``),d(``),p(``)}})}function T(){return b?n.mutateAsync(b).then(()=>void 0):Promise.resolve()}let E=v.trim().toLowerCase(),D=(0,N.useMemo)(()=>{let t=e.data?.models??[];return E?t.filter(e=>e.model_name.toLowerCase().includes(E)):t},[e.data,E]);return(0,P.jsxs)(`div`,{children:[(0,P.jsxs)(`div`,{className:`models-discover`,children:[(0,P.jsx)(`div`,{className:`section-label`,children:`Discover Models`}),(0,P.jsxs)(`div`,{className:`models-discover-row`,children:[(0,P.jsxs)(`select`,{value:m,onChange:e=>{h(e.target.value),r.reset()},children:[(0,P.jsx)(`option`,{value:`openrouter`,children:`OpenRouter`}),(0,P.jsx)(`option`,{value:`deepinfra`,children:`DeepInfra`}),(0,P.jsx)(`option`,{value:`ollama`,children:`Ollama (local)`}),(0,P.jsx)(`option`,{value:`configured`,children:`Configured backend`}),(0,P.jsx)(`option`,{value:`custom`,children:`Custom URL`})]}),m===`custom`&&(0,P.jsx)(`input`,{name:`discover-url`,placeholder:`https://api.example.com`,value:g,onChange:e=>_(e.target.value),style:{minWidth:220}}),(0,P.jsx)(H,{onClick:C,disabled:r.isPending||m===`custom`&&!g,loading:r.isPending,children:`Fetch`}),(0,P.jsxs)(`span`,{className:`dim models-discover-hint`,children:[S.needsKey&&(0,P.jsx)(Oc,{}),S.text]})]}),r.isError&&(0,P.jsx)(`div`,{className:`inline-error`,children:r.error.message}),r.data&&r.data.models.length>0&&(0,P.jsxs)(`div`,{className:`models-discover-results`,children:[(0,P.jsxs)(`div`,{className:`dim models-discover-count`,children:[r.data.models.length,` model`,r.data.models.length===1?``:`s`,` found. Click to populate the form below.`]}),(0,P.jsx)(`div`,{className:`models-discover-list`,children:r.data.models.map(e=>(0,P.jsxs)(`div`,{onClick:()=>{if(d(e.id),!o||o===c){let t=e.name&&e.name!==e.id?e.name:e.id;s(t),l(t)}},className:`models-discover-item${u===e.id?` is-selected`:``}`,children:[(0,P.jsx)(`span`,{className:`mono`,children:e.id}),e.name&&e.name!==e.id&&(0,P.jsx)(`span`,{className:`dim models-discover-item-name`,children:e.name})]},e.id))})]}),r.data&&r.data.models.length===0&&(0,P.jsx)(`div`,{className:`dim models-discover-count`,children:`No models returned.`})]}),(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsx)(`div`,{className:`form-label`,children:`Add Model`}),(0,P.jsxs)(`div`,{className:`form-row`,style:{flexWrap:`wrap`},children:[(0,P.jsx)(`input`,{name:`model-name`,placeholder:`Virtual name`,value:o,onChange:e=>s(e.target.value)}),(0,P.jsx)(`input`,{name:`model-id`,placeholder:`Model ID`,value:u,onChange:e=>d(e.target.value)}),(0,P.jsxs)(`select`,{name:`backend`,value:f,onChange:e=>p(e.target.value),children:[(0,P.jsx)(`option`,{value:``,children:`Backend…`}),i?.map(e=>(0,P.jsx)(`option`,{value:e.name,children:e.name},e.name)),a?.backends.map(e=>(0,P.jsxs)(`option`,{value:e.name,children:[e.name,` (managed)`]},`managed-${e.name}`))]}),(0,P.jsx)(H,{tone:`primary`,onClick:w,disabled:!o||!u||!f||t.isPending,loading:t.isPending,children:`Add`})]}),t.isError&&(0,P.jsx)(`div`,{className:`inline-error`,children:t.error.message})]}),(0,P.jsxs)(`div`,{className:`toolbar`,children:[(0,P.jsx)(`input`,{type:`search`,name:`models-search`,placeholder:`Search models…`,value:v,onChange:e=>y(e.target.value),className:`toolbar-search`}),e.data&&(0,P.jsxs)(`span`,{className:`dim toolbar-count`,children:[D.length,` of `,e.data.models.length]})]}),(0,P.jsx)(ac,{query:e,errorTitle:`Failed to load models`,empty:{when:e=>(e.models?.length??0)===0,render:()=>(0,P.jsxs)(U,{className:`empty-cta`,children:[(0,P.jsx)(`div`,{className:`empty-cta-title`,children:`No models configured`}),(0,P.jsx)(`div`,{className:`empty-cta-body`,children:`Add a model above, or use Discover to pull a catalog from OpenRouter, DeepInfra, Ollama, or a custom endpoint.`})]})},children:e=>D.length===0?(0,P.jsxs)(`div`,{className:`empty`,children:[`No models match "`,v,`".`]}):(0,P.jsxs)(`table`,{className:`route-table`,children:[(0,P.jsx)(`thead`,{children:(0,P.jsxs)(`tr`,{children:[(0,P.jsx)(`th`,{children:`Virtual Name`}),(0,P.jsx)(`th`,{children:`Deployments`}),(0,P.jsx)(`th`,{children:`Strategy`}),(0,P.jsx)(`th`,{})]})}),(0,P.jsx)(`tbody`,{children:D.map(t=>(0,P.jsxs)(`tr`,{children:[(0,P.jsx)(`td`,{className:`mono`,children:t.model_name}),(0,P.jsx)(`td`,{className:`mono`,children:t.deployments}),(0,P.jsx)(`td`,{className:`dim`,children:e.strategy??`—`}),(0,P.jsx)(`td`,{children:(0,P.jsx)(H,{tone:`danger`,size:`sm`,onClick:()=>x(t.model_name),children:`Remove`})})]},t.model_name))})]})}),(0,P.jsx)(mc,{open:b!==null,onClose:()=>x(null),onConfirm:T,title:`Remove model?`,message:(0,P.jsxs)(P.Fragment,{children:[`Remove model `,(0,P.jsx)(`span`,{className:`mono`,children:b}),`? Requests using this virtual name will fail until another model with the same name is added.`]}),confirmLabel:`Remove`})]})}function Ac(){let[e,t]=Vi(),n=Math.max(1,Number(e.get(`page`)??`1`)||1);function r(r){let i=new URLSearchParams(e),a=r(n);a<=1?i.delete(`page`):i.set(`page`,String(a)),t(i,{replace:!0})}return(0,P.jsx)(`div`,{children:(0,P.jsx)(ac,{query:eo({page:n,page_size:50}),errorTitle:`Failed to load audit log`,empty:{when:e=>e.entries.length===0&&n===1,render:()=>(0,P.jsxs)(`div`,{className:`empty-cta`,children:[(0,P.jsx)(`div`,{className:`empty-cta-title`,children:`No audit entries yet`}),(0,P.jsx)(`div`,{className:`empty-cta-body`,children:`Admin actions (creating keys, editing routes, managing backends) are recorded here.`})]})},children:e=>(0,P.jsxs)(P.Fragment,{children:[(0,P.jsxs)(`table`,{className:`route-table`,children:[(0,P.jsx)(`thead`,{children:(0,P.jsxs)(`tr`,{children:[(0,P.jsx)(`th`,{children:`Time`}),(0,P.jsx)(`th`,{children:`Action`}),(0,P.jsx)(`th`,{children:`Target`}),(0,P.jsx)(`th`,{children:`Detail`}),(0,P.jsx)(`th`,{children:`IP`})]})}),(0,P.jsx)(`tbody`,{children:e.entries.map(e=>(0,P.jsxs)(`tr`,{children:[(0,P.jsx)(`td`,{className:`mono dim`,children:e.timestamp.slice(0,19)}),(0,P.jsx)(`td`,{className:`mono`,children:e.action}),(0,P.jsxs)(`td`,{className:`dim`,children:[e.target_type,e.target_id?` #${e.target_id}`:``]}),(0,P.jsx)(`td`,{className:`dim audit-detail`,children:e.detail??`—`}),(0,P.jsx)(`td`,{className:`mono dim`,children:e.source_ip??`—`})]},e.id))})]}),(0,P.jsx)(ic,{page:n,hasMore:e.has_more,onPrev:()=>r(e=>Math.max(1,e-1)),onNext:()=>r(e=>e+1)})]})})})}function jc({routes:e}){let t=[...e].sort((e,t)=>t.requests_per_min-e.requests_per_min);return(0,P.jsxs)(`table`,{className:`route-table`,children:[(0,P.jsx)(`thead`,{children:(0,P.jsxs)(`tr`,{children:[(0,P.jsx)(`th`,{children:`Route`}),(0,P.jsx)(`th`,{children:`Req/min`}),(0,P.jsx)(`th`,{children:`Error rate`}),(0,P.jsx)(`th`,{children:`Avg latency`}),(0,P.jsx)(`th`,{children:`P95 latency`}),(0,P.jsx)(`th`,{children:`Total`})]})}),(0,P.jsx)(`tbody`,{children:t.map(e=>(0,P.jsxs)(`tr`,{children:[(0,P.jsx)(`td`,{className:`mono`,children:e.path}),(0,P.jsx)(`td`,{className:`mono`,children:e.requests_per_min.toFixed(2)}),(0,P.jsxs)(`td`,{className:`mono`,style:{color:e.error_rate>.05?`var(--err)`:e.error_rate>.01?`var(--warn)`:void 0},children:[(e.error_rate*100).toFixed(1),`%`]}),(0,P.jsxs)(`td`,{className:`mono`,children:[e.avg_latency_ms.toFixed(0),`ms`]}),(0,P.jsxs)(`td`,{className:`mono`,children:[e.p95_latency_ms,`ms`]}),(0,P.jsx)(`td`,{className:`mono`,children:e.total_requests.toLocaleString()})]},e.path))})]})}var Mc=[`#e8a030`,`#d4922b`,`#c07820`,`#a86015`,`#8c500a`],Nc=[`#6eb5c0`,`#5aa0ab`,`#468b96`,`#327681`,`#1e616c`];function Pc(){let[e,t]=(0,N.useState)(6),{data:n,isLoading:r,error:i}=to(e),a=n?.routes??[],o=a.slice(0,5).map((e,t)=>{let r=(n?.series??[]).filter(t=>t.path===e.path).map(e=>e.requests);return{label:e.path,color:Mc[t%Mc.length],data:r}});return(0,P.jsxs)(`div`,{children:[(0,P.jsxs)(`div`,{className:`section-header`,children:[(0,P.jsx)(`span`,{className:`section-label`,children:`Traffic`}),(0,P.jsxs)(`select`,{value:e,onChange:e=>t(Number(e.target.value)),children:[(0,P.jsx)(`option`,{value:1,children:`Last 1 hour`}),(0,P.jsx)(`option`,{value:6,children:`Last 6 hours`}),(0,P.jsx)(`option`,{value:24,children:`Last 24 hours`})]})]}),(0,P.jsx)(tc,{loading:r,error:i?.message}),n&&(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(jc,{routes:n.routes}),(0,P.jsxs)(`div`,{className:`operator-grid`,style:{marginTop:16},children:[(0,P.jsxs)(U,{className:`chart-card`,children:[(0,P.jsx)(`div`,{className:`chart-header`,children:(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`div`,{className:`chart-title`,children:`Requests / min by route`}),(0,P.jsx)(`div`,{className:`chart-subtitle`,children:`Stacked over time window`})]})}),a.length===0?(0,P.jsx)(`div`,{className:`empty`,children:`No routes`}):(0,P.jsx)(ec,{series:o})]}),(0,P.jsxs)(U,{className:`chart-card`,children:[(0,P.jsx)(`div`,{className:`chart-header`,children:(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`div`,{className:`chart-title`,children:`Avg latency per route`}),(0,P.jsx)(`div`,{className:`chart-subtitle`,children:`ms`})]})}),a.length===0?(0,P.jsx)(`div`,{className:`empty`,children:`No routes`}):(0,P.jsx)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:8,paddingTop:8},children:a.slice(0,5).map((e,t)=>{let n=Math.max(...a.slice(0,5).map(e=>e.avg_latency_ms),1),r=e.avg_latency_ms/n*100;return(0,P.jsxs)(`div`,{children:[(0,P.jsxs)(`div`,{style:{display:`flex`,justifyContent:`space-between`,fontSize:11,marginBottom:2},children:[(0,P.jsx)(`span`,{className:`mono dim`,style:{overflow:`hidden`,textOverflow:`ellipsis`,whiteSpace:`nowrap`,maxWidth:`70%`},children:e.path}),(0,P.jsxs)(`span`,{className:`mono`,children:[e.avg_latency_ms.toFixed(0),`ms`]})]}),(0,P.jsx)(`div`,{style:{height:6,background:`var(--border)`,borderRadius:0},children:(0,P.jsx)(`div`,{style:{height:`100%`,width:`${r}%`,background:Nc[t%Nc.length],borderRadius:0}})})]},e.path)})})]})]})]})]})}function Fc(e){let t=Math.floor(Date.now()/1e3-e),n=Math.floor(t/86400),r=Math.floor(t%86400/3600),i=Math.floor(t%3600/60);return n>0?`${n}d ${r}h ${i}m`:r>0?`${r}h ${i}m`:`${i}m`}function Ic({proxy:e}){return(0,P.jsxs)(`div`,{className:`uptime-proxy`,children:[(0,P.jsxs)(`div`,{className:`uptime-proxy-stats`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`div`,{className:`section-label`,children:`Uptime (30d)`}),(0,P.jsxs)(`div`,{className:`uptime-pct`,children:[e.uptime_pct_30d.toFixed(2),`%`]})]}),(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`div`,{className:`section-label`,children:`Running`}),(0,P.jsx)(`div`,{className:`stat-value`,style:{fontSize:16},children:Fc(e.started_at)})]})]}),(0,P.jsx)(`div`,{className:`section-label`,style:{marginBottom:4},children:`30-day history`}),(0,P.jsx)(`div`,{className:`history-bar`,children:e.history.map(e=>(0,P.jsx)(`div`,{className:`history-day ${e.status}`,title:`${e.date}: ${e.status}`},e.date))})]})}var Lc=Bs;function Rc({b:e}){let t=e.status===`up`?`ok`:e.status===`down`?`err`:`dim`,n=e.last_checked_at?new Date(e.last_checked_at*1e3).toLocaleTimeString():`—`;return(0,P.jsxs)(`tr`,{children:[(0,P.jsx)(`td`,{className:`mono`,children:e.name}),(0,P.jsxs)(`td`,{children:[(0,P.jsx)(Lc,{status:t,pulse:e.status===`up`}),e.status]}),(0,P.jsxs)(`td`,{className:`mono`,children:[e.uptime_pct_30d.toFixed(2),`%`]}),(0,P.jsx)(`td`,{className:`mono dim`,children:n}),(0,P.jsx)(`td`,{className:`mono dim`,children:e.last_latency_ms==null?`—`:`${e.last_latency_ms}ms`}),(0,P.jsx)(`td`,{children:(0,P.jsx)(`div`,{className:`history-bar`,style:{height:12},children:e.history.map(e=>(0,P.jsx)(`div`,{className:`history-day ${e.status}`,title:`${e.date}: ${e.status}`},e.date))})})]})}function zc(){let{data:e,isLoading:t,error:n}=no();return(0,P.jsxs)(`div`,{children:[(0,P.jsx)(tc,{loading:t,error:n?.message}),e&&(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(Ic,{proxy:e.proxy}),(0,P.jsx)(`div`,{className:`section-label`,style:{marginTop:16,marginBottom:8},children:`Backend Availability`}),(0,P.jsxs)(`table`,{className:`backend-health-table`,children:[(0,P.jsx)(`thead`,{children:(0,P.jsxs)(`tr`,{children:[(0,P.jsx)(`th`,{children:`Backend`}),(0,P.jsx)(`th`,{children:`Status`}),(0,P.jsx)(`th`,{children:`Uptime (30d)`}),(0,P.jsx)(`th`,{children:`Last checked`}),(0,P.jsx)(`th`,{children:`Latency`}),(0,P.jsx)(`th`,{children:`History`})]})}),(0,P.jsx)(`tbody`,{children:e.backends.slice().sort((e,t)=>e.name.localeCompare(t.name)).map(e=>(0,P.jsx)(Rc,{b:e},e.name))})]})]})]})}function Bc(e){let t=e.trim().replace(/\/+$/,``);return t?t.endsWith(`/models`)?t:t.endsWith(`/v1`)?`${t}/models`:`${t}/v1/models`:``}function Vc(e){let t=[],n=e.env_vars.length>0?e.env_vars[0]:null,{protocol:r,auth:i,default_base_url:a}=e;return r===`openai_compat`&&i===`bearer`?(t.push({name:`api_key`,label:`API Key`,type:`password`,required:!0,group:`auth`,...n?{hint:`or set ${n} env var`}:{}}),t.push({name:`api_base`,label:`API Base URL`,type:`url`,required:!a,group:`endpoint`,...a?{placeholder:a}:{}})):r===`openai_compat`&&i===`none`?(t.push({name:`api_base`,label:`API Base URL`,type:`url`,required:!a,group:`endpoint`,...a?{placeholder:a}:{}}),t.push({name:`api_key`,label:`API Key (optional)`,type:`password`,required:!1,group:`auth`,hint:`Only if your local server enforces a key`})):r===`azure_openai`&&i===`azure_api_key`?(t.push({name:`api_key`,label:`Azure API Key`,type:`password`,required:!0,group:`auth`}),t.push({name:`api_base`,label:`Endpoint URL`,type:`url`,required:!0,placeholder:`https://.openai.azure.com`,group:`endpoint`}),t.push({name:`deployment`,label:`Deployment Name`,type:`text`,required:!0,group:`endpoint`}),t.push({name:`api_version`,label:`API Version`,type:`text`,required:!0,placeholder:`2024-08-01-preview`,group:`endpoint`})):r===`vertex_ai`&&i===`google_api_key`?(t.push({name:`api_key`,label:`API Key`,type:`password`,required:!0,group:`auth`}),t.push({name:`project`,label:`GCP Project ID`,type:`text`,required:!0,group:`endpoint`}),t.push({name:`region`,label:`GCP Region`,type:`text`,required:!0,placeholder:`us-central1`,group:`endpoint`})):r===`bedrock_native`&&i===`aws_sigv4`?(t.push({name:`aws_access_key_id`,label:`AWS Access Key ID`,type:`text`,required:!0,group:`auth`}),t.push({name:`aws_secret_access_key`,label:`AWS Secret Access Key`,type:`password`,required:!0,group:`auth`}),t.push({name:`aws_session_token`,label:`AWS Session Token`,type:`password`,required:!1,group:`auth`}),t.push({name:`region`,label:`AWS Region`,type:`text`,required:!0,placeholder:`us-east-1`,group:`endpoint`})):(r===`gemini_openai`||r===`gemini_native`)&&i===`google_api_key`||r===`anthropic_native`&&i===`bearer`?t.push({name:`api_key`,label:`API Key`,type:`password`,required:!0,group:`auth`,...n?{hint:`or set ${n} env var`}:{}}):(i.includes(`bearer`)&&t.push({name:`api_key`,label:`API Key`,type:`password`,required:!0,group:`auth`}),(i===`none`||i.includes(`bearer`))&&t.push({name:`api_base`,label:`API Base URL`,type:`url`,required:!a,group:`endpoint`,...a?{placeholder:a}:{}}),i===`none`&&t.push({name:`api_key`,label:`API Key (optional)`,type:`password`,required:!1,group:`auth`,hint:`Only if your server enforces a key`})),t.push({name:`rpm`,label:`Rate Limit (req/min)`,type:`number`,required:!1,group:`limits`,hint:`Stored for reference; not enforced on managed backends`}),t.push({name:`tpm`,label:`Token Limit (tokens/min)`,type:`number`,required:!1,group:`limits`,hint:`Stored for reference; not enforced on managed backends`}),t}var Hc={openai:0,anthropic:0,gemini:0,vertex:0,azure:1,bedrock:1,mistral:1,groq:1,deepseek:1,xai:1,together_ai:2,openrouter:2,fireworks_ai:2,perplexity:2,cohere_chat:2,cerebras:2,sambanova:2,ollama:2,deepinfra:2,replicate:2,nvidia_nim:2},Uc={0:`Top providers`,1:`Popular`,2:`Notable`,3:`More providers`},Wc=new Set([`gemini`,`groq`,`openrouter`,`mistral`,`deepseek`,`cohere_chat`,`cohere`]);function Gc(e){let t=e.default_base_url??``;return/localhost|127\.0\.0\.1|0\.0\.0\.0/.test(t)}function Kc(e,t){let n=[],r=[],i=[],a=new Map;for(let o of e)if(t.has(o.id))n.push(o);else if(Gc(o))r.push(o);else if(Wc.has(o.id))i.push(o);else{let e=Hc[o.id]??3;a.has(e)||a.set(e,[]),a.get(e).push(o)}let o=(e,t)=>e.display_name.localeCompare(t.display_name);n.sort(o),r.sort(o),i.sort(o);let s=[];n.length&&s.push({key:`favorites`,label:`Favorites`,top:!0,providers:n}),r.length&&s.push({key:`local`,label:`Local LLMs`,top:!1,providers:r}),i.length&&s.push({key:`free`,label:`Free`,top:!1,providers:i});for(let[e,t]of[...a.entries()].sort(([e],[t])=>e-t))t.sort(o),s.push({key:`tier-${e}`,label:Uc[e]??`Other`,top:!1,providers:t});return s}var qc=function(e){return typeof window<`u`?matchMedia&&matchMedia(`(prefers-color-scheme: ${e})`):{matches:!1}},Jc,Yc=(0,N.createContext)({appearance:`light`,setAppearance:function(){},isDarkMode:!1,themeMode:`light`,setThemeMode:function(){},browserPrefers:(Jc=qc(`dark`))!=null&&Jc.matches?`dark`:`light`}),Xc=function(){return(0,N.useContext)(Yc)},Zc=(e,t)=>{if(t)return`row`;switch(e){case`horizontal`:return`row`;case`horizontal-reverse`:return`row-reverse`;case`vertical`:default:return`column`;case`vertical-reverse`:return`column-reverse`}},Qc=e=>{if(e)return[`space-between`,`space-around`,`space-evenly`].includes(e)},$c=(e,t)=>Zc(e,t)===`row`,el=e=>typeof e==`number`?`${e}px`:e,tl=(0,N.memo)(({visible:e,flex:t,gap:n,direction:r,horizontal:i,align:a,justify:o,distribution:s,height:c,width:l,allowShrink:u,padding:d,paddingInline:f,paddingBlock:p,prefixCls:m,as:h=`div`,className:g,style:_,children:v,wrap:y,ref:b,...x})=>{let S=o||s,C=$c(r,i)&&!l&&Qc(S)?`100%`:el(l),w={...t===void 0?{}:{"--lobe-flex":String(t)},...r||i?{"--lobe-flex-direction":Zc(r,i)}:{},...y===void 0?{}:{"--lobe-flex-wrap":y},...S===void 0?{}:{"--lobe-flex-justify":S},...a===void 0?{}:{"--lobe-flex-align":a},...C===void 0?{}:{"--lobe-flex-width":C},...c===void 0?{}:{"--lobe-flex-height":el(c)},...d===void 0?{}:{"--lobe-flex-padding":el(d)},...f===void 0?{}:{"--lobe-flex-padding-inline":el(f)},...p===void 0?{}:{"--lobe-flex-padding-block":el(p)},...n===void 0?{}:{"--lobe-flex-gap":el(n)},...u?{minWidth:0}:{},..._},T=`lobe-flex`,E=[T,e===!1?`${T}--hidden`:void 0,m?`${m}-flex`:void 0,g].filter(Boolean).join(` `);return(0,P.jsx)(h,{ref:b,...x,className:E,style:w,children:v})}),nl=({children:e,ref:t,...n})=>(0,P.jsx)(tl,{...n,align:`center`,justify:`center`,ref:t,children:e});function rl(e){return Array.from(e.match(il)??[])}var il,al=o((()=>{il=/\p{Lu}?\p{Ll}+|[0-9]+|\p{Lu}+(?!\p{Ll})|\p{Emoji_Presentation}|\p{Extended_Pictographic}|\p{L}+/gu}));function ol(e){return rl(e).map(e=>e.toLowerCase()).join(`-`)}o((()=>{al()}))();var sl=function(e){var t=(0,N.useId)(),n=`lobe-icons-${ol(e)}-${t}`;return(0,N.useMemo)(function(){return{fill:`url(#${n})`,id:n}},[e])},cl=function(e,t){var n=(0,N.useId)();return(0,N.useMemo)(function(){return Array.from({length:t},function(t,r){var i=`lobe-icons-${ol(e)}-${r}-${n}`;return{fill:`url(#${i})`,id:i}})},[e,t,n])},W=`Gemini`,ll=`#fff`,ul=`#fff`,dl=.8;function fl(e){"@babel/helpers - typeof";return fl=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},fl(e)}var pl=[`size`,`style`];function ml(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function hl(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=Array(t);n=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function El(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Dl=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Tl(e,pl),a=yl(cl(W,3),3),o=a[0],s=a[1],c=a[2];return(0,P.jsxs)(`svg`,hl(hl({height:n,style:hl({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:W}),(0,P.jsx)(`path`,{d:`M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z`,fill:`#3186FF`}),(0,P.jsx)(`path`,{d:`M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z`,fill:o.fill}),(0,P.jsx)(`path`,{d:`M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z`,fill:s.fill}),(0,P.jsx)(`path`,{d:`M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z`,fill:c.fill}),(0,P.jsxs)(`defs`,{children:[(0,P.jsxs)(`linearGradient`,{gradientUnits:`userSpaceOnUse`,id:o.id,x1:`7`,x2:`11`,y1:`15.5`,y2:`12`,children:[(0,P.jsx)(`stop`,{stopColor:`#08B962`}),(0,P.jsx)(`stop`,{offset:`1`,stopColor:`#08B962`,stopOpacity:`0`})]}),(0,P.jsxs)(`linearGradient`,{gradientUnits:`userSpaceOnUse`,id:s.id,x1:`8`,x2:`11.5`,y1:`5.5`,y2:`11`,children:[(0,P.jsx)(`stop`,{stopColor:`#F94543`}),(0,P.jsx)(`stop`,{offset:`1`,stopColor:`#F94543`,stopOpacity:`0`})]}),(0,P.jsxs)(`linearGradient`,{gradientUnits:`userSpaceOnUse`,id:c.id,x1:`3.5`,x2:`17.5`,y1:`13.5`,y2:`12`,children:[(0,P.jsx)(`stop`,{stopColor:`#FABC12`}),(0,P.jsx)(`stop`,{offset:`.46`,stopColor:`#FABC12`,stopOpacity:`0`})]})]})]}))});function Ol(e){"@babel/helpers - typeof";return Ol=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Ol(e)}function kl(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Al(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Xl(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Zl=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Yl(e,Ul);return(0,P.jsxs)(`svg`,Gl(Gl({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Gl({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Il}),(0,P.jsx)(`path`,{d:`M9.205 8.658v-2.26c0-.19.072-.333.238-.428l4.543-2.616c.619-.357 1.356-.523 2.117-.523 2.854 0 4.662 2.212 4.662 4.566 0 .167 0 .357-.024.547l-4.71-2.759a.797.797 0 00-.856 0l-5.97 3.473zm10.609 8.8V12.06c0-.333-.143-.57-.429-.737l-5.97-3.473 1.95-1.118a.433.433 0 01.476 0l4.543 2.617c1.309.76 2.189 2.378 2.189 3.948 0 1.808-1.07 3.473-2.76 4.163zM7.802 12.703l-1.95-1.142c-.167-.095-.239-.238-.239-.428V5.899c0-2.545 1.95-4.472 4.591-4.472 1 0 1.927.333 2.712.928L8.23 5.067c-.285.166-.428.404-.428.737v6.898zM12 15.128l-2.795-1.57v-3.33L12 8.658l2.795 1.57v3.33L12 15.128zm1.796 7.23c-1 0-1.927-.332-2.712-.927l4.686-2.712c.285-.166.428-.404.428-.737v-6.898l1.974 1.142c.167.095.238.238.238.428v5.233c0 2.545-1.974 4.472-4.614 4.472zm-5.637-5.303l-4.544-2.617c-1.308-.761-2.188-2.378-2.188-3.948A4.482 4.482 0 014.21 6.327v5.423c0 .333.143.571.428.738l5.947 3.449-1.95 1.118a.432.432 0 01-.476 0zm-.262 3.9c-2.688 0-4.662-2.021-4.662-4.519 0-.19.024-.38.047-.57l4.686 2.71c.286.167.571.167.856 0l5.97-3.448v2.26c0 .19-.07.333-.237.428l-4.543 2.616c-.619.357-1.356.523-2.117.523zm5.899 2.83a5.947 5.947 0 005.827-4.756C22.287 18.339 24 15.84 24 13.296c0-1.665-.713-3.282-1.998-4.448.119-.5.19-.999.19-1.498 0-3.401-2.759-5.947-5.946-5.947-.642 0-1.26.095-1.88.31A5.962 5.962 0 0010.205 0a5.947 5.947 0 00-5.827 4.757C1.713 5.447 0 7.945 0 10.49c0 1.666.713 3.283 1.998 4.448-.119.5-.19 1-.19 1.499 0 3.401 2.759 5.946 5.946 5.946.642 0 1.26-.095 1.88-.309a5.96 5.96 0 004.162 1.713z`})]}))});function Ql(e){"@babel/helpers - typeof";return Ql=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Ql(e)}var $l=[`type`];function eu(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function tu(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function ou(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var su=(0,N.memo)(function(e){var t=e.type,n=t===void 0?`normal`:t,r=au(e,$l);return(0,P.jsx)($,tu({Icon:Zl,"aria-label":Il,background:(0,N.useMemo)(function(){switch(n){case`gpt3`:return Rl;case`gpt4`:return zl;case`gpt5`:return G;case`o3`:case`o1`:return K;case`oss`:return q;case`platform`:return J;default:return Y}},[n]),color:Bl,iconMultiple:Vl},r))}),cu=`Qwen`,lu=`#615ced`,uu=`#fff`,du=.75;function fu(e){"@babel/helpers - typeof";return fu=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},fu(e)}var pu=[`size`,`style`];function mu(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function hu(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function bu(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var xu=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=yu(e,pu);return(0,P.jsxs)(`svg`,hu(hu({fill:`currentColor`,fillRule:`evenodd`,height:n,style:hu({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:cu}),(0,P.jsx)(`path`,{d:`M12.604 1.34c.393.69.784 1.382 1.174 2.075a.18.18 0 00.157.091h5.552c.174 0 .322.11.446.327l1.454 2.57c.19.337.24.478.024.837-.26.43-.513.864-.76 1.3l-.367.658c-.106.196-.223.28-.04.512l2.652 4.637c.172.301.111.494-.043.77-.437.785-.882 1.564-1.335 2.34-.159.272-.352.375-.68.37-.777-.016-1.552-.01-2.327.016a.099.099 0 00-.081.05 575.097 575.097 0 01-2.705 4.74c-.169.293-.38.363-.725.364-.997.003-2.002.004-3.017.002a.537.537 0 01-.465-.271l-1.335-2.323a.09.09 0 00-.083-.049H4.982c-.285.03-.553-.001-.805-.092l-1.603-2.77a.543.543 0 01-.002-.54l1.207-2.12a.198.198 0 000-.197 550.951 550.951 0 01-1.875-3.272l-.79-1.395c-.16-.31-.173-.496.095-.965.465-.813.927-1.625 1.387-2.436.132-.234.304-.334.584-.335a338.3 338.3 0 012.589-.001.124.124 0 00.107-.063l2.806-4.895a.488.488 0 01.422-.246c.524-.001 1.053 0 1.583-.006L11.704 1c.341-.003.724.032.9.34zm-3.432.403a.06.06 0 00-.052.03L6.254 6.788a.157.157 0 01-.135.078H3.253c-.056 0-.07.025-.041.074l5.81 10.156c.025.042.013.062-.034.063l-2.795.015a.218.218 0 00-.2.116l-1.32 2.31c-.044.078-.021.118.068.118l5.716.008c.046 0 .08.02.104.061l1.403 2.454c.046.081.092.082.139 0l5.006-8.76.783-1.382a.055.055 0 01.096 0l1.424 2.53a.122.122 0 00.107.062l2.763-.02a.04.04 0 00.035-.02.041.041 0 000-.04l-2.9-5.086a.108.108 0 010-.113l.293-.507 1.12-1.977c.024-.041.012-.062-.035-.062H9.2c-.059 0-.073-.026-.043-.077l1.434-2.505a.107.107 0 000-.114L9.225 1.774a.06.06 0 00-.053-.031zm6.29 8.02c.046 0 .058.02.034.06l-.832 1.465-2.613 4.585a.056.056 0 01-.05.029.058.058 0 01-.05-.029L8.498 9.841c-.02-.034-.01-.052.028-.054l.216-.012 6.722-.012z`})]}))});function Su(e){"@babel/helpers - typeof";return Su=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Su(e)}function Cu(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function wu(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Hu(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Uu=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Vu(e,Fu);return(0,P.jsxs)(`svg`,Lu(Lu({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Lu({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Au}),(0,P.jsx)(`path`,{d:`M6.47 17l-.367-1.189H2.718L2.35 17H0l3.398-9.789h2.026L8.864 17H6.47zm-2.052-6.993l-1.17 4.028H5.56l-1.142-4.028zm4.707-2.796h2.23V17h-2.23V7.211zM11.955 15c.1-.483.277-.946.524-1.37.214-.359.482-.68.795-.951.32-.273.658-.52 1.013-.741.28-.168.54-.33.781-.483.222-.14.433-.296.632-.468.172-.148.317-.325.428-.525.107-.199.16-.423.157-.65 0-.392-.104-.674-.313-.846a1.176 1.176 0 00-.775-.259 1.207 1.207 0 00-.863.329c-.231.219-.347.585-.347 1.098H11.8a3.387 3.387 0 01.224-1.245c.146-.377.371-.716.66-.993.306-.29.667-.514 1.06-.657A4.04 4.04 0 0115.183 7c.42-.002.84.057 1.244.175.376.107.73.287 1.04.531.305.246.55.562.714.923.185.419.275.875.265 1.335.005.39-.084.774-.259 1.12-.167.328-.38.63-.632.894-.246.259-.517.49-.808.693-.29.2-.554.37-.789.51-.326.224-.596.417-.809.58a3.872 3.872 0 00-.51.455 1.229 1.229 0 00-.265.434 1.633 1.633 0 00-.074.517h4.078V17h-6.606a9.24 9.24 0 01.183-2zM18.8 8.93a5.05 5.05 0 001.135-.105c.25-.049.484-.156.686-.314.163-.139.28-.324.34-.532.068-.25.1-.51.095-.77H23V17h-2.243v-6.475H18.8V8.93z`})]}))}),Wu=`Anthropic`,Gu=`#F1F0E8`,X=`#141413`,Ku=.75;function qu(e){"@babel/helpers - typeof";return qu=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},qu(e)}var Ju=[`size`,`style`];function Yu(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Xu(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function td(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var nd=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=ed(e,Ju);return(0,P.jsxs)(`svg`,Xu(Xu({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Xu({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Wu}),(0,P.jsx)(`path`,{d:`M13.827 3.52h3.603L24 20h-3.603l-6.57-16.48zm-7.258 0h3.767L16.906 20h-3.674l-1.343-3.461H5.017l-1.344 3.46H0L6.57 3.522zm4.132 9.959L8.453 7.687 6.205 13.48H10.7z`})]}))});function rd(e){"@babel/helpers - typeof";return rd=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},rd(e)}function id(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function ad(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Sd(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Cd=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=xd(e,gd);return(0,P.jsxs)(`svg`,vd(vd({fill:`currentColor`,fillRule:`evenodd`,height:n,style:vd({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:dd}),(0,P.jsx)(`path`,{d:`M10.595 1.5a3.695 3.695 0 00-3.444 2.355L0 22.26h5.432l5.629-14.486h.002a.96.96 0 011.782 0h.75V4.835h-1.393L13.498 1.5h-2.902z`}),(0,P.jsx)(`path`,{d:`M7.151 3.855a3.695 3.695 0 013.26-2.35l-.002-.005H13.405c1.524 0 2.893.936 3.444 2.355L24 22.26h-5.525L11.54 4.413a2.528 2.528 0 00-4.609.006l.22-.564z`})]}))});function wd(e){"@babel/helpers - typeof";return wd=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},wd(e)}function Td(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Ed(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Hd(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Ud=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Vd(e,Fd);return(0,P.jsxs)(`svg`,Ld(Ld({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Ld({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:`AWS`}),(0,P.jsx)(`path`,{d:`M6.763 11.212c0 .296.032.535.088.71.064.176.144.368.256.576.04.063.056.127.056.183 0 .08-.048.16-.152.24l-.503.335a.383.383 0 01-.208.072c-.08 0-.16-.04-.239-.112a2.47 2.47 0 01-.287-.375 6.18 6.18 0 01-.248-.471c-.622.734-1.405 1.101-2.347 1.101-.67 0-1.205-.191-1.596-.574-.39-.384-.59-.894-.59-1.533 0-.678.24-1.23.726-1.644.487-.415 1.133-.623 1.955-.623.272 0 .551.024.846.064.296.04.6.104.918.176v-.583c0-.607-.127-1.03-.375-1.277-.255-.248-.686-.367-1.3-.367-.28 0-.568.031-.863.103-.295.072-.583.16-.862.272a2.4 2.4 0 01-.28.104.488.488 0 01-.127.023c-.112 0-.168-.08-.168-.247v-.391c0-.128.016-.224.056-.28a.597.597 0 01.224-.167 4.577 4.577 0 011.005-.36 4.84 4.84 0 011.246-.151c.95 0 1.644.216 2.091.647.44.43.662 1.085.662 1.963v2.586h.016zm-3.24 1.214c.263 0 .534-.048.822-.144a1.78 1.78 0 00.758-.51 1.27 1.27 0 00.272-.512c.047-.191.08-.423.08-.694v-.335a6.66 6.66 0 00-.735-.136 6.02 6.02 0 00-.75-.048c-.535 0-.926.104-1.19.32-.263.215-.39.518-.39.917 0 .375.095.655.295.846.191.2.47.296.838.296zm6.41.862c-.144 0-.24-.024-.304-.08-.064-.048-.12-.16-.168-.311L7.586 6.726a1.398 1.398 0 01-.072-.32c0-.128.064-.2.191-.2h.783c.151 0 .255.025.31.08.065.048.113.16.16.312l1.342 5.284 1.245-5.284c.04-.16.088-.264.151-.312a.549.549 0 01.32-.08h.638c.152 0 .256.025.32.08.063.048.12.16.151.312l1.261 5.348 1.381-5.348c.048-.16.104-.264.16-.312a.52.52 0 01.311-.08h.743c.127 0 .2.065.2.2 0 .04-.009.08-.017.128a1.137 1.137 0 01-.056.2l-1.923 6.17c-.048.16-.104.263-.168.311a.51.51 0 01-.303.08h-.687c-.15 0-.255-.024-.32-.08-.063-.056-.119-.16-.15-.32L12.32 7.747l-1.23 5.14c-.04.16-.087.264-.15.32-.065.056-.177.08-.32.08l-.686.001zm10.256.215c-.415 0-.83-.048-1.229-.143-.399-.096-.71-.2-.918-.32-.128-.071-.215-.151-.247-.223a.563.563 0 01-.048-.224v-.407c0-.167.064-.247.183-.247.048 0 .096.008.144.024.048.016.12.048.2.08.271.12.566.215.878.279.32.064.63.096.95.096.502 0 .894-.088 1.165-.264a.86.86 0 00.415-.758.777.777 0 00-.215-.559c-.144-.151-.416-.287-.807-.415l-1.157-.36c-.583-.183-1.014-.454-1.277-.813a1.902 1.902 0 01-.4-1.158c0-.335.073-.63.216-.886.144-.255.335-.479.575-.654.24-.184.51-.32.83-.415.32-.096.655-.136 1.006-.136.175 0 .36.008.535.032.183.024.35.056.518.088.16.04.312.08.455.127.144.048.256.096.336.144a.69.69 0 01.24.2.43.43 0 01.071.263v.375c0 .168-.064.256-.184.256a.83.83 0 01-.303-.096 3.652 3.652 0 00-1.532-.311c-.455 0-.815.071-1.062.223-.248.152-.375.383-.375.71 0 .224.08.416.24.567.16.152.454.304.877.44l1.134.358c.574.184.99.44 1.237.767.247.327.367.702.367 1.117 0 .343-.072.655-.207.926a2.157 2.157 0 01-.583.703c-.248.2-.543.343-.886.447-.36.111-.734.167-1.142.167z`}),(0,P.jsx)(`path`,{d:`M.378 15.475c3.384 1.963 7.56 3.153 11.877 3.153 2.914 0 6.114-.607 9.06-1.852.44-.2.814.287.383.607-2.626 1.94-6.442 2.969-9.722 2.969-4.598 0-8.74-1.7-11.87-4.526-.247-.223-.024-.527.272-.351zm23.531-.2c.287.36-.08 2.826-1.485 4.007-.215.184-.423.088-.327-.151l.175-.439c.343-.88.802-2.198.52-2.555-.336-.43-2.22-.207-3.074-.103-.255.032-.295-.192-.063-.36 1.5-1.053 3.967-.75 4.254-.399z`,fill:`#F90`})]}))});function Wd(e){"@babel/helpers - typeof";return Wd=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Wd(e)}function Gd(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Kd(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function df(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var ff=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=uf(e,rf);return(0,P.jsxs)(`svg`,of(of({fill:`currentColor`,fillRule:`evenodd`,height:n,style:of({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Qd}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M8.128 14.099c.592 0 1.77-.033 3.398-.703 1.897-.781 5.672-2.2 8.395-3.656 1.905-1.018 2.74-2.366 2.74-4.18A4.56 4.56 0 0018.1 1H7.549A6.55 6.55 0 001 7.55c0 3.617 2.745 6.549 7.128 6.549z`}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M9.912 18.61a4.387 4.387 0 012.705-4.052l3.323-1.38c3.361-1.394 7.06 1.076 7.06 4.715a5.104 5.104 0 01-5.105 5.104l-3.597-.001a4.386 4.386 0 01-4.386-4.387z`}),(0,P.jsx)(`path`,{d:`M4.776 14.962A3.775 3.775 0 001 18.738v.489a3.776 3.776 0 007.551 0v-.49a3.775 3.775 0 00-3.775-3.775z`})]}))});function pf(e){"@babel/helpers - typeof";return pf=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},pf(e)}function mf(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function hf(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Nf(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Pf=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Mf(e,Ef);return(0,P.jsxs)(`svg`,Of(Of({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Of({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:xf}),(0,P.jsx)(`path`,{d:`M21.821 9.894l-9.81 5.595L1.505 9.511 1 9.787v4.34l11.01 6.256 9.811-5.574v2.297l-9.81 5.596-10.506-5.979L1 17v.745L12.01 24 23 17.745v-4.34l-.505-.277-10.484 5.957-9.832-5.574v-2.298l9.832 5.574L23 10.532V6.255l-.547-.319-10.442 5.936-9.327-5.276 9.327-5.298 7.663 4.362.673-.383v-.532L12.011 0 1 6.255v.681l11.01 6.255 9.811-5.595z`})]}))});function Ff(e){"@babel/helpers - typeof";return Ff=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Ff(e)}function If(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Lf(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function tp(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var np=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=ep(e,Jf);return(0,P.jsxs)(`svg`,Xf(Xf({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Xf({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Uf}),(0,P.jsx)(`path`,{d:`M23.748 4.482c-.254-.124-.364.113-.512.234-.051.039-.094.09-.137.136-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.156-.708-.311-.955-.65-.172-.241-.219-.51-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.093.172.187.129.323-.082.28-.18.552-.266.833-.055.179-.137.217-.329.14a5.526 5.526 0 01-1.736-1.18c-.857-.828-1.631-1.742-2.597-2.458a11.365 11.365 0 00-.689-.471c-.985-.957.13-1.743.388-1.836.27-.098.093-.432-.779-.428-.872.004-1.67.295-2.687.684a3.055 3.055 0 01-.465.137 9.597 9.597 0 00-2.883-.102c-1.885.21-3.39 1.102-4.497 2.623C.082 8.606-.231 10.684.152 12.85c.403 2.284 1.569 4.175 3.36 5.653 1.858 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.133-.284 4.994-1.86.47.234.962.327 1.78.397.63.059 1.236-.03 1.705-.128.735-.156.684-.837.419-.961-2.155-1.004-1.682-.595-2.113-.926 1.096-1.296 2.746-2.642 3.392-7.003.05-.347.007-.565 0-.845-.004-.17.035-.237.23-.256a4.173 4.173 0 001.545-.475c1.396-.763 1.96-2.015 2.093-3.517.02-.23-.004-.467-.247-.588zM11.581 18c-2.089-1.642-3.102-2.183-3.52-2.16-.392.024-.321.471-.235.763.09.288.207.486.371.739.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.167-1.361-.802-2.5-1.86-3.301-3.307-.774-1.393-1.224-2.887-1.298-4.482-.02-.386.093-.522.477-.592a4.696 4.696 0 011.529-.039c2.132.312 3.946 1.265 5.468 2.774.868.86 1.525 1.887 2.202 2.891.72 1.066 1.494 2.082 2.48 2.914.348.292.625.514.891.677-.802.09-2.14.11-3.054-.614zm1-6.44a.306.306 0 01.415-.287.302.302 0 01.2.288.306.306 0 01-.31.307.303.303 0 01-.304-.308zm3.11 1.596c-.2.081-.399.151-.59.16a1.245 1.245 0 01-.798-.254c-.274-.23-.47-.358-.552-.758a1.73 1.73 0 01.016-.588c.07-.327-.008-.537-.239-.727-.187-.156-.426-.199-.688-.199a.559.559 0 01-.254-.078c-.11-.054-.2-.19-.114-.358.028-.054.16-.186.192-.21.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.391.451.462.576.685.914.176.265.336.537.445.848.067.195-.019.354-.25.452z`})]}))});function rp(e){"@babel/helpers - typeof";return rp=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},rp(e)}function ip(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function ap(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Cp(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var wp=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Sp(e,gp);return(0,P.jsxs)(`svg`,vp(vp({fill:`currentColor`,fillRule:`evenodd`,height:n,style:vp({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:dp}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M14.8 5l-2.801 6.795L9.195 5H7.397l3.072 7.428a1.64 1.64 0 003.038.002L16.598 5H14.8zm1.196 10.352l5.124-5.244-.699-1.669-5.596 5.739a1.664 1.664 0 00-.343 1.807 1.642 1.642 0 001.516 1.012L16 17l8-.02-.699-1.669-7.303.041h-.002zM2.88 10.104l.699-1.669 5.596 5.739c.468.479.603 1.189.343 1.807a1.643 1.643 0 01-1.516 1.012l-8-.018-.002.002.699-1.669 7.303.042-5.122-5.246z`})]}))});function Tp(e){"@babel/helpers - typeof";return Tp=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Tp(e)}function Ep(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Dp(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Gp(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Kp=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Wp(e,Rp);return(0,P.jsxs)(`svg`,Bp(Bp({height:n,style:Bp({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Np}),(0,P.jsx)(`path`,{d:`M23 12.245c0-.905-.075-1.565-.236-2.25h-10.54v4.083h6.186c-.124 1.014-.797 2.542-2.294 3.569l-.021.136 3.332 2.53.23.022C21.779 18.417 23 15.593 23 12.245z`,fill:`#4285F4`}),(0,P.jsx)(`path`,{d:`M12.225 23c3.03 0 5.574-.978 7.433-2.665l-3.542-2.688c-.948.648-2.22 1.1-3.891 1.1a6.745 6.745 0 01-6.386-4.572l-.132.011-3.465 2.628-.045.124C4.043 20.531 7.835 23 12.225 23z`,fill:`#34A853`}),(0,P.jsx)(`path`,{d:`M5.84 14.175A6.65 6.65 0 015.463 12c0-.758.138-1.491.361-2.175l-.006-.147-3.508-2.67-.115.054A10.831 10.831 0 001 12c0 1.772.436 3.447 1.197 4.938l3.642-2.763z`,fill:`#FBBC05`}),(0,P.jsx)(`path`,{d:`M12.225 5.253c2.108 0 3.529.892 4.34 1.638l3.167-3.031C17.787 2.088 15.255 1 12.225 1 7.834 1 4.043 3.469 2.197 7.062l3.63 2.763a6.77 6.77 0 016.398-4.572z`,fill:`#EB4335`})]}))});function qp(e){"@babel/helpers - typeof";return qp=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},qp(e)}function Jp(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Yp(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function fm(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var pm=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=dm(e,am);return(0,P.jsxs)(`svg`,sm(sm({fill:`currentColor`,fillRule:`evenodd`,height:n,style:sm({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:`IBM`}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M24 16.333V17h-3.158v-.667H24zm-7.579 0V17h-3.158v-.667h3.158zm2.464 0L18.63 17l-.25-.667h.504zm-7.075 0a2.528 2.528 0 01-1.717.667h-5.04v-.667h6.757zm-7.389 0V17H0v-.667h4.421zm12-1.333v.667h-3.158V15h3.158zm2.958 0l-.246.667h-1L17.885 15h1.494zm-6.937 0c-.057.237-.148.46-.265.667H5.053V15h7.39zm-8.02 0v.667H0V15h4.421zM24 15v.667h-3.158V15H24zm-1.263-1.333v.666h-1.895v-.666h1.895zm-6.316 0v.666h-1.895v-.666h1.895zm3.453 0l-.248.666h-1.989l-.25-.666h2.487zm-7.52 0c.056.212.088.435.088.666h-2.337v-.666h2.249zm-4.143 0v.666H6.316v-.666H8.21zm-5.053 0v.666H1.263v-.666h1.895zm19.579-1.334V13h-1.895v-.667h1.895zm-6.316 0V13h-1.895v-.667h1.895zm3.948 0l-.247.667h-2.987l-.245-.667h3.48zm-8.792 0c.218.188.405.414.55.667H6.315v-.667h5.26zm-8.42 0V13H1.264v-.667h1.895zM18.456 11l.177.539.176-.539h3.929v.667h-1.895v-.613l-.215.613H16.63l-.209-.613v.613h-1.895V11h3.929zM3.158 11v.667H1.263V11h1.895zm8.968 0a2.555 2.555 0 01-.55.667h-5.26V11h5.81zm10.61-1.333v.666h-3.709l.224-.666h3.486zm-4.722 0l.224.666h-3.712v-.666h3.488zm-5.572 0c0 .23-.032.454-.088.666h-2.249v-.666h2.337zm-4.231 0v.666H6.316v-.666H8.21zm-5.053 0v.666H1.263v-.666h1.895zm14.419-1.334l.22.667h-4.534v-.667h4.314zm6.423 0V9h-4.536l.229-.667H24zm-11.823 0c.117.206.208.43.265.667h-7.39v-.667h7.125zm-7.756 0V9H0v-.667h4.421zM17.133 7l.224.667h-4.094V7h3.87zM24 7v.667h-4.089L20.13 7H24zM10.093 7c.662 0 1.264.253 1.717.667H5.053V7h5.04zM4.42 7v.667H0V7h4.421z`})]}))});function mm(e){"@babel/helpers - typeof";return mm=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},mm(e)}function hm(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function gm(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Pm(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Fm=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Nm(e,Dm);return(0,P.jsxs)(`svg`,km(km({fill:`currentColor`,fillRule:`evenodd`,height:n,style:km({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Sm}),(0,P.jsx)(`path`,{d:`M6.608 21.416a4.608 4.608 0 100-9.217 4.608 4.608 0 000 9.217zM20.894 2.015c.614 0 1.106.492 1.106 1.106v9.002c0 5.13-4.148 9.309-9.217 9.37v-9.355l-.03-9.032c0-.614.491-1.106 1.106-1.106h7.158l-.123.015z`})]}))});function Im(e){"@babel/helpers - typeof";return Im=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Im(e)}function Lm(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Rm(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function nh(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var rh=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=th(e,Ym);return(0,P.jsxs)(`svg`,Zm(Zm({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Zm({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Wm}),(0,P.jsx)(`path`,{d:`M6.897 4c1.915 0 3.516.932 5.43 3.376l.282-.373c.19-.246.383-.484.58-.71l.313-.35C14.588 4.788 15.792 4 17.225 4c1.273 0 2.469.557 3.491 1.516l.218.213c1.73 1.765 2.917 4.71 3.053 8.026l.011.392.002.25c0 1.501-.28 2.759-.818 3.7l-.14.23-.108.153c-.301.42-.664.758-1.086 1.009l-.265.142-.087.04a3.493 3.493 0 01-.302.118 4.117 4.117 0 01-1.33.208c-.524 0-.996-.067-1.438-.215-.614-.204-1.163-.56-1.726-1.116l-.227-.235c-.753-.812-1.534-1.976-2.493-3.586l-1.43-2.41-.544-.895-1.766 3.13-.343.592C7.597 19.156 6.227 20 4.356 20c-1.21 0-2.205-.42-2.936-1.182l-.168-.184c-.484-.573-.837-1.311-1.043-2.189l-.067-.32a8.69 8.69 0 01-.136-1.288L0 14.468c.002-.745.06-1.49.174-2.23l.1-.573c.298-1.53.828-2.958 1.536-4.157l.209-.34c1.177-1.83 2.789-3.053 4.615-3.16L6.897 4zm-.033 2.615l-.201.01c-.83.083-1.606.673-2.252 1.577l-.138.199-.01.018c-.67 1.017-1.185 2.378-1.456 3.845l-.004.022a12.591 12.591 0 00-.207 2.254l.002.188c.004.18.017.36.04.54l.043.291c.092.503.257.908.486 1.208l.117.137c.303.323.698.492 1.17.492 1.1 0 1.796-.676 3.696-3.641l2.175-3.4.454-.701-.139-.198C9.11 7.3 8.084 6.616 6.864 6.616zm10.196-.552l-.176.007c-.635.048-1.223.359-1.82.933l-.196.198c-.439.462-.887 1.064-1.367 1.807l.266.398c.18.274.362.56.55.858l.293.475 1.396 2.335.695 1.114c.583.926 1.03 1.6 1.408 2.082l.213.262c.282.326.529.54.777.673l.102.05c.227.1.457.138.718.138.176.002.35-.023.518-.073.338-.104.61-.32.813-.637l.095-.163.077-.162c.194-.459.29-1.06.29-1.785l-.006-.449c-.08-2.871-.938-5.372-2.2-6.798l-.176-.189c-.67-.683-1.444-1.074-2.27-1.074z`})]}))});function ih(e){"@babel/helpers - typeof";return ih=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},ih(e)}function ah(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function oh(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function wh(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Th=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Ch(e,_h);return(0,P.jsxs)(`svg`,yh(yh({fill:`currentColor`,fillRule:`evenodd`,height:n,style:yh({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:fh}),(0,P.jsx)(`path`,{d:`M16.278 2c1.156 0 2.093.927 2.093 2.07v12.501a.74.74 0 00.744.709.74.74 0 00.743-.709V9.099a2.06 2.06 0 012.071-2.049A2.06 2.06 0 0124 9.1v6.561a.649.649 0 01-.652.645.649.649 0 01-.653-.645V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v7.472a2.037 2.037 0 01-2.048 2.026 2.037 2.037 0 01-2.048-2.026v-12.5a.785.785 0 00-.788-.753.785.785 0 00-.789.752l-.001 15.904A2.037 2.037 0 0113.441 22a2.037 2.037 0 01-2.048-2.026V18.04c0-.356.292-.645.652-.645.36 0 .652.289.652.645v1.934c0 .263.142.506.372.638.23.131.514.131.744 0a.734.734 0 00.372-.638V4.07c0-1.143.937-2.07 2.093-2.07zm-5.674 0c1.156 0 2.093.927 2.093 2.07v11.523a.648.648 0 01-.652.645.648.648 0 01-.652-.645V4.07a.785.785 0 00-.789-.78.785.785 0 00-.789.78v14.013a2.06 2.06 0 01-2.07 2.048 2.06 2.06 0 01-2.071-2.048V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v3.8a2.06 2.06 0 01-2.071 2.049A2.06 2.06 0 010 12.9v-1.378c0-.357.292-.646.652-.646.36 0 .653.29.653.646V12.9c0 .418.343.757.766.757s.766-.339.766-.757V9.099a2.06 2.06 0 012.07-2.048 2.06 2.06 0 012.071 2.048v8.984c0 .419.343.758.767.758.423 0 .766-.339.766-.758V4.07c0-1.143.937-2.07 2.093-2.07z`})]}))});function Eh(e){"@babel/helpers - typeof";return Eh=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Eh(e)}function Dh(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Oh(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Kh(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var qh=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Gh(e,zh);return(0,P.jsxs)(`svg`,Vh(Vh({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Vh({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Ph}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M3.428 3.4h3.429v3.428h3.429v3.429h-.002 3.431V6.828h3.427V3.4h3.43v13.714H24v3.429H13.714v-3.428h-3.428v-3.429h-3.43v3.428h3.43v3.429H0v-3.429h3.428V3.4zm10.286 13.715h3.428v-3.429h-3.427v3.429z`})]}))});function Jh(e){"@babel/helpers - typeof";return Jh=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Jh(e)}function Yh(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Xh(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function mg(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var hg=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=pg(e,sg);return(0,P.jsxs)(`svg`,lg(lg({fill:`currentColor`,fillRule:`evenodd`,height:n,style:lg({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:ng}),(0,P.jsx)(`path`,{d:`M1.052 16.916l9.539 2.552a21.007 21.007 0 00.06 2.033l5.956 1.593a11.997 11.997 0 01-5.586.865l-.18-.016-.044-.004-.084-.009-.094-.01a11.605 11.605 0 01-.157-.02l-.107-.014-.11-.016a11.962 11.962 0 01-.32-.051l-.042-.008-.075-.013-.107-.02-.07-.015-.093-.019-.075-.016-.095-.02-.097-.023-.094-.022-.068-.017-.088-.022-.09-.024-.095-.025-.082-.023-.109-.03-.062-.02-.084-.025-.093-.028-.105-.034-.058-.019-.08-.026-.09-.031-.066-.024a6.293 6.293 0 01-.044-.015l-.068-.025-.101-.037-.057-.022-.08-.03-.087-.035-.088-.035-.079-.032-.095-.04-.063-.028-.063-.027a5.655 5.655 0 01-.041-.018l-.066-.03-.103-.047-.052-.024-.096-.046-.062-.03-.084-.04-.086-.044-.093-.047-.052-.027-.103-.055-.057-.03-.058-.032a6.49 6.49 0 01-.046-.026l-.094-.053-.06-.034-.051-.03-.072-.041-.082-.05-.093-.056-.052-.032-.084-.053-.061-.039-.079-.05-.07-.047-.053-.035a7.785 7.785 0 01-.054-.036l-.044-.03-.044-.03a6.066 6.066 0 01-.04-.028l-.057-.04-.076-.054-.069-.05-.074-.054-.056-.042-.076-.057-.076-.059-.086-.067-.045-.035-.064-.052-.074-.06-.089-.073-.046-.039-.046-.039a7.516 7.516 0 01-.043-.037l-.045-.04-.061-.053-.07-.062-.068-.06-.062-.058-.067-.062-.053-.05-.088-.084a13.28 13.28 0 01-.099-.097l-.029-.028-.041-.042-.069-.07-.05-.051-.05-.053a6.457 6.457 0 01-.168-.179l-.08-.088-.062-.07-.071-.08-.042-.049-.053-.062-.058-.068-.046-.056a7.175 7.175 0 01-.027-.033l-.045-.055-.066-.082-.041-.052-.05-.064-.02-.025a11.99 11.99 0 01-1.44-2.402zm-1.02-5.794l11.353 3.037a20.468 20.468 0 00-.469 2.011l10.817 2.894a12.076 12.076 0 01-1.845 2.005L.657 15.923l-.016-.046-.035-.104a11.965 11.965 0 01-.05-.153l-.007-.023a11.896 11.896 0 01-.207-.741l-.03-.126-.018-.08-.021-.097-.018-.081-.018-.09-.017-.084-.018-.094c-.026-.141-.05-.283-.071-.426l-.017-.118-.011-.083-.013-.102a12.01 12.01 0 01-.019-.161l-.005-.047a12.12 12.12 0 01-.034-2.145zm1.593-5.15l11.948 3.196c-.368.605-.705 1.231-1.01 1.875l11.295 3.022c-.142.82-.368 1.612-.668 2.365l-11.55-3.09L.124 10.26l.015-.1.008-.049.01-.067.015-.087.018-.098c.026-.148.056-.295.088-.442l.028-.124.02-.085.024-.097c.022-.09.045-.18.07-.268l.028-.102.023-.083.03-.1.025-.082.03-.096.026-.082.031-.095a11.896 11.896 0 011.01-2.232zm4.442-4.4L17.352 4.59a20.77 20.77 0 00-1.688 1.721l7.823 2.093c.267.852.442 1.744.513 2.665L2.106 5.213l.045-.065.027-.04.04-.055.046-.065.055-.076.054-.072.064-.086.05-.065.057-.073.055-.07.06-.074.055-.069.065-.077.054-.066.066-.077.053-.06.072-.082.053-.06.067-.074.054-.058.073-.078.058-.06.063-.067.168-.17.1-.098.059-.056.076-.071a12.084 12.084 0 012.272-1.677zM12.017 0h.097l.082.001.069.001.054.002.068.002.046.001.076.003.047.002.06.003.054.002.087.005.105.007.144.011.088.007.044.004.077.008.082.008.047.005.102.012.05.006.108.014.081.01.042.006.065.01.207.032.07.012.065.011.14.026.092.018.11.022.046.01.075.016.041.01L14.7.3l.042.01.065.015.049.012.071.017.096.024.112.03.113.03.113.032.05.015.07.02.078.024.073.023.05.016.05.016.076.025.099.033.102.036.048.017.064.023.093.034.11.041.116.045.1.04.047.02.06.024.041.018.063.026.04.018.057.025.11.048.1.046.074.035.075.036.06.028.092.046.091.045.102.052.053.028.049.026.046.024.06.033.041.022.052.029.088.05.106.06.087.051.057.034.053.032.096.059.088.055.098.062.036.024.064.041.084.056.04.027.062.042.062.043.023.017c.054.037.108.075.161.114l.083.06.065.048.056.043.086.065.082.064.04.03.05.041.086.069.079.065.085.071c.712.6 1.353 1.283 1.909 2.031L7.222.994l.062-.027.065-.028.081-.034.086-.035c.113-.045.227-.09.341-.131l.096-.035.093-.033.084-.03.096-.031c.087-.03.176-.058.264-.085l.091-.027.086-.025.102-.03.085-.023.1-.026L9.04.37l.09-.023.091-.022.095-.022.09-.02.098-.021.091-.02.095-.018.092-.018.1-.018.091-.016.098-.017.092-.014.097-.015.092-.013.102-.013.091-.012.105-.012.09-.01.105-.01c.093-.01.186-.018.28-.024l.106-.008.09-.005.11-.006.093-.004.1-.004.097-.002.099-.002.197-.002z`})]}))});function gg(e){"@babel/helpers - typeof";return gg=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},gg(e)}function _g(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function vg(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Ig(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Lg=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Fg(e,kg);return(0,P.jsxs)(`svg`,jg(jg({fill:`currentColor`,fillRule:`evenodd`,height:n,style:jg({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:wg}),(0,P.jsx)(`path`,{d:`M7.941 2c.23 0 .452.073.638.21.186.136.325.328.397.55l.593 1.814c.073.221.212.413.397.55.186.136.409.21.638.21h2.791c.23 0 .452-.074.638-.21a1.11 1.11 0 00.397-.55l.594-1.815a1.11 1.11 0 01.397-.55c.185-.136.408-.209.637-.209h1.7c.23 0 .453.073.639.21.185.136.324.328.397.55l.652 1.994c.118.361.41.635.77.728l2.957.752c.236.06.446.199.596.394.15.195.23.436.231.684v9.376c0 .248-.081.488-.231.684a1.09 1.09 0 01-.595.394l-2.957.752a1.086 1.086 0 00-.477.263 1.114 1.114 0 00-.293.465l-.653 1.994a1.11 1.11 0 01-.396.55c-.186.136-.41.21-.638.21h-1.702c-.229 0-.452-.073-.637-.21a1.11 1.11 0 01-.397-.55l-.364-1.11a1.131 1.131 0 01.15-1.002 1.074 1.074 0 01.885-.462h2.85c.29 0 .567-.116.772-.325.204-.208.32-.49.32-.785V6.444c0-.294-.116-.577-.32-.785a1.08 1.08 0 00-.771-.326h-3.273c-.29 0-.567.117-.772.326-.204.208-.32.49-.32.785v7.778c0 .295-.114.578-.319.786a1.08 1.08 0 01-.771.325h-2.182a1.08 1.08 0 01-.771-.325 1.122 1.122 0 01-.32-.786V6.444c0-.294-.115-.577-.32-.785a1.081 1.081 0 00-.77-.326H5.454c-.29 0-.567.117-.772.326-.204.208-.32.49-.32.785v11.112c0 .294.116.577.32.785.205.209.482.326.772.326h2.85a1.075 1.075 0 01.885.461 1.122 1.122 0 01.15 1.001l-.364 1.112a1.11 1.11 0 01-.397.55c-.185.136-.408.209-.637.209H6.24c-.229 0-.452-.073-.638-.21a1.11 1.11 0 01-.397-.55l-.652-1.994a1.114 1.114 0 00-.294-.465 1.086 1.086 0 00-.477-.263l-2.956-.752a1.09 1.09 0 01-.595-.394A1.124 1.124 0 010 16.688V7.312c0-.248.081-.489.231-.684.15-.195.36-.334.595-.394l2.957-.753c.178-.045.342-.136.477-.263.134-.127.235-.287.293-.464l.653-1.995a1.11 1.11 0 01.397-.55C5.788 2.075 6.01 2 6.24 2h1.701z`})]}))});function Rg(e){"@babel/helpers - typeof";return Rg=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Rg(e)}function zg(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Bg(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function i_(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var a_=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=r_(e,Zg);return(0,P.jsxs)(`svg`,$g($g({fill:`none`,height:n,style:$g({flex:`none`,lineHeight:1},r),viewBox:`0 0 33 32`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Kg}),(0,P.jsx)(`path`,{d:`m17.865 23.28 1.533 1.543c.07.07.092.175.055.267l-2.398 6.118A1.24 1.24 0 0 1 15.9 32c-.51 0-.969-.315-1.155-.793l-3.451-8.804-5.582 5.617a.246.246 0 0 1-.35 0l-1.407-1.415a.25.25 0 0 1 0-.352l6.89-6.932a1.3 1.3 0 0 1 .834-.398 1.25 1.25 0 0 1 1.232.79l2.992 7.63 1.557-3.977a.248.248 0 0 1 .408-.085zm8.224-19.3-5.583 5.617-3.45-8.805a1.24 1.24 0 0 0-1.43-.762c-.414.092-.744.407-.899.805l-2.38 6.072a.25.25 0 0 0 .055.267l1.533 1.543c.127.127.34.082.407-.085L15.9 4.655l2.991 7.629a1.24 1.24 0 0 0 2.035.425l6.922-6.965a.25.25 0 0 0 0-.352L26.44 3.977a.246.246 0 0 0-.35 0zM8.578 17.566l-3.953-1.567 7.582-3.01c.49-.195.815-.685.785-1.24a1.3 1.3 0 0 0-.395-.84l-6.886-6.93a.246.246 0 0 0-.35 0L3.954 5.395a.25.25 0 0 0 0 .353l5.583 5.617-8.75 3.472a1.25 1.25 0 0 0 0 2.325l6.079 2.412a.24.24 0 0 0 .266-.055l1.533-1.542a.25.25 0 0 0-.085-.41zm22.434-2.73-6.08-2.412a.24.24 0 0 0-.265.055l-1.533 1.542a.25.25 0 0 0 .084.41L27.172 16l-7.583 3.01a1.255 1.255 0 0 0-.785 1.24c.018.317.172.614.395.84l6.89 6.931a.246.246 0 0 0 .35 0l1.406-1.415a.25.25 0 0 0 0-.352l-5.582-5.617 8.75-3.472a1.25 1.25 0 0 0 0-2.325z`,fill:`currentColor`})]}))});function o_(e){"@babel/helpers - typeof";return o_=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},o_(e)}function s_(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function c_(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function E_(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var D_=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=T_(e,y_);return(0,P.jsxs)(`svg`,x_(x_({fill:`currentColor`,fillRule:`evenodd`,height:n,style:x_({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:m_}),(0,P.jsx)(`path`,{d:`M10.212 8.976V7.62c.127-.01.256-.017.388-.021 3.596-.117 5.957 3.184 5.957 3.184s-2.548 3.647-5.282 3.647a3.227 3.227 0 01-1.063-.175v-4.109c1.4.174 1.681.812 2.523 2.258l1.873-1.627a4.905 4.905 0 00-3.67-1.846 6.594 6.594 0 00-.729.044m0-4.476v2.025c.13-.01.259-.019.388-.024 5.002-.174 8.261 4.226 8.261 4.226s-3.743 4.69-7.643 4.69c-.338 0-.675-.031-1.007-.092v1.25c.278.038.558.057.838.057 3.629 0 6.253-1.91 8.794-4.169.421.347 2.146 1.193 2.501 1.564-2.416 2.083-8.048 3.763-11.24 3.763-.308 0-.603-.02-.894-.048V19.5H24v-15H10.21zm0 9.756v1.068c-3.356-.616-4.287-4.21-4.287-4.21a7.173 7.173 0 014.287-2.138v1.172h-.005a3.182 3.182 0 00-2.502 1.178s.615 2.276 2.507 2.931m-5.961-3.3c1.436-1.935 3.604-3.148 5.961-3.336V6.523C5.81 6.887 2 10.723 2 10.723s2.158 6.427 8.21 7.015v-1.166C5.77 16 4.25 10.958 4.25 10.958h-.002z`})]}))});function O_(e){"@babel/helpers - typeof";return O_=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},O_(e)}function k_(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function A_(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function J_(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Y_=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=q_(e,V_);return(0,P.jsxs)(`svg`,U_(U_({fill:`currentColor`,fillRule:`evenodd`,height:n,style:U_({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:I_}),(0,P.jsx)(`path`,{d:`M16.804 1.957l7.22 4.105v.087L16.73 10.21l.017-2.117-.821-.03c-1.059-.028-1.611.002-2.268.11-1.064.175-2.038.577-3.147 1.352L8.345 11.03c-.284.195-.495.336-.68.455l-.515.322-.397.234.385.23.53.338c.476.314 1.17.796 2.701 1.866 1.11.775 2.083 1.177 3.147 1.352l.3.045c.694.091 1.375.094 2.825.033l.022-2.159 7.22 4.105v.087L16.589 22l.014-1.862-.635.022c-1.386.042-2.137.002-3.138-.162-1.694-.28-3.26-.926-4.881-2.059l-2.158-1.5a21.997 21.997 0 00-.755-.498l-.467-.28a55.927 55.927 0 00-.76-.43C2.908 14.73.563 14.116 0 14.116V9.888l.14.004c.564-.007 2.91-.622 3.809-1.124l1.016-.58.438-.274c.428-.28 1.072-.726 2.686-1.853 1.621-1.133 3.186-1.78 4.881-2.059 1.152-.19 1.974-.213 3.814-.138l.02-1.907z`})]}))});function X_(e){"@babel/helpers - typeof";return X_=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},X_(e)}function Z_(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Q_(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function gv(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var _v=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=hv(e,lv);return(0,P.jsxs)(`svg`,dv(dv({height:n,style:dv({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:iv}),(0,P.jsx)(`path`,{d:`M12 22.926c.928 0 1.679-.752 1.679-1.68V6.696h-3.358v14.552c0 .927.751 1.679 1.679 1.679z`,fill:`#F9AB00`}),(0,P.jsx)(`path`,{d:`M18.69 12.005A5.819 5.819 0 0012 10.904l7.188 7.188c.296.296.807.179.933-.22a5.815 5.815 0 00-1.431-5.867z`,fill:`#5BB974`}),(0,P.jsx)(`path`,{d:`M5.31 12.005A5.819 5.819 0 0112 10.904l-7.188 7.188a.562.562 0 01-.933-.22 5.815 5.815 0 011.431-5.867z`,fill:`#129EAF`}),(0,P.jsx)(`path`,{d:`M18.157 6.426c-2.86 0-5.288 1.875-6.157 4.478h11.367a.629.629 0 00.565-.908c-1.08-2.12-3.26-3.57-5.775-3.57z`,fill:`#AF5CF7`}),(0,P.jsx)(`path`,{d:`M13.188 3.384c-2.023 2.024-2.414 5.064-1.188 7.52l8.038-8.039a.629.629 0 00-.242-1.042c-2.264-.735-4.83-.217-6.608 1.561z`,fill:`#FF8BCB`}),(0,P.jsx)(`path`,{d:`M10.812 3.384c2.023 2.024 2.414 5.064 1.188 7.52L3.962 2.865a.629.629 0 01.242-1.042c2.264-.735 4.83-.217 6.608 1.561z`,fill:`#FA7B17`}),(0,P.jsx)(`path`,{d:`M5.843 6.426c2.86 0 5.288 1.875 6.157 4.478H.633a.629.629 0 01-.565-.908c1.08-2.12 3.26-3.57 5.775-3.57z`,fill:`#4285F4`})]}))});function vv(e){"@babel/helpers - typeof";return vv=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},vv(e)}function yv(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function bv(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Rv(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var zv=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Lv(e,jv);return(0,P.jsxs)(`svg`,Nv(Nv({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Nv({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Ev}),(0,P.jsx)(`path`,{d:`M19.785 0v7.272H22.5V17.62h-2.935V24l-7.037-6.194v6.145h-1.091v-6.152L4.392 24v-6.465H1.5V7.188h2.884V0l7.053 6.494V.19h1.09v6.49L19.786 0zm-7.257 9.044v7.319l5.946 5.234V14.44l-5.946-5.397zm-1.099-.08l-5.946 5.398v7.235l5.946-5.234V8.965zm8.136 7.58h1.844V8.349H13.46l6.105 5.54v2.655zm-8.982-8.28H2.59v8.195h1.8v-2.576l6.192-5.62zM5.475 2.476v4.71h5.115l-5.115-4.71zm13.219 0l-5.115 4.71h5.115v-4.71z`})]}))});function Bv(e){"@babel/helpers - typeof";return Bv=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Bv(e)}function Vv(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Hv(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function oy(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var sy=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=ay(e,$v);return(0,P.jsxs)(`svg`,ty(ty({fill:`currentColor`,fillRule:`evenodd`,height:n,style:ty({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Jv}),(0,P.jsx)(`path`,{d:`M11.615 0l6.237 6.107c2.382 2.338 2.823 3.743 3.161 6.15-1.197-1.732-1.776-2.02-4.504-2.772C12.48 8.374 11.095 5.933 11.615 0z`}),(0,P.jsx)(`path`,{d:`M9.32 2.122C4.771 6.367 2 9.182 2 13.08c0 5.76 4.288 9.788 9.745 9.918 5.457.13 9.441-5.284 9.095-8.403-.347-3.118-4.418-3.81-4.418-3.81 1.69 3.16-.13 8.098-4.894 8.098-5.154 0-6.8-6.02-4.2-9.008.82 1.617 1.879 2.563 2.674 3.273.717.64 1.219 1.09 1.136 1.664-.173 1.213-1.385.866-1.385.866.346.607 3.6 1.473 4.59-1.342.613-1.741-.423-2.789-1.714-4.096-1.632-1.651-3.672-3.717-3.31-8.118z`})]}))});function cy(e){"@babel/helpers - typeof";return cy=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},cy(e)}function ly(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function uy(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Oy(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var ky=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Dy(e,xy);return(0,P.jsxs)(`svg`,Cy(Cy({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Cy({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:gy}),(0,P.jsx)(`path`,{d:`M7.223 21c4.252 0 7.018-2.22 7.018-5.56 0-2.59-1.682-4.236-4.69-4.918l-1.93-.571c-1.694-.375-2.683-.825-2.45-1.975.194-.957.773-1.497 2.122-1.497 4.285 0 5.873 1.497 5.873 1.497v-3.6S11.62 3 7.293 3C3.213 3 1 5.07 1 8.273c0 2.59 1.534 4.097 4.645 4.812l.334.083c.473.144 1.112.335 1.916.572 1.59.375 1.999.773 1.999 1.966 0 1.09-1.15 1.71-2.67 1.71C2.841 17.416 1 15.231 1 15.231v3.989S2.152 21 7.223 21z`}),(0,P.jsx)(`path`,{d:`M20.374 20.73c1.505 0 2.626-1.073 2.626-2.526 0-1.484-1.089-2.526-2.626-2.526-1.505 0-2.594 1.042-2.594 2.526 0 1.484 1.089 2.526 2.594 2.526z`})]}))});function Ay(e){"@babel/helpers - typeof";return Ay=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Ay(e)}function jy(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function My(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Yy(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Xy=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Jy(e,Hy);return(0,P.jsxs)(`svg`,Wy(Wy({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Wy({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:`V0`}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M14.252 8.25h5.624c.088 0 .176.006.26.018l-5.87 5.87a1.889 1.889 0 01-.019-.265V8.25h-2.25v5.623a4.124 4.124 0 004.125 4.125h5.624v-2.25h-5.624c-.09 0-.179-.006-.265-.018l5.874-5.875a1.9 1.9 0 01.02.27v5.623H24v-5.624A4.124 4.124 0 0019.876 6h-5.624v2.25zM0 7.5v.006l7.686 9.788c.924 1.176 2.813.523 2.813-.973V7.5H8.25v6.87L2.856 7.5H0z`})]}))});function Zy(e){"@babel/helpers - typeof";return Zy=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Zy(e)}function Qy(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function $y(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function _b(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var vb=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=gb(e,ub);return(0,P.jsxs)(`svg`,fb(fb({fill:`currentColor`,fillRule:`evenodd`,height:n,style:fb({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:ab}),(0,P.jsx)(`path`,{d:`M11.995 20.216a1.892 1.892 0 100 3.785 1.892 1.892 0 000-3.785zm0 2.806a.927.927 0 11.927-.914.914.914 0 01-.927.914z`}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M21.687 14.144c.237.038.452.16.605.344a.978.978 0 01-.18 1.3l-8.24 6.082a1.892 1.892 0 00-1.147-1.508l8.28-6.08a.991.991 0 01.682-.138z`}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M10.122 21.842l-8.217-6.066a.952.952 0 01-.206-1.287.978.978 0 011.287-.206l8.28 6.08a1.893 1.893 0 00-1.144 1.479z`}),(0,P.jsx)(`path`,{d:`M4.273 4.475a.978.978 0 01-.965-.965V1.09a.978.978 0 111.943 0v2.42a.978.978 0 01-.978.965zM4.247 13.034a.978.978 0 100-1.956.978.978 0 000 1.956zM4.247 10.19a.978.978 0 100-1.956.978.978 0 000 1.956zM4.247 7.332a.978.978 0 100-1.956.978.978 0 000 1.956z`}),(0,P.jsx)(`path`,{d:`M19.718 7.307a.978.978 0 01-.965-.979v-2.42a.965.965 0 011.93 0v2.42a.964.964 0 01-.965.979zM19.743 13.047a.978.978 0 100-1.956.978.978 0 000 1.956zM19.743 10.151a.978.978 0 100-1.956.978.978 0 000 1.956zM19.743 2.068a.978.978 0 100-1.956.978.978 0 000 1.956z`}),(0,P.jsx)(`path`,{d:`M11.995 15.917a.978.978 0 01-.965-.965v-2.459a.978.978 0 011.943 0v2.433a.976.976 0 01-.978.991zM11.995 18.762a.978.978 0 100-1.956.978.978 0 000 1.956zM11.995 10.64a.978.978 0 100-1.956.978.978 0 000 1.956zM11.995 7.783a.978.978 0 100-1.956.978.978 0 000 1.956z`}),(0,P.jsx)(`path`,{d:`M15.856 10.177a.978.978 0 01-.965-.965v-2.42a.977.977 0 011.702-.763.979.979 0 01.241.763v2.42a.978.978 0 01-.978.965zM15.869 4.913a.978.978 0 100-1.956.978.978 0 000 1.956zM15.869 15.853a.978.978 0 100-1.956.978.978 0 000 1.956zM15.869 12.996a.978.978 0 100-1.956.978.978 0 000 1.956z`}),(0,P.jsx)(`path`,{d:`M8.121 15.853a.978.978 0 100-1.956.978.978 0 000 1.956zM8.121 7.783a.978.978 0 100-1.956.978.978 0 000 1.956zM8.121 4.913a.978.978 0 100-1.957.978.978 0 000 1.957zM8.134 12.996a.978.978 0 01-.978-.94V9.611a.965.965 0 011.93 0v2.445a.966.966 0 01-.952.94z`})]}))});function yb(e){"@babel/helpers - typeof";return yb=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},yb(e)}function bb(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function xb(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function zb(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Bb=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Rb(e,Mb);return(0,P.jsxs)(`svg`,Pb(Pb({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Pb({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Db}),(0,P.jsx)(`path`,{d:`M5.407 0v.066a.974.974 0 00-.048.245c-.011.11-.016.208-.016.295 0 .339.043.715.128 1.13.097.405.274.912.531 1.524l7.125 16.366L20.011 3.39c.161-.404.333-.846.515-1.327.182-.48.273-.966.273-1.458a1.406 1.406 0 00-.096-.54V0H24v.066c-.204.207-.45.578-.74 1.114-.29.535-.606 1.195-.949 1.982L13.095 24h-1.287L3.075 3.965c-.204-.47-.418-.923-.644-1.36-.214-.437-.418-.83-.61-1.18-.194-.36-.365-.66-.515-.9A5.666 5.666 0 001 .064V0h4.407z`})]}))});function Vb(e){"@babel/helpers - typeof";return Vb=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Vb(e)}function Hb(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Ub(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function sx(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var cx=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=ox(e,ex);return(0,P.jsxs)(`svg`,nx(nx({fill:`currentColor`,fillRule:`evenodd`,height:n,style:nx({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Yb}),(0,P.jsx)(`path`,{d:`M.958 15.936a.459.459 0 01.459.44v2.729a.46.46 0 01-.918 0v-2.729a.459.459 0 01.459-.44zm4.814-2.035a.46.46 0 01.553.45v4.754a.458.458 0 11-.918 0V15.48L3.74 17.202a.462.462 0 01-.655.016.462.462 0 01-.065-.082L.628 14.67a.459.459 0 01.658-.637l2.124 2.187 2.127-2.188a.46.46 0 01.235-.13zm2.068.004a.46.46 0 01.458.445v4.755a.46.46 0 01-.458.458.459.459 0 01-.458-.458V14.35a.459.459 0 01.458-.445zm1.973 2.014a.46.46 0 01.46.457v2.729a.46.46 0 01-.784.324.46.46 0 01-.134-.324v-2.729a.46.46 0 01.458-.458zm.002-2.045a.458.458 0 01.328.157l2.127 2.19 2.125-2.19a.459.459 0 01.784.318v4.756a.46.46 0 01-.455.458.46.46 0 01-.458-.458V15.48l-1.667 1.723a.46.46 0 01-.65.008l-.005-.005c0-.002-.002-.002-.004-.003l-2.455-2.534a.46.46 0 01-.008-.667.461.461 0 01.338-.128zm6.797 1.206a.46.46 0 01.53.651A1.966 1.966 0 0019.81 18.4a.462.462 0 01.623.18.46.46 0 01-.181.624 2.863 2.863 0 01-1.38.353l-.142-.004a2.88 2.88 0 01-2.393-4.263.461.461 0 01.274-.21zm.864-.931a2.884 2.884 0 013.915 3.914.46.46 0 01-.402.24l-.057-.004a.458.458 0 01-.164-.055.46.46 0 01-.182-.622 1.967 1.967 0 00-2.669-2.67.459.459 0 11-.441-.803zM9.59 6.368c1.481 0 1.696 1.202 1.696 1.654v2.648h-.917v-.432c-.26.346-.792.535-1.36.535-.133 0-1.289-.03-1.384-1.136-.082-.932.675-1.61 2.053-1.61h.691c0-.563-.367-.886-.983-.886-.44.013-.864.174-1.2.458l-.36-.664c.484-.379 1.012-.567 1.764-.567zm4.427.1c1.263 0 2.082.97 2.083 2.15 0 1.181-.824 2.154-2.083 2.154-1.26 0-2.084-.972-2.084-2.152 0-1.18.82-2.153 2.084-2.153zm6.801.015c.68 0 1.202.465 1.197 1.548v2.642H21.1V8.29c0-.312-.002-.98-.63-.98s-.628.667-.628.838v2.524h-.89V8.148c0-.17-.001-.838-.63-.838-.628 0-.628.668-.628.98v2.383h-.917v-4.03h.917V7a1.22 1.22 0 01.947-.516c.398 0 .76.193.982.686a1.321 1.321 0 011.195-.686zm-18.093.872l1.457-1.772H5.32L3.311 8.07l2.14 2.602H4.24L2.725 8.796 1.21 10.672H0L2.138 8.07.13 5.583h1.138l1.458 1.772zm4.149 3.317h-.916V6.644h.916v4.028zm16.99 0h-.916V6.644h.916v4.028zM9.925 8.71c-1.055 0-1.359.412-1.326.742.032.329.324.537.757.537a1.013 1.013 0 001.014-.968l.002-.31h-.447zM14.018 7.3c-.663 0-1.184.487-1.184 1.32 0 .832.52 1.32 1.184 1.32.662 0 1.182-.49 1.182-1.32 0-.832-.52-1.32-1.182-1.32zM6.417 5.001a.568.568 0 01.587.582.588.588 0 01-1.175 0A.57.57 0 016.417 5zm16.991 0a.57.57 0 01.592.582.588.588 0 01-1.174 0 .57.57 0 01.357-.542.572.572 0 01.225-.04z`})]}))});function lx(e){"@babel/helpers - typeof";return lx=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},lx(e)}function ux(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function dx(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function kx(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Ax=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Ox(e,Sx);return(0,P.jsxs)(`svg`,wx(wx({fill:`currentColor`,fillRule:`evenodd`,height:n,style:wx({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:_x}),(0,P.jsx)(`path`,{d:`M12.105 2L9.927 4.953H.653L2.83 2h9.276zM23.254 19.048L21.078 22h-9.242l2.174-2.952h9.244zM24 2L9.264 22H0L14.736 2H24z`})]}))});function jx(e){"@babel/helpers - typeof";return jx=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},jx(e)}function Mx(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Nx(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Zx(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Qx=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Xx(e,Wx);return(0,P.jsxs)(`svg`,Kx(Kx({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Kx({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:zx}),(0,P.jsx)(`path`,{d:`M2.373 4.301L1 7.663a4.608 4.608 0 012.602 2.602c.976 2.422-.18 5.169-2.566 6.145L2.41 19.77c2.096-.867 3.723-2.494 4.554-4.59A8.346 8.346 0 002.374 4.3zM5.916 21.072L8.084 24c1.049-.759 1.988-1.699 2.783-2.71l-2.819-2.242a11.324 11.324 0 01-2.132 2.024zM14.157 12.036c0-4.699-2.277-9.144-6.073-11.928L5.916 3.036c2.891 2.096 4.59 5.458 4.626 9.036A14.81 14.81 0 0016.578 24l2.133-2.928c-2.856-2.132-4.554-5.458-4.554-9.036zM18.82 2.964L16.722 0a14.601 14.601 0 00-2.964 2.82l2.82 2.24a11.256 11.256 0 012.24-2.096zM21.277 14.06c-1.12-2.421-.036-5.313 2.386-6.433l-1.518-3.29a8.457 8.457 0 00-4.193 4.265c-1.916 4.265 0 9.29 4.301 11.17l1.482-3.29a4.862 4.862 0 01-2.458-2.422z`})]}))});function $x(e){"@babel/helpers - typeof";return $x=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},$x(e)}function eS(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function tS(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function yS(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var bS=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=vS(e,fS);return(0,P.jsxs)(`svg`,mS(mS({fill:`currentColor`,fillRule:`evenodd`,height:n,style:mS({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:sS}),(0,P.jsx)(`path`,{d:`M17.583 12.344L14.606 17.5H20.6c.22 0 .424-.117.535-.308l2.799-4.848h-6.351zM23.934 11.656l-2.799-4.848A.616.616 0 0020.6 6.5h-5.994l2.977 5.156h6.35zM8.653 6.5h5.953l-2.997-5.191A.616.616 0 0011.074 1H5.476l3.176 5.5zM4.881 1.343L2.083 6.191a.618.618 0 000 .617l2.997 5.191 2.976-5.156-3.175-5.5zM8.057 17.155L5.081 12l-2.998 5.192a.618.618 0 000 .617l2.798 4.848 3.175-5.5h.001zM5.476 23h5.598c.22 0 .424-.117.535-.308l2.997-5.192H8.653L5.477 23z`})]}))});function xS(e){"@babel/helpers - typeof";return xS=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},xS(e)}function SS(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function CS(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=Array(t);n=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function qS(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var JS=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=KS(e,PS),a=BS(cl(kS,3),3),o=a[0],s=a[1],c=a[2];return(0,P.jsxs)(`svg`,IS(IS({height:n,style:IS({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:kS}),(0,P.jsx)(`path`,{d:`M7.242 1.613A1.11 1.11 0 018.295.857h6.977L8.03 22.316a1.11 1.11 0 01-1.052.755h-5.43a1.11 1.11 0 01-1.053-1.466L7.242 1.613z`,fill:o.fill}),(0,P.jsx)(`path`,{d:`M18.397 15.296H7.4a.51.51 0 00-.347.882l7.066 6.595c.206.192.477.298.758.298h6.226l-2.706-7.775z`,fill:`#0078D4`}),(0,P.jsx)(`path`,{d:`M15.272.857H7.497L0 23.071h7.775l1.596-4.73 5.068 4.73h6.665l-2.707-7.775h-7.998L15.272.857z`,fill:s.fill}),(0,P.jsx)(`path`,{d:`M17.193 1.613a1.11 1.11 0 00-1.052-.756h-7.81.035c.477 0 .9.304 1.052.756l6.748 19.992a1.11 1.11 0 01-1.052 1.466h-.12 7.895a1.11 1.11 0 001.052-1.466L17.193 1.613z`,fill:c.fill}),(0,P.jsxs)(`defs`,{children:[(0,P.jsxs)(`linearGradient`,{gradientUnits:`userSpaceOnUse`,id:o.id,x1:`8.247`,x2:`1.002`,y1:`1.626`,y2:`23.03`,children:[(0,P.jsx)(`stop`,{stopColor:`#114A8B`}),(0,P.jsx)(`stop`,{offset:`1`,stopColor:`#0669BC`})]}),(0,P.jsxs)(`linearGradient`,{gradientUnits:`userSpaceOnUse`,id:s.id,x1:`14.042`,x2:`12.324`,y1:`15.302`,y2:`15.888`,children:[(0,P.jsx)(`stop`,{stopOpacity:`.3`}),(0,P.jsx)(`stop`,{offset:`.071`,stopOpacity:`.2`}),(0,P.jsx)(`stop`,{offset:`.321`,stopOpacity:`.1`}),(0,P.jsx)(`stop`,{offset:`.623`,stopOpacity:`.05`}),(0,P.jsx)(`stop`,{offset:`1`,stopOpacity:`0`})]}),(0,P.jsxs)(`linearGradient`,{gradientUnits:`userSpaceOnUse`,id:c.id,x1:`12.841`,x2:`20.793`,y1:`1.626`,y2:`22.814`,children:[(0,P.jsx)(`stop`,{stopColor:`#3CCBF4`}),(0,P.jsx)(`stop`,{offset:`1`,stopColor:`#2892DF`})]})]})]}))});function YS(e){"@babel/helpers - typeof";return YS=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},YS(e)}function XS(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function ZS(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=Array(t);n=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function xC(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var SC=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=bC(e,cC),a=mC(cl(rC,3),3),o=a[0],s=a[1],c=a[2];return(0,P.jsxs)(`svg`,uC(uC({height:n,style:uC({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:rC}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M16.233 0c.713 0 1.345.551 1.572 1.329.227.778 1.555 5.59 1.555 5.59v9.562h-4.813L14.645 0h1.588z`,fill:o.fill,fillRule:`evenodd`}),(0,P.jsx)(`path`,{d:`M23.298 7.47c0-.34-.275-.6-.6-.6h-2.835a3.617 3.617 0 00-3.614 3.615v5.996h3.436a3.617 3.617 0 003.613-3.614V7.47z`,fill:s.fill}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M16.233 0a.982.982 0 00-.989.989l-.097 18.198A4.814 4.814 0 0110.334 24H1.6a.597.597 0 01-.567-.794l7-19.981A4.819 4.819 0 0112.57 0h3.679-.016z`,fill:c.fill,fillRule:`evenodd`}),(0,P.jsxs)(`defs`,{children:[(0,P.jsxs)(`linearGradient`,{gradientUnits:`userSpaceOnUse`,id:o.id,x1:`18.242`,x2:`14.191`,y1:`16.837`,y2:`.616`,children:[(0,P.jsx)(`stop`,{stopColor:`#712575`}),(0,P.jsx)(`stop`,{offset:`.09`,stopColor:`#9A2884`}),(0,P.jsx)(`stop`,{offset:`.18`,stopColor:`#BF2C92`}),(0,P.jsx)(`stop`,{offset:`.27`,stopColor:`#DA2E9C`}),(0,P.jsx)(`stop`,{offset:`.34`,stopColor:`#EB30A2`}),(0,P.jsx)(`stop`,{offset:`.4`,stopColor:`#F131A5`}),(0,P.jsx)(`stop`,{offset:`.5`,stopColor:`#EC30A3`}),(0,P.jsx)(`stop`,{offset:`.61`,stopColor:`#DF2F9E`}),(0,P.jsx)(`stop`,{offset:`.72`,stopColor:`#C92D96`}),(0,P.jsx)(`stop`,{offset:`.83`,stopColor:`#AA2A8A`}),(0,P.jsx)(`stop`,{offset:`.95`,stopColor:`#83267C`}),(0,P.jsx)(`stop`,{offset:`1`,stopColor:`#712575`})]}),(0,P.jsxs)(`linearGradient`,{gradientUnits:`userSpaceOnUse`,id:s.id,x1:`19.782`,x2:`19.782`,y1:`.34`,y2:`23.222`,children:[(0,P.jsx)(`stop`,{stopColor:`#DA7ED0`}),(0,P.jsx)(`stop`,{offset:`.08`,stopColor:`#B17BD5`}),(0,P.jsx)(`stop`,{offset:`.19`,stopColor:`#8778DB`}),(0,P.jsx)(`stop`,{offset:`.3`,stopColor:`#6276E1`}),(0,P.jsx)(`stop`,{offset:`.41`,stopColor:`#4574E5`}),(0,P.jsx)(`stop`,{offset:`.54`,stopColor:`#2E72E8`}),(0,P.jsx)(`stop`,{offset:`.67`,stopColor:`#1D71EB`}),(0,P.jsx)(`stop`,{offset:`.81`,stopColor:`#1471EC`}),(0,P.jsx)(`stop`,{offset:`1`,stopColor:`#1171ED`})]}),(0,P.jsxs)(`linearGradient`,{gradientUnits:`userSpaceOnUse`,id:c.id,x1:`18.404`,x2:`3.236`,y1:`.859`,y2:`25.183`,children:[(0,P.jsx)(`stop`,{stopColor:`#DA7ED0`}),(0,P.jsx)(`stop`,{offset:`.05`,stopColor:`#B77BD4`}),(0,P.jsx)(`stop`,{offset:`.11`,stopColor:`#9079DA`}),(0,P.jsx)(`stop`,{offset:`.18`,stopColor:`#6E77DF`}),(0,P.jsx)(`stop`,{offset:`.25`,stopColor:`#5175E3`}),(0,P.jsx)(`stop`,{offset:`.33`,stopColor:`#3973E7`}),(0,P.jsx)(`stop`,{offset:`.42`,stopColor:`#2772E9`}),(0,P.jsx)(`stop`,{offset:`.54`,stopColor:`#1A71EB`}),(0,P.jsx)(`stop`,{offset:`.68`,stopColor:`#1371EC`}),(0,P.jsx)(`stop`,{offset:`1`,stopColor:`#1171ED`})]})]})]}))});function CC(e){"@babel/helpers - typeof";return CC=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},CC(e)}function wC(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function TC(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function UC(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var WC=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=HC(e,IC);return(0,P.jsxs)(`svg`,RC(RC({fill:`currentColor`,fillRule:`evenodd`,height:n,style:RC({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:jC}),(0,P.jsx)(`path`,{d:`M8.859 11.735c1.017-1.71 4.059-3.083 6.202.286 1.579 2.284 4.284 4.397 4.284 4.397s2.027 1.601.73 4.684c-1.24 2.956-5.64 1.607-6.005 1.49l-.024-.009s-1.746-.568-3.776-.112c-2.026.458-3.773.286-3.773.286l-.045-.001c-.328-.01-2.38-.187-3.001-2.968-.675-3.028 2.365-4.687 2.592-4.968.226-.288 1.802-1.37 2.816-3.085zm.986 1.738v2.032h-1.64s-1.64.138-2.213 2.014c-.2 1.252.177 1.99.242 2.148.067.157.596 1.073 1.927 1.342h3.078v-7.514l-1.394-.022zm3.588 2.191l-1.44.024v3.956s.064.985 1.44 1.344h3.541v-5.3h-1.528v3.979h-1.46s-.466-.068-.553-.447v-3.556zM9.82 16.715v3.06H8.58s-.863-.045-1.126-1.049c-.136-.445.02-.959.088-1.16.063-.203.353-.671.951-.85H9.82zm9.525-9.036c2.086 0 2.646 2.06 2.646 2.742 0 .688.284 3.597-2.309 3.655-2.595.057-2.704-1.77-2.704-3.08 0-1.374.277-3.317 2.367-3.317zM4.24 6.08c1.523-.135 2.645 1.55 2.762 2.513.07.625.393 3.486-1.975 4-2.364.515-3.244-2.249-2.984-3.544 0 0 .28-2.797 2.197-2.969zm8.847-1.483c.14-1.31 1.69-3.316 2.931-3.028 1.236.285 2.367 1.944 2.137 3.37-.224 1.428-1.345 3.313-3.095 3.082-1.748-.226-2.143-1.823-1.973-3.424zM9.425 1c1.307 0 2.364 1.519 2.364 3.398 0 1.879-1.057 3.4-2.364 3.4s-2.367-1.521-2.367-3.4C7.058 2.518 8.118 1 9.425 1z`})]}))});function GC(e){"@babel/helpers - typeof";return GC=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},GC(e)}function KC(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function qC(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function dw(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var fw=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=uw(e,iw);return(0,P.jsxs)(`svg`,ow(ow({fill:`currentColor`,fillRule:`evenodd`,height:n,style:ow({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:$C}),(0,P.jsx)(`path`,{d:`M2.316 4.8h14.682v4.8H7.31a.302.302 0 00-.308.3v4.2c0 .171.14.3.308.3h9.688v4.8h-4.686a.302.302 0 00-.308.3v4.2c0 .171.141.3.308.3h4.378a.297.297 0 00.308-.3v-4.5h4.694a.302.302 0 00.308-.3v-4.2c0-.171-.14-.3-.308-.3h-4.694V9.6h4.694A.302.302 0 0022 9.3V5.1c0-.171-.14-.3-.308-.3h-4.694V.3c0-.171-.14-.3-.308-.3H2.316A.31.31 0 002 .3v4.2c0 .171.14.3.316.3z`})]}))});function pw(e){"@babel/helpers - typeof";return pw=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},pw(e)}function mw(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function hw(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Nw(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Pw=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Mw(e,Ew);return(0,P.jsxs)(`svg`,Ow(Ow({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Ow({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:xw}),(0,P.jsx)(`path`,{d:`M13.05 15.513h3.08c.214 0 .389.177.389.394v1.82a1.704 1.704 0 011.296 1.661c0 .943-.755 1.708-1.685 1.708-.931 0-1.686-.765-1.686-1.708 0-.807.554-1.484 1.297-1.662v-1.425h-2.69v4.663a.395.395 0 01-.188.338l-2.69 1.641a.385.385 0 01-.405-.002l-4.926-3.086a.395.395 0 01-.185-.336V16.3L2.196 14.87A.395.395 0 012 14.555L2 14.528V9.406c0-.14.073-.27.192-.34l2.465-1.462V4.448c0-.129.062-.249.165-.322l.021-.014L9.77 1.058a.385.385 0 01.407 0l2.69 1.675a.395.395 0 01.185.336V7.6h3.856V5.683a1.704 1.704 0 01-1.296-1.662c0-.943.755-1.708 1.685-1.708.931 0 1.685.765 1.685 1.708 0 .807-.553 1.484-1.296 1.662v2.311a.391.391 0 01-.389.394h-4.245v1.806h6.624a1.69 1.69 0 011.64-1.313c.93 0 1.685.764 1.685 1.707 0 .943-.754 1.708-1.685 1.708a1.69 1.69 0 01-1.64-1.314H13.05v1.937h4.953l.915 1.18a1.66 1.66 0 01.84-.227c.931 0 1.685.764 1.685 1.707 0 .943-.754 1.708-1.685 1.708-.93 0-1.685-.765-1.685-1.708 0-.346.102-.668.276-.937l-.724-.935H13.05v1.806zM9.973 1.856L7.93 3.122V6.09h-.778V3.604L5.435 4.669v2.945l2.11 1.36L9.712 7.61V5.334h.778V7.83c0 .136-.07.263-.184.335L7.963 9.638v2.081l1.422 1.009-.446.646-1.406-.998-1.53 1.005-.423-.66 1.605-1.055v-1.99L5.038 8.29l-2.26 1.34v1.676l1.972-1.189.398.677-2.37 1.429V14.3l2.166 1.258 2.27-1.368.397.677-2.176 1.311V19.3l1.876 1.175 2.365-1.426.398.678-2.017 1.216 1.918 1.201 2.298-1.403v-5.78l-4.758 2.893-.4-.675 5.158-3.136V3.289L9.972 1.856zM16.13 18.47a.913.913 0 00-.908.92c0 .507.406.918.908.918a.913.913 0 00.907-.919.913.913 0 00-.907-.92zm3.63-3.81a.913.913 0 00-.908.92c0 .508.406.92.907.92a.913.913 0 00.908-.92.913.913 0 00-.908-.92zm1.555-4.99a.913.913 0 00-.908.92c0 .507.407.918.908.918a.913.913 0 00.907-.919.913.913 0 00-.907-.92zM17.296 3.1a.913.913 0 00-.907.92c0 .508.406.92.907.92a.913.913 0 00.908-.92.913.913 0 00-.908-.92z`})]}))});function Fw(e){"@babel/helpers - typeof";return Fw=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Fw(e)}function Iw(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Lw(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function tT(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var nT=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=eT(e,Jw);return(0,P.jsxs)(`svg`,Xw(Xw({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Xw({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Uw}),(0,P.jsx)(`path`,{d:`M17.113 10.248H14.56l-2.553-3.616-7.963 11.27h2.558l5.405-7.654h2.552l-5.404 7.653h2.565l5.392-7.653L24 20 19.97 20v-2.091l-2.857-4.044-2.842 4.037V20H0L12.008 3l5.105 7.249z`})]}))});function rT(e){"@babel/helpers - typeof";return rT=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},rT(e)}function iT(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function aT(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function CT(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var wT=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=ST(e,gT);return(0,P.jsxs)(`svg`,vT(vT({fill:`currentColor`,fillRule:`evenodd`,height:n,style:vT({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:dT}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M14.121 2.701a9.299 9.299 0 000 18.598V22.7c-5.91 0-10.7-4.791-10.7-10.701S8.21 1.299 14.12 1.299V2.7zm4.752 3.677A7.353 7.353 0 109.42 17.643l-.901 1.074a8.754 8.754 0 01-1.08-12.334 8.755 8.755 0 0112.335-1.08l-.901 1.075zm-2.255.844a5.407 5.407 0 00-5.048 9.563l-.656 1.24a6.81 6.81 0 016.358-12.043l-.654 1.24zM14.12 8.539a3.46 3.46 0 100 6.922v1.402a4.863 4.863 0 010-9.726v1.402z`}),(0,P.jsx)(`path`,{d:`M15.407 10.836a2.24 2.24 0 00-.51-.409 1.084 1.084 0 00-.544-.152c-.255 0-.483.047-.684.14a1.58 1.58 0 00-.84.912c-.074.203-.11.416-.11.631 0 .218.036.43.11.631a1.594 1.594 0 00.84.913c.2.093.43.14.684.14.216 0 .417-.046.602-.135.188-.09.35-.225.475-.392l.928 1.006c-.14.14-.3.261-.482.363a3.367 3.367 0 01-1.083.38c-.17.026-.317.04-.44.04a3.315 3.315 0 01-1.182-.21 2.825 2.825 0 01-.961-.597 2.816 2.816 0 01-.644-.929 2.987 2.987 0 01-.238-1.21c0-.444.08-.847.238-1.21.15-.35.368-.666.643-.929.278-.261.605-.464.962-.596a3.315 3.315 0 011.182-.21c.355 0 .712.068 1.072.204.361.138.685.36.944.649l-.962.97z`})]}))});function TT(e){"@babel/helpers - typeof";return TT=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},TT(e)}function ET(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function DT(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function GT(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var KT=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=WT(e,RT);return(0,P.jsxs)(`svg`,BT(BT({fill:`currentColor`,fillRule:`evenodd`,height:n,style:BT({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:NT}),(0,P.jsx)(`path`,{d:`M16.493 17.4c.135-.52.08-.983-.161-1.338-.215-.328-.592-.519-1.05-.519l-8.663-.109a.148.148 0 01-.135-.082c-.027-.054-.027-.109-.027-.163.027-.082.108-.164.189-.164l8.744-.11c1.05-.054 2.153-.9 2.556-1.937l.511-1.31c.027-.055.027-.11.027-.164C17.92 8.91 15.66 7 12.942 7c-2.503 0-4.628 1.638-5.381 3.903a2.432 2.432 0 00-1.803-.491c-1.21.109-2.153 1.092-2.287 2.32-.027.328 0 .628.054.9C1.56 13.688 0 15.326 0 17.319c0 .19.027.355.027.545 0 .082.08.137.161.137h15.983c.08 0 .188-.055.215-.164l.107-.437`}),(0,P.jsx)(`path`,{d:`M19.238 11.75h-.242c-.054 0-.108.054-.135.109l-.35 1.2c-.134.52-.08.983.162 1.338.215.328.592.518 1.05.518l1.855.11c.054 0 .108.027.135.082.027.054.027.109.027.163-.027.082-.108.164-.188.164l-1.91.11c-1.05.054-2.153.9-2.557 1.937l-.134.355c-.027.055.026.137.107.137h6.592c.081 0 .162-.055.162-.137.107-.41.188-.846.188-1.31-.027-2.62-2.153-4.777-4.762-4.777`})]}))});function qT(e){"@babel/helpers - typeof";return qT=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},qT(e)}function JT(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function YT(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function pE(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var mE=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=fE(e,oE),a=sl(tE),o=a.id,s=a.fill;return(0,P.jsxs)(`svg`,cE(cE({height:n,style:cE({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:tE}),(0,P.jsx)(`path`,{d:`M12 0L4.583 6.583c-3.23 2.869-3.23 7.965 0 10.834L12 24l7.417-6.583c3.23-2.869 3.23-7.965 0-10.834L12 0z`,fill:s}),(0,P.jsx)(`defs`,{children:(0,P.jsxs)(`linearGradient`,{gradientUnits:`userSpaceOnUse`,id:o,x1:`18.919`,x2:`4.853`,y1:`5.595`,y2:`18.301`,children:[(0,P.jsx)(`stop`,{stopColor:`#F4BF45`}),(0,P.jsx)(`stop`,{offset:`.35`,stopColor:`#E48047`}),(0,P.jsx)(`stop`,{offset:`.69`,stopColor:`#C73361`}),(0,P.jsx)(`stop`,{offset:`1`,stopColor:`#A42F5F`})]})})]}))});function hE(e){"@babel/helpers - typeof";return hE=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},hE(e)}function gE(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function _E(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function FE(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var IE=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=PE(e,OE);return(0,P.jsxs)(`svg`,AE(AE({height:n,style:AE({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:CE}),(0,P.jsx)(`path`,{d:`M3.294 7.821A2.297 2.297 0 011 5.527a2.297 2.297 0 012.294-2.295A2.297 2.297 0 015.59 5.527 2.297 2.297 0 013.294 7.82zm0-3.688a1.396 1.396 0 000 2.79 1.396 1.396 0 000-2.79zM3.294 14.293A2.297 2.297 0 011 11.998a2.297 2.297 0 012.294-2.294 2.297 2.297 0 012.295 2.294 2.297 2.297 0 01-2.295 2.295zm0-3.688a1.395 1.395 0 000 2.788 1.395 1.395 0 100-2.788zM3.294 20.761A2.297 2.297 0 011 18.467a2.297 2.297 0 012.294-2.295 2.297 2.297 0 012.295 2.295 2.297 2.297 0 01-2.295 2.294zm0-3.688a1.396 1.396 0 000 2.79 1.396 1.396 0 000-2.79zM20.738 7.821a2.297 2.297 0 01-2.295-2.294 2.297 2.297 0 012.294-2.295 2.297 2.297 0 012.295 2.295 2.297 2.297 0 01-2.294 2.294zm0-3.688a1.396 1.396 0 101.395 1.395c0-.77-.626-1.395-1.395-1.395zM20.738 14.293a2.297 2.297 0 01-2.295-2.295 2.297 2.297 0 012.294-2.294 2.297 2.297 0 012.295 2.294 2.297 2.297 0 01-2.294 2.295zm0-3.688c-.769 0-1.395.625-1.395 1.393a1.396 1.396 0 002.79 0c0-.77-.626-1.393-1.395-1.393zM20.738 20.761a2.297 2.297 0 01-2.295-2.294 2.297 2.297 0 012.294-2.295 2.297 2.297 0 012.295 2.295 2.297 2.297 0 01-2.294 2.294zm0-3.688a1.396 1.396 0 101.395 1.395c0-.77-.626-1.395-1.395-1.395zM12.016 11.057a2.297 2.297 0 01-2.294-2.294 2.297 2.297 0 012.294-2.295 2.297 2.297 0 012.295 2.295 2.297 2.297 0 01-2.295 2.294zm0-3.688a1.396 1.396 0 101.395 1.395c0-.77-.625-1.395-1.395-1.395zM12.017 4.589a2.297 2.297 0 01-2.295-2.295A2.297 2.297 0 0112.017 0a2.297 2.297 0 012.294 2.294 2.297 2.297 0 01-2.294 2.295zm0-3.688a1.396 1.396 0 101.395 1.395c0-.77-.626-1.395-1.395-1.395zM12.017 17.529a2.297 2.297 0 01-2.295-2.295 2.297 2.297 0 012.295-2.294 2.297 2.297 0 012.294 2.294 2.297 2.297 0 01-2.294 2.295zm0-3.688a1.396 1.396 0 101.395 1.395c0-.77-.626-1.395-1.395-1.395zM12.016 24a2.297 2.297 0 01-2.294-2.295 2.297 2.297 0 012.294-2.294 2.297 2.297 0 012.295 2.294A2.297 2.297 0 0112.016 24zm0-3.688a1.396 1.396 0 101.395 1.395c0-.77-.625-1.395-1.395-1.395z`,fill:`#2A3275`}),(0,P.jsx)(`path`,{d:`M8.363 8.222a.742.742 0 01-.277-.053l-1.494-.596a.75.75 0 11.557-1.392l1.493.595a.75.75 0 01-.278 1.446h-.001zM8.363 14.566a.743.743 0 01-.277-.053l-1.494-.595a.75.75 0 11.557-1.393l1.493.596a.75.75 0 01-.278 1.445h-.001zM17.124 11.397a.741.741 0 01-.277-.054l-1.493-.595a.75.75 0 11.555-1.392l1.493.595a.75.75 0 01-.278 1.446zM17.124 5.05a.744.744 0 01-.277-.054L15.354 4.4a.75.75 0 01.555-1.392l1.493.596a.75.75 0 01-.278 1.445zM17.124 17.739a.743.743 0 01-.277-.053l-1.494-.596a.75.75 0 11.556-1.392l1.493.596a.75.75 0 01-.278 1.445zM6.91 17.966a.75.75 0 01-.279-1.445l1.494-.595a.749.749 0 11.556 1.392l-1.493.595a.743.743 0 01-.277.053H6.91zM6.91 11.66a.75.75 0 01-.279-1.446l1.494-.595a.75.75 0 01.556 1.392l-1.493.595a.743.743 0 01-.277.053H6.91zM6.91 5.033a.75.75 0 01-.279-1.446l1.494-.595a.75.75 0 01.556 1.392l-1.493.596a.744.744 0 01-.277.053H6.91zM8.363 21.364a.743.743 0 01-.277-.053l-1.494-.596a.75.75 0 01.555-1.392l1.494.595a.75.75 0 01-.278 1.446zM15.63 8.223a.75.75 0 01-.278-1.447l1.494-.595a.75.75 0 01.556 1.393l-1.494.595a.744.744 0 01-.276.054h-.002zM15.63 14.567a.75.75 0 01-.278-1.446l1.494-.596a.75.75 0 01.556 1.394l-1.494.595a.743.743 0 01-.276.053h-.002zM15.63 21.363a.749.749 0 01-.278-1.445l1.494-.595a.75.75 0 11.555 1.392l-1.494.595a.741.741 0 01-.277.053z`,fill:`#5699DB`})]}))});function LE(e){"@babel/helpers - typeof";return LE=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},LE(e)}function RE(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function zE(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function nD(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var rD=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=tD(e,YE);return(0,P.jsxs)(`svg`,ZE(ZE({fill:`currentColor`,fillRule:`evenodd`,height:n,style:ZE({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:`Exa`}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M3 0h19v1.791L13.892 12 22 22.209V24H3V0zm9.62 10.348l6.589-8.557H6.03l6.59 8.557zM5.138 3.935v7.17h5.52l-5.52-7.17zm5.52 8.96h-5.52v7.17l5.52-7.17zM6.03 22.21l6.59-8.557 6.589 8.557H6.03z`})]}))});function iD(e){"@babel/helpers - typeof";return iD=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},iD(e)}function aD(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function oD(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function CD(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var wD=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=SD(e,gD);return(0,P.jsxs)(`svg`,vD(vD({fill:`currentColor`,fillRule:`evenodd`,height:n,style:vD({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:`Fal`}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M15.477 0c.415 0 .749.338.788.752a7.775 7.775 0 006.985 6.984c.413.04.752.373.752.788v6.952c0 .415-.338.748-.752.788a7.775 7.775 0 00-6.985 6.984c-.04.414-.373.752-.788.752H8.525c-.416 0-.749-.338-.789-.752a7.775 7.775 0 00-6.984-6.984c-.414-.04-.752-.373-.752-.788V8.524c0-.415.338-.748.752-.788A7.775 7.775 0 007.736.752C7.776.338 8.11 0 8.526 0h6.95zM4.819 11.98a7.226 7.226 0 007.223 7.23 7.226 7.226 0 007.223-7.23c0-3.994-3.234-7.23-7.223-7.23a7.227 7.227 0 00-7.223 7.23z`})]}))});function TD(e){"@babel/helpers - typeof";return TD=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},TD(e)}function ED(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function DD(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function GD(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var KD=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=WD(e,RD);return(0,P.jsxs)(`svg`,BD(BD({fill:`currentColor`,fillRule:`evenodd`,height:n,style:BD({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:ND}),(0,P.jsx)(`path`,{d:`M22.724 3.088C21.527 2.376 19.91 2 18.044 2c-2.854 0-6 .877-8.826 2.403l-.02-.007-.004.021c-.855.464-1.684.981-2.462 1.558C2.147 9.376.863 13.412 1.947 15.57.76 17.542.03 19.583 0 22c2.28-4.233 3.648-7.663 11.076-13.438-2.122.443-5.79 2.545-8.258 5.735-.233-1.866 1.28-4.879 4.65-7.379.428-.316.871-.612 1.324-.893-.354 1.071-.24.805-.975 2.307 1.086-1.001 1.8-1.62 2.873-3.335a18.995 18.995 0 014.276-1.465c-.238.767-.69 2.067-1.302 3.095 0 0 1.553-.324 2.837-.25-.701.753-1.333 1.569-1.973 2.403-.876 1.142-1.782 2.322-2.943 3.421-.14.133-.273.253-.408.377-1.784-.167-2.961.483-4.065 1.63.87-.395 2.04-.72 2.772-.524-1.35 1.073-3.477 2.487-5.224 2.37-.332.492-.353.507-.717 1.1 2.835.688 6.395-2.118 8.49-4.103 1.229-1.164 2.165-2.383 3.07-3.56 1.862-2.427 3.471-4.523 7.04-5.32L24 3.846l-1.276-.758z`})]}))});function qD(e){"@babel/helpers - typeof";return qD=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},qD(e)}function JD(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function YD(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function mO(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var hO=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=pO(e,sO);return(0,P.jsxs)(`svg`,lO(lO({fill:`currentColor`,fillRule:`evenodd`,height:n,style:lO({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:tO}),(0,P.jsx)(`path`,{d:`M3.675 7.386A3.684 3.684 0 007.35 3.693 3.684 3.684 0 003.675 0 3.684 3.684 0 000 3.693a3.684 3.684 0 003.675 3.693zm0 16.614a3.683 3.683 0 003.675-3.693 3.684 3.684 0 00-3.675-3.693A3.683 3.683 0 000 20.307 3.684 3.684 0 003.675 24z`}),(0,P.jsx)(`path`,{d:`M10.338 7.2a8.002 8.002 0 011.146-.114h2.037a2.14 2.14 0 002.136-2.139V2.44c0-1.179-.96-2.139-2.136-2.139h-2.484a2.14 2.14 0 00-2.136 2.14l-.08 1.487a8.001 8.001 0 01-.12.9 5.2 5.2 0 01-.487 1.38s-.327.627-.753 1.068a5 5 0 01-.327.306l-.219.18a4.4 4.4 0 01-1.779.786c-.285.06-.939.066-1.206.072H2.433c-1.179 0-2.136.96-2.136 2.148v2.5c0 1.187.96 2.147 2.136 2.147h2.544a2.15 2.15 0 002.136-2.148v-1.794c-.02-.62.021-1.773.567-2.547.34-.48.88-.906.972-.98a3.58 3.58 0 01.798-.487c.087-.039.36-.147.885-.246V7.2h.003z`}),(0,P.jsx)(`path`,{d:`M21.897.3H19.28c-1.146 0-2.07.927-2.07 2.073V4.14s0 1.227-.3 2.14c-.321.905-1.131 1.727-1.944 2.027-.951.348-2.064.3-2.631.3h-1.59a2.07 2.07 0 00-2.064 2.073v2.634c0 1.146.924 2.073 2.064 2.073h2.622a2.07 2.07 0 002.064-2.073l.02-1.1c-.011-.409.028-1.249.226-1.86.072-.229.219-.649.552-1.108.24-.327.474-.534.71-.753.433-.387.799-.612.9-.666.22-.132.6-.36 1.138-.528.48-.147.84-.174 1.452-.213.36-.027.858-.039 1.458-.006 1.146 0 2.07-.927 2.07-2.073V2.373A2.07 2.07 0 0021.888.3h.009z`})]}))});function gO(e){"@babel/helpers - typeof";return gO=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},gO(e)}function _O(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function vO(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function IO(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var LO=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=FO(e,kO);return(0,P.jsxs)(`svg`,jO(jO({fill:`currentColor`,fillRule:`evenodd`,height:n,style:jO({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:wO}),(0,P.jsx)(`path`,{d:`M12 0c6.63 0 12 5.276 12 11.79-.001 5.067-3.29 9.567-8.175 11.187-.6.118-.825-.25-.825-.56 0-.398.015-1.665.015-3.242 0-1.105-.375-1.813-.81-2.181 2.67-.295 5.475-1.297 5.475-5.822 0-1.297-.465-2.344-1.23-3.169.12-.295.54-1.503-.12-3.125 0 0-1.005-.324-3.3 1.209a11.32 11.32 0 00-3-.398c-1.02 0-2.04.133-3 .398-2.295-1.518-3.3-1.209-3.3-1.209-.66 1.622-.24 2.83-.12 3.125-.765.825-1.23 1.887-1.23 3.169 0 4.51 2.79 5.527 5.46 5.822-.345.294-.66.81-.765 1.577-.69.31-2.415.81-3.495-.973-.225-.354-.9-1.223-1.845-1.209-1.005.015-.405.56.015.781.51.28 1.095 1.327 1.23 1.666.24.663 1.02 1.93 4.035 1.385 0 .988.015 1.916.015 2.196 0 .31-.225.664-.825.56C3.303 21.374-.003 16.867 0 11.791 0 5.276 5.37 0 12 0z`})]}))});function RO(e){"@babel/helpers - typeof";return RO=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},RO(e)}function zO(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function BO(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function ik(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var ak=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=rk(e,ZO);return(0,P.jsxs)(`svg`,$O($O({fill:`currentColor`,fillRule:`evenodd`,height:n,style:$O({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:KO}),(0,P.jsx)(`path`,{d:`M12.036 2c-3.853-.035-7 3-7.036 6.781-.035 3.782 3.055 6.872 6.908 6.907h2.42v-2.566h-2.292c-2.407.028-4.38-1.866-4.408-4.23-.029-2.362 1.901-4.298 4.308-4.326h.1c2.407 0 4.358 1.915 4.365 4.278v6.305c0 2.342-1.944 4.25-4.323 4.279a4.375 4.375 0 01-3.033-1.252l-1.851 1.818A7 7 0 0012.029 22h.092c3.803-.056 6.858-3.083 6.879-6.816v-6.5C18.907 4.963 15.817 2 12.036 2z`})]}))});function ok(e){"@babel/helpers - typeof";return ok=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},ok(e)}function sk(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function ck(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Ek(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Dk=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Tk(e,yk);return(0,P.jsxs)(`svg`,xk(xk({height:n,style:xk({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:mk}),(0,P.jsx)(`path`,{d:`M2.25 11.535c0-3.407 1.847-6.554 4.844-8.258a9.822 9.822 0 019.687 0c2.997 1.704 4.844 4.851 4.844 8.258 0 5.266-4.337 9.535-9.687 9.535S2.25 16.8 2.25 11.535z`,fill:`#FF9D0B`}),(0,P.jsx)(`path`,{d:`M11.938 20.086c4.797 0 8.687-3.829 8.687-8.551 0-4.722-3.89-8.55-8.687-8.55-4.798 0-8.688 3.828-8.688 8.55 0 4.722 3.89 8.55 8.688 8.55z`,fill:`#FFD21E`}),(0,P.jsx)(`path`,{d:`M11.875 15.113c2.457 0 3.25-2.156 3.25-3.263 0-.576-.393-.394-1.023-.089-.582.283-1.365.675-2.224.675-1.798 0-3.25-1.693-3.25-.586 0 1.107.79 3.263 3.25 3.263h-.003z`,fill:`#FF323D`}),(0,P.jsx)(`path`,{d:`M14.76 9.21c.32.108.445.753.767.585.447-.233.707-.708.659-1.204a1.235 1.235 0 00-.879-1.059 1.262 1.262 0 00-1.33.394c-.322.384-.377.92-.14 1.36.153.283.638-.177.925-.079l-.002.003zm-5.887 0c-.32.108-.448.753-.768.585a1.226 1.226 0 01-.658-1.204c.048-.495.395-.913.878-1.059a1.262 1.262 0 011.33.394c.322.384.377.92.14 1.36-.152.283-.64-.177-.925-.079l.003.003zm1.12 5.34a2.166 2.166 0 011.325-1.106c.07-.02.144.06.219.171l.192.306c.069.1.139.175.209.175.074 0 .15-.074.223-.172l.205-.302c.08-.11.157-.188.234-.165.537.168.986.536 1.25 1.026.932-.724 1.275-1.905 1.275-2.633 0-.508-.306-.426-.81-.19l-.616.296c-.52.24-1.148.48-1.824.48-.676 0-1.302-.24-1.823-.48l-.589-.283c-.52-.248-.838-.342-.838.177 0 .703.32 1.831 1.187 2.56l.18.14z`,fill:`#3A3B45`}),(0,P.jsx)(`path`,{d:`M17.812 10.366a.806.806 0 00.813-.8c0-.441-.364-.8-.813-.8a.806.806 0 00-.812.8c0 .442.364.8.812.8zm-11.624 0a.806.806 0 00.812-.8c0-.441-.364-.8-.812-.8a.806.806 0 00-.813.8c0 .442.364.8.813.8zM4.515 13.073c-.405 0-.765.162-1.017.46a1.455 1.455 0 00-.333.925 1.801 1.801 0 00-.485-.074c-.387 0-.737.146-.985.409a1.41 1.41 0 00-.2 1.722 1.302 1.302 0 00-.447.694c-.06.222-.12.69.2 1.166a1.267 1.267 0 00-.093 1.236c.238.533.81.958 1.89 1.405l.24.096c.768.3 1.473.492 1.478.494.89.243 1.808.375 2.732.394 1.465 0 2.513-.443 3.115-1.314.93-1.342.842-2.575-.274-3.763l-.151-.154c-.692-.684-1.155-1.69-1.25-1.912-.195-.655-.71-1.383-1.562-1.383-.46.007-.889.233-1.15.605-.25-.31-.495-.553-.715-.694a1.87 1.87 0 00-.993-.312zm14.97 0c.405 0 .767.162 1.017.46.216.262.333.588.333.925.158-.047.322-.071.487-.074.388 0 .738.146.985.409a1.41 1.41 0 01.2 1.722c.22.178.377.422.445.694.06.222.12.69-.2 1.166.244.37.279.836.093 1.236-.238.533-.81.958-1.889 1.405l-.239.096c-.77.3-1.475.492-1.48.494-.89.243-1.808.375-2.732.394-1.465 0-2.513-.443-3.115-1.314-.93-1.342-.842-2.575.274-3.763l.151-.154c.695-.684 1.157-1.69 1.252-1.912.195-.655.708-1.383 1.56-1.383.46.007.889.233 1.15.605.25-.31.495-.553.718-.694.244-.162.523-.265.814-.3l.176-.012z`,fill:`#FF9D0B`}),(0,P.jsx)(`path`,{d:`M9.785 20.132c.688-.994.638-1.74-.305-2.667-.945-.928-1.495-2.288-1.495-2.288s-.205-.788-.672-.714c-.468.074-.81 1.25.17 1.971.977.721-.195 1.21-.573.534-.375-.677-1.405-2.416-1.94-2.751-.532-.332-.907-.148-.782.541.125.687 2.357 2.35 2.14 2.707-.218.362-.983-.42-.983-.42S2.953 14.9 2.43 15.46c-.52.558.398 1.026 1.7 1.803 1.308.778 1.41.985 1.225 1.28-.187.295-3.07-2.1-3.34-1.083-.27 1.011 2.943 1.304 2.745 2.006-.2.7-2.265-1.324-2.685-.537-.425.79 2.913 1.718 2.94 1.725 1.075.276 3.813.859 4.77-.522zm4.432 0c-.687-.994-.64-1.74.305-2.667.943-.928 1.493-2.288 1.493-2.288s.205-.788.675-.714c.465.074.807 1.25-.17 1.971-.98.721.195 1.21.57.534.377-.677 1.407-2.416 1.94-2.751.532-.332.91-.148.782.541-.125.687-2.355 2.35-2.137 2.707.215.362.98-.42.98-.42S21.05 14.9 21.57 15.46c.52.558-.395 1.026-1.7 1.803-1.308.778-1.408.985-1.225 1.28.187.295 3.07-2.1 3.34-1.083.27 1.011-2.94 1.304-2.743 2.006.2.7 2.263-1.324 2.685-.537.423.79-2.912 1.718-2.94 1.725-1.077.276-3.815.859-4.77-.522z`,fill:`#FFD21E`})]}))});function Ok(e){"@babel/helpers - typeof";return Ok=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Ok(e)}function kk(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Ak(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Jk(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Yk=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=qk(e,Vk);return(0,P.jsxs)(`svg`,Uk(Uk({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Uk({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Ik}),(0,P.jsx)(`path`,{d:`M.193 19.503a2.413 2.413 0 00-.186.925c0 1.317 1.112 2.518 2.95 3.437a1.337 1.337 0 001.838-.738l2.049-4.93c.359-.857.642-1.745.846-2.652-3.795.637-6.656 2.092-7.448 3.872l-.032.076-.017.01zm7.49-11.047a15.981 15.981 0 00-.846-2.653L4.79.873a1.34 1.34 0 00-1.84-.738C1.112 1.054 0 2.256 0 3.573c0 .317.064.631.186.924v.01l.032.077c.81 1.78 3.67 3.234 7.466 3.872zM21.049.136c1.838.918 2.95 2.12 2.95 3.436a2.454 2.454 0 01-.196.925l-.027.063c-.785 1.792-3.653 3.254-7.46 3.896.204-.907.487-1.795.846-2.653L19.21.873a1.337 1.337 0 011.839-.738zm-4.722 15.409c.201.906.48 1.793.837 2.65l2.048 4.932a1.338 1.338 0 001.838.738c1.839-.92 2.951-2.12 2.951-3.437a2.446 2.446 0 00-.186-.925l-.027-.062c-.782-1.792-3.66-3.256-7.46-3.896zm-.129-6.04c2.695-.415 4.935-1.223 6.48-2.278L22.24 8.28a9.755 9.755 0 000 7.437l.435 1.048c-1.547-1.055-3.787-1.855-6.479-2.275l-.07-.01A27.196 27.196 0 0012 14.172c-1.377-.002-2.752.1-4.114.307l-.071.01c-2.693.413-4.933 1.222-6.48 2.277l.437-1.05a9.755 9.755 0 000-7.437l-.437-1.052c1.54 1.06 3.78 1.863 6.473 2.278l.071.01c2.734.407 5.513.407 8.246 0l.071-.01z`})]}))});function Xk(e){"@babel/helpers - typeof";return Xk=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Xk(e)}function Zk(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Qk(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function gA(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var _A=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=hA(e,lA);return(0,P.jsxs)(`svg`,dA(dA({fill:`currentColor`,fillRule:`evenodd`,height:n,style:dA({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:iA}),(0,P.jsx)(`path`,{d:`M20.713 6.655c-.414-1.426-1.748-2.472-3.357-2.472a3.62 3.62 0 00-1.7.423C14.62 3.046 12.804 2 10.735 2 7.77 2 5.31 4.16 4.943 6.944 2.138 7.39 0 9.728 0 12.58c0 3.14 2.62 5.68 5.862 5.68 1.61 0 3.08-.646 4.138-1.671.276-.267.529-.557.736-.89a5.02 5.02 0 01-.713.845 8.998 8.998 0 00-1.77 5.39V22a16.682 16.682 0 018.666-2.717h.046c3.035 0 5.633-1.871 6.621-4.499A6.599 6.599 0 0024 12.445c0-2.427-1.31-4.565-3.287-5.79zM6.966 12.869a.836.836 0 01-.851.824.81.81 0 01-.805-.824v-2.183a.81.81 0 01.805-.824c.46 0 .85.379.85.824v2.183zm3.011 1.069a.86.86 0 01-.874.846.86.86 0 01-.873-.846v-4.9a.86.86 0 01.873-.846.86.86 0 01.874.846v4.9zm3.104-1.047c0 .445-.414.824-.874.824s-.85-.379-.85-.824v-2.227c0-.446.367-.824.85-.824.46 0 .873.378.873.824v2.227zm3.149 1.069a.86.86 0 01-.874.846.86.86 0 01-.873-.846v-4.9a.86.86 0 01.873-.846.86.86 0 01.874.846v4.9zm3.08-1.091a.836.836 0 01-.85.824.81.81 0 01-.805-.824v-2.183a.81.81 0 01.805-.824c.46 0 .85.379.85.824v2.183z`})]}))});function vA(e){"@babel/helpers - typeof";return vA=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},vA(e)}function yA(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function bA(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function RA(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var zA=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=LA(e,jA);return(0,P.jsxs)(`svg`,NA(NA({fill:`currentColor`,fillRule:`evenodd`,height:n,style:NA({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:EA}),(0,P.jsx)(`path`,{d:`M2 2h20v20H2V2zm1.768 18.237h16.459V3.761H3.768v16.476zm3.515-14.91l3.479 6.176-3.871 7.154h2.493l2.58-4.883 2.747 4.883h2.54L9.82 5.324l-2.538.002z`})]}))});function BA(e){"@babel/helpers - typeof";return BA=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},BA(e)}function VA(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function HA(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function oj(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var sj=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=aj(e,$A);return(0,P.jsxs)(`svg`,tj(tj({fill:`currentColor`,fillRule:`evenodd`,height:n,style:tj({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:JA}),(0,P.jsx)(`path`,{d:`M2.84 2a1.273 1.273 0 100 2.547h14.107a1.273 1.273 0 100-2.547H2.84zM7.935 5.33a1.273 1.273 0 000 2.548H22.04a1.274 1.274 0 000-2.547H7.935zM3.624 9.935c0-.704.57-1.274 1.274-1.274h14.106a1.274 1.274 0 010 2.547H4.898c-.703 0-1.274-.57-1.274-1.273zM1.273 12.188a1.273 1.273 0 100 2.547H15.38a1.274 1.274 0 000-2.547H1.273zM3.624 16.792c0-.704.57-1.274 1.274-1.274h14.106a1.273 1.273 0 110 2.547H4.898c-.703 0-1.274-.57-1.274-1.273zM13.029 18.849a1.273 1.273 0 100 2.547h9.698a1.273 1.273 0 100-2.547h-9.698z`,fillOpacity:`.3`}),(0,P.jsx)(`path`,{d:`M2.84 2a1.273 1.273 0 100 2.547h10.287a1.274 1.274 0 000-2.547H2.84zM7.935 5.33a1.273 1.273 0 000 2.548H18.22a1.274 1.274 0 000-2.547H7.935zM3.624 9.935c0-.704.57-1.274 1.274-1.274h10.286a1.273 1.273 0 010 2.547H4.898c-.703 0-1.274-.57-1.274-1.273zM1.273 12.188a1.273 1.273 0 100 2.547H11.56a1.274 1.274 0 000-2.547H1.273zM3.624 16.792c0-.704.57-1.274 1.274-1.274h10.286a1.273 1.273 0 110 2.547H4.898c-.703 0-1.274-.57-1.274-1.273zM13.029 18.849a1.273 1.273 0 100 2.547h5.78a1.273 1.273 0 100-2.547h-5.78z`})]}))});function cj(e){"@babel/helpers - typeof";return cj=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},cj(e)}function lj(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function uj(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Oj(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var kj=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Dj(e,xj);return(0,P.jsxs)(`svg`,Cj(Cj({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Cj({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:gj}),(0,P.jsx)(`path`,{d:`M10.033 5.807L0 18.193h10.792l4.636-5.724-5.395-6.662zm.651 5.421l-.651 2.553-.652-2.553-2.538.926 3.19-3.938 3.19 3.938-2.539-.926zM18.107 10.918l-5.893 7.275H24l-5.893-7.275zm0 4.683l-.383-1.499-1.49.544 1.873-2.313 1.873 2.313-1.49-.544-.383 1.5z`})]}))});function Aj(e){"@babel/helpers - typeof";return Aj=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Aj(e)}function jj(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Mj(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Xj(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var Zj=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=Yj(e,Uj);return(0,P.jsxs)(`svg`,Gj(Gj({fill:`currentColor`,fillRule:`evenodd`,height:n,style:Gj({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:Rj}),(0,P.jsx)(`path`,{d:`M20 2.306v16.797s4-.242 4-4.815V2.306h-4zM4 22.001V5.204s-4 .242-4 4.816V22h4z`}),(0,P.jsx)(`path`,{d:`M16.318 16.51L11.286 4.94c-.824-1.872-2.168-2.926-4.077-2.926-1.908 0-3.211 1.54-3.211 3.19 0 0 2.405-.333 3.68 2.593l5.036 11.57c.821 1.87 2.168 2.926 4.075 2.926 1.905 0 3.211-1.541 3.211-3.19 0 0-2.406.333-3.682-2.594z`})]}))});function Qj(e){"@babel/helpers - typeof";return Qj=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Qj(e)}function $j(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function eM(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function vM(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var yM=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=_M(e,dM);return(0,P.jsxs)(`svg`,pM(pM({fill:`currentColor`,fillRule:`evenodd`,height:n,style:pM({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:oM}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M9.167 4.17v5.665L0 19.003h9.167v-5.666l5.666 5.666H24L9.167 4.17z`})]}))});function bM(e){"@babel/helpers - typeof";return bM=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},bM(e)}function xM(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function SM(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function BM(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var VM=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=zM(e,NM);return(0,P.jsxs)(`svg`,FM(FM({fill:`currentColor`,fillRule:`evenodd`,height:n,style:FM({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:OM}),(0,P.jsx)(`path`,{d:`M7.905 1.09c.216.085.411.225.588.41.295.306.544.744.734 1.263.191.522.315 1.1.362 1.68a5.054 5.054 0 012.049-.636l.051-.004c.87-.07 1.73.087 2.48.474.101.053.2.11.297.17.05-.569.172-1.134.36-1.644.19-.52.439-.957.733-1.264a1.67 1.67 0 01.589-.41c.257-.1.53-.118.796-.042.401.114.745.368 1.016.737.248.337.434.769.561 1.287.23.934.27 2.163.115 3.645l.053.04.026.019c.757.576 1.284 1.397 1.563 2.35.435 1.487.216 3.155-.534 4.088l-.018.021.002.003c.417.762.67 1.567.724 2.4l.002.03c.064 1.065-.2 2.137-.814 3.19l-.007.01.01.024c.472 1.157.62 2.322.438 3.486l-.006.039a.651.651 0 01-.747.536.648.648 0 01-.54-.742c.167-1.033.01-2.069-.48-3.123a.643.643 0 01.04-.617l.004-.006c.604-.924.854-1.83.8-2.72-.046-.779-.325-1.544-.8-2.273a.644.644 0 01.18-.886l.009-.006c.243-.159.467-.565.58-1.12a4.229 4.229 0 00-.095-1.974c-.205-.7-.58-1.284-1.105-1.683-.595-.454-1.383-.673-2.38-.61a.653.653 0 01-.632-.371c-.314-.665-.772-1.141-1.343-1.436a3.288 3.288 0 00-1.772-.332c-1.245.099-2.343.801-2.67 1.686a.652.652 0 01-.61.425c-1.067.002-1.893.252-2.497.703-.522.39-.878.935-1.066 1.588a4.07 4.07 0 00-.068 1.886c.112.558.331 1.02.582 1.269l.008.007c.212.207.257.53.109.785-.36.622-.629 1.549-.673 2.44-.05 1.018.186 1.902.719 2.536l.016.019a.643.643 0 01.095.69c-.576 1.236-.753 2.252-.562 3.052a.652.652 0 01-1.269.298c-.243-1.018-.078-2.184.473-3.498l.014-.035-.008-.012a4.339 4.339 0 01-.598-1.309l-.005-.019a5.764 5.764 0 01-.177-1.785c.044-.91.278-1.842.622-2.59l.012-.026-.002-.002c-.293-.418-.51-.953-.63-1.545l-.005-.024a5.352 5.352 0 01.093-2.49c.262-.915.777-1.701 1.536-2.269.06-.045.123-.09.186-.132-.159-1.493-.119-2.73.112-3.67.127-.518.314-.95.562-1.287.27-.368.614-.622 1.015-.737.266-.076.54-.059.797.042zm4.116 9.09c.936 0 1.8.313 2.446.855.63.527 1.005 1.235 1.005 1.94 0 .888-.406 1.58-1.133 2.022-.62.375-1.451.557-2.403.557-1.009 0-1.871-.259-2.493-.734-.617-.47-.963-1.13-.963-1.845 0-.707.398-1.417 1.056-1.946.668-.537 1.55-.849 2.485-.849zm0 .896a3.07 3.07 0 00-1.916.65c-.461.37-.722.835-.722 1.25 0 .428.21.829.61 1.134.455.347 1.124.548 1.943.548.799 0 1.473-.147 1.932-.426.463-.28.7-.686.7-1.257 0-.423-.246-.89-.683-1.256-.484-.405-1.14-.643-1.864-.643zm.662 1.21l.004.004c.12.151.095.37-.056.49l-.292.23v.446a.375.375 0 01-.376.373.375.375 0 01-.376-.373v-.46l-.271-.218a.347.347 0 01-.052-.49.353.353 0 01.494-.051l.215.172.22-.174a.353.353 0 01.49.051zm-5.04-1.919c.478 0 .867.39.867.871a.87.87 0 01-.868.871.87.87 0 01-.867-.87.87.87 0 01.867-.872zm8.706 0c.48 0 .868.39.868.871a.87.87 0 01-.868.871.87.87 0 01-.867-.87.87.87 0 01.867-.872zM7.44 2.3l-.003.002a.659.659 0 00-.285.238l-.005.006c-.138.189-.258.467-.348.832-.17.692-.216 1.631-.124 2.782.43-.128.899-.208 1.404-.237l.01-.001.019-.034c.046-.082.095-.161.148-.239.123-.771.022-1.692-.253-2.444-.134-.364-.297-.65-.453-.813a.628.628 0 00-.107-.09L7.44 2.3zm9.174.04l-.002.001a.628.628 0 00-.107.09c-.156.163-.32.45-.453.814-.29.794-.387 1.776-.23 2.572l.058.097.008.014h.03a5.184 5.184 0 011.466.212c.086-1.124.038-2.043-.128-2.722-.09-.365-.21-.643-.349-.832l-.004-.006a.659.659 0 00-.285-.239h-.004z`})]}))});function HM(e){"@babel/helpers - typeof";return HM=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},HM(e)}function UM(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function WM(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function cN(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var lN=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=sN(e,tN);return(0,P.jsxs)(`svg`,rN(rN({fill:`currentColor`,fillRule:`evenodd`,height:n,style:rN({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:XM}),(0,P.jsx)(`path`,{d:`M22 10.552v2.26h-7.932V22H11.54V10.552H22zM22 2v2.264H4.528V22H2V2h20zm0 4.276V8.54H9.296V22H6.768V6.276H22z`})]}))});function uN(e){"@babel/helpers - typeof";return uN=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},uN(e)}function dN(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function fN(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function AN(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var jN=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=kN(e,CN);return(0,P.jsxs)(`svg`,TN(TN({fill:`currentColor`,fillRule:`evenodd`,height:n,style:TN({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:vN}),(0,P.jsx)(`path`,{d:`M23 23h-1.223V8.028c0-3.118-2.568-5.806-5.744-5.806H8.027c-3.176 0-5.744 2.565-5.744 5.686 0 3.119 2.568 5.684 5.744 5.684h.794c1.346 0 2.445 1.1 2.445 2.444 0 1.346-1.1 2.446-2.445 2.446H1v-1.223h7.761c.671 0 1.223-.551 1.223-1.16 0-.67-.552-1.16-1.223-1.16h-.794C4.177 14.872 1 11.756 1 7.909 1 4.058 4.176 1 8.027 1h8.066C19.88 1 23 4.239 23 8.028V23z`}),(0,P.jsx)(`path`,{d:`M8.884 12.672c1.71.06 3.361 1.588 3.361 3.422 0 1.833-1.528 3.421-3.421 3.421H1v1.223h7.761c2.568 0 4.705-2.077 4.705-4.644 0-.672-.123-1.283-.43-1.894-.245-.551-.67-1.1-1.099-1.528-.489-.429-1.039-.734-1.65-.977-.525-.175-1.048-.193-1.594-.212-.218-.008-.441-.016-.669-.034-.428 0-1.406-.245-1.956-.61a3.369 3.369 0 01-1.223-1.406c-.183-.489-.305-.977-.305-1.528A3.417 3.417 0 017.96 4.482h8.066c1.895 0 3.422 1.65 3.422 3.483v15.032h1.223V8.027c0-2.568-2.077-4.768-4.645-4.768h-8c-2.568 0-4.705 2.077-4.705 4.646 0 .67.123 1.282.43 1.894a4.45 4.45 0 001.099 1.528c.429.428 1.039.734 1.588.976.306.123.611.183.976.246.857.06 1.406.123 1.466.123h.003z`}),(0,P.jsx)(`path`,{d:`M1 23h7.761v-.003c3.85 0 7.03-3.116 7.09-7.026 0-3.79-3.117-6.906-6.967-6.906H8.09c-.672 0-1.222-.552-1.222-1.16 0-.608.487-1.16 1.159-1.16h8.069c.608 0 1.159.611 1.159 1.283v14.97h1.223V8.024c0-1.345-1.1-2.505-2.445-2.505H7.967a2.451 2.451 0 00-2.445 2.445 2.45 2.45 0 002.445 2.445h.794c3.176 0 5.744 2.568 5.744 5.684s-2.568 5.684-5.744 5.684H1V23z`})]}))});function MN(e){"@babel/helpers - typeof";return MN=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},MN(e)}function NN(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function PN(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function QN(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var $N=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=ZN(e,GN);return(0,P.jsxs)(`svg`,qN(qN({fill:`currentColor`,fillRule:`evenodd`,height:n,style:qN({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:BN}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M22.956 6.521H12.522c-.577 0-1.044.468-1.044 1.044v3.13c0 .577-.466 1.044-1.043 1.044H1.044c-.577 0-1.044.467-1.044 1.044v4.174C0 17.533.467 18 1.044 18h10.434c.577 0 1.044-.467 1.044-1.043v-3.13c0-.578.466-1.044 1.043-1.044h9.391c.577 0 1.044-.467 1.044-1.044V7.565c0-.576-.467-1.044-1.044-1.044z`})]}))});function eP(e){"@babel/helpers - typeof";return eP=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},eP(e)}function tP(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function nP(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function bP(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var xP=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=yP(e,pP);return(0,P.jsxs)(`svg`,hP(hP({fill:`currentColor`,fillRule:`evenodd`,height:n,style:hP({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:cP}),(0,P.jsx)(`path`,{clipRule:`evenodd`,d:`M23.252 10.365l-2.843 1.636 2.843 1.631a1.47 1.47 0 01.697.903 1.492 1.492 0 01-.15 1.135c-.202.342-.53.591-.912.693a1.498 1.498 0 01-1.132-.15l-5.09-2.924a1.473 1.473 0 01-.68-.851 1.446 1.446 0 01-.068-.485 1.5 1.5 0 01.745-1.248l5.09-2.921a1.496 1.496 0 012.044.547 1.479 1.479 0 01-.544 2.034zm-2.692 7.927l-5.087-2.92a1.477 1.477 0 00-.867-.195 1.478 1.478 0 00-.982.468c-.257.276-.4.639-.403 1.017v5.847A1.49 1.49 0 0014.718 24c.828 0 1.497-.668 1.497-1.491v-3.27l2.849 1.636a1.493 1.493 0 002.044-.544 1.49 1.49 0 00-.548-2.04zm-5.87-5.719l-2.116 2.102a.42.42 0 01-.265.112h-.621a.427.427 0 01-.265-.112l-2.115-2.102a.42.42 0 01-.11-.262v-.62a.43.43 0 01.11-.265l2.114-2.102a.426.426 0 01.264-.11h.623a.422.422 0 01.265.11l2.116 2.102a.43.43 0 01.109.265v.62a.428.428 0 01-.11.262zM13 11.99a.442.442 0 00-.113-.266l-.612-.607a.431.431 0 00-.266-.11h-.024a.426.426 0 00-.264.11l-.612.607a.436.436 0 00-.107.266v.024c0 .085.047.202.107.262l.612.61c.061.06.179.11.264.11h.024a.434.434 0 00.266-.11l.612-.61a.429.429 0 00.112-.262v-.024zM3.436 5.704l5.089 2.924c.274.157.578.219.868.195.375-.026.726-.194.983-.47.256-.275.4-.64.403-1.017V1.489C10.78.667 10.11 0 9.284 0c-.829 0-1.498.667-1.498 1.49v3.27l-2.85-1.639a1.496 1.496 0 00-2.045.546 1.489 1.489 0 00.546 2.037zm11.17 3.119c.29.024.594-.038.866-.195l5.087-2.923a1.474 1.474 0 00.697-.903 1.496 1.496 0 00-.149-1.135 1.496 1.496 0 00-2.044-.545L16.215 4.76V1.489C16.215.667 15.546 0 14.718 0c-.83 0-1.497.667-1.497 1.49v5.845a1.491 1.491 0 001.385 1.487zm-5.213 6.354a1.479 1.479 0 00-.868.194l-5.089 2.92a1.476 1.476 0 00-.696.905 1.498 1.498 0 00.148 1.135 1.496 1.496 0 002.044.543l2.851-1.636v3.27c0 .825.67 1.491 1.498 1.491.826 0 1.496-.667 1.496-1.49v-5.847a1.5 1.5 0 00-.401-1.017 1.477 1.477 0 00-.982-.468zm-1.38-2.74c.05-.156.072-.32.068-.484a1.497 1.497 0 00-.751-1.248l-5.084-2.92a1.499 1.499 0 00-2.045.547 1.481 1.481 0 00.549 2.034l2.841 1.636L.75 13.633a1.47 1.47 0 00-.698.903 1.492 1.492 0 00.15 1.135c.202.343.53.592.912.693.382.102.789.048 1.132-.15l5.086-2.924c.345-.195.577-.505.684-.852z`})]}))});function SP(e){"@babel/helpers - typeof";return SP=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},SP(e)}function CP(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function wP(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function HP(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var UP=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=VP(e,FP);return(0,P.jsxs)(`svg`,LP(LP({height:n,style:LP({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:AP}),(0,P.jsx)(`path`,{d:`M23.197 4.503A6 6 0 0015 2.307a5.973 5.973 0 00-2.995 4.933l5.996.008v.515h-5.996c.039.937.298 1.87.8 2.74a6 6 0 1010.39-6z`,fill:`#EF2CC1`}),(0,P.jsx)(`path`,{d:`M.805 4.5A6 6 0 003 12.697a5.972 5.972 0 005.77.127L5.779 7.627l.446-.257 2.997 5.192A6 6 0 10.804 4.5z`,fill:`#CAAEF5`}),(0,P.jsx)(`path`,{d:`M12 23.894a6 6 0 005.999-6c0-2.13-1.1-3.996-2.775-5.06l-3.005 5.189-.444-.258 2.997-5.192A6 6 0 1012 23.894z`,fill:`#FC4C02`})]}))});function WP(e){"@babel/helpers - typeof";return WP=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},WP(e)}function GP(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function KP(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function uF(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var dF=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=lF(e,rF);return(0,P.jsxs)(`svg`,aF(aF({fill:`currentColor`,fillRule:`evenodd`,height:n,style:aF({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:QP}),(0,P.jsx)(`path`,{d:`M12 0l12 20.785H0L12 0z`})]}))});function fF(e){"@babel/helpers - typeof";return fF=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},fF(e)}function pF(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function mF(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function MF(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var NF=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=jF(e,TF);return(0,P.jsxs)(`svg`,DF(DF({height:n,style:DF({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:bF}),(0,P.jsx)(`path`,{d:`M0 4.973h9.324V23L0 4.973z`,fill:`#FDB515`}),(0,P.jsx)(`path`,{d:`M13.986 4.351L22.378 0l-6.216 23H9.324l4.662-18.649z`,fill:`#30A2FF`})]}))});function PF(e){"@babel/helpers - typeof";return PF=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},PF(e)}function FF(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function IF(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function eI(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var tI=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=$F(e,qF);return(0,P.jsxs)(`svg`,YF(YF({height:n,style:YF({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:HF}),(0,P.jsx)(`path`,{d:`M19.44 10.153l-2.936 11.586a.215.215 0 00.214.261h5.87a.215.215 0 00.214-.261l-2.95-11.586a.214.214 0 00-.412 0zM3.28 12.778l-2.275 8.96A.214.214 0 001.22 22h4.532a.212.212 0 00.214-.165.214.214 0 000-.097l-2.276-8.96a.214.214 0 00-.41 0z`,fill:`#00E5E5`}),(0,P.jsx)(`path`,{d:`M7.29 5.359L3.148 21.738a.215.215 0 00.203.261h8.29a.214.214 0 00.215-.261L7.7 5.358a.214.214 0 00-.41 0z`,fill:`#006EFF`}),(0,P.jsx)(`path`,{d:`M14.44.15a.214.214 0 00-.41 0L8.366 21.739a.214.214 0 00.214.261H19.9a.216.216 0 00.171-.078.214.214 0 00.044-.183L14.439.15z`,fill:`#006EFF`}),(0,P.jsx)(`path`,{d:`M10.278 7.741L6.685 21.736a.214.214 0 00.214.264h7.17a.215.215 0 00.214-.264L10.688 7.741a.214.214 0 00-.41 0z`,fill:`#00E5E5`})]}))});function nI(e){"@babel/helpers - typeof";return nI=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},nI(e)}function rI(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function iI(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function SI(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var CI=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=xI(e,hI);return(0,P.jsxs)(`svg`,_I(_I({fill:`currentColor`,fillRule:`evenodd`,height:n,style:_I({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:uI}),(0,P.jsx)(`path`,{d:`M6.469 8.776L16.512 23h-4.464L2.005 8.776H6.47zm-.004 7.9l2.233 3.164L6.467 23H2l4.465-6.324zM22 2.582V23h-3.659V7.764L22 2.582zM22 1l-9.952 14.095-2.233-3.163L17.533 1H22z`})]}))});function wI(e){"@babel/helpers - typeof";return wI=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},wI(e)}function TI(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function EI(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function WI(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var GI=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=UI(e,LI);return(0,P.jsxs)(`svg`,zI(zI({fill:`currentColor`,fillRule:`evenodd`,height:n,style:zI({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:MI}),(0,P.jsx)(`path`,{d:`M5.223 9.692c.652 1.795 1.925 3.376 3.396 4.573 1.482 1.229 3.254 2.17 5.122 2.653a9.99 9.99 0 002.033.302c1.302.05 2.713-.206 3.758-1.04 1.297-1.036 1.651-2.625 1.318-4.21-.209-.993-.641-1.93-1.205-2.787a10.284 10.284 0 00-.366-.525.008.008 0 01.005-.007h.004c.002 0 .004 0 .006.002l.394.405a17.227 17.227 0 012.484 3.262c.579.993 1.023 2.046 1.255 3.144.369 1.747.07 3.546-1.306 4.777-.724.648-1.655 1.041-2.59 1.235-1.297.267-2.649.228-3.965.007-.669-.112-1.315-.26-1.937-.443-2.576-.756-5.012-2.051-7.143-3.677a20.968 20.968 0 01-3.484-3.296C1.949 12.813 1.046 11.396.487 9.853.12 8.845-.087 7.725.035 6.663c.267-2.306 1.98-3.654 4.174-4.06 1.265-.234 2.594-.186 3.879.037a17.71 17.71 0 013.978 1.192v.004a.006.006 0 01-.004.004h-.004a8.907 8.907 0 00-2.869-.29c-.807.048-1.666.263-2.357.656-1.034.588-1.67 1.463-1.907 2.625a4.567 4.567 0 00-.069 1.1c.025.58.163 1.198.367 1.761z`}),(0,P.jsx)(`path`,{d:`M18.02 7.235a.05.05 0 01-.007.03c-.461.916-.923 1.832-1.386 2.747-.424.837-.745 1.437-.965 1.8a17.877 17.877 0 01-2.98 3.707.027.027 0 01-.03.005 12.678 12.678 0 01-4.205-2.777c-.14-.14-.28-.288-.42-.447a.024.024 0 01-.005-.013c0-.005 0-.01.003-.014a17.718 17.718 0 011.68-2.379 18.27 18.27 0 012.7-2.606c.408-.32 1.39-1.094 2.95-2.323L21.652.002a.008.008 0 01.01 0 .01.01 0 01.004.005.01.01 0 010 .006l-3.648 7.222z`}),(0,P.jsx)(`path`,{d:`M2.027 24c.002 0 .004 0 .005-.002l5.843-4.58a.02.02 0 00.008-.017.02.02 0 00-.01-.016 26.743 26.743 0 01-2.584-1.842h-.006a.014.014 0 00-.005.002.012.012 0 00-.004.005L2.02 23.987a.01.01 0 000 .006c0 .002 0 .004.002.005a.009.009 0 00.006.002z`})]}))});function KI(e){"@babel/helpers - typeof";return KI=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},KI(e)}function qI(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function JI(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function fL(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var pL=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=dL(e,aL);return(0,P.jsxs)(`svg`,sL(sL({fill:`currentColor`,fillRule:`evenodd`,height:n,style:sL({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:eL}),(0,P.jsx)(`path`,{d:`M11.991 23.503a.24.24 0 00-.244.248.24.24 0 00.244.249.24.24 0 00.245-.249.24.24 0 00-.22-.247l-.025-.001zM9.671 5.365a1.697 1.697 0 011.099 2.132l-.071.172-.016.04-.018.054c-.07.16-.104.32-.104.498-.035.71.47 1.279 1.186 1.314h.366c1.309.053 2.338 1.173 2.286 2.523-.052 1.332-1.152 2.38-2.478 2.327h-.174c-.715.018-1.274.64-1.239 1.368 0 .124.018.23.053.337.209.373.54.658.96.8.75.23 1.517-.125 1.9-.782l.018-.035c.402-.64 1.17-.96 1.92-.711.854.284 1.378 1.226 1.099 2.167a1.661 1.661 0 01-2.077 1.102 1.711 1.711 0 01-.907-.711l-.017-.035c-.2-.323-.463-.58-.851-.711l-.056-.018a1.646 1.646 0 00-1.954.746 1.66 1.66 0 01-1.065.764 1.677 1.677 0 01-1.989-1.279c-.209-.906.332-1.83 1.257-2.043a1.51 1.51 0 01.296-.035h.018c.68-.071 1.151-.622 1.116-1.333a1.307 1.307 0 00-.227-.693 2.515 2.515 0 01-.366-1.403 2.39 2.39 0 01.366-1.208c.14-.195.21-.444.227-.693.018-.71-.506-1.261-1.186-1.332l-.07-.018a1.43 1.43 0 01-.299-.07l-.05-.019a1.7 1.7 0 01-1.047-2.114 1.68 1.68 0 012.094-1.101zm-5.575 10.11c.26-.264.639-.367.994-.27.355.096.633.379.728.74.095.362-.007.748-.267 1.013-.402.41-1.053.41-1.455 0a1.062 1.062 0 010-1.482zm14.845-.294c.359-.09.738.024.992.297.254.274.344.665.237 1.025-.107.36-.396.634-.756.718-.551.128-1.1-.22-1.23-.781a1.05 1.05 0 01.757-1.26zm-.064-4.39c.314.32.49.753.49 1.206 0 .452-.176.886-.49 1.206-.315.32-.74.5-1.185.5-.444 0-.87-.18-1.184-.5a1.727 1.727 0 010-2.412 1.654 1.654 0 012.369 0zm-11.243.163c.364.484.447 1.128.218 1.691a1.665 1.665 0 01-2.188.923c-.855-.36-1.26-1.358-.907-2.228a1.68 1.68 0 011.33-1.038c.593-.08 1.183.169 1.547.652zm11.545-4.221c.368 0 .708.2.892.524.184.324.184.724 0 1.048a1.026 1.026 0 01-.892.524c-.568 0-1.03-.47-1.03-1.048 0-.579.462-1.048 1.03-1.048zm-14.358 0c.368 0 .707.2.891.524.184.324.184.724 0 1.048a1.026 1.026 0 01-.891.524c-.569 0-1.03-.47-1.03-1.048 0-.579.461-1.048 1.03-1.048zm10.031-1.475c.925 0 1.675.764 1.675 1.706s-.75 1.705-1.675 1.705-1.674-.763-1.674-1.705c0-.942.75-1.706 1.674-1.706zm-2.626-.684c.362-.082.653-.356.761-.718a1.062 1.062 0 00-.238-1.028 1.017 1.017 0 00-.996-.294c-.547.14-.881.7-.752 1.257.13.558.675.907 1.225.783zm0 16.876c.359-.087.644-.36.75-.72a1.062 1.062 0 00-.237-1.019 1.018 1.018 0 00-.985-.301 1.037 1.037 0 00-.762.717c-.108.361-.017.754.239 1.028.245.263.606.377.953.305l.043-.01zM17.19 3.5a.631.631 0 00.628-.64c0-.355-.279-.64-.628-.64a.631.631 0 00-.628.64c0 .355.28.64.628.64zm-10.38 0a.631.631 0 00.628-.64c0-.355-.28-.64-.628-.64a.631.631 0 00-.628.64c0 .355.279.64.628.64zm-5.182 7.852a.631.631 0 00-.628.64c0 .354.28.639.628.639a.63.63 0 00.627-.606l.001-.034a.62.62 0 00-.628-.64zm5.182 9.13a.631.631 0 00-.628.64c0 .355.279.64.628.64a.631.631 0 00.628-.64c0-.355-.28-.64-.628-.64zm10.38.018a.631.631 0 00-.628.64c0 .355.28.64.628.64a.631.631 0 00.628-.64c0-.355-.279-.64-.628-.64zm5.182-9.148a.631.631 0 00-.628.64c0 .354.279.639.628.639a.631.631 0 00.628-.64c0-.355-.28-.64-.628-.64zm-.384-4.992a.24.24 0 00.244-.249.24.24 0 00-.244-.249.24.24 0 00-.244.249c0 .142.122.249.244.249zM11.991.497a.24.24 0 00.245-.248A.24.24 0 0011.99 0a.24.24 0 00-.244.249c0 .133.108.236.223.247l.021.001zM2.011 6.36a.24.24 0 00.245-.249.24.24 0 00-.244-.249.24.24 0 00-.244.249.24.24 0 00.244.249zm0 11.263a.24.24 0 00-.243.248.24.24 0 00.244.249.24.24 0 00.244-.249.252.252 0 00-.244-.248zm19.995-.018a.24.24 0 00-.245.248.24.24 0 00.245.25.24.24 0 00.244-.25.252.252 0 00-.244-.248z`})]}))});function mL(e){"@babel/helpers - typeof";return mL=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},mL(e)}function hL(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function gL(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function PL(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var FL=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=NL(e,DL);return(0,P.jsxs)(`svg`,kL(kL({fill:`currentColor`,fillRule:`evenodd`,height:n,style:kL({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:SL}),(0,P.jsx)(`path`,{d:`M5 0h5v24H5V0zM14 0h5v24h-5V0z`})]}))});function IL(e){"@babel/helpers - typeof";return IL=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},IL(e)}function LL(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function RL(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function nR(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var rR=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=tR(e,YL);return(0,P.jsxs)(`svg`,ZL(ZL({fill:`currentColor`,fillRule:`evenodd`,height:n,style:ZL({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:WL}),(0,P.jsx)(`path`,{d:`M11.823 22.145c.127.192.217.402.276.627.099.336.037.713-.377.725-.279.028-.506-.126-.54-.397-.066-.39.105-.75.299-1.083l.011-.018c.025-.037.057-.073.095-.078h.008c.074-.007.159.122.228.224zm1.596-1.247c.75.31.95 1.246-.084 1.256h-.015c-.52.017-.906-.472-.587-.92.15-.212.417-.42.686-.336zm-3.989-.594c.256.033.512.1.762.16l.037.011c.21.058.458.11.55.297.26.585-.47 1.093-.976 1.103h-.028v-.001c-.413.01-.809-.126-.942-.535-.11-.28-.085-.603.128-.827l.014-.015c.117-.124.282-.212.455-.193zm6.094.237v.014c-.004.302-.148.608-.42.751-.186.093-.417-.01-.588-.15-.171-.147-.283-.356-.382-.568l-.04-.087c-.193-.405.088-.552.405-.627l.04-.01c.058-.012.116-.022.173-.032l.042-.008c.578-.105.792.15.77.717zM2.827 2.991c1.15.86 2.286 1.734 3.376 2.67 2.476 2.166 3.346 5.568 3.452 8.766.048 1.103.108 2.205.199 3.306l.004.04c.019.182.078.563.25.522l.007-.003c.057-.04.086-.123.109-.192.114-.336.194-.693.323-1.026.09-.216.204-.344.377-.411.3-.12.702-.062.87.246.037.05.073.123.11.202l.036.078.02.044.015.03.013.026.013.026c.1.189.223.344.42.264.117-.059.221-.19.319-.283.2-.22.505-.292.713-.053.524.616.5 1.486 0 2.106-.451.585-1.182.7-1.862.586l-.054-.01c-.972-.162-1.555-.96-2.015-1.779l-.03-.055a141.48 141.48 0 00-1.583-2.577l-.03-.045c-.116-.165-.264-.35-.43-.445-.07-.015-.006.127.009.173.467 1.253 1.07 2.425 1.961 3.427l.011.02c.086.168-.045.122-.19.041l-.016-.01-.027-.015-.019-.011a3.561 3.561 0 01-.126-.082l-.075-.05a4.532 4.532 0 01-.78-.67c-.772-.87-1.42-1.838-2.054-2.816l-.375-.58-.165-.255a32.793 32.793 0 00-1.11-1.623c-.415-.567-.89-1.092-1.298-1.666-.774-1.071-1.251-2.312-1.519-3.61-.137-.56-.297-1.114-.438-1.672l-.06-.24c-.094-.38-.166-.784-.129-1.173.083-.894.815-1.97 1.778-1.222zm18.27 14.434c-1.048 1.176-3.299 2.527-4.609 2.154l.002-.002c.014-.019.125-.043.177-.055.838-.192 1.555-.639 2.285-1.058.556-.295 1.048-.682 1.525-1.1l.04-.032c.106-.089.223-.18.352-.214.226-.053.428.11.228.307zM2.012 11.4c1.647.403 2.589 1.718 3.455 3.08l.108.17.226.358c.25.398.502.79.77 1.155.213.305.442.598.666.894.048.058.115.21.05.216h-.011c-.051 0-.127-.062-.172-.106a41.465 41.465 0 01-.727-.698l-.24-.236a26.247 26.247 0 00-1.103-1.032c-.165-.079-.093.137-.027.238.187.286.463.497.695.747.353.352.711.7 1.094 1.018.805.66 1.68 1.232 2.534 1.832l.056.042c.099.074.211.172.205.28-.005.288-1.397.287-1.663.275H7.92c-1.797-.073-3.27-1.012-4.66-2.055l-.138-.105-.097-.073a68.65 68.65 0 01-.287-.22l-.501-.387c-.286-.224-.573-.443-.848-.681C.464 15.3.032 14.098.01 12.882c-.117-1.32.826-1.809 2.003-1.482zm20.708.695l.064.014c.369.078.673.288.899.618.253.37.387.827.28 1.26-.266.87-.806 1.621-1.446 2.26-.15.147-.309.283-.47.418-.178.135-.345.331-.571.376-.19.034-.402-.135-.597-.129-.158 0-.29.092-.41.193-.806.767-1.78 1.286-2.772 1.765l-.186.089-.186.089-.248.117-.017.007c-.189.076-.786.402-.87.234l-.004-.007c-.034-.07.03-.211.091-.298.968-1.244 1.618-2.666 2.246-4.097l.213-.486.255-.576.123-.26c.19-.4.397-.807.726-1.098.77-.65 1.924-.691 2.88-.49zM21.197 4.78c.161.335.22.69.327 1.054.022.084.04.171.04.257v.012a.977.977 0 01-.027.217l-.073.3a7.983 7.983 0 01-.331 1.104c-.32.797-.632 1.597-.942 2.399l-.373.962c-.688 1.776-1.381 3.551-2.15 5.292-.453.953-1.084 1.814-1.791 2.59-.197.227-.453.404-.777.323-.51-.124-.59-.456-.416-.918.187-.546.42-1.08.567-1.636.286-1.186.382-2.408.2-3.628-.094-.72-.458-.813-.943-1.199a.586.586 0 01-.207-.52c.034-.337.158-.684.264-1.01l.015-.046c.353-1.092 1.013-2.015 1.707-2.912l.08-.103.08-.103.162-.206c.12-.154.241-.308.36-.463.222-.296.529-.5.84-.688.48-.32.792-.816 1.258-1.146.566-.437 1.708-.666 2.13.068zM11.21 18.728c.007.54.965.728.996.052v-.013c.037-.614-.95-.599-.996-.04zm-5.78-.884h-.002c.8.623 1.737 1.182 2.757 1.33 0-.02-.09-.059-.123-.076-.876-.411-1.752-.862-2.632-1.254zM.76 14.145l.009.02c.88 1.917 2.523 3.067 4.354 3.903l.055.026.001-.001-.253-.159-.152-.095-.1-.064-.096-.06c-1.466-.93-2.883-1.934-3.767-3.51l-.049-.088c-.048-.083-.047-.07-.002.028zm14.154-1.55c.293.401.325.933.331 1.411v.013a7.8 7.8 0 01-.267 1.831c-.028.081-.05.171-.074.261l-.011.042-.012.04c-.052.18-.122.351-.274.444-.317.166-.444-.222-.414-.482.028-1 .078-2.002.128-3 .016-.199.013-.403.06-.6.02-.077.055-.143.106-.173.163-.077.33.093.427.212zm1.373-9.036c.609.393.956 1.003 1.052 1.716l.005.034a.666.666 0 01-.184.569c-2.37 2.286-3.416 5.563-3.605 8.805l-.026.204c-.045.34-.094.68-.165 1.014-.09.377-.438.46-.66.124-.112-.17-.122-.388-.138-.587a98.335 98.335 0 01-.098-1.872c-.044-.646.108-.898.588-1.298.259-.263.3-.595.232-.955l-.022-.102c-.077-.352-.167-.715-.4-.996-.362-.432-1.06-.554-1.495-.158-.556.515-.644 1.344-.361 2.021l.01.025c.232.546.445 1.108.533 1.695l.009.061c.064.427.05.867-.01 1.293l-.013.096c-.026.193-.042.423-.163.554-.089.1-.257.129-.389.082-.173-.055-.201-.263-.203-.427-.002-.637-.154-1.246-.32-1.852l-.084-.304a17.057 17.057 0 01-.174-.669l-.22-.949c-.2-.87-.396-1.742-.565-2.62-.504-1.985.138-3.798 1.852-4.934.37-.228.792-.434 1.236-.434.573-.004 1.156.34 1.4.88.082.188.093.394.086.597V5.2c-.031.816-.071 1.633-.11 2.45l-.02.412-.013.274c-.023.458-.042.915-.036 1.374.003.113-.005.24.016.347.01.047.023.07.037.06l.003-.001-.002-.002c.028-.024.05-.149.054-.208.019-.184.038-.369.054-.553.102-1.29.162-2.582.28-3.87.075-.899.953-2.621 2.03-1.925zM1.84 3.237c-.33.095-.365.509-.32.804l.02.167c.045.39.099.78.187 1.16l.054.224c.05.203.093.407.078.617-.03.253-.088.499-.007.746.177.7.449 1.363.78 1.999l.014.02c.107.15.073-.046.033-.135l-.195-.482c-.065-.16-.13-.321-.19-.483l-.082-.217c-.165-.446-.307-.895-.203-1.38.082-.268.08-.537-.037-.796-.228-.605-.363-1.25-.23-1.89.23-.935 1.874.88 2.082 1.23.205.29.379.614.512.948.257.655.505 1.304.743 1.964.849 2.276 1.312 4.665 1.713 7.065l.001.001a1.37 1.37 0 00-.011-.274l-.002-.012c-.07-.573-.135-1.15-.209-1.722-.245-2.102-.837-4.132-1.562-6.113l-.037-.103a6.717 6.717 0 00-1.063-1.937l-.023-.027C3.506 4.15 2.44 3.075 1.84 3.237zm5.688 10.665c.119.666.373.617.964.656h.013c.317.03.503-.115.559-.422l.003-.019c.146-.84-.385-1.915-1.262-2.068-.781-.079-.347 1.417-.277 1.853zm.67-1.185c.214.21.4.555.353.899l-.003.011c-.066.216-.397.167-.52.054a.392.392 0 01-.088-.172c-.07-.245-.134-.507-.127-.764.023-.264.264-.158.385-.028zm4.026-2.251c.4.147.491.734.273 1.078-.328.499-1.009.373-.966-.299v-.015c.006-.369.274-.886.693-.764zm7.163-5.23v.01c-.014.173.01.355.028.529.148 1.259.17 2.518-.015 3.775-.04.28-.093.555-.15.832-.024.175-.108.41-.094.571l.002.002c.01.009.036-.032.063-.101.054-.14.092-.3.126-.448.364-1.723.499-3.504.304-5.255-.075-.246-.25-.12-.264.085zm-.962.684c.271.565.321 1.175.35 1.784l.011.278c.008.172.016.342.03.511h.001c.14-.862.114-1.766-.35-2.544l-.02-.035c-.053-.084-.064-.09-.022.006zm2.225-.994c-.021.01-.02.095-.024.133-.027.705-.073 1.37-.086 2.033h.001l.054-.243c.124-.57.231-1.16.103-1.79l-.009-.036c-.01-.04-.023-.09-.039-.097zm-12.524-2.2c.281.257.521.567.68.917.282.646-.196 1.064-.811 1.063h-.032c-.55.015-1.096-.403-1.18-.955l-.002-.02c-.11-.691.705-1.576 1.345-1.005zm5.76-1.494l.007.012c.094.15.17.315.247.475l.017.036c.064.136.131.253.098.395l-.002.01v.001l-.017.064c-.037.139-.087.302-.187.404-.227.264-.684.22-.867-.073l-.009-.015c-.239-.394-.148-1.039.19-1.344.147-.133.41-.141.523.035zM20.337.5c.96.022.681 1.21-.15 1.16a.568.568 0 01-.493-.65c.035-.33.279-.505.61-.51h.033z`})]}))});function iR(e){"@babel/helpers - typeof";return iR=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},iR(e)}function aR(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function oR(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function wR(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var TR=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=CR(e,_R);return(0,P.jsxs)(`svg`,yR(yR({fill:`currentColor`,fillRule:`evenodd`,height:n,style:yR({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:fR}),(0,P.jsx)(`path`,{d:`M19.667 8.275c0-4.57-4.15-8.275-9.27-8.275-1.774 0-3.213 3.705-3.213 8.275 0 1.143.09 2.233.253 3.224H4.29L1 23h9.4v-6.447c5.117 0 9.266-3.707 9.266-8.275l.001-.002zm-9.27-6.76c.93 0 1.682 3.028 1.682 6.76 0 3.733-.752 6.76-1.681 6.76-.93 0-1.681-3.027-1.681-6.76 0-3.732.752-6.76 1.68-6.76z`}),(0,P.jsx)(`path`,{d:`M19.848 16.552h-9.44L14.028 23h9.438l-3.618-6.448z`})]}))});function ER(e){"@babel/helpers - typeof";return ER=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},ER(e)}function DR(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function OR(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function KR(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var qR=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=GR(e,zR);return(0,P.jsxs)(`svg`,VR(VR({fill:`currentColor`,fillRule:`evenodd`,height:n,style:VR({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:PR}),(0,P.jsx)(`path`,{d:`M17.86 22.992c-2.669.245-4.887-2.876-6.597-4.454C10.398 24.759 1 24.177 1 17.86V6.15c0-.921.244-1.861.733-2.65C2.635 1.977 4.383.98 6.15 1h11.71c6.316 0 6.918 9.398.677 10.243l2.97 2.951c3.252 3.064.808 8.929-3.646 8.797zm-1.428-3.721c1.842 1.898 4.774-1.034 2.876-2.876l-5.132-5.132H11.3v2.876l4.436 4.436.696.696zM4.12 17.842c-.037 2.632 4.117 2.632 4.06 0V6.132c.038-1.316-1.353-2.35-2.612-1.955-.057.019-.113.037-.15.056-.79.301-1.335 1.09-1.317 1.936v11.673h.02zm13.74-9.68c2.632.037 2.632-4.098 0-4.06h-6.973c.526 1.109.395 2.857.413 4.06h6.56z`})]}))});function JR(e){"@babel/helpers - typeof";return JR=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},JR(e)}function YR(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function XR(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function mz(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var hz=(0,N.memo)(function(e){var t=e.size,n=t===void 0?`1em`:t,r=e.style,i=pz(e,sz);return(0,P.jsxs)(`svg`,lz(lz({height:n,style:lz({flex:`none`,lineHeight:1},r),viewBox:`0 0 24 24`,width:n,xmlns:`http://www.w3.org/2000/svg`},i),{},{children:[(0,P.jsx)(`title`,{children:nz}),(0,P.jsx)(`path`,{d:`M9.1.503l2.824 4.47a1.078 1.078 0 01-.911 1.655H9.858v6.692h-1.67V0c.35 0 .7.168.912.503z`,fill:`#8FBCFA`}),(0,P.jsx)(`path`,{d:`M4.453 4.974L7.277.503A1.07 1.07 0 018.189 0v13.32a2.633 2.633 0 00-1.67.48V6.628H5.364c-.85 0-1.366-.936-.912-1.654z`,fill:`#468BFF`}),(0,P.jsx)(`path`,{d:`M17.041 17.74h-7.028c.423-.457.67-1.049.7-1.67h12.956c0 .35-.168.7-.502.912l-4.472 2.823a1.078 1.078 0 01-1.654-.911v-1.155z`,fill:`#FDBB11`}),(0,P.jsx)(`path`,{d:`M18.695 12.334l4.47 2.824c.336.212.503.562.503.912H10.713a2.65 2.65 0 00-.493-1.67h6.822v-1.154c0-.85.935-1.366 1.653-.912z`,fill:`#F6D785`}),(0,P.jsx)(`path`,{d:`M4.394 19.605L.316 23.683a1.07 1.07 0 001 .29l5.158-1.165A1.078 1.078 0 007 20.994l-.816-.816 3.073-3.074a1.61 1.61 0 000-2.276l-.042-.043-4.82 4.82z`,fill:`#FF9A9D`}),(0,P.jsx)(`path`,{d:`M3.822 17.817l3.073-3.074a1.61 1.61 0 012.277 0l.042.043-4.818 4.819-4.08 4.079a1.07 1.07 0 01-.289-1l1.165-5.158A1.078 1.078 0 013.006 17l.816.817z`,fill:`#FE363B`})]}))});function gz(e){"@babel/helpers - typeof";return gz=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},gz(e)}function _z(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function vz(e){for(var t=1;t=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Nz(e,t){if(e==null)return{};var n={},r=Object.keys(e),i,a;for(a=0;a=0)&&(n[i]=e[i]);return n}var $=(0,N.memo)(function(e){var t=e.shape,n=t===void 0?`circle`:t,r=e.color,i=r===void 0?`#fff`:r,a=e.background,o=e.size,s=e.style,c=e.iconMultiple,l=c===void 0?.75:c,u=e.Icon,d=e.iconStyle,f=e.iconClassName,p=Mz(e,Ez),m=Xc().isDarkMode;return(0,P.jsx)(nl,Oz(Oz({flex:`none`,style:Oz({background:a,borderRadius:n===`circle`?`50%`:Math.floor(o*.1),boxShadow:wz(m,a),color:i,height:o,width:o},s)},p),{},{children:u&&(0,P.jsx)(u,{className:f,color:i,size:o,style:Oz({transform:`scale(${l})`},d)})}))});function Pz(e){"@babel/helpers - typeof";return Pz=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},Pz(e)}function Fz(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Iz(e){for(var t=1;t0?` has-backends`:``}`,onClick:i,children:[(0,P.jsx)(Wz,{id:e.id,size:28}),(0,P.jsx)(`span`,{className:`provider-tile-name`,children:e.display_name}),(0,P.jsx)(`span`,{className:`provider-tile-id`,children:e.id}),t>0&&(0,P.jsxs)(`span`,{className:`provider-tile-count`,children:[t,` key`,t===1?``:`s`]})]}),(0,P.jsx)(`button`,{type:`button`,className:`provider-fav-btn${n?` active`:``}`,"aria-label":n?`Remove from favorites`:`Add to favorites`,"aria-pressed":n,title:n?`Unfavorite`:`Favorite`,onClick:e=>{e.stopPropagation(),r()},children:n?`♥`:`♡`})]})}function Jz({backend:e,healthStatus:t,onDelete:n}){return(0,P.jsxs)(`div`,{className:`provider-backend-row`,children:[(0,P.jsx)(Lc,{status:t===`up`?`ok`:t===`down`?`err`:`dim`,pulse:t===`up`}),(0,P.jsx)(`span`,{className:`backend-name`,children:e.name}),(0,P.jsxs)(`span`,{className:`backend-status`,children:[e.api_key_set?`key set`:`no key`,e.rpm!=null&&(0,P.jsxs)(P.Fragment,{children:[` · RPM `,e.rpm]})]}),(0,P.jsx)(H,{tone:`danger`,size:`sm`,onClick:n,children:`Delete`})]})}function Yz({provider:e,existingCount:t}){let n=co(),r=$a(),i=()=>{let n={name:`${e.id}-${t+1}`,provider_id:e.id};return Gz(e)&&e.default_base_url&&(n.api_base=e.default_base_url),n},[a,o]=(0,N.useState)(i);(0,N.useEffect)(()=>{o(i())},[e.id,t]);function s(){n.mutate({name:a.name,provider_id:e.id,api_key:a.api_key||void 0,api_base:a.api_base?a.api_base.trim().replace(/\/+$/,``):void 0,deployment:a.deployment||void 0,api_version:a.api_version||void 0,project:a.project||void 0,region:a.region||void 0,aws_access_key_id:a.aws_access_key_id||void 0,aws_secret_access_key:a.aws_secret_access_key||void 0,aws_session_token:a.aws_session_token||void 0,rpm:a.rpm?Number(a.rpm):void 0,tpm:a.tpm?Number(a.tpm):void 0},{onSuccess:()=>o(i())})}function c(){r.mutate({source:`custom`,url:a.api_base||e.default_base_url,provider_id:e.id,api_key:a.api_key||void 0})}let l=Vc(e);return(0,P.jsxs)(`div`,{className:`provider-add-form`,children:[(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsx)(`label`,{className:`form-label`,htmlFor:`add-backend-name`,children:`Name`}),(0,P.jsx)(`input`,{id:`add-backend-name`,name:`name`,type:`text`,value:a.name,onChange:e=>o(t=>({...t,name:e.target.value})),style:{width:`100%`}})]}),l.map(t=>(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsx)(`label`,{className:`form-label`,htmlFor:`add-${t.name}`,children:t.label}),t.hint&&(0,P.jsx)(`div`,{className:`form-hint`,children:t.hint}),(0,P.jsx)(`input`,{id:`add-${t.name}`,name:t.name,type:t.type,placeholder:t.placeholder,value:a[t.name]??``,onChange:e=>o(n=>({...n,[t.name]:e.target.value})),style:{width:`100%`}}),t.name===`api_base`&&(()=>{let t=Bc(a.api_base||e.default_base_url||``);if(!t)return null;let n=[`vertex_ai`,`gemini_native`,`bedrock_native`].includes(e.protocol);return(0,P.jsxs)(`div`,{className:`form-hint`,children:[`Query models will request: `,(0,P.jsx)(`span`,{className:`mono`,children:t}),n&&` — model discovery may not work for this provider.`]})})()]},t.name)),r.isError&&(0,P.jsx)(`div`,{className:`inline-error`,children:Kz(r.error,`Failed to query models`)}),r.isSuccess&&(0,P.jsx)(`div`,{className:`form-hint`,children:r.data.models.length>0?`Found ${r.data.models.length} model(s): ${r.data.models.slice(0,8).map(e=>e.id).join(`, `)}${r.data.models.length>8?`, …`:``}`:`No models returned by the server.`}),n.isError&&(0,P.jsx)(`div`,{className:`inline-error`,children:Kz(n.error,`Failed to create backend`)}),(0,P.jsxs)(`div`,{className:`provider-add-actions`,children:[(0,P.jsx)(H,{size:`sm`,onClick:()=>o(i()),disabled:n.isPending,children:`Reset`}),(0,P.jsx)(H,{size:`sm`,onClick:c,disabled:r.isPending||!a.api_base&&!e.default_base_url,loading:r.isPending,children:`Query models`}),(0,P.jsx)(H,{tone:`primary`,size:`sm`,onClick:s,disabled:!a.name||n.isPending,loading:n.isPending,children:`Create`})]})]})}function Xz({provider:e,backends:t,healthMap:n,onClose:r,onDeleteBackend:i}){let a=e.capabilities,o=[[`chat`,a.chat_completions],[`streaming`,a.streaming],[`tool use`,a.tool_use],[`vision`,a.vision],[`embeddings`,a.embeddings],[`batch`,a.batch]];return(0,P.jsxs)(pc,{open:!0,onClose:r,title:`${e.display_name} (${e.id})`,size:`md`,children:[(0,P.jsxs)(`div`,{className:`provider-panel-caps`,children:[o.map(([e,t])=>(0,P.jsx)(`span`,{className:`badge-cap${t?` active`:``}`,children:e},e)),(0,P.jsxs)(`span`,{style:{marginLeft:`auto`},className:`badge-cap active`,children:[e.model_count,` models`]})]}),(0,P.jsxs)(`div`,{className:`provider-panel-meta`,children:[(0,P.jsxs)(`span`,{children:[`Protocol: `,(0,P.jsx)(`span`,{className:`mono`,children:e.protocol.replace(/_/g,` `)})]}),(0,P.jsxs)(`span`,{children:[`Auth: `,(0,P.jsx)(`span`,{className:`mono`,children:e.auth.replace(/_/g,` `)})]}),(0,P.jsxs)(`span`,{children:[`Status: `,(0,P.jsx)(`span`,{className:`mono`,children:e.status})]}),e.env_vars.length>0&&(0,P.jsxs)(`span`,{children:[`Env: `,(0,P.jsx)(`span`,{className:`mono`,children:e.env_vars[0]})]})]}),(0,P.jsxs)(`div`,{className:`provider-panel-section`,children:[(0,P.jsxs)(`div`,{className:`provider-panel-section-label`,children:[`Configured keys (`,t.length,`)`]}),t.length===0&&(0,P.jsx)(`div`,{className:`provider-empty-hint`,children:`No keys configured. Add one below to start forwarding requests.`}),t.map(e=>(0,P.jsx)(Jz,{backend:e,healthStatus:n.get(e.name),onDelete:()=>i(e)},e.id)),(0,P.jsx)(Yz,{provider:e,existingCount:t.length})]})]})}function Zz(){let e=io(),t=so(),{data:n}=no(),{data:r}=ao(),i=oo(),a=uo(),o=(0,N.useMemo)(()=>new Set(r??[]),[r]),[s,c]=(0,N.useState)(null),[l,u]=(0,N.useState)(``),[d,f]=(0,N.useState)(null),p=(0,N.useMemo)(()=>e.data??[],[e.data]),m=(0,N.useMemo)(()=>t.data?.backends??[],[t.data]),h=(0,N.useMemo)(()=>{let e=new Map;for(let t of m)e.has(t.provider_id)||e.set(t.provider_id,[]),e.get(t.provider_id).push(t);return e},[m]),g=(0,N.useMemo)(()=>{let e=new Map;for(let t of n?.backends??[])e.set(t.name,t.status);return e},[n]),_=(0,N.useMemo)(()=>{let e=l.toLowerCase();return Kc(e?p.filter(t=>t.display_name.toLowerCase().includes(e)||t.id.includes(e)):p,o)},[p,l,o]),v=s?p.find(e=>e.id===s):null,y=s?h.get(s)??[]:[];function b(){return d?a.mutateAsync(d.name).then(()=>void 0):Promise.resolve()}return(0,P.jsxs)(`div`,{children:[(0,P.jsxs)(`div`,{className:`section-header`,children:[(0,P.jsx)(`h2`,{children:`Providers`}),(0,P.jsx)(`input`,{type:`search`,name:`provider-search`,placeholder:`Search providers...`,value:l,onChange:e=>u(e.target.value),style:{width:260}})]}),(0,P.jsx)(ac,{query:e,errorTitle:`Failed to load provider catalog`,empty:{when:()=>p.length===0,render:()=>(0,P.jsxs)(U,{className:`empty-cta`,children:[(0,P.jsx)(`div`,{className:`empty-cta-title`,children:`No providers available`}),(0,P.jsx)(`div`,{className:`empty-cta-body`,children:`The provider catalog is empty. Check that the providers crate is loaded.`})]})},children:()=>(0,P.jsxs)(`div`,{className:`provider-catalog`,children:[_.map(e=>(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`div`,{className:`provider-tier-label`,children:e.label}),(0,P.jsx)(`div`,{className:`provider-tile-grid${e.top?` tier-top`:``}`,children:e.providers.map(e=>(0,P.jsx)(qz,{provider:e,backendCount:h.get(e.id)?.length??0,favorited:o.has(e.id),onToggleFavorite:()=>i.mutate({providerId:e.id,on:!o.has(e.id)}),onClick:()=>c(e.id)},e.id))})]},e.key)),_.length===0&&l&&(0,P.jsxs)(`div`,{className:`dim`,style:{padding:20},children:[`No providers match "`,l,`".`]})]})}),v&&(0,P.jsx)(Xz,{provider:v,backends:y,healthMap:g,onClose:()=>c(null),onDeleteBackend:f},s),(0,P.jsx)(mc,{open:d!==null,onClose:()=>f(null),onConfirm:b,title:`Delete backend?`,message:(0,P.jsxs)(P.Fragment,{children:[`Delete backend `,(0,P.jsx)(`span`,{className:`mono`,children:d?.name}),`? Routes referencing this backend will lose it from their provider list.`]})})]})}function Qz({initial:e,onSuccess:t,onCancel:n}){let r=!!e,{data:i=[]}=io(),a=co(),o=lo(),[s,c]=(0,N.useState)(e?.name??``),[l,u]=(0,N.useState)(e?.provider_id??``),[d,f]=(0,N.useState)(()=>{if(!e)return{};let t={};for(let n of[`api_base`,`deployment`,`api_version`,`project`,`region`])e[n]!=null&&(t[n]=e[n]);return e.rpm!=null&&(t.rpm=String(e.rpm)),e.tpm!=null&&(t.tpm=String(e.tpm)),t}),[p,m]=(0,N.useState)(null);(0,N.useEffect)(()=>{i.length>0&&!l&&u(e?.provider_id??i[0].id)},[i.length]);let h=i.find(e=>e.id===l)??(i.length>0?i[0]:void 0),g=h?Vc(h):[],_=g.filter(e=>e.group===`auth`),v=g.filter(e=>e.group===`endpoint`),y=g.filter(e=>e.group===`limits`);function b(e){return d[e]??``}function x(e,t){f(n=>({...n,[e]:t}))}function S(t){return!r||!e?!1:t===`api_key`?e.api_key_set:t===`aws_secret_access_key`||t===`aws_access_key_id`?e.aws_creds_set:!1}function C(e){return Object.fromEntries(Object.entries(e).filter(([,e])=>e!==``))}function w(n){n.preventDefault(),m(null);let i=C(d);if(r&&e)o.mutate({name:e.name,data:i},{onSuccess:()=>t(),onError:e=>m(e.message)});else{if(!s){m(`Name is required`);return}a.mutate({name:s,provider_id:l,...i},{onSuccess:()=>t(),onError:e=>m(e.message)})}}let T=a.isPending||o.isPending;function E(e){let t=b(e.name),n=S(e.name),r=e.type===`url`?`text`:e.type;return(0,P.jsxs)(`div`,{style:{marginBottom:10},children:[(0,P.jsxs)(`div`,{style:{fontSize:12,color:`var(--text-2)`,marginBottom:3},children:[e.label,e.required&&(0,P.jsx)(`span`,{style:{color:`var(--err)`,marginLeft:2},children:`*`})]}),(0,P.jsx)(`input`,{type:r,value:t,placeholder:n?`••••••••`:e.placeholder,onChange:t=>x(e.name,t.target.value),style:{width:`100%`}}),e.hint&&(0,P.jsx)(`div`,{style:{fontSize:11,color:`var(--text-2)`,marginTop:3},children:e.hint})]},e.name)}return(0,P.jsxs)(`form`,{onSubmit:w,style:{padding:`12px`,background:`var(--bg-raised)`,border:`1px solid var(--border)`,borderRadius:`var(--rm)`,marginBottom:14},children:[(0,P.jsx)(`div`,{style:{fontWeight:600,marginBottom:12,fontSize:13},children:r?`Edit backend: ${e.name}`:`Add managed backend`}),(0,P.jsxs)(`div`,{style:{marginBottom:10},children:[(0,P.jsxs)(`div`,{style:{fontSize:12,color:`var(--text-2)`,marginBottom:3},children:[`Provider`,(0,P.jsx)(`span`,{style:{color:`var(--err)`,marginLeft:2},children:`*`})]}),(0,P.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:8},children:[l&&(0,P.jsx)(Wz,{id:l,size:18,style:{flexShrink:0}}),(0,P.jsxs)(`select`,{value:l,onChange:e=>{u(e.target.value),f({})},disabled:r,style:{flex:1},children:[i.length===0&&(0,P.jsx)(`option`,{value:``,children:`Loading providers…`}),[`implemented`,`wired`,`stub`].map(e=>{let t=i.filter(t=>t.status===e);return t.length===0?null:(0,P.jsx)(`optgroup`,{label:e.charAt(0).toUpperCase()+e.slice(1),children:t.map(e=>(0,P.jsxs)(`option`,{value:e.id,children:[e.display_name,` (`,e.id,`)`]},e.id))},e)})]})]})]}),(0,P.jsxs)(`div`,{style:{marginBottom:10},children:[(0,P.jsxs)(`div`,{style:{fontSize:12,color:`var(--text-2)`,marginBottom:3},children:[`Name`,(0,P.jsx)(`span`,{style:{color:`var(--err)`,marginLeft:2},children:`*`})]}),(0,P.jsx)(`input`,{type:`text`,value:s,onChange:e=>c(e.target.value),required:!0,pattern:`[a-zA-Z0-9_\\-]+`,placeholder:`my-backend`,disabled:r,style:{width:`100%`}}),!r&&(0,P.jsx)(`div`,{style:{fontSize:11,color:`var(--text-2)`,marginTop:3},children:`Letters, numbers, underscores, hyphens only`})]}),_.length>0&&(0,P.jsxs)(`div`,{style:{marginBottom:4},children:[(0,P.jsx)(`div`,{className:`section-label`,style:{marginBottom:6},children:`Authentication`}),_.map(E)]}),v.length>0&&(0,P.jsxs)(`div`,{style:{marginBottom:4},children:[(0,P.jsx)(`div`,{className:`section-label`,style:{marginBottom:6},children:`Endpoint`}),v.map(E)]}),y.length>0&&(0,P.jsxs)(`details`,{style:{marginBottom:10},children:[(0,P.jsx)(`summary`,{style:{fontSize:11,color:`var(--text-2)`,cursor:`pointer`,textTransform:`uppercase`,letterSpacing:`0.07em`,fontWeight:500,marginBottom:6},children:`Rate Limits`}),(0,P.jsx)(`div`,{style:{marginTop:8},children:y.map(E)})]}),p&&(0,P.jsx)(`div`,{style:{marginBottom:10,padding:`6px 10px`,background:`var(--err-dim)`,borderLeft:`3px solid var(--err)`,borderRadius:`var(--r)`,fontSize:12},children:p}),(0,P.jsxs)(`div`,{style:{display:`flex`,gap:8},children:[(0,P.jsx)(H,{type:`submit`,tone:`primary`,size:`sm`,loading:T,children:r?`Save changes`:`Create backend`}),(0,P.jsx)(H,{type:`button`,size:`sm`,onClick:n,disabled:T,children:`Cancel`})]})]})}function $z(){let{data:e,isLoading:t,error:n}=so(),{data:r=[]}=io(),i=uo(),[a,o]=(0,N.useState)({mode:`none`}),[s,c]=(0,N.useState)(null),l=(0,N.useMemo)(()=>Object.fromEntries(r.map(e=>[e.id,e.display_name])),[r]);function u(e){return l[e]??e}function d(){return s?i.mutateAsync(s.name).then(()=>void 0):Promise.resolve()}return(0,P.jsxs)(`div`,{style:{marginBottom:24},children:[(0,P.jsxs)(`div`,{className:`section-header`,children:[(0,P.jsx)(`div`,{className:`section-label`,style:{margin:0},children:`Managed Backends`}),(0,P.jsx)(H,{tone:`primary`,size:`sm`,onClick:()=>o({mode:`create`}),children:`Add Backend`})]}),(0,P.jsx)(`div`,{style:{fontSize:12,color:`var(--text-2)`,marginBottom:10},children:`Configure provider credentials and backend settings at runtime.`}),a.mode===`create`&&(0,P.jsx)(Qz,{onSuccess:()=>o({mode:`none`}),onCancel:()=>o({mode:`none`})}),a.mode===`edit`&&(0,P.jsx)(Qz,{initial:a.backend,onSuccess:()=>o({mode:`none`}),onCancel:()=>o({mode:`none`})}),(0,P.jsx)(tc,{loading:t,error:n?.message}),e&&e.backends.length===0&&(0,P.jsx)(`div`,{style:{padding:`20px 0`,color:`var(--text-2)`,fontSize:13},children:`No managed backends yet. Add one to configure provider credentials at runtime.`}),e&&e.backends.length>0&&(0,P.jsxs)(`table`,{className:`route-table`,children:[(0,P.jsx)(`thead`,{children:(0,P.jsxs)(`tr`,{children:[(0,P.jsx)(`th`,{children:`Name`}),(0,P.jsx)(`th`,{children:`Provider`}),(0,P.jsx)(`th`,{children:`Credentials`}),(0,P.jsx)(`th`,{children:`Base URL`}),(0,P.jsx)(`th`,{})]})}),(0,P.jsx)(`tbody`,{children:e.backends.map(e=>(0,P.jsxs)(`tr`,{children:[(0,P.jsx)(`td`,{className:`mono`,children:e.name}),(0,P.jsxs)(`td`,{className:`dim`,style:{whiteSpace:`nowrap`},children:[(0,P.jsx)(Wz,{id:e.provider_id,size:16,style:{marginRight:6,verticalAlign:`middle`,opacity:.8}}),u(e.provider_id)]}),(0,P.jsxs)(`td`,{children:[e.api_key_set&&(0,P.jsx)(`span`,{className:`badge badge-active`,style:{marginRight:4},children:`Key set`}),!e.api_key_set&&!e.aws_creds_set&&(0,P.jsx)(`span`,{className:`badge badge-revoked`,children:`No key`}),e.aws_creds_set&&(0,P.jsx)(`span`,{className:`badge badge-active`,children:`AWS creds set`})]}),(0,P.jsx)(`td`,{className:`dim mono`,style:{fontSize:11},children:e.api_base??`—`}),(0,P.jsx)(`td`,{children:(0,P.jsxs)(`div`,{style:{display:`flex`,gap:6},children:[(0,P.jsx)(H,{size:`sm`,onClick:()=>{a.mode===`edit`&&a.backend.id===e.id?o({mode:`none`}):o({mode:`edit`,backend:e})},children:`Edit`}),(0,P.jsx)(H,{tone:`danger`,size:`sm`,onClick:()=>c(e),disabled:i.isPending&&i.variables===e.name,children:`Delete`})]})})]},e.id))})]}),(0,P.jsx)(mc,{open:s!==null,onClose:()=>c(null),onConfirm:d,title:`Delete managed backend?`,message:(0,P.jsxs)(P.Fragment,{children:[`Delete backend `,(0,P.jsx)(`span`,{className:`mono`,children:s?.name}),`? Stored credentials will be removed. Routes still referencing it will fail until reconfigured.`]})})]})}function eB(){let{data:e,isLoading:t,error:n}=Ua(),{data:r}=no(),i=(0,N.useMemo)(()=>{let e=new Map;for(let t of r?.backends??[])e.set(t.name,t);return e},[r]);return(0,P.jsxs)(`div`,{children:[(0,P.jsx)($z,{}),(0,P.jsx)(`div`,{className:`section-label`,style:{marginTop:8},children:`Backend Status`}),(0,P.jsx)(tc,{loading:t,error:n?.message,empty:e?.length===0}),(0,P.jsx)(`div`,{className:`backend-cards`,children:e?.map(e=>{let t=i.get(e.name),n=t?.status===`up`?`ok`:t?.status===`down`?`err`:`dim`;return(0,P.jsxs)(U,{className:`card`,children:[(0,P.jsxs)(`div`,{className:`card-header`,children:[(0,P.jsx)(`span`,{className:`card-name`,children:e.name}),(0,P.jsx)(Lc,{status:n,pulse:n===`ok`})]}),(0,P.jsxs)(`div`,{className:`card-body`,children:[(0,P.jsxs)(`div`,{className:`mono`,children:[e.big_model,` / `,e.small_model]}),(0,P.jsxs)(`div`,{style:{marginTop:6,display:`grid`,gridTemplateColumns:`1fr 1fr`,gap:4},children:[(0,P.jsx)(`span`,{className:`dim`,children:`Requests`}),(0,P.jsx)(`span`,{className:`mono`,children:e.metrics.requests_total}),(0,P.jsx)(`span`,{className:`dim`,children:`Errors`}),(0,P.jsx)(`span`,{className:`mono`,style:{color:e.metrics.requests_error>0?`var(--err)`:void 0},children:e.metrics.requests_error}),t?.last_latency_ms!=null&&(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(`span`,{className:`dim`,children:`Last latency`}),(0,P.jsxs)(`span`,{className:`mono`,children:[t.last_latency_ms,`ms`]})]}),t&&(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(`span`,{className:`dim`,children:`30d uptime`}),(0,P.jsxs)(`span`,{className:`mono`,children:[t.uptime_pct_30d,`%`]})]})]})]})]},e.name)})})]})}var tB=[`failover`,`round-robin`,`least-busy`,`latency`,`weighted`,`cost`];function nB({value:e,onChange:t}){return(0,P.jsxs)(`select`,{value:e===null?`inherit`:e?`on`:`off`,onChange:e=>{let n=e.target.value;t(n===`inherit`?null:n===`on`)},children:[(0,P.jsx)(`option`,{value:`inherit`,children:`inherit (global)`}),(0,P.jsx)(`option`,{value:`on`,children:`on`}),(0,P.jsx)(`option`,{value:`off`,children:`off`})]})}function rB({route:e,onClose:t}){let{data:n,isLoading:r}=B(e.id),i=ho(),a=go(),o=_o(),s=vo(),c=mo(),l=lo(),{data:u}=so(),{data:d}=Fa(),[f,p]=(0,N.useState)(!1),[m,h]=(0,N.useState)(``),[g,_]=(0,N.useState)(`*`),v=n?.providers??[],y=u?.backends??[],b=[`curl ${`http://${window.location.hostname}:${d?.proxy_port??3e3}/v1/chat/completions`} \\`,` -H 'Authorization: Bearer ' \\`,` -H 'Content-Type: application/json' \\`,` -d '${JSON.stringify({model:e.name,messages:[{role:`user`,content:`hi`}]})}'`].join(` +`);async function x(){ma(await Tc(b)?{variant:`success`,message:`curl snippet copied`}:{variant:`error`,message:`Copy failed (clipboard blocked)`})}let S=new Set(v.map(e=>e.backend_id)),C=y.filter(e=>!S.has(e.id));function w(){if(!m)return;let t=g.trim()===`*`?[`*`]:g.split(`,`).map(e=>e.trim()).filter(Boolean);i.mutate({routeId:e.id,data:{backend_id:m,models:t,priority:v.length,enabled:!0}},{onSuccess:()=>{p(!1),h(``),_(`*`)}})}function T(t,n){let r=t+n;if(r<0||r>=v.length)return;let i=v.slice(),[a]=i.splice(t,1);i.splice(r,0,a),s.mutate({routeId:e.id,data:{provider_ids:i.map(e=>e.id)}})}return(0,P.jsxs)(`div`,{className:`route-detail`,children:[(0,P.jsxs)(`div`,{className:`route-detail-header`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`span`,{className:`route-detail-title`,children:e.name}),e.description&&(0,P.jsx)(`span`,{className:`dim route-detail-desc`,children:e.description})]}),(0,P.jsxs)(`div`,{className:`route-detail-meta`,children:[e.rpm&&(0,P.jsxs)(`span`,{className:`dim mono`,children:[`RPM `,e.rpm]}),(0,P.jsx)(H,{size:`sm`,tone:e.enabled?`primary`:`secondary`,title:`Route on/off. Disabled routes stop dispatching and lose virtual-key scope.`,onClick:()=>c.mutate({id:e.id,data:{enabled:!e.enabled}}),children:e.enabled?`route on`:`route off`}),(0,P.jsx)(H,{size:`sm`,onClick:t,children:`Close`})]})]}),e.enabled&&(0,P.jsxs)(`div`,{className:`route-detail-curl`,children:[(0,P.jsxs)(`div`,{className:`route-detail-curl-head`,children:[(0,P.jsx)(`span`,{className:`section-label`,children:`Call this route`}),(0,P.jsx)(H,{size:`sm`,onClick:x,children:`Copy curl`})]}),(0,P.jsx)(`pre`,{className:`route-detail-curl-body mono`,children:b}),(0,P.jsxs)(`div`,{className:`dim route-detail-curl-hint`,children:[`The route is selected by the `,(0,P.jsx)(`code`,{children:`model`}),` field (= route name). Replace`,` `,(0,P.jsx)(`code`,{children:``}),` with a proxy or virtual key.`]})]}),(0,P.jsxs)(`div`,{className:`route-detail-options`,children:[(0,P.jsx)(`span`,{className:`section-label route-detail-subhead-label`,children:`Route options`}),(0,P.jsxs)(`div`,{className:`route-options-grid`,children:[(0,P.jsxs)(`label`,{className:`route-option`,children:[(0,P.jsx)(`span`,{className:`dim`,children:`Strategy`}),(0,P.jsx)(`select`,{value:e.strategy,onChange:t=>c.mutate({id:e.id,data:{strategy:t.target.value}}),children:tB.map(e=>(0,P.jsx)(`option`,{value:e,children:e},e))})]}),(0,P.jsxs)(`label`,{className:`route-option`,children:[(0,P.jsx)(`span`,{className:`dim`,children:`Position (lower wins across routes)`}),(0,P.jsx)(`input`,{type:`number`,name:`route-position`,defaultValue:e.position,onBlur:t=>{let n=Number.parseInt(t.target.value,10);Number.isNaN(n)||n===e.position||c.mutate({id:e.id,data:{position:n}})}})]}),(0,P.jsxs)(`label`,{className:`route-option`,children:[(0,P.jsx)(`span`,{className:`dim`,children:`Guardrails`}),(0,P.jsxs)(`select`,{value:e.guardrail_mode??`inherit`,onChange:t=>c.mutate({id:e.id,data:{guardrail_mode:t.target.value===`inherit`?null:t.target.value}}),children:[(0,P.jsx)(`option`,{value:`inherit`,children:`inherit (global)`}),(0,P.jsx)(`option`,{value:`disabled`,children:`disabled`}),(0,P.jsx)(`option`,{value:`standard`,children:`standard`})]})]}),(0,P.jsxs)(`label`,{className:`route-option`,children:[(0,P.jsx)(`span`,{className:`dim`,children:`Secret redaction`}),(0,P.jsx)(nB,{value:e.redact_secrets,onChange:t=>c.mutate({id:e.id,data:{redact_secrets:t}})})]}),(0,P.jsxs)(`label`,{className:`route-option`,children:[(0,P.jsx)(`span`,{className:`dim`,children:`Image compression`}),(0,P.jsx)(nB,{value:e.pxpipe_compress,onChange:t=>c.mutate({id:e.id,data:{pxpipe_compress:t}})})]}),(0,P.jsxs)(`label`,{className:`route-option route-option-wide`,children:[(0,P.jsx)(`span`,{className:`dim`,children:`Compression model scope (CSV, blank = inherit)`}),(0,P.jsx)(`input`,{type:`text`,name:`route-pxpipe-models`,defaultValue:e.pxpipe_models??``,placeholder:`inherit global`,onBlur:t=>{let n=t.target.value.trim();(e.pxpipe_models??``)!==n&&c.mutate({id:e.id,data:{pxpipe_models:n===``?null:n}})}})]})]}),(0,P.jsx)(`div`,{className:`dim route-options-note`,children:`Overrides apply only where the feature already runs (image compression: Anthropic passthrough backends only). "inherit" / blank uses the global value from Settings.`})]}),(0,P.jsxs)(`div`,{className:`route-detail-subhead`,children:[(0,P.jsx)(`span`,{className:`section-label route-detail-subhead-label`,children:`Providers (priority order)`}),(0,P.jsx)(H,{tone:`primary`,size:`sm`,onClick:()=>p(!f),children:f?`Cancel`:`+ Add Provider`})]}),f&&(0,P.jsxs)(`div`,{className:`route-detail-add`,children:[(0,P.jsxs)(`select`,{value:m,onChange:e=>h(e.target.value),className:`route-detail-add-select`,children:[(0,P.jsx)(`option`,{value:``,children:`Select provider...`}),C.map(e=>(0,P.jsxs)(`option`,{value:e.id,children:[e.name,` (`,e.provider_id,`)`]},e.id))]}),(0,P.jsx)(`input`,{type:`text`,name:`route-provider-models`,placeholder:`models (* for all)`,value:g,onChange:e=>_(e.target.value),className:`route-detail-add-models`}),(0,P.jsx)(H,{tone:`primary`,size:`sm`,onClick:w,disabled:!m||i.isPending,loading:i.isPending,children:`Add`})]}),r&&(0,P.jsx)(`div`,{className:`dim`,children:(0,P.jsx)(Vs,{label:`Loading providers`})}),!r&&v.length===0&&(0,P.jsx)(`div`,{className:`dim route-detail-empty`,children:`No providers assigned. Click "+ Add Provider" above.`}),!r&&v.map((t,n)=>(0,P.jsxs)(`div`,{className:`route-provider-row`,children:[(0,P.jsxs)(`span`,{className:`dim mono`,children:[n+1,`.`]}),(0,P.jsxs)(`span`,{children:[(0,P.jsx)(`span`,{className:`route-provider-name`,children:t.backend_name}),(0,P.jsxs)(`span`,{className:`dim route-provider-id`,children:[`(`,t.provider_id,`)`]})]}),(0,P.jsxs)(`span`,{className:`mono dim route-provider-models`,children:[`[`,t.models.join(`, `),`]`]}),(0,P.jsxs)(`span`,{className:`route-provider-reorder`,children:[(0,P.jsx)(H,{tone:`icon`,size:`sm`,className:`btn-icon`,onClick:()=>T(n,-1),disabled:n===0||s.isPending,"aria-label":`Move up`,children:`↑`}),(0,P.jsx)(H,{tone:`icon`,size:`sm`,className:`btn-icon`,onClick:()=>T(n,1),disabled:n>=v.length-1||s.isPending,"aria-label":`Move down`,children:`↓`})]}),(0,P.jsx)(H,{size:`sm`,tone:t.enabled?`primary`:`secondary`,className:`route-provider-toggle`,title:`In-route membership: whether this backend is active within this route.`,onClick:()=>a.mutate({routeId:e.id,providerId:t.id,data:{enabled:!t.enabled}}),children:t.enabled?`in route`:`excluded`}),(()=>{let e=y.find(e=>e.id===t.backend_id);return e?(0,P.jsx)(H,{size:`sm`,tone:e.enabled?`primary`:`secondary`,className:`route-provider-toggle`,title:`Backend online (global). Disables this backend everywhere, not just this route.`,onClick:()=>l.mutate({name:e.name,data:{enabled:!e.enabled}}),children:e.enabled?`backend on`:`backend off`}):null})(),(0,P.jsx)(H,{tone:`danger`,size:`sm`,className:`route-provider-remove`,onClick:()=>o.mutate({routeId:e.id,providerId:t.id}),children:`Remove`})]},t.id))]})}function iB({onClose:e}){let t=po(),[n,r]=(0,N.useState)(``),[i,a]=(0,N.useState)(``),[o,s]=(0,N.useState)(`failover`);function c(){t.mutate({name:n,description:i||void 0,strategy:o},{onSuccess:e})}return(0,P.jsxs)(pc,{open:!0,onClose:e,title:`New Route`,size:`sm`,dismissable:!t.isPending,footer:(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(H,{onClick:e,disabled:t.isPending,children:`Cancel`}),(0,P.jsx)(H,{tone:`primary`,onClick:c,disabled:!n.trim()||t.isPending,loading:t.isPending,children:`Create`})]}),children:[(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsx)(`label`,{className:`form-label`,htmlFor:`route-name`,children:`Name`}),(0,P.jsx)(`input`,{id:`route-name`,name:`name`,type:`text`,value:n,onChange:e=>r(e.target.value),placeholder:`e.g. default, cheap`,style:{width:`100%`}})]}),(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsx)(`label`,{className:`form-label`,htmlFor:`route-desc`,children:`Description`}),(0,P.jsx)(`input`,{id:`route-desc`,name:`description`,type:`text`,value:i,onChange:e=>a(e.target.value),placeholder:`optional`,style:{width:`100%`}})]}),(0,P.jsxs)(`div`,{className:`form-group`,children:[(0,P.jsx)(`label`,{className:`form-label`,htmlFor:`route-strategy`,children:`Strategy`}),(0,P.jsx)(`select`,{id:`route-strategy`,name:`strategy`,value:o,onChange:e=>s(e.target.value),style:{width:`100%`},children:tB.map(e=>(0,P.jsx)(`option`,{value:e,children:e},e))})]}),(0,P.jsx)(`div`,{className:`dim`,style:{fontSize:`0.85em`},children:`Per-route options (guardrails, compression, secret redaction) and on/off are set after creation from the route's detail panel.`}),t.isError&&(0,P.jsx)(`div`,{className:`error`,children:`Failed to create route`})]})}function aB(){let e=fo(),t=z(),[n,r]=(0,N.useState)(null),[i,a]=(0,N.useState)(!1),[o,s]=(0,N.useState)(null);function c(){return o?t.mutateAsync(o.id).then(()=>void 0):Promise.resolve()}return(0,P.jsxs)(`div`,{children:[(0,P.jsxs)(`div`,{className:`section-header`,children:[(0,P.jsx)(`h2`,{children:`Routes`}),(0,P.jsx)(H,{tone:`primary`,onClick:()=>a(!0),children:`+ New Route`})]}),(0,P.jsx)(ac,{query:e,errorTitle:`Failed to load routes`,empty:{when:e=>(e.routes?.length??0)===0,render:()=>(0,P.jsxs)(U,{className:`empty-cta`,children:[(0,P.jsx)(`div`,{className:`empty-cta-title`,children:`No routes yet`}),(0,P.jsx)(`div`,{className:`empty-cta-body`,children:`Create a route to fan requests out across multiple backends with priority-based failover.`}),(0,P.jsx)(H,{tone:`primary`,onClick:()=>a(!0),children:`+ New Route`})]})},children:e=>(0,P.jsxs)(`table`,{className:`route-table`,children:[(0,P.jsx)(`thead`,{children:(0,P.jsxs)(`tr`,{children:[(0,P.jsx)(`th`,{children:`Name`}),(0,P.jsx)(`th`,{children:`Strategy`}),(0,P.jsx)(`th`,{children:`Providers`}),(0,P.jsx)(`th`,{children:`Limits`}),(0,P.jsx)(`th`,{})]})}),(0,P.jsx)(`tbody`,{children:e.routes.map(e=>(0,P.jsx)(oB,{route:e,expanded:n===e.id,onToggle:()=>r(n===e.id?null:e.id),onDelete:()=>s(e)},e.id))})]})}),i&&(0,P.jsx)(iB,{onClose:()=>a(!1)}),(0,P.jsx)(mc,{open:o!==null,onClose:()=>s(null),onConfirm:c,title:`Delete route?`,message:(0,P.jsxs)(P.Fragment,{children:[`Delete route `,(0,P.jsx)(`span`,{className:`mono`,children:o?.name}),`? Virtual keys scoped to this route will lose access. This cannot be undone.`]})})]})}function oB({route:e,expanded:t,onToggle:n,onDelete:r}){let i=[e.rpm&&`RPM ${e.rpm}`,e.tpm&&`TPM ${e.tpm}`].filter(Boolean).join(`, `)||`—`;return(0,P.jsxs)(P.Fragment,{children:[(0,P.jsxs)(`tr`,{className:`route-row`,onClick:n,children:[(0,P.jsxs)(`td`,{className:`route-row-name`,children:[t?`▾ `:`▸ `,e.name,!e.enabled&&(0,P.jsx)(`span`,{className:`dim route-row-desc`,children:`(disabled)`}),e.description&&(0,P.jsx)(`span`,{className:`dim route-row-desc`,children:e.description})]}),(0,P.jsx)(`td`,{className:`dim`,children:e.strategy}),(0,P.jsx)(`td`,{children:e.provider_count}),(0,P.jsx)(`td`,{className:`mono dim`,children:i}),(0,P.jsx)(`td`,{className:`route-row-actions`,children:(0,P.jsx)(H,{tone:`danger`,size:`sm`,onClick:e=>{e.stopPropagation(),r()},children:`Delete`})})]}),t&&(0,P.jsx)(`tr`,{children:(0,P.jsx)(`td`,{colSpan:5,className:`route-row-detail-cell`,children:(0,P.jsx)(rB,{route:e,onClose:n})})})]})}function sB(){let e=ea(e=>e.token),t=ta(e=>e.lastEvent),n=Tn(),{data:r}=Fa(!!e);if((0,N.useEffect)(()=>{e?sa():ca()},[e]),(0,N.useEffect)(()=>{t&&(t.type===`metrics_snapshot`?n.setQueryData([`metrics`],t.data):t.type===`backend_health_changed`?n.invalidateQueries({queryKey:[`uptime`]}):t.type===`config_changed`&&(n.invalidateQueries({queryKey:[`config`]}),n.invalidateQueries({queryKey:[`env`]})))},[t,n]),!e)return(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(Us,{}),(0,P.jsx)(Ks,{})]});let i=r?.configured??!0;return(0,P.jsx)(Pi,{children:(0,P.jsxs)(`div`,{className:`app-layout`,children:[(0,P.jsx)(Gs,{}),(0,P.jsx)(`div`,{className:`tab-content`,children:(0,P.jsxs)(bi,{children:[(0,P.jsx)(vi,{path:`/`,element:(0,P.jsx)(_i,{to:i?`/dashboard`:`/settings`,replace:!0})}),(0,P.jsx)(vi,{path:`/dashboard`,element:(0,P.jsx)(rc,{})}),(0,P.jsx)(vi,{path:`/requests`,element:(0,P.jsx)(sc,{})}),(0,P.jsx)(vi,{path:`/traffic`,element:(0,P.jsx)(Pc,{})}),(0,P.jsx)(vi,{path:`/providers`,element:(0,P.jsx)(Zz,{})}),(0,P.jsx)(vi,{path:`/routes`,element:(0,P.jsx)(aB,{})}),(0,P.jsx)(vi,{path:`/models`,element:(0,P.jsx)(kc,{})}),(0,P.jsx)(vi,{path:`/backends`,element:(0,P.jsx)(eB,{})}),(0,P.jsx)(vi,{path:`/keys`,element:(0,P.jsx)(Ec,{})}),(0,P.jsx)(vi,{path:`/audit`,element:(0,P.jsx)(Ac,{})}),(0,P.jsx)(vi,{path:`/settings`,element:(0,P.jsx)(vc,{configured:i})}),(0,P.jsx)(vi,{path:`/uptime`,element:(0,P.jsx)(zc,{})}),(0,P.jsx)(vi,{path:`*`,element:(0,P.jsx)(_i,{to:`/dashboard`,replace:!0})})]})}),(0,P.jsx)(Ks,{})]})})}var cB=new xn({defaultOptions:{queries:{retry:(e,t)=>t instanceof ka?!1:e<1,refetchOnWindowFocus:!1,staleTime:3e4},mutations:{retry:(e,t)=>t instanceof ka?!1:e<1}}});(0,Un.createRoot)(document.getElementById(`root`)).render((0,P.jsx)(N.StrictMode,{children:(0,P.jsx)(En,{client:cB,children:(0,P.jsx)(sB,{})})})); diff --git a/crates/proxy/admin-ui/src/api/types.ts b/crates/proxy/admin-ui/src/api/types.ts index 60a44c5..71f0bdf 100644 --- a/crates/proxy/admin-ui/src/api/types.ts +++ b/crates/proxy/admin-ui/src/api/types.ts @@ -1,758 +1,17 @@ // Mirrors the JSON shapes returned by /admin/api/* endpoints. // Keep in sync with Rust structs in crates/proxy/src/admin/state.rs and routes/. -/** Represents the status and configuration details of the proxy server. */ -export interface ProxyStatus { - /** True if the proxy backend has been configured. */ - configured: boolean - /** The port number the proxy listens on for client requests. */ - proxy_port: number - /** Whether the proxy is currently running and accepting connections. */ - proxy_running: boolean -} - -/** Represents real-time usage and performance metrics for the proxy. */ -export interface Metrics { - /** The total number of requests received by the proxy. */ - total_requests: number - /** The number of successful requests. */ - successful_requests: number - /** The number of failed requests. */ - failed_requests: number - /** The current request rate per minute. */ - requests_per_minute: number - /** The p50 latency in milliseconds, if available. */ - p50_latency_ms: number | null - /** The p95 latency in milliseconds, if available. */ - p95_latency_ms: number | null - /** The percentage rate of request failures. */ - error_rate: number - /** The number of streaming connection requests started. */ - streams_started: number - /** The number of streaming connections completed. */ - streams_completed: number - /** The number of streaming connections that failed. */ - streams_failed: number - /** The number of streaming connections disconnected by the client. */ - streams_client_disconnected: number - /** Requests where pxpipe text-to-image compression fired. */ - pxpipe_compressed_total: number - /** Total PNG image blocks pxpipe emitted. */ - pxpipe_images_total: number - /** Total source chars pxpipe replaced with images. */ - pxpipe_imaged_chars_total: number - /** Requests where RTK tool-output compression fired. */ - rtk_compressed_total: number - /** Total tool-result payloads RTK rewrote. */ - rtk_blocks_total: number - /** Total source chars RTK removed from tool output. */ - rtk_saved_chars_total: number -} - -/** A single request transaction log entry. */ -export interface RequestLogEntry { - /** Unique transaction ID. */ - request_id: string - /** ISO 8601 timestamp when the request was made. */ - timestamp: string - /** The backend model provider targeted. */ - backend: string - /** The model identifier requested by the client. */ - model_requested: string | null - /** The actual model identifier routed to on the backend. */ - model_mapped: string | null - /** HTTP response status code. */ - status_code: number - /** Request duration in milliseconds. */ - latency_ms: number - /** Number of input tokens processed. */ - input_tokens: number | null - /** Number of output tokens generated. */ - output_tokens: number | null - /** Whether the request was streamed. */ - is_streaming: boolean - /** The raw error message returned by the backend, if any. */ - error_message: string | null - /** Normalized error class/category. */ - error_kind: string | null - /** The virtual key ID used for authorization, if any. */ - key_id: number | null - /** Calculated transaction cost in USD. */ - cost_usd: number | null -} - -/** Paginated list response for request log queries. */ -export interface RequestsResponse { - /** The page of request log entries. */ - requests: RequestLogEntry[] - /** Maximum number of items returned. */ - limit: number - /** Pagination offset. */ - offset: number - /** True if more records are available. */ - has_more: boolean -} - -/** Represents a virtual API key configuration and its associated limits. */ -export interface VirtualKey { - /** Unique database ID. */ - id: number - /** The prefix of the key shown to users. */ - key_prefix: string - /** Optional descriptive note. */ - description: string | null - /** ISO 8601 creation timestamp. */ - created_at: string - /** ISO 8601 expiration timestamp, if set. */ - expires_at: string | null - /** ISO 8601 revocation timestamp, if revoked. */ - revoked_at: string | null - /** Spend limit in USD. */ - spend_limit: number | null - /** Monthly budget limit in USD. */ - max_budget_usd: number | null - /** Duration of the budget period (e.g. 'monthly'). */ - budget_duration: string | null - /** Requests-per-minute limit. */ - rpm_limit: number | null - /** Tokens-per-minute limit. */ - tpm_limit: number | null - /** Total spend in USD across the key lifetime. */ - total_spend: number - /** Total count of requests made with this key. */ - total_requests: number - /** Total count of tokens processed. */ - total_tokens: number - /** ISO 8601 reset timestamp for the current budget period. */ - period_reset_at: string | null - /** List of model names this key is restricted to, if any. */ - allowed_models: string[] | null - /** List of route names this key is restricted to, if any. */ - allowed_routes: string[] | null - /** Active status of the key. */ - status: 'active' | 'revoked' | 'expired' | 'override' - /** Spend in USD during the current budget period. */ - period_spend_usd: number -} - -/** Spent token and requests details for a virtual key. */ -export interface KeySpend { - /** Unique key database ID. */ - id: number - /** Total spend in USD. */ - total_spend: number - /** Total request count. */ - total_requests: number - /** Total token count. */ - total_tokens: number -} - -/** Represents a backend endpoint model and its metrics. */ -export interface Backend { - /** Name of the backend. */ - name: string - /** Model mapped for heavy workloads. */ - big_model: string - /** Model mapped for light workloads. */ - small_model: string - /** Request outcome counters. */ - metrics: { - requests_total: number - requests_success: number - requests_error: number - } -} - -/** Represents a single configuration entry key-value pair. */ -export interface ConfigEntry { - /** Configuration key. */ - key: string - /** Configuration value. */ - value: string - /** Timestamp when updated. */ - updated_at: string -} - -/** Represents the full system configuration and environment variables. */ -export interface ConfigResponse { - /** List of config database overrides. */ - entries: ConfigEntry[] - /** Server environment variables map. */ - env: Record - /** System logging level. */ - log_level: string - /** Whether requests and responses are logged. */ - log_bodies: boolean - /** Whether credentials are redacted from logs. */ - redact_secrets: boolean - /** Whether to attempt to repair broken thinking blocks in Anthropic streams. */ - anthropic_thinking_repair: boolean - /** True if pxpipe image compression is enabled. */ - pxpipe_compress: boolean - /** CSV of model bases in pxpipe compression scope. */ - pxpipe_models: string - /** Vision-capable Claude models offered as per-model scope toggles. */ - pxpipe_available_models: string[] - /** Whether RTK tool-output compression is active. */ - rtk_compress: boolean - /** CSV of model bases in RTK compression scope (empty = all models). */ - rtk_models: string - /** Whether to pass client authorization headers to backend. */ - forward_client_auth: boolean - /** Tool guardrail mode configured. */ - tool_guardrail_mode: string - /** Prompt-compression optimizer mode: 'off' | 'shadow' | 'live'. */ - optimizer_mode: string - /** Mappings of backend names to their configured big/small models. */ - backends: Record - /** List of keys whose overrides are active. */ - overridden_keys: string[] -} - -/** Status of the optional LLMLingua-2 ONNX model artifact (optimizer scorer tier). */ -export interface OptimizerModelStatus { - /** Proxy built with the `optimizer-onnx` feature. When false the tier is inert. */ - compiled_in: boolean - /** Verified model artifact is present on disk. */ - present: boolean - /** A download+verify is currently in flight. */ - downloading: boolean - /** Last download error, if any. */ - error: string | null - /** Pinned sha256 the artifact is verified against. */ - sha256: string - /** Expected download size in bytes. */ - size_bytes: number -} - -/** A time-series data point for observability charts. */ -export interface ObservabilityPoint { - /** UNIX timestamp representing the bucket start time. */ - bucket_start: number - /** Total requests in this bucket. */ - requests: number - /** Total errors in this bucket. */ - errors: number - /** Total input tokens processed in this bucket. */ - input_tokens: number - /** Total output tokens generated in this bucket. */ - output_tokens: number - /** Calculated cost in USD in this bucket. */ - cost_usd: number -} - -/** Summary of error occurrences on a backend. */ -export interface ObservabilityFailure { - /** Class or classification of error. */ - error_kind: string - /** Occurrence count. */ - count: number - /** Last occurrence timestamp. */ - last_seen: string - /** Last error message received. */ - last_message: string -} - -/** Represents a single trace event in the observability timeline. */ -export interface ObservabilityTimeline { - /** Transaction request ID. */ - request_id: string - /** ISO 8601 request timestamp. */ - timestamp: string - /** Backend provider routed to. */ - backend: string - /** Model mapped to. */ - model: string - /** Latency in milliseconds. */ - latency_ms: number - /** Request outcome status. */ - status: string -} - -/** Observability stats summary response. */ -export interface ObservabilityResponse { - /** The time window in hours for metrics. */ - window_hours: number - /** The name of the backend. */ - backend: string - /** Total requests within the window. */ - total_requests: number - /** Total errors within the window. */ - total_errors: number - /** Total input tokens processed. */ - total_input_tokens: number - /** Total output tokens generated. */ - total_output_tokens: number - /** Total cost in USD. */ - total_cost_usd: number - /** Historical metrics series points. */ - series: ObservabilityPoint[] - /** Error breakdown summary. */ - failures: ObservabilityFailure[] - /** Timeline events. */ - timeline: ObservabilityTimeline[] -} - -/** Represents a single model configuration entry. */ -export interface ModelEntry { - /** The unique name/identifier of the model. */ - model_name: string - /** The number of active deployments for this model. */ - deployments: number -} - -/** Response shape for models list requests. */ -export interface ModelsResponse { - /** List of model configurations. */ - models: ModelEntry[] - /** Router strategy (e.g. priority, failover). */ - strategy: string | null - /** Optional descriptive note. */ - note?: string -} - -/** Represents a model discovered from a backend API. */ -export interface DiscoveredModel { - /** The model identifier. */ - id: string - /** Optional human-readable name. */ - name: string | null -} - -/** Response containing discovered models. */ -export interface DiscoverResponse { - /** List of discovered models. */ - models: DiscoveredModel[] - /** Source or method used for discovery. */ - source: string - /** True if authorization was used. */ - auth_used: boolean -} - -/** A single audit log entry tracking administrative actions. */ -export interface AuditEntry { - /** Unique database ID. */ - id: number - /** ISO 8601 action timestamp. */ - timestamp: string - /** The action performed. */ - action: string - /** Type of target resource affected. */ - target_type: string - /** ID of target resource affected. */ - target_id: string | null - /** Detailed changes/payload. */ - detail: string | null - /** Source IP of the requester. */ - source_ip: string | null -} - -/** Paginated list response for audit log queries. */ -export interface AuditResponse { - /** List of audit log entries. */ - entries: AuditEntry[] - /** Maximum number of items returned. */ - limit: number - /** Pagination offset. */ - offset: number - /** True if more records are available. */ - has_more: boolean -} - -// --- Traffic tab (new) --- - -/** Real-time metrics for a specific API route. */ -export interface RouteMetrics { - /** The request path/route. */ - path: string - /** Number of requests per minute. */ - requests_per_min: number - /** Percentage rate of failures. */ - error_rate: number - /** Average latency in milliseconds. */ - avg_latency_ms: number - /** The p95 latency in milliseconds. */ - p95_latency_ms: number - /** Total number of requests. */ - total_requests: number -} - -/** Time-series requests count point for a route. */ -export interface TrafficSeriesPoint { - /** UNIX timestamp representing the bucket start time. */ - bucket_start: number - /** API path. */ - path: string - /** Request count. */ - requests: number -} - -/** Response containing traffic analytics. */ -export interface TrafficResponse { - /** Time window in hours. */ - window_hours: number - /** Metrics per route. */ - routes: RouteMetrics[] - /** Time-series data points. */ - series: TrafficSeriesPoint[] -} - -// --- Uptime tab (new) --- - -/** A single day's uptime status. */ -export interface HistoryDay { - /** Date in YYYY-MM-DD format. */ - date: string - /** Availability status. */ - status: 'up' | 'down' | 'degraded' | 'no-data' -} - -/** Uptime info for the proxy itself. */ -export interface ProxyUptimeInfo { - /** UNIX timestamp when the proxy started. */ - started_at: number - /** Uptime percentage over the last 30 days. */ - uptime_pct_30d: number - /** Daily uptime history. */ - history: HistoryDay[] -} - -/** Uptime info for a backend endpoint. */ -export interface BackendUptimeInfo { - /** Name of the backend. */ - name: string - /** Current connection status. */ - status: 'up' | 'down' | 'unknown' - /** UNIX timestamp of the last health check. */ - last_checked_at: number | null - /** Last checked latency in milliseconds. */ - last_latency_ms: number | null - /** 30-day uptime percentage. */ - uptime_pct_30d: number - /** Daily uptime history. */ - history: HistoryDay[] -} - -/** Full uptime summary response. */ -export interface UptimeResponse { - /** Proxy server uptime details. */ - proxy: ProxyUptimeInfo - /** Backend endpoints uptime details. */ - backends: BackendUptimeInfo[] -} - -// --- Env file import / export --- - -/** Warning message generated during env import. */ -export interface EnvWarning { - /** Affected line number. */ - line: number | null - /** Affected environment variable key. */ - key: string | null - /** Warning message. */ - message: string -} - -/** Response detailing the result of importing an env file. */ -export interface EnvImportResponse { - /** Count of imported variables applied. */ - applied: number - /** Non-fatal warnings generated. */ - warnings: EnvWarning[] -} - -/** Error payload for env file imports. */ -export interface EnvImportError { - /** Hard/blocking validation errors. */ - hard_errors: string[] - /** Non-fatal warnings. */ - warnings: EnvWarning[] -} - -// --- Provider catalog --- - -/** Details of a provider available in the LiteLLM catalog. */ -export interface CatalogProvider { - /** Unique provider identifier. */ - id: string - /** User-facing display name. */ - display_name: string - /** Keep for backwards compatibility. */ - name?: string - /** Communication protocol. */ - protocol: string - /** Auth mechanism. */ - auth: string - /** Proxy integration status. */ - status: 'implemented' | 'wired' | 'stub' - /** Default base URL. */ - default_base_url: string - /** Expected environment variables for API keys. */ - env_vars: string[] - /** LiteLLM prefix string. */ - litellm_prefix: string - /** Supported capabilities. */ - capabilities: { - chat_completions: boolean - streaming: boolean - tool_use: boolean - embeddings: boolean - vision: boolean - batch: boolean - } - /** Total count of models. */ - model_count: number - /** Count of models currently cached. */ - cached_model_count: number - /** UNIX timestamp of the last cache refresh. */ - last_refreshed: number | null -} - -// --- Managed backends --- - -/** Represents a backend credentials deployment managed by the admin. */ -export interface ManagedBackend { - /** Unique database ID. */ - id: string - /** Unique backend name. */ - name: string - /** Provider ID. */ - provider_id: string - /** True if the API key is configured. */ - api_key_set: boolean - /** True if AWS credentials are set (for AWS Bedrock, etc.). */ - aws_creds_set: boolean - /** Base URL of the API endpoint. */ - api_base: string | null - /** Optional deployment name (e.g. Azure deployment name). */ - deployment: string | null - /** Optional API version. */ - api_version: string | null - /** Optional cloud project ID. */ - project: string | null - /** Optional cloud region (e.g. AWS/Azure region). */ - region: string | null - /** Rate limit: requests per minute override. */ - rpm: number | null - /** Rate limit: tokens per minute override. */ - tpm: number | null - /** True if this backend is enabled. */ - enabled: boolean - /** ISO 8601 creation timestamp. */ - created_at: string - /** ISO 8601 last update timestamp. */ - updated_at: string -} - -/** Response containing managed backends. */ -export interface ManagedBackendsResponse { - /** List of managed backends. */ - backends: ManagedBackend[] -} - -/** Request payload for creating a new managed backend. */ -export interface CreateManagedBackendRequest { - /** Unique name for the backend. */ - name: string - /** Catalog provider ID. */ - provider_id: string - /** API key credential string. */ - api_key?: string - /** API base URL. */ - api_base?: string - /** API deployment name. */ - deployment?: string - /** API version. */ - api_version?: string - /** Cloud project ID. */ - project?: string - /** Cloud region. */ - region?: string - /** AWS access key ID. */ - aws_access_key_id?: string - /** AWS secret access key. */ - aws_secret_access_key?: string - /** AWS session token. */ - aws_session_token?: string - /** Requests per minute limit. */ - rpm?: number - /** Tokens per minute limit. */ - tpm?: number - /** Active status toggle. */ - enabled?: boolean -} - -/** Request payload for updating an existing managed backend. */ -export type UpdateManagedBackendRequest = Partial> - -// --- Routes --- - -/** Represents a configured API routing definition. */ -export interface Route { - /** Unique route database ID. */ - id: string - /** Route name. */ - name: string - /** Optional description. */ - description: string | null - /** Load balancing strategy. */ - strategy: string - /** Requests per minute limit. */ - rpm: number | null - /** Tokens per minute limit. */ - tpm: number | null - /** Cost budget in USD. */ - budget_usd: number | null - /** Active status toggle. */ - enabled: boolean - /** Tool guardrail mode override. */ - guardrail_mode: string | null - /** pxpipe image compression toggle override. */ - pxpipe_compress: boolean | null - /** pxpipe models CSV override. */ - pxpipe_models: string | null - /** Secret redaction toggle override. */ - redact_secrets: boolean | null - /** Position order in the router. */ - position: number - /** Count of providers assigned to this route. */ - provider_count: number - /** ISO 8601 creation timestamp. */ - created_at: string - /** ISO 8601 last update timestamp. */ - updated_at: string -} - -/** Response containing routing configs. */ -export interface RoutesResponse { - /** List of routes. */ - routes: Route[] -} - -/** Request payload to create a new route. */ -export interface CreateRouteRequest { - /** Unique route name. */ - name: string - /** Route description. */ - description?: string - /** Load balancing strategy. */ - strategy?: string - /** Requests per minute limit. */ - rpm?: number - /** Tokens per minute limit. */ - tpm?: number - /** Cost budget in USD. */ - budget_usd?: number - /** Active status toggle. */ - enabled?: boolean - /** Tool guardrail mode override. */ - guardrail_mode?: string | null - /** pxpipe image compression toggle override. */ - pxpipe_compress?: boolean | null - /** pxpipe models CSV override. */ - pxpipe_models?: string | null - /** Secret redaction toggle override. */ - redact_secrets?: boolean | null - /** Position order. */ - position?: number -} - -/** Request payload to update an existing route. */ -export type UpdateRouteRequest = Partial - -/** Represents a provider mapped to a route. */ -export interface RouteProvider { - /** Unique assignment ID. */ - id: string - /** Target route ID. */ - route_id: string - /** Backend ID. */ - backend_id: string - /** Name of the backend. */ - backend_name: string - /** Catalog provider ID. */ - provider_id: string - /** Supported models list. */ - models: string[] - /** Evaluation priority. */ - priority: number - /** Active status. */ - enabled: boolean -} - -/** Response containing route provider mappings. */ -export interface RouteProvidersResponse { - /** List of mappings. */ - providers: RouteProvider[] -} - -/** Request payload to add a provider assignment to a route. */ -export interface AddRouteProviderRequest { - /** Target backend ID. */ - backend_id: string - /** Supported models list. */ - models?: string[] - /** Priority position. */ - priority?: number - /** Active status. */ - enabled?: boolean -} - -/** Request payload to update a provider assignment configuration. */ -export interface UpdateRouteProviderRequest { - /** Supported models list. */ - models?: string[] - /** Priority position. */ - priority?: number - /** Active status. */ - enabled?: boolean -} - -/** Request payload to reorder provider mappings. */ -export interface ReorderRouteProvidersRequest { - /** Ordered list of provider assignment IDs. */ - provider_ids: string[] -} - -// --- WebSocket events --- - -/** Types of events sent over the admin WebSocket channel. */ -export type WSEvent = - | { type: 'request_completed'; data: RequestLogEntry } - | { type: 'metrics_snapshot'; data: Metrics } - | { type: 'config_changed'; data: { key: string; value: string } } - | { type: 'backend_health_changed'; data: { backend: string; status: 'up' | 'down'; latency_ms: number | null } } - -/** Details of a model available in the LiteLLM catalog. */ -export interface CatalogModel { - /** Unique model identifier. */ - id: string - /** Maximum context window size in tokens. */ - context_window: number - /** Maximum output tokens. */ - max_output_tokens: number - /** Availability status of the model. */ - status: 'available' | 'deprecated' | 'stub' - /** Capabilities flags. */ - capabilities: { - streaming: boolean - tool_use: boolean - vision: boolean - extended_thinking: boolean - } - /** Pricing per million tokens. */ - pricing: { - input_per_million_tokens: number - output_per_million_tokens: number - } | null -} - -/** Response containing model lists. */ -export interface CatalogModelsResponse { - /** Catalog provider ID. */ - provider_id: string - /** True if this provider has models. */ - has_models: boolean - /** List of models. */ - models: CatalogModel[] -} +export * from './types/proxy' +export * from './types/metrics' +export * from './types/requests' +export * from './types/keys' +export * from './types/backends' +export * from './types/config' +export * from './types/optimizer' +export * from './types/models' +export * from './types/audit' +export * from './types/uptime' +export * from './types/env' +export * from './types/catalog' +export * from './types/routes' +export * from './types/ws' diff --git a/crates/proxy/admin-ui/src/api/types/audit.ts b/crates/proxy/admin-ui/src/api/types/audit.ts new file mode 100644 index 0000000..6e8995e --- /dev/null +++ b/crates/proxy/admin-ui/src/api/types/audit.ts @@ -0,0 +1,29 @@ +/** A single audit log entry tracking administrative actions. */ +export interface AuditEntry { + /** Unique database ID. */ + id: number + /** ISO 8601 action timestamp. */ + timestamp: string + /** The action performed. */ + action: string + /** Type of target resource affected. */ + target_type: string + /** ID of target resource affected. */ + target_id: string | null + /** Detailed changes/payload. */ + detail: string | null + /** Source IP of the requester. */ + source_ip: string | null +} + +/** Paginated list response for audit log queries. */ +export interface AuditResponse { + /** List of audit log entries. */ + entries: AuditEntry[] + /** Maximum number of items returned. */ + limit: number + /** Pagination offset. */ + offset: number + /** True if more records are available. */ + has_more: boolean +} diff --git a/crates/proxy/admin-ui/src/api/types/backends.ts b/crates/proxy/admin-ui/src/api/types/backends.ts new file mode 100644 index 0000000..35204cc --- /dev/null +++ b/crates/proxy/admin-ui/src/api/types/backends.ts @@ -0,0 +1,90 @@ +/** Represents a backend endpoint model and its metrics. */ +export interface Backend { + /** Name of the backend. */ + name: string + /** Model mapped for heavy workloads. */ + big_model: string + /** Model mapped for light workloads. */ + small_model: string + /** Request outcome counters. */ + metrics: { + requests_total: number + requests_success: number + requests_error: number + } +} + +/** Represents a backend credentials deployment managed by the admin. */ +export interface ManagedBackend { + /** Unique database ID. */ + id: string + /** Unique backend name. */ + name: string + /** Provider ID. */ + provider_id: string + /** True if the API key is configured. */ + api_key_set: boolean + /** True if AWS credentials are set (for AWS Bedrock, etc.). */ + aws_creds_set: boolean + /** Base URL of the API endpoint. */ + api_base: string | null + /** Optional deployment name (e.g. Azure deployment name). */ + deployment: string | null + /** Optional API version. */ + api_version: string | null + /** Optional cloud project ID. */ + project: string | null + /** Optional cloud region (e.g. AWS/Azure region). */ + region: string | null + /** Rate limit: requests per minute override. */ + rpm: number | null + /** Rate limit: tokens per minute override. */ + tpm: number | null + /** True if this backend is enabled. */ + enabled: boolean + /** ISO 8601 creation timestamp. */ + created_at: string + /** ISO 8601 last update timestamp. */ + updated_at: string +} + +/** Response containing managed backends. */ +export interface ManagedBackendsResponse { + /** List of managed backends. */ + backends: ManagedBackend[] +} + +/** Request payload for creating a new managed backend. */ +export interface CreateManagedBackendRequest { + /** Unique name for the backend. */ + name: string + /** Catalog provider ID. */ + provider_id: string + /** API key credential string. */ + api_key?: string + /** API base URL. */ + api_base?: string + /** API deployment name. */ + deployment?: string + /** API version. */ + api_version?: string + /** Cloud project ID. */ + project?: string + /** Cloud region. */ + region?: string + /** AWS access key ID. */ + aws_access_key_id?: string + /** AWS secret access key. */ + aws_secret_access_key?: string + /** AWS session token. */ + aws_session_token?: string + /** Requests per minute limit. */ + rpm?: number + /** Tokens per minute limit. */ + tpm?: number + /** Active status toggle. */ + enabled?: boolean +} + +/** Request payload for updating an existing managed backend. */ +export type UpdateManagedBackendRequest = Partial> diff --git a/crates/proxy/admin-ui/src/api/types/catalog.ts b/crates/proxy/admin-ui/src/api/types/catalog.ts new file mode 100644 index 0000000..5007e1d --- /dev/null +++ b/crates/proxy/admin-ui/src/api/types/catalog.ts @@ -0,0 +1,36 @@ +/** Details of a provider available in the LiteLLM catalog. */ +export interface CatalogProvider { + /** Unique provider identifier. */ + id: string + /** User-facing display name. */ + display_name: string + /** Keep for backwards compatibility. */ + name?: string + /** Communication protocol. */ + protocol: string + /** Auth mechanism. */ + auth: string + /** Proxy integration status. */ + status: 'implemented' | 'wired' | 'stub' + /** Default base URL. */ + default_base_url: string + /** Expected environment variables for API keys. */ + env_vars: string[] + /** LiteLLM prefix string. */ + litellm_prefix: string + /** Supported capabilities. */ + capabilities: { + chat_completions: boolean + streaming: boolean + tool_use: boolean + embeddings: boolean + vision: boolean + batch: boolean + } + /** Total count of models. */ + model_count: number + /** Count of models currently cached. */ + cached_model_count: number + /** UNIX timestamp of the last cache refresh. */ + last_refreshed: number | null +} diff --git a/crates/proxy/admin-ui/src/api/types/config.ts b/crates/proxy/admin-ui/src/api/types/config.ts new file mode 100644 index 0000000..0dfb5a7 --- /dev/null +++ b/crates/proxy/admin-ui/src/api/types/config.ts @@ -0,0 +1,45 @@ +/** Represents a single configuration entry key-value pair. */ +export interface ConfigEntry { + /** Configuration key. */ + key: string + /** Configuration value. */ + value: string + /** Timestamp when updated. */ + updated_at: string +} + +/** Represents the full system configuration and environment variables. */ +export interface ConfigResponse { + /** List of config database overrides. */ + entries: ConfigEntry[] + /** Server environment variables map. */ + env: Record + /** System logging level. */ + log_level: string + /** Whether requests and responses are logged. */ + log_bodies: boolean + /** Whether credentials are redacted from logs. */ + redact_secrets: boolean + /** Whether to attempt to repair broken thinking blocks in Anthropic streams. */ + anthropic_thinking_repair: boolean + /** True if pxpipe image compression is enabled. */ + pxpipe_compress: boolean + /** CSV of model bases in pxpipe compression scope. */ + pxpipe_models: string + /** Vision-capable Claude models offered as per-model scope toggles. */ + pxpipe_available_models: string[] + /** Whether RTK tool-output compression is active. */ + rtk_compress: boolean + /** CSV of model bases in RTK compression scope (empty = all models). */ + rtk_models: string + /** Whether to pass client authorization headers to backend. */ + forward_client_auth: boolean + /** Tool guardrail mode configured. */ + tool_guardrail_mode: string + /** Prompt-compression optimizer mode: 'off' | 'shadow' | 'live'. */ + optimizer_mode: string + /** Mappings of backend names to their configured big/small models. */ + backends: Record + /** List of keys whose overrides are active. */ + overridden_keys: string[] +} diff --git a/crates/proxy/admin-ui/src/api/types/env.ts b/crates/proxy/admin-ui/src/api/types/env.ts new file mode 100644 index 0000000..8f201c1 --- /dev/null +++ b/crates/proxy/admin-ui/src/api/types/env.ts @@ -0,0 +1,25 @@ +/** Warning message generated during env import. */ +export interface EnvWarning { + /** Affected line number. */ + line: number | null + /** Affected environment variable key. */ + key: string | null + /** Warning message. */ + message: string +} + +/** Response detailing the result of importing an env file. */ +export interface EnvImportResponse { + /** Count of imported variables applied. */ + applied: number + /** Non-fatal warnings generated. */ + warnings: EnvWarning[] +} + +/** Error payload for env file imports. */ +export interface EnvImportError { + /** Hard/blocking validation errors. */ + hard_errors: string[] + /** Non-fatal warnings. */ + warnings: EnvWarning[] +} diff --git a/crates/proxy/admin-ui/src/api/types/keys.ts b/crates/proxy/admin-ui/src/api/types/keys.ts new file mode 100644 index 0000000..a1d6ed7 --- /dev/null +++ b/crates/proxy/admin-ui/src/api/types/keys.ts @@ -0,0 +1,53 @@ +/** Represents a virtual API key configuration and its associated limits. */ +export interface VirtualKey { + /** Unique database ID. */ + id: number + /** The prefix of the key shown to users. */ + key_prefix: string + /** Optional descriptive note. */ + description: string | null + /** ISO 8601 creation timestamp. */ + created_at: string + /** ISO 8601 expiration timestamp, if set. */ + expires_at: string | null + /** ISO 8601 revocation timestamp, if revoked. */ + revoked_at: string | null + /** Spend limit in USD. */ + spend_limit: number | null + /** Monthly budget limit in USD. */ + max_budget_usd: number | null + /** Duration of the budget period (e.g. 'monthly'). */ + budget_duration: string | null + /** Requests-per-minute limit. */ + rpm_limit: number | null + /** Tokens-per-minute limit. */ + tpm_limit: number | null + /** Total spend in USD across the key lifetime. */ + total_spend: number + /** Total count of requests made with this key. */ + total_requests: number + /** Total count of tokens processed. */ + total_tokens: number + /** ISO 8601 reset timestamp for the current budget period. */ + period_reset_at: string | null + /** List of model names this key is restricted to, if any. */ + allowed_models: string[] | null + /** List of route names this key is restricted to, if any. */ + allowed_routes: string[] | null + /** Active status of the key. */ + status: 'active' | 'revoked' | 'expired' | 'override' + /** Spend in USD during the current budget period. */ + period_spend_usd: number +} + +/** Spent token and requests details for a virtual key. */ +export interface KeySpend { + /** Unique key database ID. */ + id: number + /** Total spend in USD. */ + total_spend: number + /** Total request count. */ + total_requests: number + /** Total token count. */ + total_tokens: number +} diff --git a/crates/proxy/admin-ui/src/api/types/metrics.ts b/crates/proxy/admin-ui/src/api/types/metrics.ts new file mode 100644 index 0000000..3b51a99 --- /dev/null +++ b/crates/proxy/admin-ui/src/api/types/metrics.ts @@ -0,0 +1,141 @@ +/** Represents real-time usage and performance metrics for the proxy. */ +export interface Metrics { + /** The total number of requests received by the proxy. */ + total_requests: number + /** The number of successful requests. */ + successful_requests: number + /** The number of failed requests. */ + failed_requests: number + /** The current request rate per minute. */ + requests_per_minute: number + /** The p50 latency in milliseconds, if available. */ + p50_latency_ms: number | null + /** The p95 latency in milliseconds, if available. */ + p95_latency_ms: number | null + /** The percentage rate of request failures. */ + error_rate: number + /** The number of streaming connection requests started. */ + streams_started: number + /** The number of streaming connections completed. */ + streams_completed: number + /** The number of streaming connections that failed. */ + streams_failed: number + /** The number of streaming connections disconnected by the client. */ + streams_client_disconnected: number + /** Requests where pxpipe text-to-image compression fired. */ + pxpipe_compressed_total: number + /** Total PNG image blocks pxpipe emitted. */ + pxpipe_images_total: number + /** Total source chars pxpipe replaced with images. */ + pxpipe_imaged_chars_total: number + /** Requests where RTK tool-output compression fired. */ + rtk_compressed_total: number + /** Total tool-result payloads RTK rewrote. */ + rtk_blocks_total: number + /** Total source chars RTK removed from tool output. */ + rtk_saved_chars_total: number +} + +/** A time-series data point for observability charts. */ +export interface ObservabilityPoint { + /** UNIX timestamp representing the bucket start time. */ + bucket_start: number + /** Total requests in this bucket. */ + requests: number + /** Total errors in this bucket. */ + errors: number + /** Total input tokens processed in this bucket. */ + input_tokens: number + /** Total output tokens generated in this bucket. */ + output_tokens: number + /** Calculated cost in USD in this bucket. */ + cost_usd: number +} + +/** Summary of error occurrences on a backend. */ +export interface ObservabilityFailure { + /** Class or classification of error. */ + error_kind: string + /** Occurrence count. */ + count: number + /** Last occurrence timestamp. */ + last_seen: string + /** Last error message received. */ + last_message: string +} + +/** Represents a single trace event in the observability timeline. */ +export interface ObservabilityTimeline { + /** Transaction request ID. */ + request_id: string + /** ISO 8601 request timestamp. */ + timestamp: string + /** Backend provider routed to. */ + backend: string + /** Model mapped to. */ + model: string + /** Latency in milliseconds. */ + latency_ms: number + /** Request outcome status. */ + status: string +} + +/** Observability stats summary response. */ +export interface ObservabilityResponse { + /** The time window in hours for metrics. */ + window_hours: number + /** The name of the backend. */ + backend: string + /** Total requests within the window. */ + total_requests: number + /** Total errors within the window. */ + total_errors: number + /** Total input tokens processed. */ + total_input_tokens: number + /** Total output tokens generated. */ + total_output_tokens: number + /** Total cost in USD. */ + total_cost_usd: number + /** Historical metrics series points. */ + series: ObservabilityPoint[] + /** Error breakdown summary. */ + failures: ObservabilityFailure[] + /** Timeline events. */ + timeline: ObservabilityTimeline[] +} + +/** Real-time metrics for a specific API route. */ +export interface RouteMetrics { + /** The request path/route. */ + path: string + /** Number of requests per minute. */ + requests_per_min: number + /** Percentage rate of failures. */ + error_rate: number + /** Average latency in milliseconds. */ + avg_latency_ms: number + /** The p95 latency in milliseconds. */ + p95_latency_ms: number + /** Total number of requests. */ + total_requests: number +} + +/** Time-series requests count point for a route. */ +export interface TrafficSeriesPoint { + /** UNIX timestamp representing the bucket start time. */ + bucket_start: number + /** API path. */ + path: string + /** Request count. */ + requests: number +} + +/** Response containing traffic analytics. */ +export interface TrafficResponse { + /** Time window in hours. */ + window_hours: number + /** Metrics per route. */ + routes: RouteMetrics[] + /** Time-series data points. */ + series: TrafficSeriesPoint[] +} diff --git a/crates/proxy/admin-ui/src/api/types/models.ts b/crates/proxy/admin-ui/src/api/types/models.ts new file mode 100644 index 0000000..370b142 --- /dev/null +++ b/crates/proxy/admin-ui/src/api/types/models.ts @@ -0,0 +1,69 @@ +/** Represents a single model configuration entry. */ +export interface ModelEntry { + /** The unique name/identifier of the model. */ + model_name: string + /** The number of active deployments for this model. */ + deployments: number +} + +/** Response shape for models list requests. */ +export interface ModelsResponse { + /** List of model configurations. */ + models: ModelEntry[] + /** Router strategy (e.g. priority, failover). */ + strategy: string | null + /** Optional descriptive note. */ + note?: string +} + +/** Represents a model discovered from a backend API. */ +export interface DiscoveredModel { + /** The model identifier. */ + id: string + /** Optional human-readable name. */ + name: string | null +} + +/** Response containing discovered models. */ +export interface DiscoverResponse { + /** List of discovered models. */ + models: DiscoveredModel[] + /** Source or method used for discovery. */ + source: string + /** True if authorization was used. */ + auth_used: boolean +} + +/** Details of a model available in the LiteLLM catalog. */ +export interface CatalogModel { + /** Unique model identifier. */ + id: string + /** Maximum context window size in tokens. */ + context_window: number + /** Maximum output tokens. */ + max_output_tokens: number + /** Availability status of the model. */ + status: 'available' | 'deprecated' | 'stub' + /** Capabilities flags. */ + capabilities: { + streaming: boolean + tool_use: boolean + vision: boolean + extended_thinking: boolean + } + /** Pricing per million tokens. */ + pricing: { + input_per_million_tokens: number + output_per_million_tokens: number + } | null +} + +/** Response containing model lists. */ +export interface CatalogModelsResponse { + /** Catalog provider ID. */ + provider_id: string + /** True if this provider has models. */ + has_models: boolean + /** List of models. */ + models: CatalogModel[] +} diff --git a/crates/proxy/admin-ui/src/api/types/optimizer.ts b/crates/proxy/admin-ui/src/api/types/optimizer.ts new file mode 100644 index 0000000..374a504 --- /dev/null +++ b/crates/proxy/admin-ui/src/api/types/optimizer.ts @@ -0,0 +1,15 @@ +/** Status of the optional LLMLingua-2 ONNX model artifact (optimizer scorer tier). */ +export interface OptimizerModelStatus { + /** Proxy built with the `optimizer-onnx` feature. When false the tier is inert. */ + compiled_in: boolean + /** Verified model artifact is present on disk. */ + present: boolean + /** A download+verify is currently in flight. */ + downloading: boolean + /** Last download error, if any. */ + error: string | null + /** Pinned sha256 the artifact is verified against. */ + sha256: string + /** Expected download size in bytes. */ + size_bytes: number +} diff --git a/crates/proxy/admin-ui/src/api/types/proxy.ts b/crates/proxy/admin-ui/src/api/types/proxy.ts new file mode 100644 index 0000000..26e7c5a --- /dev/null +++ b/crates/proxy/admin-ui/src/api/types/proxy.ts @@ -0,0 +1,9 @@ +/** Represents the status and configuration details of the proxy server. */ +export interface ProxyStatus { + /** True if the proxy backend has been configured. */ + configured: boolean + /** The port number the proxy listens on for client requests. */ + proxy_port: number + /** Whether the proxy is currently running and accepting connections. */ + proxy_running: boolean +} diff --git a/crates/proxy/admin-ui/src/api/types/requests.ts b/crates/proxy/admin-ui/src/api/types/requests.ts new file mode 100644 index 0000000..ef7960a --- /dev/null +++ b/crates/proxy/admin-ui/src/api/types/requests.ts @@ -0,0 +1,43 @@ +/** A single request transaction log entry. */ +export interface RequestLogEntry { + /** Unique transaction ID. */ + request_id: string + /** ISO 8601 timestamp when the request was made. */ + timestamp: string + /** The backend model provider targeted. */ + backend: string + /** The model identifier requested by the client. */ + model_requested: string | null + /** The actual model identifier routed to on the backend. */ + model_mapped: string | null + /** HTTP response status code. */ + status_code: number + /** Request duration in milliseconds. */ + latency_ms: number + /** Number of input tokens processed. */ + input_tokens: number | null + /** Number of output tokens generated. */ + output_tokens: number | null + /** Whether the request was streamed. */ + is_streaming: boolean + /** The raw error message returned by the backend, if any. */ + error_message: string | null + /** Normalized error class/category. */ + error_kind: string | null + /** The virtual key ID used for authorization, if any. */ + key_id: number | null + /** Calculated transaction cost in USD. */ + cost_usd: number | null +} + +/** Paginated list response for request log queries. */ +export interface RequestsResponse { + /** The page of request log entries. */ + requests: RequestLogEntry[] + /** Maximum number of items returned. */ + limit: number + /** Pagination offset. */ + offset: number + /** True if more records are available. */ + has_more: boolean +} diff --git a/crates/proxy/admin-ui/src/api/types/routes.ts b/crates/proxy/admin-ui/src/api/types/routes.ts new file mode 100644 index 0000000..e3c2844 --- /dev/null +++ b/crates/proxy/admin-ui/src/api/types/routes.ts @@ -0,0 +1,126 @@ +/** Represents a configured API routing definition. */ +export interface Route { + /** Unique route database ID. */ + id: string + /** Route name. */ + name: string + /** Optional description. */ + description: string | null + /** Load balancing strategy. */ + strategy: string + /** Requests per minute limit. */ + rpm: number | null + /** Tokens per minute limit. */ + tpm: number | null + /** Cost budget in USD. */ + budget_usd: number | null + /** Active status toggle. */ + enabled: boolean + /** Tool guardrail mode override. */ + guardrail_mode: string | null + /** pxpipe image compression toggle override. */ + pxpipe_compress: boolean | null + /** pxpipe models CSV override. */ + pxpipe_models: string | null + /** Secret redaction toggle override. */ + redact_secrets: boolean | null + /** Position order in the router. */ + position: number + /** Count of providers assigned to this route. */ + provider_count: number + /** ISO 8601 creation timestamp. */ + created_at: string + /** ISO 8601 last update timestamp. */ + updated_at: string +} + +/** Response containing routing configs. */ +export interface RoutesResponse { + /** List of routes. */ + routes: Route[] +} + +/** Request payload to create a new route. */ +export interface CreateRouteRequest { + /** Unique route name. */ + name: string + /** Route description. */ + description?: string + /** Load balancing strategy. */ + strategy?: string + /** Requests per minute limit. */ + rpm?: number + /** Tokens per minute limit. */ + tpm?: number + /** Cost budget in USD. */ + budget_usd?: number + /** Active status toggle. */ + enabled?: boolean + /** Tool guardrail mode override. */ + guardrail_mode?: string | null + /** pxpipe image compression toggle override. */ + pxpipe_compress?: boolean | null + /** pxpipe models CSV override. */ + pxpipe_models?: string | null + /** Secret redaction toggle override. */ + redact_secrets?: boolean | null + /** Position order. */ + position?: number +} + +/** Request payload to update an existing route. */ +export type UpdateRouteRequest = Partial + +/** Represents a provider mapped to a route. */ +export interface RouteProvider { + /** Unique assignment ID. */ + id: string + /** Target route ID. */ + route_id: string + /** Backend ID. */ + backend_id: string + /** Name of the backend. */ + backend_name: string + /** Catalog provider ID. */ + provider_id: string + /** Supported models list. */ + models: string[] + /** Evaluation priority. */ + priority: number + /** Active status. */ + enabled: boolean +} + +/** Response containing route provider mappings. */ +export interface RouteProvidersResponse { + /** List of mappings. */ + providers: RouteProvider[] +} + +/** Request payload to add a provider assignment to a route. */ +export interface AddRouteProviderRequest { + /** Target backend ID. */ + backend_id: string + /** Supported models list. */ + models?: string[] + /** Priority position. */ + priority?: number + /** Active status. */ + enabled?: boolean +} + +/** Request payload to update a provider assignment configuration. */ +export interface UpdateRouteProviderRequest { + /** Supported models list. */ + models?: string[] + /** Priority position. */ + priority?: number + /** Active status. */ + enabled?: boolean +} + +/** Request payload to reorder provider mappings. */ +export interface ReorderRouteProvidersRequest { + /** Ordered list of provider assignment IDs. */ + provider_ids: string[] +} diff --git a/crates/proxy/admin-ui/src/api/types/uptime.ts b/crates/proxy/admin-ui/src/api/types/uptime.ts new file mode 100644 index 0000000..39478b5 --- /dev/null +++ b/crates/proxy/admin-ui/src/api/types/uptime.ts @@ -0,0 +1,41 @@ +/** A single day's uptime status. */ +export interface HistoryDay { + /** Date in YYYY-MM-DD format. */ + date: string + /** Availability status. */ + status: 'up' | 'down' | 'degraded' | 'no-data' +} + +/** Uptime info for the proxy itself. */ +export interface ProxyUptimeInfo { + /** UNIX timestamp when the proxy started. */ + started_at: number + /** Uptime percentage over the last 30 days. */ + uptime_pct_30d: number + /** Daily uptime history. */ + history: HistoryDay[] +} + +/** Uptime info for a backend endpoint. */ +export interface BackendUptimeInfo { + /** Name of the backend. */ + name: string + /** Current connection status. */ + status: 'up' | 'down' | 'unknown' + /** UNIX timestamp of the last health check. */ + last_checked_at: number | null + /** Last checked latency in milliseconds. */ + last_latency_ms: number | null + /** 30-day uptime percentage. */ + uptime_pct_30d: number + /** Daily uptime history. */ + history: HistoryDay[] +} + +/** Full uptime summary response. */ +export interface UptimeResponse { + /** Proxy server uptime details. */ + proxy: ProxyUptimeInfo + /** Backend endpoints uptime details. */ + backends: BackendUptimeInfo[] +} diff --git a/crates/proxy/admin-ui/src/api/types/ws.ts b/crates/proxy/admin-ui/src/api/types/ws.ts new file mode 100644 index 0000000..52c41c9 --- /dev/null +++ b/crates/proxy/admin-ui/src/api/types/ws.ts @@ -0,0 +1,9 @@ +import type { RequestLogEntry } from './requests' +import type { Metrics } from './metrics' + +/** Types of events sent over the admin WebSocket channel. */ +export type WSEvent = + | { type: 'request_completed'; data: RequestLogEntry } + | { type: 'metrics_snapshot'; data: Metrics } + | { type: 'config_changed'; data: { key: string; value: string } } + | { type: 'backend_health_changed'; data: { backend: string; status: 'up' | 'down'; latency_ms: number | null } } diff --git a/crates/proxy/admin-ui/src/tabs/settings/EnvFileSection.tsx b/crates/proxy/admin-ui/src/tabs/settings/EnvFileSection.tsx new file mode 100644 index 0000000..9d1f026 --- /dev/null +++ b/crates/proxy/admin-ui/src/tabs/settings/EnvFileSection.tsx @@ -0,0 +1,149 @@ +import { useRef, useState } from 'react' +import { useImportEnv, downloadEnvExport } from '../../api/queries' +import { AdminButton, AdminSurface } from '../../components/shared/Performative' +import type { EnvImportResponse, EnvImportError } from '../../api/types' + +const RESTART_KEY = 'env_import_pending_restart' + +function restartPending() { + return sessionStorage.getItem(RESTART_KEY) === '1' +} + +export default function EnvFileSection() { + const importEnv = useImportEnv() + const fileRef = useRef(null) + + const [importResult, setImportResult] = useState(null) + const [importError, setImportError] = useState(null) + const [exportError, setExportError] = useState(null) + const [showRestartBanner, setShowRestartBanner] = useState(restartPending) + + function handleFileChange(e: React.ChangeEvent) { + const file = e.target.files?.[0] + if (!file) return + setImportResult(null) + setImportError(null) + + importEnv.mutate(file, { + onSuccess(data) { + setImportResult(data) + sessionStorage.setItem(RESTART_KEY, '1') + setShowRestartBanner(true) + }, + onError(err) { + // Try to parse hard_errors from the response body + try { + const parsed = JSON.parse(err.message) as EnvImportError + if (parsed.hard_errors) { + setImportError(parsed) + return + } + } catch { + // fall through to generic error + } + setImportError({ hard_errors: [err.message], warnings: [] }) + }, + }) + + // Reset file input so the same file can be re-selected after fixing issues + if (fileRef.current) fileRef.current.value = '' + } + + async function handleExport() { + setExportError(null) + try { + await downloadEnvExport() + } catch (err) { + setExportError(err instanceof Error ? err.message : String(err)) + } + } + + function dismissRestartBanner() { + sessionStorage.removeItem(RESTART_KEY) + setShowRestartBanner(false) + } + + return ( +
+ {/* Restart-required banner — shown after a successful import */} + {showRestartBanner && ( + + Restart the proxy for imported env vars to take effect. + Dismiss + + )} + +
Env File
+
+ + fileRef.current?.click()} + disabled={importEnv.isPending} + loading={importEnv.isPending} + > + Import .anyllm.env + + + Export .anyllm.env + +
+ + {/* Import success */} + {importResult && ( +
+
+ {importResult.applied} variable{importResult.applied !== 1 ? 's' : ''} imported. + {importResult.warnings.length === 0 && ' No issues.'} +
+ {importResult.warnings.length > 0 && ( +
+
Warnings
+ {importResult.warnings.map((w, i) => ( +
+ {w.line != null && [line {w.line}] } + {w.key && {w.key}: } + {w.message} +
+ ))} +
+ )} +
+ )} + + {/* Import hard error */} + {importError && ( +
+
Import rejected
+ {importError.hard_errors.map((e, i) => ( +
{e}
+ ))} + {importError.warnings.length > 0 && ( + <> +
Warnings (from partial parse)
+ {importError.warnings.map((w, i) => ( +
+ {w.line != null && [line {w.line}] } + {w.message} +
+ ))} + + )} +
+ )} + + {/* Export error */} + {exportError && ( +
+ Export failed: {exportError} +
+ )} +
+ ) +} diff --git a/crates/proxy/admin-ui/src/tabs/settings/EnvVariablesSection.tsx b/crates/proxy/admin-ui/src/tabs/settings/EnvVariablesSection.tsx new file mode 100644 index 0000000..6efe03f --- /dev/null +++ b/crates/proxy/admin-ui/src/tabs/settings/EnvVariablesSection.tsx @@ -0,0 +1,23 @@ +import { Fragment } from 'react' + +interface EnvVariablesSectionProps { + envData: Record | undefined +} + +export default function EnvVariablesSection({ envData }: EnvVariablesSectionProps) { + if (!envData) return null + + return ( +
+
Environment
+
+ {Object.entries(envData).map(([k, v]) => ( + + {k} + {v} + + ))} +
+
+ ) +} diff --git a/crates/proxy/admin-ui/src/tabs/settings/GettingStartedNotice.tsx b/crates/proxy/admin-ui/src/tabs/settings/GettingStartedNotice.tsx new file mode 100644 index 0000000..fadc3c0 --- /dev/null +++ b/crates/proxy/admin-ui/src/tabs/settings/GettingStartedNotice.tsx @@ -0,0 +1,43 @@ +interface GettingStartedNoticeProps { + configured: boolean +} + +export default function GettingStartedNotice({ configured }: GettingStartedNoticeProps) { + if (configured) return null + + return ( +
+
No backend configured — nothing to forward requests to.
+
+ Add a backend on the Backends tab, or configure one via env. + The proxy needs a backend endpoint (where to forward) and a listen port (where to accept). + LISTEN_PORT defaults to 3000. Create a .anyllm.env and import it below, + or pass it at startup: anyllm-proxy --webui --env-file .anyllm.env +
+
+
+
OpenAI
+
+{`OPENAI_API_KEY=sk-...
+PROXY_API_KEYS=my-key`}
+          
+
+
+
Ollama / local LLM
+
+{`OPENAI_BASE_URL=http://localhost:11434/v1
+PROXY_OPEN_RELAY=true`}
+          
+
+
+
OpenRouter / custom
+
+{`OPENAI_BASE_URL=https://openrouter.ai/api/v1
+OPENAI_API_KEY=sk-or-...
+PROXY_API_KEYS=my-key`}
+          
+
+
+
+ ) +} diff --git a/crates/proxy/admin-ui/src/tabs/settings/RuntimeSettingsSection.tsx b/crates/proxy/admin-ui/src/tabs/settings/RuntimeSettingsSection.tsx new file mode 100644 index 0000000..6896074 --- /dev/null +++ b/crates/proxy/admin-ui/src/tabs/settings/RuntimeSettingsSection.tsx @@ -0,0 +1,376 @@ +import { useState } from 'react' +import { + useSaveConfig, useDeleteConfigOverride, + useOptimizerModel, useDownloadOptimizerModel, +} from '../../api/queries' +import ConfirmDialog from '../../components/shared/ConfirmDialog' +import { AdminButton } from '../../components/shared/Performative' +import type { ConfigResponse } from '../../api/types' + +function fmtMB(bytes: number): string { + return `${Math.round(bytes / 1_000_000)} MB` +} + +interface RuntimeSettingsSectionProps { + cfg: ConfigResponse +} + +export default function RuntimeSettingsSection({ cfg }: RuntimeSettingsSectionProps) { + const save = useSaveConfig() + const del = useDeleteConfigOverride() + const { data: model } = useOptimizerModel() + const downloadModel = useDownloadOptimizerModel() + + const [form, setForm] = useState>({}) + const [pendingReset, setPendingReset] = useState(null) + + /** Resets a config override key back to its default value. */ + function doReset() { + if (!pendingReset) return Promise.resolve() + const key = pendingReset + return del.mutateAsync(key).then(() => undefined) + } + + /** Saves a text configuration setting value. */ + function handleSave(key: string, currentValue: string) { + save.mutate({ [key]: form[key] ?? currentValue }) + } + + /** Saves a boolean configuration setting value. */ + function handleBooleanSave(key: string, value: boolean) { + save.mutate({ [key]: value }) + } + + // pxpipe model scope is a CSV of model bases; a model is "in scope" when any + // base is a substring of its id (mirrors the backend's model_in_scope). + function pxpipeScope(): string[] { + return (cfg.pxpipe_models ?? '').split(',').map((s) => s.trim()).filter(Boolean) + } + function pxpipeModelChecked(model: string): boolean { + const m = model.toLowerCase() + return pxpipeScope().some((base) => m.includes(base.toLowerCase())) + } + function togglePxpipeModel(model: string, on: boolean) { + const cur = pxpipeScope() + const next = on + ? (pxpipeModelChecked(model) ? cur : [...cur, model]) + : cur.filter((base) => !model.toLowerCase().includes(base.toLowerCase())) + save.mutate({ pxpipe_models: next.join(',') }) + } + + return ( +
+
Runtime
+
+ + {cfg.overridden_keys.includes('redact_secrets') && ( +
+ setPendingReset('redact_secrets')}> + Reset + +
+ )} +
+ +
+ + {cfg.overridden_keys.includes('log_bodies') && ( +
+ setPendingReset('log_bodies')}> + Reset + +
+ )} +
+ +
+ +
+ Repairs corrupted thinking/redacted_thinking blocks in Anthropic passthrough + requests (applies to any backend running in BACKEND=anthropic passthrough mode, + including a named backend in a multi-backend config). Off by default. +
+ {cfg.overridden_keys.includes('anthropic_thinking_repair') && ( +
+ setPendingReset('anthropic_thinking_repair')}> + Reset + +
+ )} +
+ +
+ +
+ Renders the stable system + tool-definition slab of Anthropic passthrough requests to a + PNG image block to save input tokens on vision models. Off by default. Enable per-model + below — only models that read imaged text reliably are offered. +
+ {cfg.overridden_keys.includes('pxpipe_compress') && ( +
+ setPendingReset('pxpipe_compress')}> + Reset + +
+ )} + {cfg.pxpipe_compress && ( +
+
Models in scope (vision-capable)
+ {cfg.pxpipe_available_models.length === 0 ? ( +
No vision-capable models in the catalog.
+ ) : ( +
+ {cfg.pxpipe_available_models.map((model) => ( + + ))} +
+ )} + {cfg.overridden_keys.includes('pxpipe_models') && ( +
+ setPendingReset('pxpipe_models')}> + Reset scope + +
+ )} +
+ )} +
+ +
+ +
+ Command-aware filtering of tool-result text (test/build/git/log output) using the RTK + filter catalog. Shrinks noisy machine output before it reaches the backend; deterministic + and cache-safe. Off by default. Applies to Anthropic passthrough and translate paths. +
+ {cfg.overridden_keys.includes('rtk_compress') && ( +
+ setPendingReset('rtk_compress')}> + Reset + +
+ )} + {cfg.rtk_compress && ( +
+
Models in scope (CSV, empty = all)
+ { + const v = e.target.value.trim() + if (v === (cfg.rtk_models ?? '')) return + save.mutate({ rtk_models: v }) + }} + /> + {cfg.overridden_keys.includes('rtk_models') && ( +
+ setPendingReset('rtk_models')}> + Reset scope + +
+ )} +
+ )} +
+ +
+ +
+ Forwards the client's own x-api-key/Authorization header upstream instead of the + operator's configured credential (BACKEND=anthropic passthrough only, single-key/BYOK + deployments). The proxy refuses to enable this with 2+ PROXY_API_KEYS entries and no + PROXY_OPEN_RELAY. Off by default. +
+ {cfg.overridden_keys.includes('forward_client_auth') && ( +
+ setPendingReset('forward_client_auth')}> + Reset + +
+ )} +
+ +
+ +
+ + {cfg.overridden_keys.includes('tool_guardrail_mode') && ( + setPendingReset('tool_guardrail_mode')}> + Reset + + )} +
+
+ Applies advisory guardrails to tool calls the proxy auto-executes. Disabled by default. +
+
+ +
+ +
+ + {cfg.overridden_keys.includes('optimizer_mode') && ( + setPendingReset('optimizer_mode')}> + Reset + + )} +
+
+ Frozen-Frontier compression of long conversation history (latest turn untouched). Off by default. +
+ + {model && !model.compiled_in && ( +
+ Heuristic scorer only. Rebuild the proxy with --features optimizer-onnx to enable the LLMLingua-2 ONNX scorer. +
+ )} + {model?.compiled_in && !model.present && !model.downloading && ( +
+ downloadModel.mutate()} + > + Download model ({fmtMB(model.size_bytes)}) + + + Required before enabling. Verified against a pinned sha256. + +
+ )} + {model?.downloading && ( +
+ Downloading and verifying model ({fmtMB(model.size_bytes)})… +
+ )} + {model?.error && !model.downloading && ( +
+ Download failed: {model.error} +
+ )} + {model?.compiled_in && model.present && ( +
+ ONNX scorer ready — live mode uses LLMLingua-2 (loaded on the next request). +
+ )} +
+ + {cfg.entries.filter((entry) => !['redact_secrets', 'log_bodies', 'anthropic_thinking_repair', 'pxpipe_compress', 'pxpipe_models', 'rtk_compress', 'rtk_models', 'forward_client_auth', 'tool_guardrail_mode', 'optimizer_mode'].includes(entry.key)).map((entry) => { + const inputId = `cfg-${entry.key}` + return ( +
+ +
+ setForm((f) => ({ ...f, [entry.key]: e.target.value }))} + /> + handleSave(entry.key, entry.value)}>Save + setPendingReset(entry.key)}>Reset +
+
+ ) + })} + + setPendingReset(null)} + onConfirm={doReset} + title="Reset override?" + message={ + <> + Reset override for {pendingReset}? The runtime value will revert + to the env-file or default. Active connections are not affected. + + } + confirmLabel="Reset" + variant="primary" + /> +
+ ) +} diff --git a/crates/proxy/admin-ui/src/tabs/settings/Settings.tsx b/crates/proxy/admin-ui/src/tabs/settings/Settings.tsx index 8059fe0..9be69fc 100644 --- a/crates/proxy/admin-ui/src/tabs/settings/Settings.tsx +++ b/crates/proxy/admin-ui/src/tabs/settings/Settings.tsx @@ -1,126 +1,19 @@ -import { Fragment, useRef, useState } from 'react' -import { - useConfig, useSaveConfig, useDeleteConfigOverride, useEnv, - useImportEnv, downloadEnvExport, useStatus, - useOptimizerModel, useDownloadOptimizerModel, -} from '../../api/queries' +import { useConfig, useEnv, useStatus } from '../../api/queries' import EmptyState from '../../components/shared/EmptyState' -import ConfirmDialog from '../../components/shared/ConfirmDialog' -import { AdminButton, AdminSurface } from '../../components/shared/Performative' -import type { EnvImportResponse, EnvImportError } from '../../api/types' - -const RESTART_KEY = 'env_import_pending_restart' - -function restartPending() { - return sessionStorage.getItem(RESTART_KEY) === '1' -} - -function fmtMB(bytes: number): string { - return `${Math.round(bytes / 1_000_000)} MB` -} +import GettingStartedNotice from './GettingStartedNotice' +import EnvFileSection from './EnvFileSection' +import RuntimeSettingsSection from './RuntimeSettingsSection' +import EnvVariablesSection from './EnvVariablesSection' /** * Settings Component. - * Provides controls for configuring system settings, environment variables export/import, - * and proxy properties. + * Coordinates and provides controls for configuring system settings, + * environment variables export/import, and proxy properties. */ export default function Settings({ configured = true }: { configured?: boolean }) { const { data: cfg, isLoading, error } = useConfig() - const { data: model } = useOptimizerModel() - const downloadModel = useDownloadOptimizerModel() const { data: envData } = useEnv() const { data: status } = useStatus() - const save = useSaveConfig() - const del = useDeleteConfigOverride() - const importEnv = useImportEnv() - const fileRef = useRef(null) - - const [form, setForm] = useState>({}) - const [importResult, setImportResult] = useState(null) - const [importError, setImportError] = useState(null) - const [exportError, setExportError] = useState(null) - const [showRestartBanner, setShowRestartBanner] = useState(restartPending) - const [pendingReset, setPendingReset] = useState(null) - - /** Resets a config override key back to its default value. */ - function doReset() { - if (!pendingReset) return Promise.resolve() - const key = pendingReset - return del.mutateAsync(key).then(() => undefined) - } - - /** Saves a text configuration setting value. */ - function handleSave(key: string, currentValue: string) { - save.mutate({ [key]: form[key] ?? currentValue }) - } - - /** Saves a boolean configuration setting value. */ - function handleBooleanSave(key: string, value: boolean) { - save.mutate({ [key]: value }) - } - - // pxpipe model scope is a CSV of model bases; a model is "in scope" when any - // base is a substring of its id (mirrors the backend's model_in_scope). - function pxpipeScope(): string[] { - return (cfg?.pxpipe_models ?? '').split(',').map((s) => s.trim()).filter(Boolean) - } - function pxpipeModelChecked(model: string): boolean { - const m = model.toLowerCase() - return pxpipeScope().some((base) => m.includes(base.toLowerCase())) - } - function togglePxpipeModel(model: string, on: boolean) { - const cur = pxpipeScope() - const next = on - ? (pxpipeModelChecked(model) ? cur : [...cur, model]) - : cur.filter((base) => !model.toLowerCase().includes(base.toLowerCase())) - save.mutate({ pxpipe_models: next.join(',') }) - } - - function handleFileChange(e: React.ChangeEvent) { - const file = e.target.files?.[0] - if (!file) return - setImportResult(null) - setImportError(null) - - importEnv.mutate(file, { - onSuccess(data) { - setImportResult(data) - sessionStorage.setItem(RESTART_KEY, '1') - setShowRestartBanner(true) - }, - onError(err) { - // Try to parse hard_errors from the response body - try { - const parsed = JSON.parse(err.message) as EnvImportError - if (parsed.hard_errors) { - setImportError(parsed) - return - } - } catch { - // fall through to generic error - } - setImportError({ hard_errors: [err.message], warnings: [] }) - }, - }) - - // Reset file input so the same file can be re-selected after fixing issues - if (fileRef.current) fileRef.current.value = '' - } - - async function handleExport() { - setExportError(null) - try { - await downloadEnvExport() - } catch (err) { - setExportError(err instanceof Error ? err.message : String(err)) - } - } - - /** Dismisses the restart banner reminding the operator that imports require a restart. */ - function dismissRestartBanner() { - sessionStorage.removeItem(RESTART_KEY) - setShowRestartBanner(false) - } const proxyUrl = status ? `http://${window.location.hostname}:${status.proxy_port}` : '' @@ -142,458 +35,19 @@ export default function Settings({ configured = true }: { configured?: boolean } )} - {/* Getting-started notice — shown when no backend is configured (no env/config-file - signal and no managed backend). Manage backends on the Backends tab. */} - {!configured && ( -
-
No backend configured — nothing to forward requests to.
-
- Add a backend on the Backends tab, or configure one via env. - The proxy needs a backend endpoint (where to forward) and a listen port (where to accept). - LISTEN_PORT defaults to 3000. Create a .anyllm.env and import it below, - or pass it at startup: anyllm-proxy --webui --env-file .anyllm.env -
-
-
-
OpenAI
-
-{`OPENAI_API_KEY=sk-...
-PROXY_API_KEYS=my-key`}
-              
-
-
-
Ollama / local LLM
-
-{`OPENAI_BASE_URL=http://localhost:11434/v1
-PROXY_OPEN_RELAY=true`}
-              
-
-
-
OpenRouter / custom
-
-{`OPENAI_BASE_URL=https://openrouter.ai/api/v1
-OPENAI_API_KEY=sk-or-...
-PROXY_API_KEYS=my-key`}
-              
-
-
-
- )} - - {/* Restart-required banner — shown after a successful import */} - {showRestartBanner && ( - - Restart the proxy for imported env vars to take effect. - Dismiss - - )} + {/* Getting-started notice — shown when no backend is configured */} + {/* Env file import / export */} -
-
Env File
-
- - fileRef.current?.click()} - disabled={importEnv.isPending} - loading={importEnv.isPending} - > - Import .anyllm.env - - - Export .anyllm.env - -
- - {/* Import success */} - {importResult && ( -
-
- {importResult.applied} variable{importResult.applied !== 1 ? 's' : ''} imported. - {importResult.warnings.length === 0 && ' No issues.'} -
- {importResult.warnings.length > 0 && ( -
-
Warnings
- {importResult.warnings.map((w, i) => ( -
- {w.line != null && [line {w.line}] } - {w.key && {w.key}: } - {w.message} -
- ))} -
- )} -
- )} - - {/* Import hard error */} - {importError && ( -
-
Import rejected
- {importError.hard_errors.map((e, i) => ( -
{e}
- ))} - {importError.warnings.length > 0 && ( - <> -
Warnings (from partial parse)
- {importError.warnings.map((w, i) => ( -
- {w.line != null && [line {w.line}] } - {w.message} -
- ))} - - )} -
- )} - - {/* Export error */} - {exportError && ( -
- Export failed: {exportError} -
- )} -
+ - {cfg && ( -
-
Runtime
-
- - {cfg.overridden_keys.includes('redact_secrets') && ( -
- setPendingReset('redact_secrets')}> - Reset - -
- )} -
-
- - {cfg.overridden_keys.includes('log_bodies') && ( -
- setPendingReset('log_bodies')}> - Reset - -
- )} -
+ {/* Runtime settings override form */} + {cfg && } -
- -
- Repairs corrupted thinking/redacted_thinking blocks in Anthropic passthrough - requests (applies to any backend running in BACKEND=anthropic passthrough mode, - including a named backend in a multi-backend config). Off by default. -
- {cfg.overridden_keys.includes('anthropic_thinking_repair') && ( -
- setPendingReset('anthropic_thinking_repair')}> - Reset - -
- )} -
- -
- -
- Renders the stable system + tool-definition slab of Anthropic passthrough requests to a - PNG image block to save input tokens on vision models. Off by default. Enable per-model - below — only models that read imaged text reliably are offered. -
- {cfg.overridden_keys.includes('pxpipe_compress') && ( -
- setPendingReset('pxpipe_compress')}> - Reset - -
- )} - {cfg.pxpipe_compress && ( -
-
Models in scope (vision-capable)
- {cfg.pxpipe_available_models.length === 0 ? ( -
No vision-capable models in the catalog.
- ) : ( -
- {cfg.pxpipe_available_models.map((model) => ( - - ))} -
- )} - {cfg.overridden_keys.includes('pxpipe_models') && ( -
- setPendingReset('pxpipe_models')}> - Reset scope - -
- )} -
- )} -
- -
- -
- Command-aware filtering of tool-result text (test/build/git/log output) using the RTK - filter catalog. Shrinks noisy machine output before it reaches the backend; deterministic - and cache-safe. Off by default. Applies to Anthropic passthrough and translate paths. -
- {cfg.overridden_keys.includes('rtk_compress') && ( -
- setPendingReset('rtk_compress')}> - Reset - -
- )} - {cfg.rtk_compress && ( -
-
Models in scope (CSV, empty = all)
- { - const v = e.target.value.trim() - if (v === (cfg.rtk_models ?? '')) return - save.mutate({ rtk_models: v }) - }} - /> - {cfg.overridden_keys.includes('rtk_models') && ( -
- setPendingReset('rtk_models')}> - Reset scope - -
- )} -
- )} -
- -
- -
- Forwards the client's own x-api-key/Authorization header upstream instead of the - operator's configured credential (BACKEND=anthropic passthrough only, single-key/BYOK - deployments). The proxy refuses to enable this with 2+ PROXY_API_KEYS entries and no - PROXY_OPEN_RELAY. Off by default. -
- {cfg.overridden_keys.includes('forward_client_auth') && ( -
- setPendingReset('forward_client_auth')}> - Reset - -
- )} -
- -
- -
- - {cfg.overridden_keys.includes('tool_guardrail_mode') && ( - setPendingReset('tool_guardrail_mode')}> - Reset - - )} -
-
- Applies advisory guardrails to tool calls the proxy auto-executes. Disabled by default. -
-
- -
- -
- - {cfg.overridden_keys.includes('optimizer_mode') && ( - setPendingReset('optimizer_mode')}> - Reset - - )} -
-
- Frozen-Frontier compression of long conversation history (latest turn untouched). Off by default. -
- - {model && !model.compiled_in && ( -
- Heuristic scorer only. Rebuild the proxy with --features optimizer-onnx to enable the LLMLingua-2 ONNX scorer. -
- )} - {model?.compiled_in && !model.present && !model.downloading && ( -
- downloadModel.mutate()} - > - Download model ({fmtMB(model.size_bytes)}) - - - Required before enabling. Verified against a pinned sha256. - -
- )} - {model?.downloading && ( -
- Downloading and verifying model ({fmtMB(model.size_bytes)})… -
- )} - {model?.error && !model.downloading && ( -
- Download failed: {model.error} -
- )} - {model?.compiled_in && model.present && ( -
- ONNX scorer ready — live mode uses LLMLingua-2 (loaded on the next request). -
- )} -
- - {cfg.entries.filter((entry) => !['redact_secrets', 'log_bodies', 'anthropic_thinking_repair', 'pxpipe_compress', 'pxpipe_models', 'rtk_compress', 'rtk_models', 'forward_client_auth', 'tool_guardrail_mode', 'optimizer_mode'].includes(entry.key)).map((entry) => { - const inputId = `cfg-${entry.key}` - return ( -
- -
- setForm((f) => ({ ...f, [entry.key]: e.target.value }))} - /> - handleSave(entry.key, entry.value)}>Save - setPendingReset(entry.key)}>Reset -
-
- ) - })} -
- )} - {envData && ( -
-
Environment
-
- {Object.entries(envData).map(([k, v]) => ( - - {k} - {v} - - ))} -
-
- )} - - setPendingReset(null)} - onConfirm={doReset} - title="Reset override?" - message={ - <> - Reset override for {pendingReset}? The runtime value will revert - to the env-file or default. Active connections are not affected. - - } - confirmLabel="Reset" - variant="primary" - /> + {/* Environment grid */} + ) } diff --git a/crates/proxy/assets/model_pricing.json b/crates/proxy/assets/model_pricing.json index e429a74..74fc4a2 100644 --- a/crates/proxy/assets/model_pricing.json +++ b/crates/proxy/assets/model_pricing.json @@ -875,6 +875,30 @@ "output_cost_per_token": 3e-05, "provider": "openai" }, + { + "model_pattern": "gpt-5.6", + "input_cost_per_token": 5e-06, + "output_cost_per_token": 3e-05, + "provider": "openai" + }, + { + "model_pattern": "gpt-5.6-luna", + "input_cost_per_token": 1e-06, + "output_cost_per_token": 6e-06, + "provider": "openai" + }, + { + "model_pattern": "gpt-5.6-sol", + "input_cost_per_token": 5e-06, + "output_cost_per_token": 3e-05, + "provider": "openai" + }, + { + "model_pattern": "gpt-5.6-terra", + "input_cost_per_token": 2.5e-06, + "output_cost_per_token": 1.5e-05, + "provider": "openai" + }, { "model_pattern": "gpt-audio", "input_cost_per_token": 2.5e-06, @@ -929,6 +953,18 @@ "output_cost_per_token": 1.6e-05, "provider": "openai" }, + { + "model_pattern": "gpt-realtime-2.1", + "input_cost_per_token": 4e-06, + "output_cost_per_token": 2.4e-05, + "provider": "openai" + }, + { + "model_pattern": "gpt-realtime-2.1-mini", + "input_cost_per_token": 6e-07, + "output_cost_per_token": 2.4e-06, + "provider": "openai" + }, { "model_pattern": "gpt-realtime-2025-08-28", "input_cost_per_token": 4e-06, diff --git a/crates/proxy/src/admin/db/routes.rs b/crates/proxy/src/admin/db/routes.rs index e4df1fe..ce9b9ab 100644 --- a/crates/proxy/src/admin/db/routes.rs +++ b/crates/proxy/src/admin/db/routes.rs @@ -458,212 +458,5 @@ pub fn reorder_route_providers( } #[cfg(test)] -mod tests { - use super::*; - use crate::admin::db::backends::ManagedBackendRow; - - fn in_memory_db() -> Connection { - let conn = Connection::open_in_memory().unwrap(); - super::super::init_db(&conn).unwrap(); - conn - } - - fn test_row(name: &str) -> ManagedBackendRow { - ManagedBackendRow { - id: format!("id-{name}"), - name: name.to_string(), - provider_id: "openai".to_string(), - api_key: Some("sk-test".to_string()), - api_base: None, - deployment: None, - api_version: None, - project: None, - region: None, - aws_access_key_id: None, - aws_secret_access_key: None, - aws_session_token: None, - rpm: Some(100), - tpm: Some(10_000), - enabled: true, - created_at: "2026-01-01T00:00:00Z".to_string(), - updated_at: "2026-01-01T00:00:00Z".to_string(), - } - } - - fn seed_route_with_providers(conn: &Connection, count: usize) -> (String, Vec) { - let route = RouteRow { - id: uuid::Uuid::new_v4().to_string(), - name: "r".into(), - description: None, - strategy: "failover".into(), - rpm: None, - tpm: None, - budget_usd: None, - enabled: true, - guardrail_mode: None, - pxpipe_compress: None, - pxpipe_models: None, - redact_secrets: None, - position: 0, - created_at: now_iso8601(), - updated_at: now_iso8601(), - }; - insert_route(conn, &route).unwrap(); - - for i in 0..count { - let mut b = test_row(&format!("b{i}")); - b.id = format!("backend-{i}"); - crate::admin::db::backends::insert_managed_backend(conn, &b).unwrap(); - add_route_provider(conn, &route.id, &b.id, &["*".to_string()], i as i32, true).unwrap(); - } - - let ids: Vec = list_route_providers(conn, &route.id) - .unwrap() - .into_iter() - .map(|p| p.id) - .collect(); - (route.id, ids) - } - - #[test] - fn update_route_sets_and_clears_option_override() { - let conn = in_memory_db(); - let (route_id, _) = seed_route_with_providers(&conn, 1); - - // Set an override. - let set = RoutePatch { - name: None, - description: None, - strategy: None, - rpm: None, - tpm: None, - budget_usd: None, - enabled: Some(false), - guardrail_mode: Some(Some("standard".into())), - pxpipe_compress: Some(Some(true)), - pxpipe_models: None, - redact_secrets: None, - position: None, - }; - assert!(update_route(&conn, &route_id, &set).unwrap()); - let r = get_route(&conn, &route_id).unwrap().unwrap(); - assert!(!r.enabled); - assert_eq!(r.guardrail_mode.as_deref(), Some("standard")); - assert_eq!(r.pxpipe_compress, Some(true)); - - // Clear the override back to NULL (inherit). - let clear = RoutePatch { - name: None, - description: None, - strategy: None, - rpm: None, - tpm: None, - budget_usd: None, - enabled: None, - guardrail_mode: Some(None), - pxpipe_compress: Some(None), - pxpipe_models: None, - redact_secrets: None, - position: None, - }; - assert!(update_route(&conn, &route_id, &clear).unwrap()); - let r = get_route(&conn, &route_id).unwrap().unwrap(); - assert_eq!(r.guardrail_mode, None); - assert_eq!(r.pxpipe_compress, None); - // enabled was left unchanged (None) — still false. - assert!(!r.enabled); - } - - #[test] - fn disabled_route_excluded_from_enabled_route_ids() { - let conn = in_memory_db(); - let (route_id, _) = seed_route_with_providers(&conn, 1); - // The seeded backend is "b0"; while enabled the route id is returned. - assert_eq!( - enabled_route_ids_for_backend_name(&conn, "b0").unwrap(), - vec![route_id.clone()] - ); - - // Disable the route -> it drops out of the virtual-key scope query. - let patch = RoutePatch { - name: None, - description: None, - strategy: None, - rpm: None, - tpm: None, - budget_usd: None, - enabled: Some(false), - guardrail_mode: None, - pxpipe_compress: None, - pxpipe_models: None, - redact_secrets: None, - position: None, - }; - assert!(update_route(&conn, &route_id, &patch).unwrap()); - assert!(enabled_route_ids_for_backend_name(&conn, "b0") - .unwrap() - .is_empty()); - } - - #[test] - fn reorder_route_providers_rewrites_priorities() { - let conn = in_memory_db(); - let (route_id, ids) = seed_route_with_providers(&conn, 3); - - // Reverse the order. - let reversed: Vec = ids.iter().rev().cloned().collect(); - let outcome = reorder_route_providers(&conn, &route_id, &reversed).unwrap(); - - match outcome { - ReorderOutcome::Ok(rows) => { - let new_order: Vec = rows.iter().map(|p| p.id.clone()).collect(); - assert_eq!(new_order, reversed); - for (i, row) in rows.iter().enumerate() { - assert_eq!(row.priority, i as i32); - } - } - ReorderOutcome::Mismatch => panic!("expected Ok"), - } - } - - #[test] - fn reorder_route_providers_mismatch_rolls_back() { - let conn = in_memory_db(); - let (route_id, ids) = seed_route_with_providers(&conn, 3); - - // Submit a subset — should be rejected as Mismatch, priorities unchanged. - let partial: Vec = ids.iter().take(2).cloned().collect(); - let outcome = reorder_route_providers(&conn, &route_id, &partial).unwrap(); - assert!(matches!(outcome, ReorderOutcome::Mismatch)); - - // Priorities must be untouched. - let rows = list_route_providers(&conn, &route_id).unwrap(); - for (i, row) in rows.iter().enumerate() { - assert_eq!( - row.priority, i as i32, - "priorities must be unchanged after Mismatch" - ); - } - } - - #[test] - fn reorder_route_providers_rejects_duplicates_and_extras() { - let conn = in_memory_db(); - let (route_id, ids) = seed_route_with_providers(&conn, 3); - - // Duplicate id. - let dup = vec![ids[0].clone(), ids[0].clone(), ids[1].clone()]; - assert!(matches!( - reorder_route_providers(&conn, &route_id, &dup).unwrap(), - ReorderOutcome::Mismatch - )); - - // Extra id that isn't a provider on this route. - let mut extra = ids.clone(); - extra.push("bogus-id".into()); - assert!(matches!( - reorder_route_providers(&conn, &route_id, &extra).unwrap(), - ReorderOutcome::Mismatch - )); - } -} +#[path = "routes/tests.rs"] +mod tests; diff --git a/crates/proxy/src/admin/db/routes/tests.rs b/crates/proxy/src/admin/db/routes/tests.rs new file mode 100644 index 0000000..2970e63 --- /dev/null +++ b/crates/proxy/src/admin/db/routes/tests.rs @@ -0,0 +1,207 @@ +use super::*; +use crate::admin::db::backends::ManagedBackendRow; + +fn in_memory_db() -> Connection { + let conn = Connection::open_in_memory().unwrap(); + super::super::init_db(&conn).unwrap(); + conn +} + +fn test_row(name: &str) -> ManagedBackendRow { + ManagedBackendRow { + id: format!("id-{name}"), + name: name.to_string(), + provider_id: "openai".to_string(), + api_key: Some("sk-test".to_string()), + api_base: None, + deployment: None, + api_version: None, + project: None, + region: None, + aws_access_key_id: None, + aws_secret_access_key: None, + aws_session_token: None, + rpm: Some(100), + tpm: Some(10_000), + enabled: true, + created_at: "2026-01-01T00:00:00Z".to_string(), + updated_at: "2026-01-01T00:00:00Z".to_string(), + } +} + +fn seed_route_with_providers(conn: &Connection, count: usize) -> (String, Vec) { + let route = RouteRow { + id: uuid::Uuid::new_v4().to_string(), + name: "r".into(), + description: None, + strategy: "failover".into(), + rpm: None, + tpm: None, + budget_usd: None, + enabled: true, + guardrail_mode: None, + pxpipe_compress: None, + pxpipe_models: None, + redact_secrets: None, + position: 0, + created_at: now_iso8601(), + updated_at: now_iso8601(), + }; + insert_route(conn, &route).unwrap(); + + for i in 0..count { + let mut b = test_row(&format!("b{i}")); + b.id = format!("backend-{i}"); + crate::admin::db::backends::insert_managed_backend(conn, &b).unwrap(); + add_route_provider(conn, &route.id, &b.id, &["*".to_string()], i as i32, true).unwrap(); + } + + let ids: Vec = list_route_providers(conn, &route.id) + .unwrap() + .into_iter() + .map(|p| p.id) + .collect(); + (route.id, ids) +} + +#[test] +fn update_route_sets_and_clears_option_override() { + let conn = in_memory_db(); + let (route_id, _) = seed_route_with_providers(&conn, 1); + + // Set an override. + let set = RoutePatch { + name: None, + description: None, + strategy: None, + rpm: None, + tpm: None, + budget_usd: None, + enabled: Some(false), + guardrail_mode: Some(Some("standard".into())), + pxpipe_compress: Some(Some(true)), + pxpipe_models: None, + redact_secrets: None, + position: None, + }; + assert!(update_route(&conn, &route_id, &set).unwrap()); + let r = get_route(&conn, &route_id).unwrap().unwrap(); + assert!(!r.enabled); + assert_eq!(r.guardrail_mode.as_deref(), Some("standard")); + assert_eq!(r.pxpipe_compress, Some(true)); + + // Clear the override back to NULL (inherit). + let clear = RoutePatch { + name: None, + description: None, + strategy: None, + rpm: None, + tpm: None, + budget_usd: None, + enabled: None, + guardrail_mode: Some(None), + pxpipe_compress: Some(None), + pxpipe_models: None, + redact_secrets: None, + position: None, + }; + assert!(update_route(&conn, &route_id, &clear).unwrap()); + let r = get_route(&conn, &route_id).unwrap().unwrap(); + assert_eq!(r.guardrail_mode, None); + assert_eq!(r.pxpipe_compress, None); + // enabled was left unchanged (None) — still false. + assert!(!r.enabled); +} + +#[test] +fn disabled_route_excluded_from_enabled_route_ids() { + let conn = in_memory_db(); + let (route_id, _) = seed_route_with_providers(&conn, 1); + // The seeded backend is "b0"; while enabled the route id is returned. + assert_eq!( + enabled_route_ids_for_backend_name(&conn, "b0").unwrap(), + vec![route_id.clone()] + ); + + // Disable the route -> it drops out of the virtual-key scope query. + let patch = RoutePatch { + name: None, + description: None, + strategy: None, + rpm: None, + tpm: None, + budget_usd: None, + enabled: Some(false), + guardrail_mode: None, + pxpipe_compress: None, + pxpipe_models: None, + redact_secrets: None, + position: None, + }; + assert!(update_route(&conn, &route_id, &patch).unwrap()); + assert!(enabled_route_ids_for_backend_name(&conn, "b0") + .unwrap() + .is_empty()); +} + +#[test] +fn reorder_route_providers_rewrites_priorities() { + let conn = in_memory_db(); + let (route_id, ids) = seed_route_with_providers(&conn, 3); + + // Reverse the order. + let reversed: Vec = ids.iter().rev().cloned().collect(); + let outcome = reorder_route_providers(&conn, &route_id, &reversed).unwrap(); + + match outcome { + ReorderOutcome::Ok(rows) => { + let new_order: Vec = rows.iter().map(|p| p.id.clone()).collect(); + assert_eq!(new_order, reversed); + for (i, row) in rows.iter().enumerate() { + assert_eq!(row.priority, i as i32); + } + } + ReorderOutcome::Mismatch => panic!("expected Ok"), + } +} + +#[test] +fn reorder_route_providers_mismatch_rolls_back() { + let conn = in_memory_db(); + let (route_id, ids) = seed_route_with_providers(&conn, 3); + + // Submit a subset — should be rejected as Mismatch, priorities unchanged. + let partial: Vec = ids.iter().take(2).cloned().collect(); + let outcome = reorder_route_providers(&conn, &route_id, &partial).unwrap(); + assert!(matches!(outcome, ReorderOutcome::Mismatch)); + + // Priorities must be untouched. + let rows = list_route_providers(&conn, &route_id).unwrap(); + for (i, row) in rows.iter().enumerate() { + assert_eq!( + row.priority, i as i32, + "priorities must be unchanged after Mismatch" + ); + } +} + +#[test] +fn reorder_route_providers_rejects_duplicates_and_extras() { + let conn = in_memory_db(); + let (route_id, ids) = seed_route_with_providers(&conn, 3); + + // Duplicate id. + let dup = vec![ids[0].clone(), ids[0].clone(), ids[1].clone()]; + assert!(matches!( + reorder_route_providers(&conn, &route_id, &dup).unwrap(), + ReorderOutcome::Mismatch + )); + + // Extra id that isn't a provider on this route. + let mut extra = ids.clone(); + extra.push("bogus-id".into()); + assert!(matches!( + reorder_route_providers(&conn, &route_id, &extra).unwrap(), + ReorderOutcome::Mismatch + )); +} diff --git a/crates/proxy/src/admin/routes/config.rs b/crates/proxy/src/admin/routes/config.rs index 57aec41..5fdee23 100644 --- a/crates/proxy/src/admin/routes/config.rs +++ b/crates/proxy/src/admin/routes/config.rs @@ -7,75 +7,6 @@ use axum::{ }; use std::net::SocketAddr; -/// GET /admin/api/env -- effective environment variable values. -/// Secrets (API keys, tokens) are masked; plain config values are shown as-is. -pub(super) async fn get_env() -> Json { - fn plain(key: &str) -> serde_json::Value { - match std::env::var(key) { - Ok(v) if !v.is_empty() => serde_json::Value::String(v), - _ => serde_json::Value::Null, - } - } - fn secret(key: &str) -> serde_json::Value { - match std::env::var(key) { - Ok(v) if !v.is_empty() => { - serde_json::Value::String(anyllm_translate::util::redact::redact_secret(&v)) - } - _ => serde_json::Value::Null, - } - } - - Json(serde_json::json!({ - // Core proxy config - "BACKEND": plain("BACKEND"), - "LISTEN_PORT": plain("LISTEN_PORT"), - "BIG_MODEL": plain("BIG_MODEL"), - "SMALL_MODEL": plain("SMALL_MODEL"), - "RUST_LOG": plain("RUST_LOG"), - "LOG_BODIES": plain("LOG_BODIES"), - "REDACT_SECRETS": plain("REDACT_SECRETS"), - "PROXY_CONFIG": plain("PROXY_CONFIG"), - // OpenAI / compatible - "OPENAI_BASE_URL": plain("OPENAI_BASE_URL"), - "OPENAI_API_FORMAT": plain("OPENAI_API_FORMAT"), - "OPENAI_API_KEY": secret("OPENAI_API_KEY"), - // Vertex AI - "VERTEX_PROJECT": plain("VERTEX_PROJECT"), - "VERTEX_REGION": plain("VERTEX_REGION"), - "VERTEX_API_KEY": secret("VERTEX_API_KEY"), - // Gemini - "GEMINI_BASE_URL": plain("GEMINI_BASE_URL"), - "GEMINI_API_KEY": secret("GEMINI_API_KEY"), - // Azure OpenAI - "AZURE_OPENAI_ENDPOINT": plain("AZURE_OPENAI_ENDPOINT"), - "AZURE_OPENAI_DEPLOYMENT": plain("AZURE_OPENAI_DEPLOYMENT"), - "AZURE_OPENAI_API_KEY": secret("AZURE_OPENAI_API_KEY"), - "AZURE_OPENAI_API_VERSION": plain("AZURE_OPENAI_API_VERSION"), - // AWS Bedrock - "AWS_REGION": plain("AWS_REGION"), - "AWS_ACCESS_KEY_ID": secret("AWS_ACCESS_KEY_ID"), - "AWS_SECRET_ACCESS_KEY": secret("AWS_SECRET_ACCESS_KEY"), - "AWS_SESSION_TOKEN": secret("AWS_SESSION_TOKEN"), - // Google OAuth bearer token (full token — treat as secret) - "GOOGLE_ACCESS_TOKEN": secret("GOOGLE_ACCESS_TOKEN"), - // Auth - "PROXY_API_KEYS": secret("PROXY_API_KEYS"), - "PROXY_OPEN_RELAY": plain("PROXY_OPEN_RELAY"), - // TLS - "TLS_CLIENT_CERT_P12": plain("TLS_CLIENT_CERT_P12"), - "TLS_CA_CERT": plain("TLS_CA_CERT"), - // Network / security - "IP_ALLOWLIST": plain("IP_ALLOWLIST"), - "TRUST_PROXY_HEADERS": plain("TRUST_PROXY_HEADERS"), - "WEBHOOK_URLS": plain("WEBHOOK_URLS"), - "RATE_LIMIT_FAIL_POLICY": plain("RATE_LIMIT_FAIL_POLICY"), - // Admin - "ADMIN_PORT": plain("ADMIN_PORT"), - "ADMIN_DB_PATH": plain("ADMIN_DB_PATH"), - "ADMIN_LOG_RETENTION_DAYS": plain("ADMIN_LOG_RETENTION_DAYS"), - })) -} - /// GET /admin/api/config -- effective config (env defaults + overrides). pub(super) async fn get_config(State(shared): State) -> Json { // Clone config snapshot and drop the read guard before any .await points. diff --git a/crates/proxy/src/admin/routes/env.rs b/crates/proxy/src/admin/routes/env.rs index c068475..44235d5 100644 --- a/crates/proxy/src/admin/routes/env.rs +++ b/crates/proxy/src/admin/routes/env.rs @@ -17,6 +17,75 @@ use axum::{ use serde::Serialize; use std::net::SocketAddr; +/// GET /admin/api/env -- effective environment variable values. +/// Secrets (API keys, tokens) are masked; plain config values are shown as-is. +pub(super) async fn get_env() -> Json { + fn plain(key: &str) -> serde_json::Value { + match std::env::var(key) { + Ok(v) if !v.is_empty() => serde_json::Value::String(v), + _ => serde_json::Value::Null, + } + } + fn secret(key: &str) -> serde_json::Value { + match std::env::var(key) { + Ok(v) if !v.is_empty() => { + serde_json::Value::String(anyllm_translate::util::redact::redact_secret(&v)) + } + _ => serde_json::Value::Null, + } + } + + Json(serde_json::json!({ + // Core proxy config + "BACKEND": plain("BACKEND"), + "LISTEN_PORT": plain("LISTEN_PORT"), + "BIG_MODEL": plain("BIG_MODEL"), + "SMALL_MODEL": plain("SMALL_MODEL"), + "RUST_LOG": plain("RUST_LOG"), + "LOG_BODIES": plain("LOG_BODIES"), + "REDACT_SECRETS": plain("REDACT_SECRETS"), + "PROXY_CONFIG": plain("PROXY_CONFIG"), + // OpenAI / compatible + "OPENAI_BASE_URL": plain("OPENAI_BASE_URL"), + "OPENAI_API_FORMAT": plain("OPENAI_API_FORMAT"), + "OPENAI_API_KEY": secret("OPENAI_API_KEY"), + // Vertex AI + "VERTEX_PROJECT": plain("VERTEX_PROJECT"), + "VERTEX_REGION": plain("VERTEX_REGION"), + "VERTEX_API_KEY": secret("VERTEX_API_KEY"), + // Gemini + "GEMINI_BASE_URL": plain("GEMINI_BASE_URL"), + "GEMINI_API_KEY": secret("GEMINI_API_KEY"), + // Azure OpenAI + "AZURE_OPENAI_ENDPOINT": plain("AZURE_OPENAI_ENDPOINT"), + "AZURE_OPENAI_DEPLOYMENT": plain("AZURE_OPENAI_DEPLOYMENT"), + "AZURE_OPENAI_API_KEY": secret("AZURE_OPENAI_API_KEY"), + "AZURE_OPENAI_API_VERSION": plain("AZURE_OPENAI_API_VERSION"), + // AWS Bedrock + "AWS_REGION": plain("AWS_REGION"), + "AWS_ACCESS_KEY_ID": secret("AWS_ACCESS_KEY_ID"), + "AWS_SECRET_ACCESS_KEY": secret("AWS_SECRET_ACCESS_KEY"), + "AWS_SESSION_TOKEN": secret("AWS_SESSION_TOKEN"), + // Google OAuth bearer token (full token — treat as secret) + "GOOGLE_ACCESS_TOKEN": secret("GOOGLE_ACCESS_TOKEN"), + // Auth + "PROXY_API_KEYS": secret("PROXY_API_KEYS"), + "PROXY_OPEN_RELAY": plain("PROXY_OPEN_RELAY"), + // TLS + "TLS_CLIENT_CERT_P12": plain("TLS_CLIENT_CERT_P12"), + "TLS_CA_CERT": plain("TLS_CA_CERT"), + // Network / security + "IP_ALLOWLIST": plain("IP_ALLOWLIST"), + "TRUST_PROXY_HEADERS": plain("TRUST_PROXY_HEADERS"), + "WEBHOOK_URLS": plain("WEBHOOK_URLS"), + "RATE_LIMIT_FAIL_POLICY": plain("RATE_LIMIT_FAIL_POLICY"), + // Admin + "ADMIN_PORT": plain("ADMIN_PORT"), + "ADMIN_DB_PATH": plain("ADMIN_DB_PATH"), + "ADMIN_LOG_RETENTION_DAYS": plain("ADMIN_LOG_RETENTION_DAYS"), + })) +} + /// Maximum accepted upload size for an env file. Env files are tiny; /// anything larger is almost certainly the wrong file type. /// The router already enforces a 1 MB body limit; this is a tighter application-level check. diff --git a/crates/proxy/src/admin/routes/mod.rs b/crates/proxy/src/admin/routes/mod.rs index 9eb01a1..d2171d9 100644 --- a/crates/proxy/src/admin/routes/mod.rs +++ b/crates/proxy/src/admin/routes/mod.rs @@ -83,7 +83,7 @@ pub fn admin_router(shared: SharedState, token: Arc>) "/admin/api/config/overrides/{key}", delete(config::delete_config_override), ) - .route("/admin/api/env", get(config::get_env)) + .route("/admin/api/env", get(env::get_env)) .route("/admin/api/env/import", post(env::import_env)) .route("/admin/api/env/export", get(env::export_env)) .route("/admin/api/metrics", get(logs::get_metrics)) diff --git a/crates/proxy/src/admin/routes/models.rs b/crates/proxy/src/admin/routes/models.rs index a458034..b42abba 100644 --- a/crates/proxy/src/admin/routes/models.rs +++ b/crates/proxy/src/admin/routes/models.rs @@ -495,103 +495,5 @@ fn ensure_local_discover_host(parsed: &url::Url) -> Result<(), String> { } #[cfg(test)] -mod tests { - use super::*; - - fn custom_request(url: &str) -> DiscoverRequest { - DiscoverRequest { - source: "custom".to_string(), - url: Some(url.to_string()), - provider_id: None, - api_key: None, - } - } - - #[test] - fn custom_discover_rejects_loopback_url() { - let err = resolve_discover_target(&custom_request("http://127.0.0.1:11434"), false) - .expect_err("loopback custom discovery URL must be rejected"); - - assert!(err.contains("private/loopback")); - } - - #[test] - fn custom_discover_rejects_file_scheme() { - let err = resolve_discover_target(&custom_request("file:///tmp/anyllm"), false) - .expect_err("non-http custom discovery URL must be rejected"); - - assert!(err.contains("scheme")); - } - - #[test] - fn custom_discover_accepts_https_url_and_appends_models() { - let (url, api_key) = resolve_discover_target(&custom_request("https://1.1.1.1"), false) - .expect("public HTTPS custom discovery URL should be accepted"); - - assert_eq!(url, "https://1.1.1.1/v1/models"); - assert_eq!(api_key, None); - } - - #[test] - fn custom_discover_accepts_existing_models_suffix() { - let (url, api_key) = - resolve_discover_target(&custom_request("https://1.1.1.1/v1/models"), false) - .expect("public HTTPS custom discovery URL should be accepted"); - - assert_eq!(url, "https://1.1.1.1/v1/models"); - assert_eq!(api_key, None); - } - - #[test] - fn custom_discover_does_not_double_v1_suffix() { - // Local-provider catalog defaults already end in /v1; don't produce /v1/v1/models. - let (url, _) = - resolve_discover_target(&custom_request("http://192.168.1.72:4444/v1"), true) - .expect("local LAN /v1 discovery URL should be accepted when allow_local"); - assert_eq!(url, "http://192.168.1.72:4444/v1/models"); - - // Trailing slash on a /v1 base collapses the same way. - let (url, _) = - resolve_discover_target(&custom_request("http://192.168.1.72:4444/v1/"), true) - .expect("trailing-slash /v1 discovery URL should be accepted when allow_local"); - assert_eq!(url, "http://192.168.1.72:4444/v1/models"); - } - - #[test] - fn custom_discover_local_allows_loopback_but_keeps_scheme_check() { - // allow_local=true: loopback is accepted (LM Studio/Ollama on localhost)... - let (url, _) = resolve_discover_target(&custom_request("http://127.0.0.1:1234"), true) - .expect("local loopback discovery URL should be accepted when allow_local"); - assert_eq!(url, "http://127.0.0.1:1234/v1/models"); - - // ...but a non-http scheme is still rejected. - let err = resolve_discover_target(&custom_request("file:///tmp/anyllm"), true) - .expect_err("non-http scheme must be rejected even when allow_local"); - assert!(err.contains("scheme")); - } - - #[test] - fn custom_discover_threads_api_key() { - let mut req = custom_request("http://192.168.1.72:4444"); - req.api_key = Some("sk-local".to_string()); - let (_, api_key) = resolve_discover_target(&req, true) - .expect("local LAN discovery URL should be accepted when allow_local"); - assert_eq!(api_key.as_deref(), Some("sk-local")); - } - - #[test] - fn custom_discover_local_rejects_public_and_metadata_hosts() { - // allow_local must NOT permit public hosts (would be a general outbound fetch). - let err = resolve_discover_target(&custom_request("http://1.1.1.1/v1"), true) - .expect_err("public IP must be rejected even when allow_local"); - assert!(err.contains("loopback/LAN")); - // Cloud-metadata / link-local stay blocked here too. - let err = resolve_discover_target(&custom_request("http://169.254.169.254/"), true) - .expect_err("metadata IP must be rejected even when allow_local"); - assert!(err.contains("loopback/LAN")); - // A bare host name that isn't `localhost` cannot be verified as local. - let err = resolve_discover_target(&custom_request("http://evil.example.com/"), true) - .expect_err("non-local host name must be rejected when allow_local"); - assert!(err.contains("loopback/LAN")); - } -} +#[path = "models/tests.rs"] +mod tests; diff --git a/crates/proxy/src/admin/routes/models/tests.rs b/crates/proxy/src/admin/routes/models/tests.rs new file mode 100644 index 0000000..4bbac93 --- /dev/null +++ b/crates/proxy/src/admin/routes/models/tests.rs @@ -0,0 +1,96 @@ +use super::*; + +fn custom_request(url: &str) -> DiscoverRequest { + DiscoverRequest { + source: "custom".to_string(), + url: Some(url.to_string()), + provider_id: None, + api_key: None, + } +} + +#[test] +fn custom_discover_rejects_loopback_url() { + let err = resolve_discover_target(&custom_request("http://127.0.0.1:11434"), false) + .expect_err("loopback custom discovery URL must be rejected"); + + assert!(err.contains("private/loopback")); +} + +#[test] +fn custom_discover_rejects_file_scheme() { + let err = resolve_discover_target(&custom_request("file:///tmp/anyllm"), false) + .expect_err("non-http custom discovery URL must be rejected"); + + assert!(err.contains("scheme")); +} + +#[test] +fn custom_discover_accepts_https_url_and_appends_models() { + let (url, api_key) = resolve_discover_target(&custom_request("https://1.1.1.1"), false) + .expect("public HTTPS custom discovery URL should be accepted"); + + assert_eq!(url, "https://1.1.1.1/v1/models"); + assert_eq!(api_key, None); +} + +#[test] +fn custom_discover_accepts_existing_models_suffix() { + let (url, api_key) = + resolve_discover_target(&custom_request("https://1.1.1.1/v1/models"), false) + .expect("public HTTPS custom discovery URL should be accepted"); + + assert_eq!(url, "https://1.1.1.1/v1/models"); + assert_eq!(api_key, None); +} + +#[test] +fn custom_discover_does_not_double_v1_suffix() { + // Local-provider catalog defaults already end in /v1; don't produce /v1/v1/models. + let (url, _) = resolve_discover_target(&custom_request("http://192.168.1.72:4444/v1"), true) + .expect("local LAN /v1 discovery URL should be accepted when allow_local"); + assert_eq!(url, "http://192.168.1.72:4444/v1/models"); + + // Trailing slash on a /v1 base collapses the same way. + let (url, _) = resolve_discover_target(&custom_request("http://192.168.1.72:4444/v1/"), true) + .expect("trailing-slash /v1 discovery URL should be accepted when allow_local"); + assert_eq!(url, "http://192.168.1.72:4444/v1/models"); +} + +#[test] +fn custom_discover_local_allows_loopback_but_keeps_scheme_check() { + // allow_local=true: loopback is accepted (LM Studio/Ollama on localhost)... + let (url, _) = resolve_discover_target(&custom_request("http://127.0.0.1:1234"), true) + .expect("local loopback discovery URL should be accepted when allow_local"); + assert_eq!(url, "http://127.0.0.1:1234/v1/models"); + + // ...but a non-http scheme is still rejected. + let err = resolve_discover_target(&custom_request("file:///tmp/anyllm"), true) + .expect_err("non-http scheme must be rejected even when allow_local"); + assert!(err.contains("scheme")); +} + +#[test] +fn custom_discover_threads_api_key() { + let mut req = custom_request("http://192.168.1.72:4444"); + req.api_key = Some("sk-local".to_string()); + let (_, api_key) = resolve_discover_target(&req, true) + .expect("local LAN discovery URL should be accepted when allow_local"); + assert_eq!(api_key.as_deref(), Some("sk-local")); +} + +#[test] +fn custom_discover_local_rejects_public_and_metadata_hosts() { + // allow_local must NOT permit public hosts (would be a general outbound fetch). + let err = resolve_discover_target(&custom_request("http://1.1.1.1/v1"), true) + .expect_err("public IP must be rejected even when allow_local"); + assert!(err.contains("loopback/LAN")); + // Cloud-metadata / link-local stay blocked here too. + let err = resolve_discover_target(&custom_request("http://169.254.169.254/"), true) + .expect_err("metadata IP must be rejected even when allow_local"); + assert!(err.contains("loopback/LAN")); + // A bare host name that isn't `localhost` cannot be verified as local. + let err = resolve_discover_target(&custom_request("http://evil.example.com/"), true) + .expect_err("non-local host name must be rejected when allow_local"); + assert!(err.contains("loopback/LAN")); +} diff --git a/crates/proxy/src/cache/config.rs b/crates/proxy/src/cache/config.rs new file mode 100644 index 0000000..1e7a0eb --- /dev/null +++ b/crates/proxy/src/cache/config.rs @@ -0,0 +1,42 @@ +/// Configuration for the cache subsystem. +#[derive(Debug, Clone)] +pub struct CacheConfig { + /// Default TTL in seconds for cached responses. + pub ttl_secs: u64, + /// Maximum number of entries in the in-memory cache. + pub max_entries: u64, + /// Optional Redis URL. Used by the Redis L2 cache backend (requires `redis` feature) + /// and distributed rate limiting. When set, responses are cached in Redis in addition + /// to the in-memory L1 cache, and rate limit state is shared across proxy instances. + pub redis_url: Option, +} + +impl Default for CacheConfig { + fn default() -> Self { + Self { + ttl_secs: 300, + max_entries: 10_000, + redis_url: None, + } + } +} + +impl CacheConfig { + /// Load cache configuration from environment variables. + pub fn from_env() -> Self { + let ttl_secs = std::env::var("CACHE_TTL_SECS") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(300); + let max_entries = std::env::var("CACHE_MAX_ENTRIES") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(10_000); + let redis_url = std::env::var("REDIS_URL").ok(); + Self { + ttl_secs, + max_entries, + redis_url, + } + } +} diff --git a/crates/proxy/src/cache/control.rs b/crates/proxy/src/cache/control.rs new file mode 100644 index 0000000..e1d4918 --- /dev/null +++ b/crates/proxy/src/cache/control.rs @@ -0,0 +1,189 @@ +use super::CacheEntry; +use super::MAX_TTL_SECS; +use std::time::Duration; + +/// Per-request cache controls after combining local and LiteLLM-style fields. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CacheControl { + /// Whether to read an existing cached response before calling upstream. + pub lookup: bool, + /// Whether to store a successful upstream response after the call. + pub store: bool, + /// Per-entry store TTL override in seconds. + pub ttl_secs: Option, + /// Maximum acceptable age for an existing cached entry. + pub max_age_secs: Option, + /// Optional caller namespace for exact-match cache isolation. + pub namespace: Option, + /// Parsed for LiteLLM compatibility. Currently no behavior change. + pub use_cache: bool, +} + +impl Default for CacheControl { + fn default() -> Self { + Self { + lookup: true, + store: true, + ttl_secs: None, + max_age_secs: None, + namespace: None, + use_cache: false, + } + } +} + +/// Parse the optional `cache_ttl_secs` field from a request body. +/// +/// Returns: +/// - `Ok(None)` if the field is absent or null (use default TTL). +/// - `Ok(Some(0))` if explicitly 0 (bypass cache). +/// - `Ok(Some(n))` for valid positive values up to MAX_TTL_SECS. +/// - `Err(message)` for negative values, values > MAX_TTL_SECS, or non-numeric. +pub fn parse_cache_ttl(body: &serde_json::Value) -> Result, String> { + let Some(val) = body.get("cache_ttl_secs") else { + return Ok(None); + }; + if val.is_null() { + return Ok(None); + } + if let Some(n) = val.as_u64() { + if n > MAX_TTL_SECS { + return Err(format!("cache_ttl_secs must be <= {MAX_TTL_SECS}, got {n}")); + } + return Ok(Some(n)); + } + if let Some(n) = val.as_i64() { + // Negative values are invalid + return Err(format!("cache_ttl_secs must be non-negative, got {n}")); + } + if let Some(n) = val.as_f64() { + if n < 0.0 { + return Err(format!("cache_ttl_secs must be non-negative, got {n}")); + } + let truncated = n as u64; + if truncated > MAX_TTL_SECS { + return Err(format!( + "cache_ttl_secs must be <= {MAX_TTL_SECS}, got {truncated}" + )); + } + return Ok(Some(truncated)); + } + Err(format!("cache_ttl_secs must be a number, got {}", val)) +} + +/// Parse local `cache_ttl_secs` and LiteLLM-compatible top-level `cache`. +pub fn parse_cache_control(body: &serde_json::Value) -> Result { + let mut control = CacheControl::default(); + match parse_cache_ttl(body)? { + Some(0) => { + control.lookup = false; + control.store = false; + control.ttl_secs = Some(0); + } + Some(ttl) => { + control.ttl_secs = Some(ttl); + } + None => {} + } + + let Some(cache_value) = body.get("cache") else { + return Ok(control); + }; + if cache_value.is_null() { + return Ok(control); + } + let Some(cache_obj) = cache_value.as_object() else { + return Err(format!("cache must be an object, got {}", cache_value)); + }; + + if parse_cache_bool_field(cache_obj, "no-cache")?.unwrap_or(false) { + control.lookup = false; + } + if parse_cache_bool_field(cache_obj, "no-store")?.unwrap_or(false) { + control.store = false; + } + if let Some(use_cache) = parse_cache_bool_field(cache_obj, "use-cache")? { + control.use_cache = use_cache; + } + if let Some(ttl) = parse_cache_secs_field(cache_obj, "ttl")? { + control.ttl_secs = Some(ttl); + } + if let Some(max_age) = parse_cache_secs_field(cache_obj, "s-maxage")? { + control.max_age_secs = Some(max_age); + } + if let Some(max_age) = parse_cache_secs_field(cache_obj, "s-max-age")? { + control.max_age_secs = Some(max_age); + } + if let Some(namespace_value) = cache_obj.get("namespace") { + if !namespace_value.is_null() { + let Some(namespace) = namespace_value.as_str() else { + return Err(format!( + "cache.namespace must be a string, got {}", + namespace_value + )); + }; + if !namespace.is_empty() { + control.namespace = Some(namespace.to_string()); + } + } + } + + Ok(control) +} + +fn parse_cache_bool_field( + cache_obj: &serde_json::Map, + field: &str, +) -> Result, String> { + let Some(value) = cache_obj.get(field) else { + return Ok(None); + }; + if value.is_null() { + return Ok(None); + } + value + .as_bool() + .map(Some) + .ok_or_else(|| format!("cache.{field} must be a boolean, got {value}")) +} + +fn parse_cache_secs_field( + cache_obj: &serde_json::Map, + field: &str, +) -> Result, String> { + let Some(value) = cache_obj.get(field) else { + return Ok(None); + }; + if value.is_null() { + return Ok(None); + } + if let Some(n) = value.as_u64() { + if n > MAX_TTL_SECS { + return Err(format!("cache.{field} must be <= {MAX_TTL_SECS}, got {n}")); + } + return Ok(Some(n)); + } + if let Some(n) = value.as_i64() { + return Err(format!("cache.{field} must be non-negative, got {n}")); + } + if let Some(n) = value.as_f64() { + if n < 0.0 { + return Err(format!("cache.{field} must be non-negative, got {n}")); + } + let truncated = n as u64; + if truncated > MAX_TTL_SECS { + return Err(format!( + "cache.{field} must be <= {MAX_TTL_SECS}, got {truncated}" + )); + } + return Ok(Some(truncated)); + } + Err(format!("cache.{field} must be a number, got {}", value)) +} + +pub fn cache_entry_is_fresh(entry: &CacheEntry, max_age_secs: Option) -> bool { + match max_age_secs { + Some(max_age) => entry.created_at.elapsed() <= Duration::from_secs(max_age), + None => true, + } +} diff --git a/crates/proxy/src/cache/key.rs b/crates/proxy/src/cache/key.rs new file mode 100644 index 0000000..0019e2c --- /dev/null +++ b/crates/proxy/src/cache/key.rs @@ -0,0 +1,148 @@ +use sha2::{Digest, Sha256}; +use std::io::{self, Write}; + +/// Namespace prefix for cache keys, preventing cross-endpoint collisions. +#[derive(Debug, Clone, Copy)] +pub enum CacheNamespace { + /// Anthropic /v1/messages endpoint. + Anthropic, + /// OpenAI /v1/chat/completions endpoint. + OpenAI, +} + +impl CacheNamespace { + fn prefix(self) -> &'static str { + match self { + Self::Anthropic => "anth", + Self::OpenAI => "oai", + } + } +} + +pub struct CacheScope<'a> { + pub backend_name: &'a str, + pub auth_identity: &'a str, + pub namespace: Option<&'a str>, +} + +/// Compute a deterministic cache key for a request body. +/// +/// Extracts the fields that affect response content, sorts them via BTreeMap, +/// serializes to canonical JSON, SHA-256 hashes the result, and prepends the +/// namespace prefix. +pub fn cache_key_for_request( + body: &serde_json::Value, + ns: CacheNamespace, + scope: &CacheScope<'_>, +) -> String { + let mut hasher = Sha256::new(); + write_canonical_cache_body(&mut hasher, body, scope); + let hash = hasher.finalize(); + let hex = hex::encode(hash); + format!("{}:{}", ns.prefix(), hex) +} + +enum CacheField<'a> { + Json(&'a str, &'a serde_json::Value), + Str(&'static str, &'a str), +} + +impl<'a> CacheField<'a> { + fn key(&self) -> &str { + match self { + Self::Json(key, _) | Self::Str(key, _) => key, + } + } +} + +struct HashWriter<'a> { + hasher: &'a mut Sha256, +} + +impl Write for HashWriter<'_> { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.hasher.update(buf); + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +fn write_canonical_cache_body( + hasher: &mut Sha256, + body: &serde_json::Value, + scope: &CacheScope<'_>, +) { + let mut fields = Vec::new(); + if let Some(obj) = body.as_object() { + fields.extend( + obj.iter() + .filter(|(key, value)| should_include_cache_field(key, value)) + .map(|(key, value)| CacheField::Json(key.as_str(), value)), + ); + } + fields.push(CacheField::Str("_scope_auth", scope.auth_identity)); + fields.push(CacheField::Str("_scope_backend", scope.backend_name)); + if let Some(namespace) = scope.namespace { + fields.push(CacheField::Str("_scope_cache_namespace", namespace)); + } + fields.sort_unstable_by(|a, b| a.key().cmp(b.key())); + + let mut writer = HashWriter { hasher }; + writer + .write_all(b"{") + .expect("hash writer should not fail writing object start"); + for (idx, field) in fields.iter().enumerate() { + if idx > 0 { + writer + .write_all(b",") + .expect("hash writer should not fail writing separator"); + } + serde_json::to_writer(&mut writer, field.key()) + .expect("hash writer should not fail writing key"); + writer + .write_all(b":") + .expect("hash writer should not fail writing colon"); + match field { + CacheField::Json(_, value) => serde_json::to_writer(&mut writer, value) + .expect("hash writer should not fail writing JSON value"), + CacheField::Str(_, value) => serde_json::to_writer(&mut writer, value) + .expect("hash writer should not fail writing string value"), + } + } + writer + .write_all(b"}") + .expect("hash writer should not fail writing object end"); +} + +fn should_include_cache_field(key: &str, value: &serde_json::Value) -> bool { + if value.is_null() { + return false; + } + // Exclude fields that do not affect the backend response: + // - stream / stream_options: transport only (a cached non-stream response is + // replayed as a stream and vice versa). + // - cache: request cache controls, handled outside response-content hashing. + // - _scope_*: added separately as scope fields. + // - user / metadata: tracking fields documented as "Ignored" (anyllm_translate + // ChatCompletionRequest user, anthropic Metadata). Hashing them fragments the + // cache per end-user with no correctness benefit (tenant isolation is already + // provided by _scope_auth). + // + // parallel_tool_calls is NOT excluded: backends that honor it (e.g. OpenAI) + // produce different output for true vs false, so it must be part of the cache + // identity. (Gemini/Vertex have it stripped before dispatch by the tool policy.) + !matches!( + key, + "stream" + | "stream_options" + | "cache" + | "_scope_auth" + | "_scope_backend" + | "_scope_cache_namespace" + | "user" + | "metadata" + ) +} diff --git a/crates/proxy/src/cache/mod.rs b/crates/proxy/src/cache/mod.rs index 2787393..a6fc359 100644 --- a/crates/proxy/src/cache/mod.rs +++ b/crates/proxy/src/cache/mod.rs @@ -15,14 +15,23 @@ pub mod redis; #[cfg(feature = "qdrant")] pub mod semantic; +mod config; +mod control; +mod key; + +#[cfg(test)] +mod tests; + use bytes::Bytes; -use sha2::{Digest, Sha256}; -use std::io::{self, Write}; -use std::time::{Duration, Instant}; +use std::time::Instant; /// Maximum allowed value for per-request `cache_ttl_secs`. pub const MAX_TTL_SECS: u64 = 86_400; +pub use config::CacheConfig; +pub use control::{cache_entry_is_fresh, parse_cache_control, parse_cache_ttl, CacheControl}; +pub use key::{cache_key_for_request, CacheNamespace, CacheScope}; + /// Cached response entry stored in any cache backend. #[derive(Clone, Debug)] pub struct CacheEntry { @@ -37,24 +46,6 @@ pub struct CacheEntry { pub ttl_secs: Option, } -/// Namespace prefix for cache keys, preventing cross-endpoint collisions. -#[derive(Debug, Clone, Copy)] -pub enum CacheNamespace { - /// Anthropic /v1/messages endpoint. - Anthropic, - /// OpenAI /v1/chat/completions endpoint. - OpenAI, -} - -impl CacheNamespace { - fn prefix(self) -> &'static str { - match self { - Self::Anthropic => "anth", - Self::OpenAI => "oai", - } - } -} - /// Pluggable cache backend trait. Implementations must be Send + Sync /// for use behind Arc in axum handlers. pub trait CacheBackend: Send + Sync { @@ -69,863 +60,3 @@ pub trait CacheBackend: Send + Sync { ttl_secs: u64, ) -> impl std::future::Future + Send; } - -/// Compute a deterministic cache key for a request body. -/// -/// Extracts the fields that affect response content, sorts them via BTreeMap, -/// serializes to canonical JSON, SHA-256 hashes the result, and prepends the -/// namespace prefix. -pub fn cache_key_for_request( - body: &serde_json::Value, - ns: CacheNamespace, - scope: &CacheScope<'_>, -) -> String { - let mut hasher = Sha256::new(); - write_canonical_cache_body(&mut hasher, body, scope); - let hash = hasher.finalize(); - let hex = hex::encode(hash); - format!("{}:{}", ns.prefix(), hex) -} - -enum CacheField<'a> { - Json(&'a str, &'a serde_json::Value), - Str(&'static str, &'a str), -} - -impl<'a> CacheField<'a> { - fn key(&self) -> &str { - match self { - Self::Json(key, _) | Self::Str(key, _) => key, - } - } -} - -struct HashWriter<'a> { - hasher: &'a mut Sha256, -} - -impl Write for HashWriter<'_> { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.hasher.update(buf); - Ok(buf.len()) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -fn write_canonical_cache_body( - hasher: &mut Sha256, - body: &serde_json::Value, - scope: &CacheScope<'_>, -) { - let mut fields = Vec::new(); - if let Some(obj) = body.as_object() { - fields.extend( - obj.iter() - .filter(|(key, value)| should_include_cache_field(key, value)) - .map(|(key, value)| CacheField::Json(key.as_str(), value)), - ); - } - fields.push(CacheField::Str("_scope_auth", scope.auth_identity)); - fields.push(CacheField::Str("_scope_backend", scope.backend_name)); - if let Some(namespace) = scope.namespace { - fields.push(CacheField::Str("_scope_cache_namespace", namespace)); - } - fields.sort_unstable_by(|a, b| a.key().cmp(b.key())); - - let mut writer = HashWriter { hasher }; - writer - .write_all(b"{") - .expect("hash writer should not fail writing object start"); - for (idx, field) in fields.iter().enumerate() { - if idx > 0 { - writer - .write_all(b",") - .expect("hash writer should not fail writing separator"); - } - serde_json::to_writer(&mut writer, field.key()) - .expect("hash writer should not fail writing key"); - writer - .write_all(b":") - .expect("hash writer should not fail writing colon"); - match field { - CacheField::Json(_, value) => serde_json::to_writer(&mut writer, value) - .expect("hash writer should not fail writing JSON value"), - CacheField::Str(_, value) => serde_json::to_writer(&mut writer, value) - .expect("hash writer should not fail writing string value"), - } - } - writer - .write_all(b"}") - .expect("hash writer should not fail writing object end"); -} - -fn should_include_cache_field(key: &str, value: &serde_json::Value) -> bool { - if value.is_null() { - return false; - } - // Exclude fields that do not affect the backend response: - // - stream / stream_options: transport only (a cached non-stream response is - // replayed as a stream and vice versa). - // - cache: request cache controls, handled outside response-content hashing. - // - _scope_*: added separately as scope fields. - // - user / metadata: tracking fields documented as "Ignored" (anyllm_translate - // ChatCompletionRequest user, anthropic Metadata). Hashing them fragments the - // cache per end-user with no correctness benefit (tenant isolation is already - // provided by _scope_auth). - // - // parallel_tool_calls is NOT excluded: backends that honor it (e.g. OpenAI) - // produce different output for true vs false, so it must be part of the cache - // identity. (Gemini/Vertex have it stripped before dispatch by the tool policy.) - !matches!( - key, - "stream" - | "stream_options" - | "cache" - | "_scope_auth" - | "_scope_backend" - | "_scope_cache_namespace" - | "user" - | "metadata" - ) -} - -pub struct CacheScope<'a> { - pub backend_name: &'a str, - pub auth_identity: &'a str, - pub namespace: Option<&'a str>, -} - -/// Per-request cache controls after combining local and LiteLLM-style fields. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct CacheControl { - /// Whether to read an existing cached response before calling upstream. - pub lookup: bool, - /// Whether to store a successful upstream response after the call. - pub store: bool, - /// Per-entry store TTL override in seconds. - pub ttl_secs: Option, - /// Maximum acceptable age for an existing cached entry. - pub max_age_secs: Option, - /// Optional caller namespace for exact-match cache isolation. - pub namespace: Option, - /// Parsed for LiteLLM compatibility. Currently no behavior change. - pub use_cache: bool, -} - -impl Default for CacheControl { - fn default() -> Self { - Self { - lookup: true, - store: true, - ttl_secs: None, - max_age_secs: None, - namespace: None, - use_cache: false, - } - } -} - -/// Parse the optional `cache_ttl_secs` field from a request body. -/// -/// Returns: -/// - `Ok(None)` if the field is absent or null (use default TTL). -/// - `Ok(Some(0))` if explicitly 0 (bypass cache). -/// - `Ok(Some(n))` for valid positive values up to MAX_TTL_SECS. -/// - `Err(message)` for negative values, values > MAX_TTL_SECS, or non-numeric. -pub fn parse_cache_ttl(body: &serde_json::Value) -> Result, String> { - let Some(val) = body.get("cache_ttl_secs") else { - return Ok(None); - }; - if val.is_null() { - return Ok(None); - } - if let Some(n) = val.as_u64() { - if n > MAX_TTL_SECS { - return Err(format!("cache_ttl_secs must be <= {MAX_TTL_SECS}, got {n}")); - } - return Ok(Some(n)); - } - if let Some(n) = val.as_i64() { - // Negative values are invalid - return Err(format!("cache_ttl_secs must be non-negative, got {n}")); - } - if let Some(n) = val.as_f64() { - if n < 0.0 { - return Err(format!("cache_ttl_secs must be non-negative, got {n}")); - } - let truncated = n as u64; - if truncated > MAX_TTL_SECS { - return Err(format!( - "cache_ttl_secs must be <= {MAX_TTL_SECS}, got {truncated}" - )); - } - return Ok(Some(truncated)); - } - Err(format!("cache_ttl_secs must be a number, got {}", val)) -} - -/// Parse local `cache_ttl_secs` and LiteLLM-compatible top-level `cache`. -pub fn parse_cache_control(body: &serde_json::Value) -> Result { - let mut control = CacheControl::default(); - match parse_cache_ttl(body)? { - Some(0) => { - control.lookup = false; - control.store = false; - control.ttl_secs = Some(0); - } - Some(ttl) => { - control.ttl_secs = Some(ttl); - } - None => {} - } - - let Some(cache_value) = body.get("cache") else { - return Ok(control); - }; - if cache_value.is_null() { - return Ok(control); - } - let Some(cache_obj) = cache_value.as_object() else { - return Err(format!("cache must be an object, got {}", cache_value)); - }; - - if parse_cache_bool_field(cache_obj, "no-cache")?.unwrap_or(false) { - control.lookup = false; - } - if parse_cache_bool_field(cache_obj, "no-store")?.unwrap_or(false) { - control.store = false; - } - if let Some(use_cache) = parse_cache_bool_field(cache_obj, "use-cache")? { - control.use_cache = use_cache; - } - if let Some(ttl) = parse_cache_secs_field(cache_obj, "ttl")? { - control.ttl_secs = Some(ttl); - } - if let Some(max_age) = parse_cache_secs_field(cache_obj, "s-maxage")? { - control.max_age_secs = Some(max_age); - } - if let Some(max_age) = parse_cache_secs_field(cache_obj, "s-max-age")? { - control.max_age_secs = Some(max_age); - } - if let Some(namespace_value) = cache_obj.get("namespace") { - if !namespace_value.is_null() { - let Some(namespace) = namespace_value.as_str() else { - return Err(format!( - "cache.namespace must be a string, got {}", - namespace_value - )); - }; - if !namespace.is_empty() { - control.namespace = Some(namespace.to_string()); - } - } - } - - Ok(control) -} - -fn parse_cache_bool_field( - cache_obj: &serde_json::Map, - field: &str, -) -> Result, String> { - let Some(value) = cache_obj.get(field) else { - return Ok(None); - }; - if value.is_null() { - return Ok(None); - } - value - .as_bool() - .map(Some) - .ok_or_else(|| format!("cache.{field} must be a boolean, got {value}")) -} - -fn parse_cache_secs_field( - cache_obj: &serde_json::Map, - field: &str, -) -> Result, String> { - let Some(value) = cache_obj.get(field) else { - return Ok(None); - }; - if value.is_null() { - return Ok(None); - } - if let Some(n) = value.as_u64() { - if n > MAX_TTL_SECS { - return Err(format!("cache.{field} must be <= {MAX_TTL_SECS}, got {n}")); - } - return Ok(Some(n)); - } - if let Some(n) = value.as_i64() { - return Err(format!("cache.{field} must be non-negative, got {n}")); - } - if let Some(n) = value.as_f64() { - if n < 0.0 { - return Err(format!("cache.{field} must be non-negative, got {n}")); - } - let truncated = n as u64; - if truncated > MAX_TTL_SECS { - return Err(format!( - "cache.{field} must be <= {MAX_TTL_SECS}, got {truncated}" - )); - } - return Ok(Some(truncated)); - } - Err(format!("cache.{field} must be a number, got {}", value)) -} - -pub fn cache_entry_is_fresh(entry: &CacheEntry, max_age_secs: Option) -> bool { - match max_age_secs { - Some(max_age) => entry.created_at.elapsed() <= Duration::from_secs(max_age), - None => true, - } -} - -/// Configuration for the cache subsystem. -#[derive(Debug, Clone)] -pub struct CacheConfig { - /// Default TTL in seconds for cached responses. - pub ttl_secs: u64, - /// Maximum number of entries in the in-memory cache. - pub max_entries: u64, - /// Optional Redis URL. Used by the Redis L2 cache backend (requires `redis` feature) - /// and distributed rate limiting. When set, responses are cached in Redis in addition - /// to the in-memory L1 cache, and rate limit state is shared across proxy instances. - pub redis_url: Option, -} - -impl Default for CacheConfig { - fn default() -> Self { - Self { - ttl_secs: 300, - max_entries: 10_000, - redis_url: None, - } - } -} - -impl CacheConfig { - /// Load cache configuration from environment variables. - pub fn from_env() -> Self { - let ttl_secs = std::env::var("CACHE_TTL_SECS") - .ok() - .and_then(|v| v.parse().ok()) - .unwrap_or(300); - let max_entries = std::env::var("CACHE_MAX_ENTRIES") - .ok() - .and_then(|v| v.parse().ok()) - .unwrap_or(10_000); - let redis_url = std::env::var("REDIS_URL").ok(); - Self { - ttl_secs, - max_entries, - redis_url, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn cache_key_deterministic_same_fields() { - let body = serde_json::json!({ - "model": "claude-sonnet-4-6", - "messages": [{"role": "user", "content": "hello"}], - "temperature": 0.7, - "max_tokens": 100 - }); - let key1 = cache_key_for_request( - &body, - CacheNamespace::Anthropic, - &CacheScope { - backend_name: "openai", - auth_identity: "k1", - namespace: None, - }, - ); - let key2 = cache_key_for_request( - &body, - CacheNamespace::Anthropic, - &CacheScope { - backend_name: "openai", - auth_identity: "k1", - namespace: None, - }, - ); - assert_eq!(key1, key2); - assert!(key1.starts_with("anth:")); - } - - #[test] - fn cache_key_different_for_different_temperature() { - let body1 = serde_json::json!({ - "model": "claude-sonnet-4-6", - "messages": [{"role": "user", "content": "hello"}], - "temperature": 0.7 - }); - let body2 = serde_json::json!({ - "model": "claude-sonnet-4-6", - "messages": [{"role": "user", "content": "hello"}], - "temperature": 0.9 - }); - let key1 = cache_key_for_request( - &body1, - CacheNamespace::Anthropic, - &CacheScope { - backend_name: "openai", - auth_identity: "k1", - namespace: None, - }, - ); - let key2 = cache_key_for_request( - &body2, - CacheNamespace::Anthropic, - &CacheScope { - backend_name: "openai", - auth_identity: "k1", - namespace: None, - }, - ); - assert_ne!(key1, key2); - } - - #[test] - fn cache_key_ignores_field_order() { - // JSON object field order should not affect the key because we - // extract into a BTreeMap. - let body1 = serde_json::json!({ - "model": "gpt-4o", - "temperature": 0.5, - "messages": [{"role": "user", "content": "hi"}] - }); - let body2 = serde_json::json!({ - "messages": [{"role": "user", "content": "hi"}], - "model": "gpt-4o", - "temperature": 0.5 - }); - let key1 = cache_key_for_request( - &body1, - CacheNamespace::OpenAI, - &CacheScope { - backend_name: "openai", - auth_identity: "k1", - namespace: None, - }, - ); - let key2 = cache_key_for_request( - &body2, - CacheNamespace::OpenAI, - &CacheScope { - backend_name: "openai", - auth_identity: "k1", - namespace: None, - }, - ); - assert_eq!(key1, key2); - } - - #[test] - fn cache_key_ignores_non_cache_fields() { - let body1 = serde_json::json!({ - "model": "gpt-4o", - "messages": [{"role": "user", "content": "hi"}], - "stream": true - }); - let body2 = serde_json::json!({ - "model": "gpt-4o", - "messages": [{"role": "user", "content": "hi"}] - }); - let key1 = cache_key_for_request( - &body1, - CacheNamespace::OpenAI, - &CacheScope { - backend_name: "openai", - auth_identity: "k1", - namespace: None, - }, - ); - let key2 = cache_key_for_request( - &body2, - CacheNamespace::OpenAI, - &CacheScope { - backend_name: "openai", - auth_identity: "k1", - namespace: None, - }, - ); - assert_eq!(key1, key2); - } - - #[test] - fn cache_key_namespace_differs() { - let body = serde_json::json!({ - "model": "test", - "messages": [] - }); - let anth = cache_key_for_request( - &body, - CacheNamespace::Anthropic, - &CacheScope { - backend_name: "openai", - auth_identity: "k1", - namespace: None, - }, - ); - let oai = cache_key_for_request( - &body, - CacheNamespace::OpenAI, - &CacheScope { - backend_name: "openai", - auth_identity: "k1", - namespace: None, - }, - ); - assert_ne!(anth, oai); - assert!(anth.starts_with("anth:")); - assert!(oai.starts_with("oai:")); - } - - #[test] - fn cache_key_null_field_same_as_absent() { - let body1 = serde_json::json!({ - "model": "gpt-4o", - "messages": [], - "temperature": null - }); - let body2 = serde_json::json!({ - "model": "gpt-4o", - "messages": [] - }); - let key1 = cache_key_for_request( - &body1, - CacheNamespace::OpenAI, - &CacheScope { - backend_name: "openai", - auth_identity: "k1", - namespace: None, - }, - ); - let key2 = cache_key_for_request( - &body2, - CacheNamespace::OpenAI, - &CacheScope { - backend_name: "openai", - auth_identity: "k1", - namespace: None, - }, - ); - assert_eq!(key1, key2); - } - - #[test] - fn parse_cache_ttl_absent() { - let body = serde_json::json!({"model": "test"}); - assert_eq!(parse_cache_ttl(&body).unwrap(), None); - } - - #[test] - fn parse_cache_ttl_null() { - let body = serde_json::json!({"cache_ttl_secs": null}); - assert_eq!(parse_cache_ttl(&body).unwrap(), None); - } - - #[test] - fn parse_cache_ttl_zero() { - let body = serde_json::json!({"cache_ttl_secs": 0}); - assert_eq!(parse_cache_ttl(&body).unwrap(), Some(0)); - } - - #[test] - fn parse_cache_ttl_valid() { - let body = serde_json::json!({"cache_ttl_secs": 600}); - assert_eq!(parse_cache_ttl(&body).unwrap(), Some(600)); - } - - #[test] - fn parse_cache_ttl_max() { - let body = serde_json::json!({"cache_ttl_secs": 86400}); - assert_eq!(parse_cache_ttl(&body).unwrap(), Some(86400)); - } - - #[test] - fn parse_cache_ttl_over_max() { - let body = serde_json::json!({"cache_ttl_secs": 86401}); - assert!(parse_cache_ttl(&body).is_err()); - } - - #[test] - fn parse_cache_ttl_negative() { - let body = serde_json::json!({"cache_ttl_secs": -1}); - assert!(parse_cache_ttl(&body).is_err()); - } - - #[test] - fn parse_cache_ttl_string() { - let body = serde_json::json!({"cache_ttl_secs": "not a number"}); - assert!(parse_cache_ttl(&body).is_err()); - } - - #[test] - fn cache_key_differs_for_different_cache_ttl_secs() { - let body1 = serde_json::json!({ - "model": "gpt-4o", - "messages": [{"role": "user", "content": "hi"}], - "cache_ttl_secs": 60 - }); - let body2 = serde_json::json!({ - "model": "gpt-4o", - "messages": [{"role": "user", "content": "hi"}], - "cache_ttl_secs": 3600 - }); - let key1 = cache_key_for_request( - &body1, - CacheNamespace::OpenAI, - &CacheScope { - backend_name: "openai", - auth_identity: "k1", - namespace: None, - }, - ); - let key2 = cache_key_for_request( - &body2, - CacheNamespace::OpenAI, - &CacheScope { - backend_name: "openai", - auth_identity: "k1", - namespace: None, - }, - ); - assert_ne!( - key1, key2, - "different cache_ttl_secs must produce different cache keys" - ); - } - - #[test] - fn cache_key_ignores_litellm_cache_controls() { - let body1 = serde_json::json!({ - "model": "gpt-4o", - "messages": [{"role": "user", "content": "hi"}], - "cache": {"ttl": 60, "no-cache": true} - }); - let body2 = serde_json::json!({ - "model": "gpt-4o", - "messages": [{"role": "user", "content": "hi"}], - "cache": {"ttl": 3600, "no-store": true} - }); - - assert_eq!(openai_key(&body1), openai_key(&body2)); - } - - #[test] - fn cache_key_namespace_control_separates_keys() { - let body = serde_json::json!({ - "model": "gpt-4o", - "messages": [{"role": "user", "content": "hi"}] - }); - let key1 = cache_key_for_request( - &body, - CacheNamespace::OpenAI, - &CacheScope { - backend_name: "openai", - auth_identity: "k1", - namespace: Some("tenant-a"), - }, - ); - let key2 = cache_key_for_request( - &body, - CacheNamespace::OpenAI, - &CacheScope { - backend_name: "openai", - auth_identity: "k1", - namespace: Some("tenant-b"), - }, - ); - - assert_ne!(key1, key2); - } - - #[test] - fn parse_cache_control_litellm_fields() { - let body = serde_json::json!({ - "cache": { - "ttl": 120, - "no-cache": true, - "no-store": false, - "s-maxage": 30, - "namespace": "tenant-a", - "use-cache": true - } - }); - - let control = parse_cache_control(&body).unwrap(); - - assert!(!control.lookup); - assert!(control.store); - assert_eq!(control.ttl_secs, Some(120)); - assert_eq!(control.max_age_secs, Some(30)); - assert_eq!(control.namespace.as_deref(), Some("tenant-a")); - assert!(control.use_cache); - } - - #[test] - fn parse_cache_control_preserves_cache_ttl_secs_bypass() { - let body = serde_json::json!({ - "cache_ttl_secs": 0, - "cache": {"ttl": 120} - }); - - let control = parse_cache_control(&body).unwrap(); - - assert!(!control.lookup); - assert!(!control.store); - assert_eq!(control.ttl_secs, Some(120)); - } - - #[test] - fn parse_cache_control_rejects_invalid_cache_object() { - let body = serde_json::json!({"cache": true}); - - assert!(parse_cache_control(&body).is_err()); - } - - #[test] - fn cache_entry_s_maxage_rejects_stale_entries() { - let entry = CacheEntry { - response_body: Bytes::from_static(b"{}"), - model: "gpt-4o".to_string(), - created_at: Instant::now() - std::time::Duration::from_secs(10), - ttl_secs: None, - }; - - assert!(!cache_entry_is_fresh(&entry, Some(5))); - assert!(cache_entry_is_fresh(&entry, Some(30))); - assert!(cache_entry_is_fresh(&entry, None)); - } - - fn test_scope() -> CacheScope<'static> { - CacheScope { - backend_name: "openai", - auth_identity: "k1", - namespace: None, - } - } - - fn anthropic_key(body: &serde_json::Value) -> String { - cache_key_for_request(body, CacheNamespace::Anthropic, &test_scope()) - } - - fn openai_key(body: &serde_json::Value) -> String { - cache_key_for_request(body, CacheNamespace::OpenAI, &test_scope()) - } - - #[test] - fn cache_key_includes_anthropic_response_affecting_fields() { - let base = serde_json::json!({ - "model": "claude-sonnet-4-6", - "max_tokens": 128, - "messages": [{"role": "user", "content": "hi"}] - }); - - let with_top_k = serde_json::json!({ - "model": "claude-sonnet-4-6", - "max_tokens": 128, - "messages": [{"role": "user", "content": "hi"}], - "top_k": 10 - }); - let with_stop_sequences = serde_json::json!({ - "model": "claude-sonnet-4-6", - "max_tokens": 128, - "messages": [{"role": "user", "content": "hi"}], - "stop_sequences": ["END"] - }); - let with_thinking = serde_json::json!({ - "model": "claude-sonnet-4-6", - "max_tokens": 128, - "messages": [{"role": "user", "content": "hi"}], - "thinking": {"type": "enabled", "budget_tokens": 1024} - }); - - assert_ne!(anthropic_key(&base), anthropic_key(&with_top_k)); - assert_ne!(anthropic_key(&base), anthropic_key(&with_stop_sequences)); - assert_ne!(anthropic_key(&base), anthropic_key(&with_thinking)); - } - - #[test] - fn cache_key_includes_unknown_extra_fields() { - let base = serde_json::json!({ - "model": "gpt-4o", - "messages": [{"role": "user", "content": "hi"}] - }); - let with_extra = serde_json::json!({ - "model": "gpt-4o", - "messages": [{"role": "user", "content": "hi"}], - "prediction": {"type": "content", "content": "expected"} - }); - - assert_ne!(openai_key(&base), openai_key(&with_extra)); - } - - #[test] - fn cache_key_ignores_tracking_fields() { - let base = serde_json::json!({ - "model": "gpt-4o", - "messages": [{"role": "user", "content": "hi"}] - }); - let with_user = serde_json::json!({ - "model": "gpt-4o", - "messages": [{"role": "user", "content": "hi"}], - "user": "end-user-123" - }); - let with_metadata = serde_json::json!({ - "model": "claude-sonnet-4-6", - "max_tokens": 128, - "messages": [{"role": "user", "content": "hi"}], - "metadata": {"user_id": "session-abc"} - }); - let metadata_base = serde_json::json!({ - "model": "claude-sonnet-4-6", - "max_tokens": 128, - "messages": [{"role": "user", "content": "hi"}] - }); - - assert_eq!(openai_key(&base), openai_key(&with_user)); - assert_eq!(anthropic_key(&metadata_base), anthropic_key(&with_metadata)); - } - - #[test] - fn cache_key_includes_parallel_tool_calls() { - let base = serde_json::json!({ - "model": "gpt-4o", - "messages": [{"role": "user", "content": "hi"}], - "tools": [{ - "type": "function", - "function": { - "name": "lookup", - "description": "lookup", - "parameters": {"type": "object", "properties": {}} - } - }] - }); - let with_parallel_tool_calls = serde_json::json!({ - "model": "gpt-4o", - "messages": [{"role": "user", "content": "hi"}], - "tools": [{ - "type": "function", - "function": { - "name": "lookup", - "description": "lookup", - "parameters": {"type": "object", "properties": {}} - } - }], - "parallel_tool_calls": false - }); - - assert_ne!(openai_key(&base), openai_key(&with_parallel_tool_calls)); - } -} diff --git a/crates/proxy/src/cache/tests.rs b/crates/proxy/src/cache/tests.rs new file mode 100644 index 0000000..ae5a8fb --- /dev/null +++ b/crates/proxy/src/cache/tests.rs @@ -0,0 +1,501 @@ +use super::*; +use bytes::Bytes; +use std::time::Instant; + +#[test] +fn cache_key_deterministic_same_fields() { + let body = serde_json::json!({ + "model": "claude-sonnet-4-6", + "messages": [{"role": "user", "content": "hello"}], + "temperature": 0.7, + "max_tokens": 100 + }); + let key1 = cache_key_for_request( + &body, + CacheNamespace::Anthropic, + &CacheScope { + backend_name: "openai", + auth_identity: "k1", + namespace: None, + }, + ); + let key2 = cache_key_for_request( + &body, + CacheNamespace::Anthropic, + &CacheScope { + backend_name: "openai", + auth_identity: "k1", + namespace: None, + }, + ); + assert_eq!(key1, key2); + assert!(key1.starts_with("anth:")); +} + +#[test] +fn cache_key_different_for_different_temperature() { + let body1 = serde_json::json!({ + "model": "claude-sonnet-4-6", + "messages": [{"role": "user", "content": "hello"}], + "temperature": 0.7 + }); + let body2 = serde_json::json!({ + "model": "claude-sonnet-4-6", + "messages": [{"role": "user", "content": "hello"}], + "temperature": 0.9 + }); + let key1 = cache_key_for_request( + &body1, + CacheNamespace::Anthropic, + &CacheScope { + backend_name: "openai", + auth_identity: "k1", + namespace: None, + }, + ); + let key2 = cache_key_for_request( + &body2, + CacheNamespace::Anthropic, + &CacheScope { + backend_name: "openai", + auth_identity: "k1", + namespace: None, + }, + ); + assert_ne!(key1, key2); +} + +#[test] +fn cache_key_ignores_field_order() { + // JSON object field order should not affect the key because we + // extract into a BTreeMap. + let body1 = serde_json::json!({ + "model": "gpt-4o", + "temperature": 0.5, + "messages": [{"role": "user", "content": "hi"}] + }); + let body2 = serde_json::json!({ + "messages": [{"role": "user", "content": "hi"}], + "model": "gpt-4o", + "temperature": 0.5 + }); + let key1 = cache_key_for_request( + &body1, + CacheNamespace::OpenAI, + &CacheScope { + backend_name: "openai", + auth_identity: "k1", + namespace: None, + }, + ); + let key2 = cache_key_for_request( + &body2, + CacheNamespace::OpenAI, + &CacheScope { + backend_name: "openai", + auth_identity: "k1", + namespace: None, + }, + ); + assert_eq!(key1, key2); +} + +#[test] +fn cache_key_ignores_non_cache_fields() { + let body1 = serde_json::json!({ + "model": "gpt-4o", + "messages": [{"role": "user", "content": "hi"}], + "stream": true + }); + let body2 = serde_json::json!({ + "model": "gpt-4o", + "messages": [{"role": "user", "content": "hi"}] + }); + let key1 = cache_key_for_request( + &body1, + CacheNamespace::OpenAI, + &CacheScope { + backend_name: "openai", + auth_identity: "k1", + namespace: None, + }, + ); + let key2 = cache_key_for_request( + &body2, + CacheNamespace::OpenAI, + &CacheScope { + backend_name: "openai", + auth_identity: "k1", + namespace: None, + }, + ); + assert_eq!(key1, key2); +} + +#[test] +fn cache_key_namespace_differs() { + let body = serde_json::json!({ + "model": "test", + "messages": [] + }); + let anth = cache_key_for_request( + &body, + CacheNamespace::Anthropic, + &CacheScope { + backend_name: "openai", + auth_identity: "k1", + namespace: None, + }, + ); + let oai = cache_key_for_request( + &body, + CacheNamespace::OpenAI, + &CacheScope { + backend_name: "openai", + auth_identity: "k1", + namespace: None, + }, + ); + assert_ne!(anth, oai); + assert!(anth.starts_with("anth:")); + assert!(oai.starts_with("oai:")); +} + +#[test] +fn cache_key_null_field_same_as_absent() { + let body1 = serde_json::json!({ + "model": "gpt-4o", + "messages": [], + "temperature": null + }); + let body2 = serde_json::json!({ + "model": "gpt-4o", + "messages": [] + }); + let key1 = cache_key_for_request( + &body1, + CacheNamespace::OpenAI, + &CacheScope { + backend_name: "openai", + auth_identity: "k1", + namespace: None, + }, + ); + let key2 = cache_key_for_request( + &body2, + CacheNamespace::OpenAI, + &CacheScope { + backend_name: "openai", + auth_identity: "k1", + namespace: None, + }, + ); + assert_eq!(key1, key2); +} + +#[test] +fn parse_cache_ttl_absent() { + let body = serde_json::json!({"model": "test"}); + assert_eq!(parse_cache_ttl(&body).unwrap(), None); +} + +#[test] +fn parse_cache_ttl_null() { + let body = serde_json::json!({"cache_ttl_secs": null}); + assert_eq!(parse_cache_ttl(&body).unwrap(), None); +} + +#[test] +fn parse_cache_ttl_zero() { + let body = serde_json::json!({"cache_ttl_secs": 0}); + assert_eq!(parse_cache_ttl(&body).unwrap(), Some(0)); +} + +#[test] +fn parse_cache_ttl_valid() { + let body = serde_json::json!({"cache_ttl_secs": 600}); + assert_eq!(parse_cache_ttl(&body).unwrap(), Some(600)); +} + +#[test] +fn parse_cache_ttl_max() { + let body = serde_json::json!({"cache_ttl_secs": 86400}); + assert_eq!(parse_cache_ttl(&body).unwrap(), Some(86400)); +} + +#[test] +fn parse_cache_ttl_over_max() { + let body = serde_json::json!({"cache_ttl_secs": 86401}); + assert!(parse_cache_ttl(&body).is_err()); +} + +#[test] +fn parse_cache_ttl_negative() { + let body = serde_json::json!({"cache_ttl_secs": -1}); + assert!(parse_cache_ttl(&body).is_err()); +} + +#[test] +fn parse_cache_ttl_string() { + let body = serde_json::json!({"cache_ttl_secs": "not a number"}); + assert!(parse_cache_ttl(&body).is_err()); +} + +#[test] +fn cache_key_differs_for_different_cache_ttl_secs() { + let body1 = serde_json::json!({ + "model": "gpt-4o", + "messages": [{"role": "user", "content": "hi"}], + "cache_ttl_secs": 60 + }); + let body2 = serde_json::json!({ + "model": "gpt-4o", + "messages": [{"role": "user", "content": "hi"}], + "cache_ttl_secs": 3600 + }); + let key1 = cache_key_for_request( + &body1, + CacheNamespace::OpenAI, + &CacheScope { + backend_name: "openai", + auth_identity: "k1", + namespace: None, + }, + ); + let key2 = cache_key_for_request( + &body2, + CacheNamespace::OpenAI, + &CacheScope { + backend_name: "openai", + auth_identity: "k1", + namespace: None, + }, + ); + assert_ne!( + key1, key2, + "different cache_ttl_secs must produce different cache keys" + ); +} + +#[test] +fn cache_key_ignores_litellm_cache_controls() { + let body1 = serde_json::json!({ + "model": "gpt-4o", + "messages": [{"role": "user", "content": "hi"}], + "cache": {"ttl": 60, "no-cache": true} + }); + let body2 = serde_json::json!({ + "model": "gpt-4o", + "messages": [{"role": "user", "content": "hi"}], + "cache": {"ttl": 3600, "no-store": true} + }); + + assert_eq!(openai_key(&body1), openai_key(&body2)); +} + +#[test] +fn cache_key_namespace_control_separates_keys() { + let body = serde_json::json!({ + "model": "gpt-4o", + "messages": [{"role": "user", "content": "hi"}] + }); + let key1 = cache_key_for_request( + &body, + CacheNamespace::OpenAI, + &CacheScope { + backend_name: "openai", + auth_identity: "k1", + namespace: Some("tenant-a"), + }, + ); + let key2 = cache_key_for_request( + &body, + CacheNamespace::OpenAI, + &CacheScope { + backend_name: "openai", + auth_identity: "k1", + namespace: Some("tenant-b"), + }, + ); + + assert_ne!(key1, key2); +} + +#[test] +fn parse_cache_control_litellm_fields() { + let body = serde_json::json!({ + "cache": { + "ttl": 120, + "no-cache": true, + "no-store": false, + "s-maxage": 30, + "namespace": "tenant-a", + "use-cache": true + } + }); + + let control = parse_cache_control(&body).unwrap(); + + assert!(!control.lookup); + assert!(control.store); + assert_eq!(control.ttl_secs, Some(120)); + assert_eq!(control.max_age_secs, Some(30)); + assert_eq!(control.namespace.as_deref(), Some("tenant-a")); + assert!(control.use_cache); +} + +#[test] +fn parse_cache_control_preserves_cache_ttl_secs_bypass() { + let body = serde_json::json!({ + "cache_ttl_secs": 0, + "cache": {"ttl": 120} + }); + + let control = parse_cache_control(&body).unwrap(); + + assert!(!control.lookup); + assert!(!control.store); + assert_eq!(control.ttl_secs, Some(120)); +} + +#[test] +fn parse_cache_control_rejects_invalid_cache_object() { + let body = serde_json::json!({"cache": true}); + + assert!(parse_cache_control(&body).is_err()); +} + +#[test] +fn cache_entry_s_maxage_rejects_stale_entries() { + let entry = CacheEntry { + response_body: Bytes::from_static(b"{}"), + model: "gpt-4o".to_string(), + created_at: Instant::now() - std::time::Duration::from_secs(10), + ttl_secs: None, + }; + + assert!(!cache_entry_is_fresh(&entry, Some(5))); + assert!(cache_entry_is_fresh(&entry, Some(30))); + assert!(cache_entry_is_fresh(&entry, None)); +} + +fn test_scope() -> CacheScope<'static> { + CacheScope { + backend_name: "openai", + auth_identity: "k1", + namespace: None, + } +} + +fn anthropic_key(body: &serde_json::Value) -> String { + cache_key_for_request(body, CacheNamespace::Anthropic, &test_scope()) +} + +fn openai_key(body: &serde_json::Value) -> String { + cache_key_for_request(body, CacheNamespace::OpenAI, &test_scope()) +} + +#[test] +fn cache_key_includes_anthropic_response_affecting_fields() { + let base = serde_json::json!({ + "model": "claude-sonnet-4-6", + "max_tokens": 128, + "messages": [{"role": "user", "content": "hi"}] + }); + + let with_top_k = serde_json::json!({ + "model": "claude-sonnet-4-6", + "max_tokens": 128, + "messages": [{"role": "user", "content": "hi"}], + "top_k": 10 + }); + let with_stop_sequences = serde_json::json!({ + "model": "claude-sonnet-4-6", + "max_tokens": 128, + "messages": [{"role": "user", "content": "hi"}], + "stop_sequences": ["END"] + }); + let with_thinking = serde_json::json!({ + "model": "claude-sonnet-4-6", + "max_tokens": 128, + "messages": [{"role": "user", "content": "hi"}], + "thinking": {"type": "enabled", "budget_tokens": 1024} + }); + + assert_ne!(anthropic_key(&base), anthropic_key(&with_top_k)); + assert_ne!(anthropic_key(&base), anthropic_key(&with_stop_sequences)); + assert_ne!(anthropic_key(&base), anthropic_key(&with_thinking)); +} + +#[test] +fn cache_key_includes_unknown_extra_fields() { + let base = serde_json::json!({ + "model": "gpt-4o", + "messages": [{"role": "user", "content": "hi"}] + }); + let with_extra = serde_json::json!({ + "model": "gpt-4o", + "messages": [{"role": "user", "content": "hi"}], + "prediction": {"type": "content", "content": "expected"} + }); + + assert_ne!(openai_key(&base), openai_key(&with_extra)); +} + +#[test] +fn cache_key_ignores_tracking_fields() { + let base = serde_json::json!({ + "model": "gpt-4o", + "messages": [{"role": "user", "content": "hi"}] + }); + let with_user = serde_json::json!({ + "model": "gpt-4o", + "messages": [{"role": "user", "content": "hi"}], + "user": "end-user-123" + }); + let with_metadata = serde_json::json!({ + "model": "claude-sonnet-4-6", + "max_tokens": 128, + "messages": [{"role": "user", "content": "hi"}], + "metadata": {"user_id": "session-abc"} + }); + let metadata_base = serde_json::json!({ + "model": "claude-sonnet-4-6", + "max_tokens": 128, + "messages": [{"role": "user", "content": "hi"}] + }); + + assert_eq!(openai_key(&base), openai_key(&with_user)); + assert_eq!(anthropic_key(&metadata_base), anthropic_key(&with_metadata)); +} + +#[test] +fn cache_key_includes_parallel_tool_calls() { + let base = serde_json::json!({ + "model": "gpt-4o", + "messages": [{"role": "user", "content": "hi"}], + "tools": [{ + "type": "function", + "function": { + "name": "lookup", + "description": "lookup", + "parameters": {"type": "object", "properties": {}} + } + }] + }); + let with_parallel_tool_calls = serde_json::json!({ + "model": "gpt-4o", + "messages": [{"role": "user", "content": "hi"}], + "tools": [{ + "type": "function", + "function": { + "name": "lookup", + "description": "lookup", + "parameters": {"type": "object", "properties": {}} + } + }], + "parallel_tool_calls": false + }); + + assert_ne!(openai_key(&base), openai_key(&with_parallel_tool_calls)); +} diff --git a/crates/proxy/src/config/litellm/mod.rs b/crates/proxy/src/config/litellm/mod.rs index 88fb88d..60483e3 100644 --- a/crates/proxy/src/config/litellm/mod.rs +++ b/crates/proxy/src/config/litellm/mod.rs @@ -1,667 +1,18 @@ -/// LiteLLM config.yaml parser. -/// -/// Accepts LiteLLM's YAML config format (model_list, litellm_settings, -/// router_settings, general_settings) and converts it to anyllm-proxy's -/// MultiConfig + ModelRouter. -use std::collections::HashMap; -use std::sync::Arc; +//! LiteLLM config.yaml parser. +//! +//! Accepts LiteLLM's YAML config format (model_list, litellm_settings, +//! router_settings, general_settings) and converts it to anyllm-proxy's +//! MultiConfig + ModelRouter. +//! +//! [`types`] holds the Serde structs mapping the LiteLLM schema; [`parser`] +//! holds the conversion functions. -use indexmap::IndexMap; -use serde::Deserialize; +mod parser; +mod types; -use super::model_router::{Deployment, ModelRouter, RoutingStrategy}; -use super::single::validate_gcp_identifier; -use super::{ - resolve_env_value, validate_base_url, BackendAuth, BackendConfig, BackendKind, ModelMapping, - MultiConfig, OpenAIApiFormat, TlsConfig, -}; - -// ---- Serde structs for LiteLLM config.yaml ---- - -/// Root structure of a LiteLLM `config.yaml` file. -#[derive(Deserialize)] -pub(crate) struct LiteLLMConfig { - #[serde(default)] - model_list: Vec, - #[serde(default)] - litellm_settings: Option, - #[serde(default)] - router_settings: Option, - #[serde(default)] - general_settings: Option, -} - -#[derive(Deserialize)] -struct LiteLLMModelEntry { - model_name: String, - litellm_params: LiteLLMParams, -} - -#[derive(Deserialize)] -struct LiteLLMParams { - model: String, - api_base: Option, - api_key: Option, - rpm: Option, - tpm: Option, - weight: Option, - // Azure-specific - api_version: Option, - // Vertex-specific - vertex_project: Option, - vertex_location: Option, - // Bedrock-specific - aws_access_key_id: Option, - aws_secret_access_key: Option, - aws_region_name: Option, - // Catch unknown fields silently (LiteLLM has many we don't support). - #[serde(flatten)] - _extra: serde_json::Map, -} - -#[derive(Deserialize)] -struct LiteLLMSettings { - #[serde(default)] - num_retries: Option, - #[serde(default)] - request_timeout: Option, - #[serde(default)] - callbacks: Vec, - #[serde(flatten)] - _extra: serde_json::Map, -} - -#[derive(Deserialize)] -struct RouterSettings { - #[serde(default)] - routing_strategy: Option, - #[serde(flatten)] - _extra: serde_json::Map, -} - -/// Map LiteLLM routing_strategy string to our enum. -pub(crate) fn parse_routing_strategy_str(s: &str) -> RoutingStrategy { - match s.to_ascii_lowercase().replace('_', "-").as_str() { - "simple-shuffle" | "round-robin" => RoutingStrategy::RoundRobin, - "least-busy" => RoutingStrategy::LeastBusy, - "latency-based-routing" | "latency-based" => RoutingStrategy::LatencyBased, - "usage-based-routing" | "usage-based" => RoutingStrategy::LeastBusy, - "weighted" => RoutingStrategy::Weighted, - "cost-based" => RoutingStrategy::CostBased, - other => { - tracing::warn!( - strategy = %other, - "unknown routing_strategy, falling back to round-robin" - ); - RoutingStrategy::RoundRobin - } - } -} - -#[derive(Deserialize)] -struct GeneralSettings { - master_key: Option, - #[serde(flatten)] - _extra: serde_json::Map, -} - -// ---- Provider parsing ---- - -/// Parse LiteLLM's "provider/model_name" format. -/// No prefix defaults to OpenAI (matches LiteLLM behavior). -/// Returns (kind, model_name, stub_provider) where stub_provider is set for -/// registry-resolved OpenAI-compatible providers so callers can use their default URL. -fn parse_provider_model( - model: &str, -) -> ( - BackendKind, - String, - Option<&'static anyllm_providers::ProviderDef>, -) { - let (provider, model_name) = model.split_once('/').unwrap_or(("openai", model)); - let mut stub_provider: Option<&'static anyllm_providers::ProviderDef> = None; - let kind = match provider.to_ascii_lowercase().as_str() { - "openai" => BackendKind::OpenAI, - "azure" => BackendKind::AzureOpenAI, - "vertex_ai" | "vertex" => BackendKind::Vertex, - "gemini" => BackendKind::Gemini, - "anthropic" => { - stub_provider = anyllm_providers::get_provider("anthropic"); - BackendKind::Anthropic - } - "bedrock" => BackendKind::Bedrock, - other => { - // Try the provider registry for known OpenAI-compatible providers - // (e.g. "groq", "together_ai", "mistral", etc.) - let prefix_with_slash = format!("{other}/"); - if let Some(p) = anyllm_providers::find_by_litellm_prefix(&prefix_with_slash) { - let resolved = match anyllm_providers::resolve_backend(p.id) { - Some(("openai", _)) => { - stub_provider = Some(p); - BackendKind::OpenAI - } - Some(("anthropic", _)) => BackendKind::Anthropic, - Some(("gemini", _)) => BackendKind::Gemini, - Some(("vertex", _)) => BackendKind::Vertex, - Some(("azure", _)) => BackendKind::AzureOpenAI, - Some(("bedrock", _)) => BackendKind::Bedrock, - _ => { - tracing::warn!(provider = %other, "provider found in registry but protocol not mappable, treating as openai-compatible"); - stub_provider = Some(p); - BackendKind::OpenAI - } - }; - resolved - } else { - tracing::warn!( - provider = %other, - "unknown LiteLLM provider, treating as openai-compatible" - ); - BackendKind::OpenAI - } - } - }; - (kind, model_name.to_string(), stub_provider) -} - -fn provider_id_for_litellm_model( - model: &str, - kind: &BackendKind, - stub_provider: Option<&'static anyllm_providers::ProviderDef>, -) -> String { - if let Some(provider) = stub_provider { - return provider.id.to_string(); - } - - let raw_provider = model - .split_once('/') - .map(|(provider, _)| provider) - .unwrap_or("openai") - .to_ascii_lowercase(); - - match kind { - BackendKind::OpenAI => raw_provider, - BackendKind::AzureOpenAI => "azure".to_string(), - BackendKind::Vertex => "vertex_ai".to_string(), - BackendKind::Gemini => "gemini".to_string(), - BackendKind::Anthropic => "anthropic".to_string(), - BackendKind::Bedrock => "bedrock".to_string(), - } -} - -// ---- Backend deduplication key ---- - -/// Unique identity for a backend: same kind + base_url + api_key share one connection pool. -#[derive(Hash, PartialEq, Eq, Clone)] -struct BackendKey { - kind: String, - provider_id: String, - base_url: String, - /// Hash of the API key (not the key itself) to avoid holding secrets in hash keys. - api_key_hash: u64, -} - -fn hash_string(s: &str) -> u64 { - use std::hash::{Hash, Hasher}; - let mut hasher = std::collections::hash_map::DefaultHasher::new(); - s.hash(&mut hasher); - hasher.finish() -} - -// ---- Conversion ---- - -/// Parse a LiteLLM config.yaml string and produce a MultiConfig + ModelRouter. -/// -/// # Panics -/// On invalid YAML, missing required fields, or unresolvable env var references. -/// Parsed result from a LiteLLM config file. -pub struct LiteLLMParsed { - pub multi_config: MultiConfig, - pub router: ModelRouter, - /// Webhook callback URLs from litellm_settings.callbacks (non-named entries). - pub callback_urls: Vec, - /// True when "langfuse" appears in litellm_settings.callbacks. - pub langfuse_requested: bool, - /// Resolved `general_settings.master_key`, if present. - /// Caller should apply as PROXY_API_KEYS if that var is not already set. - pub master_key: Option, -} - -/// Parse a LiteLLM YAML config and return the multi-backend config + model router pair. -pub fn from_litellm_yaml(yaml: &str) -> (MultiConfig, ModelRouter) { - let parsed = parse_litellm_yaml(yaml); - (parsed.multi_config, parsed.router) -} - -/// Parse a LiteLLM YAML config into the intermediate `LiteLLMParsed` struct. -/// Panics on invalid YAML (startup-time validation; misconfiguration is unrecoverable). -pub fn parse_litellm_yaml(yaml: &str) -> LiteLLMParsed { - let config: LiteLLMConfig = - serde_yaml::from_str(yaml).unwrap_or_else(|e| panic!("invalid LiteLLM config YAML: {e}")); - - if config.model_list.is_empty() { - panic!("LiteLLM config must define at least one entry in model_list"); - } - - // Resolve general_settings.master_key but do not call set_var here. - // The caller applies it in the consolidated env override block. - let master_key = if let Some(ref gs) = config.general_settings { - let mk = gs.master_key.as_ref().map(|mk| { - resolve_env_value(mk).unwrap_or_else(|e| panic!("general_settings.master_key: {e}")) - }); - // Log unsupported keys at warn. - for key in gs._extra.keys() { - tracing::warn!(key = %key, "unsupported general_settings key (ignored)"); - } - mk - } else { - None - }; - - if let Some(ref ls) = config.litellm_settings { - for key in ls._extra.keys() { - tracing::warn!(key = %key, "unsupported litellm_settings key (ignored)"); - } - } - - if let Some(ref rs) = config.router_settings { - for key in rs._extra.keys() { - tracing::warn!(key = %key, "unsupported router_settings key (ignored)"); - } - } - - let listen_port = std::env::var("LISTEN_PORT") - .ok() - .and_then(|v| v.parse().ok()) - .unwrap_or(3000); - - let log_bodies = std::env::var("LOG_BODIES") - .map(|v| v == "true" || v == "1") - .unwrap_or(false); - let redact_secrets = std::env::var("REDACT_SECRETS") - .map(|v| v == "true" || v == "1") - .unwrap_or(false); - let anthropic_thinking_repair = std::env::var("ANTHROPIC_THINKING_REPAIR") - .map(|v| v == "true" || v == "1") - .unwrap_or(false); - let pxpipe_compress = std::env::var("PXPIPE_COMPRESS") - .map(|v| v == "true" || v == "1") - .unwrap_or(false); - - let tls = TlsConfig::from_env(); - - // Group model_list entries into deduplicated backends + deployment list. - let mut backend_map: HashMap = HashMap::new(); - let mut backend_counter = 0u32; - // model_name -> Vec<(backend_name, actual_model, rpm, tpm)> - let mut model_deployments: HashMap> = HashMap::new(); - - for entry in &config.model_list { - let (kind, actual_model, stub_provider) = parse_provider_model(&entry.litellm_params.model); - let provider_id = - provider_id_for_litellm_model(&entry.litellm_params.model, &kind, stub_provider); - let params = &entry.litellm_params; - - let api_key = super::sanitize_api_key( - ¶ms - .api_key - .as_deref() - .map(|v| resolve_env_value(v).unwrap_or_else(|e| panic!("model_list api_key: {e}"))) - .unwrap_or_else(|| { - // Fall back to the provider's own env vars when no api_key in YAML. - stub_provider - .and_then(|p| p.env_vars.iter().find_map(|v| std::env::var(v).ok())) - .or_else(|| { - (kind == BackendKind::Anthropic) - .then(|| std::env::var("ANTHROPIC_AUTH_TOKEN").ok()) - .flatten() - }) - .unwrap_or_default() - }), - ); - - let base_url = resolve_base_url(&kind, params, stub_provider, &actual_model); - - let bk = BackendKey { - kind: format!("{kind:?}"), - provider_id: provider_id.clone(), - base_url: base_url.clone(), - api_key_hash: hash_string(&api_key), - }; - - let backend_name = if let Some((name, _)) = backend_map.get(&bk) { - name.clone() - } else { - let name = format!("litellm_{backend_counter}"); - backend_counter += 1; - - let bc = build_backend_config( - &name, - &kind, - &provider_id, - &api_key, - &base_url, - params, - &tls, - log_bodies, - &config, - ); - backend_map.insert(bk, (name.clone(), bc)); - name - }; - - model_deployments - .entry(entry.model_name.clone()) - .or_default() - .push(DeploymentSpec { - backend_name, - actual_model, - rpm: params.rpm, - tpm: params.tpm, - weight: params.weight, - }); - } - - // Build MultiConfig backends (ordered). - let mut backends = IndexMap::new(); - for (name, bc) in backend_map.values() { - backends.insert(name.clone(), bc.clone()); - } - - let default_backend = backends - .keys() - .next() - .cloned() - .expect("at least one backend"); - - let multi = MultiConfig { - listen_port, - log_bodies, - redact_secrets, - anthropic_thinking_repair, - pxpipe_compress, - forward_client_auth: crate::config::env_bool_flag("ANTHROPIC_FORWARD_CLIENT_AUTH"), - default_backend, - backends, - expose_degradation_warnings: false, // overridden in MultiConfig::load() - }; - - // Determine routing strategy from router_settings. - let strategy = config - .router_settings - .as_ref() - .and_then(|rs| rs.routing_strategy.as_deref()) - .map(parse_routing_strategy_str) - .unwrap_or_default(); - - if strategy != RoutingStrategy::RoundRobin { - tracing::info!(strategy = ?strategy, "using routing strategy from config"); - } - - // Build ModelRouter. - let mut routes: HashMap>> = HashMap::new(); - for (model_name, specs) in model_deployments { - let deployments = specs - .into_iter() - .map(|s| { - Arc::new(Deployment::with_weight( - s.backend_name, - s.actual_model, - s.rpm, - s.tpm, - s.weight.unwrap_or(1), - )) - }) - .collect(); - routes.insert(model_name, deployments); - } - - let router = ModelRouter::with_strategy(routes, strategy); - - let callbacks = config - .litellm_settings - .as_ref() - .map(|s| s.callbacks.clone()) - .unwrap_or_default(); - let langfuse_requested = callbacks.iter().any(|c| c.eq_ignore_ascii_case("langfuse")); - let callback_urls: Vec = callbacks - .into_iter() - .filter(|c| !c.eq_ignore_ascii_case("langfuse")) - .collect(); - - LiteLLMParsed { - multi_config: multi, - router, - callback_urls, - langfuse_requested, - master_key, - } -} - -struct DeploymentSpec { - backend_name: String, - actual_model: String, - rpm: Option, - tpm: Option, - weight: Option, -} - -/// Extract `general_settings.master_key` from a LiteLLM YAML string without -/// performing full config parsing. Used by the synchronous `fn main()` to apply -/// the key via `set_var` before the tokio runtime spawns worker threads. -pub fn extract_master_key(yaml: &str) -> Option { - #[derive(Deserialize)] - struct Probe { - general_settings: Option, - } - let probe: Probe = serde_yaml::from_str(yaml).ok()?; - let gs = probe.general_settings?; - let raw = gs.master_key?; - resolve_env_value(&raw).ok() -} - -/// Determine the base URL for a deployment, applying provider-specific defaults. -fn resolve_base_url( - kind: &BackendKind, - params: &LiteLLMParams, - stub_provider: Option<&'static anyllm_providers::ProviderDef>, - actual_model: &str, -) -> String { - if let Some(ref url) = params.api_base { - let resolved = - resolve_env_value(url).unwrap_or_else(|e| panic!("model_list api_base: {e}")); - if *kind == BackendKind::AzureOpenAI { - let api_version = params.api_version.as_deref().unwrap_or("2024-10-21"); - if !resolved.contains("/openai/deployments/") { - let deployment = azure_deployment_from_model(actual_model); - return format!( - "{}/openai/deployments/{deployment}/chat/completions?api-version={api_version}", - resolved.trim_end_matches('/'), - ); - } - // api_base is already a deployment URL. Ensure the required api-version - // query is present so a partial deployment URL still authenticates. - if !resolved.contains("api-version=") { - let sep = if resolved.contains('?') { '&' } else { '?' }; - return format!("{resolved}{sep}api-version={api_version}"); - } - return resolved; - } - return resolved; - } - match kind { - BackendKind::OpenAI => { - // Use the stub provider's default URL when available (e.g. groq, xai, mistral). - // If a known provider has no safe global default, require explicit api_base. - let url = if let Some(provider) = stub_provider { - if provider.default_base_url.is_empty() { - panic!( - "model_list provider '{}' requires api_base because it has no safe global API base URL", - provider.id - ); - } - provider.default_base_url - } else { - "https://api.openai.com" - }; - super::strip_v1_suffix(url).to_string() - } - BackendKind::Gemini => { - "https://generativelanguage.googleapis.com/v1beta/openai".to_string() - } - BackendKind::Anthropic => std::env::var("ANTHROPIC_BASE_URL") - .unwrap_or_else(|_| "https://api.anthropic.com".to_string()), - BackendKind::Bedrock => { - // For Bedrock, base_url stores the region. - params - .aws_region_name - .as_deref() - .map(|v| v.to_string()) - .or_else(|| std::env::var("AWS_REGION").ok()) - .unwrap_or_else(|| "us-east-1".to_string()) - } - // Azure and Vertex require api_base in the config. - BackendKind::AzureOpenAI => { - panic!("api_base is required for azure deployments in model_list") - } - BackendKind::Vertex => { - let project = params.vertex_project.as_deref().unwrap_or_else(|| { - panic!("vertex_project is required for vertex deployments in model_list") - }); - let location = params.vertex_location.as_deref().unwrap_or_else(|| { - panic!("vertex_location is required for vertex deployments in model_list") - }); - validate_gcp_identifier("vertex_project", project); - validate_gcp_identifier("vertex_location", location); - format!( - "https://{location}-aiplatform.googleapis.com/v1/projects/{project}/locations/{location}/endpoints/openapi" - ) - } - } -} - -// LiteLLM Azure model names may carry a route-group marker prefix that selects a -// request-shaping path but is not part of the deployment name. This is the complete -// set of such markers; they are defined only here (no external producer). Add a -// marker here if a new Azure route group is introduced. -fn azure_deployment_from_model(model: &str) -> &str { - for marker in ["o_series/", "gpt5_series/"] { - if let Some(deployment) = model.strip_prefix(marker) { - if !deployment.is_empty() { - return deployment; - } - } - } - model -} - -/// Build a BackendConfig from LiteLLM model_list params. -#[allow(clippy::too_many_arguments)] -fn build_backend_config( - name: &str, - kind: &BackendKind, - provider_id: &str, - api_key: &str, - base_url: &str, - params: &LiteLLMParams, - tls: &TlsConfig, - log_bodies: bool, - config: &LiteLLMConfig, -) -> BackendConfig { - let backend_auth = match kind { - BackendKind::AzureOpenAI => BackendAuth::AzureApiKey(api_key.to_string()), - BackendKind::Gemini | BackendKind::Vertex => BackendAuth::GoogleApiKey(api_key.to_string()), - BackendKind::Anthropic => BackendAuth::anthropic_from_api_key_like(api_key.to_string()), - _ => BackendAuth::BearerToken(api_key.to_string()), - }; - - // For Azure, resolve_base_url already produced the full deployment URL - // (deployment name + api-version), so use it as-is here. - let effective_url = if *kind == BackendKind::AzureOpenAI { - base_url.to_string() - } else { - // Validate non-Azure URLs. - if *kind != BackendKind::Bedrock { - if let Err(e) = validate_base_url(base_url) { - panic!("backend '{name}' base_url rejected: {e}"); - } - } - base_url.to_string() - }; - - // Bedrock credentials. - let bedrock_credentials = if *kind == BackendKind::Bedrock { - let region = params - .aws_region_name - .as_deref() - .map(|v| resolve_env_value(v).unwrap_or_else(|e| panic!("backend '{name}': {e}"))) - .or_else(|| std::env::var("AWS_REGION").ok()) - .unwrap_or_else(|| "us-east-1".to_string()); - - let access_key = params - .aws_access_key_id - .as_deref() - .map(|v| resolve_env_value(v).unwrap_or_else(|e| panic!("backend '{name}': {e}"))) - .or_else(|| std::env::var("AWS_ACCESS_KEY_ID").ok()) - .unwrap_or_else(|| panic!("backend '{name}': aws_access_key_id required for bedrock")); - - let secret_key = params - .aws_secret_access_key - .as_deref() - .map(|v| resolve_env_value(v).unwrap_or_else(|e| panic!("backend '{name}': {e}"))) - .or_else(|| std::env::var("AWS_SECRET_ACCESS_KEY").ok()) - .unwrap_or_else(|| { - panic!("backend '{name}': aws_secret_access_key required for bedrock") - }); - - // Store region as base_url for Bedrock (matches existing convention). - // The effective_url was already set to the region string. - let _ = region; // region is used as base_url via resolve_base_url - - Some(aws_credential_types::Credentials::new( - access_key, - secret_key, - None, // session token not commonly in LiteLLM configs - None, - "litellm-config", - )) - } else { - None - }; - - // Placeholder model mapping: with model router, these are not used for routing. - // They serve as fallback for Anthropic model name translation if needed. - let model_mapping = ModelMapping { - big_model: String::new(), - small_model: String::new(), - }; - - let _num_retries = config.litellm_settings.as_ref().and_then(|s| s.num_retries); - let _request_timeout = config - .litellm_settings - .as_ref() - .and_then(|s| s.request_timeout); - - BackendConfig { - kind: kind.clone(), - provider_id: Some(provider_id.to_string()), - api_key: api_key.to_string(), - base_url: effective_url, - api_format: OpenAIApiFormat::Chat, - model_mapping, - tls: tls.clone(), - backend_auth, - log_bodies, - omit_stream_options: false, - stream_timeout_secs: std::env::var("REQUEST_TIMEOUT_SECS") - .ok() - .and_then(|v| v.parse().ok()) - .unwrap_or(900u64), - bedrock_credentials, - // Config-file backends are validated against SSRF at load time; no auto-relax. - allow_local_ssrf: false, - } -} +pub(crate) use parser::parse_routing_strategy_str; +pub use parser::{extract_master_key, from_litellm_yaml, parse_litellm_yaml}; +pub use types::LiteLLMParsed; #[cfg(test)] mod tests; diff --git a/crates/proxy/src/config/litellm/parser.rs b/crates/proxy/src/config/litellm/parser.rs new file mode 100644 index 0000000..48f5569 --- /dev/null +++ b/crates/proxy/src/config/litellm/parser.rs @@ -0,0 +1,576 @@ +//! Conversion logic: turn parsed LiteLLM YAML structs into anyllm-proxy's +//! `MultiConfig` + `ModelRouter`. + +use std::collections::HashMap; +use std::sync::Arc; + +use indexmap::IndexMap; +use serde::Deserialize; + +use super::types::{GeneralSettings, LiteLLMConfig, LiteLLMParams, LiteLLMParsed}; +use crate::config::model_router::{Deployment, ModelRouter, RoutingStrategy}; +use crate::config::single::validate_gcp_identifier; +use crate::config::{ + resolve_env_value, sanitize_api_key, strip_v1_suffix, validate_base_url, BackendAuth, + BackendConfig, BackendKind, ModelMapping, MultiConfig, OpenAIApiFormat, TlsConfig, +}; + +// ---- Provider parsing ---- + +/// Map LiteLLM routing_strategy string to our enum. +pub(crate) fn parse_routing_strategy_str(s: &str) -> RoutingStrategy { + match s.to_ascii_lowercase().replace('_', "-").as_str() { + "simple-shuffle" | "round-robin" => RoutingStrategy::RoundRobin, + "least-busy" => RoutingStrategy::LeastBusy, + "latency-based-routing" | "latency-based" => RoutingStrategy::LatencyBased, + "usage-based-routing" | "usage-based" => RoutingStrategy::LeastBusy, + "weighted" => RoutingStrategy::Weighted, + "cost-based" => RoutingStrategy::CostBased, + other => { + tracing::warn!( + strategy = %other, + "unknown routing_strategy, falling back to round-robin" + ); + RoutingStrategy::RoundRobin + } + } +} + +/// Parse LiteLLM's "provider/model_name" format. +/// No prefix defaults to OpenAI (matches LiteLLM behavior). +/// Returns (kind, model_name, stub_provider) where stub_provider is set for +/// registry-resolved OpenAI-compatible providers so callers can use their default URL. +pub(super) fn parse_provider_model( + model: &str, +) -> ( + BackendKind, + String, + Option<&'static anyllm_providers::ProviderDef>, +) { + let (provider, model_name) = model.split_once('/').unwrap_or(("openai", model)); + let mut stub_provider: Option<&'static anyllm_providers::ProviderDef> = None; + let kind = match provider.to_ascii_lowercase().as_str() { + "openai" => BackendKind::OpenAI, + "azure" => BackendKind::AzureOpenAI, + "vertex_ai" | "vertex" => BackendKind::Vertex, + "gemini" => BackendKind::Gemini, + "anthropic" => { + stub_provider = anyllm_providers::get_provider("anthropic"); + BackendKind::Anthropic + } + "bedrock" => BackendKind::Bedrock, + other => { + // Try the provider registry for known OpenAI-compatible providers + // (e.g. "groq", "together_ai", "mistral", etc.) + let prefix_with_slash = format!("{other}/"); + if let Some(p) = anyllm_providers::find_by_litellm_prefix(&prefix_with_slash) { + let resolved = match anyllm_providers::resolve_backend(p.id) { + Some(("openai", _)) => { + stub_provider = Some(p); + BackendKind::OpenAI + } + Some(("anthropic", _)) => BackendKind::Anthropic, + Some(("gemini", _)) => BackendKind::Gemini, + Some(("vertex", _)) => BackendKind::Vertex, + Some(("azure", _)) => BackendKind::AzureOpenAI, + Some(("bedrock", _)) => BackendKind::Bedrock, + _ => { + tracing::warn!(provider = %other, "provider found in registry but protocol not mappable, treating as openai-compatible"); + stub_provider = Some(p); + BackendKind::OpenAI + } + }; + resolved + } else { + tracing::warn!( + provider = %other, + "unknown LiteLLM provider, treating as openai-compatible" + ); + BackendKind::OpenAI + } + } + }; + (kind, model_name.to_string(), stub_provider) +} + +fn provider_id_for_litellm_model( + model: &str, + kind: &BackendKind, + stub_provider: Option<&'static anyllm_providers::ProviderDef>, +) -> String { + if let Some(provider) = stub_provider { + return provider.id.to_string(); + } + + let raw_provider = model + .split_once('/') + .map(|(provider, _)| provider) + .unwrap_or("openai") + .to_ascii_lowercase(); + + match kind { + BackendKind::OpenAI => raw_provider, + BackendKind::AzureOpenAI => "azure".to_string(), + BackendKind::Vertex => "vertex_ai".to_string(), + BackendKind::Gemini => "gemini".to_string(), + BackendKind::Anthropic => "anthropic".to_string(), + BackendKind::Bedrock => "bedrock".to_string(), + } +} + +// ---- Backend deduplication key ---- + +/// Unique identity for a backend: same kind + base_url + api_key share one connection pool. +#[derive(Hash, PartialEq, Eq, Clone)] +struct BackendKey { + kind: String, + provider_id: String, + base_url: String, + /// Hash of the API key (not the key itself) to avoid holding secrets in hash keys. + api_key_hash: u64, +} + +fn hash_string(s: &str) -> u64 { + use std::hash::{Hash, Hasher}; + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + s.hash(&mut hasher); + hasher.finish() +} + +// ---- Conversion ---- + +/// Parse a LiteLLM YAML config and return the multi-backend config + model router pair. +pub fn from_litellm_yaml(yaml: &str) -> (MultiConfig, ModelRouter) { + let parsed = parse_litellm_yaml(yaml); + (parsed.multi_config, parsed.router) +} + +/// Parse a LiteLLM YAML config into the intermediate `LiteLLMParsed` struct. +/// Panics on invalid YAML (startup-time validation; misconfiguration is unrecoverable). +pub fn parse_litellm_yaml(yaml: &str) -> LiteLLMParsed { + let config: LiteLLMConfig = + serde_yaml::from_str(yaml).unwrap_or_else(|e| panic!("invalid LiteLLM config YAML: {e}")); + + if config.model_list.is_empty() { + panic!("LiteLLM config must define at least one entry in model_list"); + } + + // Resolve general_settings.master_key but do not call set_var here. + // The caller applies it in the consolidated env override block. + let master_key = if let Some(ref gs) = config.general_settings { + let mk = gs.master_key.as_ref().map(|mk| { + resolve_env_value(mk).unwrap_or_else(|e| panic!("general_settings.master_key: {e}")) + }); + // Log unsupported keys at warn. + for key in gs._extra.keys() { + tracing::warn!(key = %key, "unsupported general_settings key (ignored)"); + } + mk + } else { + None + }; + + if let Some(ref ls) = config.litellm_settings { + for key in ls._extra.keys() { + tracing::warn!(key = %key, "unsupported litellm_settings key (ignored)"); + } + } + + if let Some(ref rs) = config.router_settings { + for key in rs._extra.keys() { + tracing::warn!(key = %key, "unsupported router_settings key (ignored)"); + } + } + + let listen_port = std::env::var("LISTEN_PORT") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(3000); + + let log_bodies = std::env::var("LOG_BODIES") + .map(|v| v == "true" || v == "1") + .unwrap_or(false); + let redact_secrets = std::env::var("REDACT_SECRETS") + .map(|v| v == "true" || v == "1") + .unwrap_or(false); + let anthropic_thinking_repair = std::env::var("ANTHROPIC_THINKING_REPAIR") + .map(|v| v == "true" || v == "1") + .unwrap_or(false); + let pxpipe_compress = std::env::var("PXPIPE_COMPRESS") + .map(|v| v == "true" || v == "1") + .unwrap_or(false); + + let tls = TlsConfig::from_env(); + + // Group model_list entries into deduplicated backends + deployment list. + let mut backend_map: HashMap = HashMap::new(); + let mut backend_counter = 0u32; + // model_name -> Vec<(backend_name, actual_model, rpm, tpm)> + let mut model_deployments: HashMap> = HashMap::new(); + + for entry in &config.model_list { + let (kind, actual_model, stub_provider) = parse_provider_model(&entry.litellm_params.model); + let provider_id = + provider_id_for_litellm_model(&entry.litellm_params.model, &kind, stub_provider); + let params = &entry.litellm_params; + + let api_key = sanitize_api_key( + ¶ms + .api_key + .as_deref() + .map(|v| resolve_env_value(v).unwrap_or_else(|e| panic!("model_list api_key: {e}"))) + .unwrap_or_else(|| { + // Fall back to the provider's own env vars when no api_key in YAML. + stub_provider + .and_then(|p| p.env_vars.iter().find_map(|v| std::env::var(v).ok())) + .or_else(|| { + (kind == BackendKind::Anthropic) + .then(|| std::env::var("ANTHROPIC_AUTH_TOKEN").ok()) + .flatten() + }) + .unwrap_or_default() + }), + ); + + let base_url = resolve_base_url(&kind, params, stub_provider, &actual_model); + + let bk = BackendKey { + kind: format!("{kind:?}"), + provider_id: provider_id.clone(), + base_url: base_url.clone(), + api_key_hash: hash_string(&api_key), + }; + + let backend_name = if let Some((name, _)) = backend_map.get(&bk) { + name.clone() + } else { + let name = format!("litellm_{backend_counter}"); + backend_counter += 1; + + let bc = build_backend_config( + &name, + &kind, + &provider_id, + &api_key, + &base_url, + params, + &tls, + log_bodies, + &config, + ); + backend_map.insert(bk, (name.clone(), bc)); + name + }; + + model_deployments + .entry(entry.model_name.clone()) + .or_default() + .push(DeploymentSpec { + backend_name, + actual_model, + rpm: params.rpm, + tpm: params.tpm, + weight: params.weight, + }); + } + + // Build MultiConfig backends (ordered). + let mut backends = IndexMap::new(); + for (name, bc) in backend_map.values() { + backends.insert(name.clone(), bc.clone()); + } + + let default_backend = backends + .keys() + .next() + .cloned() + .expect("at least one backend"); + + let multi = MultiConfig { + listen_port, + log_bodies, + redact_secrets, + anthropic_thinking_repair, + pxpipe_compress, + forward_client_auth: crate::config::env_bool_flag("ANTHROPIC_FORWARD_CLIENT_AUTH"), + default_backend, + backends, + expose_degradation_warnings: false, // overridden in MultiConfig::load() + }; + + // Determine routing strategy from router_settings. + let strategy = config + .router_settings + .as_ref() + .and_then(|rs| rs.routing_strategy.as_deref()) + .map(parse_routing_strategy_str) + .unwrap_or_default(); + + if strategy != RoutingStrategy::RoundRobin { + tracing::info!(strategy = ?strategy, "using routing strategy from config"); + } + + // Build ModelRouter. + let mut routes: HashMap>> = HashMap::new(); + for (model_name, specs) in model_deployments { + let deployments = specs + .into_iter() + .map(|s| { + Arc::new(Deployment::with_weight( + s.backend_name, + s.actual_model, + s.rpm, + s.tpm, + s.weight.unwrap_or(1), + )) + }) + .collect(); + routes.insert(model_name, deployments); + } + + let router = ModelRouter::with_strategy(routes, strategy); + + let callbacks = config + .litellm_settings + .as_ref() + .map(|s| s.callbacks.clone()) + .unwrap_or_default(); + let langfuse_requested = callbacks.iter().any(|c| c.eq_ignore_ascii_case("langfuse")); + let callback_urls: Vec = callbacks + .into_iter() + .filter(|c| !c.eq_ignore_ascii_case("langfuse")) + .collect(); + + LiteLLMParsed { + multi_config: multi, + router, + callback_urls, + langfuse_requested, + master_key, + } +} + +struct DeploymentSpec { + backend_name: String, + actual_model: String, + rpm: Option, + tpm: Option, + weight: Option, +} + +/// Extract `general_settings.master_key` from a LiteLLM YAML string without +/// performing full config parsing. Used by the synchronous `fn main()` to apply +/// the key via `set_var` before the tokio runtime spawns worker threads. +pub fn extract_master_key(yaml: &str) -> Option { + #[derive(Deserialize)] + struct Probe { + general_settings: Option, + } + let probe: Probe = serde_yaml::from_str(yaml).ok()?; + let gs = probe.general_settings?; + let raw = gs.master_key?; + resolve_env_value(&raw).ok() +} + +/// Determine the base URL for a deployment, applying provider-specific defaults. +fn resolve_base_url( + kind: &BackendKind, + params: &LiteLLMParams, + stub_provider: Option<&'static anyllm_providers::ProviderDef>, + actual_model: &str, +) -> String { + if let Some(ref url) = params.api_base { + let resolved = + resolve_env_value(url).unwrap_or_else(|e| panic!("model_list api_base: {e}")); + if *kind == BackendKind::AzureOpenAI { + let api_version = params.api_version.as_deref().unwrap_or("2024-10-21"); + if !resolved.contains("/openai/deployments/") { + let deployment = azure_deployment_from_model(actual_model); + return format!( + "{}/openai/deployments/{deployment}/chat/completions?api-version={api_version}", + resolved.trim_end_matches('/'), + ); + } + // api_base is already a deployment URL. Ensure the required api-version + // query is present so a partial deployment URL still authenticates. + if !resolved.contains("api-version=") { + let sep = if resolved.contains('?') { '&' } else { '?' }; + return format!("{resolved}{sep}api-version={api_version}"); + } + return resolved; + } + return resolved; + } + match kind { + BackendKind::OpenAI => { + // Use the stub provider's default URL when available (e.g. groq, xai, mistral). + // If a known provider has no safe global default, require explicit api_base. + let url = if let Some(provider) = stub_provider { + if provider.default_base_url.is_empty() { + panic!( + "model_list provider '{}' requires api_base because it has no safe global API base URL", + provider.id + ); + } + provider.default_base_url + } else { + "https://api.openai.com" + }; + strip_v1_suffix(url).to_string() + } + BackendKind::Gemini => { + "https://generativelanguage.googleapis.com/v1beta/openai".to_string() + } + BackendKind::Anthropic => std::env::var("ANTHROPIC_BASE_URL") + .unwrap_or_else(|_| "https://api.anthropic.com".to_string()), + BackendKind::Bedrock => { + // For Bedrock, base_url stores the region. + params + .aws_region_name + .as_deref() + .map(|v| v.to_string()) + .or_else(|| std::env::var("AWS_REGION").ok()) + .unwrap_or_else(|| "us-east-1".to_string()) + } + // Azure and Vertex require api_base in the config. + BackendKind::AzureOpenAI => { + panic!("api_base is required for azure deployments in model_list") + } + BackendKind::Vertex => { + let project = params.vertex_project.as_deref().unwrap_or_else(|| { + panic!("vertex_project is required for vertex deployments in model_list") + }); + let location = params.vertex_location.as_deref().unwrap_or_else(|| { + panic!("vertex_location is required for vertex deployments in model_list") + }); + validate_gcp_identifier("vertex_project", project); + validate_gcp_identifier("vertex_location", location); + format!( + "https://{location}-aiplatform.googleapis.com/v1/projects/{project}/locations/{location}/endpoints/openapi" + ) + } + } +} + +// LiteLLM Azure model names may carry a route-group marker prefix that selects a +// request-shaping path but is not part of the deployment name. This is the complete +// set of such markers; they are defined only here (no external producer). Add a +// marker here if a new Azure route group is introduced. +fn azure_deployment_from_model(model: &str) -> &str { + for marker in ["o_series/", "gpt5_series/"] { + if let Some(deployment) = model.strip_prefix(marker) { + if !deployment.is_empty() { + return deployment; + } + } + } + model +} + +/// Build a BackendConfig from LiteLLM model_list params. +#[allow(clippy::too_many_arguments)] +fn build_backend_config( + name: &str, + kind: &BackendKind, + provider_id: &str, + api_key: &str, + base_url: &str, + params: &LiteLLMParams, + tls: &TlsConfig, + log_bodies: bool, + config: &LiteLLMConfig, +) -> BackendConfig { + let backend_auth = match kind { + BackendKind::AzureOpenAI => BackendAuth::AzureApiKey(api_key.to_string()), + BackendKind::Gemini | BackendKind::Vertex => BackendAuth::GoogleApiKey(api_key.to_string()), + BackendKind::Anthropic => BackendAuth::anthropic_from_api_key_like(api_key.to_string()), + _ => BackendAuth::BearerToken(api_key.to_string()), + }; + + // For Azure, resolve_base_url already produced the full deployment URL + // (deployment name + api-version), so use it as-is here. + let effective_url = if *kind == BackendKind::AzureOpenAI { + base_url.to_string() + } else { + // Validate non-Azure URLs. + if *kind != BackendKind::Bedrock { + if let Err(e) = validate_base_url(base_url) { + panic!("backend '{name}' base_url rejected: {e}"); + } + } + base_url.to_string() + }; + + // Bedrock credentials. + let bedrock_credentials = if *kind == BackendKind::Bedrock { + let region = params + .aws_region_name + .as_deref() + .map(|v| resolve_env_value(v).unwrap_or_else(|e| panic!("backend '{name}': {e}"))) + .or_else(|| std::env::var("AWS_REGION").ok()) + .unwrap_or_else(|| "us-east-1".to_string()); + + let access_key = params + .aws_access_key_id + .as_deref() + .map(|v| resolve_env_value(v).unwrap_or_else(|e| panic!("backend '{name}': {e}"))) + .or_else(|| std::env::var("AWS_ACCESS_KEY_ID").ok()) + .unwrap_or_else(|| panic!("backend '{name}': aws_access_key_id required for bedrock")); + + let secret_key = params + .aws_secret_access_key + .as_deref() + .map(|v| resolve_env_value(v).unwrap_or_else(|e| panic!("backend '{name}': {e}"))) + .or_else(|| std::env::var("AWS_SECRET_ACCESS_KEY").ok()) + .unwrap_or_else(|| { + panic!("backend '{name}': aws_secret_access_key required for bedrock") + }); + + // Store region as base_url for Bedrock (matches existing convention). + // The effective_url was already set to the region string. + let _ = region; // region is used as base_url via resolve_base_url + + Some(aws_credential_types::Credentials::new( + access_key, + secret_key, + None, // session token not commonly in LiteLLM configs + None, + "litellm-config", + )) + } else { + None + }; + + // Placeholder model mapping: with model router, these are not used for routing. + // They serve as fallback for Anthropic model name translation if needed. + let model_mapping = ModelMapping { + big_model: String::new(), + small_model: String::new(), + }; + + let _num_retries = config.litellm_settings.as_ref().and_then(|s| s.num_retries); + let _request_timeout = config + .litellm_settings + .as_ref() + .and_then(|s| s.request_timeout); + + BackendConfig { + kind: kind.clone(), + provider_id: Some(provider_id.to_string()), + api_key: api_key.to_string(), + base_url: effective_url, + api_format: OpenAIApiFormat::Chat, + model_mapping, + tls: tls.clone(), + backend_auth, + log_bodies, + omit_stream_options: false, + stream_timeout_secs: std::env::var("REQUEST_TIMEOUT_SECS") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(900u64), + bedrock_credentials, + // Config-file backends are validated against SSRF at load time; no auto-relax. + allow_local_ssrf: false, + } +} diff --git a/crates/proxy/src/config/litellm/tests.rs b/crates/proxy/src/config/litellm/tests.rs index 8795d26..319e8a6 100644 --- a/crates/proxy/src/config/litellm/tests.rs +++ b/crates/proxy/src/config/litellm/tests.rs @@ -1,4 +1,7 @@ +use super::parser::parse_provider_model; use super::*; +use crate::config::model_router::RoutingStrategy; +use crate::config::BackendKind; #[test] fn parse_provider_model_openai() { diff --git a/crates/proxy/src/config/litellm/types.rs b/crates/proxy/src/config/litellm/types.rs new file mode 100644 index 0000000..aacb3b6 --- /dev/null +++ b/crates/proxy/src/config/litellm/types.rs @@ -0,0 +1,88 @@ +//! Serde structs mapping the LiteLLM `config.yaml` schema, plus the parsed-result +//! type returned by the conversion functions in [`super::parser`]. + +use serde::Deserialize; + +use crate::config::model_router::ModelRouter; +use crate::config::MultiConfig; + +/// Root structure of a LiteLLM `config.yaml` file. +#[derive(Deserialize)] +pub(super) struct LiteLLMConfig { + #[serde(default)] + pub(super) model_list: Vec, + #[serde(default)] + pub(super) litellm_settings: Option, + #[serde(default)] + pub(super) router_settings: Option, + #[serde(default)] + pub(super) general_settings: Option, +} + +#[derive(Deserialize)] +pub(super) struct LiteLLMModelEntry { + pub(super) model_name: String, + pub(super) litellm_params: LiteLLMParams, +} + +#[derive(Deserialize)] +pub(super) struct LiteLLMParams { + pub(super) model: String, + pub(super) api_base: Option, + pub(super) api_key: Option, + pub(super) rpm: Option, + pub(super) tpm: Option, + pub(super) weight: Option, + // Azure-specific + pub(super) api_version: Option, + // Vertex-specific + pub(super) vertex_project: Option, + pub(super) vertex_location: Option, + // Bedrock-specific + pub(super) aws_access_key_id: Option, + pub(super) aws_secret_access_key: Option, + pub(super) aws_region_name: Option, + // Catch unknown fields silently (LiteLLM has many we don't support). + #[serde(flatten)] + pub(super) _extra: serde_json::Map, +} + +#[derive(Deserialize)] +pub(super) struct LiteLLMSettings { + #[serde(default)] + pub(super) num_retries: Option, + #[serde(default)] + pub(super) request_timeout: Option, + #[serde(default)] + pub(super) callbacks: Vec, + #[serde(flatten)] + pub(super) _extra: serde_json::Map, +} + +#[derive(Deserialize)] +pub(super) struct RouterSettings { + #[serde(default)] + pub(super) routing_strategy: Option, + #[serde(flatten)] + pub(super) _extra: serde_json::Map, +} + +#[derive(Deserialize)] +pub(super) struct GeneralSettings { + pub(super) master_key: Option, + #[serde(flatten)] + pub(super) _extra: serde_json::Map, +} + +/// Parsed result from a LiteLLM config file. +pub struct LiteLLMParsed { + pub multi_config: MultiConfig, + pub router: ModelRouter, + /// Webhook callback URLs from litellm_settings.callbacks (non-named entries). + pub callback_urls: Vec, + /// True when "langfuse" appears in litellm_settings.callbacks. + pub langfuse_requested: bool, + /// Resolved `general_settings.master_key`, if present. + /// Caller should apply as PROXY_API_KEYS if that var is not already set. + pub master_key: Option, +} diff --git a/crates/proxy/src/openai_tool_policy.rs b/crates/proxy/src/openai_tool_policy.rs index 41b16c6..d4fca60 100644 --- a/crates/proxy/src/openai_tool_policy.rs +++ b/crates/proxy/src/openai_tool_policy.rs @@ -346,432 +346,4 @@ fn anthropic_request_requires_tool_choice(req: &anthropic::MessageCreateRequest) } #[cfg(test)] -mod tests { - use serde_json::json; - - use super::*; - - fn catalog(json: serde_json::Value) -> ProviderCatalog { - ProviderCatalog::from_litellm_json(&json.to_string()).unwrap() - } - - fn empty_catalog() -> ProviderCatalog { - ProviderCatalog::from_litellm_json("{}").unwrap() - } - - fn request(value: serde_json::Value) -> openai::ChatCompletionRequest { - serde_json::from_value(value).unwrap() - } - - fn ctx<'a>( - backend_kind: BackendKind, - provider_id: Option<&'a str>, - model: &'a str, - provider_catalog: &'a ProviderCatalog, - ) -> OpenAiToolPolicyContext<'a> { - OpenAiToolPolicyContext { - backend_kind, - provider_id, - model, - provider_catalog, - } - } - - #[test] - fn openai_tool_policy_mistral_rewrites_ids_and_matching_tool_results() { - let mut req = request(json!({ - "model": "mistral-large-latest", - "messages": [ - { - "role": "assistant", - "tool_calls": [ - {"id": "call_alpha", "type": "function", "function": {"name": "a", "arguments": "{}"}}, - {"id": "call_alpha", "type": "function", "function": {"name": "b", "arguments": "{}"}} - ] - }, - {"role": "tool", "tool_call_id": "call_alpha", "content": "first"}, - {"role": "tool", "tool_call_id": "call_alpha", "content": "second"} - ] - })); - let catalog = empty_catalog(); - let mut warnings = TranslationWarnings::default(); - - let report = prepare_openai_tool_request( - &mut req, - ctx( - BackendKind::OpenAI, - Some("mistral"), - "mistral-large-latest", - &catalog, - ), - &mut warnings, - ) - .unwrap(); - - let calls = req.messages[0].tool_calls.as_ref().unwrap(); - assert_eq!(calls[0].id, "000000000"); - assert_eq!(calls[1].id, "000000001"); - assert_eq!(req.messages[1].tool_call_id.as_deref(), Some("000000000")); - assert_eq!(req.messages[2].tool_call_id.as_deref(), Some("000000001")); - assert_eq!(report.duplicate_tool_call_ids, 1); - assert_eq!(report.remapped_tool_results, 2); - } - - #[test] - fn openai_tool_policy_gemini_sanitizes_schema_and_removes_strict() { - let mut req = request(json!({ - "model": "gemini-2.5-pro", - "messages": [{"role": "user", "content": "hi"}], - "tools": [{ - "type": "function", - "function": { - "name": "search", - "strict": true, - "parameters": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "additionalProperties": false, - "properties": { - "q": {"type": "string", "default": ""} - } - } - } - }] - })); - let catalog = empty_catalog(); - let mut warnings = TranslationWarnings::default(); - - prepare_openai_tool_request( - &mut req, - ctx( - BackendKind::Gemini, - Some("gemini"), - "gemini-2.5-pro", - &catalog, - ), - &mut warnings, - ) - .unwrap(); - - let tool = req.tools.as_ref().unwrap().first().unwrap(); - assert_eq!(tool.function.strict, None); - let params = tool.function.parameters.as_ref().unwrap(); - assert!(params.get("$schema").is_none()); - assert!(params.get("additionalProperties").is_none()); - assert!( - params.pointer("/properties/q/default").is_none(), - "Gemini rejects JSON Schema default in nested properties" - ); - assert_eq!( - warnings.as_header_value().as_deref(), - Some("tools.function.strict") - ); - } - - #[test] - fn openai_tool_policy_applies_gemini_policy_by_protocol_for_managed_backend() { - // A managed/custom backend can run BackendKind::OpenAI while its provider_id - // resolves to the Gemini OpenAI shim in the catalog. The policy must key off - // the provider protocol, not a hardcoded "gemini" id, so strict-stripping - // still applies. - let catalog = ProviderCatalog::bundled(); - let mut req = request(json!({ - "model": "gemini-2.5-pro", - "messages": [{"role": "user", "content": "hi"}], - "tools": [{ - "type": "function", - "function": { - "name": "search", - "strict": true, - "parameters": {"type": "object", "additionalProperties": false} - } - }] - })); - let mut warnings = TranslationWarnings::default(); - - prepare_openai_tool_request( - &mut req, - ctx( - BackendKind::OpenAI, - Some("gemini"), - "gemini-2.5-pro", - &catalog, - ), - &mut warnings, - ) - .unwrap(); - - let tool = req.tools.as_ref().unwrap().first().unwrap(); - assert_eq!(tool.function.strict, None); - assert!(tool - .function - .parameters - .as_ref() - .unwrap() - .get("additionalProperties") - .is_none()); - } - - #[test] - fn openai_tool_policy_gemini_strips_parallel_false_with_multiple_tools() { - let mut req = request(json!({ - "model": "gemini-2.5-pro", - "messages": [{"role": "user", "content": "hi"}], - "parallel_tool_calls": false, - "tools": [ - {"type": "function", "function": {"name": "a", "parameters": {"type": "object"}}}, - {"type": "function", "function": {"name": "b", "parameters": {"type": "object"}}} - ] - })); - let catalog = empty_catalog(); - let mut warnings = TranslationWarnings::default(); - - prepare_openai_tool_request( - &mut req, - ctx( - BackendKind::Gemini, - Some("gemini"), - "gemini-2.5-pro", - &catalog, - ), - &mut warnings, - ) - .unwrap(); - - // Gemini cannot honor parallel_tool_calls; the field is stripped and the - // degradation is reported rather than the request being rejected. - assert!(req.parallel_tool_calls.is_none()); - assert_eq!( - warnings.as_header_value().as_deref(), - Some("parallel_tool_calls") - ); - } - - #[test] - fn openai_tool_policy_rejects_required_tool_choice_when_model_lacks_it() { - let catalog = catalog(json!({ - "demo-model": { - "litellm_provider": "demo", - "mode": "chat", - "supports_function_calling": true, - "supports_tool_choice": false - } - })); - let mut req = request(json!({ - "model": "demo-model", - "messages": [{"role": "user", "content": "hi"}], - "tools": [{"type": "function", "function": {"name": "a", "parameters": {"type": "object"}}}], - "tool_choice": "required" - })); - let mut warnings = TranslationWarnings::default(); - - let err = prepare_openai_tool_request( - &mut req, - ctx(BackendKind::OpenAI, Some("demo"), "demo-model", &catalog), - &mut warnings, - ) - .unwrap_err(); - - assert!(err.message().contains("tool_choice=required")); - } - - #[test] - fn openai_tool_policy_drops_auto_tool_choice_when_model_lacks_it() { - let catalog = catalog(json!({ - "demo-model": { - "litellm_provider": "demo", - "mode": "chat", - "supports_function_calling": true, - "supports_tool_choice": false - } - })); - let mut req = request(json!({ - "model": "demo-model", - "messages": [{"role": "user", "content": "hi"}], - "tools": [{"type": "function", "function": {"name": "a", "parameters": {"type": "object"}}}], - "tool_choice": "auto" - })); - let mut warnings = TranslationWarnings::default(); - - prepare_openai_tool_request( - &mut req, - ctx(BackendKind::OpenAI, Some("demo"), "demo-model", &catalog), - &mut warnings, - ) - .unwrap(); - - assert!(req.tool_choice.is_none()); - assert!(req.tools.is_some()); - assert_eq!(warnings.as_header_value().as_deref(), Some("tool_choice")); - } - - #[test] - fn openai_tool_policy_drops_none_tool_choice_and_tools_without_continuation() { - let catalog = catalog(json!({ - "demo-model": { - "litellm_provider": "demo", - "mode": "chat", - "supports_function_calling": true, - "supports_tool_choice": false - } - })); - let mut req = request(json!({ - "model": "demo-model", - "messages": [{"role": "user", "content": "hi"}], - "tools": [{"type": "function", "function": {"name": "a", "parameters": {"type": "object"}}}], - "tool_choice": "none" - })); - let mut warnings = TranslationWarnings::default(); - - prepare_openai_tool_request( - &mut req, - ctx(BackendKind::OpenAI, Some("demo"), "demo-model", &catalog), - &mut warnings, - ) - .unwrap(); - - assert!(req.tool_choice.is_none()); - assert!(req.tools.is_none()); - assert_eq!( - warnings.as_header_value().as_deref(), - Some("tool_choice, tools") - ); - } - - #[test] - fn openai_tool_policy_rejects_tools_when_known_model_lacks_tool_use() { - let catalog = catalog(json!({ - "demo-model": { - "litellm_provider": "demo", - "mode": "chat", - "supports_function_calling": false, - "supports_tool_choice": false - } - })); - let mut req = request(json!({ - "model": "demo-model", - "messages": [{"role": "user", "content": "hi"}], - "tools": [{"type": "function", "function": {"name": "a", "parameters": {"type": "object"}}}] - })); - let mut warnings = TranslationWarnings::default(); - - let err = prepare_openai_tool_request( - &mut req, - ctx(BackendKind::OpenAI, Some("demo"), "demo-model", &catalog), - &mut warnings, - ) - .unwrap_err(); - - assert!(err.message().contains("does not support tools")); - } - - #[test] - fn openai_tool_policy_allows_forced_tool_choice_for_self_hosted_provider() { - // vllm/lm_studio/llamafile/triton stubs advertise tool_use but a - // conservative provider-level tool_choice:false and carry no per-model - // metadata. Forced tool_choice must pass through (the backend decides), - // not 400 -- the provider-level flag is not authoritative for an unknown - // self-hosted model. - let catalog = ProviderCatalog::bundled(); - assert!( - catalog.list_models("vllm").is_empty(), - "test assumes vllm has no per-model metadata" - ); - let mut req = request(json!({ - "model": "Qwen/Qwen2.5-7B-Instruct", - "messages": [{"role": "user", "content": "hi"}], - "tools": [{"type": "function", "function": {"name": "a", "parameters": {"type": "object"}}}], - "tool_choice": "required" - })); - let mut warnings = TranslationWarnings::default(); - - prepare_openai_tool_request( - &mut req, - ctx( - BackendKind::OpenAI, - Some("vllm"), - "Qwen/Qwen2.5-7B-Instruct", - &catalog, - ), - &mut warnings, - ) - .unwrap(); - - assert!(matches!( - req.tool_choice, - Some(ChatToolChoice::Simple(ref v)) if v == "required" - )); - } - - #[test] - fn openai_tool_policy_rejects_native_anthropic_tools_when_known_model_lacks_tool_use() { - let catalog = catalog(json!({ - "demo-model": { - "litellm_provider": "bedrock", - "mode": "chat", - "supports_function_calling": false, - "supports_tool_choice": false - } - })); - let req: anthropic::MessageCreateRequest = serde_json::from_value(json!({ - "model": "demo-model", - "max_tokens": 64, - "tools": [{ - "name": "lookup", - "input_schema": {"type": "object"} - }], - "messages": [ - {"role": "user", "content": "hi"} - ] - })) - .unwrap(); - - let err = validate_anthropic_tool_request( - &req, - ctx( - BackendKind::Bedrock, - Some("bedrock"), - "demo-model", - &catalog, - ), - ) - .unwrap_err(); - - assert!(err.message().contains("does not support tools")); - } - - #[test] - fn openai_tool_policy_normalizes_streaming_top_level_tool_call_delta() { - let chunk = parse_openai_chat_completion_chunk( - r#"{ - "id": "chatcmpl_1", - "object": "chat.completion.chunk", - "created": 1, - "model": "local", - "choices": [{ - "index": 0, - "delta": { - "tool_calls": [{ - "index": 0, - "id": "call_1", - "name": "lookup", - "arguments": {"q":"x"} - }] - } - }] - }"#, - ) - .unwrap(); - - let tool_call = &chunk.choices[0].delta.tool_calls.as_ref().unwrap()[0]; - assert_eq!(tool_call.call_type.as_deref(), Some("function")); - assert_eq!( - tool_call.function.as_ref().unwrap().name.as_deref(), - Some("lookup") - ); - assert_eq!( - tool_call.function.as_ref().unwrap().arguments.as_deref(), - Some(r#"{"q":"x"}"#) - ); - } -} +mod tests; diff --git a/crates/proxy/src/openai_tool_policy/tests.rs b/crates/proxy/src/openai_tool_policy/tests.rs new file mode 100644 index 0000000..0d9b5b2 --- /dev/null +++ b/crates/proxy/src/openai_tool_policy/tests.rs @@ -0,0 +1,427 @@ +use serde_json::json; + +use super::*; + +fn catalog(json: serde_json::Value) -> ProviderCatalog { + ProviderCatalog::from_litellm_json(&json.to_string()).unwrap() +} + +fn empty_catalog() -> ProviderCatalog { + ProviderCatalog::from_litellm_json("{}").unwrap() +} + +fn request(value: serde_json::Value) -> openai::ChatCompletionRequest { + serde_json::from_value(value).unwrap() +} + +fn ctx<'a>( + backend_kind: BackendKind, + provider_id: Option<&'a str>, + model: &'a str, + provider_catalog: &'a ProviderCatalog, +) -> OpenAiToolPolicyContext<'a> { + OpenAiToolPolicyContext { + backend_kind, + provider_id, + model, + provider_catalog, + } +} + +#[test] +fn openai_tool_policy_mistral_rewrites_ids_and_matching_tool_results() { + let mut req = request(json!({ + "model": "mistral-large-latest", + "messages": [ + { + "role": "assistant", + "tool_calls": [ + {"id": "call_alpha", "type": "function", "function": {"name": "a", "arguments": "{}"}}, + {"id": "call_alpha", "type": "function", "function": {"name": "b", "arguments": "{}"}} + ] + }, + {"role": "tool", "tool_call_id": "call_alpha", "content": "first"}, + {"role": "tool", "tool_call_id": "call_alpha", "content": "second"} + ] + })); + let catalog = empty_catalog(); + let mut warnings = TranslationWarnings::default(); + + let report = prepare_openai_tool_request( + &mut req, + ctx( + BackendKind::OpenAI, + Some("mistral"), + "mistral-large-latest", + &catalog, + ), + &mut warnings, + ) + .unwrap(); + + let calls = req.messages[0].tool_calls.as_ref().unwrap(); + assert_eq!(calls[0].id, "000000000"); + assert_eq!(calls[1].id, "000000001"); + assert_eq!(req.messages[1].tool_call_id.as_deref(), Some("000000000")); + assert_eq!(req.messages[2].tool_call_id.as_deref(), Some("000000001")); + assert_eq!(report.duplicate_tool_call_ids, 1); + assert_eq!(report.remapped_tool_results, 2); +} + +#[test] +fn openai_tool_policy_gemini_sanitizes_schema_and_removes_strict() { + let mut req = request(json!({ + "model": "gemini-2.5-pro", + "messages": [{"role": "user", "content": "hi"}], + "tools": [{ + "type": "function", + "function": { + "name": "search", + "strict": true, + "parameters": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "additionalProperties": false, + "properties": { + "q": {"type": "string", "default": ""} + } + } + } + }] + })); + let catalog = empty_catalog(); + let mut warnings = TranslationWarnings::default(); + + prepare_openai_tool_request( + &mut req, + ctx( + BackendKind::Gemini, + Some("gemini"), + "gemini-2.5-pro", + &catalog, + ), + &mut warnings, + ) + .unwrap(); + + let tool = req.tools.as_ref().unwrap().first().unwrap(); + assert_eq!(tool.function.strict, None); + let params = tool.function.parameters.as_ref().unwrap(); + assert!(params.get("$schema").is_none()); + assert!(params.get("additionalProperties").is_none()); + assert!( + params.pointer("/properties/q/default").is_none(), + "Gemini rejects JSON Schema default in nested properties" + ); + assert_eq!( + warnings.as_header_value().as_deref(), + Some("tools.function.strict") + ); +} + +#[test] +fn openai_tool_policy_applies_gemini_policy_by_protocol_for_managed_backend() { + // A managed/custom backend can run BackendKind::OpenAI while its provider_id + // resolves to the Gemini OpenAI shim in the catalog. The policy must key off + // the provider protocol, not a hardcoded "gemini" id, so strict-stripping + // still applies. + let catalog = ProviderCatalog::bundled(); + let mut req = request(json!({ + "model": "gemini-2.5-pro", + "messages": [{"role": "user", "content": "hi"}], + "tools": [{ + "type": "function", + "function": { + "name": "search", + "strict": true, + "parameters": {"type": "object", "additionalProperties": false} + } + }] + })); + let mut warnings = TranslationWarnings::default(); + + prepare_openai_tool_request( + &mut req, + ctx( + BackendKind::OpenAI, + Some("gemini"), + "gemini-2.5-pro", + &catalog, + ), + &mut warnings, + ) + .unwrap(); + + let tool = req.tools.as_ref().unwrap().first().unwrap(); + assert_eq!(tool.function.strict, None); + assert!(tool + .function + .parameters + .as_ref() + .unwrap() + .get("additionalProperties") + .is_none()); +} + +#[test] +fn openai_tool_policy_gemini_strips_parallel_false_with_multiple_tools() { + let mut req = request(json!({ + "model": "gemini-2.5-pro", + "messages": [{"role": "user", "content": "hi"}], + "parallel_tool_calls": false, + "tools": [ + {"type": "function", "function": {"name": "a", "parameters": {"type": "object"}}}, + {"type": "function", "function": {"name": "b", "parameters": {"type": "object"}}} + ] + })); + let catalog = empty_catalog(); + let mut warnings = TranslationWarnings::default(); + + prepare_openai_tool_request( + &mut req, + ctx( + BackendKind::Gemini, + Some("gemini"), + "gemini-2.5-pro", + &catalog, + ), + &mut warnings, + ) + .unwrap(); + + // Gemini cannot honor parallel_tool_calls; the field is stripped and the + // degradation is reported rather than the request being rejected. + assert!(req.parallel_tool_calls.is_none()); + assert_eq!( + warnings.as_header_value().as_deref(), + Some("parallel_tool_calls") + ); +} + +#[test] +fn openai_tool_policy_rejects_required_tool_choice_when_model_lacks_it() { + let catalog = catalog(json!({ + "demo-model": { + "litellm_provider": "demo", + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": false + } + })); + let mut req = request(json!({ + "model": "demo-model", + "messages": [{"role": "user", "content": "hi"}], + "tools": [{"type": "function", "function": {"name": "a", "parameters": {"type": "object"}}}], + "tool_choice": "required" + })); + let mut warnings = TranslationWarnings::default(); + + let err = prepare_openai_tool_request( + &mut req, + ctx(BackendKind::OpenAI, Some("demo"), "demo-model", &catalog), + &mut warnings, + ) + .unwrap_err(); + + assert!(err.message().contains("tool_choice=required")); +} + +#[test] +fn openai_tool_policy_drops_auto_tool_choice_when_model_lacks_it() { + let catalog = catalog(json!({ + "demo-model": { + "litellm_provider": "demo", + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": false + } + })); + let mut req = request(json!({ + "model": "demo-model", + "messages": [{"role": "user", "content": "hi"}], + "tools": [{"type": "function", "function": {"name": "a", "parameters": {"type": "object"}}}], + "tool_choice": "auto" + })); + let mut warnings = TranslationWarnings::default(); + + prepare_openai_tool_request( + &mut req, + ctx(BackendKind::OpenAI, Some("demo"), "demo-model", &catalog), + &mut warnings, + ) + .unwrap(); + + assert!(req.tool_choice.is_none()); + assert!(req.tools.is_some()); + assert_eq!(warnings.as_header_value().as_deref(), Some("tool_choice")); +} + +#[test] +fn openai_tool_policy_drops_none_tool_choice_and_tools_without_continuation() { + let catalog = catalog(json!({ + "demo-model": { + "litellm_provider": "demo", + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": false + } + })); + let mut req = request(json!({ + "model": "demo-model", + "messages": [{"role": "user", "content": "hi"}], + "tools": [{"type": "function", "function": {"name": "a", "parameters": {"type": "object"}}}], + "tool_choice": "none" + })); + let mut warnings = TranslationWarnings::default(); + + prepare_openai_tool_request( + &mut req, + ctx(BackendKind::OpenAI, Some("demo"), "demo-model", &catalog), + &mut warnings, + ) + .unwrap(); + + assert!(req.tool_choice.is_none()); + assert!(req.tools.is_none()); + assert_eq!( + warnings.as_header_value().as_deref(), + Some("tool_choice, tools") + ); +} + +#[test] +fn openai_tool_policy_rejects_tools_when_known_model_lacks_tool_use() { + let catalog = catalog(json!({ + "demo-model": { + "litellm_provider": "demo", + "mode": "chat", + "supports_function_calling": false, + "supports_tool_choice": false + } + })); + let mut req = request(json!({ + "model": "demo-model", + "messages": [{"role": "user", "content": "hi"}], + "tools": [{"type": "function", "function": {"name": "a", "parameters": {"type": "object"}}}] + })); + let mut warnings = TranslationWarnings::default(); + + let err = prepare_openai_tool_request( + &mut req, + ctx(BackendKind::OpenAI, Some("demo"), "demo-model", &catalog), + &mut warnings, + ) + .unwrap_err(); + + assert!(err.message().contains("does not support tools")); +} + +#[test] +fn openai_tool_policy_allows_forced_tool_choice_for_self_hosted_provider() { + // vllm/lm_studio/llamafile/triton stubs advertise tool_use but a + // conservative provider-level tool_choice:false and carry no per-model + // metadata. Forced tool_choice must pass through (the backend decides), + // not 400 -- the provider-level flag is not authoritative for an unknown + // self-hosted model. + let catalog = ProviderCatalog::bundled(); + assert!( + catalog.list_models("vllm").is_empty(), + "test assumes vllm has no per-model metadata" + ); + let mut req = request(json!({ + "model": "Qwen/Qwen2.5-7B-Instruct", + "messages": [{"role": "user", "content": "hi"}], + "tools": [{"type": "function", "function": {"name": "a", "parameters": {"type": "object"}}}], + "tool_choice": "required" + })); + let mut warnings = TranslationWarnings::default(); + + prepare_openai_tool_request( + &mut req, + ctx( + BackendKind::OpenAI, + Some("vllm"), + "Qwen/Qwen2.5-7B-Instruct", + &catalog, + ), + &mut warnings, + ) + .unwrap(); + + assert!(matches!( + req.tool_choice, + Some(ChatToolChoice::Simple(ref v)) if v == "required" + )); +} + +#[test] +fn openai_tool_policy_rejects_native_anthropic_tools_when_known_model_lacks_tool_use() { + let catalog = catalog(json!({ + "demo-model": { + "litellm_provider": "bedrock", + "mode": "chat", + "supports_function_calling": false, + "supports_tool_choice": false + } + })); + let req: anthropic::MessageCreateRequest = serde_json::from_value(json!({ + "model": "demo-model", + "max_tokens": 64, + "tools": [{ + "name": "lookup", + "input_schema": {"type": "object"} + }], + "messages": [ + {"role": "user", "content": "hi"} + ] + })) + .unwrap(); + + let err = validate_anthropic_tool_request( + &req, + ctx( + BackendKind::Bedrock, + Some("bedrock"), + "demo-model", + &catalog, + ), + ) + .unwrap_err(); + + assert!(err.message().contains("does not support tools")); +} + +#[test] +fn openai_tool_policy_normalizes_streaming_top_level_tool_call_delta() { + let chunk = parse_openai_chat_completion_chunk( + r#"{ + "id": "chatcmpl_1", + "object": "chat.completion.chunk", + "created": 1, + "model": "local", + "choices": [{ + "index": 0, + "delta": { + "tool_calls": [{ + "index": 0, + "id": "call_1", + "name": "lookup", + "arguments": {"q":"x"} + }] + } + }] + }"#, + ) + .unwrap(); + + let tool_call = &chunk.choices[0].delta.tool_calls.as_ref().unwrap()[0]; + assert_eq!(tool_call.call_type.as_deref(), Some("function")); + assert_eq!( + tool_call.function.as_ref().unwrap().name.as_deref(), + Some("lookup") + ); + assert_eq!( + tool_call.function.as_ref().unwrap().arguments.as_deref(), + Some(r#"{"q":"x"}"#) + ); +} diff --git a/crates/proxy/src/optimizer.rs b/crates/proxy/src/optimizer.rs index 34369e5..dc14ce9 100644 --- a/crates/proxy/src/optimizer.rs +++ b/crates/proxy/src/optimizer.rs @@ -435,249 +435,5 @@ pub fn resolve_runtime_optimizer_locked( } #[cfg(test)] -mod tests { - use super::*; - use serde_json::json; - - fn long_text() -> String { - "The quick brown fox jumps over the lazy dog again and again across the wide \ - green field toward the distant blue mountains far beyond the winding river." - .repeat(2) - } - - fn long_openai_body() -> Value { - let long = long_text(); - let mut messages = vec![json!({"role":"system","content":"you are helpful"})]; - for _ in 0..16 { - messages.push(json!({"role":"user","content": long})); - messages.push(json!({"role":"assistant","content": long})); - } - messages.push(json!({"role":"user","content":"what is the latest?"})); - json!({"model":"gpt-4o","messages": messages}) - } - - #[test] - fn resolve_default_mode_unset_is_off() { - // Deliberately does not touch process env: relies on OPTIMIZER_MODE being - // absent by default in the test environment. - if std::env::var("OPTIMIZER_MODE").is_err() { - assert_eq!(resolve_default_mode(), Mode::Off); - } - } - - #[test] - fn off_mode_never_mutates_and_reports_off() { - let mut body = long_openai_body(); - let before = body.clone(); - let engine = OptimizerEngine::new(Mode::Off); - let report = engine.optimize_openai(&mut body, "chat_completions"); - assert_eq!(body, before, "off mode must not mutate the body"); - assert_eq!(report.mode, Mode::Off); - assert!(!report.applied); - } - - #[test] - fn shadow_mode_never_mutates_but_reports_savings() { - let mut body = long_openai_body(); - let before = body.clone(); - let engine = OptimizerEngine::new(Mode::Shadow); - let report = engine.optimize_openai(&mut body, "chat_completions"); - assert_eq!(body, before, "shadow mode must not mutate the body"); - assert!( - report.removed_tokens_est > 0, - "shadow should still report would-be savings on a long convo" - ); - } - - #[test] - fn live_mode_compresses_openai_history_and_preserves_latest_and_system() { - let mut body = long_openai_body(); - let orig_msgs = body["messages"].as_array().unwrap().clone(); - let engine = OptimizerEngine::new(Mode::Live); - let report = engine.optimize_openai(&mut body, "chat_completions"); - assert!(report.applied || report.rewrite_suffix_tokens == 0); - - let new_msgs = body["messages"].as_array().unwrap(); - assert_eq!(orig_msgs.len(), new_msgs.len()); - assert_eq!(orig_msgs[0], new_msgs[0], "system untouched"); - assert_eq!( - orig_msgs.last().unwrap(), - new_msgs.last().unwrap(), - "latest message untouched" - ); - } - - #[test] - fn live_mode_compresses_anthropic_history_and_preserves_latest() { - let long = long_text(); - let mut messages = vec![]; - for _ in 0..16 { - messages.push(json!({"role":"user","content": long})); - messages.push(json!({"role":"assistant","content": long})); - } - messages.push(json!({"role":"user","content":"what is the latest?"})); - let mut body = json!({ - "model":"claude-sonnet-5", - "system":"you are helpful", - "messages": messages, - }); - let orig_msgs = body["messages"].as_array().unwrap().clone(); - let orig_system = body["system"].clone(); - - let engine = OptimizerEngine::new(Mode::Live); - let report = engine.optimize_anthropic(&mut body, "messages"); - - assert_eq!(body["system"], orig_system, "system field untouched"); - let new_msgs = body["messages"].as_array().unwrap(); - assert_eq!(orig_msgs.len(), new_msgs.len()); - assert_eq!( - orig_msgs.last().unwrap(), - new_msgs.last().unwrap(), - "latest message untouched" - ); - // Live mode must place the deepest cache breakpoint at the frontier, not just - // compress text (crates/optimizer/CLAUDE.md checklist item 5). - assert!( - report.frontier > 0, - "long history must have a nonzero frontier" - ); - let bp_idx = report.frontier - 1; - let bp_msg = &new_msgs[bp_idx]; - let has_marker = bp_msg["content"] - .as_array() - .is_some_and(|arr| arr.iter().any(|b| b.get("cache_control").is_some())); - assert!( - has_marker, - "expected a cache_control breakpoint on message {bp_idx}, got {bp_msg}" - ); - } - - fn long_anthropic_body() -> Value { - let long = long_text(); - let mut messages = vec![]; - for _ in 0..16 { - messages.push(json!({"role":"user","content": long})); - messages.push(json!({"role":"assistant","content": long})); - } - messages.push(json!({"role":"user","content":"what is the latest?"})); - json!({ - "model":"claude-sonnet-5", - "system":"you are helpful", - "messages": messages, - }) - } - - #[test] - fn optimize_anthropic_bytes_live_shrinks_and_keeps_cache_control() { - let body = Bytes::from(serde_json::to_vec(&long_anthropic_body()).unwrap()); - let metrics = crate::metrics::Metrics::new(); - let engine = OptimizerEngine::new(Mode::Live); - let out = engine.optimize_anthropic_bytes(body.clone(), "messages", &metrics); - assert!(out.len() < body.len(), "live output must be smaller"); - // The frontier cache_control breakpoint must survive to the wire bytes -- - // the whole point of doing this on Bytes instead of the typed round-trip. - let root: Value = serde_json::from_slice(&out).unwrap(); - let has_marker = root["messages"].as_array().unwrap().iter().any(|m| { - m["content"] - .as_array() - .is_some_and(|arr| arr.iter().any(|b| b.get("cache_control").is_some())) - }); - assert!( - has_marker, - "cache_control breakpoint dropped from wire bytes" - ); - assert_eq!(metrics.snapshot().optimizer_compressed_total, 1); - } - - #[test] - fn optimize_anthropic_bytes_off_is_noop() { - let body = Bytes::from(serde_json::to_vec(&long_anthropic_body()).unwrap()); - let metrics = crate::metrics::Metrics::new(); - let engine = OptimizerEngine::new(Mode::Off); - let out = engine.optimize_anthropic_bytes(body.clone(), "messages", &metrics); - assert_eq!(out, body, "off mode must return the body unchanged"); - assert_eq!(metrics.snapshot().optimizer_compressed_total, 0); - } - - #[test] - fn optimize_anthropic_bytes_fails_open_on_garbage() { - let body = Bytes::from_static(b"not json"); - let metrics = crate::metrics::Metrics::new(); - let engine = OptimizerEngine::new(Mode::Live); - let out = engine.optimize_anthropic_bytes(body.clone(), "messages", &metrics); - assert_eq!(out, body, "garbage body returned unchanged"); - assert_eq!(metrics.snapshot().optimizer_compressed_total, 0); - } - - #[test] - fn with_mode_override_prefers_override() { - let engine = OptimizerEngine::new(Mode::Off); - let overridden = engine.with_mode_override("live"); - assert_eq!(overridden.policy.mode, Mode::Live); - // The original engine is untouched. - assert_eq!(engine.policy.mode, Mode::Off); - } - - #[test] - fn with_mode_override_falls_back_on_unparseable_value() { - let engine = OptimizerEngine::new(Mode::Shadow); - let overridden = engine.with_mode_override("not-a-real-mode"); - assert_eq!(overridden.policy.mode, Mode::Shadow); - } - - #[test] - fn resolve_runtime_optimizer_prefers_runtime_override() { - let engine = OptimizerEngine::new(Mode::Off); - let resolved = resolve_runtime_optimizer(&engine, "live"); - assert_eq!(resolved.policy.mode, Mode::Live); - } - - #[test] - fn resolve_runtime_optimizer_keeps_static_when_modes_match() { - let engine = OptimizerEngine::new(Mode::Shadow); - let resolved = resolve_runtime_optimizer(&engine, "shadow"); - assert_eq!(resolved.policy.mode, Mode::Shadow); - } - - #[test] - fn resolve_runtime_optimizer_falls_back_on_unparseable_value() { - let engine = OptimizerEngine::new(Mode::Shadow); - let resolved = resolve_runtime_optimizer(&engine, "not-a-real-mode"); - assert_eq!(resolved.policy.mode, Mode::Shadow); - } - - #[test] - fn route_precedence_wins_over_runtime_and_static() { - // Full three-tier precedence, mirroring `effective_optimizer`'s own logic: - // static (env-seeded) engine mode is Off, runtime tier says Shadow, but a - // route-level override of Live must win over both. - let static_engine = OptimizerEngine::new(Mode::Off); - let runtime_tier = resolve_runtime_optimizer(&static_engine, "shadow"); - assert_eq!( - runtime_tier.policy.mode, - Mode::Shadow, - "runtime beats static" - ); - - // Route override is applied directly against the static engine (as - // `effective_optimizer` does), bypassing the runtime tier entirely. - let route_tier = static_engine.with_mode_override("live"); - assert_eq!( - route_tier.policy.mode, - Mode::Live, - "route beats runtime and static" - ); - } - - #[test] - fn fails_open_on_malformed_messages_field() { - // "messages" is a string, not an array — adapters degrade to an empty - // Conversation rather than panicking, so this must not mutate or panic. - let mut body = json!({"model":"gpt-4o","messages":"not an array"}); - let before = body.clone(); - let engine = OptimizerEngine::new(Mode::Live); - let report = engine.optimize_openai(&mut body, "chat_completions"); - assert_eq!(body, before); - assert!(!report.applied); - } -} +#[path = "optimizer/tests.rs"] +mod tests; diff --git a/crates/proxy/src/optimizer/tests.rs b/crates/proxy/src/optimizer/tests.rs new file mode 100644 index 0000000..08d1b61 --- /dev/null +++ b/crates/proxy/src/optimizer/tests.rs @@ -0,0 +1,244 @@ +use super::*; +use serde_json::json; + +fn long_text() -> String { + "The quick brown fox jumps over the lazy dog again and again across the wide \ + green field toward the distant blue mountains far beyond the winding river." + .repeat(2) +} + +fn long_openai_body() -> Value { + let long = long_text(); + let mut messages = vec![json!({"role":"system","content":"you are helpful"})]; + for _ in 0..16 { + messages.push(json!({"role":"user","content": long})); + messages.push(json!({"role":"assistant","content": long})); + } + messages.push(json!({"role":"user","content":"what is the latest?"})); + json!({"model":"gpt-4o","messages": messages}) +} + +#[test] +fn resolve_default_mode_unset_is_off() { + // Deliberately does not touch process env: relies on OPTIMIZER_MODE being + // absent by default in the test environment. + if std::env::var("OPTIMIZER_MODE").is_err() { + assert_eq!(resolve_default_mode(), Mode::Off); + } +} + +#[test] +fn off_mode_never_mutates_and_reports_off() { + let mut body = long_openai_body(); + let before = body.clone(); + let engine = OptimizerEngine::new(Mode::Off); + let report = engine.optimize_openai(&mut body, "chat_completions"); + assert_eq!(body, before, "off mode must not mutate the body"); + assert_eq!(report.mode, Mode::Off); + assert!(!report.applied); +} + +#[test] +fn shadow_mode_never_mutates_but_reports_savings() { + let mut body = long_openai_body(); + let before = body.clone(); + let engine = OptimizerEngine::new(Mode::Shadow); + let report = engine.optimize_openai(&mut body, "chat_completions"); + assert_eq!(body, before, "shadow mode must not mutate the body"); + assert!( + report.removed_tokens_est > 0, + "shadow should still report would-be savings on a long convo" + ); +} + +#[test] +fn live_mode_compresses_openai_history_and_preserves_latest_and_system() { + let mut body = long_openai_body(); + let orig_msgs = body["messages"].as_array().unwrap().clone(); + let engine = OptimizerEngine::new(Mode::Live); + let report = engine.optimize_openai(&mut body, "chat_completions"); + assert!(report.applied || report.rewrite_suffix_tokens == 0); + + let new_msgs = body["messages"].as_array().unwrap(); + assert_eq!(orig_msgs.len(), new_msgs.len()); + assert_eq!(orig_msgs[0], new_msgs[0], "system untouched"); + assert_eq!( + orig_msgs.last().unwrap(), + new_msgs.last().unwrap(), + "latest message untouched" + ); +} + +#[test] +fn live_mode_compresses_anthropic_history_and_preserves_latest() { + let long = long_text(); + let mut messages = vec![]; + for _ in 0..16 { + messages.push(json!({"role":"user","content": long})); + messages.push(json!({"role":"assistant","content": long})); + } + messages.push(json!({"role":"user","content":"what is the latest?"})); + let mut body = json!({ + "model":"claude-sonnet-5", + "system":"you are helpful", + "messages": messages, + }); + let orig_msgs = body["messages"].as_array().unwrap().clone(); + let orig_system = body["system"].clone(); + + let engine = OptimizerEngine::new(Mode::Live); + let report = engine.optimize_anthropic(&mut body, "messages"); + + assert_eq!(body["system"], orig_system, "system field untouched"); + let new_msgs = body["messages"].as_array().unwrap(); + assert_eq!(orig_msgs.len(), new_msgs.len()); + assert_eq!( + orig_msgs.last().unwrap(), + new_msgs.last().unwrap(), + "latest message untouched" + ); + // Live mode must place the deepest cache breakpoint at the frontier, not just + // compress text (crates/optimizer/CLAUDE.md checklist item 5). + assert!( + report.frontier > 0, + "long history must have a nonzero frontier" + ); + let bp_idx = report.frontier - 1; + let bp_msg = &new_msgs[bp_idx]; + let has_marker = bp_msg["content"] + .as_array() + .is_some_and(|arr| arr.iter().any(|b| b.get("cache_control").is_some())); + assert!( + has_marker, + "expected a cache_control breakpoint on message {bp_idx}, got {bp_msg}" + ); +} + +fn long_anthropic_body() -> Value { + let long = long_text(); + let mut messages = vec![]; + for _ in 0..16 { + messages.push(json!({"role":"user","content": long})); + messages.push(json!({"role":"assistant","content": long})); + } + messages.push(json!({"role":"user","content":"what is the latest?"})); + json!({ + "model":"claude-sonnet-5", + "system":"you are helpful", + "messages": messages, + }) +} + +#[test] +fn optimize_anthropic_bytes_live_shrinks_and_keeps_cache_control() { + let body = Bytes::from(serde_json::to_vec(&long_anthropic_body()).unwrap()); + let metrics = crate::metrics::Metrics::new(); + let engine = OptimizerEngine::new(Mode::Live); + let out = engine.optimize_anthropic_bytes(body.clone(), "messages", &metrics); + assert!(out.len() < body.len(), "live output must be smaller"); + // The frontier cache_control breakpoint must survive to the wire bytes -- + // the whole point of doing this on Bytes instead of the typed round-trip. + let root: Value = serde_json::from_slice(&out).unwrap(); + let has_marker = root["messages"].as_array().unwrap().iter().any(|m| { + m["content"] + .as_array() + .is_some_and(|arr| arr.iter().any(|b| b.get("cache_control").is_some())) + }); + assert!( + has_marker, + "cache_control breakpoint dropped from wire bytes" + ); + assert_eq!(metrics.snapshot().optimizer_compressed_total, 1); +} + +#[test] +fn optimize_anthropic_bytes_off_is_noop() { + let body = Bytes::from(serde_json::to_vec(&long_anthropic_body()).unwrap()); + let metrics = crate::metrics::Metrics::new(); + let engine = OptimizerEngine::new(Mode::Off); + let out = engine.optimize_anthropic_bytes(body.clone(), "messages", &metrics); + assert_eq!(out, body, "off mode must return the body unchanged"); + assert_eq!(metrics.snapshot().optimizer_compressed_total, 0); +} + +#[test] +fn optimize_anthropic_bytes_fails_open_on_garbage() { + let body = Bytes::from_static(b"not json"); + let metrics = crate::metrics::Metrics::new(); + let engine = OptimizerEngine::new(Mode::Live); + let out = engine.optimize_anthropic_bytes(body.clone(), "messages", &metrics); + assert_eq!(out, body, "garbage body returned unchanged"); + assert_eq!(metrics.snapshot().optimizer_compressed_total, 0); +} + +#[test] +fn with_mode_override_prefers_override() { + let engine = OptimizerEngine::new(Mode::Off); + let overridden = engine.with_mode_override("live"); + assert_eq!(overridden.policy.mode, Mode::Live); + // The original engine is untouched. + assert_eq!(engine.policy.mode, Mode::Off); +} + +#[test] +fn with_mode_override_falls_back_on_unparseable_value() { + let engine = OptimizerEngine::new(Mode::Shadow); + let overridden = engine.with_mode_override("not-a-real-mode"); + assert_eq!(overridden.policy.mode, Mode::Shadow); +} + +#[test] +fn resolve_runtime_optimizer_prefers_runtime_override() { + let engine = OptimizerEngine::new(Mode::Off); + let resolved = resolve_runtime_optimizer(&engine, "live"); + assert_eq!(resolved.policy.mode, Mode::Live); +} + +#[test] +fn resolve_runtime_optimizer_keeps_static_when_modes_match() { + let engine = OptimizerEngine::new(Mode::Shadow); + let resolved = resolve_runtime_optimizer(&engine, "shadow"); + assert_eq!(resolved.policy.mode, Mode::Shadow); +} + +#[test] +fn resolve_runtime_optimizer_falls_back_on_unparseable_value() { + let engine = OptimizerEngine::new(Mode::Shadow); + let resolved = resolve_runtime_optimizer(&engine, "not-a-real-mode"); + assert_eq!(resolved.policy.mode, Mode::Shadow); +} + +#[test] +fn route_precedence_wins_over_runtime_and_static() { + // Full three-tier precedence, mirroring `effective_optimizer`'s own logic: + // static (env-seeded) engine mode is Off, runtime tier says Shadow, but a + // route-level override of Live must win over both. + let static_engine = OptimizerEngine::new(Mode::Off); + let runtime_tier = resolve_runtime_optimizer(&static_engine, "shadow"); + assert_eq!( + runtime_tier.policy.mode, + Mode::Shadow, + "runtime beats static" + ); + + // Route override is applied directly against the static engine (as + // `effective_optimizer` does), bypassing the runtime tier entirely. + let route_tier = static_engine.with_mode_override("live"); + assert_eq!( + route_tier.policy.mode, + Mode::Live, + "route beats runtime and static" + ); +} + +#[test] +fn fails_open_on_malformed_messages_field() { + // "messages" is a string, not an array — adapters degrade to an empty + // Conversation rather than panicking, so this must not mutate or panic. + let mut body = json!({"model":"gpt-4o","messages":"not an array"}); + let before = body.clone(); + let engine = OptimizerEngine::new(Mode::Live); + let report = engine.optimize_openai(&mut body, "chat_completions"); + assert_eq!(body, before); + assert!(!report.applied); +} diff --git a/crates/proxy/src/runtime/error.rs b/crates/proxy/src/runtime/error.rs new file mode 100644 index 0000000..e5bfa8d --- /dev/null +++ b/crates/proxy/src/runtime/error.rs @@ -0,0 +1,72 @@ +//! Runtime-specific error type (no axum response types). + +use crate::backend::BackendError; +use crate::config::BackendKind; +use std::fmt; + +/// Runtime errors that do not expose axum response types. +#[derive(Debug)] +pub enum ChatCompletionError { + InvalidRequest(String), + Translation(anyllm_translate::TranslateError), + Routing(String), + UnsupportedBackend { + backend_name: String, + backend_kind: BackendKind, + }, + Backend(BackendError), + StreamRead(String), + StreamParse(String), + StreamBufferOverflow, + StreamTimeout, +} + +impl ChatCompletionError { + pub fn status_code(&self) -> u16 { + match self { + Self::InvalidRequest(_) | Self::Translation(_) | Self::UnsupportedBackend { .. } => 400, + Self::Routing(_) => 429, + Self::Backend(e) => e.status_code(), + Self::StreamRead(_) + | Self::StreamParse(_) + | Self::StreamBufferOverflow + | Self::StreamTimeout => 502, + } + } +} + +impl fmt::Display for ChatCompletionError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InvalidRequest(msg) => write!(f, "{msg}"), + Self::Translation(e) => write!(f, "{e}"), + Self::Routing(msg) => write!(f, "{msg}"), + Self::UnsupportedBackend { + backend_name, + backend_kind, + } => write!( + f, + "backend '{backend_name}' ({backend_kind:?}) does not support Chat Completions runtime" + ), + Self::Backend(e) => write!(f, "{e}"), + Self::StreamRead(e) => write!(f, "stream read error: {e}"), + Self::StreamParse(e) => write!(f, "stream parse error: {e}"), + Self::StreamBufferOverflow => write!(f, "SSE buffer exceeded maximum size"), + Self::StreamTimeout => write!(f, "stream exceeded wall-clock timeout"), + } + } +} + +impl std::error::Error for ChatCompletionError {} + +impl From for ChatCompletionError { + fn from(e: anyllm_translate::TranslateError) -> Self { + Self::Translation(e) + } +} + +impl From for ChatCompletionError { + fn from(e: BackendError) -> Self { + Self::Backend(e) + } +} diff --git a/crates/proxy/src/runtime/mod.rs b/crates/proxy/src/runtime/mod.rs index f085fb5..1913eb6 100644 --- a/crates/proxy/src/runtime/mod.rs +++ b/crates/proxy/src/runtime/mod.rs @@ -2,572 +2,20 @@ //! //! This module exposes model routing and backend dispatch without taking //! ownership of HTTP routing, auth, admin UI, caching, or tool execution. +//! +//! - [`error`] runtime error type +//! - [`types`] result / metadata / service-trait types +//! - [`service`] the `ChatCompletionRuntime` implementation +//! - [`stream`] SSE chunk stream adapters -use crate::backend::{BackendClient, BackendError, RateLimitHeaders}; -use crate::config::{BackendKind, Config, ModelMapping, MultiConfig, OpenAIApiFormat}; -use crate::openai_tool_policy::{prepare_openai_tool_request, OpenAiToolPolicyContext}; -use anyllm_providers::ProviderCatalog; -use anyllm_translate::{ - mapping, openai, translate_anthropic_to_openai_response, translate_openai_to_anthropic_request, - TranslationWarnings, -}; -use futures::{ - future::{BoxFuture, FutureExt}, - Stream, -}; -use std::collections::HashMap; -use std::fmt; -use std::pin::Pin; -use std::sync::{Arc, RwLock}; -use std::time::Instant; - -/// Stream returned by [`ChatCompletionRuntime::complete_stream`]. -pub type ChatCompletionChunkStream = - Pin> + Send>>; - -/// Object-safe service API for one Chat Completions call at a time. -pub trait ChatCompletionService: Send + Sync { - fn complete<'a>( - &'a self, - req: openai::ChatCompletionRequest, - ) -> BoxFuture<'a, Result>; - - fn complete_stream<'a>( - &'a self, - req: openai::ChatCompletionRequest, - ) -> BoxFuture<'a, Result>; -} - -/// Non-streaming runtime response. -#[derive(Debug)] -pub struct ChatCompletionResult { - pub response: openai::ChatCompletionResponse, - pub usage: Option, - pub rate_limits: RateLimitHeaders, - pub metadata: ChatCompletionMetadata, - pub warnings: TranslationWarnings, -} - -/// Streaming runtime response. -pub struct ChatCompletionStreamResult { - pub chunks: ChatCompletionChunkStream, - pub rate_limits: RateLimitHeaders, - pub metadata: ChatCompletionMetadata, - pub warnings: TranslationWarnings, -} - -impl fmt::Debug for ChatCompletionStreamResult { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("ChatCompletionStreamResult") - .field("chunks", &"") - .field("rate_limits", &self.rate_limits) - .field("metadata", &self.metadata) - .field("warnings", &self.warnings) - .finish() - } -} - -/// Backend selection metadata for observability and callers that need accounting. -#[derive(Debug, Clone)] -pub struct ChatCompletionMetadata { - pub requested_model: String, - pub selected_backend: String, - pub mapped_model: String, - pub backend_kind: BackendKind, - pub provider_id: Option, - pub api_format: OpenAIApiFormat, - pub used_responses_api: bool, -} - -/// Runtime errors that do not expose axum response types. -#[derive(Debug)] -pub enum ChatCompletionError { - InvalidRequest(String), - Translation(anyllm_translate::TranslateError), - Routing(String), - UnsupportedBackend { - backend_name: String, - backend_kind: BackendKind, - }, - Backend(BackendError), - StreamRead(String), - StreamParse(String), - StreamBufferOverflow, - StreamTimeout, -} - -impl ChatCompletionError { - pub fn status_code(&self) -> u16 { - match self { - Self::InvalidRequest(_) | Self::Translation(_) | Self::UnsupportedBackend { .. } => 400, - Self::Routing(_) => 429, - Self::Backend(e) => e.status_code(), - Self::StreamRead(_) - | Self::StreamParse(_) - | Self::StreamBufferOverflow - | Self::StreamTimeout => 502, - } - } -} - -impl fmt::Display for ChatCompletionError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::InvalidRequest(msg) => write!(f, "{msg}"), - Self::Translation(e) => write!(f, "{e}"), - Self::Routing(msg) => write!(f, "{msg}"), - Self::UnsupportedBackend { - backend_name, - backend_kind, - } => write!( - f, - "backend '{backend_name}' ({backend_kind:?}) does not support Chat Completions runtime" - ), - Self::Backend(e) => write!(f, "{e}"), - Self::StreamRead(e) => write!(f, "stream read error: {e}"), - Self::StreamParse(e) => write!(f, "stream parse error: {e}"), - Self::StreamBufferOverflow => write!(f, "SSE buffer exceeded maximum size"), - Self::StreamTimeout => write!(f, "stream exceeded wall-clock timeout"), - } - } -} - -impl std::error::Error for ChatCompletionError {} - -impl From for ChatCompletionError { - fn from(e: anyllm_translate::TranslateError) -> Self { - Self::Translation(e) - } -} - -impl From for ChatCompletionError { - fn from(e: BackendError) -> Self { - Self::Backend(e) - } -} - -/// Chat Completions runtime built from proxy backend configuration. -#[derive(Clone)] -pub struct ChatCompletionRuntime { - default_backend: String, - backends: Arc>, - model_router: Option>>, - provider_catalog: Arc, -} - -#[derive(Clone)] -struct RuntimeBackend { - backend: BackendClient, - backend_name: String, - model_mapping: ModelMapping, - backend_kind: BackendKind, - api_format: OpenAIApiFormat, - omit_stream_options: bool, - stream_timeout_secs: u64, - provider_id: Option, -} - -struct ResolvedBackend { - state: RuntimeBackend, - mapped_model: String, - deployment: Option>, -} - -impl ChatCompletionRuntime { - /// Build a runtime from a legacy single-backend config. - pub fn from_config(config: Config) -> Self { - let multi = MultiConfig::from_single_config(&config); - let default_backend = multi.default_backend.clone(); - let (_, bc) = multi - .backends - .get_key_value(&default_backend) - .expect("wrapped config must contain default backend"); - - let backend = if config.backend == BackendKind::Bedrock { - BackendClient::from_backend_config(bc) - } else { - BackendClient::new(&config) - }; - - let mut backends = HashMap::new(); - backends.insert( - default_backend.clone(), - RuntimeBackend { - backend, - backend_name: default_backend.clone(), - model_mapping: bc.model_mapping.clone(), - backend_kind: bc.kind.clone(), - api_format: bc.api_format.clone(), - omit_stream_options: bc.omit_stream_options, - stream_timeout_secs: bc.stream_timeout_secs, - provider_id: config.provider_id.clone(), - }, - ); - - Self { - default_backend, - backends: Arc::new(backends), - model_router: None, - provider_catalog: Arc::new(ProviderCatalog::bundled()), - } - } - - /// Build a runtime from multi-backend config without model-list routing. - pub fn from_multi_config(config: MultiConfig) -> Self { - Self::from_multi_config_with_model_router(config, None) - } - - /// Build a runtime from multi-backend config and an optional model router. - pub fn from_multi_config_with_model_router( - config: MultiConfig, - model_router: Option>>, - ) -> Self { - let mut backends = HashMap::new(); - for (name, bc) in &config.backends { - backends.insert( - name.clone(), - RuntimeBackend { - backend: BackendClient::from_backend_config(bc), - backend_name: name.clone(), - model_mapping: bc.model_mapping.clone(), - backend_kind: bc.kind.clone(), - api_format: bc.api_format.clone(), - omit_stream_options: bc.omit_stream_options, - stream_timeout_secs: bc.stream_timeout_secs, - provider_id: bc.provider_id.clone(), - }, - ); - } - - Self { - default_backend: config.default_backend, - backends: Arc::new(backends), - model_router, - provider_catalog: Arc::new(ProviderCatalog::bundled()), - } - } - - fn resolve(&self, model: &str) -> Result { - if let Some(ref router_lock) = self.model_router { - let router = router_lock.read().unwrap_or_else(|e| e.into_inner()); - if let Some(routed) = router.route(model) { - let state = self - .backends - .get(routed.backend_name) - .cloned() - .ok_or_else(|| { - ChatCompletionError::Routing(format!( - "model '{model}' routed to unknown backend '{}'", - routed.backend_name - )) - })?; - return Ok(ResolvedBackend { - state, - mapped_model: routed.actual_model.to_string(), - deployment: Some(routed.deployment.clone()), - }); - } - if router.has_model(model) { - return Err(ChatCompletionError::Routing( - "all deployments for this model are at their RPM limit".to_string(), - )); - } - return Err(ChatCompletionError::InvalidRequest(format!( - "model '{model}' is not configured in model_list" - ))); - } - - let state = self - .backends - .get(&self.default_backend) - .cloned() - .ok_or_else(|| { - ChatCompletionError::Routing(format!( - "default backend '{}' is not configured", - self.default_backend - )) - })?; - Ok(ResolvedBackend { - mapped_model: state.model_mapping.map_model(model), - state, - deployment: None, - }) - } -} - -impl ChatCompletionService for ChatCompletionRuntime { - fn complete<'a>( - &'a self, - req: openai::ChatCompletionRequest, - ) -> BoxFuture<'a, Result> { - async move { self.complete_inner(req).await }.boxed() - } - - fn complete_stream<'a>( - &'a self, - req: openai::ChatCompletionRequest, - ) -> BoxFuture<'a, Result> { - async move { self.complete_stream_inner(req).await }.boxed() - } -} - -impl ChatCompletionRuntime { - async fn complete_inner( - &self, - req: openai::ChatCompletionRequest, - ) -> Result { - if req.stream == Some(true) { - return Err(ChatCompletionError::InvalidRequest( - "complete does not accept stream=true; use complete_stream".to_string(), - )); - } - - let requested_model = req.model.clone(); - let resolved = self.resolve(&requested_model)?; - let metadata = metadata(&requested_model, &resolved); - let mut warnings = TranslationWarnings::default(); - - match &resolved.state.backend { - BackendClient::OpenAI(client) - | BackendClient::AzureOpenAI(client) - | BackendClient::Vertex(client) - | BackendClient::GeminiOpenAI(client) => { - let mut openai_req = req; - prepare_openai_request( - &mut openai_req, - &resolved, - false, - &mut warnings, - &self.provider_catalog, - )?; - - let start = record_start(&resolved.deployment); - match client.chat_completion(&openai_req).await { - Ok((response, _status, rate_limits)) => { - record_finish(&resolved.deployment, start); - if let Some(ref deployment) = resolved.deployment { - if let Some(ref usage) = response.usage { - deployment.record_tokens(usage.total_tokens as u64); - } - } - Ok(ChatCompletionResult { - usage: response.usage.clone(), - response, - rate_limits, - metadata, - warnings, - }) - } - Err(e) => { - record_finish(&resolved.deployment, start); - Err(ChatCompletionError::Backend(BackendError::from(e))) - } - } - } - BackendClient::OpenAIResponses(client) => { - let anthropic_req = translate_openai_to_anthropic_request(&req, &mut warnings)?; - let mut responses_req = - mapping::responses_message_map::anthropic_to_responses_request(&anthropic_req); - responses_req.model = resolved.mapped_model.clone(); - - let start = record_start(&resolved.deployment); - match client.responses(&responses_req).await { - Ok((resp, _status, rate_limits)) => { - record_finish(&resolved.deployment, start); - let anthropic_resp = - mapping::responses_message_map::responses_to_anthropic_response( - &resp, - &requested_model, - ); - if let Some(ref deployment) = resolved.deployment { - deployment.record_tokens( - anthropic_resp.usage.input_tokens as u64 - + anthropic_resp.usage.output_tokens as u64, - ); - } - let response = translate_anthropic_to_openai_response( - &anthropic_resp, - &requested_model, - ); - Ok(ChatCompletionResult { - usage: response.usage.clone(), - response, - rate_limits, - metadata, - warnings, - }) - } - Err(e) => { - record_finish(&resolved.deployment, start); - Err(ChatCompletionError::Backend(BackendError::from(e))) - } - } - } - BackendClient::Anthropic(_) - | BackendClient::Bedrock(_) - | BackendClient::GeminiNative(_) => Err(ChatCompletionError::UnsupportedBackend { - backend_name: resolved.state.backend_name, - backend_kind: resolved.state.backend_kind, - }), - } - } - - async fn complete_stream_inner( - &self, - req: openai::ChatCompletionRequest, - ) -> Result { - let requested_model = req.model.clone(); - let resolved = self.resolve(&requested_model)?; - let metadata = metadata(&requested_model, &resolved); - let mut warnings = TranslationWarnings::default(); - - match &resolved.state.backend { - BackendClient::OpenAI(client) - | BackendClient::AzureOpenAI(client) - | BackendClient::Vertex(client) - | BackendClient::GeminiOpenAI(client) => { - let mut openai_req = req; - prepare_openai_request( - &mut openai_req, - &resolved, - true, - &mut warnings, - &self.provider_catalog, - )?; - - let start = record_start(&resolved.deployment); - match client.chat_completion_stream(&openai_req).await { - Ok((response, rate_limits)) => Ok(ChatCompletionStreamResult { - chunks: openai_chunk_stream( - response, - resolved.state.stream_timeout_secs, - DeploymentLatencyGuard::from_started( - resolved.deployment.clone(), - start, - ), - ), - rate_limits, - metadata, - warnings, - }), - Err(e) => { - record_finish(&resolved.deployment, start); - Err(ChatCompletionError::Backend(BackendError::from(e))) - } - } - } - BackendClient::OpenAIResponses(client) => { - let anthropic_req = translate_openai_to_anthropic_request(&req, &mut warnings)?; - let mut responses_req = - mapping::responses_message_map::anthropic_to_responses_request(&anthropic_req); - responses_req.model = resolved.mapped_model.clone(); - responses_req.stream = Some(true); - - let start = record_start(&resolved.deployment); - match client.responses_stream(&responses_req).await { - Ok((response, rate_limits)) => Ok(ChatCompletionStreamResult { - chunks: responses_chunk_stream( - response, - requested_model, - resolved.state.stream_timeout_secs, - DeploymentLatencyGuard::from_started( - resolved.deployment.clone(), - start, - ), - ), - rate_limits, - metadata, - warnings, - }), - Err(e) => { - record_finish(&resolved.deployment, start); - Err(ChatCompletionError::Backend(BackendError::from(e))) - } - } - } - BackendClient::Anthropic(_) - | BackendClient::Bedrock(_) - | BackendClient::GeminiNative(_) => Err(ChatCompletionError::UnsupportedBackend { - backend_name: resolved.state.backend_name, - backend_kind: resolved.state.backend_kind, - }), - } - } -} - -fn metadata(requested_model: &str, resolved: &ResolvedBackend) -> ChatCompletionMetadata { - ChatCompletionMetadata { - requested_model: requested_model.to_string(), - selected_backend: resolved.state.backend_name.clone(), - mapped_model: resolved.mapped_model.clone(), - backend_kind: resolved.state.backend_kind.clone(), - provider_id: resolved.state.provider_id.clone(), - api_format: resolved.state.api_format.clone(), - used_responses_api: matches!(resolved.state.backend, BackendClient::OpenAIResponses(_)), - } -} - -fn record_start( - deployment: &Option>, -) -> Option { - if let Some(d) = deployment { - d.record_start(); - Some(Instant::now()) - } else { - None - } -} - -fn record_finish( - deployment: &Option>, - start: Option, -) { - if let (Some(d), Some(start)) = (deployment, start) { - d.record_finish(start.elapsed().as_millis() as u64); - } -} - +mod error; +mod service; pub mod stream; -use stream::{openai_chunk_stream, responses_chunk_stream, DeploymentLatencyGuard}; +mod types; -fn prepare_openai_request( - req: &mut openai::ChatCompletionRequest, - resolved: &ResolvedBackend, - streaming: bool, - warnings: &mut TranslationWarnings, - provider_catalog: &ProviderCatalog, -) -> Result<(), ChatCompletionError> { - req.model = resolved.mapped_model.clone(); - req.stream = Some(streaming); - - if streaming { - if resolved.state.omit_stream_options { - if req.stream_options.is_some() { - warnings.add("stream_options"); - } - req.stream_options = None; - } else { - req.stream_options = Some(openai::StreamOptions { - include_usage: true, - }); - } - } else if resolved.state.omit_stream_options { - if req.stream_options.is_some() { - warnings.add("stream_options"); - } - req.stream_options = None; - } - - prepare_openai_tool_request( - req, - OpenAiToolPolicyContext { - backend_kind: resolved.state.backend_kind.clone(), - provider_id: resolved.state.provider_id.as_deref(), - model: &resolved.mapped_model, - provider_catalog, - }, - warnings, - ) - .map(|_| ()) - .map_err(|e| ChatCompletionError::InvalidRequest(e.to_string())) -} +pub use error::ChatCompletionError; +pub use service::ChatCompletionRuntime; +pub use types::{ + ChatCompletionChunkStream, ChatCompletionMetadata, ChatCompletionResult, ChatCompletionService, + ChatCompletionStreamResult, +}; diff --git a/crates/proxy/src/runtime/service.rs b/crates/proxy/src/runtime/service.rs new file mode 100644 index 0000000..0b05669 --- /dev/null +++ b/crates/proxy/src/runtime/service.rs @@ -0,0 +1,443 @@ +//! `ChatCompletionRuntime`: model routing, backend dispatch, and the +//! `ChatCompletionService` implementation. + +use super::error::ChatCompletionError; +use super::stream::{openai_chunk_stream, responses_chunk_stream, DeploymentLatencyGuard}; +use super::types::{ + ChatCompletionMetadata, ChatCompletionResult, ChatCompletionService, ChatCompletionStreamResult, +}; +use crate::backend::{BackendClient, BackendError}; +use crate::config::{BackendKind, Config, ModelMapping, MultiConfig, OpenAIApiFormat}; +use crate::openai_tool_policy::{prepare_openai_tool_request, OpenAiToolPolicyContext}; +use anyllm_providers::ProviderCatalog; +use anyllm_translate::{ + mapping, openai, translate_anthropic_to_openai_response, translate_openai_to_anthropic_request, + TranslationWarnings, +}; +use futures::future::{BoxFuture, FutureExt}; +use std::collections::HashMap; +use std::sync::{Arc, RwLock}; +use std::time::Instant; + +/// Chat Completions runtime built from proxy backend configuration. +#[derive(Clone)] +pub struct ChatCompletionRuntime { + default_backend: String, + backends: Arc>, + model_router: Option>>, + provider_catalog: Arc, +} + +#[derive(Clone)] +struct RuntimeBackend { + backend: BackendClient, + backend_name: String, + model_mapping: ModelMapping, + backend_kind: BackendKind, + api_format: OpenAIApiFormat, + omit_stream_options: bool, + stream_timeout_secs: u64, + provider_id: Option, +} + +struct ResolvedBackend { + state: RuntimeBackend, + mapped_model: String, + deployment: Option>, +} + +impl ChatCompletionRuntime { + /// Build a runtime from a legacy single-backend config. + pub fn from_config(config: Config) -> Self { + let multi = MultiConfig::from_single_config(&config); + let default_backend = multi.default_backend.clone(); + let (_, bc) = multi + .backends + .get_key_value(&default_backend) + .expect("wrapped config must contain default backend"); + + let backend = if config.backend == BackendKind::Bedrock { + BackendClient::from_backend_config(bc) + } else { + BackendClient::new(&config) + }; + + let mut backends = HashMap::new(); + backends.insert( + default_backend.clone(), + RuntimeBackend { + backend, + backend_name: default_backend.clone(), + model_mapping: bc.model_mapping.clone(), + backend_kind: bc.kind.clone(), + api_format: bc.api_format.clone(), + omit_stream_options: bc.omit_stream_options, + stream_timeout_secs: bc.stream_timeout_secs, + provider_id: config.provider_id.clone(), + }, + ); + + Self { + default_backend, + backends: Arc::new(backends), + model_router: None, + provider_catalog: Arc::new(ProviderCatalog::bundled()), + } + } + + /// Build a runtime from multi-backend config without model-list routing. + pub fn from_multi_config(config: MultiConfig) -> Self { + Self::from_multi_config_with_model_router(config, None) + } + + /// Build a runtime from multi-backend config and an optional model router. + pub fn from_multi_config_with_model_router( + config: MultiConfig, + model_router: Option>>, + ) -> Self { + let mut backends = HashMap::new(); + for (name, bc) in &config.backends { + backends.insert( + name.clone(), + RuntimeBackend { + backend: BackendClient::from_backend_config(bc), + backend_name: name.clone(), + model_mapping: bc.model_mapping.clone(), + backend_kind: bc.kind.clone(), + api_format: bc.api_format.clone(), + omit_stream_options: bc.omit_stream_options, + stream_timeout_secs: bc.stream_timeout_secs, + provider_id: bc.provider_id.clone(), + }, + ); + } + + Self { + default_backend: config.default_backend, + backends: Arc::new(backends), + model_router, + provider_catalog: Arc::new(ProviderCatalog::bundled()), + } + } + + fn resolve(&self, model: &str) -> Result { + if let Some(ref router_lock) = self.model_router { + let router = router_lock.read().unwrap_or_else(|e| e.into_inner()); + if let Some(routed) = router.route(model) { + let state = self + .backends + .get(routed.backend_name) + .cloned() + .ok_or_else(|| { + ChatCompletionError::Routing(format!( + "model '{model}' routed to unknown backend '{}'", + routed.backend_name + )) + })?; + return Ok(ResolvedBackend { + state, + mapped_model: routed.actual_model.to_string(), + deployment: Some(routed.deployment.clone()), + }); + } + if router.has_model(model) { + return Err(ChatCompletionError::Routing( + "all deployments for this model are at their RPM limit".to_string(), + )); + } + return Err(ChatCompletionError::InvalidRequest(format!( + "model '{model}' is not configured in model_list" + ))); + } + + let state = self + .backends + .get(&self.default_backend) + .cloned() + .ok_or_else(|| { + ChatCompletionError::Routing(format!( + "default backend '{}' is not configured", + self.default_backend + )) + })?; + Ok(ResolvedBackend { + mapped_model: state.model_mapping.map_model(model), + state, + deployment: None, + }) + } +} + +impl ChatCompletionService for ChatCompletionRuntime { + fn complete<'a>( + &'a self, + req: openai::ChatCompletionRequest, + ) -> BoxFuture<'a, Result> { + async move { self.complete_inner(req).await }.boxed() + } + + fn complete_stream<'a>( + &'a self, + req: openai::ChatCompletionRequest, + ) -> BoxFuture<'a, Result> { + async move { self.complete_stream_inner(req).await }.boxed() + } +} + +impl ChatCompletionRuntime { + async fn complete_inner( + &self, + req: openai::ChatCompletionRequest, + ) -> Result { + if req.stream == Some(true) { + return Err(ChatCompletionError::InvalidRequest( + "complete does not accept stream=true; use complete_stream".to_string(), + )); + } + + let requested_model = req.model.clone(); + let resolved = self.resolve(&requested_model)?; + let metadata = metadata(&requested_model, &resolved); + let mut warnings = TranslationWarnings::default(); + + match &resolved.state.backend { + BackendClient::OpenAI(client) + | BackendClient::AzureOpenAI(client) + | BackendClient::Vertex(client) + | BackendClient::GeminiOpenAI(client) => { + let mut openai_req = req; + prepare_openai_request( + &mut openai_req, + &resolved, + false, + &mut warnings, + &self.provider_catalog, + )?; + + let start = record_start(&resolved.deployment); + match client.chat_completion(&openai_req).await { + Ok((response, _status, rate_limits)) => { + record_finish(&resolved.deployment, start); + if let Some(ref deployment) = resolved.deployment { + if let Some(ref usage) = response.usage { + deployment.record_tokens(usage.total_tokens as u64); + } + } + Ok(ChatCompletionResult { + usage: response.usage.clone(), + response, + rate_limits, + metadata, + warnings, + }) + } + Err(e) => { + record_finish(&resolved.deployment, start); + Err(ChatCompletionError::Backend(BackendError::from(e))) + } + } + } + BackendClient::OpenAIResponses(client) => { + let anthropic_req = translate_openai_to_anthropic_request(&req, &mut warnings)?; + let mut responses_req = + mapping::responses_message_map::anthropic_to_responses_request(&anthropic_req); + responses_req.model = resolved.mapped_model.clone(); + + let start = record_start(&resolved.deployment); + match client.responses(&responses_req).await { + Ok((resp, _status, rate_limits)) => { + record_finish(&resolved.deployment, start); + let anthropic_resp = + mapping::responses_message_map::responses_to_anthropic_response( + &resp, + &requested_model, + ); + if let Some(ref deployment) = resolved.deployment { + deployment.record_tokens( + anthropic_resp.usage.input_tokens as u64 + + anthropic_resp.usage.output_tokens as u64, + ); + } + let response = translate_anthropic_to_openai_response( + &anthropic_resp, + &requested_model, + ); + Ok(ChatCompletionResult { + usage: response.usage.clone(), + response, + rate_limits, + metadata, + warnings, + }) + } + Err(e) => { + record_finish(&resolved.deployment, start); + Err(ChatCompletionError::Backend(BackendError::from(e))) + } + } + } + BackendClient::Anthropic(_) + | BackendClient::Bedrock(_) + | BackendClient::GeminiNative(_) => Err(ChatCompletionError::UnsupportedBackend { + backend_name: resolved.state.backend_name, + backend_kind: resolved.state.backend_kind, + }), + } + } + + async fn complete_stream_inner( + &self, + req: openai::ChatCompletionRequest, + ) -> Result { + let requested_model = req.model.clone(); + let resolved = self.resolve(&requested_model)?; + let metadata = metadata(&requested_model, &resolved); + let mut warnings = TranslationWarnings::default(); + + match &resolved.state.backend { + BackendClient::OpenAI(client) + | BackendClient::AzureOpenAI(client) + | BackendClient::Vertex(client) + | BackendClient::GeminiOpenAI(client) => { + let mut openai_req = req; + prepare_openai_request( + &mut openai_req, + &resolved, + true, + &mut warnings, + &self.provider_catalog, + )?; + + let start = record_start(&resolved.deployment); + match client.chat_completion_stream(&openai_req).await { + Ok((response, rate_limits)) => Ok(ChatCompletionStreamResult { + chunks: openai_chunk_stream( + response, + resolved.state.stream_timeout_secs, + DeploymentLatencyGuard::from_started( + resolved.deployment.clone(), + start, + ), + ), + rate_limits, + metadata, + warnings, + }), + Err(e) => { + record_finish(&resolved.deployment, start); + Err(ChatCompletionError::Backend(BackendError::from(e))) + } + } + } + BackendClient::OpenAIResponses(client) => { + let anthropic_req = translate_openai_to_anthropic_request(&req, &mut warnings)?; + let mut responses_req = + mapping::responses_message_map::anthropic_to_responses_request(&anthropic_req); + responses_req.model = resolved.mapped_model.clone(); + responses_req.stream = Some(true); + + let start = record_start(&resolved.deployment); + match client.responses_stream(&responses_req).await { + Ok((response, rate_limits)) => Ok(ChatCompletionStreamResult { + chunks: responses_chunk_stream( + response, + requested_model, + resolved.state.stream_timeout_secs, + DeploymentLatencyGuard::from_started( + resolved.deployment.clone(), + start, + ), + ), + rate_limits, + metadata, + warnings, + }), + Err(e) => { + record_finish(&resolved.deployment, start); + Err(ChatCompletionError::Backend(BackendError::from(e))) + } + } + } + BackendClient::Anthropic(_) + | BackendClient::Bedrock(_) + | BackendClient::GeminiNative(_) => Err(ChatCompletionError::UnsupportedBackend { + backend_name: resolved.state.backend_name, + backend_kind: resolved.state.backend_kind, + }), + } + } +} + +fn metadata(requested_model: &str, resolved: &ResolvedBackend) -> ChatCompletionMetadata { + ChatCompletionMetadata { + requested_model: requested_model.to_string(), + selected_backend: resolved.state.backend_name.clone(), + mapped_model: resolved.mapped_model.clone(), + backend_kind: resolved.state.backend_kind.clone(), + provider_id: resolved.state.provider_id.clone(), + api_format: resolved.state.api_format.clone(), + used_responses_api: matches!(resolved.state.backend, BackendClient::OpenAIResponses(_)), + } +} + +fn record_start( + deployment: &Option>, +) -> Option { + if let Some(d) = deployment { + d.record_start(); + Some(Instant::now()) + } else { + None + } +} + +fn record_finish( + deployment: &Option>, + start: Option, +) { + if let (Some(d), Some(start)) = (deployment, start) { + d.record_finish(start.elapsed().as_millis() as u64); + } +} + +fn prepare_openai_request( + req: &mut openai::ChatCompletionRequest, + resolved: &ResolvedBackend, + streaming: bool, + warnings: &mut TranslationWarnings, + provider_catalog: &ProviderCatalog, +) -> Result<(), ChatCompletionError> { + req.model = resolved.mapped_model.clone(); + req.stream = Some(streaming); + + if streaming { + if resolved.state.omit_stream_options { + if req.stream_options.is_some() { + warnings.add("stream_options"); + } + req.stream_options = None; + } else { + req.stream_options = Some(openai::StreamOptions { + include_usage: true, + }); + } + } else if resolved.state.omit_stream_options { + if req.stream_options.is_some() { + warnings.add("stream_options"); + } + req.stream_options = None; + } + + prepare_openai_tool_request( + req, + OpenAiToolPolicyContext { + backend_kind: resolved.state.backend_kind.clone(), + provider_id: resolved.state.provider_id.as_deref(), + model: &resolved.mapped_model, + provider_catalog, + }, + warnings, + ) + .map(|_| ()) + .map_err(|e| ChatCompletionError::InvalidRequest(e.to_string())) +} diff --git a/crates/proxy/src/runtime/types.rs b/crates/proxy/src/runtime/types.rs new file mode 100644 index 0000000..455da73 --- /dev/null +++ b/crates/proxy/src/runtime/types.rs @@ -0,0 +1,67 @@ +//! Runtime result, metadata, and service-trait types. + +use super::error::ChatCompletionError; +use crate::backend::RateLimitHeaders; +use crate::config::{BackendKind, OpenAIApiFormat}; +use anyllm_translate::{openai, TranslationWarnings}; +use futures::{future::BoxFuture, Stream}; +use std::fmt; +use std::pin::Pin; + +/// Stream returned by [`ChatCompletionRuntime::complete_stream`]. +pub type ChatCompletionChunkStream = + Pin> + Send>>; + +/// Object-safe service API for one Chat Completions call at a time. +pub trait ChatCompletionService: Send + Sync { + fn complete<'a>( + &'a self, + req: openai::ChatCompletionRequest, + ) -> BoxFuture<'a, Result>; + + fn complete_stream<'a>( + &'a self, + req: openai::ChatCompletionRequest, + ) -> BoxFuture<'a, Result>; +} + +/// Non-streaming runtime response. +#[derive(Debug)] +pub struct ChatCompletionResult { + pub response: openai::ChatCompletionResponse, + pub usage: Option, + pub rate_limits: RateLimitHeaders, + pub metadata: ChatCompletionMetadata, + pub warnings: TranslationWarnings, +} + +/// Streaming runtime response. +pub struct ChatCompletionStreamResult { + pub chunks: ChatCompletionChunkStream, + pub rate_limits: RateLimitHeaders, + pub metadata: ChatCompletionMetadata, + pub warnings: TranslationWarnings, +} + +impl fmt::Debug for ChatCompletionStreamResult { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ChatCompletionStreamResult") + .field("chunks", &"") + .field("rate_limits", &self.rate_limits) + .field("metadata", &self.metadata) + .field("warnings", &self.warnings) + .finish() + } +} + +/// Backend selection metadata for observability and callers that need accounting. +#[derive(Debug, Clone)] +pub struct ChatCompletionMetadata { + pub requested_model: String, + pub selected_backend: String, + pub mapped_model: String, + pub backend_kind: BackendKind, + pub provider_id: Option, + pub api_format: OpenAIApiFormat, + pub used_responses_api: bool, +} diff --git a/crates/proxy/src/server/middleware/auth.rs b/crates/proxy/src/server/middleware/auth.rs index 70a65cc..eff346a 100644 --- a/crates/proxy/src/server/middleware/auth.rs +++ b/crates/proxy/src/server/middleware/auth.rs @@ -509,78 +509,5 @@ pub async fn validate_auth( } #[cfg(test)] -mod auth_mode_tests { - use super::*; - - #[test] - fn parse_auth_mode_new_names() { - assert_eq!(AuthMode::from_env_str("oidc"), AuthMode::OidcOnly); - assert_eq!(AuthMode::from_env_str("oidc-only"), AuthMode::OidcOnly); - assert_eq!(AuthMode::from_env_str("oidc_only"), AuthMode::OidcOnly); - assert_eq!(AuthMode::from_env_str("keys"), AuthMode::KeysOnly); - assert_eq!(AuthMode::from_env_str("keys-only"), AuthMode::KeysOnly); - assert_eq!(AuthMode::from_env_str("keys_only"), AuthMode::KeysOnly); - assert_eq!(AuthMode::from_env_str("both"), AuthMode::Both); - } - - #[test] - fn parse_auth_mode_legacy_names() { - assert_eq!(AuthMode::from_env_str("jwt_only"), AuthMode::OidcOnly); - assert_eq!(AuthMode::from_env_str("jwt_or_keys"), AuthMode::Both); - assert_eq!(AuthMode::from_env_str("JWT_ONLY"), AuthMode::OidcOnly); - } - - #[test] - fn parse_auth_mode_unknown_defaults_to_both() { - assert_eq!(AuthMode::from_env_str("unknown"), AuthMode::Both); - assert_eq!(AuthMode::from_env_str(""), AuthMode::Both); - } - - #[test] - fn auth_mode_oidc_only() { - assert!(AuthMode::OidcOnly.allows_oidc()); - assert!(!AuthMode::OidcOnly.allows_key_auth()); - } - - #[test] - fn auth_mode_keys_only() { - assert!(AuthMode::KeysOnly.allows_key_auth()); - assert!(!AuthMode::KeysOnly.allows_oidc()); - } - - #[test] - fn auth_mode_both_allows_all() { - assert!(AuthMode::Both.allows_oidc()); - assert!(AuthMode::Both.allows_key_auth()); - } - - #[test] - fn auth_mode_from_env_defaults_to_both() { - let mode = AuthMode::from_env_str("unrecognized_value"); - assert_eq!(mode, AuthMode::Both); - } - - #[test] - fn forward_client_auth_rejects_multiple_static_keys_without_open_relay() { - assert!(forward_client_auth_misconfigured(2, false)); - } - - #[test] - fn forward_client_auth_allows_open_relay_even_with_multiple_keys() { - // Reflects that `open_relay_active()` can only be true when - // `distinct_static_key_count()` is 0 (see the OPEN_RELAY static) -- - // this combination is unreachable via those two real accessors, but - // the pure decision function itself must still handle it sanely. - assert!(!forward_client_auth_misconfigured(2, true)); - } - - #[test] - fn forward_client_auth_allows_exactly_one_key() { - assert!(!forward_client_auth_misconfigured(1, false)); - } - - #[test] - fn forward_client_auth_allows_zero_keys() { - assert!(!forward_client_auth_misconfigured(0, false)); - } -} +#[path = "auth/tests.rs"] +mod tests; diff --git a/crates/proxy/src/server/middleware/auth/tests.rs b/crates/proxy/src/server/middleware/auth/tests.rs new file mode 100644 index 0000000..6ad9f34 --- /dev/null +++ b/crates/proxy/src/server/middleware/auth/tests.rs @@ -0,0 +1,73 @@ +use super::*; + +#[test] +fn parse_auth_mode_new_names() { + assert_eq!(AuthMode::from_env_str("oidc"), AuthMode::OidcOnly); + assert_eq!(AuthMode::from_env_str("oidc-only"), AuthMode::OidcOnly); + assert_eq!(AuthMode::from_env_str("oidc_only"), AuthMode::OidcOnly); + assert_eq!(AuthMode::from_env_str("keys"), AuthMode::KeysOnly); + assert_eq!(AuthMode::from_env_str("keys-only"), AuthMode::KeysOnly); + assert_eq!(AuthMode::from_env_str("keys_only"), AuthMode::KeysOnly); + assert_eq!(AuthMode::from_env_str("both"), AuthMode::Both); +} + +#[test] +fn parse_auth_mode_legacy_names() { + assert_eq!(AuthMode::from_env_str("jwt_only"), AuthMode::OidcOnly); + assert_eq!(AuthMode::from_env_str("jwt_or_keys"), AuthMode::Both); + assert_eq!(AuthMode::from_env_str("JWT_ONLY"), AuthMode::OidcOnly); +} + +#[test] +fn parse_auth_mode_unknown_defaults_to_both() { + assert_eq!(AuthMode::from_env_str("unknown"), AuthMode::Both); + assert_eq!(AuthMode::from_env_str(""), AuthMode::Both); +} + +#[test] +fn auth_mode_oidc_only() { + assert!(AuthMode::OidcOnly.allows_oidc()); + assert!(!AuthMode::OidcOnly.allows_key_auth()); +} + +#[test] +fn auth_mode_keys_only() { + assert!(AuthMode::KeysOnly.allows_key_auth()); + assert!(!AuthMode::KeysOnly.allows_oidc()); +} + +#[test] +fn auth_mode_both_allows_all() { + assert!(AuthMode::Both.allows_oidc()); + assert!(AuthMode::Both.allows_key_auth()); +} + +#[test] +fn auth_mode_from_env_defaults_to_both() { + let mode = AuthMode::from_env_str("unrecognized_value"); + assert_eq!(mode, AuthMode::Both); +} + +#[test] +fn forward_client_auth_rejects_multiple_static_keys_without_open_relay() { + assert!(forward_client_auth_misconfigured(2, false)); +} + +#[test] +fn forward_client_auth_allows_open_relay_even_with_multiple_keys() { + // Reflects that `open_relay_active()` can only be true when + // `distinct_static_key_count()` is 0 (see the OPEN_RELAY static) -- + // this combination is unreachable via those two real accessors, but + // the pure decision function itself must still handle it sanely. + assert!(!forward_client_auth_misconfigured(2, true)); +} + +#[test] +fn forward_client_auth_allows_exactly_one_key() { + assert!(!forward_client_auth_misconfigured(1, false)); +} + +#[test] +fn forward_client_auth_allows_zero_keys() { + assert!(!forward_client_auth_misconfigured(0, false)); +} diff --git a/crates/proxy/src/server/passthrough/auth.rs b/crates/proxy/src/server/passthrough/auth.rs new file mode 100644 index 0000000..9fb4063 --- /dev/null +++ b/crates/proxy/src/server/passthrough/auth.rs @@ -0,0 +1,207 @@ +use crate::server::middleware::ClientAuthPath; +use axum::http::HeaderMap; + +/// Selects the exact incoming credential to forward upstream when +/// `ANTHROPIC_FORWARD_CLIENT_AUTH` is enabled. Same precedence as +/// `validate_auth` (`x-api-key` / `x-goog-api-key` win over `authorization`, +/// see `server/middleware/auth.rs`'s `api_key = headers.get("x-api-key") +/// .or_else(|| headers.get("x-goog-api-key"))`) so this always forwards the +/// credential that actually gated the request into the proxy, never a +/// second, unrelated header the client also happened to send. +/// +/// `x-goog-api-key` (Gemini-CLI compatibility) is folded into the +/// `x-api-key` slot rather than forwarded under its own name: Anthropic's API +/// only recognizes `x-api-key`/`authorization`, so a client authenticated via +/// `x-goog-api-key` must still have its value sent upstream as `x-api-key`, +/// not as a header name Anthropic would silently ignore. Beyond that one +/// rename, no shape detection/conversion happens -- forwarded byte-for-byte, +/// unlike LiteLLM's `optionally_handle_anthropic_oauth()`, which mis-converts +/// a Bearer token into `x-api-key`. +fn select_client_auth_override(headers: &HeaderMap) -> Option<(&'static str, &str)> { + if let Some(v) = headers + .get("x-api-key") + .or_else(|| headers.get("x-goog-api-key")) + .and_then(|v| v.to_str().ok()) + { + if !v.is_empty() { + return Some(("x-api-key", v)); + } + } + if let Some(v) = headers.get("authorization").and_then(|v| v.to_str().ok()) { + if !v.is_empty() { + return Some(("authorization", v)); + } + } + None +} + +/// Only `StaticKey`/`OpenRelay` mean "the credential that gated this request +/// IS the operator's own secret" (a single-key/BYOK deployment). A virtual +/// key is deliberately not a real Anthropic credential and a JWT is a +/// proxy-auth artifact, so those must never be forwarded upstream regardless +/// of the `ANTHROPIC_FORWARD_CLIENT_AUTH` toggle. +fn client_auth_forwardable(auth_path: Option) -> bool { + matches!( + auth_path, + Some(ClientAuthPath::StaticKey) | Some(ClientAuthPath::OpenRelay) + ) +} + +/// Resolves the client-credential override shared by both passthrough +/// handlers. `vk_ctx`/`claims` are checked directly, not just via +/// `client_auth_forwardable(auth_path)`: `ClientAuthPath` and +/// `VirtualKeyContext`/`JwtClaims` are inserted as two independent +/// `request.extensions_mut().insert()` calls in `validate_auth`, with +/// nothing structurally coupling them, so a future edit to one of those +/// branches could desync them without a compile error. Re-checking presence +/// of the extension that actually gates virtual-key/OIDC requests fails +/// closed instead of silently forwarding a non-operator credential if that +/// ever happens. +pub(crate) fn resolve_client_auth_override<'h>( + forward_client_auth: bool, + auth_path: Option, + vk_ctx: &Option, + claims: &Option, + headers: &'h HeaderMap, +) -> Option<(&'static str, &'h str)> { + if forward_client_auth + && client_auth_forwardable(auth_path) + && vk_ctx.is_none() + && claims.is_none() + { + select_client_auth_override(headers) + } else { + None + } +} + +#[cfg(test)] +mod tests { + use super::{ + client_auth_forwardable, resolve_client_auth_override, select_client_auth_override, + ClientAuthPath, + }; + use axum::http::HeaderMap; + + fn headers(pairs: &[(&str, &str)]) -> HeaderMap { + let mut h = HeaderMap::new(); + for (k, v) in pairs { + h.insert( + axum::http::HeaderName::from_bytes(k.as_bytes()).unwrap(), + v.parse().unwrap(), + ); + } + h + } + + #[test] + fn selects_x_api_key_when_only_that_is_sent() { + let h = headers(&[("x-api-key", "client-key")]); + assert_eq!( + select_client_auth_override(&h), + Some(("x-api-key", "client-key")) + ); + } + + #[test] + fn selects_authorization_when_only_that_is_sent() { + let h = headers(&[("authorization", "Bearer sk-ant-oat-abc")]); + assert_eq!( + select_client_auth_override(&h), + Some(("authorization", "Bearer sk-ant-oat-abc")) + ); + } + + #[test] + fn prefers_x_api_key_when_both_sent_matching_validate_auth_precedence() { + let h = headers(&[ + ("x-api-key", "client-key"), + ("authorization", "Bearer sk-ant-oat-abc"), + ]); + assert_eq!( + select_client_auth_override(&h), + Some(("x-api-key", "client-key")) + ); + } + + #[test] + fn returns_none_when_neither_header_sent() { + let h = headers(&[]); + assert_eq!(select_client_auth_override(&h), None); + } + + #[test] + fn selects_x_goog_api_key_forwarded_as_x_api_key() { + // validate_auth (server/middleware/auth.rs) treats x-goog-api-key as + // fully equivalent to x-api-key for authentication, but Anthropic's + // API only understands x-api-key -- the value must be forwarded + // under the x-api-key name, not the literal x-goog-api-key name. + let h = headers(&[("x-goog-api-key", "gemini-cli-key")]); + assert_eq!( + select_client_auth_override(&h), + Some(("x-api-key", "gemini-cli-key")) + ); + } + + #[test] + fn prefers_x_api_key_over_x_goog_api_key_matching_validate_auth_precedence() { + let h = headers(&[ + ("x-api-key", "primary-key"), + ("x-goog-api-key", "secondary-key"), + ]); + assert_eq!( + select_client_auth_override(&h), + Some(("x-api-key", "primary-key")) + ); + } + + #[test] + fn client_auth_forwardable_only_for_static_key_and_open_relay() { + assert!(client_auth_forwardable(Some(ClientAuthPath::StaticKey))); + assert!(client_auth_forwardable(Some(ClientAuthPath::OpenRelay))); + assert!(!client_auth_forwardable(Some(ClientAuthPath::VirtualKey))); + assert!(!client_auth_forwardable(Some(ClientAuthPath::OidcJwt))); + assert!(!client_auth_forwardable(None)); + } + + #[test] + fn resolve_client_auth_override_forwards_on_static_key() { + let h = headers(&[("x-api-key", "client-key")]); + assert_eq!( + resolve_client_auth_override(true, Some(ClientAuthPath::StaticKey), &None, &None, &h), + Some(("x-api-key", "client-key")) + ); + } + + #[test] + fn resolve_client_auth_override_refuses_when_vk_ctx_present_even_if_auth_path_says_static_key() + { + // Regression guard for the ClientAuthPath/VirtualKeyContext desync + // risk: even if a future bug leaves auth_path reporting StaticKey + // while a VirtualKeyContext extension is also present, forwarding + // must still be refused. + let h = headers(&[("x-api-key", "client-key")]); + let vk_ctx = Some(crate::server::middleware::VirtualKeyContext { + key_id: 1, + #[cfg(feature = "redis")] + key_hash_hex: String::new(), + rate_state: std::sync::Arc::new(crate::admin::keys::RateLimitState::new()), + allowed_models: None, + allowed_routes: None, + period_reset: None, + }); + assert_eq!( + resolve_client_auth_override(true, Some(ClientAuthPath::StaticKey), &vk_ctx, &None, &h), + None + ); + } + + #[test] + fn resolve_client_auth_override_refuses_when_feature_disabled() { + let h = headers(&[("x-api-key", "client-key")]); + assert_eq!( + resolve_client_auth_override(false, Some(ClientAuthPath::StaticKey), &None, &None, &h), + None + ); + } +} diff --git a/crates/proxy/src/server/passthrough.rs b/crates/proxy/src/server/passthrough/handlers.rs similarity index 76% rename from crates/proxy/src/server/passthrough.rs rename to crates/proxy/src/server/passthrough/handlers.rs index 26157c1..a9f425f 100644 --- a/crates/proxy/src/server/passthrough.rs +++ b/crates/proxy/src/server/passthrough/handlers.rs @@ -1,12 +1,10 @@ -// Anthropic passthrough handler: forwards raw request bytes to the real Anthropic API. -// No translation: the proxy receives Anthropic format and returns Anthropic format. - use crate::backend::{BackendClient, MAX_SSE_BUFFER_SIZE}; use crate::openai_tool_policy::{ backend_kind_for_policy, validate_anthropic_tool_request, OpenAiToolPolicyContext, }; +use crate::server::middleware::ClientAuthPath; use crate::server::routes::{log_request, record_virtual_key_usage, RequestCtx}; -use crate::server::state::ConcurrencyPermit; +use crate::server::state::{AppState, ConcurrencyPermit}; use crate::server::streaming::{observe_anthropic_sse_frames, AnthropicStreamUsage, StreamOutcome}; use anyllm_translate::{anthropic, mapping}; use axum::{ @@ -20,89 +18,14 @@ use futures::StreamExt; use tokio::sync::mpsc; use tokio_stream::wrappers::ReceiverStream; -use super::middleware::ClientAuthPath; -use super::state::AppState; - -/// Selects the exact incoming credential to forward upstream when -/// `ANTHROPIC_FORWARD_CLIENT_AUTH` is enabled. Same precedence as -/// `validate_auth` (`x-api-key` / `x-goog-api-key` win over `authorization`, -/// see `server/middleware/auth.rs`'s `api_key = headers.get("x-api-key") -/// .or_else(|| headers.get("x-goog-api-key"))`) so this always forwards the -/// credential that actually gated the request into the proxy, never a -/// second, unrelated header the client also happened to send. -/// -/// `x-goog-api-key` (Gemini-CLI compatibility) is folded into the -/// `x-api-key` slot rather than forwarded under its own name: Anthropic's API -/// only recognizes `x-api-key`/`authorization`, so a client authenticated via -/// `x-goog-api-key` must still have its value sent upstream as `x-api-key`, -/// not as a header name Anthropic would silently ignore. Beyond that one -/// rename, no shape detection/conversion happens -- forwarded byte-for-byte, -/// unlike LiteLLM's `optionally_handle_anthropic_oauth()`, which mis-converts -/// a Bearer token into `x-api-key`. -fn select_client_auth_override(headers: &axum::http::HeaderMap) -> Option<(&'static str, &str)> { - if let Some(v) = headers - .get("x-api-key") - .or_else(|| headers.get("x-goog-api-key")) - .and_then(|v| v.to_str().ok()) - { - if !v.is_empty() { - return Some(("x-api-key", v)); - } - } - if let Some(v) = headers.get("authorization").and_then(|v| v.to_str().ok()) { - if !v.is_empty() { - return Some(("authorization", v)); - } - } - None -} - -/// Only `StaticKey`/`OpenRelay` mean "the credential that gated this request -/// IS the operator's own secret" (a single-key/BYOK deployment). A virtual -/// key is deliberately not a real Anthropic credential and a JWT is a -/// proxy-auth artifact, so those must never be forwarded upstream regardless -/// of the `ANTHROPIC_FORWARD_CLIENT_AUTH` toggle. -fn client_auth_forwardable(auth_path: Option) -> bool { - matches!( - auth_path, - Some(ClientAuthPath::StaticKey) | Some(ClientAuthPath::OpenRelay) - ) -} - -/// Resolves the client-credential override shared by both passthrough -/// handlers. `vk_ctx`/`claims` are checked directly, not just via -/// `client_auth_forwardable(auth_path)`: `ClientAuthPath` and -/// `VirtualKeyContext`/`JwtClaims` are inserted as two independent -/// `request.extensions_mut().insert()` calls in `validate_auth`, with -/// nothing structurally coupling them, so a future edit to one of those -/// branches could desync them without a compile error. Re-checking presence -/// of the extension that actually gates virtual-key/OIDC requests fails -/// closed instead of silently forwarding a non-operator credential if that -/// ever happens. -fn resolve_client_auth_override<'h>( - forward_client_auth: bool, - auth_path: Option, - vk_ctx: &Option, - claims: &Option, - headers: &'h axum::http::HeaderMap, -) -> Option<(&'static str, &'h str)> { - if forward_client_auth - && client_auth_forwardable(auth_path) - && vk_ctx.is_none() - && claims.is_none() - { - select_client_auth_override(headers) - } else { - None - } -} +use super::auth::resolve_client_auth_override; /// Forward an Anthropic-format request byte-for-byte to the upstream Anthropic API. /// No translation is performed. Only active when `BACKEND=anthropic`. pub(crate) async fn anthropic_passthrough( State(state): State, permit: Option>, - vk_ctx: Option>, + vk_ctx: Option>, auth_path: Option>, claims: Option>, headers: axum::http::HeaderMap, @@ -196,7 +119,7 @@ pub(crate) async fn anthropic_passthrough( if let Some(ref ctx) = vk_ctx { match &peek.model { Some(m) => { - if !super::policy::is_model_allowed(m, &ctx.allowed_models) { + if !crate::server::policy::is_model_allowed(m, &ctx.allowed_models) { let err = mapping::errors_map::create_anthropic_error( anthropic::ErrorType::PermissionError, format!("Model '{}' is not allowed for this API key.", m), @@ -303,7 +226,7 @@ pub(crate) async fn anthropic_passthrough( } } - let mut body = match super::secret_redaction::redact_body_with_content_type( + let mut body = match crate::server::secret_redaction::redact_body_with_content_type( state.redact_secrets(), Some("application/json"), body, @@ -311,7 +234,7 @@ pub(crate) async fn anthropic_passthrough( .await { Ok(body) => body, - Err(err) => return super::secret_redaction::error_response(err), + Err(err) => return crate::server::secret_redaction::error_response(err), }; // FFEC prompt compression runs FIRST: it compresses conversation history text @@ -576,7 +499,7 @@ pub(crate) async fn anthropic_passthrough( #[allow(clippy::too_many_arguments)] pub(crate) async fn anthropic_generic_passthrough( State(state): State, - vk_ctx: Option>, + vk_ctx: Option>, auth_path: Option>, claims: Option>, OriginalUri(uri): OriginalUri, @@ -650,9 +573,11 @@ pub(crate) async fn anthropic_generic_passthrough( } let body = - match super::secret_redaction::redact_body(state.redact_secrets(), &headers, body).await { + match crate::server::secret_redaction::redact_body(state.redact_secrets(), &headers, body) + .await + { Ok(body) => body, - Err(err) => return super::secret_redaction::error_response(err), + Err(err) => return crate::server::secret_redaction::error_response(err), }; match client @@ -730,134 +655,3 @@ fn virtual_key_accounting_parse_error() -> Response { ); (StatusCode::BAD_GATEWAY, Json(err)).into_response() } - -#[cfg(test)] -mod tests { - use super::{ - client_auth_forwardable, resolve_client_auth_override, select_client_auth_override, - ClientAuthPath, - }; - use axum::http::HeaderMap; - - fn headers(pairs: &[(&str, &str)]) -> HeaderMap { - let mut h = HeaderMap::new(); - for (k, v) in pairs { - h.insert( - axum::http::HeaderName::from_bytes(k.as_bytes()).unwrap(), - v.parse().unwrap(), - ); - } - h - } - - #[test] - fn selects_x_api_key_when_only_that_is_sent() { - let h = headers(&[("x-api-key", "client-key")]); - assert_eq!( - select_client_auth_override(&h), - Some(("x-api-key", "client-key")) - ); - } - - #[test] - fn selects_authorization_when_only_that_is_sent() { - let h = headers(&[("authorization", "Bearer sk-ant-oat-abc")]); - assert_eq!( - select_client_auth_override(&h), - Some(("authorization", "Bearer sk-ant-oat-abc")) - ); - } - - #[test] - fn prefers_x_api_key_when_both_sent_matching_validate_auth_precedence() { - let h = headers(&[ - ("x-api-key", "client-key"), - ("authorization", "Bearer sk-ant-oat-abc"), - ]); - assert_eq!( - select_client_auth_override(&h), - Some(("x-api-key", "client-key")) - ); - } - - #[test] - fn returns_none_when_neither_header_sent() { - let h = headers(&[]); - assert_eq!(select_client_auth_override(&h), None); - } - - #[test] - fn selects_x_goog_api_key_forwarded_as_x_api_key() { - // validate_auth (server/middleware/auth.rs) treats x-goog-api-key as - // fully equivalent to x-api-key for authentication, but Anthropic's - // API only understands x-api-key -- the value must be forwarded - // under the x-api-key name, not the literal x-goog-api-key name. - let h = headers(&[("x-goog-api-key", "gemini-cli-key")]); - assert_eq!( - select_client_auth_override(&h), - Some(("x-api-key", "gemini-cli-key")) - ); - } - - #[test] - fn prefers_x_api_key_over_x_goog_api_key_matching_validate_auth_precedence() { - let h = headers(&[ - ("x-api-key", "primary-key"), - ("x-goog-api-key", "secondary-key"), - ]); - assert_eq!( - select_client_auth_override(&h), - Some(("x-api-key", "primary-key")) - ); - } - - #[test] - fn client_auth_forwardable_only_for_static_key_and_open_relay() { - assert!(client_auth_forwardable(Some(ClientAuthPath::StaticKey))); - assert!(client_auth_forwardable(Some(ClientAuthPath::OpenRelay))); - assert!(!client_auth_forwardable(Some(ClientAuthPath::VirtualKey))); - assert!(!client_auth_forwardable(Some(ClientAuthPath::OidcJwt))); - assert!(!client_auth_forwardable(None)); - } - - #[test] - fn resolve_client_auth_override_forwards_on_static_key() { - let h = headers(&[("x-api-key", "client-key")]); - assert_eq!( - resolve_client_auth_override(true, Some(ClientAuthPath::StaticKey), &None, &None, &h), - Some(("x-api-key", "client-key")) - ); - } - - #[test] - fn resolve_client_auth_override_refuses_when_vk_ctx_present_even_if_auth_path_says_static_key() - { - // Regression guard for the ClientAuthPath/VirtualKeyContext desync - // risk: even if a future bug leaves auth_path reporting StaticKey - // while a VirtualKeyContext extension is also present, forwarding - // must still be refused. - let h = headers(&[("x-api-key", "client-key")]); - let vk_ctx = Some(crate::server::middleware::VirtualKeyContext { - key_id: 1, - #[cfg(feature = "redis")] - key_hash_hex: String::new(), - rate_state: std::sync::Arc::new(crate::admin::keys::RateLimitState::new()), - allowed_models: None, - allowed_routes: None, - period_reset: None, - }); - assert_eq!( - resolve_client_auth_override(true, Some(ClientAuthPath::StaticKey), &vk_ctx, &None, &h), - None - ); - } - - #[test] - fn resolve_client_auth_override_refuses_when_feature_disabled() { - let h = headers(&[("x-api-key", "client-key")]); - assert_eq!( - resolve_client_auth_override(false, Some(ClientAuthPath::StaticKey), &None, &None, &h), - None - ); - } -} diff --git a/crates/proxy/src/server/passthrough/mod.rs b/crates/proxy/src/server/passthrough/mod.rs new file mode 100644 index 0000000..fd107b4 --- /dev/null +++ b/crates/proxy/src/server/passthrough/mod.rs @@ -0,0 +1,7 @@ +// Anthropic passthrough handlers module: forwards raw request bytes to the real Anthropic API. +// No translation: the proxy receives Anthropic format and returns Anthropic format. + +pub(crate) mod auth; +pub(crate) mod handlers; + +pub(crate) use handlers::{anthropic_generic_passthrough, anthropic_passthrough}; diff --git a/crates/proxy/src/server/state.rs b/crates/proxy/src/server/state.rs deleted file mode 100644 index 6759459..0000000 --- a/crates/proxy/src/server/state.rs +++ /dev/null @@ -1,977 +0,0 @@ -// Shared state types for request handlers: AppState, AnthropicJson, ResolvedModel, etc. -// Extracted from routes.rs so consumers can import state independently of the router setup. - -use crate::admin::state::{RuntimeConfig, SharedState}; -use crate::backend::BackendClient; -use crate::metrics::Metrics; -use anyllm_providers::ProviderCatalog; -use anyllm_translate::{anthropic, mapping, openai}; -use axum::{ - extract::{rejection::JsonRejection, FromRequest}, - http::StatusCode, - response::{IntoResponse, Json, Response}, -}; -use std::collections::HashMap; -use std::sync::{Arc, RwLock}; -use tokio::sync::Semaphore; - -/// Custom JSON extractor that returns Anthropic-shaped error responses on -/// parse failure. Axum's built-in Json returns its own error format, which -/// would break clients expecting Anthropic error shapes. -pub(crate) struct AnthropicJson(pub T); - -impl FromRequest for AnthropicJson -where - Json: FromRequest, - S: Send + Sync, -{ - type Rejection = Response; - - async fn from_request(req: axum::extract::Request, state: &S) -> Result { - match Json::::from_request(req, state).await { - Ok(Json(value)) => Ok(AnthropicJson(value)), - Err(rejection) => { - let err = mapping::errors_map::create_anthropic_error( - anthropic::ErrorType::InvalidRequestError, - rejection.body_text(), - None, - ); - Err((StatusCode::BAD_REQUEST, Json(err)).into_response()) - } - } - } -} - -/// Result of resolving a model name through the model router. -pub(crate) enum ResolvedModel { - /// Routed via model_list to a specific backend and actual model name. - Routed { - backend_name: String, - model: String, - /// The deployment Arc for recording in-flight/latency stats. - deployment: Arc, - /// Per-route option overrides when routed via a DB route; `None` when - /// routed via the LiteLLM model_router (inherit global config). - options: Option>, - }, - /// Model is known but all deployments are at their RPM limit. - AllAtLimit, - /// Model router is active but the model alias is not configured. - UnknownModel, - /// No model router, or model not in router. Used legacy ModelMapping. - Legacy(String), -} - -/// Shared state for tool execution, stored in AppState. -#[derive(Clone)] -pub struct ToolEngineState { - pub registry: Arc, - pub policy: Arc, - pub loop_config: crate::tools::LoopConfig, - pub guardrails: crate::tools::ToolGuardrailConfig, - pub mcp_manager: Option>, -} - -/// Per-backend state shared across request handlers. -/// -/// In single-backend mode, one `AppState` serves all routes. In multi-backend mode, -/// each backend gets its own `AppState` mounted under a prefix path (e.g., `/openai/v1/messages`). -#[derive(Clone)] -pub struct AppState { - pub backend: BackendClient, - pub metrics: Metrics, - /// Runtime config (model mappings, body logging, redaction) read on every request. - /// Shared with admin server so config changes take effect immediately. - pub runtime_config: Arc>, - /// Shared admin state for request logging and live updates. None in tests. - pub shared: Option, - /// Per-route option overrides for the request that produced this (cloned) - /// state, set by `resolve_model_and_state` when a DB route was selected. - /// `None` means "no route override; use the global RuntimeConfig value". - /// Read by the option accessors (`redact_secrets`, `effective_tool_guardrails`, - /// `active_pxpipe`, `pxpipe_models`). - pub route_options: Option>, - /// Backend name for logging purposes. - pub backend_name: String, - /// Canonical provider id used for provider/model policy decisions. - pub provider_id: Option, - /// Concurrency limiter. Uses try_acquire (fail-fast) instead of queueing - /// to prevent cascading latency under load. Requests exceeding the limit - /// get 429 immediately, matching Anthropic's rate limiting behavior. - pub concurrency: Arc, - /// Strip `stream_options` from streaming requests for local LLM compat. - pub omit_stream_options: bool, - /// Wall-clock cap for streaming responses in seconds. 0 = disabled. - /// Prevents resource exhaustion from stalled backends. - pub stream_timeout_secs: u64, - /// When true, set `x-anyllm-degradation` header on responses that silently drop features. - /// Mirrors Config::expose_degradation_warnings / MultiConfig::expose_degradation_warnings. - pub expose_degradation_warnings: bool, - /// Optional response cache for non-streaming requests. - pub cache: Option>, - /// Anthropic thinking-block record-and-restore repair store. `None` - /// unless `backend` is `BackendClient::Anthropic`; only consulted by - /// `anthropic_passthrough`. Always `Some` for Anthropic backends - /// regardless of whether the feature is enabled -- use - /// `thinking_repair_enabled()` to check the live toggle before using it. - pub thinking_repair: Option>, - /// Text-to-image context compression engine (pxpipe). `None` unless - /// `backend` is `BackendClient::Anthropic`; only consulted by - /// `anthropic_passthrough`. Always `Some` for Anthropic backends regardless - /// of the live toggle -- use `active_pxpipe()`, which checks - /// `RuntimeConfig.pxpipe_compress`, before using it. - pub pxpipe: Option>, - /// Command-aware tool-output compression engine (RTK). `Some` for Anthropic - /// and Translate modes; only consulted when `RuntimeConfig.rtk_compress` is - /// on -- use `rtk_engine_for(model)`. - pub rtk: Option>, - /// FFEC prompt-compression engine (`OptimizerEngine`). `Some` for Anthropic - /// and Translate modes, mirroring `rtk`; baked with the static - /// `OPTIMIZER_MODE`-env default at startup -- use `effective_optimizer()`, - /// which applies the live `RouteOptions.optimizer_mode` override on top. - pub optimizer: Option>, - /// Model-level router for LiteLLM model_list configs. None for TOML/env configs. - /// Wrapped in RwLock for dynamic model management via admin API. - pub model_router: Option>>, - /// Immutable provider/model catalog used for runtime model metadata. - pub provider_catalog: Arc, - /// All backend states, for cross-backend model routing. None unless model_router is set. - pub all_backends: Option>>, - /// Tool execution engine state. None when tool execution is not configured. - pub tool_engine: Option>, - /// Batch orchestration engine. None in test configs that don't need batch. - pub batch_engine: Option< - Arc< - anyllm_batch_engine::BatchEngine< - anyllm_batch_engine::queue::sqlite::SqliteQueue, - anyllm_batch_engine::webhook::sqlite::SqliteWebhookQueue, - >, - >, - >, -} - -impl AppState { - /// Map a model name through the current runtime config for this backend. - pub(crate) fn map_model(&self, model: &str) -> String { - let config = self - .runtime_config - .read() - .unwrap_or_else(|e| e.into_inner()); - if let Some(mapping) = config.model_mappings.get(&self.backend_name) { - mapping.map_model(model) - } else { - model.to_string() - } - } - - /// Resolve a model name to a backend. - /// - /// Precedence: (1) admin-DB routes (`RouteRouter`), (2) LiteLLM model_router, - /// (3) legacy ModelMapping. An empty route router falls straight through so - /// installs without routes behave exactly as before. - pub(crate) fn resolve_model(&self, model: &str) -> ResolvedModel { - if let Some(shared) = self.shared.as_ref() { - if let Some(ref rr_lock) = shared.route_router { - use crate::config::route_router::RouteResolution; - let rr = rr_lock.read().unwrap_or_else(|e| e.into_inner()); - if !rr.is_empty() { - match rr.resolve(model) { - RouteResolution::Routed(res) => { - return ResolvedModel::Routed { - backend_name: res.backend_name, - model: res.model, - deployment: res.deployment, - options: Some(res.options), - }; - } - RouteResolution::AllAtLimit => return ResolvedModel::AllAtLimit, - // No route serves this model: fall through to the layers below. - RouteResolution::NoRoute => {} - } - } - } - } - if let Some(ref router_lock) = self.model_router { - let router = router_lock.read().unwrap_or_else(|e| e.into_inner()); - if let Some(routed) = router.route(model) { - return ResolvedModel::Routed { - backend_name: routed.backend_name.to_string(), - model: routed.actual_model.to_string(), - deployment: routed.deployment.clone(), - options: None, - }; - } - if router.has_model(model) { - return ResolvedModel::AllAtLimit; - } - return ResolvedModel::UnknownModel; - } - ResolvedModel::Legacy(self.map_model(model)) - } - - /// Resolve model and return (mapped_model, effective AppState, optional deployment). - /// If the model routes to a different backend, the returned state is cloned from - /// all_backends. Returns Err with a 429 response if all deployments are at limit. - /// The deployment Arc is returned so handlers can call record_start/record_finish. - #[allow(clippy::result_large_err)] - pub(crate) fn resolve_model_and_state( - &self, - model: &str, - ) -> Result< - ( - String, - AppState, - Option>, - ), - Response, - > { - match self.resolve_model(model) { - ResolvedModel::Routed { - backend_name, - model: mapped, - deployment, - options, - } => { - let mut effective = self - .all_backends - .as_ref() - .and_then(|m| m.get(&backend_name)) - .cloned() - .or_else(|| { - // Check managed backends (SQLite-backed, zero-restart) - self.shared.as_ref().and_then(|s| { - let guard = s.managed_backends - .read() - .ok() - .or_else(|| { - tracing::warn!("managed_backends RwLock is poisoned; skipping managed backend lookup"); - None - })?; - guard.get(&backend_name).map(|(row, client)| { - let mut state = self.clone(); - state.backend = client.clone(); - state.backend_name = backend_name.clone(); - state.provider_id = Some(row.provider_id.clone()); - state - }) - }) - }) - .unwrap_or_else(|| self.clone()); - // Carry the per-route option overrides onto the effective state so - // the option accessors resolve route-first, global-fallback. - effective.route_options = options; - Ok((mapped, effective, Some(deployment))) - } - ResolvedModel::AllAtLimit => { - let err = mapping::errors_map::create_anthropic_error( - anthropic::ErrorType::RateLimitError, - "all deployments for this model are at their RPM limit".to_string(), - None, - ); - Err((StatusCode::TOO_MANY_REQUESTS, Json(err)).into_response()) - } - ResolvedModel::UnknownModel => { - let err = mapping::errors_map::create_anthropic_error( - anthropic::ErrorType::InvalidRequestError, - format!("model '{model}' is not configured in model_list"), - None, - ); - Err((StatusCode::BAD_REQUEST, Json(err)).into_response()) - } - ResolvedModel::Legacy(mapped) => Ok((mapped, self.clone(), None)), - } - } - - /// Whether request/response body logging is enabled. - pub(crate) fn log_bodies(&self) -> bool { - self.runtime_config - .read() - .unwrap_or_else(|e| e.into_inner()) - .log_bodies - } - - /// Whether upstream JSON/text request payloads should be redacted. - /// Route override (if set) wins over the global RuntimeConfig value. - pub(crate) fn redact_secrets(&self) -> bool { - if let Some(v) = self.route_options.as_ref().and_then(|o| o.redact_secrets) { - return v; - } - self.runtime_config - .read() - .unwrap_or_else(|e| e.into_inner()) - .redact_secrets - } - - /// Effective tool-call guardrail config for this request: the runtime, - /// admin-tunable override (`RuntimeConfig.tool_guardrail_mode`, no - /// restart required) applied on top of `engine.guardrails` (the static - /// preset built from YAML/env at startup). See - /// `crate::tools::resolve_runtime_guardrails`. - pub(crate) fn effective_tool_guardrails( - &self, - engine: &ToolEngineState, - ) -> crate::tools::ToolGuardrailConfig { - // Route override (if set) wins over the live global RuntimeConfig mode. - if let Some(mode) = self - .route_options - .as_ref() - .and_then(|o| o.guardrail_mode.as_deref()) - { - return crate::tools::resolve_runtime_guardrails(&engine.guardrails, mode); - } - crate::tools::resolve_runtime_guardrails_locked(&self.runtime_config, &engine.guardrails) - } - - /// Whether Anthropic thinking-block repair (record + restore) is active. - /// `self.thinking_repair` may be `Some` even when this is `false` -- the - /// store is always constructed for Anthropic backends; only this flag - /// gates whether it's actually used. - pub(crate) fn thinking_repair_enabled(&self) -> bool { - self.runtime_config - .read() - .unwrap_or_else(|e| e.into_inner()) - .anthropic_thinking_repair - } - - /// Whether Anthropic passthrough forwards the client's own incoming - /// credential upstream instead of the operator's (`ANTHROPIC_FORWARD_CLIENT_AUTH`, - /// live-toggleable via `RuntimeConfig.forward_client_auth`). Read fresh on - /// every request -- unlike the old frozen `AppState` field this replaced, - /// this reflects an admin-UI change immediately without a restart, and - /// applies uniformly to every `BackendKind::Anthropic` backend since they - /// all share one `RuntimeConfig`. - pub(crate) fn forward_client_auth_enabled(&self) -> bool { - self.runtime_config - .read() - .unwrap_or_else(|e| e.into_inner()) - .forward_client_auth - } - - /// The thinking-repair store, but only when the live admin-toggleable - /// flag is actually on. `None` both when repair is entirely absent (non- - /// Anthropic backend) and when it's present-but-disabled -- single - /// accessor so call sites collapse to `if let Some(store) = ...` instead - /// of separately checking `thinking_repair_enabled()` and - /// `thinking_repair.is_some()`. - pub(crate) fn active_thinking_repair( - &self, - ) -> Option> { - if self.thinking_repair_enabled() { - self.thinking_repair.clone() - } else { - None - } - } - - /// The pxpipe compression engine, but only when the live admin-toggleable - /// flag (`RuntimeConfig.pxpipe_compress`) is on. `None` both when the engine - /// is absent (non-Anthropic backend) and when present-but-disabled. - pub(crate) fn active_pxpipe(&self) -> Option> { - let enabled = match self.route_options.as_ref().and_then(|o| o.pxpipe_compress) { - Some(v) => v, - None => { - self.runtime_config - .read() - .unwrap_or_else(|e| e.into_inner()) - .pxpipe_compress - } - }; - if enabled { - self.pxpipe.clone() - } else { - None - } - } - - /// Live model-scope CSV for pxpipe. Route override wins over the global - /// `RuntimeConfig.pxpipe_models` value. - pub(crate) fn pxpipe_models(&self) -> String { - if let Some(csv) = self - .route_options - .as_ref() - .and_then(|o| o.pxpipe_models.clone()) - { - return csv; - } - self.runtime_config - .read() - .unwrap_or_else(|e| e.into_inner()) - .pxpipe_models - .clone() - } - - /// Vision gate: if the catalog knows this model and says it is NOT - /// vision-capable, refuse (fail-closed). Unknown models fall back to the - /// scope list only — a Claude passthrough model is vision-capable in - /// practice, and the scope list is the operator's explicit control. - fn pxpipe_vision_ok(&self, model: &str) -> bool { - match self - .provider_id - .as_deref() - .and_then(|pid| self.provider_catalog.get_model(pid, model)) - { - Some(def) => def.capabilities.vision, - None => true, - } - } - - /// The pxpipe engine for `model`, or `None` if compression shouldn't run: - /// the master toggle is off, the engine is absent (non-Anthropic backend), - /// the model is out of the live scope CSV, or it isn't vision-capable. - /// Single accessor so `passthrough` collapses to - /// `if let Some(engine) = state.pxpipe_engine_for(model)`. - pub(crate) fn pxpipe_engine_for( - &self, - model: &str, - ) -> Option> { - let engine = self.active_pxpipe()?; - if crate::pxpipe::model_in_scope(model, &self.pxpipe_models()) - && self.pxpipe_vision_ok(model) - { - Some(engine) - } else { - None - } - } - - /// The RTK engine for `model`, or `None` if compression shouldn't run: the - /// toggle is off, the engine is absent, or the model is out of scope. RTK is - /// not vision-gated, so there is no capability check. - /// - /// Reads the toggle and scope from a single RwLock critical section for - /// consistency, and checks `route_options` first (matching the pxpipe pattern) - /// so per-route overrides take precedence over the global RuntimeConfig. - pub(crate) fn rtk_engine_for(&self, model: &str) -> Option> { - let cfg = self - .runtime_config - .read() - .unwrap_or_else(|e| e.into_inner()); - let enabled = self - .route_options - .as_ref() - .and_then(|o| o.rtk_compress) - .unwrap_or(cfg.rtk_compress); - if !enabled { - return None; - } - let engine = self.rtk.clone()?; - let models_csv = self - .route_options - .as_ref() - .and_then(|o| o.rtk_models.as_deref()) - .unwrap_or(&cfg.rtk_models); - if crate::rtk::model_in_scope(model, models_csv) { - Some(engine) - } else { - None - } - } - - /// Effective FFEC prompt-compression engine for this request, or `None` - /// when optimization is unconfigured for this backend/mode (`self.optimizer` - /// is `None`). Precedence, mirroring `effective_tool_guardrails` / - /// `resolve_runtime_guardrails_locked`: (1) route override - /// (`RouteOptions.optimizer_mode`, if set) wins outright; (2) otherwise the - /// live `RuntimeConfig.optimizer_mode` admin toggle (no restart required); - /// (3) otherwise the static per-process engine baked with the - /// `OPTIMIZER_MODE`-env default at startup. - pub(crate) fn effective_optimizer(&self) -> Option> { - let engine = self.optimizer.as_ref()?; - if let Some(mode_str) = self - .route_options - .as_ref() - .and_then(|o| o.optimizer_mode.as_deref()) - { - return Some(Arc::new(engine.with_mode_override(mode_str))); - } - Some(Arc::new( - crate::optimizer::resolve_runtime_optimizer_locked(&self.runtime_config, engine), - )) - } - - /// Apply RTK tool-output compression to an OpenAI-format request and record - /// metrics. Shared helper used by both the /v1/chat/completions and /v1/messages - /// translate paths (streaming and non-streaming). No-op when the engine is - /// unavailable, disabled, or no tool messages are present. - pub(crate) fn apply_rtk_to_openai(&self, req: &mut openai::ChatCompletionRequest, model: &str) { - let engine = match self.rtk_engine_for(model) { - Some(e) => e, - None => return, - }; - // Pre-check: only serialize when there are tool messages to compress. - if !req - .messages - .iter() - .any(|m| m.role == openai::ChatRole::Tool) - { - return; - } - let mut v = match serde_json::to_value(&*req) { - Ok(v) => v, - Err(_) => return, - }; - let Some((blocks, saved)) = engine.compress_openai_chat(&mut v) else { - return; - }; - match serde_json::from_value::(v) { - Ok(patched) => { - *req = patched; - self.metrics.record_rtk_compression(blocks, saved); - tracing::info!( - model, - blocks, - chars_saved = saved, - "rtk: compressed OpenAI request" - ); - } - Err(e) => tracing::warn!( - error = %e, - "rtk: failed to re-deserialize compressed OpenAI request; forwarding original" - ), - } - } - - /// Apply FFEC prompt compression (`effective_optimizer()`) to an OpenAI-format - /// request at the parsed-body seam. Client-sent history only -- callers must - /// never invoke this on proxy-appended tool-loop turns (see - /// `crates/optimizer/CLAUDE.md` "Streaming & tool-loop decision"). `Shadow` - /// mode logs the `OptimizationReport` and leaves `req` unchanged; `Live` mode - /// applies the rendered body in place. No-op when optimization is - /// unconfigured or resolves to `Mode::Off` for this request. - pub(crate) fn apply_optimizer_to_openai( - &self, - req: &mut openai::ChatCompletionRequest, - route: &str, - ) { - let Some(engine) = self.effective_optimizer() else { - return; - }; - let mut v = match serde_json::to_value(&*req) { - Ok(v) => v, - Err(_) => return, - }; - let report = engine.optimize_openai(&mut v, route); - if report.mode == anyllm_optimize_core::Mode::Shadow { - tracing::info!( - route, - removed_tokens_est = report.removed_tokens_est, - messages_compressed = report.messages_compressed, - failure = report.failure.as_deref().unwrap_or(""), - "optimizer: shadow report (not applied)" - ); - } - if !report.applied { - return; - } - match serde_json::from_value::(v) { - Ok(patched) => { - *req = patched; - self.metrics.record_optimization( - report.messages_compressed as u64, - report.removed_tokens_est, - ); - tracing::info!( - route, - removed_tokens_est = report.removed_tokens_est, - messages_compressed = report.messages_compressed, - "optimizer: compressed OpenAI request" - ); - } - Err(e) => tracing::warn!( - error = %e, - "optimizer: failed to re-deserialize compressed OpenAI request; forwarding original" - ), - } - } - - /// Apply FFEC prompt compression (`effective_optimizer()`) to an Anthropic - /// Messages request at the parsed-body seam. Same contract as - /// [`Self::apply_optimizer_to_openai`]: client-sent history only, fails open, - /// `Shadow` never mutates `req`. - pub(crate) fn apply_optimizer_to_anthropic( - &self, - req: &mut anthropic::MessageCreateRequest, - route: &str, - ) { - let Some(engine) = self.effective_optimizer() else { - return; - }; - let mut v = match serde_json::to_value(&*req) { - Ok(v) => v, - Err(_) => return, - }; - let report = engine.optimize_anthropic(&mut v, route); - if report.mode == anyllm_optimize_core::Mode::Shadow { - tracing::info!( - route, - removed_tokens_est = report.removed_tokens_est, - messages_compressed = report.messages_compressed, - failure = report.failure.as_deref().unwrap_or(""), - "optimizer: shadow report (not applied)" - ); - } - if !report.applied { - return; - } - match serde_json::from_value::(v) { - Ok(patched) => { - *req = patched; - self.metrics.record_optimization( - report.messages_compressed as u64, - report.removed_tokens_est, - ); - tracing::info!( - route, - removed_tokens_est = report.removed_tokens_est, - messages_compressed = report.messages_compressed, - "optimizer: compressed Anthropic request" - ); - } - Err(e) => tracing::warn!( - error = %e, - "optimizer: failed to re-deserialize compressed Anthropic request; forwarding original" - ), - } - } -} - -#[cfg(test)] -mod optimizer_seam_tests { - use super::*; - use crate::config::{ - BackendAuth, BackendKind, Config, ModelMapping, OpenAIApiFormat, TlsConfig, - }; - use anyllm_optimize_core::Mode; - - /// Long enough that FFEC's min-length gate actually has something to compress. - fn long_text() -> String { - "The quick brown fox jumps over the lazy dog again and again across the wide \ - green field toward the distant blue mountains far beyond the winding river." - .repeat(4) - } - - fn minimal_state(optimizer_mode: Mode) -> AppState { - let config = Config { - backend: BackendKind::OpenAI, - openai_api_key: "test".into(), - openai_base_url: "https://api.openai.com".into(), - listen_port: 3000, - model_mapping: ModelMapping { - big_model: "gpt-4o".into(), - small_model: "gpt-4o-mini".into(), - }, - tls: TlsConfig::default(), - backend_auth: BackendAuth::BearerToken("test".into()), - log_bodies: false, - redact_secrets: false, - anthropic_thinking_repair: false, - pxpipe_compress: false, - expose_degradation_warnings: false, - openai_api_format: OpenAIApiFormat::Chat, - provider_id: None, - }; - let backend = crate::backend::BackendClient::OpenAI( - crate::backend::openai_client::OpenAIClient::new(&config), - ); - let runtime_config = Arc::new(RwLock::new(RuntimeConfig { - model_mappings: indexmap::IndexMap::new(), - log_level: "info".to_string(), - log_bodies: false, - redact_secrets: false, - anthropic_thinking_repair: false, - pxpipe_compress: false, - pxpipe_models: String::new(), - rtk_compress: false, - rtk_models: String::new(), - forward_client_auth: false, - tool_guardrail_mode: "disabled".to_string(), - optimizer_mode: optimizer_mode.as_str().to_string(), - })); - AppState { - backend, - metrics: Metrics::new(), - runtime_config, - shared: None, - route_options: None, - backend_name: "openai".to_string(), - provider_id: None, - concurrency: Arc::new(Semaphore::new(64)), - omit_stream_options: false, - stream_timeout_secs: 0, - expose_degradation_warnings: false, - cache: None, - thinking_repair: None, - pxpipe: None, - rtk: None, - optimizer: Some(Arc::new(crate::optimizer::OptimizerEngine::new( - optimizer_mode, - ))), - model_router: None, - provider_catalog: Arc::new(ProviderCatalog::bundled()), - all_backends: None, - tool_engine: None, - batch_engine: None, - } - } - - fn long_openai_request() -> openai::ChatCompletionRequest { - let long = long_text(); - let mut body = serde_json::json!({ - "model": "gpt-4o", - "messages": [{"role": "system", "content": "you are helpful"}], - }); - let msgs = body["messages"].as_array_mut().unwrap(); - for _ in 0..16 { - msgs.push(serde_json::json!({"role": "user", "content": long})); - msgs.push(serde_json::json!({"role": "assistant", "content": long})); - } - msgs.push(serde_json::json!({"role": "user", "content": "what is the latest?"})); - serde_json::from_value(body).expect("valid ChatCompletionRequest") - } - - fn long_anthropic_request() -> anthropic::MessageCreateRequest { - let long = long_text(); - let mut body = serde_json::json!({ - "model": "claude-sonnet-5", - "max_tokens": 1024, - "messages": [], - }); - let msgs = body["messages"].as_array_mut().unwrap(); - for _ in 0..16 { - msgs.push(serde_json::json!({"role": "user", "content": long})); - msgs.push(serde_json::json!({"role": "assistant", "content": long})); - } - msgs.push(serde_json::json!({"role": "user", "content": "what is the latest?"})); - serde_json::from_value(body).expect("valid MessageCreateRequest") - } - - #[test] - fn shadow_mode_forwards_openai_body_unchanged() { - let state = minimal_state(Mode::Shadow); - let mut req = long_openai_request(); - let before = serde_json::to_value(&req).unwrap(); - state.apply_optimizer_to_openai(&mut req, "chat_completions"); - let after = serde_json::to_value(&req).unwrap(); - assert_eq!(before, after, "shadow mode must forward the original body"); - assert_eq!( - state.metrics.snapshot().optimizer_compressed_total, - 0, - "shadow mode must never record a metrics-visible compression" - ); - } - - #[test] - fn shadow_mode_forwards_anthropic_body_unchanged() { - let state = minimal_state(Mode::Shadow); - let mut req = long_anthropic_request(); - let before = serde_json::to_value(&req).unwrap(); - state.apply_optimizer_to_anthropic(&mut req, "messages"); - let after = serde_json::to_value(&req).unwrap(); - assert_eq!(before, after, "shadow mode must forward the original body"); - } - - #[test] - fn live_mode_compresses_openai_history_and_preserves_latest() { - let state = minimal_state(Mode::Live); - let mut req = long_openai_request(); - let before = serde_json::to_value(&req).unwrap(); - state.apply_optimizer_to_openai(&mut req, "chat_completions"); - let after = serde_json::to_value(&req).unwrap(); - assert_eq!( - before["messages"].as_array().unwrap().last(), - after["messages"].as_array().unwrap().last(), - "the latest turn must never be rewritten" - ); - assert_eq!( - state.metrics.snapshot().optimizer_compressed_total, - 1, - "an applied Live compression must be recorded in metrics" - ); - } - - #[test] - fn live_mode_compresses_anthropic_history_and_preserves_latest() { - let state = minimal_state(Mode::Live); - let mut req = long_anthropic_request(); - let before = serde_json::to_value(&req).unwrap(); - state.apply_optimizer_to_anthropic(&mut req, "messages"); - let after = serde_json::to_value(&req).unwrap(); - assert_eq!( - before["messages"].as_array().unwrap().last(), - after["messages"].as_array().unwrap().last(), - "the latest turn must never be rewritten" - ); - } - - #[test] - fn off_mode_is_noop_and_engine_absent_is_noop() { - // Off mode: engine present, mode Off -> never applied. - let state = minimal_state(Mode::Off); - let mut req = long_openai_request(); - let before = serde_json::to_value(&req).unwrap(); - state.apply_optimizer_to_openai(&mut req, "chat_completions"); - assert_eq!(before, serde_json::to_value(&req).unwrap()); - - // No engine at all (e.g. non-Anthropic/Translate mode backend): no panic, no-op. - let mut state_no_engine = minimal_state(Mode::Live); - state_no_engine.optimizer = None; - let mut req2 = long_openai_request(); - let before2 = serde_json::to_value(&req2).unwrap(); - state_no_engine.apply_optimizer_to_openai(&mut req2, "chat_completions"); - assert_eq!(before2, serde_json::to_value(&req2).unwrap()); - } - - #[test] - fn short_history_below_min_len_gate_is_a_noop_not_a_panic() { - // A short request has nothing worth compressing (below FFEC's min-length - // gate) -- the seam must still round-trip cleanly without panicking or - // corrupting the body, i.e. it fails open when there's nothing to do. - let state = minimal_state(Mode::Live); - let mut req: openai::ChatCompletionRequest = serde_json::from_value(serde_json::json!({ - "model": "gpt-4o", - "messages": [{"role": "user", "content": "hi"}], - })) - .unwrap(); - let before = serde_json::to_value(&req).unwrap(); - state.apply_optimizer_to_openai(&mut req, "chat_completions"); - assert_eq!(before, serde_json::to_value(&req).unwrap()); - } -} - -#[cfg(test)] -mod rtk_seam_tests { - use super::*; - - /// Build a minimal `AppState` with the RTK engine present and the runtime - /// `rtk_compress` toggle set to `enabled` (scope left empty = all models). - fn state_with_rtk(enabled: bool) -> AppState { - use crate::config::{ - BackendAuth, BackendKind, Config, ModelMapping, OpenAIApiFormat, TlsConfig, - }; - let config = Config { - backend: BackendKind::OpenAI, - openai_api_key: "test".into(), - openai_base_url: "https://api.openai.com".into(), - listen_port: 3000, - model_mapping: ModelMapping { - big_model: "gpt-4o".into(), - small_model: "gpt-4o-mini".into(), - }, - tls: TlsConfig::default(), - backend_auth: BackendAuth::BearerToken("test".into()), - log_bodies: false, - redact_secrets: false, - anthropic_thinking_repair: false, - pxpipe_compress: false, - expose_degradation_warnings: false, - openai_api_format: OpenAIApiFormat::Chat, - provider_id: None, - }; - let backend = crate::backend::BackendClient::OpenAI( - crate::backend::openai_client::OpenAIClient::new(&config), - ); - let runtime_config = Arc::new(RwLock::new(RuntimeConfig { - model_mappings: indexmap::IndexMap::new(), - log_level: "info".to_string(), - log_bodies: false, - redact_secrets: false, - anthropic_thinking_repair: false, - pxpipe_compress: false, - pxpipe_models: String::new(), - rtk_compress: enabled, - rtk_models: String::new(), - forward_client_auth: false, - tool_guardrail_mode: "disabled".to_string(), - optimizer_mode: "off".to_string(), - })); - AppState { - backend, - metrics: Metrics::new(), - runtime_config, - shared: None, - route_options: None, - backend_name: "openai".to_string(), - provider_id: None, - concurrency: Arc::new(Semaphore::new(64)), - omit_stream_options: false, - stream_timeout_secs: 0, - expose_degradation_warnings: false, - cache: None, - thinking_repair: None, - pxpipe: None, - rtk: Some(Arc::new(crate::rtk::RtkEngine::new())), - optimizer: None, - model_router: None, - provider_catalog: Arc::new(ProviderCatalog::bundled()), - all_backends: None, - tool_engine: None, - batch_engine: None, - } - } - - /// An OpenAI request whose `role: tool` message carries compressible git noise. - fn request_with_noisy_tool_output() -> openai::ChatCompletionRequest { - let mut noise = String::from("On branch main\nChanges not staged for commit:\n"); - for i in 0..200 { - noise.push_str(&format!(" (use \"git add ...\" file {i})\n")); - } - serde_json::from_value(serde_json::json!({ - "model": "gpt-4o", - "messages": [ - {"role": "user", "content": "run git status"}, - {"role": "assistant", "content": null, "tool_calls": [ - {"id": "t1", "type": "function", - "function": {"name": "bash", "arguments": "{\"cmd\":\"git status\"}"}} - ]}, - {"role": "tool", "tool_call_id": "t1", "content": noise}, - ], - })) - .expect("valid ChatCompletionRequest") - } - - #[test] - fn enabled_compresses_tool_output_and_records_metrics() { - let state = state_with_rtk(true); - let mut req = request_with_noisy_tool_output(); - let before = serde_json::to_value(&req).unwrap(); - state.apply_rtk_to_openai(&mut req, "gpt-4o"); - let after = serde_json::to_value(&req).unwrap(); - assert_ne!(before, after, "tool output should have been compressed"); - assert_eq!(state.metrics.snapshot().rtk_compressed_total, 1); - } - - #[test] - fn disabled_is_a_noop() { - let state = state_with_rtk(false); - let mut req = request_with_noisy_tool_output(); - let before = serde_json::to_value(&req).unwrap(); - state.apply_rtk_to_openai(&mut req, "gpt-4o"); - assert_eq!(before, serde_json::to_value(&req).unwrap()); - assert_eq!(state.metrics.snapshot().rtk_compressed_total, 0); - } - - #[test] - fn engine_absent_is_a_noop() { - let mut state = state_with_rtk(true); - state.rtk = None; - let mut req = request_with_noisy_tool_output(); - let before = serde_json::to_value(&req).unwrap(); - state.apply_rtk_to_openai(&mut req, "gpt-4o"); - assert_eq!(before, serde_json::to_value(&req).unwrap()); - assert_eq!(state.metrics.snapshot().rtk_compressed_total, 0); - } -} - -/// Global state for the multi-backend metrics endpoint. -#[derive(Clone)] -pub(crate) struct GlobalState { - pub(crate) backend_metrics: Arc>, -} - -/// Wrapper so OwnedSemaphorePermit can be stored in request extensions. -/// The field is never read directly; it exists as an RAII guard to hold -/// the permit until the struct is dropped. -#[derive(Clone)] -pub(crate) struct ConcurrencyPermit( - #[allow(dead_code)] pub(crate) Arc, -); diff --git a/crates/proxy/src/server/state/anthropic_json.rs b/crates/proxy/src/server/state/anthropic_json.rs new file mode 100644 index 0000000..492b613 --- /dev/null +++ b/crates/proxy/src/server/state/anthropic_json.rs @@ -0,0 +1,33 @@ +use anyllm_translate::{anthropic, mapping}; +use axum::{ + extract::{rejection::JsonRejection, FromRequest}, + http::StatusCode, + response::{IntoResponse, Json, Response}, +}; + +/// Custom JSON extractor that returns Anthropic-shaped error responses on +/// parse failure. Axum's built-in Json returns its own error format, which +/// would break clients expecting Anthropic error shapes. +pub(crate) struct AnthropicJson(pub T); + +impl FromRequest for AnthropicJson +where + Json: FromRequest, + S: Send + Sync, +{ + type Rejection = Response; + + async fn from_request(req: axum::extract::Request, state: &S) -> Result { + match Json::::from_request(req, state).await { + Ok(Json(value)) => Ok(AnthropicJson(value)), + Err(rejection) => { + let err = mapping::errors_map::create_anthropic_error( + anthropic::ErrorType::InvalidRequestError, + rejection.body_text(), + None, + ); + Err((StatusCode::BAD_REQUEST, Json(err)).into_response()) + } + } + } +} diff --git a/crates/proxy/src/server/state/app_state.rs b/crates/proxy/src/server/state/app_state.rs new file mode 100644 index 0000000..4fa88ac --- /dev/null +++ b/crates/proxy/src/server/state/app_state.rs @@ -0,0 +1,307 @@ +use crate::admin::state::{RuntimeConfig, SharedState}; +use crate::backend::BackendClient; +use crate::metrics::Metrics; +use anyllm_providers::ProviderCatalog; +use anyllm_translate::{anthropic, mapping}; +use axum::{ + http::StatusCode, + response::{IntoResponse, Json, Response}, +}; +use std::collections::HashMap; +use std::sync::{Arc, RwLock}; +use tokio::sync::Semaphore; + +use super::resolved_model::ResolvedModel; +use super::tool_engine::ToolEngineState; + +/// Per-backend state shared across request handlers. +/// +/// In single-backend mode, one `AppState` serves all routes. In multi-backend mode, +/// each backend gets its own `AppState` mounted under a prefix path (e.g., `/openai/v1/messages`). +#[derive(Clone)] +pub struct AppState { + pub backend: BackendClient, + pub metrics: Metrics, + /// Runtime config (model mappings, body logging, redaction) read on every request. + /// Shared with admin server so config changes take effect immediately. + pub runtime_config: Arc>, + /// Shared admin state for request logging and live updates. None in tests. + pub shared: Option, + /// Per-route option overrides for the request that produced this (cloned) + /// state, set by `resolve_model_and_state` when a DB route was selected. + /// `None` means "no route override; use the global RuntimeConfig value". + /// Read by the option accessors (`redact_secrets`, `effective_tool_guardrails`, + /// `active_pxpipe`, `pxpipe_models`). + pub route_options: Option>, + /// Backend name for logging purposes. + pub backend_name: String, + /// Canonical provider id used for provider/model policy decisions. + pub provider_id: Option, + /// Concurrency limiter. Uses try_acquire (fail-fast) instead of queueing + /// to prevent cascading latency under load. Requests exceeding the limit + /// get 429 immediately, matching Anthropic's rate limiting behavior. + pub concurrency: Arc, + /// Strip `stream_options` from streaming requests for local LLM compat. + pub omit_stream_options: bool, + /// Wall-clock cap for streaming responses in seconds. 0 = disabled. + /// Prevents resource exhaustion from stalled backends. + pub stream_timeout_secs: u64, + /// When true, set `x-anyllm-degradation` header on responses that silently drop features. + /// Mirrors Config::expose_degradation_warnings / MultiConfig::expose_degradation_warnings. + pub expose_degradation_warnings: bool, + /// Optional response cache for non-streaming requests. + pub cache: Option>, + /// Anthropic thinking-block record-and-restore repair store. `None` + /// unless `backend` is `BackendClient::Anthropic`; only consulted by + /// `anthropic_passthrough`. Always `Some` for Anthropic backends + /// regardless of whether the feature is enabled -- use + /// `thinking_repair_enabled()` to check the live toggle before using it. + pub thinking_repair: Option>, + /// Text-to-image context compression engine (pxpipe). `None` unless + /// `backend` is `BackendClient::Anthropic`; only consulted by + /// `anthropic_passthrough`. Always `Some` for Anthropic backends regardless + /// of the live toggle -- use `active_pxpipe()`, which checks + /// `RuntimeConfig.pxpipe_compress`, before using it. + pub pxpipe: Option>, + /// Command-aware tool-output compression engine (RTK). `Some` for Anthropic + /// and Translate modes; only consulted when `RuntimeConfig.rtk_compress` is + /// on -- use `rtk_engine_for(model)`. + pub rtk: Option>, + /// FFEC prompt-compression engine (`OptimizerEngine`). `Some` for Anthropic + /// and Translate modes, mirroring `rtk`; baked with the static + /// `OPTIMIZER_MODE`-env default at startup -- use `effective_optimizer()`, + /// which applies the live `RouteOptions.optimizer_mode` override on top. + pub optimizer: Option>, + /// Model-level router for LiteLLM model_list configs. None for TOML/env configs. + /// Wrapped in RwLock for dynamic model management via admin API. + pub model_router: Option>>, + /// Immutable provider/model catalog used for runtime model metadata. + pub provider_catalog: Arc, + /// All backend states, for cross-backend model routing. None unless model_router is set. + pub all_backends: Option>>, + /// Tool execution engine state. None when tool execution is not configured. + pub tool_engine: Option>, + /// Batch orchestration engine. None in test configs that don't need batch. + pub batch_engine: Option< + Arc< + anyllm_batch_engine::BatchEngine< + anyllm_batch_engine::queue::sqlite::SqliteQueue, + anyllm_batch_engine::webhook::sqlite::SqliteWebhookQueue, + >, + >, + >, +} + +impl AppState { + /// Map a model name through the current runtime config for this backend. + pub(crate) fn map_model(&self, model: &str) -> String { + let config = self + .runtime_config + .read() + .unwrap_or_else(|e| e.into_inner()); + if let Some(mapping) = config.model_mappings.get(&self.backend_name) { + mapping.map_model(model) + } else { + model.to_string() + } + } + + /// Resolve a model name to a backend. + /// + /// Precedence: (1) admin-DB routes (`RouteRouter`), (2) LiteLLM model_router, + /// (3) legacy ModelMapping. An empty route router falls straight through so + /// installs without routes behave exactly as before. + pub(crate) fn resolve_model(&self, model: &str) -> ResolvedModel { + if let Some(shared) = self.shared.as_ref() { + if let Some(ref rr_lock) = shared.route_router { + use crate::config::route_router::RouteResolution; + let rr = rr_lock.read().unwrap_or_else(|e| e.into_inner()); + if !rr.is_empty() { + match rr.resolve(model) { + RouteResolution::Routed(res) => { + return ResolvedModel::Routed { + backend_name: res.backend_name, + model: res.model, + deployment: res.deployment, + options: Some(res.options), + }; + } + RouteResolution::AllAtLimit => return ResolvedModel::AllAtLimit, + // No route serves this model: fall through to the layers below. + RouteResolution::NoRoute => {} + } + } + } + } + if let Some(ref router_lock) = self.model_router { + let router = router_lock.read().unwrap_or_else(|e| e.into_inner()); + if let Some(routed) = router.route(model) { + return ResolvedModel::Routed { + backend_name: routed.backend_name.to_string(), + model: routed.actual_model.to_string(), + deployment: routed.deployment.clone(), + options: None, + }; + } + if router.has_model(model) { + return ResolvedModel::AllAtLimit; + } + return ResolvedModel::UnknownModel; + } + ResolvedModel::Legacy(self.map_model(model)) + } + + /// Resolve model and return (mapped_model, effective AppState, optional deployment). + /// If the model routes to a different backend, the returned state is cloned from + /// all_backends. Returns Err with a 429 response if all deployments are at limit. + /// The deployment Arc is returned so handlers can call record_start/record_finish. + #[allow(clippy::result_large_err)] + pub(crate) fn resolve_model_and_state( + &self, + model: &str, + ) -> Result< + ( + String, + AppState, + Option>, + ), + Response, + > { + match self.resolve_model(model) { + ResolvedModel::Routed { + backend_name, + model: mapped, + deployment, + options, + } => { + let mut effective = self + .all_backends + .as_ref() + .and_then(|m| m.get(&backend_name)) + .cloned() + .or_else(|| { + // Check managed backends (SQLite-backed, zero-restart) + self.shared.as_ref().and_then(|s| { + let guard = s.managed_backends + .read() + .ok() + .or_else(|| { + tracing::warn!("managed_backends RwLock is poisoned; skipping managed backend lookup"); + None + })?; + guard.get(&backend_name).map(|(row, client)| { + let mut state = self.clone(); + state.backend = client.clone(); + state.backend_name = backend_name.clone(); + state.provider_id = Some(row.provider_id.clone()); + state + }) + }) + }) + .unwrap_or_else(|| self.clone()); + // Carry the per-route option overrides onto the effective state so + // the option accessors resolve route-first, global-fallback. + effective.route_options = options; + Ok((mapped, effective, Some(deployment))) + } + ResolvedModel::AllAtLimit => { + let err = mapping::errors_map::create_anthropic_error( + anthropic::ErrorType::RateLimitError, + "all deployments for this model are at their RPM limit".to_string(), + None, + ); + Err((StatusCode::TOO_MANY_REQUESTS, Json(err)).into_response()) + } + ResolvedModel::UnknownModel => { + let err = mapping::errors_map::create_anthropic_error( + anthropic::ErrorType::InvalidRequestError, + format!("model '{model}' is not configured in model_list"), + None, + ); + Err((StatusCode::BAD_REQUEST, Json(err)).into_response()) + } + ResolvedModel::Legacy(mapped) => Ok((mapped, self.clone(), None)), + } + } + + /// Whether request/response body logging is enabled. + pub(crate) fn log_bodies(&self) -> bool { + self.runtime_config + .read() + .unwrap_or_else(|e| e.into_inner()) + .log_bodies + } + + /// Whether upstream JSON/text request payloads should be redacted. + /// Route override (if set) wins over the global RuntimeConfig value. + pub(crate) fn redact_secrets(&self) -> bool { + if let Some(v) = self.route_options.as_ref().and_then(|o| o.redact_secrets) { + return v; + } + self.runtime_config + .read() + .unwrap_or_else(|e| e.into_inner()) + .redact_secrets + } + + /// Effective tool-call guardrail config for this request: the runtime, + /// admin-tunable override (`RuntimeConfig.tool_guardrail_mode`, no + /// restart required) applied on top of `engine.guardrails` (the static + /// preset built from YAML/env at startup). See + /// `crate::tools::resolve_runtime_guardrails`. + pub(crate) fn effective_tool_guardrails( + &self, + engine: &ToolEngineState, + ) -> crate::tools::ToolGuardrailConfig { + // Route override (if set) wins over the live global RuntimeConfig mode. + if let Some(mode) = self + .route_options + .as_ref() + .and_then(|o| o.guardrail_mode.as_deref()) + { + return crate::tools::resolve_runtime_guardrails(&engine.guardrails, mode); + } + crate::tools::resolve_runtime_guardrails_locked(&self.runtime_config, &engine.guardrails) + } + + /// Whether Anthropic thinking-block repair (record + restore) is active. + /// `self.thinking_repair` may be `Some` even when this is `false` -- the + /// store is always constructed for Anthropic backends; only this flag + /// gates whether it's actually used. + pub(crate) fn thinking_repair_enabled(&self) -> bool { + self.runtime_config + .read() + .unwrap_or_else(|e| e.into_inner()) + .anthropic_thinking_repair + } + + /// Whether Anthropic passthrough forwards the client's own incoming + /// credential upstream instead of the operator's (`ANTHROPIC_FORWARD_CLIENT_AUTH`, + /// live-toggleable via `RuntimeConfig.forward_client_auth`). Read fresh on + /// every request -- unlike the old frozen `AppState` field this replaced, + /// this reflects an admin-UI change immediately without a restart, and + /// applies uniformly to every `BackendKind::Anthropic` backend since they + /// all share one `RuntimeConfig`. + pub(crate) fn forward_client_auth_enabled(&self) -> bool { + self.runtime_config + .read() + .unwrap_or_else(|e| e.into_inner()) + .forward_client_auth + } + + /// The thinking-repair store, but only when the live admin-toggleable + /// flag is actually on. `None` both when repair is entirely absent (non- + /// Anthropic backend) and when it's present-but-disabled -- single + /// accessor so call sites collapse to `if let Some(store) = ...` instead + /// of separately checking `thinking_repair_enabled()` and + /// `thinking_repair.is_some()`. + pub(crate) fn active_thinking_repair( + &self, + ) -> Option> { + if self.thinking_repair_enabled() { + self.thinking_repair.clone() + } else { + None + } + } +} diff --git a/crates/proxy/src/server/state/compression.rs b/crates/proxy/src/server/state/compression.rs new file mode 100644 index 0000000..5508802 --- /dev/null +++ b/crates/proxy/src/server/state/compression.rs @@ -0,0 +1,281 @@ +use anyllm_translate::{anthropic, openai}; +use std::sync::Arc; + +use super::app_state::AppState; + +impl AppState { + /// The pxpipe compression engine, but only when the live admin-toggleable + /// flag (`RuntimeConfig.pxpipe_compress`) is on. `None` both when the engine + /// is absent (non-Anthropic backend) and when present-but-disabled. + pub(crate) fn active_pxpipe(&self) -> Option> { + let enabled = match self.route_options.as_ref().and_then(|o| o.pxpipe_compress) { + Some(v) => v, + None => { + self.runtime_config + .read() + .unwrap_or_else(|e| e.into_inner()) + .pxpipe_compress + } + }; + if enabled { + self.pxpipe.clone() + } else { + None + } + } + + /// Live model-scope CSV for pxpipe. Route override wins over the global + /// `RuntimeConfig.pxpipe_models` value. + pub(crate) fn pxpipe_models(&self) -> String { + if let Some(csv) = self + .route_options + .as_ref() + .and_then(|o| o.pxpipe_models.clone()) + { + return csv; + } + self.runtime_config + .read() + .unwrap_or_else(|e| e.into_inner()) + .pxpipe_models + .clone() + } + + /// Vision gate: if the catalog knows this model and says it is NOT + /// vision-capable, refuse (fail-closed). Unknown models fall back to the + /// scope list only — a Claude passthrough model is vision-capable in + /// practice, and the scope list is the operator's explicit control. + fn pxpipe_vision_ok(&self, model: &str) -> bool { + match self + .provider_id + .as_deref() + .and_then(|pid| self.provider_catalog.get_model(pid, model)) + { + Some(def) => def.capabilities.vision, + None => true, + } + } + + /// The pxpipe engine for `model`, or `None` if compression shouldn't run: + /// the master toggle is off, the engine is absent (non-Anthropic backend), + /// the model is out of the live scope CSV, or it isn't vision-capable. + /// Single accessor so `passthrough` collapses to + /// `if let Some(engine) = state.pxpipe_engine_for(model)`. + pub(crate) fn pxpipe_engine_for( + &self, + model: &str, + ) -> Option> { + let engine = self.active_pxpipe()?; + if crate::pxpipe::model_in_scope(model, &self.pxpipe_models()) + && self.pxpipe_vision_ok(model) + { + Some(engine) + } else { + None + } + } + + /// The RTK engine for `model`, or `None` if compression shouldn't run: the + /// toggle is off, the engine is absent, or the model is out of scope. RTK is + /// not vision-gated, so there is no capability check. + /// + /// Reads the toggle and scope from a single RwLock critical section for + /// consistency, and checks `route_options` first (matching the pxpipe pattern) + /// so per-route overrides take precedence over the global RuntimeConfig. + pub(crate) fn rtk_engine_for(&self, model: &str) -> Option> { + let cfg = self + .runtime_config + .read() + .unwrap_or_else(|e| e.into_inner()); + let enabled = self + .route_options + .as_ref() + .and_then(|o| o.rtk_compress) + .unwrap_or(cfg.rtk_compress); + if !enabled { + return None; + } + let engine = self.rtk.clone()?; + let models_csv = self + .route_options + .as_ref() + .and_then(|o| o.rtk_models.as_deref()) + .unwrap_or(&cfg.rtk_models); + if crate::rtk::model_in_scope(model, models_csv) { + Some(engine) + } else { + None + } + } + + /// Effective FFEC prompt-compression engine for this request, or `None` + /// when optimization is unconfigured for this backend/mode (`self.optimizer` + /// is `None`). Precedence, mirroring `effective_tool_guardrails` / + /// `resolve_runtime_guardrails_locked`: (1) route override + /// (`RouteOptions.optimizer_mode`, if set) wins outright; (2) otherwise the + /// live `RuntimeConfig.optimizer_mode` admin toggle (no restart required); + /// (3) otherwise the static per-process engine baked with the + /// `OPTIMIZER_MODE`-env default at startup. + pub(crate) fn effective_optimizer(&self) -> Option> { + let engine = self.optimizer.as_ref()?; + if let Some(mode_str) = self + .route_options + .as_ref() + .and_then(|o| o.optimizer_mode.as_deref()) + { + return Some(Arc::new(engine.with_mode_override(mode_str))); + } + Some(Arc::new( + crate::optimizer::resolve_runtime_optimizer_locked(&self.runtime_config, engine), + )) + } + + /// Apply RTK tool-output compression to an OpenAI-format request and record + /// metrics. Shared helper used by both the /v1/chat/completions and /v1/messages + /// translate paths (streaming and non-streaming). No-op when the engine is + /// unavailable, disabled, or no tool messages are present. + pub(crate) fn apply_rtk_to_openai(&self, req: &mut openai::ChatCompletionRequest, model: &str) { + let engine = match self.rtk_engine_for(model) { + Some(e) => e, + None => return, + }; + // Pre-check: only serialize when there are tool messages to compress. + if !req + .messages + .iter() + .any(|m| m.role == openai::ChatRole::Tool) + { + return; + } + let mut v = match serde_json::to_value(&*req) { + Ok(v) => v, + Err(_) => return, + }; + let Some((blocks, saved)) = engine.compress_openai_chat(&mut v) else { + return; + }; + match serde_json::from_value::(v) { + Ok(patched) => { + *req = patched; + self.metrics.record_rtk_compression(blocks, saved); + tracing::info!( + model, + blocks, + chars_saved = saved, + "rtk: compressed OpenAI request" + ); + } + Err(e) => tracing::warn!( + error = %e, + "rtk: failed to re-deserialize compressed OpenAI request; forwarding original" + ), + } + } + + /// Apply FFEC prompt compression (`effective_optimizer()`) to an OpenAI-format + /// request at the parsed-body seam. Client-sent history only -- callers must + /// never invoke this on proxy-appended tool-loop turns (see + /// `crates/optimizer/CLAUDE.md` "Streaming & tool-loop decision"). `Shadow` + /// mode logs the `OptimizationReport` and leaves `req` unchanged; `Live` mode + /// applies the rendered body in place. No-op when optimization is + /// unconfigured or resolves to `Mode::Off` for this request. + pub(crate) fn apply_optimizer_to_openai( + &self, + req: &mut openai::ChatCompletionRequest, + route: &str, + ) { + let Some(engine) = self.effective_optimizer() else { + return; + }; + let mut v = match serde_json::to_value(&*req) { + Ok(v) => v, + Err(_) => return, + }; + let report = engine.optimize_openai(&mut v, route); + if report.mode == anyllm_optimize_core::Mode::Shadow { + tracing::info!( + route, + removed_tokens_est = report.removed_tokens_est, + messages_compressed = report.messages_compressed, + failure = report.failure.as_deref().unwrap_or(""), + "optimizer: shadow report (not applied)" + ); + } + if !report.applied { + return; + } + match serde_json::from_value::(v) { + Ok(patched) => { + *req = patched; + self.metrics.record_optimization( + report.messages_compressed as u64, + report.removed_tokens_est, + ); + tracing::info!( + route, + removed_tokens_est = report.removed_tokens_est, + messages_compressed = report.messages_compressed, + "optimizer: compressed OpenAI request" + ); + } + Err(e) => tracing::warn!( + error = %e, + "optimizer: failed to re-deserialize compressed OpenAI request; forwarding original" + ), + } + } + + /// Apply FFEC prompt compression (`effective_optimizer()`) to an Anthropic + /// Messages request at the parsed-body seam. Same contract as + /// [`Self::apply_optimizer_to_openai`]: client-sent history only, fails open, + /// `Shadow` never mutates `req`. + pub(crate) fn apply_optimizer_to_anthropic( + &self, + req: &mut anthropic::MessageCreateRequest, + route: &str, + ) { + let Some(engine) = self.effective_optimizer() else { + return; + }; + let mut v = match serde_json::to_value(&*req) { + Ok(v) => v, + Err(_) => return, + }; + let report = engine.optimize_anthropic(&mut v, route); + if report.mode == anyllm_optimize_core::Mode::Shadow { + tracing::info!( + route, + removed_tokens_est = report.removed_tokens_est, + messages_compressed = report.messages_compressed, + failure = report.failure.as_deref().unwrap_or(""), + "optimizer: shadow report (not applied)" + ); + } + if !report.applied { + return; + } + match serde_json::from_value::(v) { + Ok(patched) => { + *req = patched; + self.metrics.record_optimization( + report.messages_compressed as u64, + report.removed_tokens_est, + ); + tracing::info!( + route, + removed_tokens_est = report.removed_tokens_est, + messages_compressed = report.messages_compressed, + "optimizer: compressed Anthropic request" + ); + } + Err(e) => tracing::warn!( + error = %e, + "optimizer: failed to re-deserialize compressed Anthropic request; forwarding original" + ), + } + } +} + +#[cfg(test)] +#[path = "compression/tests.rs"] +mod tests; diff --git a/crates/proxy/src/server/state/compression/tests.rs b/crates/proxy/src/server/state/compression/tests.rs new file mode 100644 index 0000000..c7ed4b0 --- /dev/null +++ b/crates/proxy/src/server/state/compression/tests.rs @@ -0,0 +1,315 @@ +use super::*; +use crate::admin::state::RuntimeConfig; +use crate::config::{BackendAuth, BackendKind, Config, ModelMapping, OpenAIApiFormat, TlsConfig}; +use crate::metrics::Metrics; +use anyllm_optimize_core::Mode; +use anyllm_providers::ProviderCatalog; +use std::sync::{Arc, RwLock}; +use tokio::sync::Semaphore; + +/// Long enough that FFEC's min-length gate actually has something to compress. +fn long_text() -> String { + "The quick brown fox jumps over the lazy dog again and again across the wide \ + green field toward the distant blue mountains far beyond the winding river." + .repeat(4) +} + +fn minimal_state(optimizer_mode: Mode) -> AppState { + let config = Config { + backend: BackendKind::OpenAI, + openai_api_key: "test".into(), + openai_base_url: "https://api.openai.com".into(), + listen_port: 3000, + model_mapping: ModelMapping { + big_model: "gpt-4o".into(), + small_model: "gpt-4o-mini".into(), + }, + tls: TlsConfig::default(), + backend_auth: BackendAuth::BearerToken("test".into()), + log_bodies: false, + redact_secrets: false, + anthropic_thinking_repair: false, + pxpipe_compress: false, + expose_degradation_warnings: false, + openai_api_format: OpenAIApiFormat::Chat, + provider_id: None, + }; + let backend = crate::backend::BackendClient::OpenAI( + crate::backend::openai_client::OpenAIClient::new(&config), + ); + let runtime_config = Arc::new(RwLock::new(RuntimeConfig { + model_mappings: indexmap::IndexMap::new(), + log_level: "info".to_string(), + log_bodies: false, + redact_secrets: false, + anthropic_thinking_repair: false, + pxpipe_compress: false, + pxpipe_models: String::new(), + rtk_compress: false, + rtk_models: String::new(), + forward_client_auth: false, + tool_guardrail_mode: "disabled".to_string(), + optimizer_mode: optimizer_mode.as_str().to_string(), + })); + AppState { + backend, + metrics: Metrics::new(), + runtime_config, + shared: None, + route_options: None, + backend_name: "openai".to_string(), + provider_id: None, + concurrency: Arc::new(Semaphore::new(64)), + omit_stream_options: false, + stream_timeout_secs: 0, + expose_degradation_warnings: false, + cache: None, + thinking_repair: None, + pxpipe: None, + rtk: None, + optimizer: Some(Arc::new(crate::optimizer::OptimizerEngine::new( + optimizer_mode, + ))), + model_router: None, + provider_catalog: Arc::new(ProviderCatalog::bundled()), + all_backends: None, + tool_engine: None, + batch_engine: None, + } +} + +fn long_openai_request() -> openai::ChatCompletionRequest { + let long = long_text(); + let mut body = serde_json::json!({ + "model": "gpt-4o", + "messages": [{"role": "system", "content": "you are helpful"}], + }); + let msgs = body["messages"].as_array_mut().unwrap(); + for _ in 0..16 { + msgs.push(serde_json::json!({"role": "user", "content": long})); + msgs.push(serde_json::json!({"role": "assistant", "content": long})); + } + msgs.push(serde_json::json!({"role": "user", "content": "what is the latest?"})); + serde_json::from_value(body).expect("valid ChatCompletionRequest") +} + +fn long_anthropic_request() -> anthropic::MessageCreateRequest { + let long = long_text(); + let mut body = serde_json::json!({ + "model": "claude-sonnet-5", + "max_tokens": 1024, + "messages": [], + }); + let msgs = body["messages"].as_array_mut().unwrap(); + for _ in 0..16 { + msgs.push(serde_json::json!({"role": "user", "content": long})); + msgs.push(serde_json::json!({"role": "assistant", "content": long})); + } + msgs.push(serde_json::json!({"role": "user", "content": "what is the latest?"})); + serde_json::from_value(body).expect("valid MessageCreateRequest") +} + +#[test] +fn shadow_mode_forwards_openai_body_unchanged() { + let state = minimal_state(Mode::Shadow); + let mut req = long_openai_request(); + let before = serde_json::to_value(&req).unwrap(); + state.apply_optimizer_to_openai(&mut req, "chat_completions"); + let after = serde_json::to_value(&req).unwrap(); + assert_eq!(before, after, "shadow mode must forward the original body"); + assert_eq!( + state.metrics.snapshot().optimizer_compressed_total, + 0, + "shadow mode must never record a metrics-visible compression" + ); +} + +#[test] +fn shadow_mode_forwards_anthropic_body_unchanged() { + let state = minimal_state(Mode::Shadow); + let mut req = long_anthropic_request(); + let before = serde_json::to_value(&req).unwrap(); + state.apply_optimizer_to_anthropic(&mut req, "messages"); + let after = serde_json::to_value(&req).unwrap(); + assert_eq!(before, after, "shadow mode must forward the original body"); +} + +#[test] +fn live_mode_compresses_openai_history_and_preserves_latest() { + let state = minimal_state(Mode::Live); + let mut req = long_openai_request(); + let before = serde_json::to_value(&req).unwrap(); + state.apply_optimizer_to_openai(&mut req, "chat_completions"); + let after = serde_json::to_value(&req).unwrap(); + assert_eq!( + before["messages"].as_array().unwrap().last(), + after["messages"].as_array().unwrap().last(), + "the latest turn must never be rewritten" + ); + assert_eq!( + state.metrics.snapshot().optimizer_compressed_total, + 1, + "an applied Live compression must be recorded in metrics" + ); +} + +#[test] +fn live_mode_compresses_anthropic_history_and_preserves_latest() { + let state = minimal_state(Mode::Live); + let mut req = long_anthropic_request(); + let before = serde_json::to_value(&req).unwrap(); + state.apply_optimizer_to_anthropic(&mut req, "messages"); + let after = serde_json::to_value(&req).unwrap(); + assert_eq!( + before["messages"].as_array().unwrap().last(), + after["messages"].as_array().unwrap().last(), + "the latest turn must never be rewritten" + ); +} + +#[test] +fn off_mode_is_noop_and_engine_absent_is_noop() { + // Off mode: engine present, mode Off -> never applied. + let state = minimal_state(Mode::Off); + let mut req = long_openai_request(); + let before = serde_json::to_value(&req).unwrap(); + state.apply_optimizer_to_openai(&mut req, "chat_completions"); + assert_eq!(before, serde_json::to_value(&req).unwrap()); + + // No engine at all (e.g. non-Anthropic/Translate mode backend): no panic, no-op. + let mut state_no_engine = minimal_state(Mode::Live); + state_no_engine.optimizer = None; + let mut req2 = long_openai_request(); + let before2 = serde_json::to_value(&req2).unwrap(); + state_no_engine.apply_optimizer_to_openai(&mut req2, "chat_completions"); + assert_eq!(before2, serde_json::to_value(&req2).unwrap()); +} + +#[test] +fn short_history_below_min_len_gate_is_a_noop_not_a_panic() { + // A short request has nothing worth compressing (below FFEC's min-length + // gate) -- the seam must still round-trip cleanly without panicking or + // corrupting the body, i.e. it fails open when there's nothing to do. + let state = minimal_state(Mode::Live); + let mut req: openai::ChatCompletionRequest = serde_json::from_value(serde_json::json!({ + "model": "gpt-4o", + "messages": [{"role": "user", "content": "hi"}], + })) + .unwrap(); + let before = serde_json::to_value(&req).unwrap(); + state.apply_optimizer_to_openai(&mut req, "chat_completions"); + assert_eq!(before, serde_json::to_value(&req).unwrap()); +} + +fn state_with_rtk(enabled: bool) -> AppState { + let config = Config { + backend: BackendKind::OpenAI, + openai_api_key: "test".into(), + openai_base_url: "https://api.openai.com".into(), + listen_port: 3000, + model_mapping: ModelMapping { + big_model: "gpt-4o".into(), + small_model: "gpt-4o-mini".into(), + }, + tls: TlsConfig::default(), + backend_auth: BackendAuth::BearerToken("test".into()), + log_bodies: false, + redact_secrets: false, + anthropic_thinking_repair: false, + pxpipe_compress: false, + expose_degradation_warnings: false, + openai_api_format: OpenAIApiFormat::Chat, + provider_id: None, + }; + let backend = crate::backend::BackendClient::OpenAI( + crate::backend::openai_client::OpenAIClient::new(&config), + ); + let runtime_config = Arc::new(RwLock::new(RuntimeConfig { + model_mappings: indexmap::IndexMap::new(), + log_level: "info".to_string(), + log_bodies: false, + redact_secrets: false, + anthropic_thinking_repair: false, + pxpipe_compress: false, + pxpipe_models: String::new(), + rtk_compress: enabled, + rtk_models: String::new(), + forward_client_auth: false, + tool_guardrail_mode: "disabled".to_string(), + optimizer_mode: "off".to_string(), + })); + AppState { + backend, + metrics: Metrics::new(), + runtime_config, + shared: None, + route_options: None, + backend_name: "openai".to_string(), + provider_id: None, + concurrency: Arc::new(Semaphore::new(64)), + omit_stream_options: false, + stream_timeout_secs: 0, + expose_degradation_warnings: false, + cache: None, + thinking_repair: None, + pxpipe: None, + rtk: Some(Arc::new(crate::rtk::RtkEngine::new())), + optimizer: None, + model_router: None, + provider_catalog: Arc::new(ProviderCatalog::bundled()), + all_backends: None, + tool_engine: None, + batch_engine: None, + } +} + +fn request_with_noisy_tool_output() -> openai::ChatCompletionRequest { + let mut noise = String::from("On branch main\nChanges not staged for commit:\n"); + for i in 0..200 { + noise.push_str(&format!(" (use \"git add ...\" file {i})\n")); + } + serde_json::from_value(serde_json::json!({ + "model": "gpt-4o", + "messages": [ + {"role": "user", "content": "run git status"}, + {"role": "assistant", "content": null, "tool_calls": [ + {"id": "t1", "type": "function", + "function": {"name": "bash", "arguments": "{\"cmd\":\"git status\"}"}} + ]}, + {"role": "tool", "tool_call_id": "t1", "content": noise}, + ], + })) + .expect("valid ChatCompletionRequest") +} + +#[test] +fn enabled_compresses_tool_output_and_records_metrics() { + let state = state_with_rtk(true); + let mut req = request_with_noisy_tool_output(); + let before = serde_json::to_value(&req).unwrap(); + state.apply_rtk_to_openai(&mut req, "gpt-4o"); + let after = serde_json::to_value(&req).unwrap(); + assert_ne!(before, after, "tool output should have been compressed"); + assert_eq!(state.metrics.snapshot().rtk_compressed_total, 1); +} + +#[test] +fn disabled_is_a_noop() { + let state = state_with_rtk(false); + let mut req = request_with_noisy_tool_output(); + let before = serde_json::to_value(&req).unwrap(); + state.apply_rtk_to_openai(&mut req, "gpt-4o"); + assert_eq!(before, serde_json::to_value(&req).unwrap()); + assert_eq!(state.metrics.snapshot().rtk_compressed_total, 0); +} + +#[test] +fn engine_absent_is_a_noop() { + let mut state = state_with_rtk(true); + state.rtk = None; + let mut req = request_with_noisy_tool_output(); + let before = serde_json::to_value(&req).unwrap(); + state.apply_rtk_to_openai(&mut req, "gpt-4o"); + assert_eq!(before, serde_json::to_value(&req).unwrap()); + assert_eq!(state.metrics.snapshot().rtk_compressed_total, 0); +} diff --git a/crates/proxy/src/server/state/concurrency.rs b/crates/proxy/src/server/state/concurrency.rs new file mode 100644 index 0000000..16ec7d5 --- /dev/null +++ b/crates/proxy/src/server/state/concurrency.rs @@ -0,0 +1,17 @@ +use crate::metrics::Metrics; +use std::collections::HashMap; +use std::sync::Arc; + +/// Global state for the multi-backend metrics endpoint. +#[derive(Clone)] +pub(crate) struct GlobalState { + pub(crate) backend_metrics: Arc>, +} + +/// Wrapper so OwnedSemaphorePermit can be stored in request extensions. +/// The field is never read directly; it exists as an RAII guard to hold +/// the permit until the struct is dropped. +#[derive(Clone)] +pub(crate) struct ConcurrencyPermit( + #[allow(dead_code)] pub(crate) Arc, +); diff --git a/crates/proxy/src/server/state/mod.rs b/crates/proxy/src/server/state/mod.rs new file mode 100644 index 0000000..c0251a4 --- /dev/null +++ b/crates/proxy/src/server/state/mod.rs @@ -0,0 +1,15 @@ +// Shared state types for request handlers: AppState, AnthropicJson, ResolvedModel, etc. +// Extracted from routes.rs so consumers can import state independently of the router setup. + +pub(crate) mod anthropic_json; +pub(crate) mod app_state; +pub(crate) mod compression; +pub(crate) mod concurrency; +pub(crate) mod resolved_model; +pub(crate) mod tool_engine; + +pub(crate) use anthropic_json::AnthropicJson; +pub use app_state::AppState; +pub(crate) use concurrency::{ConcurrencyPermit, GlobalState}; +pub(crate) use resolved_model::ResolvedModel; +pub use tool_engine::ToolEngineState; diff --git a/crates/proxy/src/server/state/resolved_model.rs b/crates/proxy/src/server/state/resolved_model.rs new file mode 100644 index 0000000..0bc57d6 --- /dev/null +++ b/crates/proxy/src/server/state/resolved_model.rs @@ -0,0 +1,21 @@ +use std::sync::Arc; + +/// Result of resolving a model name through the model router. +pub(crate) enum ResolvedModel { + /// Routed via model_list to a specific backend and actual model name. + Routed { + backend_name: String, + model: String, + /// The deployment Arc for recording in-flight/latency stats. + deployment: Arc, + /// Per-route option overrides when routed via a DB route; `None` when + /// routed via the LiteLLM model_router (inherit global config). + options: Option>, + }, + /// Model is known but all deployments are at their RPM limit. + AllAtLimit, + /// Model router is active but the model alias is not configured. + UnknownModel, + /// No model router, or model not in router. Used legacy ModelMapping. + Legacy(String), +} diff --git a/crates/proxy/src/server/state/tool_engine.rs b/crates/proxy/src/server/state/tool_engine.rs new file mode 100644 index 0000000..1d9ed1a --- /dev/null +++ b/crates/proxy/src/server/state/tool_engine.rs @@ -0,0 +1,11 @@ +use std::sync::Arc; + +/// Shared state for tool execution, stored in AppState. +#[derive(Clone)] +pub struct ToolEngineState { + pub registry: Arc, + pub policy: Arc, + pub loop_config: crate::tools::LoopConfig, + pub guardrails: crate::tools::ToolGuardrailConfig, + pub mcp_manager: Option>, +} diff --git a/crates/proxy/src/thinking_repair/repair.rs b/crates/proxy/src/thinking_repair/repair.rs index c1d5ba6..ce7ea8e 100644 --- a/crates/proxy/src/thinking_repair/repair.rs +++ b/crates/proxy/src/thinking_repair/repair.rs @@ -248,335 +248,5 @@ fn thinking_eq(a: &ContentBlock, b: &ContentBlock) -> bool { } #[cfg(test)] -mod tests { - use super::*; - use crate::thinking_repair::store::ThinkingRepairStore; - use anyllm_translate::anthropic::{Content, InputMessage, MessageCreateRequest}; - use serde_json::json; - - fn thinking(text: &str, sig: &str) -> ContentBlock { - ContentBlock::Thinking { - thinking: text.to_string(), - signature: Some(sig.to_string()), - } - } - - fn tool_use(id: &str) -> ContentBlock { - ContentBlock::ToolUse { - id: id.to_string(), - name: "get_weather".to_string(), - input: json!({"city": "nyc"}), - } - } - - fn text(s: &str) -> ContentBlock { - ContentBlock::Text { - text: s.to_string(), - } - } - - fn req_with_last_assistant(blocks: Vec) -> MessageCreateRequest { - MessageCreateRequest { - model: "claude-opus-4-5".to_string(), - max_tokens: 1024, - messages: vec![ - InputMessage { - role: Role::User, - content: Content::Text("hi".to_string()), - }, - InputMessage { - role: Role::Assistant, - content: Content::Blocks(blocks), - }, - ], - system: None, - temperature: None, - top_p: None, - top_k: None, - stop_sequences: None, - tools: None, - tool_choice: None, - metadata: None, - thinking: None, - stream: None, - extra: serde_json::Map::new(), - } - } - - fn last_blocks(req: &MessageCreateRequest) -> &Vec { - match &req.messages.last().unwrap().content { - Content::Blocks(b) => b, - Content::Text(_) => panic!("expected blocks"), - } - } - - #[tokio::test] - async fn tier0_byte_identical_passthrough() { - let store = ThinkingRepairStore::new(); - store - .commit( - "ns1", - "msg_1", - vec![thinking("hmm", "sig_1"), tool_use("toolu_1")], - ) - .await; - - let mut req = req_with_last_assistant(vec![thinking("hmm", "sig_1"), tool_use("toolu_1")]); - let result = repair_request(&store, "ns1", &mut req).await; - - assert!( - result.is_none(), - "byte-identical replay should not be touched" - ); - assert_eq!(last_blocks(&req).len(), 2); - } - - #[tokio::test] - async fn tier1_restores_mutated_text_under_known_signature() { - let store = ThinkingRepairStore::new(); - store - .commit( - "ns1", - "msg_1", - vec![thinking("original thought", "sig_1"), tool_use("toolu_1")], - ) - .await; - - let mut req = req_with_last_assistant(vec![ - thinking("merged garbled thought", "sig_1"), - tool_use("toolu_1"), - ]); - let result = repair_request(&store, "ns1", &mut req).await; - - assert!(result.unwrap().contains("restored 1")); - match &last_blocks(&req)[0] { - ContentBlock::Thinking { thinking, .. } => assert_eq!(thinking, "original thought"), - other => panic!("expected restored Thinking block, got {other:?}"), - } - } - - #[tokio::test] - async fn tier2_drops_intruder_block_from_different_message() { - let store = ThinkingRepairStore::new(); - store - .commit( - "ns1", - "msg_1", - vec![thinking("owner thought", "sig_owner"), tool_use("toolu_1")], - ) - .await; - store - .commit("ns1", "msg_2", vec![thinking("other thought", "sig_other")]) - .await; - - // Replay claims to be msg_1's turn (tool_use toolu_1) but carries an - // intruder thinking block whose signature belongs to msg_2. - let mut req = req_with_last_assistant(vec![ - thinking("other thought", "sig_other"), - thinking("owner thought", "sig_owner"), - tool_use("toolu_1"), - ]); - let result = repair_request(&store, "ns1", &mut req).await; - - assert!(result.unwrap().contains("dropped 1")); - let blocks = last_blocks(&req); - assert_eq!(blocks.len(), 2); - assert!( - matches!(&blocks[0], ContentBlock::Thinking { signature: Some(s), .. } if s == "sig_owner") - ); - } - - #[tokio::test] - async fn unknown_signature_with_known_owner_is_dropped() { - let store = ThinkingRepairStore::new(); - store - .commit( - "ns1", - "msg_1", - vec![thinking("owner thought", "sig_owner"), tool_use("toolu_1")], - ) - .await; - - let mut req = req_with_last_assistant(vec![ - thinking("garbage, no matching record", "sig_unknown"), - thinking("owner thought", "sig_owner"), - tool_use("toolu_1"), - ]); - let result = repair_request(&store, "ns1", &mut req).await; - - assert!(result.unwrap().contains("dropped 1")); - assert_eq!(last_blocks(&req).len(), 2); - } - - #[tokio::test] - async fn unknown_signature_with_no_owner_fails_open() { - let store = ThinkingRepairStore::new(); - // No tool_use in this turn, so no owner is resolvable. - let mut req = req_with_last_assistant(vec![ - thinking("standalone thought", "sig_unknown"), - text("done"), - ]); - let result = repair_request(&store, "ns1", &mut req).await; - - assert!( - result.is_none(), - "no owner evidence -> fail open, pass through" - ); - assert_eq!(last_blocks(&req).len(), 2); - } - - #[tokio::test] - async fn reinserts_lost_redacted_thinking_block() { - let store = ThinkingRepairStore::new(); - let redacted = ContentBlock::RedactedThinking { - data: "encrypted-blob".to_string(), - }; - store - .commit("ns1", "msg_1", vec![redacted.clone(), tool_use("toolu_1")]) - .await; - - // Client replay is missing the redacted_thinking block entirely - // (Claude Code doesn't persist it to JSONL). - let mut req = req_with_last_assistant(vec![tool_use("toolu_1")]); - let result = repair_request(&store, "ns1", &mut req).await; - - let msg = result.unwrap(); - assert!(msg.contains("reinserted"), "got: {msg}"); - let blocks = last_blocks(&req); - assert_eq!(blocks.len(), 2); - assert!( - matches!(&blocks[0], ContentBlock::RedactedThinking { data } if data == "encrypted-blob") - ); - } - - #[tokio::test] - async fn surplus_current_nonthinking_block_disables_reinsert() { - // rec has [thinking, tool_use_1]; current turn replays tool_use_1 - // AND adds an extra tool_use_2 that was never recorded, while also - // dropping the thinking block. Non-thinking counts don't line up - // (2 vs. 1), so positional reinsertion would either drop toolu_2 or - // misalign it against rec -- must fail open instead. - let store = ThinkingRepairStore::new(); - store - .commit( - "ns1", - "msg_1", - vec![thinking("hmm", "sig_1"), tool_use("toolu_1")], - ) - .await; - - let mut req = req_with_last_assistant(vec![tool_use("toolu_1"), tool_use("toolu_2")]); - let result = repair_request(&store, "ns1", &mut req).await; - let blocks = last_blocks(&req); - - assert!(result.is_none(), "count mismatch should fail open"); - assert_eq!( - blocks.len(), - 2, - "current turn's blocks must be preserved as-is" - ); - assert!( - blocks - .iter() - .any(|b| matches!(b, ContentBlock::ToolUse { id, .. } if id == "toolu_2")), - "toolu_2 must not be dropped" - ); - } - - #[tokio::test] - async fn missing_current_nonthinking_block_disables_reinsert() { - // rec has [thinking, tool_use_1, tool_use_2]; current turn only - // replays tool_use_1 (dropped tool_use_2 AND the thinking block). - // Non-thinking counts don't line up (1 vs. 2), so reinserting - // positionally would resurrect stale toolu_2 -- must fail open. - let store = ThinkingRepairStore::new(); - store - .commit( - "ns1", - "msg_1", - vec![ - thinking("hmm", "sig_1"), - tool_use("toolu_1"), - tool_use("toolu_2"), - ], - ) - .await; - - let mut req = req_with_last_assistant(vec![tool_use("toolu_1")]); - let result = repair_request(&store, "ns1", &mut req).await; - let blocks = last_blocks(&req); - - assert!(result.is_none(), "count mismatch should fail open"); - assert_eq!( - blocks.len(), - 1, - "current turn's blocks must be preserved as-is" - ); - assert!( - !blocks - .iter() - .any(|b| matches!(b, ContentBlock::ToolUse { id, .. } if id == "toolu_2")), - "stale recorded toolu_2 must not be reinserted" - ); - } - - #[tokio::test] - async fn messages_before_last_assistant_are_never_touched() { - let store = ThinkingRepairStore::new(); - store - .commit("ns1", "msg_2", vec![thinking("turn 2", "sig_2")]) - .await; - - let mut req = MessageCreateRequest { - model: "claude-opus-4-5".to_string(), - max_tokens: 1024, - messages: vec![ - InputMessage { - role: Role::User, - content: Content::Text("first".to_string()), - }, - InputMessage { - role: Role::Assistant, - // Deliberately mutated/corrupt — must be left alone - // because it is NOT the last assistant message. - content: Content::Blocks(vec![thinking("mutated turn 1", "sig_1_unknown")]), - }, - InputMessage { - role: Role::User, - content: Content::Text("second".to_string()), - }, - InputMessage { - role: Role::Assistant, - content: Content::Blocks(vec![thinking("turn 2", "sig_2")]), - }, - ], - system: None, - temperature: None, - top_p: None, - top_k: None, - stop_sequences: None, - tools: None, - tool_choice: None, - metadata: None, - thinking: None, - stream: None, - extra: serde_json::Map::new(), - }; - - repair_request(&store, "ns1", &mut req).await; - - match &req.messages[1].content { - Content::Blocks(b) => match &b[0] { - ContentBlock::Thinking { - thinking, - signature, - } => { - assert_eq!(thinking, "mutated turn 1"); - assert_eq!(signature.as_deref(), Some("sig_1_unknown")); - } - other => panic!("unexpected block: {other:?}"), - }, - Content::Text(_) => panic!("expected blocks"), - } - } -} +#[path = "repair/tests.rs"] +mod tests; diff --git a/crates/proxy/src/thinking_repair/repair/tests.rs b/crates/proxy/src/thinking_repair/repair/tests.rs new file mode 100644 index 0000000..66ae9d2 --- /dev/null +++ b/crates/proxy/src/thinking_repair/repair/tests.rs @@ -0,0 +1,330 @@ +use super::*; +use crate::thinking_repair::store::ThinkingRepairStore; +use anyllm_translate::anthropic::{Content, InputMessage, MessageCreateRequest}; +use serde_json::json; + +fn thinking(text: &str, sig: &str) -> ContentBlock { + ContentBlock::Thinking { + thinking: text.to_string(), + signature: Some(sig.to_string()), + } +} + +fn tool_use(id: &str) -> ContentBlock { + ContentBlock::ToolUse { + id: id.to_string(), + name: "get_weather".to_string(), + input: json!({"city": "nyc"}), + } +} + +fn text(s: &str) -> ContentBlock { + ContentBlock::Text { + text: s.to_string(), + } +} + +fn req_with_last_assistant(blocks: Vec) -> MessageCreateRequest { + MessageCreateRequest { + model: "claude-opus-4-5".to_string(), + max_tokens: 1024, + messages: vec![ + InputMessage { + role: Role::User, + content: Content::Text("hi".to_string()), + }, + InputMessage { + role: Role::Assistant, + content: Content::Blocks(blocks), + }, + ], + system: None, + temperature: None, + top_p: None, + top_k: None, + stop_sequences: None, + tools: None, + tool_choice: None, + metadata: None, + thinking: None, + stream: None, + extra: serde_json::Map::new(), + } +} + +fn last_blocks(req: &MessageCreateRequest) -> &Vec { + match &req.messages.last().unwrap().content { + Content::Blocks(b) => b, + Content::Text(_) => panic!("expected blocks"), + } +} + +#[tokio::test] +async fn tier0_byte_identical_passthrough() { + let store = ThinkingRepairStore::new(); + store + .commit( + "ns1", + "msg_1", + vec![thinking("hmm", "sig_1"), tool_use("toolu_1")], + ) + .await; + + let mut req = req_with_last_assistant(vec![thinking("hmm", "sig_1"), tool_use("toolu_1")]); + let result = repair_request(&store, "ns1", &mut req).await; + + assert!( + result.is_none(), + "byte-identical replay should not be touched" + ); + assert_eq!(last_blocks(&req).len(), 2); +} + +#[tokio::test] +async fn tier1_restores_mutated_text_under_known_signature() { + let store = ThinkingRepairStore::new(); + store + .commit( + "ns1", + "msg_1", + vec![thinking("original thought", "sig_1"), tool_use("toolu_1")], + ) + .await; + + let mut req = req_with_last_assistant(vec![ + thinking("merged garbled thought", "sig_1"), + tool_use("toolu_1"), + ]); + let result = repair_request(&store, "ns1", &mut req).await; + + assert!(result.unwrap().contains("restored 1")); + match &last_blocks(&req)[0] { + ContentBlock::Thinking { thinking, .. } => assert_eq!(thinking, "original thought"), + other => panic!("expected restored Thinking block, got {other:?}"), + } +} + +#[tokio::test] +async fn tier2_drops_intruder_block_from_different_message() { + let store = ThinkingRepairStore::new(); + store + .commit( + "ns1", + "msg_1", + vec![thinking("owner thought", "sig_owner"), tool_use("toolu_1")], + ) + .await; + store + .commit("ns1", "msg_2", vec![thinking("other thought", "sig_other")]) + .await; + + // Replay claims to be msg_1's turn (tool_use toolu_1) but carries an + // intruder thinking block whose signature belongs to msg_2. + let mut req = req_with_last_assistant(vec![ + thinking("other thought", "sig_other"), + thinking("owner thought", "sig_owner"), + tool_use("toolu_1"), + ]); + let result = repair_request(&store, "ns1", &mut req).await; + + assert!(result.unwrap().contains("dropped 1")); + let blocks = last_blocks(&req); + assert_eq!(blocks.len(), 2); + assert!( + matches!(&blocks[0], ContentBlock::Thinking { signature: Some(s), .. } if s == "sig_owner") + ); +} + +#[tokio::test] +async fn unknown_signature_with_known_owner_is_dropped() { + let store = ThinkingRepairStore::new(); + store + .commit( + "ns1", + "msg_1", + vec![thinking("owner thought", "sig_owner"), tool_use("toolu_1")], + ) + .await; + + let mut req = req_with_last_assistant(vec![ + thinking("garbage, no matching record", "sig_unknown"), + thinking("owner thought", "sig_owner"), + tool_use("toolu_1"), + ]); + let result = repair_request(&store, "ns1", &mut req).await; + + assert!(result.unwrap().contains("dropped 1")); + assert_eq!(last_blocks(&req).len(), 2); +} + +#[tokio::test] +async fn unknown_signature_with_no_owner_fails_open() { + let store = ThinkingRepairStore::new(); + // No tool_use in this turn, so no owner is resolvable. + let mut req = req_with_last_assistant(vec![ + thinking("standalone thought", "sig_unknown"), + text("done"), + ]); + let result = repair_request(&store, "ns1", &mut req).await; + + assert!( + result.is_none(), + "no owner evidence -> fail open, pass through" + ); + assert_eq!(last_blocks(&req).len(), 2); +} + +#[tokio::test] +async fn reinserts_lost_redacted_thinking_block() { + let store = ThinkingRepairStore::new(); + let redacted = ContentBlock::RedactedThinking { + data: "encrypted-blob".to_string(), + }; + store + .commit("ns1", "msg_1", vec![redacted.clone(), tool_use("toolu_1")]) + .await; + + // Client replay is missing the redacted_thinking block entirely + // (Claude Code doesn't persist it to JSONL). + let mut req = req_with_last_assistant(vec![tool_use("toolu_1")]); + let result = repair_request(&store, "ns1", &mut req).await; + + let msg = result.unwrap(); + assert!(msg.contains("reinserted"), "got: {msg}"); + let blocks = last_blocks(&req); + assert_eq!(blocks.len(), 2); + assert!( + matches!(&blocks[0], ContentBlock::RedactedThinking { data } if data == "encrypted-blob") + ); +} + +#[tokio::test] +async fn surplus_current_nonthinking_block_disables_reinsert() { + // rec has [thinking, tool_use_1]; current turn replays tool_use_1 + // AND adds an extra tool_use_2 that was never recorded, while also + // dropping the thinking block. Non-thinking counts don't line up + // (2 vs. 1), so positional reinsertion would either drop toolu_2 or + // misalign it against rec -- must fail open instead. + let store = ThinkingRepairStore::new(); + store + .commit( + "ns1", + "msg_1", + vec![thinking("hmm", "sig_1"), tool_use("toolu_1")], + ) + .await; + + let mut req = req_with_last_assistant(vec![tool_use("toolu_1"), tool_use("toolu_2")]); + let result = repair_request(&store, "ns1", &mut req).await; + let blocks = last_blocks(&req); + + assert!(result.is_none(), "count mismatch should fail open"); + assert_eq!( + blocks.len(), + 2, + "current turn's blocks must be preserved as-is" + ); + assert!( + blocks + .iter() + .any(|b| matches!(b, ContentBlock::ToolUse { id, .. } if id == "toolu_2")), + "toolu_2 must not be dropped" + ); +} + +#[tokio::test] +async fn missing_current_nonthinking_block_disables_reinsert() { + // rec has [thinking, tool_use_1, tool_use_2]; current turn only + // replays tool_use_1 (dropped tool_use_2 AND the thinking block). + // Non-thinking counts don't line up (1 vs. 2), so reinserting + // positionally would resurrect stale toolu_2 -- must fail open. + let store = ThinkingRepairStore::new(); + store + .commit( + "ns1", + "msg_1", + vec![ + thinking("hmm", "sig_1"), + tool_use("toolu_1"), + tool_use("toolu_2"), + ], + ) + .await; + + let mut req = req_with_last_assistant(vec![tool_use("toolu_1")]); + let result = repair_request(&store, "ns1", &mut req).await; + let blocks = last_blocks(&req); + + assert!(result.is_none(), "count mismatch should fail open"); + assert_eq!( + blocks.len(), + 1, + "current turn's blocks must be preserved as-is" + ); + assert!( + !blocks + .iter() + .any(|b| matches!(b, ContentBlock::ToolUse { id, .. } if id == "toolu_2")), + "stale recorded toolu_2 must not be reinserted" + ); +} + +#[tokio::test] +async fn messages_before_last_assistant_are_never_touched() { + let store = ThinkingRepairStore::new(); + store + .commit("ns1", "msg_2", vec![thinking("turn 2", "sig_2")]) + .await; + + let mut req = MessageCreateRequest { + model: "claude-opus-4-5".to_string(), + max_tokens: 1024, + messages: vec![ + InputMessage { + role: Role::User, + content: Content::Text("first".to_string()), + }, + InputMessage { + role: Role::Assistant, + // Deliberately mutated/corrupt — must be left alone + // because it is NOT the last assistant message. + content: Content::Blocks(vec![thinking("mutated turn 1", "sig_1_unknown")]), + }, + InputMessage { + role: Role::User, + content: Content::Text("second".to_string()), + }, + InputMessage { + role: Role::Assistant, + content: Content::Blocks(vec![thinking("turn 2", "sig_2")]), + }, + ], + system: None, + temperature: None, + top_p: None, + top_k: None, + stop_sequences: None, + tools: None, + tool_choice: None, + metadata: None, + thinking: None, + stream: None, + extra: serde_json::Map::new(), + }; + + repair_request(&store, "ns1", &mut req).await; + + match &req.messages[1].content { + Content::Blocks(b) => match &b[0] { + ContentBlock::Thinking { + thinking, + signature, + } => { + assert_eq!(thinking, "mutated turn 1"); + assert_eq!(signature.as_deref(), Some("sig_1_unknown")); + } + other => panic!("unexpected block: {other:?}"), + }, + Content::Text(_) => panic!("expected blocks"), + } +} diff --git a/crates/proxy/src/tools/execution/guardrail.rs b/crates/proxy/src/tools/execution/guardrail.rs new file mode 100644 index 0000000..828d025 --- /dev/null +++ b/crates/proxy/src/tools/execution/guardrail.rs @@ -0,0 +1,131 @@ +//! Tool guardrail logic: partition tool calls into auto-execute / pass-through / +//! denied buckets, build denial + nudge results, and the combined +//! `partition_and_nudge` shared by both tool loops. + +use std::collections::HashSet; + +use super::{ToolCall, ToolResult}; +use crate::tools::policy::{PolicyAction, ToolExecutionPolicy}; +use crate::tools::registry::ToolRegistry; +use crate::tools::trace::ToolOutcome; +use crate::tools::{evaluate_tool_guardrails, ToolGuardrailNudge, ToolGuardrailRequestState}; + +/// Partition tool calls into three categories. +/// +/// A tool call is only eligible for server-side policy evaluation when the +/// proxy advertised that exact tool name for this request. This prevents +/// client-supplied tool schemas from reusing privileged server tool names. +/// +/// - `auto_execute`: proxy-advertised AND in registry AND policy says Allow +/// - `pass_through`: not proxy-advertised, not in registry, OR policy says PassThrough +/// - `denied`: proxy-advertised AND policy says Deny +pub fn partition_tool_calls<'a>( + tool_calls: &'a [ToolCall], + registry: &ToolRegistry, + policy: &ToolExecutionPolicy, + server_advertised_tool_names: &HashSet, +) -> (Vec<&'a ToolCall>, Vec<&'a ToolCall>, Vec<&'a ToolCall>) { + let mut auto_execute = Vec::new(); + let mut pass_through = Vec::new(); + let mut denied = Vec::new(); + + for call in tool_calls { + if !server_advertised_tool_names.contains(&call.name) { + pass_through.push(call); + continue; + } + + match policy.resolve(&call.name) { + PolicyAction::Deny => denied.push(call), + PolicyAction::Allow if registry.contains(&call.name) => auto_execute.push(call), + // Allow but not in registry, or PassThrough + _ => pass_through.push(call), + } + } + + (auto_execute, pass_through, denied) +} + +/// Build error ToolResults for denied tool calls. +pub fn denied_tool_results(denied: &[&ToolCall]) -> Vec { + denied + .iter() + .map(|call| ToolResult { + tool_use_id: call.id.clone(), + tool_name: call.name.clone(), + outcome: ToolOutcome::Error { + message: format!("Tool '{}' is denied by policy", call.name), + retryable: false, + }, + }) + .collect() +} + +/// Build retryable ToolResults for guardrail nudges. +/// +/// Only the tool calls a nudge targets get a result; every other call in the +/// batch is left for normal execution/pass-through. `calls` supplies the tool +/// name for each nudged `call_id`. +pub fn guardrail_nudge_results( + calls: &[ToolCall], + nudges: &[ToolGuardrailNudge], +) -> Vec { + nudges + .iter() + .filter_map(|nudge| { + let call = calls.iter().find(|c| c.id == nudge.call_id)?; + Some(ToolResult { + tool_use_id: call.id.clone(), + tool_name: call.name.clone(), + outcome: ToolOutcome::Error { + message: format!("[ToolCallPolicyNudge] {}", nudge.content), + retryable: true, + }, + }) + }) + .collect() +} + +/// Partition `tool_calls`, then evaluate guardrail nudges only against the +/// calls that would otherwise be auto-executed by this proxy. +/// +/// A nudge target must be something the proxy actually owns: guardrails are +/// an advisory retry mechanism that answers the model on the proxy's behalf +/// (a synthetic tool_result + a follow-up backend call), so applying it to a +/// pass-through call (not in the registry / not proxy-advertised, meaning the +/// *caller* owns and expects to execute it -- e.g. a Claude-Code-style client's +/// own Bash/Grep/Edit/Write tool) would silently swallow that call's real +/// tool_use turn instead of returning it to the caller. Restricting +/// `evaluate_tool_guardrails` to the post-partition `auto_exec` set keeps +/// nudges impossible to apply to anything the proxy doesn't own, and +/// `denied` never needs nudge-filtering since nudges only ever originate +/// from `auto_exec` now (the two sets are disjoint by construction). +/// +/// Shared by the streaming and non-streaming tool loops so a fix here can't +/// drift between the two copies. +pub fn partition_and_nudge<'a>( + tool_calls: &'a [ToolCall], + tool_specs: &[anyllm_translate::anthropic::Tool], + registry: &ToolRegistry, + policy: &ToolExecutionPolicy, + server_advertised_tool_names: &HashSet, + guardrails: &crate::tools::ToolGuardrailConfig, + guardrail_state: &mut ToolGuardrailRequestState, +) -> (Vec<&'a ToolCall>, Vec, Vec) { + let (auto_exec, _pass_through, denied) = + partition_tool_calls(tool_calls, registry, policy, server_advertised_tool_names); + + let auto_exec_owned: Vec = auto_exec.iter().map(|c| (*c).clone()).collect(); + let nudges = + evaluate_tool_guardrails(&auto_exec_owned, tool_specs, guardrails, guardrail_state); + let nudged_ids: HashSet<&str> = nudges.iter().map(|n| n.call_id.as_str()).collect(); + let nudge_results = guardrail_nudge_results(&auto_exec_owned, &nudges); + + let auto_exec: Vec<&ToolCall> = auto_exec + .into_iter() + .filter(|c| !nudged_ids.contains(c.id.as_str())) + .collect(); + let denied_results = denied_tool_results(&denied); + + (auto_exec, nudge_results, denied_results) +} diff --git a/crates/proxy/src/tools/execution/helpers.rs b/crates/proxy/src/tools/execution/helpers.rs new file mode 100644 index 0000000..437c8f1 --- /dev/null +++ b/crates/proxy/src/tools/execution/helpers.rs @@ -0,0 +1,63 @@ +//! Parsers/converters between Anthropic message types and `ToolCall`/`ToolResult`. + +use super::{ToolCall, ToolResult}; +use crate::tools::trace::ToolOutcome; + +/// Extract ToolCall structs from an Anthropic MessageResponse. +pub fn extract_tool_calls( + response: &anyllm_translate::anthropic::MessageResponse, +) -> Vec { + response + .content + .iter() + .filter_map(|block| { + if let anyllm_translate::anthropic::ContentBlock::ToolUse { id, name, input } = block { + Some(ToolCall { + id: id.clone(), + name: name.clone(), + input: input.clone(), + }) + } else { + None + } + }) + .collect() +} + +/// Convert tool execution results to an Anthropic user message with ToolResult blocks. +pub fn tool_results_to_user_message( + results: &[ToolResult], +) -> anyllm_translate::anthropic::InputMessage { + let blocks: Vec = results + .iter() + .map(|r| { + let (content_text, is_error) = match &r.outcome { + ToolOutcome::Success(v) => (serde_json::to_string(v).unwrap_or_default(), false), + ToolOutcome::Error { message, .. } => (message.clone(), true), + ToolOutcome::Timeout => ("Tool execution timed out".to_string(), true), + }; + anyllm_translate::anthropic::ContentBlock::ToolResult { + tool_use_id: r.tool_use_id.clone(), + content: Some(anyllm_translate::anthropic::ToolResultContent::Text( + content_text, + )), + is_error: Some(is_error), + } + }) + .collect(); + + anyllm_translate::anthropic::InputMessage { + role: anyllm_translate::anthropic::Role::User, + content: anyllm_translate::anthropic::Content::Blocks(blocks), + } +} + +/// Convert a MessageResponse's content into an assistant InputMessage for conversation history. +pub fn response_to_assistant_message( + response: &anyllm_translate::anthropic::MessageResponse, +) -> anyllm_translate::anthropic::InputMessage { + anyllm_translate::anthropic::InputMessage { + role: anyllm_translate::anthropic::Role::Assistant, + content: anyllm_translate::anthropic::Content::Blocks(response.content.clone()), + } +} diff --git a/crates/proxy/src/tools/execution/mod.rs b/crates/proxy/src/tools/execution/mod.rs index ba4e413..d5aaad8 100644 --- a/crates/proxy/src/tools/execution/mod.rs +++ b/crates/proxy/src/tools/execution/mod.rs @@ -1,14 +1,28 @@ -use std::collections::HashSet; -use std::sync::Arc; -use std::time::{Duration, Instant}; +//! Tool execution: the shared types plus the guardrail, runner, and helper +//! submodules that implement partitioning, parallel execution, and the bounded +//! non-streaming tool loop. + +use std::time::Duration; use serde_json::Value; -use tokio::task::JoinSet; -use crate::tools::policy::{PolicyAction, ToolExecutionPolicy}; -use crate::tools::registry::ToolRegistry; use crate::tools::trace::ToolOutcome; -use crate::tools::{evaluate_tool_guardrails, ToolGuardrailNudge, ToolGuardrailRequestState}; + +mod guardrail; +mod helpers; +mod runner; + +pub use guardrail::{ + denied_tool_results, guardrail_nudge_results, partition_and_nudge, partition_tool_calls, +}; +pub use helpers::{ + extract_tool_calls, response_to_assistant_message, tool_results_to_user_message, +}; +pub use runner::{execute_tool_calls, execute_tool_calls_timed, is_duplicate, maybe_execute_tools}; + +/// Engine state needed by `maybe_execute_tools`. Re-exported alias so callers +/// do not need to reach into `server::state`. +pub use crate::server::state::ToolEngineState; /// A tool call extracted from an LLM response. #[derive(Debug, Clone)] @@ -46,555 +60,5 @@ impl Default for LoopConfig { } } -/// Partition tool calls into three categories. -/// -/// A tool call is only eligible for server-side policy evaluation when the -/// proxy advertised that exact tool name for this request. This prevents -/// client-supplied tool schemas from reusing privileged server tool names. -/// -/// - `auto_execute`: proxy-advertised AND in registry AND policy says Allow -/// - `pass_through`: not proxy-advertised, not in registry, OR policy says PassThrough -/// - `denied`: proxy-advertised AND policy says Deny -pub fn partition_tool_calls<'a>( - tool_calls: &'a [ToolCall], - registry: &ToolRegistry, - policy: &ToolExecutionPolicy, - server_advertised_tool_names: &HashSet, -) -> (Vec<&'a ToolCall>, Vec<&'a ToolCall>, Vec<&'a ToolCall>) { - let mut auto_execute = Vec::new(); - let mut pass_through = Vec::new(); - let mut denied = Vec::new(); - - for call in tool_calls { - if !server_advertised_tool_names.contains(&call.name) { - pass_through.push(call); - continue; - } - - match policy.resolve(&call.name) { - PolicyAction::Deny => denied.push(call), - PolicyAction::Allow if registry.contains(&call.name) => auto_execute.push(call), - // Allow but not in registry, or PassThrough - _ => pass_through.push(call), - } - } - - (auto_execute, pass_through, denied) -} - -/// Build error ToolResults for denied tool calls. -pub fn denied_tool_results(denied: &[&ToolCall]) -> Vec { - denied - .iter() - .map(|call| ToolResult { - tool_use_id: call.id.clone(), - tool_name: call.name.clone(), - outcome: ToolOutcome::Error { - message: format!("Tool '{}' is denied by policy", call.name), - retryable: false, - }, - }) - .collect() -} - -/// Build retryable ToolResults for guardrail nudges. -/// -/// Only the tool calls a nudge targets get a result; every other call in the -/// batch is left for normal execution/pass-through. `calls` supplies the tool -/// name for each nudged `call_id`. -pub fn guardrail_nudge_results( - calls: &[ToolCall], - nudges: &[ToolGuardrailNudge], -) -> Vec { - nudges - .iter() - .filter_map(|nudge| { - let call = calls.iter().find(|c| c.id == nudge.call_id)?; - Some(ToolResult { - tool_use_id: call.id.clone(), - tool_name: call.name.clone(), - outcome: ToolOutcome::Error { - message: format!("[ToolCallPolicyNudge] {}", nudge.content), - retryable: true, - }, - }) - }) - .collect() -} - -/// Partition `tool_calls`, then evaluate guardrail nudges only against the -/// calls that would otherwise be auto-executed by this proxy. -/// -/// A nudge target must be something the proxy actually owns: guardrails are -/// an advisory retry mechanism that answers the model on the proxy's behalf -/// (a synthetic tool_result + a follow-up backend call), so applying it to a -/// pass-through call (not in the registry / not proxy-advertised, meaning the -/// *caller* owns and expects to execute it — e.g. a Claude-Code-style client's -/// own Bash/Grep/Edit/Write tool) would silently swallow that call's real -/// tool_use turn instead of returning it to the caller. Restricting -/// `evaluate_tool_guardrails` to the post-partition `auto_exec` set keeps -/// nudges impossible to apply to anything the proxy doesn't own, and -/// `denied` never needs nudge-filtering since nudges only ever originate -/// from `auto_exec` now (the two sets are disjoint by construction). -/// -/// Shared by the streaming and non-streaming tool loops so a fix here can't -/// drift between the two copies. -pub fn partition_and_nudge<'a>( - tool_calls: &'a [ToolCall], - tool_specs: &[anyllm_translate::anthropic::Tool], - registry: &ToolRegistry, - policy: &ToolExecutionPolicy, - server_advertised_tool_names: &HashSet, - guardrails: &crate::tools::ToolGuardrailConfig, - guardrail_state: &mut ToolGuardrailRequestState, -) -> (Vec<&'a ToolCall>, Vec, Vec) { - let (auto_exec, _pass_through, denied) = - partition_tool_calls(tool_calls, registry, policy, server_advertised_tool_names); - - let auto_exec_owned: Vec = auto_exec.iter().map(|c| (*c).clone()).collect(); - let nudges = - evaluate_tool_guardrails(&auto_exec_owned, tool_specs, guardrails, guardrail_state); - let nudged_ids: HashSet<&str> = nudges.iter().map(|n| n.call_id.as_str()).collect(); - let nudge_results = guardrail_nudge_results(&auto_exec_owned, &nudges); - - let auto_exec: Vec<&ToolCall> = auto_exec - .into_iter() - .filter(|c| !nudged_ids.contains(c.id.as_str())) - .collect(); - let denied_results = denied_tool_results(&denied); - - (auto_exec, nudge_results, denied_results) -} - -/// Execute tool calls in parallel, respecting per-tool timeouts. -/// -/// Results are returned in the same order as `calls`. -pub async fn execute_tool_calls( - calls: &[&ToolCall], - registry: Arc, - policy: &ToolExecutionPolicy, - config: &LoopConfig, -) -> Vec { - let capped = &calls[..calls.len().min(config.max_tool_calls_per_turn)]; - - // Collect (original_index, ToolCall) to restore order after parallel execution. - let indexed: Vec<(usize, &ToolCall)> = capped.iter().copied().enumerate().collect(); - - let mut join_set: JoinSet<(usize, ToolResult)> = JoinSet::new(); - - for (idx, call) in indexed { - let timeout = policy - .find_rule(&call.name) - .and_then(|r| r.timeout) - .unwrap_or(config.tool_timeout); - - let registry = Arc::clone(®istry); - let id = call.id.clone(); - let name = call.name.clone(); - let input = call.input.clone(); - - join_set.spawn(async move { - let result = - tokio::time::timeout(timeout, execute_single(®istry, &name, input)).await; - - let outcome = match result { - Ok(Ok(value)) => ToolOutcome::Success(value), - Ok(Err(msg)) => ToolOutcome::Error { - message: msg, - retryable: false, - }, - Err(_elapsed) => ToolOutcome::Timeout, - }; - - ( - idx, - ToolResult { - tool_use_id: id, - tool_name: name, - outcome, - }, - ) - }); - } - - let mut collected: Vec<(usize, ToolResult)> = Vec::with_capacity(capped.len()); - while let Some(res) = join_set.join_next().await { - match res { - Ok(pair) => collected.push(pair), - Err(e) => { - // JoinError means the task panicked; treat as an error outcome. - // We don't have the index here, so we skip (shouldn't happen in practice). - tracing::error!("tool execution task panicked: {e}"); - } - } - } - - collected.sort_by_key(|(idx, _)| *idx); - collected.into_iter().map(|(_, r)| r).collect() -} - -/// Check whether two slices of ToolCall represent the same logical calls. -/// -/// Same length, same multiset of (name, input) pairs. IDs are ignored. -pub fn is_duplicate(a: &[ToolCall], b: &[ToolCall]) -> bool { - if a.len() != b.len() { - return false; - } - - let mut a_pairs: Vec<(&str, &Value)> = a.iter().map(|c| (c.name.as_str(), &c.input)).collect(); - let mut b_pairs: Vec<(&str, &Value)> = b.iter().map(|c| (c.name.as_str(), &c.input)).collect(); - - // Sort by name so comparison is order-independent. - a_pairs.sort_by_key(|(name, _)| *name); - b_pairs.sort_by_key(|(name, _)| *name); - - a_pairs == b_pairs -} - -/// Execute a single tool by name, looking it up in the registry. -async fn execute_single( - registry: &ToolRegistry, - tool_name: &str, - input: Value, -) -> Result { - match registry.get(tool_name) { - Some(tool) => tool.execute(input).await, - None => Err(format!("tool '{}' not found in registry", tool_name)), - } -} - -// --------------------------------------------------------------------------- -// Helper functions for extracting tool calls and building follow-up messages -// --------------------------------------------------------------------------- - -/// Extract ToolCall structs from an Anthropic MessageResponse. -pub fn extract_tool_calls( - response: &anyllm_translate::anthropic::MessageResponse, -) -> Vec { - response - .content - .iter() - .filter_map(|block| { - if let anyllm_translate::anthropic::ContentBlock::ToolUse { id, name, input } = block { - Some(ToolCall { - id: id.clone(), - name: name.clone(), - input: input.clone(), - }) - } else { - None - } - }) - .collect() -} - -/// Convert tool execution results to an Anthropic user message with ToolResult blocks. -pub fn tool_results_to_user_message( - results: &[ToolResult], -) -> anyllm_translate::anthropic::InputMessage { - let blocks: Vec = results - .iter() - .map(|r| { - let (content_text, is_error) = match &r.outcome { - ToolOutcome::Success(v) => (serde_json::to_string(v).unwrap_or_default(), false), - ToolOutcome::Error { message, .. } => (message.clone(), true), - ToolOutcome::Timeout => ("Tool execution timed out".to_string(), true), - }; - anyllm_translate::anthropic::ContentBlock::ToolResult { - tool_use_id: r.tool_use_id.clone(), - content: Some(anyllm_translate::anthropic::ToolResultContent::Text( - content_text, - )), - is_error: Some(is_error), - } - }) - .collect(); - - anyllm_translate::anthropic::InputMessage { - role: anyllm_translate::anthropic::Role::User, - content: anyllm_translate::anthropic::Content::Blocks(blocks), - } -} - -/// Convert a MessageResponse's content into an assistant InputMessage for conversation history. -pub fn response_to_assistant_message( - response: &anyllm_translate::anthropic::MessageResponse, -) -> anyllm_translate::anthropic::InputMessage { - anyllm_translate::anthropic::InputMessage { - role: anyllm_translate::anthropic::Role::Assistant, - content: anyllm_translate::anthropic::Content::Blocks(response.content.clone()), - } -} - -// --------------------------------------------------------------------------- -// Timing wrapper used in the execution loop (available to callers) -// --------------------------------------------------------------------------- - -/// Run `execute_tool_calls` and record wall-clock duration per call. -/// Returns (results, elapsed_per_call). Exposed for loop-level tracing. -pub async fn execute_tool_calls_timed( - calls: &[&ToolCall], - registry: Arc, - policy: &ToolExecutionPolicy, - config: &LoopConfig, -) -> (Vec, Duration) { - let start = Instant::now(); - let results = execute_tool_calls(calls, registry, policy, config).await; - (results, start.elapsed()) -} - -// --------------------------------------------------------------------------- -// Centralized bounded tool-execution loop for non-streaming requests -// --------------------------------------------------------------------------- - -use crate::tools::trace::{IterationTrace, LoopTrace, TerminationReason, ToolCallTrace}; - -/// Engine state needed by `maybe_execute_tools`. Re-exported alias so callers -/// do not need to reach into `server::routes`. -pub use crate::server::state::ToolEngineState; - -/// Process an LLM response for tool execution. If auto-executable tool calls -/// are found, executes them and makes follow-up backend calls in a bounded loop. -/// -/// Returns the final response (original if no tools were auto-executed) and a -/// `LoopTrace` recording what happened. -/// -/// `backend_call` is a closure the caller provides. It takes a -/// `MessageCreateRequest` and returns the translated `MessageResponse`. -/// This keeps the loop backend-agnostic: the handler knows how to translate -/// and call its specific backend; this function only knows about Anthropic types. -/// -/// `guardrails` is the effective guardrail config for this request (the -/// runtime-tunable override applied on top of `engine.guardrails`, the -/// static per-process preset -- see `crate::tools::resolve_runtime_guardrails` -/// and `AppState::effective_tool_guardrails`). Callers that don't need the -/// runtime override can pass `&engine.guardrails` directly. -pub async fn maybe_execute_tools( - engine: &ToolEngineState, - original_req: &anyllm_translate::anthropic::MessageCreateRequest, - server_advertised_tool_names: &HashSet, - initial_response: anyllm_translate::anthropic::MessageResponse, - guardrails: &crate::tools::ToolGuardrailConfig, - backend_call: F, -) -> (anyllm_translate::anthropic::MessageResponse, LoopTrace) -where - F: Fn(anyllm_translate::anthropic::MessageCreateRequest) -> Fut, - Fut: std::future::Future>, -{ - let loop_start = Instant::now(); - let mut iterations: Vec = Vec::new(); - let mut current_response = initial_response; - let mut current_messages = original_req.messages.clone(); - let mut prev_tool_calls: Option> = None; - let mut guardrail_state = ToolGuardrailRequestState::new(); - - for _iteration in 0..engine.loop_config.max_iterations { - // Guard: total timeout - if loop_start.elapsed() > engine.loop_config.total_timeout { - return ( - current_response, - LoopTrace { - iterations, - total_duration: loop_start.elapsed(), - termination_reason: TerminationReason::Timeout, - }, - ); - } - - let tool_calls = extract_tool_calls(¤t_response); - - // Advisory guardrails: each nudge targets one offending call and comes - // back as a retryable error result. Nudged calls are skipped this turn; - // every other call is partitioned and executed as usual. Each distinct - // decision nudges only once per request (across iterations), so a - // model that ignores a nudge and repeats the call lets it proceed - // instead of spinning the loop to max_iterations. Nudges only ever - // apply to calls this proxy would actually execute -- see - // `partition_and_nudge`'s doc comment. - let (auto_exec, nudge_results, denied_results) = partition_and_nudge( - &tool_calls, - original_req.tools.as_deref().unwrap_or(&[]), - &engine.registry, - &engine.policy, - server_advertised_tool_names, - guardrails, - &mut guardrail_state, - ); - - if auto_exec.is_empty() && denied_results.is_empty() && nudge_results.is_empty() { - return ( - current_response, - LoopTrace { - iterations, - total_duration: loop_start.elapsed(), - termination_reason: TerminationReason::NoToolCalls, - }, - ); - } - - // If there is nothing to execute (only nudges and/or denials), send the - // advisory/error results back to the LLM immediately without running a tool. - if auto_exec.is_empty() { - let mut results = nudge_results; - results.extend(denied_results); - current_messages.push(response_to_assistant_message(¤t_response)); - current_messages.push(tool_results_to_user_message(&results)); - let mut follow_up_req = original_req.clone(); - follow_up_req.messages = current_messages.clone(); - let llm_start = Instant::now(); - let traces: Vec = results - .iter() - .map(|r| ToolCallTrace { - tool_name: r.tool_name.clone(), - duration: Duration::ZERO, - outcome: r.outcome.clone(), - }) - .collect(); - iterations.push(IterationTrace { - tool_calls: traces, - llm_latency: Duration::ZERO, - }); - match backend_call(follow_up_req).await { - Ok(resp) => { - if let Some(last) = iterations.last_mut() { - last.llm_latency = llm_start.elapsed(); - } - prev_tool_calls = None; - current_response = resp; - continue; - } - Err(e) => { - tracing::warn!(error = %e, "follow-up backend call failed after nudge/deny"); - if let Some(last) = iterations.last_mut() { - last.llm_latency = llm_start.elapsed(); - } - return ( - current_response, - LoopTrace { - iterations, - total_duration: loop_start.elapsed(), - termination_reason: TerminationReason::NoToolCalls, - }, - ); - } - } - } - - // Guard: duplicate detection (same tool calls as previous iteration) - let auto_calls: Vec = auto_exec.iter().map(|c| (*c).clone()).collect(); - if let Some(ref prev) = prev_tool_calls { - if is_duplicate(prev, &auto_calls) { - return ( - current_response, - LoopTrace { - iterations, - total_duration: loop_start.elapsed(), - termination_reason: TerminationReason::DuplicateDetected, - }, - ); - } - } - - // Execute auto-allowed tools in parallel - let exec_start = Instant::now(); - let mut results = execute_tool_calls( - &auto_exec, - engine.registry.clone(), - &engine.policy, - &engine.loop_config, - ) - .await; - let exec_duration = exec_start.elapsed(); - - // Append nudge + denied-tool error results so the LLM sees all outcomes. - results.extend(nudge_results); - results.extend(denied_results); - - // Build per-tool traces - let tool_traces: Vec = results - .iter() - .map(|r| ToolCallTrace { - tool_name: r.tool_name.clone(), - duration: exec_duration, - outcome: r.outcome.clone(), - }) - .collect(); - - // Guard: all tools failed (includes deny errors, which are non-retryable) - let all_failed = results - .iter() - .all(|r| !matches!(r.outcome, ToolOutcome::Success(_))); - - iterations.push(IterationTrace { - tool_calls: tool_traces, - llm_latency: Duration::ZERO, // filled after backend call below - }); - - if all_failed { - return ( - current_response, - LoopTrace { - iterations, - total_duration: loop_start.elapsed(), - termination_reason: TerminationReason::AllToolsFailed, - }, - ); - } - - // Build follow-up: append assistant response + tool results to conversation - current_messages.push(response_to_assistant_message(¤t_response)); - current_messages.push(tool_results_to_user_message(&results)); - - let mut follow_up_req = original_req.clone(); - follow_up_req.messages = current_messages.clone(); - - // Call backend via caller-provided closure - let llm_start = Instant::now(); - match backend_call(follow_up_req).await { - Ok(resp) => { - if let Some(last) = iterations.last_mut() { - last.llm_latency = llm_start.elapsed(); - } - tracing::info!( - tools_executed = results.len(), - iteration = _iteration + 1, - "tool execution loop: iteration complete" - ); - prev_tool_calls = Some(auto_calls); - current_response = resp; - } - Err(e) => { - tracing::warn!( - error = %e, - "follow-up backend call failed, returning last good response" - ); - if let Some(last) = iterations.last_mut() { - last.llm_latency = llm_start.elapsed(); - } - return ( - current_response, - LoopTrace { - iterations, - total_duration: loop_start.elapsed(), - // Backend error is not a clean termination; closest match is NoToolCalls - // since we are stopping the loop and returning what we have. - termination_reason: TerminationReason::NoToolCalls, - }, - ); - } - } - } - - // Exhausted max_iterations - ( - current_response, - LoopTrace { - iterations, - total_duration: loop_start.elapsed(), - termination_reason: TerminationReason::MaxIterations, - }, - ) -} - #[cfg(test)] mod tests; diff --git a/crates/proxy/src/tools/execution/runner.rs b/crates/proxy/src/tools/execution/runner.rs new file mode 100644 index 0000000..1127ccc --- /dev/null +++ b/crates/proxy/src/tools/execution/runner.rs @@ -0,0 +1,373 @@ +//! Core async tool-execution runners and the centralized bounded tool loop +//! for non-streaming requests. + +use std::collections::HashSet; +use std::sync::Arc; +use std::time::{Duration, Instant}; + +use serde_json::Value; +use tokio::task::JoinSet; + +use super::{ + extract_tool_calls, partition_and_nudge, response_to_assistant_message, + tool_results_to_user_message, LoopConfig, ToolCall, ToolEngineState, ToolResult, +}; +use crate::tools::policy::ToolExecutionPolicy; +use crate::tools::registry::ToolRegistry; +use crate::tools::trace::{ + IterationTrace, LoopTrace, TerminationReason, ToolCallTrace, ToolOutcome, +}; +use crate::tools::ToolGuardrailRequestState; + +/// Execute tool calls in parallel, respecting per-tool timeouts. +/// +/// Results are returned in the same order as `calls`. +pub async fn execute_tool_calls( + calls: &[&ToolCall], + registry: Arc, + policy: &ToolExecutionPolicy, + config: &LoopConfig, +) -> Vec { + let capped = &calls[..calls.len().min(config.max_tool_calls_per_turn)]; + + // Collect (original_index, ToolCall) to restore order after parallel execution. + let indexed: Vec<(usize, &ToolCall)> = capped.iter().copied().enumerate().collect(); + + let mut join_set: JoinSet<(usize, ToolResult)> = JoinSet::new(); + + for (idx, call) in indexed { + let timeout = policy + .find_rule(&call.name) + .and_then(|r| r.timeout) + .unwrap_or(config.tool_timeout); + + let registry = Arc::clone(®istry); + let id = call.id.clone(); + let name = call.name.clone(); + let input = call.input.clone(); + + join_set.spawn(async move { + let result = + tokio::time::timeout(timeout, execute_single(®istry, &name, input)).await; + + let outcome = match result { + Ok(Ok(value)) => ToolOutcome::Success(value), + Ok(Err(msg)) => ToolOutcome::Error { + message: msg, + retryable: false, + }, + Err(_elapsed) => ToolOutcome::Timeout, + }; + + ( + idx, + ToolResult { + tool_use_id: id, + tool_name: name, + outcome, + }, + ) + }); + } + + let mut collected: Vec<(usize, ToolResult)> = Vec::with_capacity(capped.len()); + while let Some(res) = join_set.join_next().await { + match res { + Ok(pair) => collected.push(pair), + Err(e) => { + // JoinError means the task panicked; treat as an error outcome. + // We don't have the index here, so we skip (shouldn't happen in practice). + tracing::error!("tool execution task panicked: {e}"); + } + } + } + + collected.sort_by_key(|(idx, _)| *idx); + collected.into_iter().map(|(_, r)| r).collect() +} + +/// Check whether two slices of ToolCall represent the same logical calls. +/// +/// Same length, same multiset of (name, input) pairs. IDs are ignored. +pub fn is_duplicate(a: &[ToolCall], b: &[ToolCall]) -> bool { + if a.len() != b.len() { + return false; + } + + let mut a_pairs: Vec<(&str, &Value)> = a.iter().map(|c| (c.name.as_str(), &c.input)).collect(); + let mut b_pairs: Vec<(&str, &Value)> = b.iter().map(|c| (c.name.as_str(), &c.input)).collect(); + + // Sort by name so comparison is order-independent. + a_pairs.sort_by_key(|(name, _)| *name); + b_pairs.sort_by_key(|(name, _)| *name); + + a_pairs == b_pairs +} + +/// Execute a single tool by name, looking it up in the registry. +async fn execute_single( + registry: &ToolRegistry, + tool_name: &str, + input: Value, +) -> Result { + match registry.get(tool_name) { + Some(tool) => tool.execute(input).await, + None => Err(format!("tool '{}' not found in registry", tool_name)), + } +} + +/// Run `execute_tool_calls` and record wall-clock duration per call. +/// Returns (results, elapsed_per_call). Exposed for loop-level tracing. +pub async fn execute_tool_calls_timed( + calls: &[&ToolCall], + registry: Arc, + policy: &ToolExecutionPolicy, + config: &LoopConfig, +) -> (Vec, Duration) { + let start = Instant::now(); + let results = execute_tool_calls(calls, registry, policy, config).await; + (results, start.elapsed()) +} + +/// Process an LLM response for tool execution. If auto-executable tool calls +/// are found, executes them and makes follow-up backend calls in a bounded loop. +/// +/// Returns the final response (original if no tools were auto-executed) and a +/// `LoopTrace` recording what happened. +/// +/// `backend_call` is a closure the caller provides. It takes a +/// `MessageCreateRequest` and returns the translated `MessageResponse`. +/// This keeps the loop backend-agnostic: the handler knows how to translate +/// and call its specific backend; this function only knows about Anthropic types. +/// +/// `guardrails` is the effective guardrail config for this request (the +/// runtime-tunable override applied on top of `engine.guardrails`, the +/// static per-process preset -- see `crate::tools::resolve_runtime_guardrails` +/// and `AppState::effective_tool_guardrails`). Callers that don't need the +/// runtime override can pass `&engine.guardrails` directly. +pub async fn maybe_execute_tools( + engine: &ToolEngineState, + original_req: &anyllm_translate::anthropic::MessageCreateRequest, + server_advertised_tool_names: &HashSet, + initial_response: anyllm_translate::anthropic::MessageResponse, + guardrails: &crate::tools::ToolGuardrailConfig, + backend_call: F, +) -> (anyllm_translate::anthropic::MessageResponse, LoopTrace) +where + F: Fn(anyllm_translate::anthropic::MessageCreateRequest) -> Fut, + Fut: std::future::Future>, +{ + let loop_start = Instant::now(); + let mut iterations: Vec = Vec::new(); + let mut current_response = initial_response; + let mut current_messages = original_req.messages.clone(); + let mut prev_tool_calls: Option> = None; + let mut guardrail_state = ToolGuardrailRequestState::new(); + + for _iteration in 0..engine.loop_config.max_iterations { + // Guard: total timeout + if loop_start.elapsed() > engine.loop_config.total_timeout { + return ( + current_response, + LoopTrace { + iterations, + total_duration: loop_start.elapsed(), + termination_reason: TerminationReason::Timeout, + }, + ); + } + + let tool_calls = extract_tool_calls(¤t_response); + + // Advisory guardrails: each nudge targets one offending call and comes + // back as a retryable error result. Nudged calls are skipped this turn; + // every other call is partitioned and executed as usual. Each distinct + // decision nudges only once per request (across iterations), so a + // model that ignores a nudge and repeats the call lets it proceed + // instead of spinning the loop to max_iterations. Nudges only ever + // apply to calls this proxy would actually execute -- see + // `partition_and_nudge`'s doc comment. + let (auto_exec, nudge_results, denied_results) = partition_and_nudge( + &tool_calls, + original_req.tools.as_deref().unwrap_or(&[]), + &engine.registry, + &engine.policy, + server_advertised_tool_names, + guardrails, + &mut guardrail_state, + ); + + if auto_exec.is_empty() && denied_results.is_empty() && nudge_results.is_empty() { + return ( + current_response, + LoopTrace { + iterations, + total_duration: loop_start.elapsed(), + termination_reason: TerminationReason::NoToolCalls, + }, + ); + } + + // If there is nothing to execute (only nudges and/or denials), send the + // advisory/error results back to the LLM immediately without running a tool. + if auto_exec.is_empty() { + let mut results = nudge_results; + results.extend(denied_results); + current_messages.push(response_to_assistant_message(¤t_response)); + current_messages.push(tool_results_to_user_message(&results)); + let mut follow_up_req = original_req.clone(); + follow_up_req.messages = current_messages.clone(); + let llm_start = Instant::now(); + let traces: Vec = results + .iter() + .map(|r| ToolCallTrace { + tool_name: r.tool_name.clone(), + duration: Duration::ZERO, + outcome: r.outcome.clone(), + }) + .collect(); + iterations.push(IterationTrace { + tool_calls: traces, + llm_latency: Duration::ZERO, + }); + match backend_call(follow_up_req).await { + Ok(resp) => { + if let Some(last) = iterations.last_mut() { + last.llm_latency = llm_start.elapsed(); + } + prev_tool_calls = None; + current_response = resp; + continue; + } + Err(e) => { + tracing::warn!(error = %e, "follow-up backend call failed after nudge/deny"); + if let Some(last) = iterations.last_mut() { + last.llm_latency = llm_start.elapsed(); + } + return ( + current_response, + LoopTrace { + iterations, + total_duration: loop_start.elapsed(), + termination_reason: TerminationReason::NoToolCalls, + }, + ); + } + } + } + + // Guard: duplicate detection (same tool calls as previous iteration) + let auto_calls: Vec = auto_exec.iter().map(|c| (*c).clone()).collect(); + if let Some(ref prev) = prev_tool_calls { + if is_duplicate(prev, &auto_calls) { + return ( + current_response, + LoopTrace { + iterations, + total_duration: loop_start.elapsed(), + termination_reason: TerminationReason::DuplicateDetected, + }, + ); + } + } + + // Execute auto-allowed tools in parallel + let exec_start = Instant::now(); + let mut results = execute_tool_calls( + &auto_exec, + engine.registry.clone(), + &engine.policy, + &engine.loop_config, + ) + .await; + let exec_duration = exec_start.elapsed(); + + // Append nudge + denied-tool error results so the LLM sees all outcomes. + results.extend(nudge_results); + results.extend(denied_results); + + // Build per-tool traces + let tool_traces: Vec = results + .iter() + .map(|r| ToolCallTrace { + tool_name: r.tool_name.clone(), + duration: exec_duration, + outcome: r.outcome.clone(), + }) + .collect(); + + // Guard: all tools failed (includes deny errors, which are non-retryable) + let all_failed = results + .iter() + .all(|r| !matches!(r.outcome, ToolOutcome::Success(_))); + + iterations.push(IterationTrace { + tool_calls: tool_traces, + llm_latency: Duration::ZERO, // filled after backend call below + }); + + if all_failed { + return ( + current_response, + LoopTrace { + iterations, + total_duration: loop_start.elapsed(), + termination_reason: TerminationReason::AllToolsFailed, + }, + ); + } + + // Build follow-up: append assistant response + tool results to conversation + current_messages.push(response_to_assistant_message(¤t_response)); + current_messages.push(tool_results_to_user_message(&results)); + + let mut follow_up_req = original_req.clone(); + follow_up_req.messages = current_messages.clone(); + + // Call backend via caller-provided closure + let llm_start = Instant::now(); + match backend_call(follow_up_req).await { + Ok(resp) => { + if let Some(last) = iterations.last_mut() { + last.llm_latency = llm_start.elapsed(); + } + tracing::info!( + tools_executed = results.len(), + iteration = _iteration + 1, + "tool execution loop: iteration complete" + ); + prev_tool_calls = Some(auto_calls); + current_response = resp; + } + Err(e) => { + tracing::warn!( + error = %e, + "follow-up backend call failed, returning last good response" + ); + if let Some(last) = iterations.last_mut() { + last.llm_latency = llm_start.elapsed(); + } + return ( + current_response, + LoopTrace { + iterations, + total_duration: loop_start.elapsed(), + // Backend error is not a clean termination; closest match is NoToolCalls + // since we are stopping the loop and returning what we have. + termination_reason: TerminationReason::NoToolCalls, + }, + ); + } + } + } + + // Exhausted max_iterations + ( + current_response, + LoopTrace { + iterations, + total_duration: loop_start.elapsed(), + termination_reason: TerminationReason::MaxIterations, + }, + ) +} diff --git a/crates/proxy/src/tools/execution/tests.rs b/crates/proxy/src/tools/execution/tests.rs index 17afddc..09ff3a7 100644 --- a/crates/proxy/src/tools/execution/tests.rs +++ b/crates/proxy/src/tools/execution/tests.rs @@ -2,6 +2,7 @@ use super::*; use crate::tools::policy::{PolicyAction, PolicyRule, ToolExecutionPolicy}; use crate::tools::registry::ToolRegistry; use serde_json::json; +use std::collections::HashSet; use std::future::Future; use std::pin::Pin; use std::sync::Arc; diff --git a/crates/proxy/src/tools/guardrails.rs b/crates/proxy/src/tools/guardrails.rs index 908223e..7fda84d 100644 --- a/crates/proxy/src/tools/guardrails.rs +++ b/crates/proxy/src/tools/guardrails.rs @@ -6,9 +6,15 @@ use crate::tools::execution::ToolCall; use anyllm_translate::anthropic::Tool; -use serde_json::{Map, Value}; use std::str::FromStr; +mod lsp; +mod quiet; +#[cfg(test)] +mod tests; +mod utils; +mod write_cap; + /// Default maximum string payload size for write/edit policy nudges. pub const DEFAULT_MAX_WRITE_PAYLOAD_BYTES: usize = 64 * 1024; @@ -207,7 +213,7 @@ pub fn evaluate_tool_guardrails( } let lsp_tools = if config.lsp_first { - available_lsp_tools(tool_specs) + lsp::available_lsp_tools(tool_specs) } else { Vec::new() }; @@ -216,18 +222,20 @@ pub fn evaluate_tool_guardrails( let mut nudged_fingerprints_this_batch: indexmap::IndexSet = indexmap::IndexSet::new(); for call in tool_calls { let candidate = (!lsp_tools.is_empty()) - .then(|| lsp_first_nudge(call, &lsp_tools)) + .then(|| lsp::lsp_first_nudge(call, &lsp_tools)) .flatten() .or_else(|| { config .write_payload_caps - .then(|| write_payload_cap_nudge(call, config.max_write_payload_bytes)) + .then(|| { + write_cap::write_payload_cap_nudge(call, config.max_write_payload_bytes) + }) .flatten() }) .or_else(|| { config .quiet_commands - .then(|| quiet_command_nudge(call)) + .then(|| quiet::quiet_command_nudge(call)) .flatten() }); @@ -246,515 +254,3 @@ pub fn evaluate_tool_guardrails( nudges } - -fn available_lsp_tools(tool_specs: &[Tool]) -> Vec { - let supported = [ - "find_definition", - "find_references", - "get_hover", - "document_symbols", - "workspace_symbols", - ]; - tool_specs - .iter() - .filter(|tool| supported.contains(&tool.name.as_str())) - .map(|tool| tool.name.clone()) - .collect() -} - -fn lsp_first_nudge(call: &ToolCall, lsp_tools: &[String]) -> Option { - let tool_name = call.name.to_ascii_lowercase(); - let args = object_args(&call.input); - let symbol = if is_shell_tool(&tool_name) { - shell_grep_symbol(command_arg(args?)?)? - } else if is_grep_tool(&tool_name) { - string_arg( - args?, - &["symbol", "name", "pattern", "query", "regex", "needle"], - ) - .and_then(symbol_from_search_value)? - } else if is_glob_tool(&tool_name) { - string_arg(args?, &["pattern", "query", "glob"]).and_then(symbol_from_search_value)? - } else { - return None; - }; - - let tools = lsp_tools.join(", "); - let fingerprint = format!("lsp_first:{}:{symbol}", call.name); - Some(ToolGuardrailNudge { - call_id: call.id.clone(), - kind: "lsp_first", - content: format!( - "Use available LSP tools for symbol lookup instead of grep/glob/shell search. Available LSP tools: {tools}. Retry with the best matching LSP tool for `{symbol}`." - ), - fingerprint, - }) -} - -fn quiet_command_nudge(call: &ToolCall) -> Option { - let tool_name = call.name.to_ascii_lowercase(); - if !is_shell_tool(&tool_name) { - return None; - } - let command = command_arg(object_args(&call.input)?)?.trim(); - let suggestion = quiet_command_suggestion(command)?; - let fingerprint = format!("quiet:{}:{command}:{suggestion}", call.name); - Some(ToolGuardrailNudge { - call_id: call.id.clone(), - kind: "quiet_command", - content: format!( - "The requested shell command is likely to produce noisy output. Prefer `{suggestion}`. Repeat the original command only if verbose output is required." - ), - fingerprint, - }) -} - -fn write_payload_cap_nudge(call: &ToolCall, max_bytes: usize) -> Option { - // A cap of 0 disables the nudge rather than firing on every non-empty write. - if max_bytes == 0 { - return None; - } - let tool_name = call.name.to_ascii_lowercase(); - if !is_write_or_edit_tool(&tool_name) { - return None; - } - let bytes = object_args(&call.input) - .map(write_payload_bytes) - .unwrap_or(0); - if bytes <= max_bytes { - return None; - } - Some(ToolGuardrailNudge { - call_id: call.id.clone(), - kind: "write_payload_cap", - content: format!( - "The requested write/edit payload is too large for this proxy policy ({bytes} bytes > {max_bytes} bytes). Retry with a smaller targeted edit or split the change." - ), - // Include a hash of the actual payload, not just its byte length, so - // two unrelated oversized writes that happen to be the same size - // (e.g. same-size templated files) don't collide and suppress each - // other's nudge. Identical repeated calls (same target, same - // content) still dedupe as intended. - fingerprint: format!( - "write_payload_cap:{}:{:x}:{bytes}:{max_bytes}", - call.name, - payload_content_hash(&call.input) - ), - }) -} - -fn payload_content_hash(value: &Value) -> u64 { - use std::hash::{Hash, Hasher}; - let mut hasher = std::collections::hash_map::DefaultHasher::new(); - value.to_string().hash(&mut hasher); - hasher.finish() -} - -fn is_shell_tool(name: &str) -> bool { - matches!( - name, - "bash" | "shell" | "run_command" | "execute_command" | "terminal" | "exec" | "execute_bash" - ) -} - -fn is_grep_tool(name: &str) -> bool { - matches!(name, "grep" | "rg" | "ripgrep" | "st") -} - -fn is_glob_tool(name: &str) -> bool { - matches!(name, "glob" | "find_files" | "file_glob") -} - -fn is_write_or_edit_tool(name: &str) -> bool { - matches!( - name, - "write" - | "write_file" - | "edit" - | "edit_file" - | "replace" - | "apply_patch" - | "create_file" - | "update_file" - ) -} - -fn object_args(value: &Value) -> Option<&Map> { - value.as_object() -} - -fn command_arg(args: &Map) -> Option<&str> { - string_arg(args, &["command", "cmd", "shell_command", "input"]) -} - -fn string_arg<'a>(args: &'a Map, keys: &[&str]) -> Option<&'a str> { - // Only the named keys, in order. Do NOT fall back to an arbitrary string - // value: that would let an unrelated field be misread as the search symbol - // or command for an advisory nudge. - keys.iter() - .find_map(|key| args.get(*key).and_then(Value::as_str)) -} - -fn shell_grep_symbol(command: &str) -> Option { - let mut parts = command.split_whitespace(); - let binary = strip_shell_quotes(parts.next()?) - .rsplit('/') - .next()? - .to_string(); - if !matches!(binary.as_str(), "rg" | "ripgrep" | "grep" | "st") { - return None; - } - - let mut previous_took_value = false; - for part in parts { - let token = strip_shell_quotes(part); - if previous_took_value { - previous_took_value = false; - continue; - } - if token.starts_with("--") { - previous_took_value = matches!( - token.as_str(), - "--glob" | "--type" | "--context" | "--after-context" | "--before-context" - ); - continue; - } - if token.starts_with('-') { - continue; - } - return symbol_from_search_value(&token); - } - None -} - -fn symbol_from_search_value(value: &str) -> Option { - let trimmed = strip_shell_quotes(value) - .trim_matches('/') - .replace("\\b", "") - .replace(['^', '$'], ""); - for token in trimmed.split(|ch: char| !(ch.is_ascii_alphanumeric() || ch == '_')) { - if looks_like_symbol_token(token) { - return Some(token.to_string()); - } - } - None -} - -fn looks_like_symbol_token(token: &str) -> bool { - if token.len() < 3 { - return false; - } - let mut chars = token.chars(); - let Some(first) = chars.next() else { - return false; - }; - if !(first.is_ascii_alphabetic() || first == '_') { - return false; - } - if !chars.all(|ch| ch.is_ascii_alphanumeric() || ch == '_') { - return false; - } - - let lower = token.to_ascii_lowercase(); - if matches!( - lower.as_str(), - "todo" | "fixme" | "error" | "warning" | "debug" | "test" | "src" | "main" - ) { - return false; - } - token.contains('_') || token.chars().any(|ch| ch.is_ascii_uppercase()) -} - -fn strip_shell_quotes(value: &str) -> String { - value - .trim() - .trim_matches('"') - .trim_matches('\'') - .to_string() -} - -fn quiet_command_suggestion(command: &str) -> Option { - if command_has_prefix(command, "git log") && !contains_word(command, "--oneline") { - return Some(insert_after_prefix(command, "git log", "--oneline")); - } - for prefix in ["cargo build", "cargo check", "cargo clippy", "cargo test"] { - if command_has_prefix(command, prefix) && !contains_word(command, "--quiet") { - return Some(insert_after_prefix(command, prefix, "--quiet")); - } - } - if command_has_prefix(command, "pytest") && !contains_word(command, "-q") { - return Some(insert_after_prefix(command, "pytest", "-q")); - } - if command_has_prefix(command, "npm install") && !contains_word(command, "--silent") { - return Some(insert_after_prefix(command, "npm install", "--silent")); - } - if command_has_prefix(command, "pip install") && !contains_word(command, "--quiet") { - return Some(insert_after_prefix(command, "pip install", "--quiet")); - } - if command_has_prefix(command, "docker build") && !contains_word(command, "--progress=quiet") { - return Some(insert_after_prefix( - command, - "docker build", - "--progress=quiet", - )); - } - if command_has_prefix(command, "curl") && !contains_word(command, "-s") { - return Some(insert_after_prefix(command, "curl", "-s")); - } - if command_has_prefix(command, "make") && !contains_word(command, "-s") { - return Some(insert_after_prefix(command, "make", "-s")); - } - if command_has_prefix(command, "tree") && !contains_word(command, "-I") { - return Some(insert_after_prefix( - command, - "tree", - "-I \"node_modules|.git|target|dist|build\"", - )); - } - None -} - -fn command_has_prefix(command: &str, prefix: &str) -> bool { - command == prefix || command.starts_with(&format!("{prefix} ")) -} - -fn contains_word(command: &str, word: &str) -> bool { - command.split_whitespace().any(|part| part == word) -} - -fn insert_after_prefix(command: &str, prefix: &str, insertion: &str) -> String { - let rest = command[prefix.len()..].trim_start(); - if rest.is_empty() { - format!("{prefix} {insertion}") - } else { - format!("{prefix} {insertion} {rest}") - } -} - -/// Depth cap for `value_payload_bytes`'s recursion. Model-controlled JSON has -/// no natural nesting limit; a real write/edit payload never needs anywhere -/// near this depth, so hitting it is itself a signal the payload is oversized. -const MAX_PAYLOAD_DEPTH: usize = 32; - -fn write_payload_bytes(args: &Map) -> usize { - let payload_keys = [ - "content", - "text", - "new_content", - "patch", - "diff", - "replacement", - "data", - "old_string", - "new_string", - ]; - let known = args - .iter() - .filter(|(key, _)| payload_keys.contains(&key.as_str())) - .fold(0usize, |acc, (_, value)| { - acc.saturating_add(value_payload_bytes(value, 0)) - }); - if known > 0 { - return known; - } - // None of the known field names matched (a tool with a non-standard - // schema, e.g. `body`/`value`) -- fall back to summing every value in - // the payload rather than silently reporting zero and never capping it. - args.values().fold(0usize, |acc, value| { - acc.saturating_add(value_payload_bytes(value, 0)) - }) -} - -fn value_payload_bytes(value: &Value, depth: usize) -> usize { - if depth >= MAX_PAYLOAD_DEPTH { - // Treat implausibly deep nesting as oversized rather than recursing - // further; a fixed large sentinel (not usize::MAX) keeps the - // saturating sum below safe from overflowing back to a small value. - return 1_000_000_000; - } - match value { - Value::String(value) => value.len(), - Value::Array(values) => values.iter().fold(0usize, |acc, v| { - acc.saturating_add(value_payload_bytes(v, depth + 1)) - }), - Value::Object(values) => values.values().fold(0usize, |acc, v| { - acc.saturating_add(value_payload_bytes(v, depth + 1)) - }), - _ => 0, - } -} - -#[cfg(test)] -mod tests { - use super::*; - use serde_json::json; - - fn tool_spec(name: &str) -> Tool { - Tool { - name: name.to_string(), - description: None, - input_schema: json!({"type": "object"}), - } - } - - fn call(name: &str, input: Value) -> ToolCall { - ToolCall { - id: "toolu_1".to_string(), - name: name.to_string(), - input, - } - } - - #[test] - fn lsp_nudge_requires_replacement_tool() { - let mut state = ToolGuardrailRequestState::new(); - let config = ToolGuardrailConfig { - lsp_first: true, - ..ToolGuardrailConfig::disabled() - }; - let calls = vec![call("grep", json!({"pattern": "UserService"}))]; - - assert!( - evaluate_tool_guardrails(&calls, &[tool_spec("grep")], &config, &mut state).is_empty() - ); - - let nudges = evaluate_tool_guardrails( - &calls, - &[tool_spec("grep"), tool_spec("find_definition")], - &config, - &mut state, - ); - assert_eq!(nudges.len(), 1); - let nudge = &nudges[0]; - assert_eq!(nudge.kind, "lsp_first"); - assert_eq!(nudge.call_id, "toolu_1"); - assert!(nudge.content.contains("find_definition")); - assert!(nudge.content.contains("UserService")); - } - - #[test] - fn quiet_command_nudges_once() { - let mut state = ToolGuardrailRequestState::new(); - let config = ToolGuardrailConfig { - quiet_commands: true, - ..ToolGuardrailConfig::disabled() - }; - let calls = vec![call("bash", json!({"command": "cargo test"}))]; - let first = evaluate_tool_guardrails(&calls, &[], &config, &mut state); - assert_eq!(first.len(), 1); - assert_eq!(first[0].kind, "quiet_command"); - assert!(first[0].content.contains("cargo test --quiet")); - assert!(evaluate_tool_guardrails(&calls, &[], &config, &mut state).is_empty()); - } - - #[test] - fn only_offending_call_is_nudged() { - let mut state = ToolGuardrailRequestState::new(); - let config = ToolGuardrailConfig { - lsp_first: true, - ..ToolGuardrailConfig::disabled() - }; - let calls = vec![ - ToolCall { - id: "toolu_grep".into(), - name: "grep".into(), - input: json!({"pattern": "UserService"}), - }, - ToolCall { - id: "toolu_write".into(), - name: "write_file".into(), - input: json!({"content": "ok"}), - }, - ]; - let nudges = evaluate_tool_guardrails( - &calls, - &[tool_spec("grep"), tool_spec("find_definition")], - &config, - &mut state, - ); - assert_eq!(nudges.len(), 1); - assert_eq!(nudges[0].call_id, "toolu_grep"); - } - - #[test] - fn write_payload_cap_detects_oversized_payload() { - let mut state = ToolGuardrailRequestState::new(); - let config = ToolGuardrailConfig { - write_payload_caps: true, - max_write_payload_bytes: 4, - ..ToolGuardrailConfig::disabled() - }; - let calls = vec![call("write_file", json!({"content": "12345"}))]; - let nudges = evaluate_tool_guardrails(&calls, &[], &config, &mut state); - assert_eq!(nudges.len(), 1); - assert_eq!(nudges[0].kind, "write_payload_cap"); - assert!(nudges[0].content.contains("5 bytes > 4 bytes")); - } - - #[test] - fn write_payload_cap_zero_disables_nudge() { - let mut state = ToolGuardrailRequestState::new(); - let config = ToolGuardrailConfig { - write_payload_caps: true, - max_write_payload_bytes: 0, - ..ToolGuardrailConfig::disabled() - }; - let calls = vec![call("write_file", json!({"content": "12345"}))]; - assert!(evaluate_tool_guardrails(&calls, &[], &config, &mut state).is_empty()); - } - - #[test] - fn write_payload_cap_does_not_collide_on_equal_length_different_content() { - let mut state = ToolGuardrailRequestState::new(); - let config = ToolGuardrailConfig { - write_payload_caps: true, - max_write_payload_bytes: 4, - ..ToolGuardrailConfig::disabled() - }; - // Same byte length (5), different content/target -- both must nudge. - let first = vec![call("write_file", json!({"content": "aaaaa"}))]; - let second = vec![call("write_file", json!({"content": "bbbbb"}))]; - assert_eq!( - evaluate_tool_guardrails(&first, &[], &config, &mut state).len(), - 1 - ); - assert_eq!( - evaluate_tool_guardrails(&second, &[], &config, &mut state).len(), - 1, - "an unrelated oversized write of equal byte length must still be nudged" - ); - } - - #[test] - fn write_payload_bytes_falls_back_to_unrecognized_field_names() { - let args = json!({"body": "12345"}); - assert_eq!(write_payload_bytes(args.as_object().unwrap()), 5); - } - - #[test] - fn resolve_runtime_guardrails_prefers_runtime_override() { - let static_config = ToolGuardrailConfig::disabled(); - let resolved = resolve_runtime_guardrails(&static_config, "standard"); - assert_eq!(resolved, ToolGuardrailConfig::standard()); - } - - #[test] - fn resolve_runtime_guardrails_keeps_static_when_modes_match() { - let static_config = ToolGuardrailConfig { - max_write_payload_bytes: 123, - ..ToolGuardrailConfig::standard() - }; - let resolved = resolve_runtime_guardrails(&static_config, "standard"); - // Same mode as the static preset -- the static config (with its - // custom max_write_payload_bytes) must be preserved, not rebuilt - // from the bare preset. - assert_eq!(resolved, static_config); - } - - #[test] - fn resolve_runtime_guardrails_falls_back_on_unparseable_value() { - let static_config = ToolGuardrailConfig::standard(); - let resolved = resolve_runtime_guardrails(&static_config, "not-a-real-mode"); - assert_eq!(resolved, static_config); - } -} diff --git a/crates/proxy/src/tools/guardrails/lsp.rs b/crates/proxy/src/tools/guardrails/lsp.rs new file mode 100644 index 0000000..1a49dc7 --- /dev/null +++ b/crates/proxy/src/tools/guardrails/lsp.rs @@ -0,0 +1,48 @@ +use super::utils::*; +use super::ToolGuardrailNudge; +use crate::tools::execution::ToolCall; +use anyllm_translate::anthropic::Tool; + +pub(super) fn available_lsp_tools(tool_specs: &[Tool]) -> Vec { + let supported = [ + "find_definition", + "find_references", + "get_hover", + "document_symbols", + "workspace_symbols", + ]; + tool_specs + .iter() + .filter(|tool| supported.contains(&tool.name.as_str())) + .map(|tool| tool.name.clone()) + .collect() +} + +pub(super) fn lsp_first_nudge(call: &ToolCall, lsp_tools: &[String]) -> Option { + let tool_name = call.name.to_ascii_lowercase(); + let args = object_args(&call.input); + let symbol = if is_shell_tool(&tool_name) { + shell_grep_symbol(command_arg(args?)?)? + } else if is_grep_tool(&tool_name) { + string_arg( + args?, + &["symbol", "name", "pattern", "query", "regex", "needle"], + ) + .and_then(symbol_from_search_value)? + } else if is_glob_tool(&tool_name) { + string_arg(args?, &["pattern", "query", "glob"]).and_then(symbol_from_search_value)? + } else { + return None; + }; + + let tools = lsp_tools.join(", "); + let fingerprint = format!("lsp_first:{}:{symbol}", call.name); + Some(ToolGuardrailNudge { + call_id: call.id.clone(), + kind: "lsp_first", + content: format!( + "Use available LSP tools for symbol lookup instead of grep/glob/shell search. Available LSP tools: {tools}. Retry with the best matching LSP tool for `{symbol}`." + ), + fingerprint, + }) +} diff --git a/crates/proxy/src/tools/guardrails/quiet.rs b/crates/proxy/src/tools/guardrails/quiet.rs new file mode 100644 index 0000000..7d8d4f3 --- /dev/null +++ b/crates/proxy/src/tools/guardrails/quiet.rs @@ -0,0 +1,79 @@ +use super::utils::*; +use super::ToolGuardrailNudge; +use crate::tools::execution::ToolCall; + +pub(super) fn quiet_command_nudge(call: &ToolCall) -> Option { + let tool_name = call.name.to_ascii_lowercase(); + if !is_shell_tool(&tool_name) { + return None; + } + let command = command_arg(object_args(&call.input)?)?.trim(); + let suggestion = quiet_command_suggestion(command)?; + let fingerprint = format!("quiet:{}:{command}:{suggestion}", call.name); + Some(ToolGuardrailNudge { + call_id: call.id.clone(), + kind: "quiet_command", + content: format!( + "The requested shell command is likely to produce noisy output. Prefer `{suggestion}`. Repeat the original command only if verbose output is required." + ), + fingerprint, + }) +} + +fn quiet_command_suggestion(command: &str) -> Option { + if command_has_prefix(command, "git log") && !contains_word(command, "--oneline") { + return Some(insert_after_prefix(command, "git log", "--oneline")); + } + for prefix in ["cargo build", "cargo check", "cargo clippy", "cargo test"] { + if command_has_prefix(command, prefix) && !contains_word(command, "--quiet") { + return Some(insert_after_prefix(command, prefix, "--quiet")); + } + } + if command_has_prefix(command, "pytest") && !contains_word(command, "-q") { + return Some(insert_after_prefix(command, "pytest", "-q")); + } + if command_has_prefix(command, "npm install") && !contains_word(command, "--silent") { + return Some(insert_after_prefix(command, "npm install", "--silent")); + } + if command_has_prefix(command, "pip install") && !contains_word(command, "--quiet") { + return Some(insert_after_prefix(command, "pip install", "--quiet")); + } + if command_has_prefix(command, "docker build") && !contains_word(command, "--progress=quiet") { + return Some(insert_after_prefix( + command, + "docker build", + "--progress=quiet", + )); + } + if command_has_prefix(command, "curl") && !contains_word(command, "-s") { + return Some(insert_after_prefix(command, "curl", "-s")); + } + if command_has_prefix(command, "make") && !contains_word(command, "-s") { + return Some(insert_after_prefix(command, "make", "-s")); + } + if command_has_prefix(command, "tree") && !contains_word(command, "-I") { + return Some(insert_after_prefix( + command, + "tree", + "-I \"node_modules|.git|target|dist|build\"", + )); + } + None +} + +fn command_has_prefix(command: &str, prefix: &str) -> bool { + command == prefix || command.starts_with(&format!("{prefix} ")) +} + +fn contains_word(command: &str, word: &str) -> bool { + command.split_whitespace().any(|part| part == word) +} + +fn insert_after_prefix(command: &str, prefix: &str, insertion: &str) -> String { + let rest = command[prefix.len()..].trim_start(); + if rest.is_empty() { + format!("{prefix} {insertion}") + } else { + format!("{prefix} {insertion} {rest}") + } +} diff --git a/crates/proxy/src/tools/guardrails/tests.rs b/crates/proxy/src/tools/guardrails/tests.rs new file mode 100644 index 0000000..14d1ce3 --- /dev/null +++ b/crates/proxy/src/tools/guardrails/tests.rs @@ -0,0 +1,171 @@ +use super::*; +use crate::tools::execution::ToolCall; +use anyllm_translate::anthropic::Tool; +use serde_json::{json, Value}; + +fn tool_spec(name: &str) -> Tool { + Tool { + name: name.to_string(), + description: None, + input_schema: json!({"type": "object"}), + } +} + +fn call(name: &str, input: Value) -> ToolCall { + ToolCall { + id: "toolu_1".to_string(), + name: name.to_string(), + input, + } +} + +#[test] +fn lsp_nudge_requires_replacement_tool() { + let mut state = ToolGuardrailRequestState::new(); + let config = ToolGuardrailConfig { + lsp_first: true, + ..ToolGuardrailConfig::disabled() + }; + let calls = vec![call("grep", json!({"pattern": "UserService"}))]; + + assert!(evaluate_tool_guardrails(&calls, &[tool_spec("grep")], &config, &mut state).is_empty()); + + let nudges = evaluate_tool_guardrails( + &calls, + &[tool_spec("grep"), tool_spec("find_definition")], + &config, + &mut state, + ); + assert_eq!(nudges.len(), 1); + let nudge = &nudges[0]; + assert_eq!(nudge.kind, "lsp_first"); + assert_eq!(nudge.call_id, "toolu_1"); + assert!(nudge.content.contains("find_definition")); + assert!(nudge.content.contains("UserService")); +} + +#[test] +fn quiet_command_nudges_once() { + let mut state = ToolGuardrailRequestState::new(); + let config = ToolGuardrailConfig { + quiet_commands: true, + ..ToolGuardrailConfig::disabled() + }; + let calls = vec![call("bash", json!({"command": "cargo test"}))]; + let first = evaluate_tool_guardrails(&calls, &[], &config, &mut state); + assert_eq!(first.len(), 1); + assert_eq!(first[0].kind, "quiet_command"); + assert!(first[0].content.contains("cargo test --quiet")); + assert!(evaluate_tool_guardrails(&calls, &[], &config, &mut state).is_empty()); +} + +#[test] +fn only_offending_call_is_nudged() { + let mut state = ToolGuardrailRequestState::new(); + let config = ToolGuardrailConfig { + lsp_first: true, + ..ToolGuardrailConfig::disabled() + }; + let calls = vec![ + ToolCall { + id: "toolu_grep".into(), + name: "grep".into(), + input: json!({"pattern": "UserService"}), + }, + ToolCall { + id: "toolu_write".into(), + name: "write_file".into(), + input: json!({"content": "ok"}), + }, + ]; + let nudges = evaluate_tool_guardrails( + &calls, + &[tool_spec("grep"), tool_spec("find_definition")], + &config, + &mut state, + ); + assert_eq!(nudges.len(), 1); + assert_eq!(nudges[0].call_id, "toolu_grep"); +} + +#[test] +fn write_payload_cap_detects_oversized_payload() { + let mut state = ToolGuardrailRequestState::new(); + let config = ToolGuardrailConfig { + write_payload_caps: true, + max_write_payload_bytes: 4, + ..ToolGuardrailConfig::disabled() + }; + let calls = vec![call("write_file", json!({"content": "12345"}))]; + let nudges = evaluate_tool_guardrails(&calls, &[], &config, &mut state); + assert_eq!(nudges.len(), 1); + assert_eq!(nudges[0].kind, "write_payload_cap"); + assert!(nudges[0].content.contains("5 bytes > 4 bytes")); +} + +#[test] +fn write_payload_cap_zero_disables_nudge() { + let mut state = ToolGuardrailRequestState::new(); + let config = ToolGuardrailConfig { + write_payload_caps: true, + max_write_payload_bytes: 0, + ..ToolGuardrailConfig::disabled() + }; + let calls = vec![call("write_file", json!({"content": "12345"}))]; + assert!(evaluate_tool_guardrails(&calls, &[], &config, &mut state).is_empty()); +} + +#[test] +fn write_payload_cap_does_not_collide_on_equal_length_different_content() { + let mut state = ToolGuardrailRequestState::new(); + let config = ToolGuardrailConfig { + write_payload_caps: true, + max_write_payload_bytes: 4, + ..ToolGuardrailConfig::disabled() + }; + // Same byte length (5), different content/target -- both must nudge. + let first = vec![call("write_file", json!({"content": "aaaaa"}))]; + let second = vec![call("write_file", json!({"content": "bbbbb"}))]; + assert_eq!( + evaluate_tool_guardrails(&first, &[], &config, &mut state).len(), + 1 + ); + assert_eq!( + evaluate_tool_guardrails(&second, &[], &config, &mut state).len(), + 1, + "an unrelated oversized write of equal byte length must still be nudged" + ); +} + +#[test] +fn write_payload_bytes_falls_back_to_unrecognized_field_names() { + let args = json!({"body": "12345"}); + assert_eq!(write_cap::write_payload_bytes(args.as_object().unwrap()), 5); +} + +#[test] +fn resolve_runtime_guardrails_prefers_runtime_override() { + let static_config = ToolGuardrailConfig::disabled(); + let resolved = resolve_runtime_guardrails(&static_config, "standard"); + assert_eq!(resolved, ToolGuardrailConfig::standard()); +} + +#[test] +fn resolve_runtime_guardrails_keeps_static_when_modes_match() { + let static_config = ToolGuardrailConfig { + max_write_payload_bytes: 123, + ..ToolGuardrailConfig::standard() + }; + let resolved = resolve_runtime_guardrails(&static_config, "standard"); + // Same mode as the static preset -- the static config (with its + // custom max_write_payload_bytes) must be preserved, not rebuilt + // from the bare preset. + assert_eq!(resolved, static_config); +} + +#[test] +fn resolve_runtime_guardrails_falls_back_on_unparseable_value() { + let static_config = ToolGuardrailConfig::standard(); + let resolved = resolve_runtime_guardrails(&static_config, "not-a-real-mode"); + assert_eq!(resolved, static_config); +} diff --git a/crates/proxy/src/tools/guardrails/utils.rs b/crates/proxy/src/tools/guardrails/utils.rs new file mode 100644 index 0000000..d3c962b --- /dev/null +++ b/crates/proxy/src/tools/guardrails/utils.rs @@ -0,0 +1,124 @@ +use serde_json::{Map, Value}; + +pub(super) fn is_shell_tool(name: &str) -> bool { + matches!( + name, + "bash" | "shell" | "run_command" | "execute_command" | "terminal" | "exec" | "execute_bash" + ) +} + +pub(super) fn is_grep_tool(name: &str) -> bool { + matches!(name, "grep" | "rg" | "ripgrep" | "st") +} + +pub(super) fn is_glob_tool(name: &str) -> bool { + matches!(name, "glob" | "find_files" | "file_glob") +} + +pub(super) fn is_write_or_edit_tool(name: &str) -> bool { + matches!( + name, + "write" + | "write_file" + | "edit" + | "edit_file" + | "replace" + | "apply_patch" + | "create_file" + | "update_file" + ) +} + +pub(super) fn object_args(value: &Value) -> Option<&Map> { + value.as_object() +} + +pub(super) fn command_arg(args: &Map) -> Option<&str> { + string_arg(args, &["command", "cmd", "shell_command", "input"]) +} + +pub(super) fn string_arg<'a>(args: &'a Map, keys: &[&str]) -> Option<&'a str> { + // Only the named keys, in order. Do NOT fall back to an arbitrary string + // value: that would let an unrelated field be misread as the search symbol + // or command for an advisory nudge. + keys.iter() + .find_map(|key| args.get(*key).and_then(Value::as_str)) +} + +pub(super) fn shell_grep_symbol(command: &str) -> Option { + let mut parts = command.split_whitespace(); + let binary = strip_shell_quotes(parts.next()?) + .rsplit('/') + .next()? + .to_string(); + if !matches!(binary.as_str(), "rg" | "ripgrep" | "grep" | "st") { + return None; + } + + let mut previous_took_value = false; + for part in parts { + let token = strip_shell_quotes(part); + if previous_took_value { + previous_took_value = false; + continue; + } + if token.starts_with("--") { + previous_took_value = matches!( + token.as_str(), + "--glob" | "--type" | "--context" | "--after-context" | "--before-context" + ); + continue; + } + if token.starts_with('-') { + continue; + } + return symbol_from_search_value(&token); + } + None +} + +pub(super) fn symbol_from_search_value(value: &str) -> Option { + let trimmed = strip_shell_quotes(value) + .trim_matches('/') + .replace("\\b", "") + .replace(['^', '$'], ""); + for token in trimmed.split(|ch: char| !(ch.is_ascii_alphanumeric() || ch == '_')) { + if looks_like_symbol_token(token) { + return Some(token.to_string()); + } + } + None +} + +fn looks_like_symbol_token(token: &str) -> bool { + if token.len() < 3 { + return false; + } + let mut chars = token.chars(); + let Some(first) = chars.next() else { + return false; + }; + if !(first.is_ascii_alphabetic() || first == '_') { + return false; + } + if !chars.all(|ch| ch.is_ascii_alphanumeric() || ch == '_') { + return false; + } + + let lower = token.to_ascii_lowercase(); + if matches!( + lower.as_str(), + "todo" | "fixme" | "error" | "warning" | "debug" | "test" | "src" | "main" + ) { + return false; + } + token.contains('_') || token.chars().any(|ch| ch.is_ascii_uppercase()) +} + +pub(super) fn strip_shell_quotes(value: &str) -> String { + value + .trim() + .trim_matches('"') + .trim_matches('\'') + .to_string() +} diff --git a/crates/proxy/src/tools/guardrails/write_cap.rs b/crates/proxy/src/tools/guardrails/write_cap.rs new file mode 100644 index 0000000..eade9ce --- /dev/null +++ b/crates/proxy/src/tools/guardrails/write_cap.rs @@ -0,0 +1,101 @@ +use super::utils::*; +use super::ToolGuardrailNudge; +use crate::tools::execution::ToolCall; +use serde_json::{Map, Value}; + +/// Depth cap for `value_payload_bytes`'s recursion. Model-controlled JSON has +/// no natural nesting limit; a real write/edit payload never needs anywhere +/// near this depth, so hitting it is itself a signal the payload is oversized. +const MAX_PAYLOAD_DEPTH: usize = 32; + +pub(super) fn write_payload_cap_nudge( + call: &ToolCall, + max_bytes: usize, +) -> Option { + // A cap of 0 disables the nudge rather than firing on every non-empty write. + if max_bytes == 0 { + return None; + } + let tool_name = call.name.to_ascii_lowercase(); + if !is_write_or_edit_tool(&tool_name) { + return None; + } + let bytes = object_args(&call.input) + .map(write_payload_bytes) + .unwrap_or(0); + if bytes <= max_bytes { + return None; + } + Some(ToolGuardrailNudge { + call_id: call.id.clone(), + kind: "write_payload_cap", + content: format!( + "The requested write/edit payload is too large for this proxy policy ({bytes} bytes > {max_bytes} bytes). Retry with a smaller targeted edit or split the change." + ), + // Include a hash of the actual payload, not just its byte length, so + // two unrelated oversized writes that happen to be the same size + // (e.g. same-size templated files) don't collide and suppress each + // other's nudge. Identical repeated calls (same target, same + // content) still dedupe as intended. + fingerprint: format!( + "write_payload_cap:{}:{:x}:{bytes}:{max_bytes}", + call.name, + payload_content_hash(&call.input) + ), + }) +} + +fn payload_content_hash(value: &Value) -> u64 { + use std::hash::{Hash, Hasher}; + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + value.to_string().hash(&mut hasher); + hasher.finish() +} + +pub(super) fn write_payload_bytes(args: &Map) -> usize { + let payload_keys = [ + "content", + "text", + "new_content", + "patch", + "diff", + "replacement", + "data", + "old_string", + "new_string", + ]; + let known = args + .iter() + .filter(|(key, _)| payload_keys.contains(&key.as_str())) + .fold(0usize, |acc, (_, value)| { + acc.saturating_add(value_payload_bytes(value, 0)) + }); + if known > 0 { + return known; + } + // None of the known field names matched (a tool with a non-standard + // schema, e.g. `body`/`value`) -- fall back to summing every value in + // the payload rather than silently reporting zero and never capping it. + args.values().fold(0usize, |acc, value| { + acc.saturating_add(value_payload_bytes(value, 0)) + }) +} + +fn value_payload_bytes(value: &Value, depth: usize) -> usize { + if depth >= MAX_PAYLOAD_DEPTH { + // Treat implausibly deep nesting as oversized rather than recursing + // further; a fixed large sentinel (not usize::MAX) keeps the + // saturating sum below safe from overflowing back to a small value. + return 1_000_000_000; + } + match value { + Value::String(value) => value.len(), + Value::Array(values) => values.iter().fold(0usize, |acc, v| { + acc.saturating_add(value_payload_bytes(v, depth + 1)) + }), + Value::Object(values) => values.values().fold(0usize, |acc, v| { + acc.saturating_add(value_payload_bytes(v, depth + 1)) + }), + _ => 0, + } +} diff --git a/crates/pxpipe/src/transform/anthropic.rs b/crates/pxpipe/src/transform/anthropic.rs deleted file mode 100644 index 22a6009..0000000 --- a/crates/pxpipe/src/transform/anthropic.rs +++ /dev/null @@ -1,1258 +0,0 @@ -//! Anthropic Messages request transform (static-slab imaging). Port of the -//! system-slab + tool-doc path of pxpipe's `transform.ts`. -//! -//! **Value-based on purpose.** The proxy's typed `anthropic::ContentBlock`/`Tool` -//! do not model per-block `cache_control` and have no flatten catch-all, so a -//! typed round-trip would silently drop every cache breakpoint and unmodeled -//! block. We read/mutate the raw `serde_json::Value` tree and touch only the -//! system field, the tools array, and the first user message — every other byte -//! passes through untouched. This is why we cannot reuse the proxy's -//! `patch_repaired_body` (which *fails open* on cache_control to preserve it in -//! place; we need to *relocate* it onto the image). - -use base64::Engine; -use serde_json::{json, Map, Value}; - -use super::info::TransformInfo; -use super::{factsheet, gate, schema_strip}; -use crate::render::{render_text, RenderOpts}; - -#[derive(Clone, Copy, Debug)] -pub struct AnthropicOpts { - pub cols: usize, - pub max_height_px: usize, - pub chars_per_token: f64, - /// Skip entirely below this many slab chars (per-image cost dominates). - pub min_compress_chars: usize, - /// Move tool descriptions/schema annotations into the imaged Tool Reference. - pub compress_tools: bool, - /// Image large `` text blocks in the first user message. - pub compress_reminders: bool, - /// Image large `tool_result` text content across all user messages. - pub compress_tool_results: bool, - /// Per-block char floor for reminder/tool_result imaging. - pub min_live_block_chars: usize, - /// Cap on images per tool_result; source is truncated (with a paging marker) - /// above this so one giant result can't blow Anthropic's 100-image/request cap. - pub max_images_per_tool_result: usize, - /// Aggregate ceiling on TOTAL image blocks in the outgoing request - /// (client-supplied + every pxpipe pass combined). Anthropic rejects requests - /// past ~100 images, so imaging stops once this budget is exhausted and the - /// remaining regions pass through as text. The per-tool_result cap only bounds - /// one result; this bounds the sum. - pub max_total_images: usize, - /// Collapse the OLD closed-tool-call conversation prefix into history image(s), - /// keeping the recent tail as text. Default OFF — highest cache-stability risk - /// (see `apply_history`); gate on live validation before enabling by default. - pub compress_history: bool, - /// Trailing messages kept as live text (never collapsed). - pub keep_tail_messages: usize, - /// Minimum collapsible messages; below this the cache-amortization math doesn't pay. - pub min_collapse_prefix_messages: usize, - /// Snap the collapse boundary to this message-grid so the rendered history PNG - /// stays byte-identical across turns and keeps hitting the prompt cache. - pub history_collapse_chunk: usize, -} - -impl Default for AnthropicOpts { - fn default() -> Self { - Self { - cols: crate::render::RenderOpts::default().cols, - max_height_px: crate::render::RenderOpts::default().max_height_px, - // Slab is dense; 2.0 is conservative vs the real ~1.9 (see pxpipe - // SLAB_CHARS_PER_TOKEN) so the gate biases toward pass-through. - chars_per_token: 2.0, - min_compress_chars: 6_000, - compress_tools: true, - compress_reminders: true, - compress_tool_results: true, - min_live_block_chars: 2_000, - max_images_per_tool_result: 10, - // Leave headroom under Anthropic's 100-image/request cap for a few - // client-supplied images the counter may miss on unusual shapes. - max_total_images: 95, - compress_history: false, - keep_tail_messages: 8, - min_collapse_prefix_messages: 20, - history_collapse_chunk: 20, - } - } -} - -/// Neutral framing header co-rendered into the slab image. Deliberately avoids -/// "system prompt"/"authoritative" wording — pxpipe found that phrasing trips -/// Anthropic's reasoning_extraction refusal (reads as a replayed/extracted -/// prompt). First-party, matter-of-fact framing keeps the model reading it as -/// this session's own reference material. -const SLAB_HEADER: &str = - "Reference context for this session, rendered as an image. Read it as text.\n\n"; - -/// Concat the text of a system field (string or block array) and return -/// `(text, last_cache_control)`. The cache_control is the caller's prefix -/// breakpoint we will relocate onto the image. -fn read_system(system: Option<&Value>) -> (String, Option) { - match system { - Some(Value::String(s)) => (s.clone(), None), - Some(Value::Array(blocks)) => { - let mut text = String::new(); - let mut cc = None; - for b in blocks { - if let Some(t) = b.get("text").and_then(|t| t.as_str()) { - if !text.is_empty() { - text.push('\n'); - } - text.push_str(t); - } - if let Some(c) = b.get("cache_control") { - cc = Some(c.clone()); // last one wins (latest in prefix) - } - } - (text, cc) - } - _ => (String::new(), None), - } -} - -/// Render one tool's full doc (prose + compact schema) for the imaged reference. -fn render_tool_doc(tool: &Value) -> String { - let name = tool.get("name").and_then(|n| n.as_str()).unwrap_or(""); - let desc = tool - .get("description") - .and_then(|d| d.as_str()) - .unwrap_or(""); - let schema = tool - .get("input_schema") - .map(|s| serde_json::to_string(s).unwrap_or_default()) - .unwrap_or_default(); - format!("## Tool: {name}\n{desc}\n```json\n{schema}\n```\n") -} - -/// Build a base64 image content block from PNG bytes. -fn image_block(png: &[u8]) -> Value { - let data = base64::engine::general_purpose::STANDARD.encode(png); - json!({ - "type": "image", - "source": { "type": "base64", "media_type": "image/png", "data": data } - }) -} - -/// Chars-per-token for the live-region gate (reminders, tool_results). Higher -/// than the slab's 2.0 because that content is prose/log, not dense config — -/// pxpipe uses 4 here, which is conservative (biases toward pass-through). -const LIVE_CHARS_PER_TOKEN: f64 = 4.0; - -/// Transform `root` (a parsed Anthropic Messages body) in place. Returns info; -/// on any full skip `root` is left byte-identical to the input. Three -/// independent passes — static slab, `` blocks, and -/// `tool_result` content — so a request below the slab floor can still get its -/// live regions imaged (and vice-versa). -pub fn transform(root: &mut Value, opts: &AnthropicOpts) -> TransformInfo { - if !root.is_object() { - return TransformInfo::skipped("parse_error"); - } - let mut info = TransformInfo::default(); - // NEW-image budget: total ceiling minus images the client already sent, so - // the combined passes never push the request past Anthropic's ~100-image cap. - let mut budget = opts - .max_total_images - .saturating_sub(count_existing_images(root)); - let slab_reason = apply_slab(root, opts, &mut info, &mut budget); - // History runs BEFORE the live-region passes: it serializes the OLD message - // prefix to text, so tool_result imaging must not have already replaced that - // content with `[image]` placeholders. Reminders/tool_results then image only - // what survives (the protected first message + the live tail). - if opts.compress_history { - apply_history(root, opts, &mut info, &mut budget); - } - if opts.compress_reminders { - apply_reminders(root, opts, &mut info, &mut budget); - } - if opts.compress_tool_results { - apply_tool_results(root, opts, &mut info, &mut budget); - } - info.compressed = info.image_count > 0; - // "applied" when anything imaged; otherwise report why the slab (the primary - // path) declined. - info.reason = if info.compressed { - "applied" - } else { - slab_reason - }; - info -} - -/// Count image blocks already present in the request (client-supplied, incl. -/// images nested inside `tool_result` content), so the added-image budget is -/// measured against the true outgoing total under Anthropic's ~100-image cap. -fn count_existing_images(root: &Value) -> usize { - let Some(msgs) = root.get("messages").and_then(|m| m.as_array()) else { - return 0; - }; - let is_image = |b: &Value| b.get("type").and_then(|t| t.as_str()) == Some("image"); - let mut n = 0; - for m in msgs { - let Some(blocks) = m.get("content").and_then(|c| c.as_array()) else { - continue; - }; - for b in blocks { - match b.get("type").and_then(|t| t.as_str()) { - Some("image") => n += 1, - Some("tool_result") => { - if let Some(inner) = b.get("content").and_then(|c| c.as_array()) { - n += inner.iter().filter(|x| is_image(x)).count(); - } - } - _ => {} - } - } - } - n -} - -/// Render `text` to image blocks for a live region (reminder / tool_result), -/// gated on profitability AND the remaining `budget` of new images. Moves `cc` -/// (the block's cache_control, if any) onto the last image and optionally -/// appends a verbatim fact-sheet text block. Returns `None` (leave the block as -/// text) when empty, not profitable, or over budget. -fn render_live_block( - text: &str, - opts: &AnthropicOpts, - cc: Option, - append_factsheet: bool, - budget: usize, -) -> Option { - let images = render_text( - text, - RenderOpts { - cols: opts.cols, - max_height_px: opts.max_height_px, - }, - ); - let chars = text.chars().count(); - if images.len() > budget { - return None; - } - if !gate::is_profitable(&images, chars, LIVE_CHARS_PER_TOKEN) { - return None; - } - let mut blocks: Vec = images.iter().map(|im| image_block(&im.png)).collect(); - if let (Some(cc), Some(last)) = (cc, blocks.last_mut()) { - if let Some(o) = last.as_object_mut() { - o.insert("cache_control".into(), cc); - } - } - if append_factsheet { - let fact = factsheet::fact_sheet_text(text); - if !fact.is_empty() { - blocks.push(json!({ "type": "text", "text": fact })); - } - } - Some(LiveRender { - blocks, - bytes: images.iter().map(|im| im.png.len()).sum(), - pixels: images - .iter() - .map(|im| im.width as usize * im.height as usize) - .sum(), - dropped: images.iter().map(|im| im.dropped).sum(), - img_count: images.len(), - }) -} - -/// Rendered-live-block accumulators, folded into `TransformInfo` by the caller. -struct LiveRender { - blocks: Vec, - bytes: usize, - pixels: usize, - dropped: usize, - img_count: usize, -} - -/// Static system+tools slab pass. Returns the skip reason (or "applied"); only -/// mutates `root` / fills `info` when profitable. -fn apply_slab( - root: &mut Value, - opts: &AnthropicOpts, - info: &mut TransformInfo, - budget: &mut usize, -) -> &'static str { - let (system_text, system_cc) = read_system(root.get("system")); - - // Capture a tool cache_control as a fallback anchor (tools sit before system - // in the cache prefix, so system's wins when both exist). - let tools_present = opts.compress_tools - && root - .get("tools") - .and_then(|t| t.as_array()) - .is_some_and(|a| !a.is_empty()); - let mut tool_cc = None; - let mut tool_ref = String::new(); - if tools_present { - if let Some(arr) = root.get("tools").and_then(|t| t.as_array()) { - for t in arr { - tool_ref.push_str(&render_tool_doc(t)); - if let Some(c) = t.get("cache_control") { - tool_cc = Some(c.clone()); - } - } - } - } - - // Assemble the slab: header + system + tool reference. - let mut slab = String::with_capacity(system_text.len() + tool_ref.len() + 64); - slab.push_str(SLAB_HEADER); - slab.push_str(&system_text); - if !tool_ref.is_empty() { - slab.push_str("\n\n# Tool Reference\n"); - slab.push_str(&tool_ref); - } - - let slab_chars = slab.chars().count(); - // Nothing meaningful to image (no system text and no tools). - if system_text.trim().is_empty() && tool_ref.is_empty() { - return "no_slab"; - } - if slab_chars < opts.min_compress_chars { - return "below_min_chars"; - } - - let images = render_text( - &slab, - RenderOpts { - cols: opts.cols, - max_height_px: opts.max_height_px, - }, - ); - if !gate::is_profitable(&images, slab_chars, opts.chars_per_token) { - return "not_profitable"; - } - if images.len() > *budget { - return "image_budget"; - } - - // ---- commit: mutate root ------------------------------------------------ - let anchor = system_cc.or(tool_cc); // relocate this onto the last image - let fact = factsheet::fact_sheet_text(&slab); - - let mut image_blocks: Vec = images.iter().map(|im| image_block(&im.png)).collect(); - let image_bytes: usize = images.iter().map(|im| im.png.len()).sum(); - let image_pixels: usize = images - .iter() - .map(|im| im.width as usize * im.height as usize) - .sum(); - let dropped: usize = images.iter().map(|im| im.dropped).sum(); - - // Relocate the caller's cache breakpoint onto the LAST image so the whole - // imaged prefix caches as one stable segment. pxpipe never *adds* a marker. - let relocated = if let (Some(cc), Some(last)) = (anchor, image_blocks.last_mut()) { - if let Some(obj) = last.as_object_mut() { - obj.insert("cache_control".into(), cc); - } - true - } else { - false - }; - - let obj = root.as_object_mut().expect("checked is_object above"); - - // 1. system -> short pointer (+ factsheet). Removes original cache_control - // (relocated onto the image) since we're replacing the whole field. - let mut pointer = String::from( - "[Your reference context for this session is provided as an image in the first user message. Read it there.]", - ); - if !fact.is_empty() { - pointer.push('\n'); - pointer.push_str(&fact); - } - obj.insert("system".into(), Value::String(pointer)); - - // 2. tools -> stub description + annotation-stripped schema, drop the - // relocated cache_control. Keep name + structural schema so Anthropic's - // tool-use validator still accepts calls. - if tools_present { - if let Some(arr) = obj.get_mut("tools").and_then(|t| t.as_array_mut()) { - for t in arr.iter_mut() { - let Some(tm) = t.as_object_mut() else { - continue; - }; - let name = tm - .get("name") - .and_then(|n| n.as_str()) - .unwrap_or("") - .to_string(); - tm.insert( - "description".into(), - Value::String(format!("See \"## Tool: {name}\" in the reference image.")), - ); - if let Some(schema) = tm.get("input_schema") { - let stripped = schema_strip::strip(schema); - if schema_strip::has_structure(&stripped) { - tm.insert("input_schema".into(), stripped); - } - // else keep original: a bare {type:object} stub causes 400s. - } - tm.remove("cache_control"); - } - } - } - - // 3. Prepend image blocks to the first user message (system rejects images). - prepend_images_to_first_user(obj, &mut image_blocks); - - info.compressed_chars += slab.len(); - info.image_count += images.len(); - info.image_bytes += image_bytes; - info.image_pixels += image_pixels; - info.dropped_chars += dropped; - info.relocated_cache_anchor = relocated; - *budget -= images.len(); - "applied" -} - -/// Prepend `images` to the first user message's content. Converts string -/// content to a text block first; inserts a fresh user message if none exists. -fn prepend_images_to_first_user(obj: &mut Map, images: &mut Vec) { - let msgs = obj - .entry("messages") - .or_insert_with(|| Value::Array(vec![])); - let Some(arr) = msgs.as_array_mut() else { - return; - }; - let first_user = arr - .iter() - .position(|m| m.get("role").and_then(|r| r.as_str()) == Some("user")); - - match first_user { - Some(idx) => { - let content = arr[idx].as_object_mut().and_then(|m| m.get_mut("content")); - match content { - Some(Value::String(s)) => { - let text = std::mem::take(s); - let mut blocks = vec![json!({ "type": "text", "text": text })]; - let mut new = std::mem::take(images); - new.append(&mut blocks); - arr[idx] - .as_object_mut() - .unwrap() - .insert("content".into(), Value::Array(new)); - } - Some(Value::Array(existing)) => { - let mut new = std::mem::take(images); - new.append(existing); - *existing = new; - } - _ => { - arr[idx] - .as_object_mut() - .unwrap() - .insert("content".into(), Value::Array(std::mem::take(images))); - } - } - } - None => { - arr.insert( - 0, - json!({ "role": "user", "content": std::mem::take(images) }), - ); - } - } -} - -/// Image large `` text blocks in the first user message. These -/// are per-turn injected context (env, hints) that Claude Code ships as separate -/// text blocks; the big ones are pure token cost the model rarely needs to quote. -fn apply_reminders( - root: &mut Value, - opts: &AnthropicOpts, - info: &mut TransformInfo, - budget: &mut usize, -) { - let Some(arr) = root.get_mut("messages").and_then(|m| m.as_array_mut()) else { - return; - }; - let Some(msg) = arr - .iter_mut() - .find(|m| m.get("role").and_then(|r| r.as_str()) == Some("user")) - else { - return; - }; - let Some(content) = msg.get_mut("content").and_then(|c| c.as_array_mut()) else { - return; // string content: reminders arrive only as array blocks - }; - - let mut out: Vec = Vec::with_capacity(content.len()); - for block in std::mem::take(content) { - let is_reminder_text = block.get("type").and_then(|t| t.as_str()) == Some("text") - && block.get("text").and_then(|t| t.as_str()).is_some_and(|t| { - t.contains("") && t.len() >= opts.min_live_block_chars - }); - if !is_reminder_text { - out.push(block); - continue; - } - let text = block - .get("text") - .and_then(|t| t.as_str()) - .unwrap_or_default() - .to_string(); - let cc = block.get("cache_control").cloned(); - match render_live_block(&text, opts, cc, false, *budget) { - Some(r) => { - *budget -= r.img_count; - info.reminder_imgs += r.img_count; - info.image_count += r.img_count; - info.image_bytes += r.bytes; - info.image_pixels += r.pixels; - info.dropped_chars += r.dropped; - info.compressed_chars += text.len(); - out.extend(r.blocks); - } - None => out.push(block), - } - } - *content = out; -} - -/// Image large `tool_result` text content across every user message. tool_result -/// output (find trees, file dumps, logs) is the bulk of agentic input. Skips -/// `is_error` results (Anthropic rejects images inside those) and pages oversized -/// content down to the image cap. -fn apply_tool_results( - root: &mut Value, - opts: &AnthropicOpts, - info: &mut TransformInfo, - budget: &mut usize, -) { - let Some(msgs) = root.get_mut("messages").and_then(|m| m.as_array_mut()) else { - return; - }; - for msg in msgs.iter_mut() { - if *budget == 0 { - break; - } - let Some(blocks) = msg.get_mut("content").and_then(|c| c.as_array_mut()) else { - continue; - }; - for block in blocks.iter_mut() { - let Some(bm) = block.as_object_mut() else { - continue; - }; - if bm.get("type").and_then(|t| t.as_str()) != Some("tool_result") { - continue; - } - if bm.get("is_error").and_then(|e| e.as_bool()) == Some(true) { - continue; // images forbidden in error tool_results - } - // Don't clobber a tool_result that already carries an image block - // (e.g. a screenshot the tool returned) — tool_result_text only reads - // the text sub-blocks, so imaging here would silently drop that image. - if tool_result_has_image(bm.get("content")) { - continue; - } - let text = tool_result_text(bm.get("content")); - if text.len() < opts.min_live_block_chars { - continue; - } - // Cap each result at the per-result limit AND whatever total budget - // is left, so the aggregate can't exceed max_total_images. - let per_result = opts.max_images_per_tool_result.min(*budget); - if per_result == 0 { - break; - } - let (rendered, omitted) = truncate_for_budget(&text, per_result, opts); - let cc = bm.get("cache_control").cloned(); - if let Some(r) = render_live_block(&rendered, opts, cc, true, *budget) { - *budget -= r.img_count; - if omitted > 0 { - info.truncated_tool_results += 1; - info.omitted_chars += omitted; - } - info.tool_result_imgs += r.img_count; - info.image_count += r.img_count; - info.image_bytes += r.bytes; - info.image_pixels += r.pixels; - info.dropped_chars += r.dropped; - info.compressed_chars += text.len(); - bm.remove("cache_control"); // relocated onto the last image - bm.insert("content".into(), Value::Array(r.blocks)); - } - } - } -} - -/// True when a tool_result `content` array already holds an image block. Imaging -/// such a result would drop that image (only text sub-blocks are flattened), so -/// the caller skips it. -fn tool_result_has_image(content: Option<&Value>) -> bool { - matches!(content, Some(Value::Array(blocks)) - if blocks - .iter() - .any(|b| b.get("type").and_then(|t| t.as_str()) == Some("image"))) -} - -/// Flatten a tool_result `content` (string, or an array of text/other blocks) -/// to a single text string for rendering. -fn tool_result_text(content: Option<&Value>) -> String { - match content { - Some(Value::String(s)) => s.clone(), - Some(Value::Array(blocks)) => { - let mut out = String::new(); - for b in blocks { - if let Some(t) = b.get("text").and_then(|t| t.as_str()) { - if !out.is_empty() { - out.push('\n'); - } - out.push_str(t); - } - } - out - } - _ => String::new(), - } -} - -/// Truncate `text` so it renders to at most `max_images` pages, keeping a 60/40 -/// head/tail split with a paging marker in the middle. Returns the (possibly -/// truncated) text and the count of chars elided. Renders once to measure — the -/// oversized path is rare, so the extra render is acceptable. -fn truncate_for_budget(text: &str, max_images: usize, opts: &AnthropicOpts) -> (String, usize) { - let render_opts = RenderOpts { - cols: opts.cols, - max_height_px: opts.max_height_px, - }; - let full = render_text(text, render_opts); - let cap = max_images.max(1); - if full.len() <= cap { - return (text.to_string(), 0); - } - let chars: Vec = text.chars().collect(); - // Start from the linear estimate, then shrink and RE-RENDER until it truly - // fits `cap` pages — the paging marker and heavy line-wrapping can push a - // linear estimate over, so the cap must be verified, not assumed. - let mut ratio = cap as f64 / full.len() as f64; - loop { - let keep = (((chars.len() as f64) * ratio) as usize).min(chars.len()); - let head_len = keep * 6 / 10; - let tail_len = keep - head_len; - let head: String = chars[..head_len].iter().collect(); - let tail: String = chars[chars.len() - tail_len..].iter().collect(); - let omitted = chars.len() - head_len - tail_len; - let out = format!("{head}\n\n[... {omitted} chars omitted for length ...]\n\n{tail}"); - if render_text(&out, render_opts).len() <= cap || ratio < 0.05 { - return (out, omitted); - } - ratio *= 0.8; - } -} - -/// Banner text bracketing the collapsed-history image(s). Constant (byte-stable). -const HISTORY_INTRO: &str = "Transcript of EARLIER conversation turns, rendered as images below. \ -Attribute each turn strictly by its / tag. This is PAST context, not the live request.\n"; -const HISTORY_OUTRO: &str = - "\n[End of earlier transcript. The live request follows in the messages below.]"; - -/// Collapse the OLD closed-tool-call message prefix into ONE synthetic user -/// message holding history image(s); keep the recent tail as text. -/// -/// **Cache stability** is the whole risk here. Two guarantees keep the rendered -/// PNG byte-identical across turns so Anthropic prompt-caches it instead of -/// re-creating it every turn: (1) the collapse boundary is snapped DOWN to a -/// `history_collapse_chunk` message grid, so it only advances in steps and the -/// serialized text is stable for a whole window; (2) the serializer and renderer -/// are pure functions of the message bytes (no timestamps/rng, thinking blocks -/// dropped deterministically). If either breaks, this NET-LOSES money — hence -/// default-off until validated live. -/// -/// **Correctness**: only a tool-CLOSED prefix is collapsed (every `tool_use` has -/// its matching `tool_result` within the range), so no tool call is ever -/// orphaned. The first user message (which carries the slab images) is protected. -/// -/// NOTE: the synthetic message is role `user`, which can place it adjacent to the -/// protected first user message. The Anthropic Messages API accepts consecutive -/// same-role messages (pxpipe relies on this in production); images require the -/// user role, so this is unavoidable for a history-image message. -fn apply_history( - root: &mut Value, - opts: &AnthropicOpts, - info: &mut TransformInfo, - budget: &mut usize, -) { - let Some(messages) = root.get("messages").and_then(|m| m.as_array()) else { - return; - }; - let len = messages.len(); - // Protect the slab-bearing first user message: collapse starts after it. - let Some(first_user) = messages - .iter() - .position(|m| m.get("role").and_then(|r| r.as_str()) == Some("user")) - else { - return; - }; - let protected = first_user + 1; - let cutoff = len.saturating_sub(opts.keep_tail_messages); - if cutoff <= protected { - return; - } - let Some(boundary) = find_closed_boundary(messages, cutoff, protected) else { - return; - }; - // Snap DOWN to the chunk grid (relative to `protected`) for byte-stability. - let chunk = opts.history_collapse_chunk.max(1); - let grid = protected + ((boundary - protected) / chunk) * chunk; - // The grid line is NOT guaranteed tool-closed (parity shifts from text-only - // turns or parallel tool spans can leave it mid-open-span). Re-snap to the - // largest CLOSED boundary <= the grid line so no tool_use is orphaned into - // the history image; correctness beats the grid's cache-stability here. - let Some(snapped) = find_closed_boundary(messages, grid, protected) else { - return; - }; - if snapped.saturating_sub(protected) < opts.min_collapse_prefix_messages { - return; - } - - let text = messages_to_history_text(messages, protected, snapped); - if text.trim().is_empty() { - return; - } - let images = render_text( - &text, - RenderOpts { - cols: opts.cols, - max_height_px: opts.max_height_px, - }, - ); - if !gate::is_profitable(&images, text.chars().count(), opts.chars_per_token) { - return; - } - if images.len() > *budget { - return; - } - *budget -= images.len(); - - // Build the synthetic message content: intro, images, outro. - let mut content: Vec = Vec::with_capacity(images.len() + 2); - content.push(json!({ "type": "text", "text": HISTORY_INTRO })); - content.extend(images.iter().map(|im| image_block(&im.png))); - content.push(json!({ "type": "text", "text": HISTORY_OUTRO })); - let synthetic = json!({ "role": "user", "content": content }); - - let collapsed_turns = snapped - protected; - info.collapsed_turns = collapsed_turns; - info.collapsed_chars = text.len(); - info.collapsed_images = images.len(); - info.image_count += images.len(); - info.image_bytes += images.iter().map(|im| im.png.len()).sum::(); - info.image_pixels += images - .iter() - .map(|im| im.width as usize * im.height as usize) - .sum::(); - info.dropped_chars += images.iter().map(|im| im.dropped).sum::(); - info.compressed_chars += text.len(); - - // Splice: [0..protected] + synthetic + [snapped..]. - let arr = root - .get_mut("messages") - .and_then(|m| m.as_array_mut()) - .expect("messages was an array above"); - arr.splice(protected..snapped, std::iter::once(synthetic)); -} - -/// Largest exclusive end `e` in `(from, cutoff]` where messages `[from..e)` open -/// no tool call they don't also close. Returns `None` if none exists. Robust to -/// interleaved/parallel tool calls via the open-id set. -fn find_closed_boundary(messages: &[Value], cutoff: usize, from: usize) -> Option { - let mut open: std::collections::HashSet = std::collections::HashSet::new(); - let mut last_closed = None; - for (i, m) in messages.iter().enumerate().take(cutoff).skip(from) { - if let Some(blocks) = m.get("content").and_then(|c| c.as_array()) { - for b in blocks { - match b.get("type").and_then(|t| t.as_str()) { - Some("tool_use") => { - if let Some(id) = b.get("id").and_then(|i| i.as_str()) { - open.insert(id.to_string()); - } - } - Some("tool_result") => { - if let Some(id) = b.get("tool_use_id").and_then(|i| i.as_str()) { - open.remove(id); - } - } - _ => {} - } - } - } - if open.is_empty() { - last_closed = Some(i + 1); - } - } - last_closed -} - -/// Serialize messages `[from..to)` to `` XML text. thinking blocks -/// dropped; tool_use/tool_result flattened; inline images become `[image]`. -fn messages_to_history_text(messages: &[Value], from: usize, to: usize) -> String { - let mut out = String::new(); - for m in &messages[from..to] { - let role = m.get("role").and_then(|r| r.as_str()).unwrap_or("user"); - let body = flatten_content(m.get("content")); - out.push('<'); - out.push_str(role); - out.push_str(">\n"); - out.push_str(&body); - out.push_str("\n\n"); - } - out -} - -/// Flatten one message's content to text: text verbatim, tool_use/tool_result to -/// a compact marker, thinking dropped, inline images to `[image]`. -fn flatten_content(content: Option<&Value>) -> String { - match content { - Some(Value::String(s)) => s.clone(), - Some(Value::Array(blocks)) => { - let mut parts: Vec = Vec::new(); - for b in blocks { - match b.get("type").and_then(|t| t.as_str()) { - Some("text") => { - if let Some(t) = b.get("text").and_then(|t| t.as_str()) { - parts.push(t.to_string()); - } - } - Some("tool_use") => { - let name = b.get("name").and_then(|n| n.as_str()).unwrap_or(""); - let input = b - .get("input") - .map(|v| serde_json::to_string(v).unwrap_or_default()) - .unwrap_or_default(); - parts.push(format!("[tool_use {name} {input}]")); - } - Some("tool_result") => { - parts.push(format!( - "[tool_result {}]", - tool_result_text(b.get("content")) - )); - } - Some("image") => parts.push("[image]".to_string()), - // thinking / redacted_thinking / unknown: dropped. - _ => {} - } - } - parts.join("\n") - } - _ => String::new(), - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn big_system() -> String { - // ~14k chars with some notable identifiers for the factsheet. - let mut s = String::from("Operating rules. See src/main.rs and CONFIG_PATH env.\n"); - s.push_str(&"Do the thing carefully and precisely. ".repeat(320)); - s.push_str(" commit deadbeef1 flag --verbose"); - s - } - - #[test] - fn images_system_and_relocates_anchor() { - let mut root = json!({ - "model": "claude-fable-5", - "system": [ - { "type": "text", "text": big_system(), "cache_control": { "type": "ephemeral" } } - ], - "messages": [ { "role": "user", "content": "hi there" } ] - }); - let info = transform(&mut root, &AnthropicOpts::default()); - assert!(info.compressed, "reason={}", info.reason); - assert!(info.relocated_cache_anchor); - - // system is now a short pointer string. - assert!(root["system"].is_string()); - assert!(root["system"] - .as_str() - .unwrap() - .contains("first user message")); - - // first user message leads with an image block carrying the anchor. - let content = root["messages"][0]["content"].as_array().unwrap(); - assert_eq!(content[0]["type"], "image"); - assert_eq!(content.last().unwrap()["type"], "text"); // original "hi there" - assert_eq!(content.last().unwrap()["text"], "hi there"); - // anchor is on exactly the last image, nowhere in system. - let last_img = content.iter().rev().find(|b| b["type"] == "image").unwrap(); - assert!(last_img.get("cache_control").is_some()); - } - - #[test] - fn tiny_system_passes_through_unchanged() { - let mut root = json!({ - "model": "claude-fable-5", - "system": "be helpful", - "messages": [ { "role": "user", "content": "hi" } ] - }); - let before = root.clone(); - let info = transform(&mut root, &AnthropicOpts::default()); - assert!(!info.compressed); - assert_eq!(info.reason, "below_min_chars"); - assert_eq!(root, before, "skip path must not mutate the body"); - } - - #[test] - fn same_input_same_output_cache_stable() { - let make = || { - json!({ - "model": "claude-fable-5", - "system": big_system(), - "messages": [ { "role": "user", "content": "go" } ] - }) - }; - let mut a = make(); - let mut b = make(); - transform(&mut a, &AnthropicOpts::default()); - transform(&mut b, &AnthropicOpts::default()); - assert_eq!(a, b, "transform must be deterministic (cache stability)"); - } - - #[test] - fn tools_stubbed_and_schema_stripped() { - let mut root = json!({ - "model": "claude-fable-5", - "system": big_system(), - "tools": [{ - "name": "edit_file", - "description": "very long tool description ".repeat(50), - "input_schema": { - "type": "object", - "description": "annotation", - "properties": { "path": { "type": "string", "description": "p" } }, - "required": ["path"] - }, - "cache_control": { "type": "ephemeral" } - }], - "messages": [ { "role": "user", "content": "go" } ] - }); - let info = transform(&mut root, &AnthropicOpts::default()); - assert!(info.compressed); - let tool = &root["tools"][0]; - assert!(tool["description"] - .as_str() - .unwrap() - .contains("## Tool: edit_file")); - assert!(tool["input_schema"].get("description").is_none()); - assert_eq!(tool["input_schema"]["required"], json!(["path"])); - assert!(tool.get("cache_control").is_none(), "tool anchor relocated"); - } - - /// Dense filler text large enough to clear the live-block gate. - fn big_text() -> String { - "log line with detail /var/run/app.sock port 8080 CODE_X 12345 ".repeat(300) - } - - #[test] - fn images_large_reminder_block() { - let mut root = json!({ - "model": "claude-fable-5", - "system": "small", - "messages": [{ - "role": "user", - "content": [ - { "type": "text", "text": format!("{}", big_text()) }, - { "type": "text", "text": "the actual question" } - ] - }] - }); - let info = transform(&mut root, &AnthropicOpts::default()); - assert!(info.compressed); - assert!(info.reminder_imgs >= 1); - let content = root["messages"][0]["content"].as_array().unwrap(); - assert_eq!(content[0]["type"], "image", "reminder replaced by image"); - // The real question text survives untouched. - assert!(content.iter().any(|b| b["text"] == "the actual question")); - } - - #[test] - fn images_large_tool_result_with_factsheet() { - let mut root = json!({ - "model": "claude-fable-5", - "system": "small", - "messages": [{ - "role": "user", - "content": [{ - "type": "tool_result", - "tool_use_id": "tu_1", - "content": big_text(), - "cache_control": { "type": "ephemeral" } - }] - }] - }); - let info = transform(&mut root, &AnthropicOpts::default()); - assert!(info.compressed); - assert!(info.tool_result_imgs >= 1); - let tr = &root["messages"][0]["content"][0]; - assert!( - tr.get("cache_control").is_none(), - "anchor relocated onto image" - ); - let inner = tr["content"].as_array().unwrap(); - assert_eq!(inner[0]["type"], "image"); - // fact-sheet text block rides alongside the image (paths/ids survive OCR). - assert!(inner - .iter() - .any(|b| b["type"] == "text" - && b["text"].as_str().unwrap().contains("/var/run/app.sock"))); - } - - #[test] - fn skips_error_tool_result() { - let mut root = json!({ - "model": "claude-fable-5", - "system": "small", - "messages": [{ - "role": "user", - "content": [{ - "type": "tool_result", - "tool_use_id": "tu_1", - "is_error": true, - "content": big_text() - }] - }] - }); - let before = root.clone(); - let info = transform(&mut root, &AnthropicOpts::default()); - assert!(!info.compressed, "error tool_results must not be imaged"); - assert_eq!(root, before); - } - - #[test] - fn pages_oversized_tool_result() { - // Force a tiny image cap so a modest result trips the paging path. - let opts = AnthropicOpts { - max_images_per_tool_result: 1, - max_height_px: 80, // ~9 rows/page → many pages before truncation - ..AnthropicOpts::default() - }; - let mut root = json!({ - "model": "claude-fable-5", - "system": "small", - "messages": [{ - "role": "user", - "content": [{ - "type": "tool_result", - "tool_use_id": "tu_1", - "content": big_text() - }] - }] - }); - let info = transform(&mut root, &opts); - assert!(info.compressed); - assert_eq!(info.truncated_tool_results, 1); - assert!(info.omitted_chars > 0); - } - - /// A conversation with `pairs` closed assistant(tool_use)/user(tool_result) turns. - fn convo(pairs: usize) -> Value { - let mut msgs = vec![json!({ "role": "user", "content": "start the task" })]; - for i in 0..pairs { - msgs.push(json!({ - "role": "assistant", - "content": [ - { "type": "text", "text": format!("step {i}") }, - { "type": "tool_use", "id": format!("tu_{i}"), "name": "run", "input": { "cmd": format!("do {i}") } } - ] - })); - msgs.push(json!({ - "role": "user", - "content": [{ - "type": "tool_result", - "tool_use_id": format!("tu_{i}"), - "content": format!("output /path/file{i}.rs {}", "detail ".repeat(60)) - }] - })); - } - json!({ "model": "claude-fable-5", "system": "small", "messages": msgs }) - } - - fn history_opts() -> AnthropicOpts { - AnthropicOpts { - compress_history: true, - // Isolate the history pass so tail tool_results don't also image. - compress_tool_results: false, - compress_reminders: false, - ..AnthropicOpts::default() - } - } - - #[test] - fn collapses_closed_history_prefix() { - let mut root = convo(18); // 37 messages - let before_len = root["messages"].as_array().unwrap().len(); - let info = transform(&mut root, &history_opts()); - assert!(info.compressed, "reason={}", info.reason); - assert_eq!(info.collapsed_turns, 20, "snapped to the 20-message grid"); - assert!(info.collapsed_images >= 1); - - let msgs = root["messages"].as_array().unwrap(); - assert!(msgs.len() < before_len, "prefix collapsed"); - // First user message is protected (untouched). - assert_eq!(msgs[0]["content"], "start the task"); - // Synthetic history message: intro text, then an image. - let syn = msgs[1]["content"].as_array().unwrap(); - assert!(syn[0]["text"] - .as_str() - .unwrap() - .contains("EARLIER conversation")); - assert!(syn.iter().any(|b| b["type"] == "image")); - assert!(syn.last().unwrap()["text"] - .as_str() - .unwrap() - .contains("live request follows")); - } - - #[test] - fn history_render_is_cache_stable() { - let mut a = convo(18); - let mut b = convo(18); - transform(&mut a, &history_opts()); - transform(&mut b, &history_opts()); - assert_eq!(a, b, "same conversation must collapse to identical bytes"); - } - - #[test] - fn short_history_not_collapsed() { - // Below min_collapse_prefix_messages (20) → left as text. - let mut root = convo(5); // 11 messages - let before = root.clone(); - let info = transform(&mut root, &history_opts()); - assert!(!info.compressed); - assert_eq!(root, before); - } - - #[test] - fn total_image_budget_is_enforced() { - // Many large tool_results but a tiny total budget: imaging stops at the - // cap so the request can't exceed Anthropic's per-request image limit. - let mut msgs = vec![json!({ "role": "user", "content": "start" })]; - for i in 0..10 { - msgs.push(json!({ - "role": "user", - "content": [{ "type": "tool_result", "tool_use_id": format!("tu_{i}"), "content": big_text() }] - })); - } - let mut root = json!({ "model": "claude-fable-5", "system": "small", "messages": msgs }); - let opts = AnthropicOpts { - max_total_images: 3, - ..AnthropicOpts::default() - }; - let info = transform(&mut root, &opts); - assert!(info.compressed); - assert!( - info.image_count <= 3, - "aggregate image budget exceeded: {}", - info.image_count - ); - } - - #[test] - fn tool_result_carrying_an_image_is_left_alone() { - // A screenshot + long log in one tool_result: imaging the log would drop - // the screenshot, so the whole result must pass through untouched. - let mut root = json!({ - "model": "claude-fable-5", - "system": "small", - "messages": [{ - "role": "user", - "content": [{ - "type": "tool_result", - "tool_use_id": "tu_1", - "content": [ - { "type": "image", "source": { "type": "base64", "media_type": "image/png", "data": "AAAA" } }, - { "type": "text", "text": big_text() } - ] - }] - }] - }); - let before = root.clone(); - let info = transform(&mut root, &AnthropicOpts::default()); - assert!( - !info.compressed, - "tool_result with an image must not be imaged" - ); - assert_eq!(root, before); - } - - /// True unless a tool_result references a tool_use id not present as a real - /// block earlier in the message list (an orphan the API rejects). - fn no_orphaned_tool_results(root: &Value) -> bool { - let mut seen = std::collections::HashSet::new(); - for m in root["messages"].as_array().unwrap() { - let Some(blocks) = m.get("content").and_then(|c| c.as_array()) else { - continue; - }; - for b in blocks { - match b.get("type").and_then(|t| t.as_str()) { - Some("tool_use") => { - if let Some(id) = b.get("id").and_then(|i| i.as_str()) { - seen.insert(id.to_string()); - } - } - Some("tool_result") => { - if let Some(id) = b.get("tool_use_id").and_then(|i| i.as_str()) { - if !seen.contains(id) { - return false; - } - } - } - _ => {} - } - } - } - true - } - - #[test] - fn history_snap_never_orphans_a_tool_result() { - // A text-only assistant turn right after the first user message shifts - // parity so the chunk-grid line (61) lands mid-pair on an OPEN tool_use; - // the snap must back off to the nearest closed boundary (60) instead of - // orphaning tu_29's tool_result into the tail. - let mut msgs = vec![json!({ "role": "user", "content": "start" })]; - msgs.push(json!({ "role": "assistant", "content": [{ "type": "text", "text": "thinking out loud" }] })); - for i in 0..40 { - msgs.push(json!({ - "role": "assistant", - "content": [ - { "type": "text", "text": format!("step {i}") }, - { "type": "tool_use", "id": format!("tu_{i}"), "name": "run", "input": { "cmd": format!("do {i}") } } - ] - })); - msgs.push(json!({ - "role": "user", - "content": [{ "type": "tool_result", "tool_use_id": format!("tu_{i}"), "content": format!("out /p/f{i}.rs {}", "detail ".repeat(60)) }] - })); - } - let mut root = json!({ "model": "claude-fable-5", "system": "small", "messages": msgs }); - let info = transform(&mut root, &history_opts()); - assert!(info.compressed, "reason={}", info.reason); - assert!( - no_orphaned_tool_results(&root), - "history collapse orphaned a tool_result" - ); - } - - #[test] - fn open_tool_call_not_crossed() { - // Last prefix turn leaves a tool_use unmatched (open); the boundary must - // stop before it so no tool call is orphaned into the image. - let mut root = convo(18); - // Drop the tool_result of an early pair to open a call at message 4. - root["messages"][4] = json!({ "role": "user", "content": "no tool result here" }); - let info = transform(&mut root, &history_opts()); - // With an open call at msg 3 (tu_1) never closed, the closed boundary - // can't advance past msg 2, so the 20-message grid step never fills. - assert!(!info.compressed || info.collapsed_turns == 0); - } -} diff --git a/crates/pxpipe/src/transform/anthropic/common.rs b/crates/pxpipe/src/transform/anthropic/common.rs new file mode 100644 index 0000000..ed2fc5f --- /dev/null +++ b/crates/pxpipe/src/transform/anthropic/common.rs @@ -0,0 +1,340 @@ +use crate::render::{render_text, RenderOpts}; +use crate::transform::factsheet; +use crate::transform::gate; +use base64::Engine; +use serde_json::{json, Map, Value}; + +#[derive(Clone, Copy, Debug)] +pub struct AnthropicOpts { + pub cols: usize, + pub max_height_px: usize, + pub chars_per_token: f64, + /// Skip entirely below this many slab chars (per-image cost dominates). + pub min_compress_chars: usize, + /// Move tool descriptions/schema annotations into the imaged Tool Reference. + pub compress_tools: bool, + /// Image large `` text blocks in the first user message. + pub compress_reminders: bool, + /// Image large `tool_result` text content across all user messages. + pub compress_tool_results: bool, + /// Per-block char floor for reminder/tool_result imaging. + pub min_live_block_chars: usize, + /// Cap on images per tool_result; source is truncated (with a paging marker) + /// above this so one giant result can't blow Anthropic's 100-image/request cap. + pub max_images_per_tool_result: usize, + /// Aggregate ceiling on TOTAL image blocks in the outgoing request + /// (client-supplied + every pxpipe pass combined). Anthropic rejects requests + /// past ~100 images, so imaging stops once this budget is exhausted and the + /// remaining regions pass through as text. The per-tool_result cap only bounds + /// one result; this bounds the sum. + pub max_total_images: usize, + /// Collapse the OLD closed-tool-call conversation prefix into history image(s), + /// keeping the recent tail as text. Default OFF — highest cache-stability risk + /// (see `apply_history`); gate on live validation before enabling by default. + pub compress_history: bool, + /// Trailing messages kept as live text (never collapsed). + pub keep_tail_messages: usize, + /// Minimum collapsible messages; below this the cache-amortization math doesn't pay. + pub min_collapse_prefix_messages: usize, + /// Snap the collapse boundary to this message-grid so the rendered history PNG + /// stays byte-identical across turns and keeps hitting the prompt cache. + pub history_collapse_chunk: usize, +} + +impl Default for AnthropicOpts { + fn default() -> Self { + Self { + cols: crate::render::RenderOpts::default().cols, + max_height_px: crate::render::RenderOpts::default().max_height_px, + // Slab is dense; 2.0 is conservative vs the real ~1.9 (see pxpipe + // SLAB_CHARS_PER_TOKEN) so the gate biases toward pass-through. + chars_per_token: 2.0, + min_compress_chars: 6_000, + compress_tools: true, + compress_reminders: true, + compress_tool_results: true, + min_live_block_chars: 2_000, + max_images_per_tool_result: 10, + // Leave headroom under Anthropic's 100-image/request cap for a few + // client-supplied images the counter may miss on unusual shapes. + max_total_images: 95, + compress_history: false, + keep_tail_messages: 8, + min_collapse_prefix_messages: 20, + history_collapse_chunk: 20, + } + } +} + +/// Neutral framing header co-rendered into the slab image. Deliberately avoids +/// "system prompt"/"authoritative" wording — pxpipe found that phrasing trips +/// Anthropic's reasoning_extraction refusal (reads as a replayed/extracted +/// prompt). First-party, matter-of-fact framing keeps the model reading it as +/// this session's own reference material. +pub(crate) const SLAB_HEADER: &str = + "Reference context for this session, rendered as an image. Read it as text.\n\n"; + +/// Chars-per-token for the live-region gate (reminders, tool_results). Higher +/// than the slab's 2.0 because that content is prose/log, not dense config — +/// pxpipe uses 4 here, which is conservative (biases toward pass-through). +pub(crate) const LIVE_CHARS_PER_TOKEN: f64 = 4.0; + +/// Banner text bracketing the collapsed-history image(s). Constant (byte-stable). +pub(crate) const HISTORY_INTRO: &str = "Transcript of EARLIER conversation turns, rendered as images below. \ +Attribute each turn strictly by its / tag. This is PAST context, not the live request.\n"; +pub(crate) const HISTORY_OUTRO: &str = + "\n[End of earlier transcript. The live request follows in the messages below.]"; + +/// Concat the text of a system field (string or block array) and return +/// `(text, last_cache_control)`. The cache_control is the caller's prefix +/// breakpoint we will relocate onto the image. +pub(crate) fn read_system(system: Option<&Value>) -> (String, Option) { + match system { + Some(Value::String(s)) => (s.clone(), None), + Some(Value::Array(blocks)) => { + let mut text = String::new(); + let mut cc = None; + for b in blocks { + if let Some(t) = b.get("text").and_then(|t| t.as_str()) { + if !text.is_empty() { + text.push('\n'); + } + text.push_str(t); + } + if let Some(c) = b.get("cache_control") { + cc = Some(c.clone()); // last one wins (latest in prefix) + } + } + (text, cc) + } + _ => (String::new(), None), + } +} + +/// Render one tool's full doc (prose + compact schema) for the imaged reference. +pub(crate) fn render_tool_doc(tool: &Value) -> String { + let name = tool.get("name").and_then(|n| n.as_str()).unwrap_or(""); + let desc = tool + .get("description") + .and_then(|d| d.as_str()) + .unwrap_or(""); + let schema = tool + .get("input_schema") + .map(|s| serde_json::to_string(s).unwrap_or_default()) + .unwrap_or_default(); + format!("## Tool: {name}\n{desc}\n```json\n{schema}\n```\n") +} + +/// Build a base64 image content block from PNG bytes. +pub(crate) fn image_block(png: &[u8]) -> Value { + let data = base64::engine::general_purpose::STANDARD.encode(png); + json!({ + "type": "image", + "source": { "type": "base64", "media_type": "image/png", "data": data } + }) +} + +/// Count image blocks already present in the request (client-supplied, incl. +/// images nested inside `tool_result` content), so the added-image budget is +/// measured against the true outgoing total under Anthropic's ~100-image cap. +pub(crate) fn count_existing_images(root: &Value) -> usize { + let Some(msgs) = root.get("messages").and_then(|m| m.as_array()) else { + return 0; + }; + let is_image = |b: &Value| b.get("type").and_then(|t| t.as_str()) == Some("image"); + let mut n = 0; + for m in msgs { + let Some(blocks) = m.get("content").and_then(|c| c.as_array()) else { + continue; + }; + for b in blocks { + match b.get("type").and_then(|t| t.as_str()) { + Some("image") => n += 1, + Some("tool_result") => { + if let Some(inner) = b.get("content").and_then(|c| c.as_array()) { + n += inner.iter().filter(|x| is_image(x)).count(); + } + } + _ => {} + } + } + } + n +} + +/// Prepend `images` to the first user message's content. Converts string +/// content to a text block first; inserts a fresh user message if none exists. +pub(crate) fn prepend_images_to_first_user(obj: &mut Map, images: &mut Vec) { + let msgs = obj + .entry("messages") + .or_insert_with(|| Value::Array(vec![])); + let Some(arr) = msgs.as_array_mut() else { + return; + }; + let first_user = arr + .iter() + .position(|m| m.get("role").and_then(|r| r.as_str()) == Some("user")); + + match first_user { + Some(idx) => { + let content = arr[idx].as_object_mut().and_then(|m| m.get_mut("content")); + match content { + Some(Value::String(s)) => { + let text = std::mem::take(s); + let mut blocks = vec![json!({ "type": "text", "text": text })]; + let mut new = std::mem::take(images); + new.append(&mut blocks); + arr[idx] + .as_object_mut() + .unwrap() + .insert("content".into(), Value::Array(new)); + } + Some(Value::Array(existing)) => { + let mut new = std::mem::take(images); + new.append(existing); + *existing = new; + } + _ => { + arr[idx] + .as_object_mut() + .unwrap() + .insert("content".into(), Value::Array(std::mem::take(images))); + } + } + } + None => { + arr.insert( + 0, + json!({ "role": "user", "content": std::mem::take(images) }), + ); + } + } +} + +/// Rendered-live-block accumulators, folded into `TransformInfo` by the caller. +pub(crate) struct LiveRender { + pub(crate) blocks: Vec, + pub(crate) bytes: usize, + pub(crate) pixels: usize, + pub(crate) dropped: usize, + pub(crate) img_count: usize, +} + +/// Render `text` to image blocks for a live region (reminder / tool_result), +/// gated on profitability AND the remaining `budget` of new images. Moves `cc` +/// (the block's cache_control, if any) onto the last image and optionally +/// appends a verbatim fact-sheet text block. Returns `None` (leave the block as +/// text) when empty, not profitable, or over budget. +pub(crate) fn render_live_block( + text: &str, + opts: &AnthropicOpts, + cc: Option, + append_factsheet: bool, + budget: usize, +) -> Option { + let images = render_text( + text, + RenderOpts { + cols: opts.cols, + max_height_px: opts.max_height_px, + }, + ); + let chars = text.chars().count(); + if images.len() > budget { + return None; + } + if !gate::is_profitable(&images, chars, LIVE_CHARS_PER_TOKEN) { + return None; + } + let mut blocks: Vec = images.iter().map(|im| image_block(&im.png)).collect(); + if let (Some(cc), Some(last)) = (cc, blocks.last_mut()) { + if let Some(o) = last.as_object_mut() { + o.insert("cache_control".into(), cc); + } + } + if append_factsheet { + let fact = factsheet::fact_sheet_text(text); + if !fact.is_empty() { + blocks.push(json!({ "type": "text", "text": fact })); + } + } + Some(LiveRender { + blocks, + bytes: images.iter().map(|im| im.png.len()).sum(), + pixels: images + .iter() + .map(|im| im.width as usize * im.height as usize) + .sum(), + dropped: images.iter().map(|im| im.dropped).sum(), + img_count: images.len(), + }) +} + +/// Flatten a tool_result `content` (string, or an array of text/other blocks) +/// to a single text string for rendering. +pub(crate) fn tool_result_text(content: Option<&Value>) -> String { + match content { + Some(Value::String(s)) => s.clone(), + Some(Value::Array(blocks)) => { + let mut out = String::new(); + for b in blocks { + if let Some(t) = b.get("text").and_then(|t| t.as_str()) { + if !out.is_empty() { + out.push('\n'); + } + out.push_str(t); + } + } + out + } + _ => String::new(), + } +} + +/// True when a tool_result `content` array already holds an image block. Imaging +/// such a result would drop that image (only text sub-blocks are flattened), so +/// the caller skips it. +pub(crate) fn tool_result_has_image(content: Option<&Value>) -> bool { + matches!(content, Some(Value::Array(blocks)) + if blocks + .iter() + .any(|b| b.get("type").and_then(|t| t.as_str()) == Some("image"))) +} + +/// Truncate `text` so it renders to at most `max_images` pages, keeping a 60/40 +/// head/tail split with a paging marker in the middle. Returns the (possibly +/// truncated) text and the count of chars elided. Renders once to measure — the +/// oversized path is rare, so the extra render is acceptable. +pub(crate) fn truncate_for_budget( + text: &str, + max_images: usize, + opts: &AnthropicOpts, +) -> (String, usize) { + let render_opts = RenderOpts { + cols: opts.cols, + max_height_px: opts.max_height_px, + }; + let full = render_text(text, render_opts); + let cap = max_images.max(1); + if full.len() <= cap { + return (text.to_string(), 0); + } + let chars: Vec = text.chars().collect(); + // Start from the linear estimate, then shrink and RE-RENDER until it truly + // fits `cap` pages — the paging marker and heavy line-wrapping can push a + // linear estimate over, so the cap must be verified, not assumed. + let mut ratio = cap as f64 / full.len() as f64; + loop { + let keep = (((chars.len() as f64) * ratio) as usize).min(chars.len()); + let head_len = keep * 6 / 10; + let tail_len = keep - head_len; + let head: String = chars[..head_len].iter().collect(); + let tail: String = chars[chars.len() - tail_len..].iter().collect(); + let omitted = chars.len() - head_len - tail_len; + let out = format!("{head}\n\n[... {omitted} chars omitted for length ...]\n\n{tail}"); + if render_text(&out, render_opts).len() <= cap || ratio < 0.05 { + return (out, omitted); + } + ratio *= 0.8; + } +} diff --git a/crates/pxpipe/src/transform/anthropic/history.rs b/crates/pxpipe/src/transform/anthropic/history.rs new file mode 100644 index 0000000..6fe69b3 --- /dev/null +++ b/crates/pxpipe/src/transform/anthropic/history.rs @@ -0,0 +1,199 @@ +use super::common::{image_block, tool_result_text, AnthropicOpts, HISTORY_INTRO, HISTORY_OUTRO}; +use crate::render::{render_text, RenderOpts}; +use crate::transform::gate; +use crate::transform::info::TransformInfo; +use serde_json::{json, Value}; + +/// Collapse the OLD closed-tool-call message prefix into ONE synthetic user +/// message holding history image(s); keep the recent tail as text. +/// +/// **Cache stability** is the whole risk here. Two guarantees keep the rendered +/// PNG byte-identical across turns so Anthropic prompt-caches it instead of +/// re-creating it every turn: (1) the collapse boundary is snapped DOWN to a +/// `history_collapse_chunk` message grid, so it only advances in steps and the +/// serialized text is stable for a whole window; (2) the serializer and renderer +/// are pure functions of the message bytes (no timestamps/rng, thinking blocks +/// dropped deterministically). If either breaks, this NET-LOSES money — hence +/// default-off until validated live. +/// +/// **Correctness**: only a tool-CLOSED prefix is collapsed (every `tool_use` has +/// its matching `tool_result` within the range), so no tool call is ever +/// orphaned. The first user message (which carries the slab images) is protected. +/// +/// NOTE: the synthetic message is role `user`, which can place it adjacent to the +/// protected first user message. The Anthropic Messages API accepts consecutive +/// same-role messages (pxpipe relies on this in production); images require the +/// user role, so this is unavoidable for a history-image message. +pub(crate) fn apply_history( + root: &mut Value, + opts: &AnthropicOpts, + info: &mut TransformInfo, + budget: &mut usize, +) { + let Some(messages) = root.get("messages").and_then(|m| m.as_array()) else { + return; + }; + let len = messages.len(); + // Protect the slab-bearing first user message: collapse starts after it. + let Some(first_user) = messages + .iter() + .position(|m| m.get("role").and_then(|r| r.as_str()) == Some("user")) + else { + return; + }; + let protected = first_user + 1; + let cutoff = len.saturating_sub(opts.keep_tail_messages); + if cutoff <= protected { + return; + } + let Some(boundary) = find_closed_boundary(messages, cutoff, protected) else { + return; + }; + // Snap DOWN to the chunk grid (relative to `protected`) for byte-stability. + let chunk = opts.history_collapse_chunk.max(1); + let grid = protected + ((boundary - protected) / chunk) * chunk; + // The grid line is NOT guaranteed tool-closed (parity shifts from text-only + // turns or parallel tool spans can leave it mid-open-span). Re-snap to the + // largest CLOSED boundary <= the grid line so no tool_use is orphaned into + // the history image; correctness beats the grid's cache-stability here. + let Some(snapped) = find_closed_boundary(messages, grid, protected) else { + return; + }; + if snapped.saturating_sub(protected) < opts.min_collapse_prefix_messages { + return; + } + + let text = messages_to_history_text(messages, protected, snapped); + if text.trim().is_empty() { + return; + } + let images = render_text( + &text, + RenderOpts { + cols: opts.cols, + max_height_px: opts.max_height_px, + }, + ); + if !gate::is_profitable(&images, text.chars().count(), opts.chars_per_token) { + return; + } + if images.len() > *budget { + return; + } + *budget -= images.len(); + + // Build the synthetic message content: intro, images, outro. + let mut content: Vec = Vec::with_capacity(images.len() + 2); + content.push(json!({ "type": "text", "text": HISTORY_INTRO })); + content.extend(images.iter().map(|im| image_block(&im.png))); + content.push(json!({ "type": "text", "text": HISTORY_OUTRO })); + let synthetic = json!({ "role": "user", "content": content }); + + let collapsed_turns = snapped - protected; + info.collapsed_turns = collapsed_turns; + info.collapsed_chars = text.len(); + info.collapsed_images = images.len(); + info.image_count += images.len(); + info.image_bytes += images.iter().map(|im| im.png.len()).sum::(); + info.image_pixels += images + .iter() + .map(|im| im.width as usize * im.height as usize) + .sum::(); + info.dropped_chars += images.iter().map(|im| im.dropped).sum::(); + info.compressed_chars += text.len(); + + // Splice: [0..protected] + synthetic + [snapped..]. + let arr = root + .get_mut("messages") + .and_then(|m| m.as_array_mut()) + .expect("messages was an array above"); + arr.splice(protected..snapped, std::iter::once(synthetic)); +} + +/// Largest exclusive end `e` in `(from, cutoff]` where messages `[from..e)` open +/// no tool call they don't also close. Returns `None` if none exists. Robust to +/// interleaved/parallel tool calls via the open-id set. +fn find_closed_boundary(messages: &[Value], cutoff: usize, from: usize) -> Option { + let mut open: std::collections::HashSet = std::collections::HashSet::new(); + let mut last_closed = None; + for (i, m) in messages.iter().enumerate().take(cutoff).skip(from) { + if let Some(blocks) = m.get("content").and_then(|c| c.as_array()) { + for b in blocks { + match b.get("type").and_then(|t| t.as_str()) { + Some("tool_use") => { + if let Some(id) = b.get("id").and_then(|i| i.as_str()) { + open.insert(id.to_string()); + } + } + Some("tool_result") => { + if let Some(id) = b.get("tool_use_id").and_then(|i| i.as_str()) { + open.remove(id); + } + } + _ => {} + } + } + } + if open.is_empty() { + last_closed = Some(i + 1); + } + } + last_closed +} + +/// Serialize messages `[from..to)` to `` XML text. thinking blocks +/// dropped; tool_use/tool_result flattened; inline images become `[image]`. +fn messages_to_history_text(messages: &[Value], from: usize, to: usize) -> String { + let mut out = String::new(); + for m in &messages[from..to] { + let role = m.get("role").and_then(|r| r.as_str()).unwrap_or("user"); + let body = flatten_content(m.get("content")); + out.push('<'); + out.push_str(role); + out.push_str(">\n"); + out.push_str(&body); + out.push_str("\n\n"); + } + out +} + +/// Flatten one message's content to text: text verbatim, tool_use/tool_result to +/// a compact marker, thinking dropped, inline images to `[image]`. +fn flatten_content(content: Option<&Value>) -> String { + match content { + Some(Value::String(s)) => s.clone(), + Some(Value::Array(blocks)) => { + let mut parts: Vec = Vec::new(); + for b in blocks { + match b.get("type").and_then(|t| t.as_str()) { + Some("text") => { + if let Some(t) = b.get("text").and_then(|t| t.as_str()) { + parts.push(t.to_string()); + } + } + Some("tool_use") => { + let name = b.get("name").and_then(|n| n.as_str()).unwrap_or(""); + let input = b + .get("input") + .map(|v| serde_json::to_string(v).unwrap_or_default()) + .unwrap_or_default(); + parts.push(format!("[tool_use {name} {input}]")); + } + Some("tool_result") => { + parts.push(format!( + "[tool_result {}]", + tool_result_text(b.get("content")) + )); + } + Some("image") => parts.push("[image]".to_string()), + // thinking / redacted_thinking / unknown: dropped. + _ => {} + } + } + parts.join("\n") + } + _ => String::new(), + } +} diff --git a/crates/pxpipe/src/transform/anthropic/mod.rs b/crates/pxpipe/src/transform/anthropic/mod.rs new file mode 100644 index 0000000..aa26094 --- /dev/null +++ b/crates/pxpipe/src/transform/anthropic/mod.rs @@ -0,0 +1,53 @@ +pub mod common; +pub mod history; +pub mod reminders; +pub mod slab; +pub mod tool_results; + +#[cfg(test)] +mod tests; + +use crate::transform::info::TransformInfo; +use serde_json::Value; + +pub use common::AnthropicOpts; + +/// Transform `root` (a parsed Anthropic Messages body) in place. Returns info; +/// on any full skip `root` is left byte-identical to the input. Three +/// independent passes — static slab, `` blocks, and +/// `tool_result` content — so a request below the slab floor can still get its +/// live regions imaged (and vice-versa). +pub fn transform(root: &mut Value, opts: &AnthropicOpts) -> TransformInfo { + if !root.is_object() { + return TransformInfo::skipped("parse_error"); + } + let mut info = TransformInfo::default(); + // NEW-image budget: total ceiling minus images the client already sent, so + // the combined passes never push the request past Anthropic's ~100-image cap. + let mut budget = opts + .max_total_images + .saturating_sub(common::count_existing_images(root)); + let slab_reason = slab::apply_slab(root, opts, &mut info, &mut budget); + // History runs BEFORE the live-region passes: it serializes the OLD message + // prefix to text, so tool_result imaging must not have already replaced that + // content with `[image]` placeholders. Reminders/tool_results then image only + // what survives (the protected first message + the live tail). + if opts.compress_history { + history::apply_history(root, opts, &mut info, &mut budget); + } + if opts.compress_reminders { + reminders::apply_reminders(root, opts, &mut info, &mut budget); + } + if opts.compress_tool_results { + tool_results::apply_tool_results(root, opts, &mut info, &mut budget); + } + info.compressed = info.image_count > 0; + // "applied" when anything imaged; otherwise report why the slab (the primary + // path) declined. + info.reason = if info.compressed { + "applied" + } else { + slab_reason + }; + info +} diff --git a/crates/pxpipe/src/transform/anthropic/reminders.rs b/crates/pxpipe/src/transform/anthropic/reminders.rs new file mode 100644 index 0000000..4d71eba --- /dev/null +++ b/crates/pxpipe/src/transform/anthropic/reminders.rs @@ -0,0 +1,58 @@ +use super::common::{render_live_block, AnthropicOpts}; +use crate::transform::info::TransformInfo; +use serde_json::Value; + +/// Image large `` text blocks in the first user message. These +/// are per-turn injected context (env, hints) that Claude Code ships as separate +/// text blocks; the big ones are pure token cost the model rarely needs to quote. +pub(crate) fn apply_reminders( + root: &mut Value, + opts: &AnthropicOpts, + info: &mut TransformInfo, + budget: &mut usize, +) { + let Some(arr) = root.get_mut("messages").and_then(|m| m.as_array_mut()) else { + return; + }; + let Some(msg) = arr + .iter_mut() + .find(|m| m.get("role").and_then(|r| r.as_str()) == Some("user")) + else { + return; + }; + let Some(content) = msg.get_mut("content").and_then(|c| c.as_array_mut()) else { + return; // string content: reminders arrive only as array blocks + }; + + let mut out: Vec = Vec::with_capacity(content.len()); + for block in std::mem::take(content) { + let is_reminder_text = block.get("type").and_then(|t| t.as_str()) == Some("text") + && block.get("text").and_then(|t| t.as_str()).is_some_and(|t| { + t.contains("") && t.len() >= opts.min_live_block_chars + }); + if !is_reminder_text { + out.push(block); + continue; + } + let text = block + .get("text") + .and_then(|t| t.as_str()) + .unwrap_or_default() + .to_string(); + let cc = block.get("cache_control").cloned(); + match render_live_block(&text, opts, cc, false, *budget) { + Some(r) => { + *budget -= r.img_count; + info.reminder_imgs += r.img_count; + info.image_count += r.img_count; + info.image_bytes += r.bytes; + info.image_pixels += r.pixels; + info.dropped_chars += r.dropped; + info.compressed_chars += text.len(); + out.extend(r.blocks); + } + None => out.push(block), + } + } + *content = out; +} diff --git a/crates/pxpipe/src/transform/anthropic/slab.rs b/crates/pxpipe/src/transform/anthropic/slab.rs new file mode 100644 index 0000000..6373cd7 --- /dev/null +++ b/crates/pxpipe/src/transform/anthropic/slab.rs @@ -0,0 +1,151 @@ +use super::common::{ + image_block, prepend_images_to_first_user, read_system, render_tool_doc, AnthropicOpts, + SLAB_HEADER, +}; +use crate::render::{render_text, RenderOpts}; +use crate::transform::factsheet; +use crate::transform::gate; +use crate::transform::info::TransformInfo; +use crate::transform::schema_strip; +use serde_json::Value; + +/// Static system+tools slab pass. Returns the skip reason (or "applied"); only +/// mutates `root` / fills `info` when profitable. +pub(crate) fn apply_slab( + root: &mut Value, + opts: &AnthropicOpts, + info: &mut TransformInfo, + budget: &mut usize, +) -> &'static str { + let (system_text, system_cc) = read_system(root.get("system")); + + // Capture a tool cache_control as a fallback anchor (tools sit before system + // in the cache prefix, so system's wins when both exist). + let tools_present = opts.compress_tools + && root + .get("tools") + .and_then(|t| t.as_array()) + .is_some_and(|a| !a.is_empty()); + let mut tool_cc = None; + let mut tool_ref = String::new(); + if tools_present { + if let Some(arr) = root.get("tools").and_then(|t| t.as_array()) { + for t in arr { + tool_ref.push_str(&render_tool_doc(t)); + if let Some(c) = t.get("cache_control") { + tool_cc = Some(c.clone()); + } + } + } + } + + // Assemble the slab: header + system + tool reference. + let mut slab = String::with_capacity(system_text.len() + tool_ref.len() + 64); + slab.push_str(SLAB_HEADER); + slab.push_str(&system_text); + if !tool_ref.is_empty() { + slab.push_str("\n\n# Tool Reference\n"); + slab.push_str(&tool_ref); + } + + let slab_chars = slab.chars().count(); + // Nothing meaningful to image (no system text and no tools). + if system_text.trim().is_empty() && tool_ref.is_empty() { + return "no_slab"; + } + if slab_chars < opts.min_compress_chars { + return "below_min_chars"; + } + + let images = render_text( + &slab, + RenderOpts { + cols: opts.cols, + max_height_px: opts.max_height_px, + }, + ); + if !gate::is_profitable(&images, slab_chars, opts.chars_per_token) { + return "not_profitable"; + } + if images.len() > *budget { + return "image_budget"; + } + + // ---- commit: mutate root ------------------------------------------------ + let anchor = system_cc.or(tool_cc); // relocate this onto the last image + let fact = factsheet::fact_sheet_text(&slab); + + let mut image_blocks: Vec = images.iter().map(|im| image_block(&im.png)).collect(); + let image_bytes: usize = images.iter().map(|im| im.png.len()).sum(); + let image_pixels: usize = images + .iter() + .map(|im| im.width as usize * im.height as usize) + .sum(); + let dropped: usize = images.iter().map(|im| im.dropped).sum(); + + // Relocate the caller's cache breakpoint onto the LAST image so the whole + // imaged prefix caches as one stable segment. pxpipe never *adds* a marker. + let relocated = if let (Some(cc), Some(last)) = (anchor, image_blocks.last_mut()) { + if let Some(obj) = last.as_object_mut() { + obj.insert("cache_control".into(), cc); + } + true + } else { + false + }; + + let obj = root.as_object_mut().expect("checked is_object above"); + + // 1. system -> short pointer (+ factsheet). Removes original cache_control + // (relocated onto the image) since we're replacing the whole field. + let mut pointer = String::from( + "[Your reference context for this session is provided as an image in the first user message. Read it there.]", + ); + if !fact.is_empty() { + pointer.push('\n'); + pointer.push_str(&fact); + } + obj.insert("system".into(), Value::String(pointer)); + + // 2. tools -> stub description + annotation-stripped schema, drop the + // relocated cache_control. Keep name + structural schema so Anthropic's + // tool-use validator still accepts calls. + if tools_present { + if let Some(arr) = obj.get_mut("tools").and_then(|t| t.as_array_mut()) { + for t in arr.iter_mut() { + let Some(tm) = t.as_object_mut() else { + continue; + }; + let name = tm + .get("name") + .and_then(|n| n.as_str()) + .unwrap_or("") + .to_string(); + tm.insert( + "description".into(), + Value::String(format!("See \"## Tool: {name}\" in the reference image.")), + ); + if let Some(schema) = tm.get("input_schema") { + let stripped = schema_strip::strip(schema); + if schema_strip::has_structure(&stripped) { + tm.insert("input_schema".into(), stripped); + } + // else keep original: a bare {type:object} stub causes 400s. + } + tm.remove("cache_control"); + } + } + } + + // 3. Prepend image blocks to the first user message (system rejects images). + prepend_images_to_first_user(obj, &mut image_blocks); + + info.compressed_chars += slab.len(); + info.image_count += images.len(); + info.image_bytes += image_bytes; + info.image_pixels += image_pixels; + info.dropped_chars += dropped; + info.relocated_cache_anchor = relocated; + *budget -= images.len(); + "applied" +} diff --git a/crates/pxpipe/src/transform/anthropic/tests.rs b/crates/pxpipe/src/transform/anthropic/tests.rs new file mode 100644 index 0000000..47d34f6 --- /dev/null +++ b/crates/pxpipe/src/transform/anthropic/tests.rs @@ -0,0 +1,409 @@ +use super::common::AnthropicOpts; +use super::transform; +use serde_json::json; + +fn big_system() -> String { + // ~14k chars with some notable identifiers for the factsheet. + let mut s = String::from("Operating rules. See src/main.rs and CONFIG_PATH env.\n"); + s.push_str(&"Do the thing carefully and precisely. ".repeat(320)); + s.push_str(" commit deadbeef1 flag --verbose"); + s +} + +#[test] +fn images_system_and_relocates_anchor() { + let mut root = json!({ + "model": "claude-fable-5", + "system": [ + { "type": "text", "text": big_system(), "cache_control": { "type": "ephemeral" } } + ], + "messages": [ { "role": "user", "content": "hi there" } ] + }); + let info = transform(&mut root, &AnthropicOpts::default()); + assert!(info.compressed, "reason={}", info.reason); + assert!(info.relocated_cache_anchor); + + // system is now a short pointer string. + assert!(root["system"].is_string()); + assert!(root["system"] + .as_str() + .unwrap() + .contains("first user message")); + + // first user message leads with an image block carrying the anchor. + let content = root["messages"][0]["content"].as_array().unwrap(); + assert_eq!(content[0]["type"], "image"); + assert_eq!(content.last().unwrap()["type"], "text"); // original "hi there" + assert_eq!(content.last().unwrap()["text"], "hi there"); + // anchor is on exactly the last image, nowhere in system. + let last_img = content.iter().rev().find(|b| b["type"] == "image").unwrap(); + assert!(last_img.get("cache_control").is_some()); +} + +#[test] +fn tiny_system_passes_through_unchanged() { + let mut root = json!({ + "model": "claude-fable-5", + "system": "be helpful", + "messages": [ { "role": "user", "content": "hi" } ] + }); + let before = root.clone(); + let info = transform(&mut root, &AnthropicOpts::default()); + assert!(!info.compressed); + assert_eq!(info.reason, "below_min_chars"); + assert_eq!(root, before, "skip path must not mutate the body"); +} + +#[test] +fn same_input_same_output_cache_stable() { + let make = || { + json!({ + "model": "claude-fable-5", + "system": big_system(), + "messages": [ { "role": "user", "content": "go" } ] + }) + }; + let mut a = make(); + let mut b = make(); + transform(&mut a, &AnthropicOpts::default()); + transform(&mut b, &AnthropicOpts::default()); + assert_eq!(a, b, "transform must be deterministic (cache stability)"); +} + +#[test] +fn tools_stubbed_and_schema_stripped() { + let mut root = json!({ + "model": "claude-fable-5", + "system": big_system(), + "tools": [{ + "name": "edit_file", + "description": "very long tool description ".repeat(50), + "input_schema": { + "type": "object", + "description": "annotation", + "properties": { "path": { "type": "string", "description": "p" } }, + "required": ["path"] + }, + "cache_control": { "type": "ephemeral" } + }], + "messages": [ { "role": "user", "content": "go" } ] + }); + let info = transform(&mut root, &AnthropicOpts::default()); + assert!(info.compressed); + let tool = &root["tools"][0]; + assert!(tool["description"] + .as_str() + .unwrap() + .contains("## Tool: edit_file")); + assert!(tool["input_schema"].get("description").is_none()); + assert_eq!(tool["input_schema"]["required"], json!(["path"])); + assert!(tool.get("cache_control").is_none(), "tool anchor relocated"); +} + +/// Dense filler text large enough to clear the live-block gate. +fn big_text() -> String { + "log line with detail /var/run/app.sock port 8080 CODE_X 12345 ".repeat(300) +} + +#[test] +fn images_large_reminder_block() { + let mut root = json!({ + "model": "claude-fable-5", + "system": "small", + "messages": [{ + "role": "user", + "content": [ + { "type": "text", "text": format!("{}", big_text()) }, + { "type": "text", "text": "the actual question" } + ] + }] + }); + let info = transform(&mut root, &AnthropicOpts::default()); + assert!(info.compressed); + assert!(info.reminder_imgs >= 1); + let content = root["messages"][0]["content"].as_array().unwrap(); + assert_eq!(content[0]["type"], "image", "reminder replaced by image"); + // The real question text survives untouched. + assert!(content.iter().any(|b| b["text"] == "the actual question")); +} + +#[test] +fn images_large_tool_result_with_factsheet() { + let mut root = json!({ + "model": "claude-fable-5", + "system": "small", + "messages": [{ + "role": "user", + "content": [{ + "type": "tool_result", + "tool_use_id": "tu_1", + "content": big_text(), + "cache_control": { "type": "ephemeral" } + }] + }] + }); + let info = transform(&mut root, &AnthropicOpts::default()); + assert!(info.compressed); + assert!(info.tool_result_imgs >= 1); + let tr = &root["messages"][0]["content"][0]; + assert!( + tr.get("cache_control").is_none(), + "anchor relocated onto image" + ); + let inner = tr["content"].as_array().unwrap(); + assert_eq!(inner[0]["type"], "image"); + // fact-sheet text block rides alongside the image (paths/ids survive OCR). + assert!(inner + .iter() + .any(|b| b["type"] == "text" && b["text"].as_str().unwrap().contains("/var/run/app.sock"))); +} + +#[test] +fn skips_error_tool_result() { + let mut root = json!({ + "model": "claude-fable-5", + "system": "small", + "messages": [{ + "role": "user", + "content": [{ + "type": "tool_result", + "tool_use_id": "tu_1", + "is_error": true, + "content": big_text() + }] + }] + }); + let before = root.clone(); + let info = transform(&mut root, &AnthropicOpts::default()); + assert!(!info.compressed, "error tool_results must not be imaged"); + assert_eq!(root, before); +} + +#[test] +fn pages_oversized_tool_result() { + // Force a tiny image cap so a modest result trips the paging path. + let opts = AnthropicOpts { + max_images_per_tool_result: 1, + max_height_px: 80, // ~9 rows/page → many pages before truncation + ..AnthropicOpts::default() + }; + let mut root = json!({ + "model": "claude-fable-5", + "system": "small", + "messages": [{ + "role": "user", + "content": [{ + "type": "tool_result", + "tool_use_id": "tu_1", + "content": big_text() + }] + }] + }); + let info = transform(&mut root, &opts); + assert!(info.compressed); + assert_eq!(info.truncated_tool_results, 1); + assert!(info.omitted_chars > 0); +} + +/// A conversation with `pairs` closed assistant(tool_use)/user(tool_result) turns. +fn convo(pairs: usize) -> serde_json::Value { + let mut msgs = vec![json!({ "role": "user", "content": "start the task" })]; + for i in 0..pairs { + msgs.push(json!({ + "role": "assistant", + "content": [ + { "type": "text", "text": format!("step {i}") }, + { "type": "tool_use", "id": format!("tu_{i}"), "name": "run", "input": { "cmd": format!("do {i}") } } + ] + })); + msgs.push(json!({ + "role": "user", + "content": [{ + "type": "tool_result", + "tool_use_id": format!("tu_{i}"), + "content": format!("output /path/file{i}.rs {}", "detail ".repeat(60)) + }] + })); + } + json!({ "model": "claude-fable-5", "system": "small", "messages": msgs }) +} + +fn history_opts() -> AnthropicOpts { + AnthropicOpts { + compress_history: true, + // Isolate the history pass so tail tool_results don't also image. + compress_tool_results: false, + compress_reminders: false, + ..AnthropicOpts::default() + } +} + +#[test] +fn collapses_closed_history_prefix() { + let mut root = convo(18); // 37 messages + let before_len = root["messages"].as_array().unwrap().len(); + let info = transform(&mut root, &history_opts()); + assert!(info.compressed, "reason={}", info.reason); + assert_eq!(info.collapsed_turns, 20, "snapped to the 20-message grid"); + assert!(info.collapsed_images >= 1); + + let msgs = root["messages"].as_array().unwrap(); + assert!(msgs.len() < before_len, "prefix collapsed"); + // First user message is protected (untouched). + assert_eq!(msgs[0]["content"], "start the task"); + // Synthetic history message: intro text, then an image. + let syn = msgs[1]["content"].as_array().unwrap(); + assert!(syn[0]["text"] + .as_str() + .unwrap() + .contains("EARLIER conversation")); + assert!(syn.iter().any(|b| b["type"] == "image")); + assert!(syn.last().unwrap()["text"] + .as_str() + .unwrap() + .contains("live request follows")); +} + +#[test] +fn history_render_is_cache_stable() { + let mut a = convo(18); + let mut b = convo(18); + transform(&mut a, &history_opts()); + transform(&mut b, &history_opts()); + assert_eq!(a, b, "same conversation must collapse to identical bytes"); +} + +#[test] +fn short_history_not_collapsed() { + // Below min_collapse_prefix_messages (20) → left as text. + let mut root = convo(5); // 11 messages + let before = root.clone(); + let info = transform(&mut root, &history_opts()); + assert!(!info.compressed); + assert_eq!(root, before); +} + +#[test] +fn total_image_budget_is_enforced() { + // Many large tool_results but a tiny total budget: imaging stops at the + // cap so the request can't exceed Anthropic's per-request image limit. + let mut msgs = vec![json!({ "role": "user", "content": "start" })]; + for i in 0..10 { + msgs.push(json!({ + "role": "user", + "content": [{ "type": "tool_result", "tool_use_id": format!("tu_{i}"), "content": big_text() }] + })); + } + let mut root = json!({ "model": "claude-fable-5", "system": "small", "messages": msgs }); + let opts = AnthropicOpts { + max_total_images: 3, + ..AnthropicOpts::default() + }; + let info = transform(&mut root, &opts); + assert!(info.compressed); + assert!( + info.image_count <= 3, + "aggregate image budget exceeded: {}", + info.image_count + ); +} + +#[test] +fn tool_result_carrying_an_image_is_left_alone() { + // A screenshot + long log in one tool_result: imaging the log would drop + // the screenshot, so the whole result must pass through untouched. + let mut root = json!({ + "model": "claude-fable-5", + "system": "small", + "messages": [{ + "role": "user", + "content": [{ + "type": "tool_result", + "tool_use_id": "tu_1", + "content": [ + { "type": "image", "source": { "type": "base64", "media_type": "image/png", "data": "AAAA" } }, + { "type": "text", "text": big_text() } + ] + }] + }] + }); + let before = root.clone(); + let info = transform(&mut root, &AnthropicOpts::default()); + assert!( + !info.compressed, + "tool_result with an image must not be imaged" + ); + assert_eq!(root, before); +} + +/// True unless a tool_result references a tool_use id not present as a real +/// block earlier in the message list (an orphan the API rejects). +fn no_orphaned_tool_results(root: &serde_json::Value) -> bool { + let mut seen = std::collections::HashSet::new(); + for m in root["messages"].as_array().unwrap() { + let Some(blocks) = m.get("content").and_then(|c| c.as_array()) else { + continue; + }; + for b in blocks { + match b.get("type").and_then(|t| t.as_str()) { + Some("tool_use") => { + if let Some(id) = b.get("id").and_then(|i| i.as_str()) { + seen.insert(id.to_string()); + } + } + Some("tool_result") => { + if let Some(id) = b.get("tool_use_id").and_then(|i| i.as_str()) { + if !seen.contains(id) { + return false; + } + } + } + _ => {} + } + } + } + true +} + +#[test] +fn history_snap_never_orphans_a_tool_result() { + // A text-only assistant turn right after the first user message shifts + // parity so the chunk-grid line (61) lands mid-pair on an OPEN tool_use; + // the snap must back off to the nearest closed boundary (60) instead of + // orphaning tu_29's tool_result into the tail. + let mut msgs = vec![json!({ "role": "user", "content": "start" })]; + msgs.push(json!({ "role": "assistant", "content": [{ "type": "text", "text": "thinking out loud" }] })); + for i in 0..40 { + msgs.push(json!({ + "role": "assistant", + "content": [ + { "type": "text", "text": format!("step {i}") }, + { "type": "tool_use", "id": format!("tu_{i}"), "name": "run", "input": { "cmd": format!("do {i}") } } + ] + })); + msgs.push(json!({ + "role": "user", + "content": [{ "type": "tool_result", "tool_use_id": format!("tu_{i}"), "content": format!("out /p/f{i}.rs {}", "detail ".repeat(60)) }] + })); + } + let mut root = json!({ "model": "claude-fable-5", "system": "small", "messages": msgs }); + let info = transform(&mut root, &history_opts()); + assert!(info.compressed, "reason={}", info.reason); + assert!( + no_orphaned_tool_results(&root), + "history collapse orphaned a tool_result" + ); +} + +#[test] +fn open_tool_call_not_crossed() { + // Last prefix turn leaves a tool_use unmatched (open); the boundary must + // stop before it so no tool call is orphaned into the image. + let mut root = convo(18); + // Drop the tool_result of an early pair to open a call at message 4. + root["messages"][4] = json!({ "role": "user", "content": "no tool result here" }); + let info = transform(&mut root, &history_opts()); + // With an open call at msg 3 (tu_1) never closed, the closed boundary + // can't advance past msg 2, so the 20-message grid step never fills. + assert!(!info.compressed || info.collapsed_turns == 0); +} diff --git a/crates/pxpipe/src/transform/anthropic/tool_results.rs b/crates/pxpipe/src/transform/anthropic/tool_results.rs new file mode 100644 index 0000000..ca5c00e --- /dev/null +++ b/crates/pxpipe/src/transform/anthropic/tool_results.rs @@ -0,0 +1,72 @@ +use super::common::{ + render_live_block, tool_result_has_image, tool_result_text, truncate_for_budget, AnthropicOpts, +}; +use crate::transform::info::TransformInfo; +use serde_json::Value; + +/// Image large `tool_result` text content across every user message. tool_result +/// output (find trees, file dumps, logs) is the bulk of agentic input. Skips +/// `is_error` results (Anthropic rejects images inside those) and pages oversized +/// content down to the image cap. +pub(crate) fn apply_tool_results( + root: &mut Value, + opts: &AnthropicOpts, + info: &mut TransformInfo, + budget: &mut usize, +) { + let Some(msgs) = root.get_mut("messages").and_then(|m| m.as_array_mut()) else { + return; + }; + for msg in msgs.iter_mut() { + if *budget == 0 { + break; + } + let Some(blocks) = msg.get_mut("content").and_then(|c| c.as_array_mut()) else { + continue; + }; + for block in blocks.iter_mut() { + let Some(bm) = block.as_object_mut() else { + continue; + }; + if bm.get("type").and_then(|t| t.as_str()) != Some("tool_result") { + continue; + } + if bm.get("is_error").and_then(|e| e.as_bool()) == Some(true) { + continue; // images forbidden in error tool_results + } + // Don't clobber a tool_result that already carries an image block + // (e.g. a screenshot the tool returned) — tool_result_text only reads + // the text sub-blocks, so imaging here would silently drop that image. + if tool_result_has_image(bm.get("content")) { + continue; + } + let text = tool_result_text(bm.get("content")); + if text.len() < opts.min_live_block_chars { + continue; + } + // Cap each result at the per-result limit AND whatever total budget + // is left, so the aggregate can't exceed max_total_images. + let per_result = opts.max_images_per_tool_result.min(*budget); + if per_result == 0 { + break; + } + let (rendered, omitted) = truncate_for_budget(&text, per_result, opts); + let cc = bm.get("cache_control").cloned(); + if let Some(r) = render_live_block(&rendered, opts, cc, true, *budget) { + *budget -= r.img_count; + if omitted > 0 { + info.truncated_tool_results += 1; + info.omitted_chars += omitted; + } + info.tool_result_imgs += r.img_count; + info.image_count += r.img_count; + info.image_bytes += r.bytes; + info.image_pixels += r.pixels; + info.dropped_chars += r.dropped; + info.compressed_chars += text.len(); + bm.remove("cache_control"); // relocated onto the last image + bm.insert("content".into(), Value::Array(r.blocks)); + } + } + } +} diff --git a/crates/rtk/src/detector.rs b/crates/rtk/src/detector.rs index 836228f..4ce1515 100644 --- a/crates/rtk/src/detector.rs +++ b/crates/rtk/src/detector.rs @@ -17,16 +17,16 @@ pub struct CommandDetection { pub category: String, } -struct Detector { - r#type: &'static str, - category: &'static str, - command_patterns: Vec, - content_patterns: Vec, +pub(super) struct Detector { + pub(super) r#type: &'static str, + pub(super) category: &'static str, + pub(super) command_patterns: Vec, + pub(super) content_patterns: Vec, } /// Compile with explicit flags: `i` = case-insensitive, `m` = multiline. /// Fixed, in-tree patterns — a bad transcription panics (caught by `table_builds`). -fn rx(pattern: &str, flags: &str) -> Regex { +pub(super) fn rx(pattern: &str, flags: &str) -> Regex { RegexBuilder::new(pattern) .case_insensitive(flags.contains('i')) .multi_line(flags.contains('m')) @@ -96,486 +96,21 @@ fn command_prefix_re() -> &'static Regex { RE.get_or_init(|| Regex::new(&format!(r"^(?:{})\b", COMMAND_PREFIXES.join("|"))).unwrap()) } +mod table; + fn detectors() -> &'static Vec { static D: OnceLock> = OnceLock::new(); - D.get_or_init(build_detectors) + D.get_or_init(table::build_detectors) } /// (type, category, command patterns (all "i"), (content pattern, flags) pairs). -type DetectorRow = ( +pub(super) type DetectorRow = ( &'static str, &'static str, &'static [&'static str], &'static [(&'static str, &'static str)], ); -fn build_detectors() -> Vec { - let table: &[DetectorRow] = &[ - ( - "git-status", - "git", - &[r"^git\s+status\b"], - &[ - (r"^On branch ", "m"), - (r"^Changes (?:not staged|to be committed)", "m"), - (r"^Untracked files:", "m"), - ], - ), - ( - "git-branch", - "git", - &[r"^git\s+branch\b", r"^git\s+checkout\b", r"^git\s+switch\b"], - &[ - (r"^\*\s+\S+", "m"), - (r"Switched to (?:a new )?branch", "i"), - (r#"Already on ['"][^'"]+['"]"#, "i"), - ], - ), - ( - "git-diff", - "git", - &[r"^git\s+diff\b", r"^git\s+show\b"], - &[ - (r"^diff --git ", "m"), - (r"^@@\s+-\d+,\d+\s+\+\d+,\d+\s+@@", "m"), - ], - ), - ( - "git-log", - "git", - &[r"^git\s+log\b"], - &[(r"^commit [0-9a-f]{7,40}", "m"), (r"^Author: ", "m")], - ), - ( - "make", - "build", - &[r"^make\b"], - &[ - (r"^make\[\d+\]: (?:Entering|Leaving) directory", "m"), - (r"make: \*\*\* ", ""), - ], - ), - ( - "gradle", - "build", - &[r"^(?:gradle|gradlew|\./gradlew)\b"], - &[ - (r"^> Task :", "m"), - (r"^BUILD (?:SUCCESSFUL|FAILED)\b", "m"), - ], - ), - ( - "dotnet", - "build", - &[ - r"^dotnet\s+(?:build|test|run|restore|publish|pack|msbuild)\b", - r"^dotnet\b", - ], - &[ - (r"^Build (?:succeeded|FAILED)\b", "m"), - (r"\b(?:error|warning) CS\d+\b", "m"), - ], - ), - ( - "terraform-plan", - "infra", - &[r"^terraform\s+plan\b"], - &[ - (r"Terraform will perform the following actions:", ""), - (r"Plan: \d+ to add", "i"), - ], - ), - ( - "tofu-plan", - "infra", - &[r"^(?:tofu|opentofu)\s+plan\b"], - &[ - (r"OpenTofu will perform the following actions:", ""), - (r"Plan: \d+ to add", "i"), - ], - ), - ( - "systemctl-status", - "infra", - &[r"^systemctl\s+status\b"], - &[ - (r"^\s*Loaded:\s+", "m"), - (r"^\s*Active:\s+", "m"), - (r"^●\s+\S+\.service", "m"), - ], - ), - ( - "test-vitest", - "test", - &[r"^vitest\b", r"^npm\s+(?:run\s+)?test:vitest\b"], - &[ - (r"\bvitest\b", "i"), - (r"^ ✓ ", "m"), - (r"^ ❯ ", "m"), - (r"Test Files\s+\d+\s+(?:passed|failed)", "i"), - ], - ), - ( - "test-jest", - "test", - &[r"^jest\b", r"^npm\s+(?:run\s+)?test\b"], - &[ - (r"Test Suites:\s+\d+", "i"), - (r"Tests:\s+\d+", "i"), - (r"^PASS\s+", "m"), - (r"^FAIL\s+", "m"), - ], - ), - ( - "test-pytest", - "test", - &[r"^pytest\b", r"^python\s+-m\s+pytest\b"], - &[ - (r"=+\s+(?:\d+\s+)?(?:passed|failed|errors?)", "i"), - (r"^E\s+", "m"), - (r"^FAILED ", "m"), - ], - ), - ( - "test-cargo", - "test", - &[r"^cargo\s+test\b", r"^cargo\s+nextest\b"], - &[ - (r"^running \d+ tests?", "m"), - (r"^test\s+[\w:.-]+\s+\.\.\.\s+(?:ok|FAILED|ignored)", "m"), - (r"test result:\s+(?:ok|FAILED)", "i"), - ], - ), - ( - "test-go", - "test", - &[r"^go\s+test\b"], - &[ - (r"^(?:ok|FAIL)\s+[\w./-]+\s+[\d.]+s", "m"), - (r"^--- FAIL: ", "m"), - (r"^panic: ", "m"), - ], - ), - ( - "build-typescript", - "build", - &[r"^tsc\b", r"^npm\s+run\s+typecheck\b"], - &[(r"TS\d{4}:", ""), (r"error TS\d{4}", "i")], - ), - ( - "build-eslint", - "build", - &[r"^eslint\b", r"^npm\s+run\s+lint\b"], - &[ - (r"\s+\d+:\d+\s+(?:error|warning)\s+", ""), - (r"✖\s+\d+\s+problems?", ""), - ], - ), - ( - "build-webpack", - "build", - &[ - r"^webpack\b", - r"^npx\s+webpack\b", - r"^npm\s+run\s+build:webpack\b", - ], - &[ - (r"webpack\s+\d", "i"), - (r"compiled (?:successfully|with \d+ errors?)", "i"), - (r"asset .+\.js", "i"), - ], - ), - ( - "build-vite", - "build", - &[ - r"^vite\s+build\b", - r"^npm\s+run\s+build\b", - r"^pnpm\s+build\b", - ], - &[ - (r"vite v[\d.]+", "i"), - (r"✓ built in", "i"), - (r"transforming \(\d+\)", "i"), - ], - ), - ( - "biome", - "build", - &[r"^biome\b", r"^npx\s+biome\b"], - &[ - (r"lint/[A-Za-z0-9/.-]+", ""), - (r"Checked \d+ files? in", "i"), - ], - ), - ( - "prettier", - "build", - &[r"^prettier\b", r"^npx\s+prettier\b"], - &[ - (r"^Checking formatting\.\.\.", "m"), - (r"Code style issues found", "i"), - ], - ), - ( - "turbo", - "build", - &[r"^turbo\b", r"^npx\s+turbo\b"], - &[ - (r"^• Packages in scope:", "m"), - (r"^Tasks:\s+\d+\s+successful", "m"), - ], - ), - ( - "nx", - "build", - &[r"^nx\b", r"^npx\s+nx\b"], - &[(r"^NX\s+", "m"), (r"^> nx run ", "m")], - ), - ( - "playwright", - "test", - &[r"^playwright\s+test\b", r"^npx\s+playwright\s+test\b"], - &[ - (r"Running \d+ tests? using \d+ workers?", "i"), - (r"^\s+\d+ failed", "m"), - ], - ), - ( - "npm-install", - "package", - &[r"^(?:npm|pnpm|yarn)\s+(?:install|add|update)\b"], - &[ - (r"added \d+ packages", "i"), - (r"packages are looking for funding", "i"), - (r"audited \d+ packages", "i"), - ], - ), - ( - "npm-audit", - "package", - &[r"^(?:npm|pnpm|yarn)\s+audit\b"], - &[ - (r"found \d+ vulnerabilities", "i"), - (r"\b(?:low|moderate|high|critical)\b", "i"), - ], - ), - ( - "ruff", - "build", - &[r"^ruff\b", r"^uv\s+run\s+ruff\b"], - &[ - (r"^[\w./-]+\.py:\d+:\d+:\s+[A-Z]\d+", "m"), - (r"Found \d+ errors?\.", "i"), - ], - ), - ( - "mypy", - "build", - &[r"^mypy\b", r"^python\s+-m\s+mypy\b"], - &[ - (r"^[\w./-]+\.py:\d+:\s+error:", "m"), - (r"Found \d+ errors? in \d+ files?", "i"), - ], - ), - ( - "pip", - "package", - &[ - r"^pip\s+(?:install|download|uninstall)\b", - r"^python\s+-m\s+pip\b", - ], - &[(r"^Collecting ", "m"), (r"^Successfully installed ", "m")], - ), - ( - "uv-sync", - "package", - &[r"^uv\s+sync\b", r"^uv\s+pip\s+install\b"], - &[ - (r"^Resolved \d+ packages?", "m"), - (r"^Installed \d+ packages?", "m"), - ], - ), - ( - "poetry-install", - "package", - &[r"^poetry\s+install\b"], - &[ - (r"^Installing dependencies from lock file", "m"), - (r"^Package operations:", "m"), - ], - ), - ( - "golangci-lint", - "build", - &[r"^golangci-lint\b"], - &[(r"^[\w./-]+\.go:\d+:\d+:", "m"), (r"^\d+ issues?:", "m")], - ), - ( - "bundle-install", - "package", - &[r"^bundle\s+install\b"], - &[ - (r"^Fetching gem metadata from ", "m"), - (r"^Bundle complete!", "m"), - ], - ), - ( - "rubocop", - "build", - &[r"^rubocop\b", r"^bundle\s+exec\s+rubocop\b"], - &[ - (r"^Inspecting \d+ files", "m"), - (r"^[\w./-]+\.rb:\d+:\d+:\s+[A-Z]:", "m"), - ], - ), - ( - "docker-ps", - "docker", - &[r"^docker\s+ps\b"], - &[(r"^CONTAINER ID\s+IMAGE\s+COMMAND", "m")], - ), - ( - "docker-logs", - "docker", - &[r"^docker\s+logs\b", r"^docker\s+compose\s+logs\b"], - &[ - (r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}", "m"), - (r"\b(?:ERROR|WARN|INFO)\b", ""), - (r"^Attaching to ", "m"), - ], - ), - ( - "aws", - "cloud", - &[r"^aws\b"], - &[ - (r"An error occurred \([A-Za-z0-9]+\) when calling", ""), - (r"^(?:upload|download): ", "m"), - ], - ), - ( - "gcloud", - "cloud", - &[r"^gcloud\b"], - &[(r"^ERROR: \(gcloud\.", "m"), (r"^Updated property \[", "m")], - ), - ( - "ssh", - "cloud", - &[r"^ssh\b"], - &[ - (r"Permission denied \(", ""), - (r"Host key verification failed", ""), - (r"Connection timed out", ""), - ], - ), - ( - "rsync", - "cloud", - &[r"^rsync\b"], - &[ - (r"^sending incremental file list", "m"), - (r"^rsync error:", "m"), - ], - ), - ( - "curl", - "cloud", - &[r"^curl\b"], - &[(r"curl: \(\d+\)", ""), (r"^HTTP/\d(?:\.\d)? \d{3}", "m")], - ), - ( - "wget", - "cloud", - &[r"^wget\b"], - &[(r"^--\d{4}-\d{2}-\d{2}", "m"), (r"^ERROR \d{3}:", "m")], - ), - ( - "json-output", - "generic", - &[r"^jq\b", r"^cat\s+.*\.json\b"], - &[(r"^\s*[\[{][\s\S]*[\]}]\s*$", "")], - ), - ( - "shell-ls", - "shell", - &[r"^ls(?:\s+-[A-Za-z]+)?\b"], - &[ - (r"^total \d+", "m"), - (r"^\S+\s+\S+\s+\d+\s+\w+\s+\d{1,2}\s+", "m"), - ], - ), - ( - "shell-find", - "shell", - &[r"^find\b"], - &[(r"^(?:\.{1,2}|/|[\w.-]+/).+", "m")], - ), - ( - "shell-grep", - "shell", - &[r"^(?:grep|rg|ag)\b"], - &[ - ( - r"^[\w./-]+\.(?:ts|tsx|js|jsx|py|go|rs|java|rb|md|json|ya?ml|txt):\d*:", - "m", - ), - (r"^[\w./-]+/[\w./-]+:\d*:", "m"), - ], - ), - ( - "shell-ps", - "shell", - &[r"^ps\b"], - &[(r"^(?:USER\s+PID|\s*PID\s+)", "m")], - ), - ( - "shell-df", - "shell", - &[r"^df\b"], - &[(r"^Filesystem\s+.*Use%", "m")], - ), - ( - "shell-du", - "shell", - &[r"^du\b"], - &[(r"^\d+(?:\.\d+)?[KMGTP]?\s+\S+", "m")], - ), - ( - "error-stacktrace", - "generic", - &[], - &[ - (r"Traceback \(most recent call last\):", ""), - (r"^\s+at\s+\S+\s+\(.+:\d+:\d+\)", "m"), - (r"^panic: ", "m"), - (r"^thread '[^']+' panicked at", "m"), - ], - ), - ( - "generic-error", - "generic", - &[], - &[ - (r"Error:", ""), - (r"Exception:", ""), - (r"Traceback \(most recent call last\):", ""), - ], - ), - ]; - - table - .iter() - .map(|(t, cat, cmds, contents)| Detector { - r#type: t, - category: cat, - command_patterns: cmds.iter().map(|p| rx(p, "i")).collect(), - content_patterns: contents.iter().map(|(p, f)| rx(p, f)).collect(), - }) - .collect() -} - /// Quote-aware composite-command splitter — port of `lastCommandSegment`. /// Returns the last top-level `&&`/`||`/`;`-separated segment (trimmed). pub fn last_command_segment(command: &str) -> String { diff --git a/crates/rtk/src/detector/table.rs b/crates/rtk/src/detector/table.rs new file mode 100644 index 0000000..4a6be44 --- /dev/null +++ b/crates/rtk/src/detector/table.rs @@ -0,0 +1,468 @@ +use super::{rx, Detector, DetectorRow}; + +pub(super) fn build_detectors() -> Vec { + let table: &[DetectorRow] = &[ + ( + "git-status", + "git", + &[r"^git\s+status\b"], + &[ + (r"^On branch ", "m"), + (r"^Changes (?:not staged|to be committed)", "m"), + (r"^Untracked files:", "m"), + ], + ), + ( + "git-branch", + "git", + &[r"^git\s+branch\b", r"^git\s+checkout\b", r"^git\s+switch\b"], + &[ + (r"^\*\s+\S+", "m"), + (r"Switched to (?:a new )?branch", "i"), + (r#"Already on ['"][^'"]+['"]"#, "i"), + ], + ), + ( + "git-diff", + "git", + &[r"^git\s+diff\b", r"^git\s+show\b"], + &[ + (r"^diff --git ", "m"), + (r"^@@\s+-\d+,\d+\s+\+\d+,\d+\s+@@", "m"), + ], + ), + ( + "git-log", + "git", + &[r"^git\s+log\b"], + &[(r"^commit [0-9a-f]{7,40}", "m"), (r"^Author: ", "m")], + ), + ( + "make", + "build", + &[r"^make\b"], + &[ + (r"^make\[\d+\]: (?:Entering|Leaving) directory", "m"), + (r"make: \*\*\* ", ""), + ], + ), + ( + "gradle", + "build", + &[r"^(?:gradle|gradlew|\./gradlew)\b"], + &[ + (r"^> Task :", "m"), + (r"^BUILD (?:SUCCESSFUL|FAILED)\b", "m"), + ], + ), + ( + "dotnet", + "build", + &[ + r"^dotnet\s+(?:build|test|run|restore|publish|pack|msbuild)\b", + r"^dotnet\b", + ], + &[ + (r"^Build (?:succeeded|FAILED)\b", "m"), + (r"\b(?:error|warning) CS\d+\b", "m"), + ], + ), + ( + "terraform-plan", + "infra", + &[r"^terraform\s+plan\b"], + &[ + (r"Terraform will perform the following actions:", ""), + (r"Plan: \d+ to add", "i"), + ], + ), + ( + "tofu-plan", + "infra", + &[r"^(?:tofu|opentofu)\s+plan\b"], + &[ + (r"OpenTofu will perform the following actions:", ""), + (r"Plan: \d+ to add", "i"), + ], + ), + ( + "systemctl-status", + "infra", + &[r"^systemctl\s+status\b"], + &[ + (r"^\s*Loaded:\s+", "m"), + (r"^\s*Active:\s+", "m"), + (r"^●\s+\S+\.service", "m"), + ], + ), + ( + "test-vitest", + "test", + &[r"^vitest\b", r"^npm\s+(?:run\s+)?test:vitest\b"], + &[ + (r"\bvitest\b", "i"), + (r"^ ✓ ", "m"), + (r"^ ❯ ", "m"), + (r"Test Files\s+\d+\s+(?:passed|failed)", "i"), + ], + ), + ( + "test-jest", + "test", + &[r"^jest\b", r"^npm\s+(?:run\s+)?test\b"], + &[ + (r"Test Suites:\s+\d+", "i"), + (r"Tests:\s+\d+", "i"), + (r"^PASS\s+", "m"), + (r"^FAIL\s+", "m"), + ], + ), + ( + "test-pytest", + "test", + &[r"^pytest\b", r"^python\s+-m\s+pytest\b"], + &[ + (r"=+\s+(?:\d+\s+)?(?:passed|failed|errors?)", "i"), + (r"^E\s+", "m"), + (r"^FAILED ", "m"), + ], + ), + ( + "test-cargo", + "test", + &[r"^cargo\s+test\b", r"^cargo\s+nextest\b"], + &[ + (r"^running \d+ tests?", "m"), + (r"^test\s+[\w:.-]+\s+\.\.\.\s+(?:ok|FAILED|ignored)", "m"), + (r"test result:\s+(?:ok|FAILED)", "i"), + ], + ), + ( + "test-go", + "test", + &[r"^go\s+test\b"], + &[ + (r"^(?:ok|FAIL)\s+[\w./-]+\s+[\d.]+s", "m"), + (r"^--- FAIL: ", "m"), + (r"^panic: ", "m"), + ], + ), + ( + "build-typescript", + "build", + &[r"^tsc\b", r"^npm\s+run\s+typecheck\b"], + &[(r"TS\d{4}:", ""), (r"error TS\d{4}", "i")], + ), + ( + "build-eslint", + "build", + &[r"^eslint\b", r"^npm\s+run\s+lint\b"], + &[ + (r"\s+\d+:\d+\s+(?:error|warning)\s+", ""), + (r"✖\s+\d+\s+problems?", ""), + ], + ), + ( + "build-webpack", + "build", + &[ + r"^webpack\b", + r"^npx\s+webpack\b", + r"^npm\s+run\s+build:webpack\b", + ], + &[ + (r"webpack\s+\d", "i"), + (r"compiled (?:successfully|with \d+ errors?)", "i"), + (r"asset .+\.js", "i"), + ], + ), + ( + "build-vite", + "build", + &[ + r"^vite\s+build\b", + r"^npm\s+run\s+build\b", + r"^pnpm\s+build\b", + ], + &[ + (r"vite v[\d.]+", "i"), + (r"✓ built in", "i"), + (r"transforming \(\d+\)", "i"), + ], + ), + ( + "biome", + "build", + &[r"^biome\b", r"^npx\s+biome\b"], + &[ + (r"lint/[A-Za-z0-9/.-]+", ""), + (r"Checked \d+ files? in", "i"), + ], + ), + ( + "prettier", + "build", + &[r"^prettier\b", r"^npx\s+prettier\b"], + &[ + (r"^Checking formatting\.\.\.", "m"), + (r"Code style issues found", "i"), + ], + ), + ( + "turbo", + "build", + &[r"^turbo\b", r"^npx\s+turbo\b"], + &[ + (r"^• Packages in scope:", "m"), + (r"^Tasks:\s+\d+\s+successful", "m"), + ], + ), + ( + "nx", + "build", + &[r"^nx\b", r"^npx\s+nx\b"], + &[(r"^NX\s+", "m"), (r"^> nx run ", "m")], + ), + ( + "playwright", + "test", + &[r"^playwright\s+test\b", r"^npx\s+playwright\s+test\b"], + &[ + (r"Running \d+ tests? using \d+ workers?", "i"), + (r"^\s+\d+ failed", "m"), + ], + ), + ( + "npm-install", + "package", + &[r"^(?:npm|pnpm|yarn)\s+(?:install|add|update)\b"], + &[ + (r"added \d+ packages", "i"), + (r"packages are looking for funding", "i"), + (r"audited \d+ packages", "i"), + ], + ), + ( + "npm-audit", + "package", + &[r"^(?:npm|pnpm|yarn)\s+audit\b"], + &[ + (r"found \d+ vulnerabilities", "i"), + (r"\b(?:low|moderate|high|critical)\b", "i"), + ], + ), + ( + "ruff", + "build", + &[r"^ruff\b", r"^uv\s+run\s+ruff\b"], + &[ + (r"^[\w./-]+\.py:\d+:\d+:\s+[A-Z]\d+", "m"), + (r"Found \d+ errors?\.", "i"), + ], + ), + ( + "mypy", + "build", + &[r"^mypy\b", r"^python\s+-m\s+mypy\b"], + &[ + (r"^[\w./-]+\.py:\d+:\s+error:", "m"), + (r"Found \d+ errors? in \d+ files?", "i"), + ], + ), + ( + "pip", + "package", + &[ + r"^pip\s+(?:install|download|uninstall)\b", + r"^python\s+-m\s+pip\b", + ], + &[(r"^Collecting ", "m"), (r"^Successfully installed ", "m")], + ), + ( + "uv-sync", + "package", + &[r"^uv\s+sync\b", r"^uv\s+pip\s+install\b"], + &[ + (r"^Resolved \d+ packages?", "m"), + (r"^Installed \d+ packages?", "m"), + ], + ), + ( + "poetry-install", + "package", + &[r"^poetry\s+install\b"], + &[ + (r"^Installing dependencies from lock file", "m"), + (r"^Package operations:", "m"), + ], + ), + ( + "golangci-lint", + "build", + &[r"^golangci-lint\b"], + &[(r"^[\w./-]+\.go:\d+:\d+:", "m"), (r"^\d+ issues?:", "m")], + ), + ( + "bundle-install", + "package", + &[r"^bundle\s+install\b"], + &[ + (r"^Fetching gem metadata from ", "m"), + (r"^Bundle complete!", "m"), + ], + ), + ( + "rubocop", + "build", + &[r"^rubocop\b", r"^bundle\s+exec\s+rubocop\b"], + &[ + (r"^Inspecting \d+ files", "m"), + (r"^[\w./-]+\.rb:\d+:\d+:\s+[A-Z]:", "m"), + ], + ), + ( + "docker-ps", + "docker", + &[r"^docker\s+ps\b"], + &[(r"^CONTAINER ID\s+IMAGE\s+COMMAND", "m")], + ), + ( + "docker-logs", + "docker", + &[r"^docker\s+logs\b", r"^docker\s+compose\s+logs\b"], + &[ + (r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}", "m"), + (r"\b(?:ERROR|WARN|INFO)\b", ""), + (r"^Attaching to ", "m"), + ], + ), + ( + "aws", + "cloud", + &[r"^aws\b"], + &[ + (r"An error occurred \([A-Za-z0-9]+\) when calling", ""), + (r"^(?:upload|download): ", "m"), + ], + ), + ( + "gcloud", + "cloud", + &[r"^gcloud\b"], + &[(r"^ERROR: \(gcloud\.", "m"), (r"^Updated property \[", "m")], + ), + ( + "ssh", + "cloud", + &[r"^ssh\b"], + &[ + (r"Permission denied \(", ""), + (r"Host key verification failed", ""), + (r"Connection timed out", ""), + ], + ), + ( + "rsync", + "cloud", + &[r"^rsync\b"], + &[ + (r"^sending incremental file list", "m"), + (r"^rsync error:", "m"), + ], + ), + ( + "curl", + "cloud", + &[r"^curl\b"], + &[(r"curl: \(\d+\)", ""), (r"^HTTP/\d(?:\.\d)? \d{3}", "m")], + ), + ( + "wget", + "cloud", + &[r"^wget\b"], + &[(r"^--\d{4}-\d{2}-\d{2}", "m"), (r"^ERROR \d{3}:", "m")], + ), + ( + "json-output", + "generic", + &[r"^jq\b", r"^cat\s+.*\.json\b"], + &[(r"^\s*[\[{][\s\S]*[\]}]\s*$", "")], + ), + ( + "shell-ls", + "shell", + &[r"^ls(?:\s+-[A-Za-z]+)?\b"], + &[ + (r"^total \d+", "m"), + (r"^\S+\s+\S+\s+\d+\s+\w+\s+\d{1,2}\s+", "m"), + ], + ), + ( + "shell-find", + "shell", + &[r"^find\b"], + &[(r"^(?:\.{1,2}|/|[\w.-]+/).+", "m")], + ), + ( + "shell-grep", + "shell", + &[r"^(?:grep|rg|ag)\b"], + &[ + ( + r"^[\w./-]+\.(?:ts|tsx|js|jsx|py|go|rs|java|rb|md|json|ya?ml|txt):\d*:", + "m", + ), + (r"^[\w./-]+/[\w./-]+:\d*:", "m"), + ], + ), + ( + "shell-ps", + "shell", + &[r"^ps\b"], + &[(r"^(?:USER\s+PID|\s*PID\s+)", "m")], + ), + ( + "shell-df", + "shell", + &[r"^df\b"], + &[(r"^Filesystem\s+.*Use%", "m")], + ), + ( + "shell-du", + "shell", + &[r"^du\b"], + &[(r"^\d+(?:\.\d+)?[KMGTP]?\s+\S+", "m")], + ), + ( + "error-stacktrace", + "generic", + &[], + &[ + (r"Traceback \(most recent call last\):", ""), + (r"^\s+at\s+\S+\s+\(.+:\d+:\d+\)", "m"), + (r"^panic: ", "m"), + (r"^thread '[^']+' panicked at", "m"), + ], + ), + ( + "generic-error", + "generic", + &[], + &[ + (r"Error:", ""), + (r"Exception:", ""), + (r"Traceback \(most recent call last\):", ""), + ], + ), + ]; + + table + .iter() + .map(|(t, cat, cmds, contents)| Detector { + r#type: t, + category: cat, + command_patterns: cmds.iter().map(|p| rx(p, "i")).collect(), + content_patterns: contents.iter().map(|(p, f)| rx(p, f)).collect(), + }) + .collect() +} diff --git a/crates/rtk/src/filter.rs b/crates/rtk/src/filter.rs index 09b6f8c..95433b6 100644 --- a/crates/rtk/src/filter.rs +++ b/crates/rtk/src/filter.rs @@ -257,13 +257,11 @@ impl RtkFilter { .build() .ok()?; let unless = match r.unless { - Some(u) => { - RegexBuilder::new(&u) - .case_insensitive(true) - .multi_line(true) - .build() - .ok() - } + Some(u) => RegexBuilder::new(&u) + .case_insensitive(true) + .multi_line(true) + .build() + .ok(), None => None, }; Some(MatchOutputRule { diff --git a/crates/rtk/src/transform/anthropic.rs b/crates/rtk/src/transform/anthropic.rs index 2f3cc21..3f0ed45 100644 --- a/crates/rtk/src/transform/anthropic.rs +++ b/crates/rtk/src/transform/anthropic.rs @@ -31,11 +31,14 @@ fn build_lookup(messages: &[Value]) -> ToolLookup { .unwrap_or_default() .to_string(); let command = part.get("input").and_then(command_from_input); - lookup.insert(id.to_string(), ToolMeta { - name, - command, - arguments_str: None, - }); + lookup.insert( + id.to_string(), + ToolMeta { + name, + command, + arguments_str: None, + }, + ); } } lookup diff --git a/crates/rtk/src/transform/openai.rs b/crates/rtk/src/transform/openai.rs index c80704e..268cf71 100644 --- a/crates/rtk/src/transform/openai.rs +++ b/crates/rtk/src/transform/openai.rs @@ -40,11 +40,14 @@ fn build_lookup(messages: &[Value]) -> ToolLookup { // strings). For OpenAI the parse is deferred — build_lookup only // stores the raw string. let command = None; - lookup.insert(id.to_string(), ToolMeta { - name, - command, - arguments_str, - }); + lookup.insert( + id.to_string(), + ToolMeta { + name, + command, + arguments_str, + }, + ); } } lookup diff --git a/crates/translator/src/mapping/gemini_message_map.rs b/crates/translator/src/mapping/gemini_message_map.rs index c6f0220..9d5a9ce 100644 --- a/crates/translator/src/mapping/gemini_message_map.rs +++ b/crates/translator/src/mapping/gemini_message_map.rs @@ -1,682 +1,20 @@ // Anthropic Messages API <-> Gemini generateContent API message mapping. // -// Pure translation functions, no IO. Converts Anthropic request types into -// Gemini request types and Gemini response types back into Anthropic responses. +// Pure translation functions, no IO. Split by direction: +// - `request` Anthropic -> Gemini request +// - `response` Gemini -> Anthropic response +// - `reverse` Gemini -> Anthropic request & Anthropic -> Gemini response +// - `helpers` shared utilities (role merging, tool-id mapping) -use std::collections::HashMap; +mod helpers; +mod request; +mod response; +mod reverse; -use crate::anthropic::messages as anthropic; -use crate::gemini::request as gemini; -use crate::gemini::response as gemini_resp; -use crate::mapping::tools_map::sanitize_schema_for_gemini; -use crate::util::ids::{generate_message_id, generate_tool_use_id}; - -// --------------------------------------------------------------------------- -// Request direction: Anthropic -> Gemini -// --------------------------------------------------------------------------- - -/// Compute degradation warnings for a Gemini-bound request. -/// -/// Call before translating to surface features that are silently dropped during -/// Anthropic → Gemini translation. Emit the result as an `x-anyllm-degradation` -/// response header so clients can detect lossy translations. -pub fn compute_gemini_request_warnings( - req: &anthropic::MessageCreateRequest, -) -> crate::mapping::warnings::TranslationWarnings { - use crate::mapping::warnings::TranslationWarnings; - let mut w = TranslationWarnings::default(); - - // Single pass: collect all per-block warning flags at once. - let mut has_thinking = false; - let mut has_document = false; - let mut has_url_image = false; - for msg in &req.messages { - if let anthropic::Content::Blocks(blocks) = &msg.content { - for b in blocks { - match b { - // Thinking/RedactedThinking: no Gemini Content equivalent. - anthropic::ContentBlock::Thinking { .. } - | anthropic::ContentBlock::RedactedThinking { .. } => has_thinking = true, - // Document blocks have no Gemini equivalent. - anthropic::ContentBlock::Document { .. } => has_document = true, - // URL-type images: Gemini only accepts inline base64 data. - anthropic::ContentBlock::Image { source } if source.source_type != "base64" => { - has_url_image = true - } - _ => {} - } - } - } - } - if has_thinking { - w.add("thinking_blocks"); - } - if has_document { - w.add("document_blocks"); - } - if has_url_image { - w.add("url_images"); - } - - // cache_control on system blocks is dropped; Gemini has no prompt-caching API. - if let Some(anthropic::System::Blocks(blocks)) = &req.system { - if blocks.iter().any(|b| b.cache_control.is_some()) { - w.add("cache_control"); - } - } - - w -} - -/// Convert an Anthropic `MessageCreateRequest` into a Gemini `GenerateContentRequest`. -/// -/// Maps `thinking_config` to `generationConfig.thinkingConfig` for Gemini 2.5 -/// thinking models. Drops unsupported features (thinking content blocks in prior -/// messages, document blocks, cache_control) and merges consecutive same-role -/// messages to satisfy Gemini's strict alternation requirement. -pub fn anthropic_to_gemini_request( - req: &anthropic::MessageCreateRequest, -) -> gemini::GenerateContentRequest { - let tool_id_map = build_tool_id_map(&req.messages); - - // System instruction - let system_instruction = req.system.as_ref().map(|sys| { - let text = match sys { - anthropic::System::Text(s) => s.clone(), - anthropic::System::Blocks(blocks) => blocks - .iter() - .map(|b| b.text.as_str()) - .collect::>() - .join("\n"), - }; - gemini::Content { - role: None, - parts: vec![gemini::Part::text(text)], - } - }); - - // Convert messages - let mut contents: Vec = Vec::new(); - for msg in &req.messages { - let role = match msg.role { - anthropic::Role::User => "user", - anthropic::Role::Assistant => "model", - }; - let parts = content_blocks_to_parts(&msg.content, &tool_id_map); - if !parts.is_empty() { - contents.push(gemini::Content { - role: Some(role.to_string()), - parts, - }); - } - } - contents = merge_consecutive_roles(contents); - - // Tools - let tools = req.tools.as_ref().map(|tools| { - vec![gemini::Tool { - function_declarations: tools - .iter() - .map(|t| gemini::FunctionDeclaration { - name: t.name.clone(), - description: t.description.clone(), - parameters: Some(sanitize_schema_for_gemini(t.input_schema.clone())), - }) - .collect(), - }] - }); - - // Tool config - let tool_config = req.tool_choice.as_ref().map(|tc| { - if matches!( - tc, - anthropic::ToolChoice::Auto { - disable_parallel_tool_use: Some(true) - } | anthropic::ToolChoice::Any { - disable_parallel_tool_use: Some(true) - } - ) { - tracing::warn!( - "disable_parallel_tool_use=true is not supported by Gemini; \ - parallel tool calls may still occur" - ); - } - let (mode, allowed) = match tc { - anthropic::ToolChoice::Auto { .. } => ("AUTO", None), - anthropic::ToolChoice::Any { .. } => ("ANY", None), - anthropic::ToolChoice::None => ("NONE", None), - // Gemini ANY + allowedFunctionNames restricts to a specific tool. - anthropic::ToolChoice::Tool { name, .. } => ("ANY", Some(vec![name.clone()])), - }; - gemini::ToolConfig { - function_calling_config: gemini::FunctionCallingConfig { - mode: mode.to_string(), - allowed_function_names: allowed, - }, - } - }); - - // Generation config - let generation_config = { - let thinking_config = - if let Some(anthropic::ThinkingConfig::Enabled { budget_tokens }) = &req.thinking { - Some(gemini::ThinkingConfig { - thinking_budget: *budget_tokens, - include_thoughts: Some(true), - }) - } else { - None - }; - let gc = gemini::GenerationConfig { - max_output_tokens: Some(req.max_tokens), - temperature: req.temperature, - top_p: req.top_p, - top_k: req.top_k, - stop_sequences: req.stop_sequences.clone(), - thinking_config, - ..Default::default() - }; - Some(gc) - }; - - gemini::GenerateContentRequest { - contents, - system_instruction, - generation_config, - tools, - tool_config, - safety_settings: None, - } -} - -/// Build a map from Anthropic tool_use IDs to tool names. -/// -/// Scans all messages for `ToolUse` blocks so that `ToolResult` translation can -/// look up the function name Gemini expects. -pub fn build_tool_id_map(messages: &[anthropic::InputMessage]) -> HashMap { - let mut map = HashMap::new(); - for msg in messages { - let blocks = match &msg.content { - anthropic::Content::Text(_) => continue, - anthropic::Content::Blocks(b) => b, - }; - for block in blocks { - if let anthropic::ContentBlock::ToolUse { id, name, .. } = block { - map.insert(id.clone(), name.clone()); - } - } - } - map -} - -/// Merge consecutive same-role `Content` entries by concatenating their parts. -/// -/// Gemini requires strict user/model role alternation. When the Anthropic -/// conversation has two consecutive user (or model) turns, this merges them -/// into a single turn. -pub fn merge_consecutive_roles(contents: Vec) -> Vec { - let mut merged: Vec = Vec::with_capacity(contents.len()); - for c in contents { - if let Some(last) = merged.last_mut() { - if last.role == c.role { - last.parts.extend(c.parts); - continue; - } - } - merged.push(c); - } - - // Gemini requires the first content turn to have role "user". An Anthropic - // client may legally send an assistant-first conversation (for few-shot - // prompting). Prepend a dummy user turn so Gemini does not return a 400. - if merged.first().and_then(|c| c.role.as_deref()) == Some("model") { - merged.insert( - 0, - gemini::Content { - role: Some("user".to_string()), - parts: vec![gemini::Part::text(String::new())], - }, - ); - } - - merged -} - -/// Convert Anthropic message content into a vec of Gemini Parts. -fn content_blocks_to_parts( - content: &anthropic::Content, - tool_id_map: &HashMap, -) -> Vec { - match content { - anthropic::Content::Text(s) => vec![gemini::Part::text(s.clone())], - anthropic::Content::Blocks(blocks) => blocks - .iter() - .filter_map(|block| content_block_to_part(block, tool_id_map)) - .collect(), - } -} - -/// Convert a single Anthropic ContentBlock to a Gemini Part, or None if dropped. -fn content_block_to_part( - block: &anthropic::ContentBlock, - tool_id_map: &HashMap, -) -> Option { - match block { - anthropic::ContentBlock::Text { text } => Some(gemini::Part::text(text.clone())), - - anthropic::ContentBlock::Image { source } => { - // Gemini only supports inline base64 data, not URLs. - if source.source_type == "base64" { - let mime = source - .media_type - .clone() - .unwrap_or_else(|| "image/png".into()); - let data = source.data.clone().unwrap_or_default(); - Some(gemini::Part::inline_data(mime, data)) - } else { - // URL-type images cannot be sent as inline_data; drop. - None - } - } - - anthropic::ContentBlock::ToolUse { name, input, .. } => { - // Strip the Anthropic tool_use id; Gemini uses name-based correlation. - Some(gemini::Part::function_call(name.clone(), input.clone())) - } - - anthropic::ContentBlock::ToolResult { - tool_use_id, - content, - is_error, - } => { - let Some(name) = tool_id_map.get(tool_use_id).cloned() else { - // Gemini requires the function name to match a declared FunctionDeclaration. - // Emitting an unknown name causes a 400; drop the result instead. - tracing::warn!( - tool_use_id, - "dropping ToolResult: tool_use_id not found in tool_id_map" - ); - return None; - }; - - let response_value = tool_result_to_json(content, *is_error); - Some(gemini::Part::function_response(name, response_value)) - } - - // Thinking, RedactedThinking, Document: not supported by Gemini, drop. - anthropic::ContentBlock::Thinking { .. } - | anthropic::ContentBlock::RedactedThinking { .. } - | anthropic::ContentBlock::Document { .. } => None, - _ => None, - } -} - -/// Convert Anthropic ToolResult content into a JSON value for Gemini FunctionResponse. -fn tool_result_to_json( - content: &Option, - is_error: Option, -) -> serde_json::Value { - let text = match content { - Some(anthropic::ToolResultContent::Text(s)) => s.clone(), - Some(anthropic::ToolResultContent::Blocks(blocks)) => { - // Concatenate text blocks; other block types (e.g., images) cannot be - // represented in Gemini FunctionResponse and are replaced with a placeholder. - blocks - .iter() - .map(|b| match b { - anthropic::ContentBlock::Text { text } => text.clone(), - _ => { - tracing::warn!( - "tool_result contains non-text block; \ - replacing with \"[non-text]\" placeholder for Gemini" - ); - "[non-text]".into() - } - }) - .collect::>() - .join("\n") - } - None => String::new(), - }; - - if is_error == Some(true) { - serde_json::json!({ "error": text }) - } else { - serde_json::json!({ "result": text }) - } -} - -// --------------------------------------------------------------------------- -// Response direction: Gemini -> Anthropic -// --------------------------------------------------------------------------- - -/// Convert a Gemini `GenerateContentResponse` into an Anthropic `MessageResponse`. -/// -/// Uses only the first candidate. Synthesizes Anthropic-format tool IDs for any -/// function calls. -pub fn gemini_to_anthropic_response( - resp: &gemini_resp::GenerateContentResponse, - model: &str, -) -> anthropic::MessageResponse { - let candidate = resp.candidates.first(); - - let content = candidate - .map(|c| { - c.content - .parts - .iter() - .filter_map(gemini_part_to_content_block) - .collect::>() - }) - .unwrap_or_default(); - - let has_function_call = content - .iter() - .any(|b| matches!(b, anthropic::ContentBlock::ToolUse { .. })); - - let stop_reason = candidate - .and_then(|c| c.finish_reason.as_ref()) - .map(|fr| match fr { - gemini_resp::FinishReason::STOP if has_function_call => anthropic::StopReason::ToolUse, - gemini_resp::FinishReason::STOP => anthropic::StopReason::EndTurn, - gemini_resp::FinishReason::MAX_TOKENS => anthropic::StopReason::MaxTokens, - // SAFETY, RECITATION, LANGUAGE, OTHER, Unknown all map to EndTurn. - _ => anthropic::StopReason::EndTurn, - }) - // No finish_reason at all (e.g. empty candidates) -> EndTurn. - .or(if candidate.is_some() { - Some(anthropic::StopReason::EndTurn) - } else { - None - }); - - let usage = resp - .usage_metadata - .as_ref() - .map(|u| anthropic::Usage { - input_tokens: u.prompt_token_count, - output_tokens: u.candidates_token_count, - cache_creation_input_tokens: None, - cache_read_input_tokens: None, - ..Default::default() - }) - .unwrap_or_default(); - - anthropic::MessageResponse { - id: generate_message_id(), - response_type: "message".into(), - role: anthropic::Role::Assistant, - content, - model: model.to_string(), - stop_reason, - stop_sequence: None, - usage, - created: None, - } -} - -/// Convert a single Gemini Part to an Anthropic ContentBlock, or None if not mappable. -fn gemini_part_to_content_block(part: &gemini::Part) -> Option { - // Thought parts from thinking models map to Anthropic thinking blocks. - if part.thought == Some(true) { - return part - .text - .as_ref() - .map(|text| anthropic::ContentBlock::Thinking { - thinking: text.clone(), - signature: None, - }); - } - if let Some(text) = &part.text { - return Some(anthropic::ContentBlock::Text { text: text.clone() }); - } - if let Some(fc) = &part.function_call { - return Some(anthropic::ContentBlock::ToolUse { - id: generate_tool_use_id(), - name: fc.name.clone(), - input: fc.args.clone(), - }); - } - // inline_data, file_data, function_response: not expected in model output, - // or have no Anthropic equivalent. Drop. - None -} - -// --------------------------------------------------------------------------- -// Input direction: Gemini CLI -> Anthropic (for accepting Gemini-format input) -// --------------------------------------------------------------------------- - -/// Convert a Gemini CLI `GenerateContentRequest` into an Anthropic `MessageCreateRequest`. -/// -/// `model` is the model name extracted from the URL path (e.g. `gemini-2.5-pro` from -/// `POST /v1beta/models/gemini-2.5-pro:generateContent`). All generation config fields -/// map directly; unsupported Gemini features (safety settings, response_schema) are dropped. -pub fn gemini_to_anthropic_request( - req: &gemini::GenerateContentRequest, - model: &str, -) -> anthropic::MessageCreateRequest { - // Build name->id map so that function_response parts reference the same - // synthetic tool_use_id as the corresponding function_call part. - let mut name_to_id: HashMap = HashMap::new(); - for content in &req.contents { - for part in &content.parts { - if let Some(ref fc) = part.function_call { - name_to_id - .entry(fc.name.clone()) - .or_insert_with(generate_tool_use_id); - } - } - } - - // System instruction -> system - let system = req.system_instruction.as_ref().map(|si| { - let text = si - .parts - .iter() - .filter_map(|p| p.text.as_deref()) - .collect::>() - .join("\n"); - anthropic::System::Text(text) - }); - - // Contents -> messages - let messages: Vec = req - .contents - .iter() - .filter_map(|c| gemini_content_to_input_message(c, &name_to_id)) - .collect(); - - // Generation config - let gc = req.generation_config.as_ref(); - let max_tokens = gc.and_then(|g| g.max_output_tokens).unwrap_or(8192); - let temperature = gc.and_then(|g| g.temperature); - let top_p = gc.and_then(|g| g.top_p); - let top_k = gc.and_then(|g| g.top_k); - let stop_sequences = gc - .and_then(|g| g.stop_sequences.clone()) - .filter(|v| !v.is_empty()); - - // Tools - let tools = req.tools.as_ref().map(|ts| { - ts.iter() - .flat_map(|t| t.function_declarations.iter()) - .map(|fd| anthropic::Tool { - name: fd.name.clone(), - description: fd.description.clone(), - input_schema: fd - .parameters - .clone() - .unwrap_or_else(|| serde_json::json!({"type": "object"})), - }) - .collect::>() - }); - - // Tool choice - let tool_choice = req.tool_config.as_ref().map(|tc| { - match tc.function_calling_config.mode.as_str() { - "NONE" => anthropic::ToolChoice::None, - "ANY" => match tc.function_calling_config.allowed_function_names.as_deref() { - Some([name]) => anthropic::ToolChoice::Tool { name: name.clone() }, - _ => anthropic::ToolChoice::Any { - disable_parallel_tool_use: None, - }, - }, - // AUTO or anything else - _ => anthropic::ToolChoice::Auto { - disable_parallel_tool_use: None, - }, - } - }); - - anthropic::MessageCreateRequest { - model: model.to_string(), - max_tokens, - messages, - system, - temperature, - top_p, - top_k, - stop_sequences, - tools, - tool_choice, - metadata: None, - thinking: None, - stream: None, - extra: Default::default(), - } -} - -/// Convert a single Gemini `Content` turn into an Anthropic `InputMessage`. -/// Returns `None` if the turn produces no content blocks (e.g. all parts were dropped). -fn gemini_content_to_input_message( - content: &gemini::Content, - name_to_id: &HashMap, -) -> Option { - let role = match content.role.as_deref() { - Some("model") => anthropic::Role::Assistant, - // "user", None, or anything unrecognised -> user. - _ => anthropic::Role::User, - }; - let blocks: Vec = content - .parts - .iter() - .filter_map(|p| gemini_input_part_to_block(p, name_to_id)) - .collect(); - if blocks.is_empty() { - return None; - } - Some(anthropic::InputMessage { - role, - content: anthropic::Content::Blocks(blocks), - }) -} - -/// Convert a single Gemini `Part` from a user/model message into an Anthropic `ContentBlock`. -fn gemini_input_part_to_block( - part: &gemini::Part, - name_to_id: &HashMap, -) -> Option { - if let Some(ref text) = part.text { - return Some(anthropic::ContentBlock::Text { text: text.clone() }); - } - if let Some(ref fc) = part.function_call { - let id = name_to_id - .get(&fc.name) - .cloned() - .unwrap_or_else(generate_tool_use_id); - return Some(anthropic::ContentBlock::ToolUse { - id, - name: fc.name.clone(), - input: fc.args.clone(), - }); - } - if let Some(ref fr) = part.function_response { - let tool_use_id = name_to_id - .get(&fr.name) - .cloned() - .unwrap_or_else(generate_tool_use_id); - return Some(anthropic::ContentBlock::ToolResult { - tool_use_id, - content: Some(anthropic::ToolResultContent::Text( - serde_json::to_string(&fr.response).unwrap_or_default(), - )), - is_error: None, - }); - } - if let Some(ref data) = part.inline_data { - if data.mime_type.starts_with("image/") { - return Some(anthropic::ContentBlock::Image { - source: anthropic::ImageSource { - source_type: "base64".to_string(), - media_type: Some(data.mime_type.clone()), - data: Some(data.data.clone()), - url: None, - }, - }); - } - // Non-image inline data (audio, video, etc.) has no Anthropic equivalent — drop. - tracing::warn!( - mime_type = %data.mime_type, - "dropping inline_data part with non-image mime type (no Anthropic equivalent)" - ); - } - None -} - -/// Convert an Anthropic `MessageResponse` into a Gemini `GenerateContentResponse`. -/// -/// Used when the proxy accepts Gemini CLI input and must return Gemini-format output. -/// This is the inverse of `gemini_to_anthropic_response`. -pub fn anthropic_to_gemini_response( - resp: &anthropic::MessageResponse, -) -> gemini_resp::GenerateContentResponse { - let parts: Vec = resp - .content - .iter() - .filter_map(|block| match block { - anthropic::ContentBlock::Text { text } => Some(gemini::Part::text(text.clone())), - anthropic::ContentBlock::ToolUse { name, input, .. } => { - Some(gemini::Part::function_call(name.clone(), input.clone())) - } - anthropic::ContentBlock::Thinking { thinking, .. } => Some(gemini::Part { - thought: Some(true), - text: Some(thinking.clone()), - ..Default::default() - }), - // ToolResult, Image, Document, RedactedThinking: not expected in model output. - _ => None, - }) - .collect(); - - let finish_reason = resp.stop_reason.as_ref().map(|sr| match sr { - anthropic::StopReason::EndTurn | anthropic::StopReason::ToolUse => { - gemini_resp::FinishReason::STOP - } - anthropic::StopReason::MaxTokens => gemini_resp::FinishReason::MAX_TOKENS, - anthropic::StopReason::StopSequence => gemini_resp::FinishReason::STOP, - _ => gemini_resp::FinishReason::STOP, - }); - - let candidate = gemini_resp::Candidate { - content: gemini::Content { - role: Some("model".to_string()), - parts, - }, - finish_reason, - safety_ratings: None, - }; - - gemini_resp::GenerateContentResponse { - candidates: vec![candidate], - usage_metadata: Some(gemini_resp::UsageMetadata { - prompt_token_count: resp.usage.input_tokens, - candidates_token_count: resp.usage.output_tokens, - total_token_count: resp.usage.input_tokens + resp.usage.output_tokens, - cached_content_token_count: 0, - }), - model_version: Some(resp.model.clone()), - } -} +pub use helpers::{build_tool_id_map, merge_consecutive_roles}; +pub use request::{anthropic_to_gemini_request, compute_gemini_request_warnings}; +pub use response::gemini_to_anthropic_response; +pub use reverse::{anthropic_to_gemini_response, gemini_to_anthropic_request}; #[cfg(test)] mod tests; diff --git a/crates/translator/src/mapping/gemini_message_map/helpers.rs b/crates/translator/src/mapping/gemini_message_map/helpers.rs new file mode 100644 index 0000000..9d6f2fd --- /dev/null +++ b/crates/translator/src/mapping/gemini_message_map/helpers.rs @@ -0,0 +1,60 @@ +// Common translation utilities shared across the Gemini message-map directions: +// tool-id lookup and Gemini role-alternation merging. + +use std::collections::HashMap; + +use crate::anthropic::messages as anthropic; +use crate::gemini::request as gemini; + +/// Build a map from Anthropic tool_use IDs to tool names. +/// +/// Scans all messages for `ToolUse` blocks so that `ToolResult` translation can +/// look up the function name Gemini expects. +pub fn build_tool_id_map(messages: &[anthropic::InputMessage]) -> HashMap { + let mut map = HashMap::new(); + for msg in messages { + let blocks = match &msg.content { + anthropic::Content::Text(_) => continue, + anthropic::Content::Blocks(b) => b, + }; + for block in blocks { + if let anthropic::ContentBlock::ToolUse { id, name, .. } = block { + map.insert(id.clone(), name.clone()); + } + } + } + map +} + +/// Merge consecutive same-role `Content` entries by concatenating their parts. +/// +/// Gemini requires strict user/model role alternation. When the Anthropic +/// conversation has two consecutive user (or model) turns, this merges them +/// into a single turn. +pub fn merge_consecutive_roles(contents: Vec) -> Vec { + let mut merged: Vec = Vec::with_capacity(contents.len()); + for c in contents { + if let Some(last) = merged.last_mut() { + if last.role == c.role { + last.parts.extend(c.parts); + continue; + } + } + merged.push(c); + } + + // Gemini requires the first content turn to have role "user". An Anthropic + // client may legally send an assistant-first conversation (for few-shot + // prompting). Prepend a dummy user turn so Gemini does not return a 400. + if merged.first().and_then(|c| c.role.as_deref()) == Some("model") { + merged.insert( + 0, + gemini::Content { + role: Some("user".to_string()), + parts: vec![gemini::Part::text(String::new())], + }, + ); + } + + merged +} diff --git a/crates/translator/src/mapping/gemini_message_map/request.rs b/crates/translator/src/mapping/gemini_message_map/request.rs new file mode 100644 index 0000000..e0db877 --- /dev/null +++ b/crates/translator/src/mapping/gemini_message_map/request.rs @@ -0,0 +1,286 @@ +// Request direction: Anthropic -> Gemini. + +use std::collections::HashMap; + +use super::helpers::{build_tool_id_map, merge_consecutive_roles}; +use crate::anthropic::messages as anthropic; +use crate::gemini::request as gemini; +use crate::mapping::tools_map::sanitize_schema_for_gemini; + +/// Compute degradation warnings for a Gemini-bound request. +/// +/// Call before translating to surface features that are silently dropped during +/// Anthropic → Gemini translation. Emit the result as an `x-anyllm-degradation` +/// response header so clients can detect lossy translations. +pub fn compute_gemini_request_warnings( + req: &anthropic::MessageCreateRequest, +) -> crate::mapping::warnings::TranslationWarnings { + use crate::mapping::warnings::TranslationWarnings; + let mut w = TranslationWarnings::default(); + + // Single pass: collect all per-block warning flags at once. + let mut has_thinking = false; + let mut has_document = false; + let mut has_url_image = false; + for msg in &req.messages { + if let anthropic::Content::Blocks(blocks) = &msg.content { + for b in blocks { + match b { + // Thinking/RedactedThinking: no Gemini Content equivalent. + anthropic::ContentBlock::Thinking { .. } + | anthropic::ContentBlock::RedactedThinking { .. } => has_thinking = true, + // Document blocks have no Gemini equivalent. + anthropic::ContentBlock::Document { .. } => has_document = true, + // URL-type images: Gemini only accepts inline base64 data. + anthropic::ContentBlock::Image { source } if source.source_type != "base64" => { + has_url_image = true + } + _ => {} + } + } + } + } + if has_thinking { + w.add("thinking_blocks"); + } + if has_document { + w.add("document_blocks"); + } + if has_url_image { + w.add("url_images"); + } + + // cache_control on system blocks is dropped; Gemini has no prompt-caching API. + if let Some(anthropic::System::Blocks(blocks)) = &req.system { + if blocks.iter().any(|b| b.cache_control.is_some()) { + w.add("cache_control"); + } + } + + w +} + +/// Convert an Anthropic `MessageCreateRequest` into a Gemini `GenerateContentRequest`. +/// +/// Maps `thinking_config` to `generationConfig.thinkingConfig` for Gemini 2.5 +/// thinking models. Drops unsupported features (thinking content blocks in prior +/// messages, document blocks, cache_control) and merges consecutive same-role +/// messages to satisfy Gemini's strict alternation requirement. +pub fn anthropic_to_gemini_request( + req: &anthropic::MessageCreateRequest, +) -> gemini::GenerateContentRequest { + let tool_id_map = build_tool_id_map(&req.messages); + + // System instruction + let system_instruction = req.system.as_ref().map(|sys| { + let text = match sys { + anthropic::System::Text(s) => s.clone(), + anthropic::System::Blocks(blocks) => blocks + .iter() + .map(|b| b.text.as_str()) + .collect::>() + .join("\n"), + }; + gemini::Content { + role: None, + parts: vec![gemini::Part::text(text)], + } + }); + + // Convert messages + let mut contents: Vec = Vec::new(); + for msg in &req.messages { + let role = match msg.role { + anthropic::Role::User => "user", + anthropic::Role::Assistant => "model", + }; + let parts = content_blocks_to_parts(&msg.content, &tool_id_map); + if !parts.is_empty() { + contents.push(gemini::Content { + role: Some(role.to_string()), + parts, + }); + } + } + contents = merge_consecutive_roles(contents); + + // Tools + let tools = req.tools.as_ref().map(|tools| { + vec![gemini::Tool { + function_declarations: tools + .iter() + .map(|t| gemini::FunctionDeclaration { + name: t.name.clone(), + description: t.description.clone(), + parameters: Some(sanitize_schema_for_gemini(t.input_schema.clone())), + }) + .collect(), + }] + }); + + // Tool config + let tool_config = req.tool_choice.as_ref().map(|tc| { + if matches!( + tc, + anthropic::ToolChoice::Auto { + disable_parallel_tool_use: Some(true) + } | anthropic::ToolChoice::Any { + disable_parallel_tool_use: Some(true) + } + ) { + tracing::warn!( + "disable_parallel_tool_use=true is not supported by Gemini; \ + parallel tool calls may still occur" + ); + } + let (mode, allowed) = match tc { + anthropic::ToolChoice::Auto { .. } => ("AUTO", None), + anthropic::ToolChoice::Any { .. } => ("ANY", None), + anthropic::ToolChoice::None => ("NONE", None), + // Gemini ANY + allowedFunctionNames restricts to a specific tool. + anthropic::ToolChoice::Tool { name, .. } => ("ANY", Some(vec![name.clone()])), + }; + gemini::ToolConfig { + function_calling_config: gemini::FunctionCallingConfig { + mode: mode.to_string(), + allowed_function_names: allowed, + }, + } + }); + + // Generation config + let generation_config = { + let thinking_config = + if let Some(anthropic::ThinkingConfig::Enabled { budget_tokens }) = &req.thinking { + Some(gemini::ThinkingConfig { + thinking_budget: *budget_tokens, + include_thoughts: Some(true), + }) + } else { + None + }; + let gc = gemini::GenerationConfig { + max_output_tokens: Some(req.max_tokens), + temperature: req.temperature, + top_p: req.top_p, + top_k: req.top_k, + stop_sequences: req.stop_sequences.clone(), + thinking_config, + ..Default::default() + }; + Some(gc) + }; + + gemini::GenerateContentRequest { + contents, + system_instruction, + generation_config, + tools, + tool_config, + safety_settings: None, + } +} + +/// Convert Anthropic message content into a vec of Gemini Parts. +fn content_blocks_to_parts( + content: &anthropic::Content, + tool_id_map: &HashMap, +) -> Vec { + match content { + anthropic::Content::Text(s) => vec![gemini::Part::text(s.clone())], + anthropic::Content::Blocks(blocks) => blocks + .iter() + .filter_map(|block| content_block_to_part(block, tool_id_map)) + .collect(), + } +} + +/// Convert a single Anthropic ContentBlock to a Gemini Part, or None if dropped. +fn content_block_to_part( + block: &anthropic::ContentBlock, + tool_id_map: &HashMap, +) -> Option { + match block { + anthropic::ContentBlock::Text { text } => Some(gemini::Part::text(text.clone())), + + anthropic::ContentBlock::Image { source } => { + // Gemini only supports inline base64 data, not URLs. + if source.source_type == "base64" { + let mime = source + .media_type + .clone() + .unwrap_or_else(|| "image/png".into()); + let data = source.data.clone().unwrap_or_default(); + Some(gemini::Part::inline_data(mime, data)) + } else { + // URL-type images cannot be sent as inline_data; drop. + None + } + } + + anthropic::ContentBlock::ToolUse { name, input, .. } => { + // Strip the Anthropic tool_use id; Gemini uses name-based correlation. + Some(gemini::Part::function_call(name.clone(), input.clone())) + } + + anthropic::ContentBlock::ToolResult { + tool_use_id, + content, + is_error, + } => { + let Some(name) = tool_id_map.get(tool_use_id).cloned() else { + // Gemini requires the function name to match a declared FunctionDeclaration. + // Emitting an unknown name causes a 400; drop the result instead. + tracing::warn!( + tool_use_id, + "dropping ToolResult: tool_use_id not found in tool_id_map" + ); + return None; + }; + + let response_value = tool_result_to_json(content, *is_error); + Some(gemini::Part::function_response(name, response_value)) + } + + // Thinking, RedactedThinking, Document: not supported by Gemini, drop. + anthropic::ContentBlock::Thinking { .. } + | anthropic::ContentBlock::RedactedThinking { .. } + | anthropic::ContentBlock::Document { .. } => None, + _ => None, + } +} + +/// Convert Anthropic ToolResult content into a JSON value for Gemini FunctionResponse. +fn tool_result_to_json( + content: &Option, + is_error: Option, +) -> serde_json::Value { + let text = match content { + Some(anthropic::ToolResultContent::Text(s)) => s.clone(), + Some(anthropic::ToolResultContent::Blocks(blocks)) => { + // Concatenate text blocks; other block types (e.g., images) cannot be + // represented in Gemini FunctionResponse and are replaced with a placeholder. + blocks + .iter() + .map(|b| match b { + anthropic::ContentBlock::Text { text } => text.clone(), + _ => { + tracing::warn!( + "tool_result contains non-text block; \ + replacing with \"[non-text]\" placeholder for Gemini" + ); + "[non-text]".into() + } + }) + .collect::>() + .join("\n") + } + None => String::new(), + }; + + if is_error == Some(true) { + serde_json::json!({ "error": text }) + } else { + serde_json::json!({ "result": text }) + } +} diff --git a/crates/translator/src/mapping/gemini_message_map/response.rs b/crates/translator/src/mapping/gemini_message_map/response.rs new file mode 100644 index 0000000..50d6e79 --- /dev/null +++ b/crates/translator/src/mapping/gemini_message_map/response.rs @@ -0,0 +1,98 @@ +// Response direction: Gemini -> Anthropic. + +use crate::anthropic::messages as anthropic; +use crate::gemini::request as gemini; +use crate::gemini::response as gemini_resp; +use crate::util::ids::{generate_message_id, generate_tool_use_id}; + +/// Convert a Gemini `GenerateContentResponse` into an Anthropic `MessageResponse`. +/// +/// Uses only the first candidate. Synthesizes Anthropic-format tool IDs for any +/// function calls. +pub fn gemini_to_anthropic_response( + resp: &gemini_resp::GenerateContentResponse, + model: &str, +) -> anthropic::MessageResponse { + let candidate = resp.candidates.first(); + + let content = candidate + .map(|c| { + c.content + .parts + .iter() + .filter_map(gemini_part_to_content_block) + .collect::>() + }) + .unwrap_or_default(); + + let has_function_call = content + .iter() + .any(|b| matches!(b, anthropic::ContentBlock::ToolUse { .. })); + + let stop_reason = candidate + .and_then(|c| c.finish_reason.as_ref()) + .map(|fr| match fr { + gemini_resp::FinishReason::STOP if has_function_call => anthropic::StopReason::ToolUse, + gemini_resp::FinishReason::STOP => anthropic::StopReason::EndTurn, + gemini_resp::FinishReason::MAX_TOKENS => anthropic::StopReason::MaxTokens, + // SAFETY, RECITATION, LANGUAGE, OTHER, Unknown all map to EndTurn. + _ => anthropic::StopReason::EndTurn, + }) + // No finish_reason at all (e.g. empty candidates) -> EndTurn. + .or(if candidate.is_some() { + Some(anthropic::StopReason::EndTurn) + } else { + None + }); + + let usage = resp + .usage_metadata + .as_ref() + .map(|u| anthropic::Usage { + input_tokens: u.prompt_token_count, + output_tokens: u.candidates_token_count, + cache_creation_input_tokens: None, + cache_read_input_tokens: None, + ..Default::default() + }) + .unwrap_or_default(); + + anthropic::MessageResponse { + id: generate_message_id(), + response_type: "message".into(), + role: anthropic::Role::Assistant, + content, + model: model.to_string(), + stop_reason, + stop_sequence: None, + usage, + created: None, + } +} + +/// Convert a single Gemini Part to an Anthropic ContentBlock, or None if not mappable. +fn gemini_part_to_content_block(part: &gemini::Part) -> Option { + // Thought parts from thinking models map to Anthropic thinking blocks. + if part.thought == Some(true) { + return part + .text + .as_ref() + .map(|text| anthropic::ContentBlock::Thinking { + thinking: text.clone(), + signature: None, + }); + } + if let Some(text) = &part.text { + return Some(anthropic::ContentBlock::Text { text: text.clone() }); + } + if let Some(fc) = &part.function_call { + return Some(anthropic::ContentBlock::ToolUse { + id: generate_tool_use_id(), + name: fc.name.clone(), + input: fc.args.clone(), + }); + } + // inline_data, file_data, function_response: not expected in model output, + // or have no Anthropic equivalent. Drop. + None +} diff --git a/crates/translator/src/mapping/gemini_message_map/reverse.rs b/crates/translator/src/mapping/gemini_message_map/reverse.rs new file mode 100644 index 0000000..f90d624 --- /dev/null +++ b/crates/translator/src/mapping/gemini_message_map/reverse.rs @@ -0,0 +1,242 @@ +// Reverse directions: +// - Gemini CLI request -> Anthropic request (for accepting Gemini-format input) +// - Anthropic response -> Gemini response (for returning Gemini-format output) + +use std::collections::HashMap; + +use crate::anthropic::messages as anthropic; +use crate::gemini::request as gemini; +use crate::gemini::response as gemini_resp; +use crate::util::ids::generate_tool_use_id; + +/// Convert a Gemini CLI `GenerateContentRequest` into an Anthropic `MessageCreateRequest`. +/// +/// `model` is the model name extracted from the URL path (e.g. `gemini-2.5-pro` from +/// `POST /v1beta/models/gemini-2.5-pro:generateContent`). All generation config fields +/// map directly; unsupported Gemini features (safety settings, response_schema) are dropped. +pub fn gemini_to_anthropic_request( + req: &gemini::GenerateContentRequest, + model: &str, +) -> anthropic::MessageCreateRequest { + // Build name->id map so that function_response parts reference the same + // synthetic tool_use_id as the corresponding function_call part. + let mut name_to_id: HashMap = HashMap::new(); + for content in &req.contents { + for part in &content.parts { + if let Some(ref fc) = part.function_call { + name_to_id + .entry(fc.name.clone()) + .or_insert_with(generate_tool_use_id); + } + } + } + + // System instruction -> system + let system = req.system_instruction.as_ref().map(|si| { + let text = si + .parts + .iter() + .filter_map(|p| p.text.as_deref()) + .collect::>() + .join("\n"); + anthropic::System::Text(text) + }); + + // Contents -> messages + let messages: Vec = req + .contents + .iter() + .filter_map(|c| gemini_content_to_input_message(c, &name_to_id)) + .collect(); + + // Generation config + let gc = req.generation_config.as_ref(); + let max_tokens = gc.and_then(|g| g.max_output_tokens).unwrap_or(8192); + let temperature = gc.and_then(|g| g.temperature); + let top_p = gc.and_then(|g| g.top_p); + let top_k = gc.and_then(|g| g.top_k); + let stop_sequences = gc + .and_then(|g| g.stop_sequences.clone()) + .filter(|v| !v.is_empty()); + + // Tools + let tools = req.tools.as_ref().map(|ts| { + ts.iter() + .flat_map(|t| t.function_declarations.iter()) + .map(|fd| anthropic::Tool { + name: fd.name.clone(), + description: fd.description.clone(), + input_schema: fd + .parameters + .clone() + .unwrap_or_else(|| serde_json::json!({"type": "object"})), + }) + .collect::>() + }); + + // Tool choice + let tool_choice = req.tool_config.as_ref().map(|tc| { + match tc.function_calling_config.mode.as_str() { + "NONE" => anthropic::ToolChoice::None, + "ANY" => match tc.function_calling_config.allowed_function_names.as_deref() { + Some([name]) => anthropic::ToolChoice::Tool { name: name.clone() }, + _ => anthropic::ToolChoice::Any { + disable_parallel_tool_use: None, + }, + }, + // AUTO or anything else + _ => anthropic::ToolChoice::Auto { + disable_parallel_tool_use: None, + }, + } + }); + + anthropic::MessageCreateRequest { + model: model.to_string(), + max_tokens, + messages, + system, + temperature, + top_p, + top_k, + stop_sequences, + tools, + tool_choice, + metadata: None, + thinking: None, + stream: None, + extra: Default::default(), + } +} + +/// Convert a single Gemini `Content` turn into an Anthropic `InputMessage`. +/// Returns `None` if the turn produces no content blocks (e.g. all parts were dropped). +fn gemini_content_to_input_message( + content: &gemini::Content, + name_to_id: &HashMap, +) -> Option { + let role = match content.role.as_deref() { + Some("model") => anthropic::Role::Assistant, + // "user", None, or anything unrecognised -> user. + _ => anthropic::Role::User, + }; + let blocks: Vec = content + .parts + .iter() + .filter_map(|p| gemini_input_part_to_block(p, name_to_id)) + .collect(); + if blocks.is_empty() { + return None; + } + Some(anthropic::InputMessage { + role, + content: anthropic::Content::Blocks(blocks), + }) +} + +/// Convert a single Gemini `Part` from a user/model message into an Anthropic `ContentBlock`. +fn gemini_input_part_to_block( + part: &gemini::Part, + name_to_id: &HashMap, +) -> Option { + if let Some(ref text) = part.text { + return Some(anthropic::ContentBlock::Text { text: text.clone() }); + } + if let Some(ref fc) = part.function_call { + let id = name_to_id + .get(&fc.name) + .cloned() + .unwrap_or_else(generate_tool_use_id); + return Some(anthropic::ContentBlock::ToolUse { + id, + name: fc.name.clone(), + input: fc.args.clone(), + }); + } + if let Some(ref fr) = part.function_response { + let tool_use_id = name_to_id + .get(&fr.name) + .cloned() + .unwrap_or_else(generate_tool_use_id); + return Some(anthropic::ContentBlock::ToolResult { + tool_use_id, + content: Some(anthropic::ToolResultContent::Text( + serde_json::to_string(&fr.response).unwrap_or_default(), + )), + is_error: None, + }); + } + if let Some(ref data) = part.inline_data { + if data.mime_type.starts_with("image/") { + return Some(anthropic::ContentBlock::Image { + source: anthropic::ImageSource { + source_type: "base64".to_string(), + media_type: Some(data.mime_type.clone()), + data: Some(data.data.clone()), + url: None, + }, + }); + } + // Non-image inline data (audio, video, etc.) has no Anthropic equivalent — drop. + tracing::warn!( + mime_type = %data.mime_type, + "dropping inline_data part with non-image mime type (no Anthropic equivalent)" + ); + } + None +} + +/// Convert an Anthropic `MessageResponse` into a Gemini `GenerateContentResponse`. +/// +/// Used when the proxy accepts Gemini CLI input and must return Gemini-format output. +/// This is the inverse of `gemini_to_anthropic_response`. +pub fn anthropic_to_gemini_response( + resp: &anthropic::MessageResponse, +) -> gemini_resp::GenerateContentResponse { + let parts: Vec = resp + .content + .iter() + .filter_map(|block| match block { + anthropic::ContentBlock::Text { text } => Some(gemini::Part::text(text.clone())), + anthropic::ContentBlock::ToolUse { name, input, .. } => { + Some(gemini::Part::function_call(name.clone(), input.clone())) + } + anthropic::ContentBlock::Thinking { thinking, .. } => Some(gemini::Part { + thought: Some(true), + text: Some(thinking.clone()), + ..Default::default() + }), + // ToolResult, Image, Document, RedactedThinking: not expected in model output. + _ => None, + }) + .collect(); + + let finish_reason = resp.stop_reason.as_ref().map(|sr| match sr { + anthropic::StopReason::EndTurn | anthropic::StopReason::ToolUse => { + gemini_resp::FinishReason::STOP + } + anthropic::StopReason::MaxTokens => gemini_resp::FinishReason::MAX_TOKENS, + anthropic::StopReason::StopSequence => gemini_resp::FinishReason::STOP, + _ => gemini_resp::FinishReason::STOP, + }); + + let candidate = gemini_resp::Candidate { + content: gemini::Content { + role: Some("model".to_string()), + parts, + }, + finish_reason, + safety_ratings: None, + }; + + gemini_resp::GenerateContentResponse { + candidates: vec![candidate], + usage_metadata: Some(gemini_resp::UsageMetadata { + prompt_token_count: resp.usage.input_tokens, + candidates_token_count: resp.usage.output_tokens, + total_token_count: resp.usage.input_tokens + resp.usage.output_tokens, + cached_content_token_count: 0, + }), + model_version: Some(resp.model.clone()), + } +} diff --git a/crates/translator/src/mapping/gemini_message_map/tests.rs b/crates/translator/src/mapping/gemini_message_map/tests.rs index 410c8bb..c7bb9bc 100644 --- a/crates/translator/src/mapping/gemini_message_map/tests.rs +++ b/crates/translator/src/mapping/gemini_message_map/tests.rs @@ -1,4 +1,7 @@ use super::*; +use crate::anthropic::messages as anthropic; +use crate::gemini::request as gemini; +use crate::gemini::response as gemini_resp; use serde_json::json; // Helper: build a minimal Anthropic request for testing. diff --git a/crates/translator/src/mapping/reverse_message_map/context.rs b/crates/translator/src/mapping/reverse_message_map/context.rs new file mode 100644 index 0000000..1a6f893 --- /dev/null +++ b/crates/translator/src/mapping/reverse_message_map/context.rs @@ -0,0 +1,94 @@ +// Request-local translation context: sanitized <-> original Anthropic tool +// name mapping used to round-trip OpenAI tool names through Anthropic. + +use crate::openai; +use std::collections::BTreeMap; + +/// Request-local metadata needed to round-trip Anthropic-compatible tool names. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct AnthropicTranslationContext { + original_to_sanitized_tool_names: BTreeMap, + sanitized_to_original_tool_names: BTreeMap, +} + +impl AnthropicTranslationContext { + pub fn from_openai_request(req: &openai::ChatCompletionRequest) -> Self { + let mut ctx = Self::default(); + + if let Some(tools) = &req.tools { + for tool in tools { + ctx.register_tool_name(&tool.function.name); + } + } + if let Some(openai::ChatToolChoice::Named(named)) = &req.tool_choice { + ctx.register_tool_name(&named.function.name); + } + for message in &req.messages { + if let Some(tool_calls) = &message.tool_calls { + for tool_call in tool_calls { + ctx.register_tool_name(&tool_call.function.name); + } + } + } + + ctx + } + + pub fn sanitized_tool_name(&self, name: &str) -> String { + self.original_to_sanitized_tool_names + .get(name) + .cloned() + .unwrap_or_else(|| name.to_string()) + } + + pub fn original_tool_name(&self, name: &str) -> String { + self.sanitized_to_original_tool_names + .get(name) + .cloned() + .unwrap_or_else(|| name.to_string()) + } + + fn register_tool_name(&mut self, original: &str) -> String { + if let Some(existing) = self.original_to_sanitized_tool_names.get(original) { + return existing.clone(); + } + + let base = basic_sanitize_anthropic_tool_name(original); + let mut candidate = base.clone(); + let mut suffix_index = 2usize; + while self + .sanitized_to_original_tool_names + .contains_key(&candidate) + { + let suffix = format!("_{suffix_index}"); + let keep = 128usize.saturating_sub(suffix.len()); + candidate = format!("{}{}", &base[..base.len().min(keep)], suffix); + suffix_index += 1; + } + + self.original_to_sanitized_tool_names + .insert(original.to_string(), candidate.clone()); + self.sanitized_to_original_tool_names + .insert(candidate.clone(), original.to_string()); + candidate + } +} + +fn basic_sanitize_anthropic_tool_name(original: &str) -> String { + let mut sanitized: String = original + .chars() + .map(|c| { + if c.is_ascii_alphanumeric() || c == '_' || c == '-' { + c + } else { + '_' + } + }) + .take(128) + .collect(); + + if sanitized.is_empty() { + sanitized = "tool".to_string(); + } + sanitized +} diff --git a/crates/translator/src/mapping/reverse_message_map/mod.rs b/crates/translator/src/mapping/reverse_message_map/mod.rs index 32d65b6..0cfcedb 100644 --- a/crates/translator/src/mapping/reverse_message_map/mod.rs +++ b/crates/translator/src/mapping/reverse_message_map/mod.rs @@ -2,596 +2,24 @@ // // Converts OpenAI-format requests to Anthropic format (for accepting OpenAI // input) and Anthropic responses back to OpenAI format. +// +// - [`context`] request-local sanitized <-> original tool-name mapping +// - [`request`] OpenAI request -> Anthropic request +// - [`response`] Anthropic response -> OpenAI response -use crate::anthropic; -use crate::error::TranslateError; -use crate::mapping::{tools_map, usage_map, warnings::TranslationWarnings}; -use crate::openai; -use crate::util; -use std::collections::BTreeMap; +mod context; +mod request; +mod response; -/// Request-local metadata needed to round-trip Anthropic-compatible tool names. -#[derive(Debug, Clone, Default, PartialEq, Eq)] -pub struct AnthropicTranslationContext { - original_to_sanitized_tool_names: BTreeMap, - sanitized_to_original_tool_names: BTreeMap, -} - -impl AnthropicTranslationContext { - pub fn from_openai_request(req: &openai::ChatCompletionRequest) -> Self { - let mut ctx = Self::default(); - - if let Some(tools) = &req.tools { - for tool in tools { - ctx.register_tool_name(&tool.function.name); - } - } - if let Some(openai::ChatToolChoice::Named(named)) = &req.tool_choice { - ctx.register_tool_name(&named.function.name); - } - for message in &req.messages { - if let Some(tool_calls) = &message.tool_calls { - for tool_call in tool_calls { - ctx.register_tool_name(&tool_call.function.name); - } - } - } - - ctx - } - - pub fn sanitized_tool_name(&self, name: &str) -> String { - self.original_to_sanitized_tool_names - .get(name) - .cloned() - .unwrap_or_else(|| name.to_string()) - } - - pub fn original_tool_name(&self, name: &str) -> String { - self.sanitized_to_original_tool_names - .get(name) - .cloned() - .unwrap_or_else(|| name.to_string()) - } - - fn register_tool_name(&mut self, original: &str) -> String { - if let Some(existing) = self.original_to_sanitized_tool_names.get(original) { - return existing.clone(); - } - - let base = basic_sanitize_anthropic_tool_name(original); - let mut candidate = base.clone(); - let mut suffix_index = 2usize; - while self - .sanitized_to_original_tool_names - .contains_key(&candidate) - { - let suffix = format!("_{suffix_index}"); - let keep = 128usize.saturating_sub(suffix.len()); - candidate = format!("{}{}", &base[..base.len().min(keep)], suffix); - suffix_index += 1; - } - - self.original_to_sanitized_tool_names - .insert(original.to_string(), candidate.clone()); - self.sanitized_to_original_tool_names - .insert(candidate.clone(), original.to_string()); - candidate - } -} - -fn basic_sanitize_anthropic_tool_name(original: &str) -> String { - let mut sanitized: String = original - .chars() - .map(|c| { - if c.is_ascii_alphanumeric() || c == '_' || c == '-' { - c - } else { - '_' - } - }) - .take(128) - .collect(); - - if sanitized.is_empty() { - sanitized = "tool".to_string(); - } - sanitized -} - -/// Convert an OpenAI ChatCompletionRequest to an Anthropic MessageCreateRequest. -/// -/// Returns an error if `max_tokens` and `max_completion_tokens` are both absent -/// (Anthropic requires `max_tokens`). -pub fn openai_to_anthropic_request( - req: &openai::ChatCompletionRequest, - warnings: &mut TranslationWarnings, -) -> Result { - openai_to_anthropic_request_inner(req, warnings, &AnthropicTranslationContext::default()) -} - -/// Convert an OpenAI request and return request-local translation context. -pub fn openai_to_anthropic_request_with_context( - req: &openai::ChatCompletionRequest, - warnings: &mut TranslationWarnings, -) -> Result<(anthropic::MessageCreateRequest, AnthropicTranslationContext), TranslateError> { - let context = AnthropicTranslationContext::from_openai_request(req); - let req = openai_to_anthropic_request_inner(req, warnings, &context)?; - Ok((req, context)) -} - -fn openai_to_anthropic_request_inner( - req: &openai::ChatCompletionRequest, - warnings: &mut TranslationWarnings, - context: &AnthropicTranslationContext, -) -> Result { - // max_tokens is required in Anthropic; reject if absent. - // NOT A BUG: Anthropic has no server-side default for max_tokens — the field - // is mandatory per the API spec. Injecting a silent default would mask - // misconfigured clients. Standard OpenAI SDKs that omit max_tokens are not - // supported by the Anthropic API regardless, so rejecting with 400 is correct. - let max_tokens = req - .max_completion_tokens - .or(req.max_tokens) - .ok_or_else(|| { - TranslateError::MissingField("max_tokens or max_completion_tokens is required".into()) - })?; - - let mut system: Option = None; - let mut messages = Vec::new(); - - for msg in &req.messages { - match msg.role { - openai::ChatRole::System | openai::ChatRole::Developer => { - // Extract system messages into the Anthropic system field. - // Multiple system messages are concatenated. - let text = extract_text_content(&msg.content); - if !text.is_empty() { - match &mut system { - Some(anthropic::System::Text(existing)) => { - existing.push('\n'); - existing.push_str(&text); - } - None => { - system = Some(anthropic::System::Text(text)); - } - _ => {} - } - } - } - openai::ChatRole::User => { - let content = convert_openai_content_to_anthropic(&msg.content); - messages.push(anthropic::InputMessage { - role: anthropic::Role::User, - content, - }); - } - openai::ChatRole::Assistant => { - let content = convert_assistant_to_anthropic(msg, context, warnings); - messages.push(anthropic::InputMessage { - role: anthropic::Role::Assistant, - content, - }); - } - openai::ChatRole::Tool => { - // Tool role messages become Anthropic tool_result blocks - // on a user message (Anthropic requires tool results in user turn) - let text = extract_text_content(&msg.content); - let tool_use_id = msg.tool_call_id.clone().unwrap_or_default(); - let content_block = anthropic::ContentBlock::ToolResult { - tool_use_id, - content: if text.is_empty() { - None - } else { - Some(anthropic::ToolResultContent::Text(text)) - }, - is_error: None, - }; - messages.push(anthropic::InputMessage { - role: anthropic::Role::User, - content: anthropic::Content::Blocks(vec![content_block]), - }); - } - openai::ChatRole::Function => { - // Deprecated function role: treat as tool - let text = extract_text_content(&msg.content); - let tool_use_id = msg.name.clone().unwrap_or_default(); - let content_block = anthropic::ContentBlock::ToolResult { - tool_use_id, - content: if text.is_empty() { - None - } else { - Some(anthropic::ToolResultContent::Text(text)) - }, - is_error: None, - }; - messages.push(anthropic::InputMessage { - role: anthropic::Role::User, - content: anthropic::Content::Blocks(vec![content_block]), - }); - } - } - } - - let tools = req.tools.as_ref().map(|t| { - let mut tools = tools_map::openai_tools_to_anthropic(t); - for tool in &mut tools { - tool.name = context.sanitized_tool_name(&tool.name); - } - tools - }); - - let mut tool_choice = req - .tool_choice - .as_ref() - .map(tools_map::openai_tool_choice_to_anthropic); - if let Some(anthropic::ToolChoice::Tool { name }) = &mut tool_choice { - *name = context.sanitized_tool_name(name); - } - - let stop_sequences = req.stop.as_ref().map(|s| match s { - openai::Stop::Single(s) => vec![s.clone()], - openai::Stop::Multiple(v) => v.clone(), - }); - - let metadata = req.user.as_ref().map(|u| anthropic::Metadata { - user_id: Some(u.clone()), - }); - - if req.presence_penalty.is_some() { - warnings.add("presence_penalty"); - } - if req.frequency_penalty.is_some() { - warnings.add("frequency_penalty"); - } - if req.response_format.is_some() { - warnings.add("response_format"); - } - if req.extra.contains_key("logprobs") { - warnings.add("logprobs"); - } - if req.extra.contains_key("n") { - warnings.add("n"); - } - if req.extra.contains_key("seed") { - warnings.add("seed"); - } - if req.stream_options.is_some() { - warnings.add("stream_options"); - } - - let tool_choice = match (tool_choice, req.parallel_tool_calls) { - (Some(anthropic::ToolChoice::Auto { .. }), Some(false)) => { - Some(anthropic::ToolChoice::Auto { - disable_parallel_tool_use: Some(true), - }) - } - (Some(anthropic::ToolChoice::Any { .. }), Some(false)) => { - Some(anthropic::ToolChoice::Any { - disable_parallel_tool_use: Some(true), - }) - } - (tc, _) => tc, - }; - - Ok(anthropic::MessageCreateRequest { - model: req.model.clone(), - max_tokens, - messages, - system, - temperature: req.temperature, - top_p: req.top_p, - top_k: None, - stop_sequences, - tools, - tool_choice, - metadata, - thinking: None, - stream: req.stream, - extra: serde_json::Map::new(), - }) -} - -/// Convert an Anthropic MessageResponse to an OpenAI ChatCompletionResponse. -pub fn anthropic_to_openai_response( - resp: &anthropic::MessageResponse, - model: &str, -) -> openai::ChatCompletionResponse { - anthropic_to_openai_response_with_context(resp, model, &AnthropicTranslationContext::default()) -} - -/// Convert an Anthropic MessageResponse using request-local translation context. -pub fn anthropic_to_openai_response_with_context( - resp: &anthropic::MessageResponse, - model: &str, - context: &AnthropicTranslationContext, -) -> openai::ChatCompletionResponse { - let mut text_parts = Vec::new(); - let mut tool_calls = Vec::new(); - let mut reasoning_content: Option = None; - let mut thinking_blocks = Vec::new(); - - for block in &resp.content { - match block { - anthropic::ContentBlock::Text { text } => { - text_parts.push(text.clone()); - } - anthropic::ContentBlock::ToolUse { id, name, input } - | anthropic::ContentBlock::ServerToolUse { id, name, input } => { - tool_calls.push(openai::ToolCall { - id: id.clone(), - call_type: "function".to_string(), - function: openai::FunctionCall { - name: context.original_tool_name(name), - arguments: util::json::value_to_json_string(input), - }, - }); - } - anthropic::ContentBlock::Thinking { - thinking, - signature, - } => { - thinking_blocks.push(openai::ThinkingBlock::Thinking { - thinking: thinking.clone(), - signature: signature.clone(), - }); - match &mut reasoning_content { - Some(existing) => { - existing.push_str(thinking); - } - None => { - reasoning_content = Some(thinking.clone()); - } - } - } - anthropic::ContentBlock::RedactedThinking { data } => { - thinking_blocks - .push(openai::ThinkingBlock::RedactedThinking { data: data.clone() }); - } - _ => {} - } - } - - let content = if text_parts.is_empty() { - None - } else { - Some(openai::ChatContent::Text(text_parts.join(""))) - }; - - let finish_reason = resp - .stop_reason - .as_ref() - .map(anthropic_stop_reason_to_openai); - - let usage = usage_map::anthropic_to_openai_usage(&resp.usage); - - let id = format!("chatcmpl-{}", util::ids::generate_uuid()); - - openai::ChatCompletionResponse { - id, - object: "chat.completion".to_string(), - model: model.to_string(), - choices: vec![openai::Choice { - index: 0, - message: openai::ChatMessage { - role: openai::ChatRole::Assistant, - content, - name: None, - tool_calls: if tool_calls.is_empty() { - None - } else { - Some(tool_calls) - }, - tool_call_id: None, - refusal: None, - reasoning_content, - thinking_blocks: if thinking_blocks.is_empty() { - None - } else { - Some(thinking_blocks) - }, - }, - finish_reason, - logprobs: None, - }], - usage: Some(usage), - created: resp.created, - system_fingerprint: None, - service_tier: None, - } -} - -/// Map Anthropic stop_reason to OpenAI finish_reason. -pub fn anthropic_stop_reason_to_openai( - stop_reason: &anthropic::StopReason, -) -> openai::FinishReason { - match stop_reason { - anthropic::StopReason::EndTurn => openai::FinishReason::Stop, - anthropic::StopReason::MaxTokens => openai::FinishReason::Length, - anthropic::StopReason::ToolUse => openai::FinishReason::ToolCalls, - anthropic::StopReason::StopSequence => openai::FinishReason::Stop, - anthropic::StopReason::PauseTurn => openai::FinishReason::Stop, - anthropic::StopReason::Refusal => openai::FinishReason::ContentFilter, - anthropic::StopReason::Unknown => openai::FinishReason::Unknown, - } -} - -/// Compute warnings for an OpenAI request about features that will be dropped. -pub fn compute_openai_request_warnings(req: &openai::ChatCompletionRequest) -> TranslationWarnings { - let mut w = TranslationWarnings::default(); - openai_to_anthropic_request(req, &mut w).ok(); - w -} - -// --- Helper functions --- - -fn extract_text_content(content: &Option) -> String { - match content { - Some(openai::ChatContent::Text(s)) => s.clone(), - Some(openai::ChatContent::Parts(parts)) => { - let mut had_non_text = false; - let text = parts - .iter() - .filter_map(|p| match p { - openai::ChatContentPart::Text { text } => Some(text.as_str()), - _ => { - had_non_text = true; - None - } - }) - .collect::>() - .join(""); - if had_non_text { - tracing::warn!( - "message contains non-text content parts (image/file); \ - only text parts are extracted as plain text" - ); - } - text - } - None => String::new(), - } -} - -fn convert_openai_content_to_anthropic( - content: &Option, -) -> anthropic::Content { - match content { - Some(openai::ChatContent::Text(s)) => anthropic::Content::Text(s.clone()), - Some(openai::ChatContent::Parts(parts)) => { - let mut blocks = Vec::new(); - for part in parts { - match part { - openai::ChatContentPart::Text { text } => { - blocks.push(anthropic::ContentBlock::Text { text: text.clone() }); - } - openai::ChatContentPart::ImageUrl { image_url } => { - // Parse data URIs back to base64 + media_type - let source = url_to_image_source(&image_url.url); - blocks.push(anthropic::ContentBlock::Image { source }); - } - // InputAudio and File have no Anthropic equivalent; drop them - _ => {} - } - } - if blocks.is_empty() { - anthropic::Content::Text(String::new()) - } else { - anthropic::Content::Blocks(blocks) - } - } - None => anthropic::Content::Text(String::new()), - } -} - -fn convert_assistant_to_anthropic( - msg: &openai::ChatMessage, - context: &AnthropicTranslationContext, - warnings: &mut TranslationWarnings, -) -> anthropic::Content { - let mut blocks = Vec::new(); - - // Prefer exact LiteLLM/Anthropic blocks because they preserve signatures and - // redacted state needed for tool-result continuations. Only treat them as - // authoritative when they yield at least one block; an empty or all-`Unknown` - // array must not suppress the reasoning_content fallback. - let mut pushed_thinking = false; - if let Some(ref thinking_blocks) = msg.thinking_blocks { - for block in thinking_blocks { - if let Some(block) = crate::mapping::openai_thinking_block_to_anthropic(block) { - blocks.push(block); - pushed_thinking = true; - } - } - } - if !pushed_thinking { - if msg - .tool_calls - .as_ref() - .is_some_and(|calls| !calls.is_empty()) - { - // Text-only reasoning_content cannot carry Anthropic signatures. Do not - // synthesize unsigned thinking next to tool_use blocks; record that the - // reasoning text itself was dropped rather than losing it silently. - if msg - .reasoning_content - .as_ref() - .is_some_and(|r| !r.is_empty()) - { - warnings.add("reasoning_content_dropped_with_tool_calls"); - } - } else if let Some(ref reasoning) = msg.reasoning_content { - if !reasoning.is_empty() { - blocks.push(anthropic::ContentBlock::Thinking { - thinking: reasoning.clone(), - signature: None, - }); - } - } - } - - // Map text content - match &msg.content { - Some(openai::ChatContent::Text(text)) if !text.is_empty() => { - blocks.push(anthropic::ContentBlock::Text { text: text.clone() }); - } - Some(openai::ChatContent::Text(_)) => {} - Some(openai::ChatContent::Parts(parts)) => { - for part in parts { - if let openai::ChatContentPart::Text { text } = part { - blocks.push(anthropic::ContentBlock::Text { text: text.clone() }); - } - } - } - None => {} - } - - // Map tool calls to tool_use blocks - if let Some(ref tool_calls) = msg.tool_calls { - for tc in tool_calls { - blocks.push(anthropic::ContentBlock::ToolUse { - id: tc.id.clone(), - name: context.sanitized_tool_name(&tc.function.name), - input: util::json::parse_tool_arguments(&tc.function.arguments), - }); - } - } - - if blocks.is_empty() { - anthropic::Content::Text(String::new()) - } else if blocks.len() == 1 { - if let anthropic::ContentBlock::Text { ref text } = blocks[0] { - return anthropic::Content::Text(text.clone()); - } - anthropic::Content::Blocks(blocks) - } else { - anthropic::Content::Blocks(blocks) - } -} - -/// Parse a URL string into an Anthropic ImageSource. -/// Handles both data URIs (data:image/png;base64,...) and regular URLs. -fn url_to_image_source(url: &str) -> anthropic::ImageSource { - if let Some(rest) = url.strip_prefix("data:") { - // Parse data URI: data:media_type;base64,data - if let Some((meta, data)) = rest.split_once(',') { - let media_type = meta.strip_suffix(";base64").unwrap_or(meta); - return anthropic::ImageSource { - source_type: "base64".to_string(), - media_type: Some(media_type.to_string()), - data: Some(data.to_string()), - url: None, - }; - } - } - // Regular URL - anthropic::ImageSource { - source_type: "url".to_string(), - media_type: None, - data: None, - url: Some(url.to_string()), - } -} +pub use context::AnthropicTranslationContext; +pub use request::{ + compute_openai_request_warnings, openai_to_anthropic_request, + openai_to_anthropic_request_with_context, +}; +pub use response::{ + anthropic_stop_reason_to_openai, anthropic_to_openai_response, + anthropic_to_openai_response_with_context, +}; #[cfg(test)] mod tests; diff --git a/crates/translator/src/mapping/reverse_message_map/request.rs b/crates/translator/src/mapping/reverse_message_map/request.rs new file mode 100644 index 0000000..1b2c5f6 --- /dev/null +++ b/crates/translator/src/mapping/reverse_message_map/request.rs @@ -0,0 +1,378 @@ +// OpenAI Chat Completions request -> Anthropic Messages request. + +use super::context::AnthropicTranslationContext; +use crate::anthropic; +use crate::error::TranslateError; +use crate::mapping::{tools_map, warnings::TranslationWarnings}; +use crate::openai; +use crate::util; + +/// Convert an OpenAI ChatCompletionRequest to an Anthropic MessageCreateRequest. +/// +/// Returns an error if `max_tokens` and `max_completion_tokens` are both absent +/// (Anthropic requires `max_tokens`). +pub fn openai_to_anthropic_request( + req: &openai::ChatCompletionRequest, + warnings: &mut TranslationWarnings, +) -> Result { + openai_to_anthropic_request_inner(req, warnings, &AnthropicTranslationContext::default()) +} + +/// Convert an OpenAI request and return request-local translation context. +pub fn openai_to_anthropic_request_with_context( + req: &openai::ChatCompletionRequest, + warnings: &mut TranslationWarnings, +) -> Result<(anthropic::MessageCreateRequest, AnthropicTranslationContext), TranslateError> { + let context = AnthropicTranslationContext::from_openai_request(req); + let req = openai_to_anthropic_request_inner(req, warnings, &context)?; + Ok((req, context)) +} + +fn openai_to_anthropic_request_inner( + req: &openai::ChatCompletionRequest, + warnings: &mut TranslationWarnings, + context: &AnthropicTranslationContext, +) -> Result { + // max_tokens is required in Anthropic; reject if absent. + // NOT A BUG: Anthropic has no server-side default for max_tokens — the field + // is mandatory per the API spec. Injecting a silent default would mask + // misconfigured clients. Standard OpenAI SDKs that omit max_tokens are not + // supported by the Anthropic API regardless, so rejecting with 400 is correct. + let max_tokens = req + .max_completion_tokens + .or(req.max_tokens) + .ok_or_else(|| { + TranslateError::MissingField("max_tokens or max_completion_tokens is required".into()) + })?; + + let mut system: Option = None; + let mut messages = Vec::new(); + + for msg in &req.messages { + match msg.role { + openai::ChatRole::System | openai::ChatRole::Developer => { + // Extract system messages into the Anthropic system field. + // Multiple system messages are concatenated. + let text = extract_text_content(&msg.content); + if !text.is_empty() { + match &mut system { + Some(anthropic::System::Text(existing)) => { + existing.push('\n'); + existing.push_str(&text); + } + None => { + system = Some(anthropic::System::Text(text)); + } + _ => {} + } + } + } + openai::ChatRole::User => { + let content = convert_openai_content_to_anthropic(&msg.content); + messages.push(anthropic::InputMessage { + role: anthropic::Role::User, + content, + }); + } + openai::ChatRole::Assistant => { + let content = convert_assistant_to_anthropic(msg, context, warnings); + messages.push(anthropic::InputMessage { + role: anthropic::Role::Assistant, + content, + }); + } + openai::ChatRole::Tool => { + // Tool role messages become Anthropic tool_result blocks + // on a user message (Anthropic requires tool results in user turn) + let text = extract_text_content(&msg.content); + let tool_use_id = msg.tool_call_id.clone().unwrap_or_default(); + let content_block = anthropic::ContentBlock::ToolResult { + tool_use_id, + content: if text.is_empty() { + None + } else { + Some(anthropic::ToolResultContent::Text(text)) + }, + is_error: None, + }; + messages.push(anthropic::InputMessage { + role: anthropic::Role::User, + content: anthropic::Content::Blocks(vec![content_block]), + }); + } + openai::ChatRole::Function => { + // Deprecated function role: treat as tool + let text = extract_text_content(&msg.content); + let tool_use_id = msg.name.clone().unwrap_or_default(); + let content_block = anthropic::ContentBlock::ToolResult { + tool_use_id, + content: if text.is_empty() { + None + } else { + Some(anthropic::ToolResultContent::Text(text)) + }, + is_error: None, + }; + messages.push(anthropic::InputMessage { + role: anthropic::Role::User, + content: anthropic::Content::Blocks(vec![content_block]), + }); + } + } + } + + let tools = req.tools.as_ref().map(|t| { + let mut tools = tools_map::openai_tools_to_anthropic(t); + for tool in &mut tools { + tool.name = context.sanitized_tool_name(&tool.name); + } + tools + }); + + let mut tool_choice = req + .tool_choice + .as_ref() + .map(tools_map::openai_tool_choice_to_anthropic); + if let Some(anthropic::ToolChoice::Tool { name }) = &mut tool_choice { + *name = context.sanitized_tool_name(name); + } + + let stop_sequences = req.stop.as_ref().map(|s| match s { + openai::Stop::Single(s) => vec![s.clone()], + openai::Stop::Multiple(v) => v.clone(), + }); + + let metadata = req.user.as_ref().map(|u| anthropic::Metadata { + user_id: Some(u.clone()), + }); + + if req.presence_penalty.is_some() { + warnings.add("presence_penalty"); + } + if req.frequency_penalty.is_some() { + warnings.add("frequency_penalty"); + } + if req.response_format.is_some() { + warnings.add("response_format"); + } + if req.extra.contains_key("logprobs") { + warnings.add("logprobs"); + } + if req.extra.contains_key("n") { + warnings.add("n"); + } + if req.extra.contains_key("seed") { + warnings.add("seed"); + } + if req.stream_options.is_some() { + warnings.add("stream_options"); + } + + let tool_choice = match (tool_choice, req.parallel_tool_calls) { + (Some(anthropic::ToolChoice::Auto { .. }), Some(false)) => { + Some(anthropic::ToolChoice::Auto { + disable_parallel_tool_use: Some(true), + }) + } + (Some(anthropic::ToolChoice::Any { .. }), Some(false)) => { + Some(anthropic::ToolChoice::Any { + disable_parallel_tool_use: Some(true), + }) + } + (tc, _) => tc, + }; + + Ok(anthropic::MessageCreateRequest { + model: req.model.clone(), + max_tokens, + messages, + system, + temperature: req.temperature, + top_p: req.top_p, + top_k: None, + stop_sequences, + tools, + tool_choice, + metadata, + thinking: None, + stream: req.stream, + extra: serde_json::Map::new(), + }) +} + +/// Compute warnings for an OpenAI request about features that will be dropped. +pub fn compute_openai_request_warnings(req: &openai::ChatCompletionRequest) -> TranslationWarnings { + let mut w = TranslationWarnings::default(); + openai_to_anthropic_request(req, &mut w).ok(); + w +} + +// --- Helper functions --- + +fn extract_text_content(content: &Option) -> String { + match content { + Some(openai::ChatContent::Text(s)) => s.clone(), + Some(openai::ChatContent::Parts(parts)) => { + let mut had_non_text = false; + let text = parts + .iter() + .filter_map(|p| match p { + openai::ChatContentPart::Text { text } => Some(text.as_str()), + _ => { + had_non_text = true; + None + } + }) + .collect::>() + .join(""); + if had_non_text { + tracing::warn!( + "message contains non-text content parts (image/file); \ + only text parts are extracted as plain text" + ); + } + text + } + None => String::new(), + } +} + +fn convert_openai_content_to_anthropic( + content: &Option, +) -> anthropic::Content { + match content { + Some(openai::ChatContent::Text(s)) => anthropic::Content::Text(s.clone()), + Some(openai::ChatContent::Parts(parts)) => { + let mut blocks = Vec::new(); + for part in parts { + match part { + openai::ChatContentPart::Text { text } => { + blocks.push(anthropic::ContentBlock::Text { text: text.clone() }); + } + openai::ChatContentPart::ImageUrl { image_url } => { + // Parse data URIs back to base64 + media_type + let source = url_to_image_source(&image_url.url); + blocks.push(anthropic::ContentBlock::Image { source }); + } + // InputAudio and File have no Anthropic equivalent; drop them + _ => {} + } + } + if blocks.is_empty() { + anthropic::Content::Text(String::new()) + } else { + anthropic::Content::Blocks(blocks) + } + } + None => anthropic::Content::Text(String::new()), + } +} + +fn convert_assistant_to_anthropic( + msg: &openai::ChatMessage, + context: &AnthropicTranslationContext, + warnings: &mut TranslationWarnings, +) -> anthropic::Content { + let mut blocks = Vec::new(); + + // Prefer exact LiteLLM/Anthropic blocks because they preserve signatures and + // redacted state needed for tool-result continuations. Only treat them as + // authoritative when they yield at least one block; an empty or all-`Unknown` + // array must not suppress the reasoning_content fallback. + let mut pushed_thinking = false; + if let Some(ref thinking_blocks) = msg.thinking_blocks { + for block in thinking_blocks { + if let Some(block) = crate::mapping::openai_thinking_block_to_anthropic(block) { + blocks.push(block); + pushed_thinking = true; + } + } + } + if !pushed_thinking { + if msg + .tool_calls + .as_ref() + .is_some_and(|calls| !calls.is_empty()) + { + // Text-only reasoning_content cannot carry Anthropic signatures. Do not + // synthesize unsigned thinking next to tool_use blocks; record that the + // reasoning text itself was dropped rather than losing it silently. + if msg + .reasoning_content + .as_ref() + .is_some_and(|r| !r.is_empty()) + { + warnings.add("reasoning_content_dropped_with_tool_calls"); + } + } else if let Some(ref reasoning) = msg.reasoning_content { + if !reasoning.is_empty() { + blocks.push(anthropic::ContentBlock::Thinking { + thinking: reasoning.clone(), + signature: None, + }); + } + } + } + + // Map text content + match &msg.content { + Some(openai::ChatContent::Text(text)) if !text.is_empty() => { + blocks.push(anthropic::ContentBlock::Text { text: text.clone() }); + } + Some(openai::ChatContent::Text(_)) => {} + Some(openai::ChatContent::Parts(parts)) => { + for part in parts { + if let openai::ChatContentPart::Text { text } = part { + blocks.push(anthropic::ContentBlock::Text { text: text.clone() }); + } + } + } + None => {} + } + + // Map tool calls to tool_use blocks + if let Some(ref tool_calls) = msg.tool_calls { + for tc in tool_calls { + blocks.push(anthropic::ContentBlock::ToolUse { + id: tc.id.clone(), + name: context.sanitized_tool_name(&tc.function.name), + input: util::json::parse_tool_arguments(&tc.function.arguments), + }); + } + } + + if blocks.is_empty() { + anthropic::Content::Text(String::new()) + } else if blocks.len() == 1 { + if let anthropic::ContentBlock::Text { ref text } = blocks[0] { + return anthropic::Content::Text(text.clone()); + } + anthropic::Content::Blocks(blocks) + } else { + anthropic::Content::Blocks(blocks) + } +} + +/// Parse a URL string into an Anthropic ImageSource. +/// Handles both data URIs (data:image/png;base64,...) and regular URLs. +pub(super) fn url_to_image_source(url: &str) -> anthropic::ImageSource { + if let Some(rest) = url.strip_prefix("data:") { + // Parse data URI: data:media_type;base64,data + if let Some((meta, data)) = rest.split_once(',') { + let media_type = meta.strip_suffix(";base64").unwrap_or(meta); + return anthropic::ImageSource { + source_type: "base64".to_string(), + media_type: Some(media_type.to_string()), + data: Some(data.to_string()), + url: None, + }; + } + } + // Regular URL + anthropic::ImageSource { + source_type: "url".to_string(), + media_type: None, + data: None, + url: Some(url.to_string()), + } +} diff --git a/crates/translator/src/mapping/reverse_message_map/response.rs b/crates/translator/src/mapping/reverse_message_map/response.rs new file mode 100644 index 0000000..a2bb110 --- /dev/null +++ b/crates/translator/src/mapping/reverse_message_map/response.rs @@ -0,0 +1,131 @@ +// Anthropic Messages response -> OpenAI Chat Completions response. + +use super::context::AnthropicTranslationContext; +use crate::anthropic; +use crate::mapping::usage_map; +use crate::openai; +use crate::util; + +/// Convert an Anthropic MessageResponse to an OpenAI ChatCompletionResponse. +pub fn anthropic_to_openai_response( + resp: &anthropic::MessageResponse, + model: &str, +) -> openai::ChatCompletionResponse { + anthropic_to_openai_response_with_context(resp, model, &AnthropicTranslationContext::default()) +} + +/// Convert an Anthropic MessageResponse using request-local translation context. +pub fn anthropic_to_openai_response_with_context( + resp: &anthropic::MessageResponse, + model: &str, + context: &AnthropicTranslationContext, +) -> openai::ChatCompletionResponse { + let mut text_parts = Vec::new(); + let mut tool_calls = Vec::new(); + let mut reasoning_content: Option = None; + let mut thinking_blocks = Vec::new(); + + for block in &resp.content { + match block { + anthropic::ContentBlock::Text { text } => { + text_parts.push(text.clone()); + } + anthropic::ContentBlock::ToolUse { id, name, input } + | anthropic::ContentBlock::ServerToolUse { id, name, input } => { + tool_calls.push(openai::ToolCall { + id: id.clone(), + call_type: "function".to_string(), + function: openai::FunctionCall { + name: context.original_tool_name(name), + arguments: util::json::value_to_json_string(input), + }, + }); + } + anthropic::ContentBlock::Thinking { + thinking, + signature, + } => { + thinking_blocks.push(openai::ThinkingBlock::Thinking { + thinking: thinking.clone(), + signature: signature.clone(), + }); + match &mut reasoning_content { + Some(existing) => { + existing.push_str(thinking); + } + None => { + reasoning_content = Some(thinking.clone()); + } + } + } + anthropic::ContentBlock::RedactedThinking { data } => { + thinking_blocks + .push(openai::ThinkingBlock::RedactedThinking { data: data.clone() }); + } + _ => {} + } + } + + let content = if text_parts.is_empty() { + None + } else { + Some(openai::ChatContent::Text(text_parts.join(""))) + }; + + let finish_reason = resp + .stop_reason + .as_ref() + .map(anthropic_stop_reason_to_openai); + + let usage = usage_map::anthropic_to_openai_usage(&resp.usage); + + let id = format!("chatcmpl-{}", util::ids::generate_uuid()); + + openai::ChatCompletionResponse { + id, + object: "chat.completion".to_string(), + model: model.to_string(), + choices: vec![openai::Choice { + index: 0, + message: openai::ChatMessage { + role: openai::ChatRole::Assistant, + content, + name: None, + tool_calls: if tool_calls.is_empty() { + None + } else { + Some(tool_calls) + }, + tool_call_id: None, + refusal: None, + reasoning_content, + thinking_blocks: if thinking_blocks.is_empty() { + None + } else { + Some(thinking_blocks) + }, + }, + finish_reason, + logprobs: None, + }], + usage: Some(usage), + created: resp.created, + system_fingerprint: None, + service_tier: None, + } +} + +/// Map Anthropic stop_reason to OpenAI finish_reason. +pub fn anthropic_stop_reason_to_openai( + stop_reason: &anthropic::StopReason, +) -> openai::FinishReason { + match stop_reason { + anthropic::StopReason::EndTurn => openai::FinishReason::Stop, + anthropic::StopReason::MaxTokens => openai::FinishReason::Length, + anthropic::StopReason::ToolUse => openai::FinishReason::ToolCalls, + anthropic::StopReason::StopSequence => openai::FinishReason::Stop, + anthropic::StopReason::PauseTurn => openai::FinishReason::Stop, + anthropic::StopReason::Refusal => openai::FinishReason::ContentFilter, + anthropic::StopReason::Unknown => openai::FinishReason::Unknown, + } +} diff --git a/crates/translator/src/mapping/reverse_message_map/tests.rs b/crates/translator/src/mapping/reverse_message_map/tests.rs index 2ec7c90..b4964a4 100644 --- a/crates/translator/src/mapping/reverse_message_map/tests.rs +++ b/crates/translator/src/mapping/reverse_message_map/tests.rs @@ -1,4 +1,7 @@ +use super::request::url_to_image_source; use super::*; +use crate::mapping::warnings::TranslationWarnings; +use crate::{anthropic, openai}; use serde_json::json; fn make_basic_request() -> openai::ChatCompletionRequest {