use oino_agent::QueueMode;
use oino_ai::{JsonValue, ThinkingLevel};
use oino_core::session::AgentSession;
use serde_json::json;
use crate::error::AcpError;
pub const MODEL: &str = "model";
pub const THINKING: &str = "thinking";
pub const STEERING_MODE: &str = "steering_mode";
pub const FOLLOW_UP_MODE: &str = "follow_up_mode";
pub const AUTO_COMPACTION: &str = "auto_compaction";
pub const AUTO_RETRY: &str = "auto_retry";
#[must_use]
pub fn model_value(provider: &str, id: &str) -> String {
format!("{provider}/{id}")
}
fn wire<T: serde::Serialize>(value: T) -> String {
serde_json::to_value(value)
.ok()
.and_then(|v| v.as_str().map(str::to_owned))
.unwrap_or_default()
}
fn queue_modes() -> JsonValue {
json!([
{"value": "one-at-a-time", "name": "One at a time"},
{"value": "all", "name": "All at once"},
])
}
#[must_use]
pub fn config_options(session: &AgentSession) -> Vec<JsonValue> {
let mut options = Vec::new();
let current = session.model();
let mut models: Vec<JsonValue> = session
.models()
.available()
.into_iter()
.map(|model| {
json!({
"value": model_value(&model.provider, &model.id),
"name": model.name,
"description": model.provider,
})
})
.collect();
if let Some(model) = ¤t {
let value = model_value(&model.provider, &model.id);
if !models.iter().any(|m| m["value"] == json!(value)) {
models.insert(
0,
json!({"value": value, "name": model.name, "description": model.provider}),
);
}
options.push(json!({
"id": MODEL, "name": "Model", "category": "model", "type": "select",
"currentValue": value, "options": models,
}));
}
let levels: Vec<JsonValue> = session
.available_thinking_levels()
.into_iter()
.map(|level| json!({"value": wire(level), "name": wire(level)}))
.collect();
options.push(json!({
"id": THINKING, "name": "Thinking", "category": "thought_level", "type": "select",
"currentValue": wire(session.thinking_level()), "options": levels,
}));
options.push(json!({
"id": STEERING_MODE, "name": "Steering queue", "type": "select",
"description": "How queued steering messages are delivered",
"currentValue": wire(session.steering_mode()), "options": queue_modes(),
}));
options.push(json!({
"id": FOLLOW_UP_MODE, "name": "Follow-up queue", "type": "select",
"description": "How queued follow-up messages are delivered",
"currentValue": wire(session.follow_up_mode()), "options": queue_modes(),
}));
let (compaction, retry) = session.with_settings(|s| {
(
s.settings().compaction_enabled(),
s.settings().retry_enabled(),
)
});
options.push(json!({
"id": AUTO_COMPACTION, "name": "Auto-compaction", "type": "boolean",
"currentValue": compaction,
}));
options.push(json!({
"id": AUTO_RETRY, "name": "Auto-retry", "type": "boolean", "currentValue": retry,
}));
options
}
fn parse<T: serde::de::DeserializeOwned>(id: &str, value: &JsonValue) -> Result<T, AcpError> {
serde_json::from_value(value.clone())
.map_err(|_| AcpError::InvalidParams(format!("Invalid value {value} for {id}")))
}
fn boolean(id: &str, value: &JsonValue) -> Result<bool, AcpError> {
value
.as_bool()
.ok_or_else(|| AcpError::InvalidParams(format!("{id} takes a boolean")))
}
pub async fn set_config_option(
session: &AgentSession,
id: &str,
value: &JsonValue,
) -> Result<(), AcpError> {
match id {
MODEL => {
let wanted = value.as_str().unwrap_or_default();
let model = session
.models()
.available()
.into_iter()
.find(|m| model_value(&m.provider, &m.id) == wanted)
.ok_or_else(|| AcpError::InvalidParams(format!("Model not found: {wanted}")))?;
session
.set_model(model, true)
.await
.map_err(|e| AcpError::Command(e.to_string()))?;
}
THINKING => {
let level: ThinkingLevel = parse(id, value)?;
session.set_thinking_level(level, true).await;
}
STEERING_MODE => session.set_steering_mode(parse::<QueueMode>(id, value)?),
FOLLOW_UP_MODE => session.set_follow_up_mode(parse::<QueueMode>(id, value)?),
AUTO_COMPACTION => session.set_auto_compaction_enabled(boolean(id, value)?),
AUTO_RETRY => session.set_auto_retry_enabled(boolean(id, value)?),
other => {
return Err(AcpError::InvalidParams(format!(
"Unknown config option: {other}"
)));
}
}
Ok(())
}
pub async fn set_mode(session: &AgentSession, mode_id: &str) -> Result<(), AcpError> {
let level: ThinkingLevel = parse("mode", &json!(mode_id))?;
session.set_thinking_level(level, true).await;
Ok(())
}