use serde_json::{json, Map, Value};
use crate::canonical::{CanonicalError, CanonicalRequest, ErrorKind, OutputFormat, Tool};
use crate::protocol::json::finish_body;
use crate::protocol::{ProviderCtx, WireRequest};
mod messages;
pub(super) const REQUEST_PATH: &str = "/api/chat";
pub(super) fn encode(
req: &CanonicalRequest,
ctx: &ProviderCtx,
) -> Result<WireRequest, CanonicalError> {
let mut body = Map::new();
body.insert("model".into(), json!(ctx.model));
body.insert("messages".into(), messages::messages_value(req)?);
if !req.tools.is_empty() {
body.insert("tools".into(), tools_value(&req.tools)?); }
let options = options_value(req);
if !options.is_empty() {
body.insert("options".into(), Value::Object(options)); }
if req.reasoning.is_some() {
body.insert("think".into(), json!(true));
}
body.insert("stream".into(), json!(req.stream.unwrap_or(false)));
match &req.output {
None => {}
Some(OutputFormat::Json) => {
body.insert("format".into(), json!("json"));
}
Some(OutputFormat::JsonSchema { schema, .. }) => {
body.insert("format".into(), schema.clone());
}
}
for (k, v) in &req.extra {
body.entry(k.clone()).or_insert_with(|| v.clone()); }
Ok(finish_body(body, format!("{}{REQUEST_PATH}", ctx.base_url)))
}
fn options_value(req: &CanonicalRequest) -> Map<String, Value> {
let mut options = Map::new();
if let Some(n) = req.max_tokens {
options.insert("num_predict".into(), json!(n)); }
if let Some(t) = req.temperature {
options.insert("temperature".into(), json!(t));
}
if let Some(p) = req.top_p {
options.insert("top_p".into(), json!(p));
}
if !req.stop.is_empty() {
options.insert("stop".into(), json!(req.stop));
}
options
}
fn tools_value(tools: &[Tool]) -> Result<Value, CanonicalError> {
let mut out = Vec::new();
for t in tools {
let Tool::Custom {
name,
description,
input_schema,
.. } = t
else {
return Err(CanonicalError {
kind: ErrorKind::ParseInput,
message: "provider-typed tools are not projected for this dialect".into(),
provider_detail: None,
retry_after_seconds: None,
});
};
let mut f = json!({"name": name, "parameters": input_schema});
if let Some(d) = description {
f["description"] = json!(d);
}
out.push(json!({"type": "function", "function": f}));
}
Ok(Value::Array(out))
}