Skip to main content

anyllm_cloudflare_worker/
lib.rs

1//! Cloudflare Workers AI provider for anyllm.
2//!
3//! This crate implements [`anyllm::ChatProvider`] using the `worker::Ai` binding,
4//! enabling AI model calls directly from Cloudflare Workers without HTTP overhead.
5//! Construction is runtime-bound: unlike the HTTP providers, this adapter is not
6//! loaded from local environment variables and instead requires a live Worker
7//! `Ai` binding.
8//!
9//! # Usage
10//!
11//! ```rust,ignore
12//! use anyllm::prelude::*;
13//! use anyllm_cloudflare_worker::Provider;
14//!
15//! async fn handler(env: &worker::Env) -> Result<String> {
16//!     let ai = env.ai("AI").map_err(|e| anyllm::Error::Provider {
17//!         status: None,
18//!         message: e.to_string(),
19//!         body: None,
20//!         request_id: None,
21//!     })?;
22//!
23//!     let provider = Provider::new(ai);
24//!
25//!     let response = provider.chat(
26//!         &ChatRequest::new("@cf/meta/llama-3.1-8b-instruct")
27//!             .message(Message::user("What is Rust?"))
28//!     ).await?;
29//!
30//!     Ok(response.text_or_empty())
31//! }
32//! ```
33//!
34//! # Streaming
35//!
36//! Streaming uses `Ai::run_bytes()` with `stream: true`, parsing SSE events
37//! from the raw byte stream:
38//!
39//! ```rust,ignore
40//! use anyllm::prelude::*;
41//! use anyllm_cloudflare_worker::Provider;
42//! use futures_util::StreamExt;
43//!
44//! async fn stream_handler(env: &worker::Env) -> Result<()> {
45//!     let ai = env.ai("AI").map_err(|e| anyllm::Error::Provider {
46//!         status: None,
47//!         message: e.to_string(),
48//!         body: None,
49//!         request_id: None,
50//!     })?;
51//!
52//!     let provider = Provider::new(ai);
53//!
54//!     let mut stream = provider.chat_stream(
55//!         &ChatRequest::new("@cf/meta/llama-3.1-8b-instruct")
56//!             .message(Message::user("Tell me a story"))
57//!     ).await?;
58//!
59//!     while let Some(event) = stream.next().await {
60//!         let event = event?;
61//!         if let StreamEvent::TextDelta { text, .. } = &event {
62//!             // Process streaming text
63//!         }
64//!     }
65//!     Ok(())
66//! }
67//! ```
68
69mod chat;
70#[cfg(test)]
71mod conformance_tests;
72mod embedding;
73mod error;
74mod streaming;
75mod wire;
76
77use std::sync::Arc;
78
79use anyllm::{CapabilitySupport, ChatCapability, ChatCapabilityResolver};
80
81/// Cloudflare Workers AI provider implementing `anyllm::ChatProvider`.
82///
83/// Wraps a `worker::Ai` binding to call AI models directly from within
84/// a Cloudflare Worker. No HTTP overhead: calls go through the Workers
85/// runtime's internal AI binding.
86///
87/// # Construction
88///
89/// Obtain the `worker::Ai` binding from the Worker's `Env`:
90///
91/// ```rust,ignore
92/// let ai = env.ai("AI")?;
93/// let provider = Provider::new(ai);
94/// ```
95///
96/// The `"AI"` string must match the binding name in `wrangler.toml`:
97///
98/// ```toml
99/// [ai]
100/// binding = "AI"
101/// ```
102pub struct Provider {
103    pub(crate) ai: worker::Ai,
104    pub(crate) chat_capability_resolver: Option<Arc<dyn ChatCapabilityResolver>>,
105}
106
107impl Provider {
108    /// Create a new provider from a `worker::Ai` binding.
109    pub fn new(ai: worker::Ai) -> Self {
110        Self {
111            ai,
112            chat_capability_resolver: None,
113        }
114    }
115
116    fn builtin_chat_capability(
117        &self,
118        _model: &str,
119        capability: ChatCapability,
120    ) -> CapabilitySupport {
121        match capability {
122            ChatCapability::Streaming
123            | ChatCapability::NativeStreaming
124            | ChatCapability::ToolCalls
125            | ChatCapability::StructuredOutput => CapabilitySupport::Supported,
126            ChatCapability::ParallelToolCalls
127            | ChatCapability::ImageInput
128            | ChatCapability::ImageDetail
129            | ChatCapability::ImageOutput
130            | ChatCapability::ImageReplay
131            | ChatCapability::ReasoningOutput
132            | ChatCapability::ReasoningReplay
133            | ChatCapability::ReasoningConfig => CapabilitySupport::Unsupported,
134            _ => CapabilitySupport::Unknown,
135        }
136    }
137
138    /// Install a resolver consulted before the provider's built-in capability logic.
139    #[must_use]
140    pub fn with_chat_capabilities(mut self, resolver: impl ChatCapabilityResolver) -> Self {
141        self.chat_capability_resolver = Some(Arc::new(resolver));
142        self
143    }
144}