use serde::Deserialize;
use serde_json::{Map, Value};
use crate::canonical::{Content, ReasoningEffort};
use crate::store::Secret;
mod ingress;
mod row;
pub use ingress::{LossyMode, PartialIngress};
pub use row::PartialProvider;
use ingress::or_ingress;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, serde::Serialize)]
#[serde(rename_all = "lowercase")]
pub enum OutMode {
Text,
Ndjson,
Raw,
}
impl OutMode {
pub fn parse(s: &str) -> Option<OutMode> {
match s {
"text" => Some(OutMode::Text),
"ndjson" => Some(OutMode::Ndjson),
"raw" => Some(OutMode::Raw),
_ => None,
}
}
}
#[derive(Default, Clone, Debug, PartialEq)]
pub struct PartialConfig {
pub provider: Option<String>,
pub model: Option<String>,
pub base_url: Option<String>,
pub api_key: Option<Secret>,
pub output: Option<OutMode>,
pub raw_in: Option<bool>,
pub thinking: Option<bool>,
pub max_tokens: Option<u32>,
pub temperature: Option<f32>,
pub top_p: Option<f32>,
pub reasoning: Option<ReasoningEffort>,
pub stream: Option<bool>,
pub timeout: Option<u64>,
pub system: Option<Vec<Content>>,
pub providers: Vec<(String, PartialProvider)>,
pub ingress: Option<PartialIngress>,
pub extra: Map<String, Value>,
}
impl PartialConfig {
pub fn row(&self, name: &str) -> Option<&PartialProvider> {
self.providers
.iter()
.find(|(n, _)| n == name)
.map(|(_, row)| row)
}
pub fn or(self, other: PartialConfig) -> PartialConfig {
PartialConfig {
provider: self.provider.or(other.provider),
model: self.model.or(other.model),
base_url: self.base_url.or(other.base_url),
api_key: self.api_key.or(other.api_key),
output: self.output.or(other.output),
raw_in: self.raw_in.or(other.raw_in),
thinking: self.thinking.or(other.thinking),
max_tokens: self.max_tokens.or(other.max_tokens),
temperature: self.temperature.or(other.temperature),
top_p: self.top_p.or(other.top_p),
reasoning: self.reasoning.or(other.reasoning),
stream: self.stream.or(other.stream),
timeout: self.timeout.or(other.timeout),
system: self.system.or(other.system),
providers: merge_providers(self.providers, other.providers),
ingress: or_ingress(self.ingress, other.ingress),
extra: or_map(self.extra, other.extra),
}
}
}
fn merge_providers(
hi: Vec<(String, PartialProvider)>,
mut lo: Vec<(String, PartialProvider)>,
) -> Vec<(String, PartialProvider)> {
let mut out: Vec<(String, PartialProvider)> = hi
.into_iter()
.map(|(name, hi_row)| {
let merged = match take_row(&mut lo, &name) {
Some(lo_row) => hi_row.or(lo_row),
None => hi_row,
};
(name, merged)
})
.collect();
out.extend(lo);
out
}
fn take_row(rows: &mut Vec<(String, PartialProvider)>, name: &str) -> Option<PartialProvider> {
let at = rows.iter().position(|(n, _)| n == name)?;
Some(rows.remove(at).1)
}
pub(crate) fn or_map(mut hi: Map<String, Value>, lo: Map<String, Value>) -> Map<String, Value> {
for (key, value) in lo {
hi.entry(key).or_insert(value);
}
hi
}