use std::collections::{HashMap, HashSet};
use std::env;
use std::fs;
use std::path::Path as StdPath;
use std::sync::{Arc, RwLock};
use std::time::{SystemTime, UNIX_EPOCH};
use axum::extract::{Path, State};
use axum::response::{IntoResponse, Response as AxumResponse};
use axum::Json;
use embacle::config::{CliRunnerType, RunnerConfig};
use embacle::discovery::resolve_binary;
use embacle::factory::create_runner_with_config;
use embacle::types::{ChatMessage, ChatRequest, LlmProvider, RunnerError};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use uuid::Uuid;
use super::error::{ErrorType, UhpFailure};
use super::files;
use super::harnesses;
use super::state::UhpState;
const RESERVED: [&str; 2] = ["tools", "include"];
#[derive(Debug, Clone, Deserialize)]
pub struct CreateResponse {
pub input: Value,
#[serde(default)]
pub model: Option<String>,
#[serde(default)]
pub metadata: Option<Map<String, Value>>,
#[serde(default)]
pub stream: bool,
#[serde(default)]
pub background: bool,
#[serde(default)]
pub previous_response_id: Option<String>,
#[serde(default)]
pub instructions: Option<String>,
#[serde(flatten)]
pub rest: Map<String, Value>,
}
impl CreateResponse {
pub fn prompt(&self) -> String {
match &self.input {
Value::String(s) => s.clone(),
Value::Array(items) => items
.iter()
.filter_map(|item| {
item.get("content").map(|c| match c {
Value::String(s) => s.clone(),
other => other
.as_array()
.map(|parts| {
parts
.iter()
.filter_map(|p| p.get("text").and_then(Value::as_str))
.collect::<Vec<_>>()
.join("")
})
.unwrap_or_default(),
})
})
.collect::<Vec<_>>()
.join("\n"),
other => other.as_str().unwrap_or_default().to_owned(),
}
}
pub fn ignored_fields(&self) -> Vec<String> {
RESERVED
.iter()
.filter(|f| self.rest.contains_key(**f))
.map(|f| (*f).to_owned())
.collect()
}
pub fn harness_id(&self) -> Option<&str> {
self.metadata
.as_ref()?
.get("harness_id")
.and_then(Value::as_str)
}
}
#[derive(Debug, Clone, Serialize)]
pub struct OutputItem {
#[serde(rename = "type")]
pub kind: &'static str,
pub id: String,
pub status: &'static str,
pub role: &'static str,
pub content: Vec<ContentPart>,
}
#[derive(Debug, Clone, Serialize)]
pub struct ContentPart {
#[serde(rename = "type")]
pub kind: &'static str,
pub text: String,
}
#[derive(Debug, Clone, Copy, Serialize)]
pub struct Usage {
pub input_tokens: u32,
pub output_tokens: u32,
pub total_tokens: u32,
}
#[derive(Debug, Clone, Serialize)]
pub struct Response {
pub id: String,
pub object: &'static str,
pub created_at: u64,
pub status: &'static str,
pub error: Option<super::error::UhpError>,
pub previous_response_id: Option<String>,
pub model: String,
pub output: Vec<OutputItem>,
pub store: bool,
pub usage: Option<Usage>,
pub metadata: Map<String, Value>,
}
#[derive(Debug, Default)]
pub struct ResponseStore {
responses: RwLock<HashMap<String, Response>>,
sessions: RwLock<HashMap<String, Vec<String>>>,
cancelled: RwLock<HashSet<String>>,
}
impl ResponseStore {
pub fn put(&self, session_id: &str, response: Response) {
if let Ok(mut sessions) = self.sessions.write() {
sessions
.entry(session_id.to_owned())
.or_default()
.push(response.id.clone());
}
if let Ok(mut responses) = self.responses.write() {
responses.insert(response.id.clone(), response);
}
}
pub fn get(&self, id: &str) -> Option<Response> {
self.responses.read().ok()?.get(id).cloned()
}
pub fn session_ids(&self) -> Vec<String> {
self.sessions
.read()
.map(|s| s.keys().cloned().collect())
.unwrap_or_default()
}
pub fn session_turns(&self, session_id: &str) -> Option<Vec<Response>> {
let ids = self.sessions.read().ok()?.get(session_id)?.clone();
let responses = self.responses.read().ok()?;
Some(
ids.iter()
.filter_map(|i| responses.get(i).cloned())
.collect(),
)
}
pub fn cancel(&self, id: &str) -> bool {
let running = self.get(id).is_some_and(|r| r.status == "in_progress");
if running {
if let Ok(mut cancelled) = self.cancelled.write() {
cancelled.insert(id.to_owned());
}
if let Ok(mut responses) = self.responses.write() {
if let Some(r) = responses.get_mut(id) {
r.status = "cancelled";
}
}
}
running
}
pub fn is_cancelled(&self, id: &str) -> bool {
self.cancelled.read().is_ok_and(|c| c.contains(id))
}
pub fn settle(&self, response: Response) {
if self.is_cancelled(&response.id) {
return;
}
if let Ok(mut responses) = self.responses.write() {
responses.insert(response.id.clone(), response);
}
}
pub fn running_in(&self, session_id: &str) -> Vec<String> {
self.session_turns(session_id)
.unwrap_or_default()
.into_iter()
.filter(|r| r.status == "in_progress")
.map(|r| r.id)
.collect()
}
pub fn delete_session(&self, session_id: &str) -> bool {
let ids = self
.sessions
.write()
.map_or(None, |mut sessions| sessions.remove(session_id));
let Some(ids) = ids else { return false };
if let Ok(mut responses) = self.responses.write() {
for id in &ids {
responses.remove(id);
}
}
true
}
pub fn session_of(&self, response_id: &str) -> Option<String> {
self.get(response_id)?
.metadata
.get("session_id")
.and_then(Value::as_str)
.map(str::to_owned)
}
}
fn now_secs() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or_default()
}
fn id_with(prefix: &str) -> String {
format!("{prefix}{}", Uuid::new_v4().simple())
}
pub async fn create(
State(state): State<UhpState>,
Json(request): Json<CreateResponse>,
) -> Result<AxumResponse, UhpFailure> {
if request.background {
return Ok(Json(start_background(state, request)).into_response());
}
if request.stream {
let prepared = prepare(&state, &request).await?;
let previous = request.previous_response_id.clone();
return Ok(super::streaming::sse(state, prepared, previous).into_response());
}
let outcome = run(&state, &request).await?;
Ok(Json(outcome.response).into_response())
}
fn start_background(state: UhpState, request: CreateResponse) -> Response {
let id = id_with("resp_");
let session_id = request
.previous_response_id
.as_ref()
.and_then(|prev| state.store.session_of(prev))
.unwrap_or_else(|| id_with("sess_"));
let mut metadata = request.metadata.clone().unwrap_or_default();
metadata.insert("session_id".to_owned(), Value::String(session_id.clone()));
let pending = Response {
id: id.clone(),
object: "response",
created_at: now_secs(),
status: "in_progress",
error: None,
previous_response_id: request.previous_response_id.clone(),
model: request.model.clone().unwrap_or_default(),
output: Vec::new(),
store: true,
usage: None,
metadata,
};
state.store.put(&session_id, pending.clone());
let spawned = pending.clone();
tokio::spawn(async move {
let finished = match run(&state, &request).await {
Ok(outcome) => {
let mut r = outcome.response;
r.id = id;
r
}
Err(failure) => {
let mut r = spawned;
r.status = "failed";
r.error = Some(failure.into_error());
r
}
};
state.store.settle(finished);
});
pending
}
pub async fn cancel(
State(state): State<UhpState>,
Path(id): Path<String>,
) -> Result<Json<Response>, UhpFailure> {
if state.store.get(&id).is_none() {
return Err(UhpFailure::not_found(
"response_not_found",
"no response with that id exists",
));
}
state.store.cancel(&id);
state.store.get(&id).map(Json).ok_or_else(|| {
UhpFailure::not_found("response_not_found", "no response with that id exists")
})
}
pub struct Outcome {
pub response: Response,
}
pub struct Prepared {
pub runner: Arc<dyn LlmProvider>,
pub chat: ChatRequest,
pub model: String,
pub session_id: String,
pub id: String,
pub metadata: Map<String, Value>,
pub requested_model: Option<String>,
}
async fn runner_in(
provider: CliRunnerType,
workdir: &StdPath,
) -> Result<Arc<dyn LlmProvider>, RunnerError> {
fs::create_dir_all(workdir)
.map_err(|e| RunnerError::internal(format!("session working folder: {e}")))?;
let env_override = env::var(provider.env_override_key()).ok();
let binary = resolve_binary(provider.binary_name(), env_override.as_deref())?;
let mut config = RunnerConfig::new(binary).with_working_directory(workdir.to_path_buf());
config.extra_args = write_permission_args(provider);
Ok(Arc::from(
create_runner_with_config(provider, config).await?,
))
}
fn write_permission_args(provider: CliRunnerType) -> Vec<String> {
match provider {
CliRunnerType::ClaudeCode => vec![
"--permission-mode".to_owned(),
"acceptEdits".to_owned(),
"--allowed-tools".to_owned(),
"Write Edit".to_owned(),
],
_ => Vec::new(),
}
}
pub async fn prepare(state: &UhpState, request: &CreateResponse) -> Result<Prepared, UhpFailure> {
let harnesses = harnesses::discover(&state.shared).await;
let chosen = match request.harness_id() {
Some(id) => harnesses
.iter()
.find(|d| d.harness.id == id)
.ok_or_else(|| {
UhpFailure::not_found("harness_not_found", "no harness with that id is configured")
})?,
None => harnesses.first().ok_or_else(|| {
UhpFailure::new(
ErrorType::InvalidRequestError,
"harness_not_found",
"this server has no installed harness to run",
)
})?,
};
let session_id = request
.previous_response_id
.as_ref()
.and_then(|prev| state.store.session_of(prev))
.unwrap_or_else(|| id_with("sess_"));
let workdir = files::session_workdir(&session_id);
let runner = runner_in(chosen.provider, &workdir).await.map_err(|_| {
UhpFailure::new(
ErrorType::HarnessError,
"harness_unavailable",
"the harness could not be started",
)
})?;
let requested_model = request.model.clone();
let model = requested_model
.clone()
.or_else(|| chosen.harness.default_model.clone())
.unwrap_or_else(|| runner.default_model().to_owned());
let mut chat = ChatRequest::new(vec![ChatMessage::user(request.prompt())]);
chat.model = Some(model.clone());
let mut metadata = request.metadata.clone().unwrap_or_default();
metadata.insert("session_id".to_owned(), Value::String(session_id.clone()));
let ignored = request.ignored_fields();
if !ignored.is_empty() {
metadata.insert(
"ignored_fields".to_owned(),
Value::Array(ignored.into_iter().map(Value::String).collect()),
);
}
Ok(Prepared {
runner,
chat,
model,
session_id,
id: id_with("resp_"),
metadata,
requested_model,
})
}
#[must_use]
pub fn pending_response(p: &Prepared, previous: Option<String>) -> Response {
Response {
id: p.id.clone(),
object: "response",
created_at: now_secs(),
status: "in_progress",
error: None,
previous_response_id: previous,
model: p.model.clone(),
output: Vec::new(),
store: true,
usage: None,
metadata: p.metadata.clone(),
}
}
#[must_use]
pub fn completed_response(
p: Prepared,
previous: Option<String>,
text: String,
usage: Option<Usage>,
ran_model: String,
) -> Response {
let mut metadata = p.metadata;
if p.requested_model.as_ref().is_some_and(|m| *m != ran_model) {
metadata.insert(
"requested_model".to_owned(),
Value::String(p.requested_model.unwrap_or_default()),
);
metadata.insert("model_fallback".to_owned(), Value::Bool(true));
}
Response {
id: p.id,
object: "response",
created_at: now_secs(),
status: "completed",
error: None,
previous_response_id: previous,
model: ran_model,
output: vec![OutputItem {
kind: "message",
id: id_with("msg_"),
status: "completed",
role: "assistant",
content: vec![ContentPart {
kind: "output_text",
text,
}],
}],
store: true,
usage,
metadata,
}
}
pub async fn run(state: &UhpState, request: &CreateResponse) -> Result<Outcome, UhpFailure> {
let prepared = prepare(state, request).await?;
let session_id = prepared.session_id.clone();
let previous = request.previous_response_id.clone();
let model = prepared.model.clone();
let response = match prepared.runner.complete(&prepared.chat).await {
Ok(reply) => {
let ran = if reply.model.is_empty() {
model
} else {
reply.model
};
let usage = reply.usage.map(|u| Usage {
input_tokens: u.prompt_tokens,
output_tokens: u.completion_tokens,
total_tokens: u.total_tokens,
});
completed_response(prepared, previous, reply.content, usage, ran)
}
Err(e) => {
let mut failed = pending_response(&prepared, previous);
failed.status = "failed";
failed.error = Some(super::error::UhpError {
kind: ErrorType::HarnessError,
code: "harness_error".to_owned(),
message: e.message,
detail: None,
});
failed
}
};
state.store.put(&session_id, response.clone());
Ok(Outcome { response })
}
pub async fn get(
State(state): State<UhpState>,
Path(id): Path<String>,
) -> Result<Json<Response>, UhpFailure> {
state.store.get(&id).map(Json).ok_or_else(|| {
UhpFailure::not_found("response_not_found", "no response with that id exists")
})
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn parse(body: Value) -> CreateResponse {
serde_json::from_value(body).expect("request parses") }
#[test]
fn a_bare_string_input_is_one_user_message() {
assert_eq!(parse(json!({"input": "hello"})).prompt(), "hello");
}
#[test]
fn an_item_array_input_is_flattened_to_its_text() {
let r = parse(json!({"input": [
{"role": "user", "content": [{"type": "input_text", "text": "first"}]},
{"role": "user", "content": "second"}
]}));
assert_eq!(r.prompt(), "first\nsecond");
}
#[test]
fn ignored_fields_reports_only_what_the_request_carried() {
let both = parse(json!({"input": "x", "tools": [], "include": []}));
assert_eq!(both.ignored_fields(), vec!["tools", "include"]);
let one = parse(json!({"input": "x", "include": []}));
assert_eq!(one.ignored_fields(), vec!["include"]);
let neither = parse(json!({"input": "x"}));
assert!(
neither.ignored_fields().is_empty(),
"a request that sent no reserved field must not be told one was ignored"
);
}
#[test]
fn a_reserved_field_does_not_make_the_request_unparseable() {
let r = parse(json!({
"input": "x",
"tools": [{"type": "function", "name": "probe"}],
"metadata": {"harness_id": "chrn_copilot"}
}));
assert_eq!(r.harness_id(), Some("chrn_copilot"));
assert_eq!(r.prompt(), "x");
}
#[test]
fn ids_carry_the_prefix_the_object_model_requires() {
assert!(id_with("resp_").starts_with("resp_"));
assert!(id_with("sess_").starts_with("sess_"));
}
#[test]
fn a_terminal_response_reads_back_unchanged() {
let store = ResponseStore::default();
let resp = Response {
id: "resp_abc".to_owned(),
object: "response",
created_at: 1,
status: "completed",
error: None,
previous_response_id: None,
model: "m".to_owned(),
output: Vec::new(),
store: true,
usage: None,
metadata: Map::new(),
};
store.put("sess_1", resp);
let got = store.get("resp_abc").expect("stored"); assert_eq!(got.status, "completed", "a terminal state must not change");
assert_eq!(store.session_turns("sess_1").map(|t| t.len()), Some(1));
assert!(store.get("resp_missing").is_none());
}
}