kernel/profiles/
configuration.rs1use std::collections::BTreeMap;
5
6use crate::records::{Capability, JsonValue, ModelRecord};
7
8pub fn normalized_param_values(record: &ModelRecord) -> BTreeMap<String, JsonValue> {
14 let mut kept = BTreeMap::new();
15 for (key, value) in &record.param_values {
16 if let Some(spec) = record.params.iter().find(|spec| &spec.key == key)
17 && let Some(normalized) = spec.normalized(value)
18 {
19 kept.insert(key.clone(), normalized);
20 }
21 }
22 kept
23}
24
25pub fn dropping_vanished_param_values(record: &ModelRecord) -> ModelRecord {
32 let mut record = record.clone();
33 record.param_values = normalized_param_values(&record);
34 record
35}
36
37pub fn merged(
44 record: &ModelRecord,
45 capability: &Capability,
46 payload: JsonValue,
47 fallback_prompt: Option<&str>,
48 session_prompt: Option<&str>,
49 appended_block: Option<&str>,
50) -> JsonValue {
51 let overrides = normalized_param_values(record);
52 let (prompt, block) = if *capability == Capability::chat() {
53 let prompt = match session_prompt {
54 Some(session) => trimmed(Some(session)),
55 None => trimmed(record.system_prompt.as_deref()).or_else(|| trimmed(fallback_prompt)),
56 };
57 (prompt, trimmed(appended_block))
58 } else {
59 (None, None)
60 };
61
62 if overrides.is_empty() && prompt.is_none() && block.is_none() {
63 return payload;
64 }
65
66 let mut fields = match payload {
67 JsonValue::Object(fields) => fields,
68 JsonValue::Null => BTreeMap::new(),
69 other => return other,
70 };
71
72 for (key, value) in overrides {
73 fields.entry(key).or_insert(value);
74 }
75
76 if (prompt.is_some() || block.is_some())
77 && let Some(messages) = fields.get_mut("messages")
78 && let JsonValue::Array(turns) = messages
79 {
80 *messages = seeded(prompt.as_deref(), block.as_deref(), std::mem::take(turns));
81 }
82
83 JsonValue::Object(fields)
84}
85
86fn trimmed(prompt: Option<&str>) -> Option<String> {
87 let cleaned = prompt?.trim();
88 (!cleaned.is_empty()).then(|| cleaned.to_owned())
89}
90
91fn seeded(prompt: Option<&str>, block: Option<&str>, mut turns: Vec<JsonValue>) -> JsonValue {
92 let system_index = turns.iter().position(is_system_turn);
93
94 if let Some(index) = system_index {
95 let Some(block) = block else {
96 return JsonValue::Array(turns);
97 };
98 if let JsonValue::Object(fields) = &mut turns[index] {
99 let existing = match fields.get("content") {
100 Some(JsonValue::String(content)) => Some(content.clone()),
101 _ => None,
102 };
103 if let Some(existing) = existing {
104 let joined = [existing.as_str(), block]
105 .into_iter()
106 .filter(|part| !part.is_empty())
107 .collect::<Vec<_>>()
108 .join("\n\n");
109 fields.insert("content".to_owned(), JsonValue::String(joined));
110 }
111 }
112 return JsonValue::Array(turns);
113 }
114
115 let content = [prompt, block]
116 .into_iter()
117 .flatten()
118 .collect::<Vec<_>>()
119 .join("\n\n");
120 if content.is_empty() {
121 return JsonValue::Array(turns);
122 }
123 let mut system = BTreeMap::new();
124 system.insert("role".to_owned(), JsonValue::String("system".to_owned()));
125 system.insert("content".to_owned(), JsonValue::String(content));
126 let mut updated = Vec::with_capacity(turns.len() + 1);
127 updated.push(JsonValue::Object(system));
128 updated.extend(turns);
129 JsonValue::Array(updated)
130}
131
132fn is_system_turn(turn: &JsonValue) -> bool {
133 matches!(turn, JsonValue::Object(fields)
134 if fields.get("role") == Some(&JsonValue::String("system".to_owned())))
135}