use serde::{Deserialize, Deserializer, Serialize, Serializer};
use super::{default_context_length, default_max_tokens, default_temperature};
#[derive(Clone)]
pub struct RedactedString(String);
impl RedactedString {
pub fn new(secret: impl Into<String>) -> Self {
Self(secret.into())
}
pub fn expose(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for RedactedString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[REDACTED]")
}
}
impl std::fmt::Debug for RedactedString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[REDACTED]")
}
}
impl PartialEq for RedactedString {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl Eq for RedactedString {}
impl PartialEq<str> for RedactedString {
fn eq(&self, other: &str) -> bool {
self.0 == other
}
}
impl Serialize for RedactedString {
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
self.0.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for RedactedString {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
Ok(RedactedString(s))
}
}
impl From<String> for RedactedString {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for RedactedString {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
pub const REDACTED_SECRET_MARKER: &str = "<redacted>";
pub fn redact_config_secrets(value: &mut serde_json::Value) {
match value {
serde_json::Value::Object(map) => {
for (key, val) in map.iter_mut() {
if key == "api_key" {
if val.is_string() {
*val = serde_json::Value::String(REDACTED_SECRET_MARKER.to_string());
}
continue;
}
if key == "env" {
if let serde_json::Value::Object(env_map) = val {
for env_val in env_map.values_mut() {
if env_val.is_string() {
*env_val =
serde_json::Value::String(REDACTED_SECRET_MARKER.to_string());
}
}
}
continue;
}
redact_config_secrets(val);
}
}
serde_json::Value::Array(items) => {
for item in items {
redact_config_secrets(item);
}
}
_ => {}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelProfile {
pub endpoint: String,
pub model: String,
pub api_key: Option<RedactedString>,
#[serde(default = "default_max_tokens")]
pub max_tokens: usize,
#[serde(default = "default_temperature")]
pub temperature: f32,
#[serde(default = "default_modalities")]
pub modalities: Vec<String>,
#[serde(default = "default_context_length")]
pub context_length: usize,
#[serde(default)]
pub extra_body: Option<serde_json::Map<String, serde_json::Value>>,
#[serde(default)]
pub native_function_calling: Option<bool>,
}
impl ModelProfile {
pub fn supports_vision(&self) -> bool {
self.modalities.iter().any(|m| m == "vision")
}
pub fn effective_native_function_calling(&self, default_native: bool) -> bool {
self.native_function_calling.unwrap_or(default_native)
}
}
pub fn default_modalities() -> Vec<String> {
vec!["text".to_string()]
}
#[cfg(test)]
#[path = "../../tests/unit/config/model/model_test.rs"]
mod tests;