use crate::behaviors::app::{AppPageRequest, AppPageResponse};
use crate::behaviors::tool::ToolConnector;
use crate::behaviors::tool_adapter::ToolAdapter;
use crate::connector::{BaseConnector, ConnectorConfig, ConnectorRunner};
use crate::error::Result;
use crate::types::{ConnectorBehavior, PayloadEncoding, TaskTypeSchema};
use async_trait::async_trait;
use serde_json::Value;
use std::collections::HashMap;
use std::future::Future;
use std::sync::Arc;
#[async_trait]
pub trait SimpleConnector: Send + Sync + 'static {
fn name(&self) -> &str;
fn version(&self) -> &str;
async fn handle(&self, request: Value) -> Result<Value>;
async fn handle_capability(&self, request: Value, _capability_id: &str) -> Result<Value> {
self.handle(request).await
}
fn behavior(&self) -> ConnectorBehavior {
ConnectorBehavior::RequestResponse
}
fn encodings(&self) -> Vec<PayloadEncoding> {
vec![PayloadEncoding::Json]
}
fn metadata(&self) -> HashMap<String, String> {
HashMap::new()
}
fn capabilities(&self) -> Vec<TaskTypeSchema> {
Vec::new()
}
async fn run(self) -> Result<()>
where
Self: Sized,
{
self.run_with_config(ConnectorConfig::from_env()).await
}
async fn run_with_config(self, mut config: ConnectorConfig) -> Result<()>
where
Self: Sized,
{
config.connector_type = self.name().to_string();
config.version = self.version().to_string();
if config.instance_id.starts_with("unknown-") {
config.instance_id =
format!("{}-{}", self.name(), chrono::Utc::now().timestamp_millis());
}
crate::logger::init_logger();
let adapter = SimpleConnectorAdapter { inner: self };
let runner = ConnectorRunner::new(config, Arc::new(adapter));
let shutdown_handle = runner.shutdown_handle();
tokio::spawn(async move {
let _ = tokio::signal::ctrl_c().await;
tracing::info!("Received shutdown signal");
shutdown_handle.shutdown();
});
runner.run().await
}
}
struct SimpleConnectorAdapter<C: SimpleConnector> {
inner: C,
}
impl<C: SimpleConnector> BaseConnector for SimpleConnectorAdapter<C> {
fn connector_type(&self) -> &str {
self.inner.name()
}
fn version(&self) -> &str {
self.inner.version()
}
fn execute(
&self,
request: Value,
capability_id: Option<&str>,
) -> std::pin::Pin<Box<dyn Future<Output = Result<Value>> + Send + '_>> {
let cap_id = capability_id.map(|s| s.to_string());
Box::pin(async move {
if let Some(ref id) = cap_id {
self.inner.handle_capability(request, id).await
} else {
self.inner.handle(request).await
}
})
}
fn behavior(&self) -> ConnectorBehavior {
self.inner.behavior()
}
fn supported_encodings(&self) -> Vec<PayloadEncoding> {
self.inner.encodings()
}
fn metadata(&self) -> HashMap<String, String> {
self.inner.metadata()
}
fn capabilities(&self) -> Vec<TaskTypeSchema> {
self.inner.capabilities()
}
}
pub async fn serve_static(directory: &str, name: &str) -> Result<()> {
use crate::behaviors::serve::App;
App::static_files(directory).display_name(name).run().await
}
pub async fn serve_app<F, Fut>(name: &str, handler: F) -> Result<()>
where
F: Fn(AppPageRequest) -> Fut + Send + Sync + 'static,
Fut: Future<Output = AppPageResponse> + Send + 'static,
{
use crate::behaviors::serve::App;
App::builder()
.display_name(name)
.handler(handler)
.run()
.await
}
pub fn html(content: &str) -> AppPageResponse {
AppPageResponse::html(content)
}
pub fn json(content: &str) -> AppPageResponse {
AppPageResponse::json(content)
}
pub fn not_found() -> AppPageResponse {
AppPageResponse::not_found()
}
pub async fn run_connector<F, Fut>(name: &str, version: &str, handler: F) -> Result<()>
where
F: Fn(Value) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Value>> + Send + 'static,
{
struct FnConnector<F> {
name: String,
version: String,
handler: F,
}
#[async_trait]
impl<F, Fut> SimpleConnector for FnConnector<F>
where
F: Fn(Value) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Value>> + Send + 'static,
{
fn name(&self) -> &str {
&self.name
}
fn version(&self) -> &str {
&self.version
}
async fn handle(&self, request: Value) -> Result<Value> {
(self.handler)(request).await
}
}
let connector = FnConnector {
name: name.to_string(),
version: version.to_string(),
handler,
};
connector.run().await
}
pub async fn run_tool<T>(adapter: ToolAdapter<T>) -> Result<()>
where
T: ToolConnector + 'static,
{
run_tool_connector(Arc::new(adapter)).await
}
async fn run_tool_connector(connector: Arc<dyn BaseConnector>) -> Result<()> {
crate::logger::init_logger();
let mut config = ConnectorConfig::from_env();
config.connector_type = connector.connector_type().to_string();
config.version = connector.version().to_string();
if config.instance_id.starts_with("unknown-") {
config.instance_id = format!(
"{}-{}",
connector.connector_type(),
chrono::Utc::now().timestamp_millis()
);
}
let runner = ConnectorRunner::new(config, connector);
let shutdown_handle = runner.shutdown_handle();
tokio::spawn(async move {
let _ = tokio::signal::ctrl_c().await;
tracing::info!("Received shutdown signal");
shutdown_handle.shutdown();
});
runner.run().await
}
pub mod prelude {
pub use super::{
SimpleConnector, html, json, not_found, run_connector, run_tool, serve_app, serve_static,
};
pub use async_trait::async_trait;
pub use crate::behaviors::app::{AppPageRequest, AppPageResponse};
pub use crate::behaviors::tool::{
ParamType, ParameterSchema, ToolConnector, ToolParam, ToolResult, ToolSchema,
};
pub use crate::behaviors::tool_adapter::ToolAdapter;
pub use crate::error::Result;
pub use serde_json::{Value, json};
pub use crate::logger::init_logger;
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
struct TestConnector;
#[async_trait]
impl SimpleConnector for TestConnector {
fn name(&self) -> &str {
"test"
}
fn version(&self) -> &str {
"1.0.0"
}
async fn handle(&self, request: Value) -> Result<Value> {
Ok(json!({ "echo": request }))
}
}
#[tokio::test]
async fn test_simple_connector_handle() {
let connector = TestConnector;
let result = connector.handle(json!({"hello": "world"})).await.unwrap();
assert_eq!(result["echo"]["hello"], "world");
}
#[test]
fn test_simple_connector_metadata() {
let connector = TestConnector;
assert_eq!(connector.name(), "test");
assert_eq!(connector.version(), "1.0.0");
assert_eq!(connector.behavior(), ConnectorBehavior::RequestResponse);
}
#[test]
fn test_html_response() {
let response = html("<h1>Hello</h1>");
assert_eq!(response.content_type, "text/html");
}
#[test]
fn test_json_response() {
let response = json(r#"{"key": "value"}"#);
assert_eq!(response.content_type, "application/json");
}
#[test]
fn test_not_found_response() {
let response = not_found();
assert_eq!(response.status, 404);
}
}