How the Search Tool Provider is Implemented in Craft Agents OSS

The search tool provider in Craft Agents OSS uses a pluggable architecture with a generic WebSearchProvider interface, a factory function that creates the web_search AgentTool, and a resilient DuckDuckGo fallback implementation featuring multi-stage endpoints and exponential backoff retries.

The search tool provider implementation in the lukilabs/craft-agents-oss repository demonstrates a clean separation of concerns between interface contracts, tool creation, and concrete provider logic. This architecture allows developers to swap search backends without modifying agent code, while built-in fallback mechanisms ensure reliability when primary services fail.

The Provider Contract: WebSearchProvider Interface

Every search backend in the system implements the WebSearchProvider interface defined in /packages/pi-agent-server/src/tools/search/types.ts. This contract ensures uniform interaction across different search services.

The interface requires two properties:

  • name: A string identifier for the provider instance
  • search(): An async method accepting a query string and result count, returning a promise of WebSearchResult[]
export interface WebSearchProvider {
  name: string;
  search(query: string, count: number): Promise<WebSearchResult[]>;
}

This abstraction allows the agent system to treat Google, DuckDuckGo, OpenAI, or ChatGPT search providers identically at the tool level.

Factory Function: createSearchTool

The createSearchTool() function in /packages/pi-agent-server/src/tools/search/create-search-tool.ts serves as the bridge between raw search providers and the agent's tool ecosystem. It constructs an AgentTool named web_search that handles parameter validation, execution, and fallback logic.

The factory accepts:

  • A primary WebSearchProvider
  • An optional fallback provider (defaults to DDGSearchProvider)

The resulting tool's execute() method performs several operations:

  1. Validates input parameters (ensuring query is a non-empty string and count is a positive number)
  2. Invokes the primary provider's search() method
  3. Formats successful results using formatResults() to convert WebSearchResult[] into agent-readable text blocks
  4. Catches errors and attempts the fallback provider if configured
  5. Returns structured content that the agent can process
const tool = createSearchTool(googleProvider);
// tool.name === 'web_search'
// tool.execute() handles the full search lifecycle

Concrete Implementation: DDGSearchProvider

The DDGSearchProvider class in /packages/pi-agent-server/src/tools/search/providers/ddg.ts demonstrates a production-ready implementation of the WebSearchProvider contract. It serves as the default fallback when primary providers fail and implements a resilient multi-stage search strategy.

Multi-Endpoint Strategy

Rather than relying on a single API, the DuckDuckGo provider implements three distinct search strategies executed sequentially:

  1. Primary API: Uses the duck-duck-scrape library for structured JSON results
  2. HTML Endpoint: Scrapes html.duckduckgo.com when the API fails
  3. Lite Endpoint: Falls back to lite.duckduckgo.com for minimal HTML results

Each endpoint targets different DuckDuckGo interfaces to maximize availability when rate limits or service changes occur.

Retry and Error Handling

Every search stage wraps execution in a withRetry() helper that implements exponential backoff with jitter. The retry logic specifically targets transient failures:

  • Network timeouts
  • HTTP 429 (rate limit) responses
  • Temporary service unavailability

The retry configuration uses aggressive backoff parameters suitable for real-time agent interactions while preventing infinite loops.

If all three endpoints exhaust their retries, the provider aggregates all encountered errors and throws a comprehensive failure message, allowing the createSearchTool factory to activate the next fallback in the chain.

Usage Examples

Creating a search tool with Google as the primary provider and DuckDuckGo as the automatic fallback:

import { createSearchTool } from './create-search-tool';
import { GoogleSearchProvider } from './providers/google';

const googleProvider = new GoogleSearchProvider({
  apiKey: process.env.GOOGLE_API_KEY!
});

export const webSearchTool = createSearchTool(googleProvider);

Using the default DuckDuckGo provider directly:

import { createSearchTool } from './create-search-tool';
import { DDGSearchProvider } from './providers/ddg';

export const webSearchTool = createSearchTool(new DDGSearchProvider());

// Executing a search
const result = await webSearchTool.execute('toolCallId', {
  query: 'craft agents architecture',
  count: 3
});
console.log(result.content[0].text);

Testing a provider implementation in isolation:

import { DDGSearchProvider } from './providers/ddg';

const provider = new DDGSearchProvider();
const results = await provider.search('open source agents', 5);
console.log(results);

Summary

  • The search tool provider architecture separates interface contracts from implementation details using the WebSearchProvider interface in /packages/pi-agent-server/src/tools/search/types.ts.

  • The createSearchTool() factory in /packages/pi-agent-server/src/tools/search/create-search-tool.ts wraps providers into agent-ready tools with built-in fallback support and result formatting.

  • The DuckDuckGo provider (DDGSearchProvider) implements a resilient three-stage search strategy with automatic retry logic, serving as the default fallback when primary providers fail.

  • All providers adhere to a uniform contract, enabling seamless swapping of search backends without modifying agent logic.

Frequently Asked Questions

What is the WebSearchProvider interface?

The WebSearchProvider interface is a TypeScript contract defined in /packages/pi-agent-server/src/tools/search/types.ts that requires every search provider to implement a name property and a search(query, count) method returning Promise<WebSearchResult[]>. This abstraction allows the agent system to interact with Google, DuckDuckGo, or any custom search backend identically.

How does the search tool handle provider failures?

The createSearchTool() factory implements a fallback chain: if the primary provider throws an error during execution, the tool automatically attempts the fallback provider (defaulting to DDGSearchProvider). Each individual provider, particularly the DuckDuckGo implementation, also implements internal retry logic with exponential backoff to handle transient network errors before surfacing failures to the tool layer.

Can I use a custom search provider instead of DuckDuckGo?

Yes. Any class implementing the WebSearchProvider interface can be passed to createSearchTool(). The repository includes example implementations for Google and OpenAI providers. To use a custom provider, instantiate your implementation and pass it as the first argument to createSearchTool(), optionally including a second fallback provider.

What endpoints does the DuckDuckGo provider use?

The DDGSearchProvider in /packages/pi-agent-server/src/tools/search/providers/ddg.ts implements a three-tier fallback strategy: first the duck-duck-scrape library API, then the HTML endpoint at html.duckduckgo.com, and finally the Lite endpoint at lite.duckduckgo.com. Each stage is wrapped in retry logic with exponential backoff to maximize availability.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →