use aonyx_core::{Message, Role};
use axum::extract::State;
use axum::Json;
use serde::Deserialize;
use serde_json::{json, Value};
use crate::agent::last_assistant_text;
use crate::error::{ApiError, ApiResult};
use crate::state::ApiState;
#[derive(Debug, Deserialize)]
pub struct ChatRequest {
#[serde(default)]
model: Option<String>,
messages: Vec<InMessage>,
}
#[derive(Debug, Deserialize)]
struct InMessage {
role: String,
#[serde(default)]
content: String,
}
fn role_from_str(role: &str) -> Role {
match role {
"system" => Role::System,
"assistant" => Role::Assistant,
"tool" => Role::Tool,
_ => Role::User,
}
}
pub async fn chat_completions(
State(state): State<ApiState>,
Json(req): Json<ChatRequest>,
) -> ApiResult<Json<Value>> {
if req.messages.is_empty() {
return Err(ApiError::BadRequest("`messages` must not be empty".into()));
}
let model = req.model.unwrap_or_else(|| state.info.model.clone());
let history: Vec<Message> = req
.messages
.into_iter()
.map(|m| Message::new(role_from_str(&m.role), m.content))
.collect();
let log = state.agent.run_turn(history).await?;
let reply = last_assistant_text(&log);
Ok(Json(json!({
"id": "chatcmpl-aonyx",
"object": "chat.completion",
"created": 0,
"model": model,
"choices": [{
"index": 0,
"message": { "role": "assistant", "content": reply },
"finish_reason": "stop"
}],
"usage": { "prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0 }
})))
}
pub async fn models(State(state): State<ApiState>) -> Json<Value> {
Json(json!({
"object": "list",
"data": [{ "id": state.info.model, "object": "model", "owned_by": "aonyx" }]
}))
}