Skip to main content

everruns_integrations_duckduckgo/
lib.rs

1//! DuckDuckGo Instant Answer integration for Everruns.
2//!
3//! Instant answers via DuckDuckGo Instant Answer API.
4//! Provides `duckduckgo_instant_answer` tool for agents to get instant answers,
5//! abstracts, related topics, and definitions. This is an instant-answer lookup,
6//! not a full web/SERP search.
7//!
8//! This crate is part of the [Everruns](https://everruns.com) ecosystem.
9//!
10//! # Example
11//!
12//! ```
13//! use everruns_core::capabilities::Capability;
14//! use everruns_integrations_duckduckgo::DuckDuckGoCapability;
15//!
16//! let capability = DuckDuckGoCapability;
17//! assert_eq!(capability.id(), "duckduckgo");
18//! assert_eq!(capability.tools().len(), 1);
19//! ```
20//!
21//! Decision: External integration crate, auto-registered via inventory plugin system
22//! Decision: No API key required — DuckDuckGo Instant Answer API is free
23//! Decision: Stateless — no per-resource state management needed
24
25pub mod client;
26mod tools;
27
28use everruns_core::capabilities::{
29    Capability, CapabilityLocalization, CapabilityStatus, IntegrationPlugin,
30};
31use everruns_core::tools::Tool;
32
33use tools::DuckDuckGoSearchTool;
34
35// ============================================================================
36// Integration Plugin Registration
37// ============================================================================
38
39inventory::submit! {
40    IntegrationPlugin {
41        experimental_only: true,
42        feature_flag: None,
43        factory: || Box::new(DuckDuckGoCapability),
44    }
45}
46
47// ============================================================================
48// Constants
49// ============================================================================
50
51const DUCKDUCKGO_API_BASE: &str = "https://api.duckduckgo.com";
52
53// ============================================================================
54// DuckDuckGoCapability
55// ============================================================================
56
57pub struct DuckDuckGoCapability;
58
59impl Capability for DuckDuckGoCapability {
60    fn id(&self) -> &str {
61        "duckduckgo"
62    }
63
64    fn name(&self) -> &str {
65        "[Experimental] DuckDuckGo"
66    }
67
68    fn description(&self) -> &str {
69        "Look up instant answers using the DuckDuckGo Instant Answer API. \
70         Agents can get abstracts, definitions, related topics, and direct answers. \
71         This is an instant-answer lookup, not a full web/SERP search — no result does \
72         not mean no web pages exist. \
73         EXPERIMENTAL: This capability may change."
74    }
75
76    fn status(&self) -> CapabilityStatus {
77        CapabilityStatus::Available
78    }
79
80    fn icon(&self) -> Option<&str> {
81        Some("search")
82    }
83
84    fn category(&self) -> Option<&str> {
85        Some("Network")
86    }
87
88    fn system_prompt_addition(&self) -> Option<&str> {
89        // Behavioral note only: the tool description covers what
90        // `duckduckgo_instant_answer` does. The model needs to know it is an
91        // instant-answer API (curated facts/abstracts), not a general web
92        // search — and that an empty result does not mean no web pages exist.
93        Some(
94            "`duckduckgo_instant_answer` returns curated instant answers (facts, definitions, abstracts), not general web results. An empty result does not mean no matching web pages exist; prefer a web-search or web-fetch tool for web discovery, but this tool can serve as a lightweight fallback when none is available.",
95        )
96    }
97
98    fn tools(&self) -> Vec<Box<dyn Tool>> {
99        vec![Box::new(DuckDuckGoSearchTool)]
100    }
101
102    fn dependencies(&self) -> Vec<&'static str> {
103        vec![]
104    }
105
106    fn localizations(&self) -> Vec<CapabilityLocalization> {
107        vec![CapabilityLocalization::text(
108            "uk",
109            "[Експериментально] DuckDuckGo",
110            "Шукайте миттєві відповіді через DuckDuckGo Instant Answer API. Агенти можуть \
111             отримувати анотації, визначення, пов'язані теми та прямі відповіді. \
112             Це пошук миттєвих відповідей, а не повноцінний веб-пошук — відсутність \
113             результату не означає, що немає відповідних веб-сторінок. \
114             ЕКСПЕРИМЕНТАЛЬНО: ця можливість може змінитися.",
115        )]
116    }
117}
118
119// ============================================================================
120// Tests
121// ============================================================================
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126    use everruns_core::capabilities::CapabilityStatus;
127
128    #[test]
129    fn test_capability_metadata() {
130        let cap = DuckDuckGoCapability;
131        assert_eq!(cap.id(), "duckduckgo");
132        assert_eq!(cap.name(), "[Experimental] DuckDuckGo");
133        assert_eq!(cap.status(), CapabilityStatus::Available);
134        assert_eq!(cap.icon(), Some("search"));
135        assert_eq!(cap.category(), Some("Network"));
136    }
137
138    #[test]
139    fn test_capability_has_all_tools() {
140        let cap = DuckDuckGoCapability;
141        let tools = cap.tools();
142        assert_eq!(tools.len(), 1);
143
144        let names: Vec<&str> = tools.iter().map(|t| t.name()).collect();
145        assert!(names.contains(&"duckduckgo_instant_answer"));
146    }
147
148    #[test]
149    fn test_capability_has_system_prompt() {
150        let cap = DuckDuckGoCapability;
151        let prompt = cap.system_prompt_addition().unwrap();
152        // Tool name and behavioral distinction (instant answers, not full
153        // web search) are the only things the prompt needs to convey —
154        // the rest lives in the tool description.
155        assert!(prompt.contains("duckduckgo_instant_answer"));
156        assert!(prompt.contains("instant answers"));
157    }
158
159    #[test]
160    fn test_capability_no_dependencies() {
161        let cap = DuckDuckGoCapability;
162        assert!(cap.dependencies().is_empty());
163    }
164}