use std::collections::BTreeMap;
use std::convert::Infallible;
use std::future::Future;
use tonic::codegen::async_trait;
use crate::agent::AgentToolRef;
use crate::catalog::Catalog;
use crate::error::{Error, Result};
use crate::proto::v1;
pub fn parse_subject_id(subject_id: &str) -> Option<(&str, &str)> {
let trimmed = subject_id.trim();
let (kind, id) = trimmed.split_once(':')?;
let kind = kind.trim();
let id = id.trim();
if kind.is_empty() || id.is_empty() {
return None;
}
Some((kind, id))
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Subject {
pub id: String,
pub credential_subject_id: String,
pub email: String,
pub display_name: String,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Credential {
pub mode: String,
pub subject_id: String,
pub connection: String,
pub instance: String,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Access {
pub policy: String,
pub role: String,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Host {
pub public_base_url: String,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Request {
pub token: String,
pub connection_params: BTreeMap<String, String>,
pub subject: Subject,
pub agent_subject: Subject,
pub credential: Credential,
pub access: Access,
pub host: Host,
pub idempotency_key: String,
pub workflow: serde_json::Map<String, serde_json::Value>,
pub tool_refs: Vec<AgentToolRef>,
pub tool_refs_set: bool,
}
tokio::task_local! {
static REQUEST_CONTEXT: Option<v1::RequestContext>;
}
impl Request {
pub fn connection_param(&self, name: &str) -> Option<&str> {
self.connection_params.get(name).map(String::as_str)
}
pub async fn app(&self) -> std::result::Result<crate::App, crate::AppError> {
crate::App::connect(self).await
}
pub async fn workflow(&self) -> std::result::Result<crate::Workflow, crate::WorkflowError> {
crate::Workflow::connect(self).await
}
pub async fn agent(&self) -> std::result::Result<crate::Agent, crate::AgentError> {
crate::Agent::connect(self).await
}
}
pub fn current_request_context() -> Option<v1::RequestContext> {
REQUEST_CONTEXT.try_with(Clone::clone).ok().flatten()
}
pub async fn with_request_context<F>(context: Option<v1::RequestContext>, future: F) -> F::Output
where
F: Future,
{
REQUEST_CONTEXT.scope(context, future).await
}
pub(crate) async fn scope_request_context<F>(
context: Option<v1::RequestContext>,
future: F,
) -> F::Output
where
F: Future,
{
with_request_context(context, future).await
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct HTTPSubjectRequest {
pub binding: String,
pub method: String,
pub path: String,
pub content_type: String,
pub headers: BTreeMap<String, Vec<String>>,
pub query: BTreeMap<String, Vec<String>>,
pub params: serde_json::Map<String, serde_json::Value>,
pub raw_body: Vec<u8>,
pub security_scheme: String,
pub verified_subject: String,
pub verified_claims: BTreeMap<String, String>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Response<T> {
pub status: Option<u16>,
pub headers: BTreeMap<String, Vec<String>>,
pub body: T,
}
impl<T> Response<T> {
pub fn new(status: u16, body: T) -> Self {
Self {
status: Some(status),
headers: BTreeMap::new(),
body,
}
}
pub fn with_header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
self.headers
.entry(name.into())
.or_default()
.push(value.into());
self
}
}
pub fn ok<T>(body: T) -> Response<T> {
Response::new(200, body)
}
pub trait IntoResponse<T> {
fn into_response(self) -> Response<T>;
}
impl<T> IntoResponse<T> for Response<T> {
fn into_response(self) -> Response<T> {
self
}
}
impl<T> IntoResponse<T> for T {
fn into_response(self) -> Response<T> {
ok(self)
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct RuntimeMetadata {
pub name: String,
pub display_name: String,
pub description: String,
pub version: String,
}
#[async_trait]
pub trait Provider: Send + Sync + 'static {
async fn configure(
&self,
_name: &str,
_config: serde_json::Map<String, serde_json::Value>,
) -> Result<()> {
Ok(())
}
fn metadata(&self) -> Option<RuntimeMetadata> {
None
}
fn warnings(&self) -> Vec<String> {
Vec::new()
}
async fn health_check(&self) -> Result<()> {
Ok(())
}
async fn start(&self) -> Result<()> {
Ok(())
}
fn supports_session_catalog(&self) -> bool {
false
}
async fn catalog_for_request(&self, _request: &Request) -> Result<Option<Catalog>> {
Ok(None)
}
async fn resolve_http_subject(
&self,
_request: HTTPSubjectRequest,
_context: &Request,
) -> Result<Option<Subject>> {
Ok(None)
}
async fn close(&self) -> Result<()> {
Ok(())
}
}
impl From<Infallible> for Error {
fn from(_value: Infallible) -> Self {
Error::internal("unreachable infallible error")
}
}