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}