use std::fmt;
use std::sync::Arc;
use std::time::Instant;
use axum::{
body::{Body, Bytes},
http::{HeaderMap, HeaderValue, Method, StatusCode, header::CONTENT_TYPE},
response::IntoResponse,
};
use crate::protocol::session::{ClientInfo, SessionInfo};
use crate::proxy::ProxyState;
use crate::proxy::forwarding::build_response;
use crate::proxy::sse::wrap_as_sse;
use crate::protocol::jsonrpc::JsonRpcEnvelope;
use crate::protocol::mcp::{ClientKind, ClientMethod, McpMessage};
use super::stubs::{OAuthKind, SessionId, TagSet, UrlMap};
#[derive(Debug)]
pub enum Request {
Mcp(McpRequest),
OAuth(OAuthRequest),
Raw(RawRequest),
}
#[derive(Debug)]
pub struct McpRequest {
pub transport: McpTransport,
pub envelope: JsonRpcEnvelope,
pub kind: ClientKind,
pub headers: HeaderMap,
pub session_hint: Option<SessionId>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum McpTransport {
StreamableHttpPost,
StreamableHttpGet,
SseLegacyGet,
}
#[derive(Debug)]
pub struct OAuthRequest {
pub kind: OAuthKind,
pub body: Bytes,
pub headers: HeaderMap,
}
#[derive(Debug)]
pub struct RawRequest {
pub method: Method,
pub path: String,
pub body: Body,
pub headers: HeaderMap,
}
#[derive(Debug)]
pub enum Response {
McpBuffered {
envelope: Envelope,
message: McpMessage,
status: StatusCode,
headers: HeaderMap,
},
McpStreamed {
envelope: Envelope,
body: Body,
status: StatusCode,
headers: HeaderMap,
},
OauthJson {
doc: serde_json::Value,
status: StatusCode,
headers: HeaderMap,
},
Raw {
body: Body,
status: StatusCode,
headers: HeaderMap,
},
Upstream502 { reason: String },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Envelope {
Json,
Sse,
}
impl IntoResponse for Response {
fn into_response(self) -> axum::response::Response {
match self {
Response::Raw {
body,
status,
headers,
} => build_response(status.as_u16(), &headers, body),
Response::McpStreamed {
body,
status,
headers,
..
} => build_response(status.as_u16(), &headers, body),
Response::McpBuffered {
envelope: env,
message,
status,
mut headers,
} => {
let json_bytes = message.envelope.to_bytes();
let (bytes, ct) = match env {
Envelope::Json => (json_bytes, "application/json"),
Envelope::Sse => (wrap_as_sse(&json_bytes), "text/event-stream"),
};
headers.insert(CONTENT_TYPE, HeaderValue::from_static(ct));
build_response(status.as_u16(), &headers, Body::from(bytes))
}
Response::OauthJson {
doc,
status,
mut headers,
} => {
let bytes = serde_json::to_vec(&doc).unwrap_or_default();
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
build_response(status.as_u16(), &headers, Body::from(bytes))
}
Response::Upstream502 { reason } => {
(StatusCode::BAD_GATEWAY, format!("Upstream error: {reason}")).into_response()
}
}
}
}
#[derive(Debug, Clone)]
pub enum Route {
McpStreamableHttp {
upstream: String,
method: ClientMethod,
buffer_policy: BufferPolicy,
},
McpSseLegacy {
upstream: String,
},
Oauth {
upstream: String,
rewrite: UrlMap,
},
Raw {
upstream: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BufferPolicy {
Streamed,
Buffered { max: usize },
}
#[derive(Debug)]
pub struct Context {
pub intake: Intake,
pub working: Working,
}
pub struct Intake {
pub start: Instant,
pub proxy: Arc<ProxyState>,
pub http_method: Method,
pub path: String,
pub request_size: usize,
}
impl fmt::Debug for Intake {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Intake")
.field("start", &self.start)
.field("proxy", &"Arc<ProxyState>")
.field("http_method", &self.http_method)
.field("path", &self.path)
.field("request_size", &self.request_size)
.finish()
}
}
#[derive(Debug, Default)]
pub struct Working {
pub session: Option<SessionInfo>,
pub client: Option<ClientInfo>,
pub request_method: Option<ClientMethod>,
pub request_tool: Option<String>,
pub request_resource_uri: Option<String>,
pub request_prompt_name: Option<String>,
pub response_size: Option<u64>,
pub upstream_us: Option<u64>,
pub tags: TagSet,
pub timings: Vec<StageTiming>,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct StageTiming {
pub name: &'static str,
pub elapsed_us: u64,
}