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::rpc::managed::outcome::RouteOutcome;
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 {
focus_core(&state, req).await
}
pub(crate) async fn focus_core(state: &Arc<DaemonState>, req: ProxyFocusRequest) -> RouteOutcome {
let proxy = local_proxy(state);
let outcome = proxy.focus(&req.conversation_key, &req.session_id).await;
RouteOutcome::ok(&ProxyFocusResponse::from(outcome))
}
pub async fn proxy_get_focus(
State(state): State<Arc<DaemonState>>,
AxumPath(conversation_key): AxumPath<String>,
) -> impl IntoResponse {
get_focus_core(&state, &conversation_key).await }
pub(crate) async fn get_focus_core(
state: &Arc<DaemonState>,
conversation_key: &str,
) -> RouteOutcome {
let proxy = local_proxy(state);
let outcome = proxy.focus(conversation_key, "").await;
RouteOutcome::ok(&ProxyFocusResponse::from(outcome))
}
pub async fn proxy_unfocus(
State(state): State<Arc<DaemonState>>,
Json(req): Json<ProxyUnfocusRequest>,
) -> impl IntoResponse {
unfocus_core(&state, req).await }
pub(crate) async fn unfocus_core(
state: &Arc<DaemonState>,
req: ProxyUnfocusRequest,
) -> RouteOutcome {
let proxy = local_proxy(state);
let cleared: Option<ProxyTargetWire> = proxy.unfocus(&req.conversation_key).map(Into::into);
RouteOutcome::ok(&ProxyUnfocusResponse { cleared })
}
pub async fn proxy_message(
State(state): State<Arc<DaemonState>>,
Json(req): Json<ProxyMessageRequest>,
) -> impl IntoResponse {
message_core(&state, req).await }
pub(crate) async fn message_core(
state: &Arc<DaemonState>,
req: ProxyMessageRequest,
) -> RouteOutcome {
let proxy = local_proxy(state);
let outcome = proxy.inject(&req.conversation_key, &req.text).await;
RouteOutcome::ok(&ProxyMessageResponse::from(outcome))
}
pub async fn proxy_summary(
State(state): State<Arc<DaemonState>>,
AxumPath(conversation_key): AxumPath<String>,
) -> impl IntoResponse {
summary_core(&state, &conversation_key).await }
pub(crate) async fn summary_core(state: &Arc<DaemonState>, conversation_key: &str) -> RouteOutcome {
let proxy = local_proxy(state);
let outcome = proxy.summarize(conversation_key).await;
RouteOutcome::ok(&ProxySummaryResponse::from(outcome))
}
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;