use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use crate::error::ApiError;
use crate::types::{
AgentFailure, AgentOutcome, AgentRequest, AgentStep, CrawlRequest, CrawlSummary, Document,
ExtractRequest, InteractRequest, InteractResponse, InteractStopped, MapLink, MapRequest,
ScrapeOptions, SearchRequest, Upload,
};
use serde_json::Value;
pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
pub trait Engine: Send + Sync + 'static {
fn scrape<'a>(
&'a self,
url: &'a str,
opts: &'a ScrapeOptions,
) -> BoxFuture<'a, Result<Document, ApiError>>;
fn parse<'a>(
&'a self,
_file: Upload,
_opts: &'a ScrapeOptions,
) -> BoxFuture<'a, Result<Document, ApiError>> {
Box::pin(async {
Err(ApiError::unsupported(
"parse is not available in this build",
))
})
}
fn map<'a>(&'a self, _req: &'a MapRequest) -> BoxFuture<'a, Result<Vec<MapLink>, ApiError>> {
Box::pin(async { Err(ApiError::unsupported("map is not available in this build")) })
}
fn crawl<'a>(
&'a self,
_req: &'a CrawlRequest,
_sink: Arc<dyn CrawlSink>,
) -> BoxFuture<'a, Result<(), ApiError>> {
Box::pin(async {
Err(ApiError::unsupported(
"crawl is not available in this build",
))
})
}
fn search<'a>(&'a self, _req: &'a SearchRequest) -> BoxFuture<'a, Result<Value, ApiError>> {
Box::pin(async {
Err(ApiError::unsupported(
"search is not configured: set [search] in pagesift.toml",
))
})
}
fn extract<'a>(&'a self, _req: &'a ExtractRequest) -> BoxFuture<'a, Result<Value, ApiError>> {
Box::pin(async {
Err(ApiError::unsupported(
"extract needs an LLM: set [llm] in pagesift.toml",
))
})
}
fn agent_check(&self, _req: &AgentRequest) -> Result<(), ApiError> {
Err(ApiError::unsupported(
"agent needs an LLM: set [llm] in pagesift.toml",
))
}
fn agent<'a>(
&'a self,
_req: &'a AgentRequest,
_sink: Arc<dyn AgentSink>,
) -> BoxFuture<'a, AgentOutcome> {
Box::pin(async {
AgentOutcome {
error: Some(AgentFailure::from(&ApiError::unsupported(
"agent needs an LLM: set [llm] in pagesift.toml",
))),
..Default::default()
}
})
}
fn supported_formats(&self) -> &'static [&'static str] {
&["markdown", "html", "rawHtml", "links", "images"]
}
fn supports_actions(&self) -> bool {
false
}
fn actions_unavailable(&self) -> &'static str {
"actions need browser rendering, which is not available in this build"
}
fn supports_interact(&self) -> bool {
false
}
fn scrape_session<'a>(
&'a self,
_url: &'a str,
_opts: &'a ScrapeOptions,
_keep: bool,
_owner: Option<&'a str>,
) -> BoxFuture<'a, Result<Document, ApiError>> {
Box::pin(async {
Err(ApiError::unsupported(
"keepSession is not available in this build",
))
})
}
fn observe<'a>(
&'a self,
_url: &'a str,
_instruction: &'a str,
) -> BoxFuture<'a, Result<crate::types::Observed, ApiError>> {
Box::pin(async {
Err(ApiError::unsupported(
"observe is not available in this build",
))
})
}
fn find_similar<'a>(
&'a self,
_url: &'a str,
_selector: &'a str,
_opts: &'a ScrapeOptions,
) -> BoxFuture<'a, Result<crate::types::SimilarData, ApiError>> {
Box::pin(async {
Err(ApiError::unsupported(
"find_similar is not available in this build",
))
})
}
fn interact<'a>(
&'a self,
_id: &'a str,
_req: &'a InteractRequest,
_owner: Option<&'a str>,
) -> BoxFuture<'a, Result<InteractResponse, ApiError>> {
Box::pin(async {
Err(ApiError::unsupported(
"interact is not available in this build",
))
})
}
fn stop_interact<'a>(
&'a self,
_id: &'a str,
_owner: Option<&'a str>,
) -> BoxFuture<'a, Result<InteractStopped, ApiError>> {
Box::pin(async {
Err(ApiError::unsupported(
"interact is not available in this build",
))
})
}
fn admitted_concurrency(&self) -> Option<usize> {
None
}
}
pub trait AgentSink: Send + Sync {
fn step<'a>(&'a self, _step: &'a AgentStep, _credits: u64) -> BoxFuture<'a, ()> {
Box::pin(async {})
}
fn cancelled(&self) -> bool {
false
}
fn charge_page(&self) -> BoxFuture<'_, ()> {
Box::pin(async {})
}
fn pages_left(&self) -> Option<u64> {
None
}
}
impl AgentSink for () {}
pub trait CrawlSink: Send + Sync {
fn page(&self, doc: Document) -> BoxFuture<'_, ()>;
fn error<'a>(&'a self, url: &'a str, err: ApiError) -> BoxFuture<'a, ()>;
fn total(&self, total: usize) -> BoxFuture<'_, ()>;
fn cancelled(&self) -> BoxFuture<'_, bool>;
fn resume(&self) -> Option<CrawlResume> {
None
}
fn tracks_frontier(&self) -> bool {
false
}
fn enqueued(&self, _urls: Vec<(String, usize)>) -> BoxFuture<'_, ()> {
Box::pin(async {})
}
fn summary(&self, _summary: CrawlSummary) -> BoxFuture<'_, ()> {
Box::pin(async {})
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct CrawlResume {
pub enqueued: Vec<String>,
pub pending: Vec<(String, usize)>,
pub done: usize,
}