use std::collections::HashMap;
use perspective_js::utils::{ApiError, ApiResult};
use serde::Deserialize;
use wasm_bindgen::prelude::*;
use super::tools::Entitlement;
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct AgentConfigFields {
name: Option<String>,
url: Option<String>,
headers: Option<HashMap<String, String>>,
api_key: Option<String>,
model: Option<String>,
system_prompt: Option<String>,
system_role: Option<SystemRole>,
max_turns: Option<usize>,
entitlements: Option<Vec<String>>,
}
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum SystemRole {
#[default]
System,
User,
}
pub struct AgentConfig {
pub name: Option<String>,
pub url: Option<String>,
pub headers: Option<HashMap<String, String>>,
pub api_key: Option<String>,
pub model: Option<String>,
pub system_prompt: Option<String>,
pub system_role: SystemRole,
pub max_turns: Option<usize>,
pub entitlements: Vec<Entitlement>,
pub engine: Option<JsValue>,
pub docs: Option<JsValue>,
}
impl AgentConfig {
pub fn from_js(config: &JsValue) -> ApiResult<Self> {
let fields: AgentConfigFields = serde_wasm_bindgen::from_value(config.clone())?;
let engine = js_sys::Reflect::get(config, &JsValue::from_str("engine"))
.ok()
.filter(|x| !x.is_undefined() && !x.is_null());
let docs = js_sys::Reflect::get(config, &JsValue::from_str("docs"))
.ok()
.filter(|x| !x.is_undefined() && !x.is_null() && x.as_bool() != Some(false));
match (&fields.url, &engine) {
(Some(_), Some(_)) => {
return Err(ApiError::from("Pass either `url` or `engine`, not both"));
},
(None, None) => {
return Err(ApiError::from(
"Either `url` (a chat-completions endpoint, e.g. a spread provider preset) or \
`engine` (an in-page engine object) is required",
));
},
_ => (),
}
let entitlements = match fields.entitlements {
None => Entitlement::default_set(),
Some(names) => names
.iter()
.map(|x| x.parse())
.collect::<Result<Vec<_>, _>>()
.map_err(ApiError::from)?,
};
Ok(Self {
name: fields.name,
url: fields.url,
headers: fields.headers,
api_key: fields.api_key,
model: fields.model,
system_prompt: fields.system_prompt,
system_role: fields.system_role.unwrap_or_default(),
max_turns: fields.max_turns,
entitlements,
engine,
docs,
})
}
pub fn model_name(&self) -> &str {
self.model.as_deref().unwrap_or("default")
}
pub fn label_name(&self) -> String {
if let Some(name) = &self.name {
return name.clone();
}
self.url
.as_deref()
.and_then(|x| x.split("//").nth(1))
.and_then(|x| x.split('/').next())
.map(str::to_owned)
.unwrap_or_else(|| "engine".to_owned())
}
}