use std::sync::Arc;
use axum::Json;
use axum::extract::{Path as AxumPath, State};
use axum::response::IntoResponse;
use serde::{Deserialize, Serialize};
use crate::client::proxy::{
FocusOutcome, FocusTarget, InjectOutcome, ManagedBackend, SessionProxy, SummarizeOutcome,
};
use crate::daemon::state::DaemonState;
mod backend;
pub use backend::DirectManagedBackend;
pub(crate) fn local_proxy(state: &Arc<DaemonState>) -> SessionProxy {
let backend: Arc<dyn ManagedBackend> = Arc::new(DirectManagedBackend::new(Arc::clone(state)));
SessionProxy::with_focus_store(backend, state.proxy_focus_store())
}
#[derive(Debug, Deserialize)]
pub struct ProxyFocusRequest {
pub conversation_key: String,
#[serde(default)]
pub session_id: String,
}
#[derive(Debug, Deserialize)]
pub struct ProxyUnfocusRequest {
pub conversation_key: String,
}
#[derive(Debug, Deserialize)]
pub struct ProxyMessageRequest {
pub conversation_key: String,
pub text: String,
}
#[derive(Debug, Serialize)]
pub struct ProxyTargetWire {
pub session_id: String,
pub name: String,
}
impl From<FocusTarget> for ProxyTargetWire {
fn from(f: FocusTarget) -> Self {
Self {
session_id: f.id,
name: f.name,
}
}
}
#[derive(Debug, Serialize)]
#[serde(tag = "outcome", rename_all = "snake_case")]
pub enum ProxyFocusResponse {
Focused(ProxyTargetWire),
Current {
target: Option<ProxyTargetWire>,
},
NotFound {
target: String,
error: String,
},
}
impl From<FocusOutcome> for ProxyFocusResponse {
fn from(o: FocusOutcome) -> Self {
match o {
FocusOutcome::Focused(t) => Self::Focused(t.into()),
FocusOutcome::Current(t) => Self::Current {
target: t.map(Into::into),
},
FocusOutcome::NotFound { target, error } => Self::NotFound { target, error },
}
}
}
#[derive(Debug, Serialize)]
pub struct ProxyUnfocusResponse {
pub cleared: Option<ProxyTargetWire>,
}
#[derive(Debug, Serialize)]
#[serde(tag = "outcome", rename_all = "snake_case")]
pub enum ProxyMessageResponse {
Sent {
target: ProxyTargetWire,
text: String,
},
AutoUnfocused {
target: ProxyTargetWire,
error: String,
},
Failed {
target: ProxyTargetWire,
error: String,
},
NoFocus,
}
impl From<InjectOutcome> for ProxyMessageResponse {
fn from(o: InjectOutcome) -> Self {
match o {
InjectOutcome::Sent { target, text } => Self::Sent {
target: target.into(),
text,
},
InjectOutcome::AutoUnfocused { target, error } => Self::AutoUnfocused {
target: target.into(),
error,
},
InjectOutcome::Failed { target, error } => Self::Failed {
target: target.into(),
error,
},
InjectOutcome::NoFocus => Self::NoFocus,
}
}
}
#[derive(Debug, Serialize)]
#[serde(tag = "outcome", rename_all = "snake_case")]
pub enum ProxySummaryResponse {
Summary {
target: ProxyTargetWire,
state: String,
summary: String,
pending_decision: Option<String>,
},
AutoUnfocused {
target: ProxyTargetWire,
error: String,
},
Failed {
target: ProxyTargetWire,
error: String,
},
NoFocus,
}
impl From<SummarizeOutcome> for ProxySummaryResponse {
fn from(o: SummarizeOutcome) -> Self {
match o {
SummarizeOutcome::Summary {
target,
state,
summary,
pending_decision,
} => Self::Summary {
target: target.into(),
state,
summary,
pending_decision,
},
SummarizeOutcome::AutoUnfocused { target, error } => Self::AutoUnfocused {
target: target.into(),
error,
},
SummarizeOutcome::Failed { target, error } => Self::Failed {
target: target.into(),
error,
},
SummarizeOutcome::NoFocus => Self::NoFocus,
}
}
}
pub async fn proxy_focus(
State(state): State<Arc<DaemonState>>,
Json(req): Json<ProxyFocusRequest>,
) -> impl IntoResponse {
let proxy = local_proxy(&state);
let outcome = proxy.focus(&req.conversation_key, &req.session_id).await;
Json(ProxyFocusResponse::from(outcome)).into_response()
}
pub async fn proxy_get_focus(
State(state): State<Arc<DaemonState>>,
AxumPath(conversation_key): AxumPath<String>,
) -> impl IntoResponse {
let proxy = local_proxy(&state);
let outcome = proxy.focus(&conversation_key, "").await;
Json(ProxyFocusResponse::from(outcome)).into_response()
}
pub async fn proxy_unfocus(
State(state): State<Arc<DaemonState>>,
Json(req): Json<ProxyUnfocusRequest>,
) -> impl IntoResponse {
let proxy = local_proxy(&state);
let cleared = proxy.unfocus(&req.conversation_key).map(Into::into);
Json(ProxyUnfocusResponse { cleared }).into_response()
}
pub async fn proxy_message(
State(state): State<Arc<DaemonState>>,
Json(req): Json<ProxyMessageRequest>,
) -> impl IntoResponse {
let proxy = local_proxy(&state);
let outcome = proxy.inject(&req.conversation_key, &req.text).await;
Json(ProxyMessageResponse::from(outcome)).into_response()
}
pub async fn proxy_summary(
State(state): State<Arc<DaemonState>>,
AxumPath(conversation_key): AxumPath<String>,
) -> impl IntoResponse {
let proxy = local_proxy(&state);
let outcome = proxy.summarize(&conversation_key).await;
Json(ProxySummaryResponse::from(outcome)).into_response()
}
pub fn proxy_router() -> axum::Router<Arc<DaemonState>> {
use axum::routing::{get, post};
axum::Router::new()
.route("/api/v1/sessions/proxy/focus", post(proxy_focus))
.route(
"/api/v1/sessions/proxy/focus/{conversation_key}",
get(proxy_get_focus),
)
.route("/api/v1/sessions/proxy/unfocus", post(proxy_unfocus))
.route("/api/v1/sessions/proxy/message", post(proxy_message))
.route(
"/api/v1/sessions/proxy/summary/{conversation_key}",
get(proxy_summary),
)
}
#[cfg(test)]
mod tests;