| 1 | //! Ed: an embeddable, local-LLM-first chat agent. |
| 2 | //! |
| 3 | //! Ed sits one layer above the [`onde`] inference SDK. `onde` gives you a |
| 4 | //! [`ChatEngine`](onde::inference::ChatEngine) you can load a model into and |
| 5 | //! send messages to; Ed adds the part every application built on it was |
| 6 | //! writing for itself: |
| 7 | //! |
| 8 | //! - **Lifecycle orchestration.** Loading a model is a sequence of state |
| 9 | //! changes, not one call. Ed drives `Loading -> Ready` (or `-> Error`) and |
| 10 | //! reports each transition. |
| 11 | //! - **Notification.** `onde` exposes status by polling |
| 12 | //! [`info()`](onde::inference::ChatEngine::info). A UI needs to be told. |
| 13 | //! Ed pushes transitions and replies to a [`StatusSink`] the host implements. |
| 14 | //! - **A flattened reply.** Inference returns `Result<InferenceResult, _>`; |
| 15 | //! a frontend wants one shape covering both outcomes. That's [`ChatReply`]. |
| 16 | //! |
| 17 | //! Ed deliberately does *not* own sessions, persistence, model catalogues, or |
| 18 | //! downloads. Those differ per application and belong to the host. For |
| 19 | //! persistence it defines the [`SessionStore`] seam the host implements. |
| 20 | //! |
| 21 | //! # Platforms |
| 22 | //! |
| 23 | //! Ed has no `cfg(target_os)` gates. `onde` already splits real and fallback |
| 24 | //! implementations internally, so this crate compiles on every platform `onde` |
| 25 | //! supports (macOS, iOS, tvOS, visionOS, watchOS, Windows, Linux, Android) and |
| 26 | //! simply surfaces `onde`'s error where it can't run inference. Outside that |
| 27 | //! list `onde` falls back to a stub whose signatures differ, so Ed does not |
| 28 | //! build there either. |
| 29 | //! |
| 30 | //! Do not add platform gates here, and be careful about adding them in a host: |
| 31 | //! a gate narrower than `onde`'s own support list silently disables chat on a |
| 32 | //! platform that would have worked. |
| 33 | //! |
| 34 | //! # Example |
| 35 | //! |
| 36 | //! ```no_run |
| 37 | //! use ed_agent::{Ed, GgufModelConfig}; |
| 38 | //! |
| 39 | //! # async fn example() { |
| 40 | //! let ed = Ed::new(); |
| 41 | //! let config = GgufModelConfig::qwen25_1_5b(); |
| 42 | //! ed.load(config, None, None).await.expect("load"); |
| 43 | //! |
| 44 | //! let reply = ed.send("Summarise this thread.").await; |
| 45 | //! println!("{}", reply.reply.unwrap_or_default()); |
| 46 | //! # } |
| 47 | //! ``` |
| 48 | |
| 49 | #![forbid(unsafe_code)] |
| 50 | |
| 51 | mod ed; |
| 52 | mod reply; |
| 53 | mod session; |
| 54 | mod sink; |
| 55 | mod stream; |
| 56 | mod tool; |
| 57 | |
| 58 | pub use ed::Ed; |
| 59 | pub use reply::ChatReply; |
| 60 | pub use session::{SessionError, SessionMeta, SessionStore}; |
| 61 | pub use sink::{EventSink, NoopSink, StatusSink}; |
| 62 | pub use stream::TextStream; |
| 63 | pub use tool::{ |
| 64 | AgentConfig, AgentError, AgentReply, AgentToolDefinition, ApprovalDecision, ApprovalHandler, |
| 65 | ApprovalRequest, DenyApprovals, RejectingExecutor, ToolCall, ToolExecutionResult, ToolExecutor, |
| 66 | ToolRisk, |
| 67 | }; |
| 68 | |
| 69 | // Re-exported so a host depends on `ed-agent` alone and never has to match a |
| 70 | // second `onde` version against this crate's. |
| 71 | pub use onde::inference::types::{ChatMessage, ChatRole}; |
| 72 | pub use onde::inference::{ |
| 73 | ChatEngine, EngineInfo, EngineStatus, GgufModelConfig, InferenceError, SamplingConfig, |
| 74 | ToolCallingSupport, UqffModelConfig, |
| 75 | }; |