use std::process::Stdio;
use async_trait::async_trait;
use newt_core::router::Tier;
use plugins_protocol::AGENT_KEY_ENV;
use tokio::process::Command;
use crate::backend::{ChatReply, ChatRequest, InferenceBackend};
pub struct ProviderPluginBackend {
name: String,
command: String,
model_id: String,
tiers: Vec<Tier>,
agent_key_envelope: Option<String>,
}
impl ProviderPluginBackend {
pub fn new(
name: impl Into<String>,
command: impl Into<String>,
model_id: impl Into<String>,
tiers: Vec<Tier>,
) -> Self {
Self {
name: name.into(),
command: command.into(),
model_id: model_id.into(),
tiers,
agent_key_envelope: None,
}
}
#[must_use]
pub fn with_agent_key_envelope(mut self, envelope: impl Into<String>) -> Self {
let s = envelope.into();
self.agent_key_envelope = if s.is_empty() { None } else { Some(s) };
self
}
#[must_use]
pub fn agent_key_envelope(&self) -> Option<&str> {
self.agent_key_envelope.as_deref()
}
#[must_use]
pub fn spawn_command(&self) -> Command {
let mut cmd = Command::new(&self.command);
cmd.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::inherit());
if let Some(envelope) = &self.agent_key_envelope {
cmd.env(AGENT_KEY_ENV, envelope);
} else {
cmd.env_remove(AGENT_KEY_ENV);
}
cmd
}
}
#[async_trait]
impl InferenceBackend for ProviderPluginBackend {
fn name(&self) -> &str {
&self.name
}
fn model_id(&self) -> &str {
&self.model_id
}
fn supports_tier(&self, tier: Tier) -> bool {
self.tiers.contains(&tier)
}
async fn complete(&self, _req: ChatRequest) -> anyhow::Result<ChatReply> {
anyhow::bail!(
"ProviderPluginBackend.complete not yet implemented (command={})",
self.command
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_defaults_envelope_to_none() {
let b = ProviderPluginBackend::new("openai", "newt-provider-openai", "gpt-4", vec![]);
assert_eq!(b.agent_key_envelope(), None);
}
#[test]
fn with_agent_key_envelope_stores_value() {
let b = ProviderPluginBackend::new("openai", "x", "gpt-4", vec![])
.with_agent_key_envelope("abc123==");
assert_eq!(b.agent_key_envelope(), Some("abc123=="));
}
#[test]
fn empty_envelope_is_treated_as_none() {
let b =
ProviderPluginBackend::new("openai", "x", "gpt-4", vec![]).with_agent_key_envelope("");
assert_eq!(b.agent_key_envelope(), None);
}
#[test]
fn supports_tier_matches_constructor_tiers() {
let b = ProviderPluginBackend::new("openai", "x", "gpt-4", vec![Tier::Fast, Tier::Complex]);
assert!(b.supports_tier(Tier::Fast));
assert!(b.supports_tier(Tier::Complex));
assert!(!b.supports_tier(Tier::Standard));
}
}