Skip to main content

provider_agent/backend/
venice.rs

1//! Venice.ai BYOK passthrough.
2//!
3//! Forwards to `https://api.venice.ai/api/v1/...` with the operator's key.
4//! `venice_parameters` in the request body is preserved unmodified — the
5//! generic `stream_chat_completions` helper forwards the entire JSON body, so
6//! Venice-specific fields ride along automatically.
7
8use async_trait::async_trait;
9
10use super::http::{build_client, get_json, parse_openai_models, probe, stream_chat_completions};
11use super::{
12    Backend, BackendError, BackendHealth, BackendModel, BackendResult, Job, JobResult, JobSink,
13};
14
15const BASE_URL: &str = "https://api.venice.ai/api/v1";
16
17pub struct VeniceBackend {
18    id: String,
19    api_key: String,
20    client: reqwest::Client,
21}
22
23impl VeniceBackend {
24    pub fn from_env(api_key_env: &str) -> BackendResult<Self> {
25        let api_key = std::env::var(api_key_env)
26            .ok()
27            .filter(|s| !s.trim().is_empty())
28            .ok_or(BackendError::MissingApiKey("venice"))?;
29        Ok(Self {
30            id: "venice".to_string(),
31            api_key,
32            client: build_client(),
33        })
34    }
35}
36
37#[async_trait]
38impl Backend for VeniceBackend {
39    fn kind(&self) -> &'static str {
40        "venice"
41    }
42
43    fn id(&self) -> &str {
44        &self.id
45    }
46
47    async fn list_models(&self) -> BackendResult<Vec<BackendModel>> {
48        let url = format!("{BASE_URL}/models");
49        let v = get_json(&self.client, &url, Some(&self.api_key)).await?;
50        Ok(parse_openai_models(&v, false))
51    }
52
53    async fn health(&self) -> BackendResult<BackendHealth> {
54        let url = format!("{BASE_URL}/models");
55        match probe(&self.client, &url, Some(&self.api_key)).await {
56            Ok(latency_ms) => Ok(BackendHealth {
57                reachable: true,
58                latency_ms: Some(latency_ms),
59                last_error: None,
60            }),
61            Err(e) => Ok(BackendHealth {
62                reachable: false,
63                latency_ms: None,
64                last_error: Some(e.to_string()),
65            }),
66        }
67    }
68
69    async fn execute(&self, job: &Job, sink: &mut dyn JobSink) -> BackendResult<JobResult> {
70        let endpoint = format!("{BASE_URL}/chat/completions");
71        stream_chat_completions(&self.client, &endpoint, Some(&self.api_key), job, sink).await
72    }
73}