41 KiB
WP Agentic Writer: Brave Search Integration Implementation Plan
Date: January 29, 2026
Status: Planning Phase
Integration Type: Seamless addition to existing plugin architecture
Executive Summary
This document outlines the complete implementation plan for integrating Brave Search API into WP Agentic Writer as an alternative web search provider alongside the existing OpenRouter :online models. The integration follows the plugin's existing architecture patterns and provides users with flexible, cost-effective web research capabilities.
Key Design Principle: Brave Search API is positioned as an alternative provider to OpenRouter's online models, giving users choice based on cost, performance, and feature requirements.
Table of Contents
- Architecture Overview
- Settings Integration
- Database Schema
- Provider Architecture
- REST API Endpoints
- Frontend Integration
- Agent Integration
- Cost Tracking Integration
- Implementation Phases
- File Structure
- Testing Strategy
Architecture Overview
Current Plugin Structure
wp-agentic-writer/
├── includes/
│ ├── class-openrouter-provider.php ← Existing AI provider
│ ├── class-gutenberg-sidebar.php ← Main REST API handler
│ ├── class-settings.php ← Settings management
│ ├── class-cost-tracker.php ← Cost tracking system
│ └── class-markdown-parser.php ← Content parsing
├── assets/
│ └── js/
│ └── sidebar.js ← Frontend React app
└── views/
└── settings/
└── tab-models.php ← Model settings UI
Integration Points
┌─────────────────────────────────────────────────────────────────┐
│ SETTINGS PANEL │
│ │
│ API Configuration │
│ ├── OpenRouter API Key [sk-or-v1-...] │
│ └── Brave Search API Key [BSA...] ← NEW │
│ │
│ Web Search Provider │
│ ├── ○ OpenRouter :online models (perplexity, etc) │
│ └── ○ Brave Search API (independent index) ← NEW │
│ │
│ [Only show if Brave API key is set] │
└─────────────────────────────────────────────────────────────────┘
Settings Integration
1. Settings Schema Extension
File: includes/class-settings.php
Add Brave Search settings to existing settings array:
// In sanitize_settings() method - line ~320
$sanitized['openrouter_api_key'] = trim( $input['openrouter_api_key'] ?? '' );
// ADD NEW:
$sanitized['brave_api_key'] = trim( $input['brave_api_key'] ?? '' );
$sanitized['brave_api_tier'] = sanitize_text_field( $input['brave_api_tier'] ?? 'base_ai' );
$sanitized['web_search_provider'] = sanitize_text_field( $input['web_search_provider'] ?? 'openrouter' );
$sanitized['brave_search_enabled'] = isset( $input['brave_search_enabled'] ) ? 1 : 0;
$sanitized['brave_cache_enabled'] = isset( $input['brave_cache_enabled'] ) ? 1 : 0;
$sanitized['brave_cache_duration_days'] = absint( $input['brave_cache_duration_days'] ?? 30 );
$sanitized['brave_monthly_budget'] = floatval( $input['brave_monthly_budget'] ?? 50.00 );
$sanitized['brave_include_citations'] = isset( $input['brave_include_citations'] ) ? 1 : 0;
2. Settings UI - API Keys Section
File: views/settings/tab-models.php
Add Brave API key field after OpenRouter API key (around line 480):
<!-- Existing OpenRouter API Key field -->
<div class="wpaw-field-row">
<div class="wpaw-field-label">
<?php esc_html_e( 'OpenRouter API Key', 'wp-agentic-writer' ); ?>
<small><?php esc_html_e( 'Required for all AI features', 'wp-agentic-writer' ); ?></small>
</div>
<div class="wpaw-field-input">
<input type="password" id="openrouter_api_key" name="wp_agentic_writer_settings[openrouter_api_key]" value="<?php echo esc_attr( $api_key ); ?>" />
</div>
</div>
<!-- NEW: Brave Search API Key -->
<div class="wpaw-field-row">
<div class="wpaw-field-label">
<?php esc_html_e( 'Brave Search API Key', 'wp-agentic-writer' ); ?>
<small><?php esc_html_e( 'Optional - for web research & citations', 'wp-agentic-writer' ); ?></small>
</div>
<div class="wpaw-field-input">
<input type="password" id="brave_api_key" name="wp_agentic_writer_settings[brave_api_key]" value="<?php echo esc_attr( $brave_api_key ); ?>" placeholder="BSA..." />
<p class="description">
<?php printf(
wp_kses_post( __( 'Get your API key from <a href="%s" target="_blank">Brave Search API</a>. Free tier: 2,000 queries/month.', 'wp-agentic-writer' ) ),
'https://brave.com/search/api/'
); ?>
</p>
</div>
</div>
<!-- NEW: Brave API Tier Selection -->
<div class="wpaw-field-row" id="brave-tier-row" style="display: <?php echo !empty($brave_api_key) ? 'flex' : 'none'; ?>;">
<div class="wpaw-field-label">
<?php esc_html_e( 'Brave API Tier', 'wp-agentic-writer' ); ?>
</div>
<div class="wpaw-field-input">
<select name="wp_agentic_writer_settings[brave_api_tier]">
<option value="free" <?php selected( $brave_api_tier, 'free' ); ?>>Free (2,000/month)</option>
<option value="base_ai" <?php selected( $brave_api_tier, 'base_ai' ); ?>>Data for AI - Base ($5/1K)</option>
<option value="pro_ai" <?php selected( $brave_api_tier, 'pro_ai' ); ?>>Data for AI - Pro ($9/1K)</option>
</select>
</div>
</div>
3. Settings UI - Web Search Provider Selection
Add new section after model presets:
<!-- NEW SECTION: Web Search Configuration -->
<div class="wpaw-section" id="brave-search-section" style="display: <?php echo !empty($brave_api_key) ? 'block' : 'none'; ?>;">
<div class="wpaw-section-header">
<h3><?php esc_html_e( 'Web Search Provider', 'wp-agentic-writer' ); ?></h3>
<p class="description"><?php esc_html_e( 'Choose how the agent performs web research during article generation.', 'wp-agentic-writer' ); ?></p>
</div>
<div class="wpaw-section-body">
<!-- Provider Selection -->
<div class="wpaw-field-row">
<div class="wpaw-field-label">
<?php esc_html_e( 'Search Provider', 'wp-agentic-writer' ); ?>
</div>
<div class="wpaw-field-input">
<label style="display: block; margin-bottom: 10px;">
<input type="radio" name="wp_agentic_writer_settings[web_search_provider]" value="openrouter" <?php checked( $web_search_provider, 'openrouter' ); ?> />
<strong>OpenRouter :online Models</strong>
<p class="description" style="margin-left: 24px;">
Uses models like Perplexity Sonar with built-in web search. Higher cost (~$5-15 per 1M tokens), but includes AI reasoning.
</p>
</label>
<label style="display: block;">
<input type="radio" name="wp_agentic_writer_settings[web_search_provider]" value="brave" <?php checked( $web_search_provider, 'brave' ); ?> />
<strong>Brave Search API</strong>
<p class="description" style="margin-left: 24px;">
Independent search index with 30B+ pages. Lower cost ($5-9 per 1K searches), includes caching. Agent processes results separately.
</p>
</label>
</div>
</div>
<!-- Brave-specific settings (show only when Brave is selected) -->
<div id="brave-specific-settings" style="display: <?php echo $web_search_provider === 'brave' ? 'block' : 'none'; ?>; margin-top: 20px; padding-left: 20px; border-left: 3px solid #2271b1;">
<div class="wpaw-field-row">
<div class="wpaw-field-label">
<?php esc_html_e( 'Enable Search Caching', 'wp-agentic-writer' ); ?>
</div>
<div class="wpaw-field-input">
<label>
<input type="checkbox" name="wp_agentic_writer_settings[brave_cache_enabled]" value="1" <?php checked( $brave_cache_enabled, 1 ); ?> />
<?php esc_html_e( 'Cache search results to reduce API calls (recommended)', 'wp-agentic-writer' ); ?>
</label>
<p class="description">Reuses search results for identical queries. Can save 40-60% on costs.</p>
</div>
</div>
<div class="wpaw-field-row">
<div class="wpaw-field-label">
<?php esc_html_e( 'Cache Duration', 'wp-agentic-writer' ); ?>
</div>
<div class="wpaw-field-input">
<input type="number" name="wp_agentic_writer_settings[brave_cache_duration_days]" value="<?php echo esc_attr( $brave_cache_duration_days ); ?>" min="1" max="90" /> days
<p class="description">How long to keep cached search results (1-90 days).</p>
</div>
</div>
<div class="wpaw-field-row">
<div class="wpaw-field-label">
<?php esc_html_e( 'Monthly Budget Limit', 'wp-agentic-writer' ); ?>
</div>
<div class="wpaw-field-input">
$<input type="number" name="wp_agentic_writer_settings[brave_monthly_budget]" value="<?php echo esc_attr( $brave_monthly_budget ); ?>" min="0" step="0.01" style="width: 100px;" />
<p class="description">Stop searches when monthly cost exceeds this amount.</p>
</div>
</div>
<div class="wpaw-field-row">
<div class="wpaw-field-label">
<?php esc_html_e( 'Include Citations', 'wp-agentic-writer' ); ?>
</div>
<div class="wpaw-field-input">
<label>
<input type="checkbox" name="wp_agentic_writer_settings[brave_include_citations]" value="1" <?php checked( $brave_include_citations, 1 ); ?> />
<?php esc_html_e( 'Add numbered citations [1], [2]... and References section', 'wp-agentic-writer' ); ?>
</label>
</div>
</div>
</div>
</div>
</div>
<script>
jQuery(document).ready(function($) {
// Show/hide Brave sections based on API key
$('#brave_api_key').on('input', function() {
const hasKey = $(this).val().trim().length > 0;
$('#brave-tier-row, #brave-search-section').toggle(hasKey);
});
// Show/hide Brave-specific settings based on provider selection
$('input[name="wp_agentic_writer_settings[web_search_provider]"]').on('change', function() {
$('#brave-specific-settings').toggle($(this).val() === 'brave');
});
});
</script>
Database Schema
New Tables
Create three new tables for Brave Search integration. Add to plugin activation hook.
File: wp-agentic-writer.php (activation hook)
function wp_agentic_writer_activate() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
// Existing tables...
// NEW: Brave Search tables
$sql_searches = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wpaw_searches (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
post_id BIGINT NOT NULL,
search_query VARCHAR(500) NOT NULL,
search_number INT,
total_searches_for_post INT,
results_count INT,
results_json LONGTEXT,
top_result_title VARCHAR(255),
top_result_url VARCHAR(500),
cost DECIMAL(10, 4),
api_tier VARCHAR(50),
cache_enabled TINYINT DEFAULT 1,
cache_expires_at TIMESTAMP NULL,
cache_hit TINYINT DEFAULT 0,
search_category VARCHAR(100),
status VARCHAR(30) DEFAULT 'completed',
error_message TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
KEY idx_post (post_id),
KEY idx_query (search_query(255)),
KEY idx_cache_expires (cache_expires_at),
KEY idx_status (status)
) $charset_collate;";
$sql_citations = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wpaw_citations (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
post_id BIGINT NOT NULL,
citation_number INT NOT NULL,
citation_text VARCHAR(500),
context_excerpt TEXT,
search_id BIGINT,
source_url VARCHAR(500) NOT NULL,
source_title VARCHAR(255),
source_domain VARCHAR(100),
source_type VARCHAR(50),
result_position INT,
article_section VARCHAR(100),
added_by VARCHAR(50),
verified TINYINT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
KEY idx_post (post_id),
KEY idx_citation_number (post_id, citation_number),
KEY idx_source_domain (source_domain)
) $charset_collate;";
$sql_cache = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wpaw_search_cache (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
search_query_normalized VARCHAR(500) NOT NULL,
search_category VARCHAR(100),
cache_key VARCHAR(64),
results_json LONGTEXT,
result_count INT,
cached_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP,
hit_count INT DEFAULT 0,
cost_saved DECIMAL(10, 4) DEFAULT 0,
quality_score DECIMAL(3,2),
UNIQUE KEY unique_query_category (search_query_normalized(255), search_category(100)),
KEY idx_expires (expires_at),
KEY idx_hit_count (hit_count)
) $charset_collate;";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $sql_searches );
dbDelta( $sql_citations );
dbDelta( $sql_cache );
}
Also add to: CREATE_TABLE.sql for manual creation
Provider Architecture
New File: includes/class-brave-search-provider.php
Create a new provider class following the same pattern as OpenRouter provider:
<?php
/**
* Brave Search API Provider
*
* Handles web search via Brave Search API with caching and cost tracking.
*
* @package WP_Agentic_Writer
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class WP_Agentic_Writer_Brave_Search_Provider {
private static $instance = null;
private $api_key;
private $api_base = 'https://api.search.brave.com/res/v1';
private $tier;
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
$settings = get_option( 'wp_agentic_writer_settings', array() );
$this->api_key = $settings['brave_api_key'] ?? '';
$this->tier = $settings['brave_api_tier'] ?? 'base_ai';
}
/**
* Perform web search
*
* @param string $query Search query
* @param array $options Search options
* @return array|WP_Error Search results or error
*/
public function search( $query, $options = array() ) {
if ( empty( $this->api_key ) ) {
return new WP_Error( 'no_api_key', 'Brave Search API key not configured' );
}
// Check cache first
if ( ! empty( $options['use_cache'] ) ) {
$cached = $this->get_cached_result( $query, $options['category'] ?? null );
if ( $cached ) {
return array(
'results' => $cached['results'],
'from_cache' => true,
'cache_age_hours' => $cached['age_hours'],
'cost' => 0,
);
}
}
// Check budget
$budget_check = $this->check_budget();
if ( is_wp_error( $budget_check ) ) {
return $budget_check;
}
// Call API
$response = $this->call_api( $query, $options );
if ( is_wp_error( $response ) ) {
return $response;
}
// Calculate cost
$cost = $this->calculate_cost();
// Store search record
$search_id = $this->store_search( $query, $response, $cost, $options );
// Cache results
if ( ! empty( $options['use_cache'] ) ) {
$this->cache_results( $query, $response, $options );
}
// Track cost
$this->track_cost( $cost, $options['post_id'] ?? 0 );
return array(
'results' => $response,
'from_cache' => false,
'search_id' => $search_id,
'cost' => $cost,
'result_count' => count( $response['web']['results'] ?? array() ),
);
}
/**
* Call Brave Search API
*/
private function call_api( $query, $options = array() ) {
$endpoint = $this->api_base . '/web/search';
$params = array(
'q' => $query,
'count' => $options['count'] ?? 10,
'safesearch' => 'moderate',
'search_lang' => $options['language'] ?? 'en',
'country' => $options['country'] ?? 'US',
);
$response = wp_remote_get(
add_query_arg( $params, $endpoint ),
array(
'headers' => array(
'X-Subscription-Token' => $this->api_key,
'Accept' => 'application/json',
),
'timeout' => 30,
)
);
if ( is_wp_error( $response ) ) {
return $response;
}
$status = wp_remote_retrieve_response_code( $response );
$body = json_decode( wp_remote_retrieve_body( $response ), true );
if ( 429 === $status ) {
return new WP_Error( 'rate_limited', 'Brave API rate limit exceeded' );
} elseif ( 401 === $status ) {
return new WP_Error( 'invalid_api_key', 'Invalid Brave API key' );
} elseif ( 200 !== $status ) {
return new WP_Error( 'api_error', 'Brave API error: ' . $status );
}
return $body;
}
/**
* Check cache for existing results
*/
private function get_cached_result( $query, $category = null ) {
global $wpdb;
$cache_key = sha1( strtolower( trim( $query ) ) );
$cached = $wpdb->get_row( $wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}wpaw_search_cache
WHERE cache_key = %s
AND expires_at > NOW()
ORDER BY hit_count DESC
LIMIT 1",
$cache_key
) );
if ( $cached ) {
// Update cache stats
$wpdb->update(
$wpdb->prefix . 'wpaw_search_cache',
array(
'hit_count' => $cached->hit_count + 1,
'cost_saved' => $cached->cost_saved + $this->calculate_cost(),
),
array( 'id' => $cached->id )
);
$age_seconds = time() - strtotime( $cached->cached_at );
return array(
'results' => json_decode( $cached->results_json, true ),
'age_hours' => ceil( $age_seconds / 3600 ),
);
}
return null;
}
/**
* Store search in database
*/
private function store_search( $query, $response, $cost, $options = array() ) {
global $wpdb;
$top_result = $response['web']['results'][0] ?? null;
$wpdb->insert(
$wpdb->prefix . 'wpaw_searches',
array(
'post_id' => $options['post_id'] ?? 0,
'search_query' => $query,
'search_number' => $options['search_number'] ?? 1,
'total_searches_for_post' => $options['total_searches'] ?? 1,
'results_count' => count( $response['web']['results'] ?? array() ),
'results_json' => wp_json_encode( $response ),
'top_result_title' => $top_result['title'] ?? null,
'top_result_url' => $top_result['url'] ?? null,
'cost' => $cost,
'api_tier' => $this->tier,
'search_category' => $options['category'] ?? 'general',
'status' => 'completed',
)
);
return $wpdb->insert_id;
}
/**
* Cache search results
*/
private function cache_results( $query, $response, $options = array() ) {
global $wpdb;
$settings = get_option( 'wp_agentic_writer_settings', array() );
$cache_days = absint( $settings['brave_cache_duration_days'] ?? 30 );
$cache_key = sha1( strtolower( trim( $query ) ) );
$wpdb->insert(
$wpdb->prefix . 'wpaw_search_cache',
array(
'search_query_normalized' => strtolower( trim( $query ) ),
'search_category' => $options['category'] ?? null,
'cache_key' => $cache_key,
'results_json' => wp_json_encode( $response ),
'result_count' => count( $response['web']['results'] ?? array() ),
'expires_at' => gmdate( 'Y-m-d H:i:s', strtotime( "+{$cache_days} days" ) ),
'quality_score' => 0.9,
)
);
}
/**
* Calculate cost per search
*/
private function calculate_cost() {
$tiers = array(
'free' => 0,
'base_ai' => 0.005,
'pro_ai' => 0.009,
);
return $tiers[ $this->tier ] ?? 0;
}
/**
* Check monthly budget
*/
private function check_budget() {
global $wpdb;
$settings = get_option( 'wp_agentic_writer_settings', array() );
$budget_limit = floatval( $settings['brave_monthly_budget'] ?? 50.00 );
$monthly_cost = $wpdb->get_var(
"SELECT COALESCE(SUM(cost), 0) FROM {$wpdb->prefix}wpaw_searches
WHERE MONTH(created_at) = MONTH(NOW())
AND YEAR(created_at) = YEAR(NOW())"
);
if ( $monthly_cost >= $budget_limit ) {
return new WP_Error(
'budget_exceeded',
sprintf( 'Monthly Brave Search budget of $%.2f exceeded', $budget_limit )
);
}
return true;
}
/**
* Track cost in cost tracker
*/
private function track_cost( $cost, $post_id ) {
do_action(
'wp_aw_after_api_request',
$post_id,
'brave-search',
'web_search',
0, // No input tokens
0, // No output tokens
$cost
);
}
}
REST API Endpoints
Add to includes/class-gutenberg-sidebar.php
Add new REST endpoints for Brave Search:
// In register_rest_routes() method, add:
// Brave Search endpoint
register_rest_route(
'wp-agentic-writer/v1',
'/brave-search',
array(
'methods' => 'POST',
'callback' => array( $this, 'handle_brave_search' ),
'permission_callback' => array( $this, 'check_permissions' ),
'args' => array(
'query' => array(
'required' => true,
'type' => 'string',
),
'postId' => array(
'required' => true,
'type' => 'integer',
),
'category' => array(
'type' => 'string',
'default' => 'general',
),
'useCache' => array(
'type' => 'boolean',
'default' => true,
),
),
)
);
// Get searches for post
register_rest_route(
'wp-agentic-writer/v1',
'/searches/(?P<post_id>\d+)',
array(
'methods' => 'GET',
'callback' => array( $this, 'handle_get_searches' ),
'permission_callback' => array( $this, 'check_permissions' ),
)
);
Handler Methods
/**
* Handle Brave Search request
*/
public function handle_brave_search( $request ) {
$query = $request->get_param( 'query' );
$post_id = $request->get_param( 'postId' );
$category = $request->get_param( 'category' );
$use_cache = $request->get_param( 'useCache' );
$provider = WP_Agentic_Writer_Brave_Search_Provider::get_instance();
$result = $provider->search(
$query,
array(
'post_id' => $post_id,
'category' => $category,
'use_cache' => $use_cache,
)
);
if ( is_wp_error( $result ) ) {
return new WP_Error(
$result->get_error_code(),
$result->get_error_message(),
array( 'status' => 400 )
);
}
return rest_ensure_response( $result );
}
/**
* Get all searches for a post
*/
public function handle_get_searches( $request ) {
global $wpdb;
$post_id = $request->get_param( 'post_id' );
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return new WP_Error( 'unauthorized', 'Not allowed', array( 'status' => 403 ) );
}
$searches = $wpdb->get_results( $wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}wpaw_searches
WHERE post_id = %d
ORDER BY created_at DESC",
$post_id
) );
$citations = $wpdb->get_results( $wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}wpaw_citations
WHERE post_id = %d
ORDER BY citation_number ASC",
$post_id
) );
return rest_ensure_response( array(
'searches' => $searches,
'citations' => $citations,
'total_cost' => array_sum( array_map( function( $s ) {
return floatval( $s->cost );
}, $searches ) ),
) );
}
Frontend Integration
Update assets/js/sidebar.js
Add Brave Search support to the frontend:
// Add to state management (around line 50)
const [webSearchProvider, setWebSearchProvider] = useState('openrouter');
const [braveSearchEnabled, setBraveSearchEnabled] = useState(false);
// Load settings on mount
useEffect(() => {
const settings = wpAgenticWriter.settings || {};
setWebSearchProvider(settings.web_search_provider || 'openrouter');
setBraveSearchEnabled(settings.brave_search_enabled || false);
}, []);
// Add Brave Search function
const performBraveSearch = async (query, category = 'general') => {
try {
const response = await fetch(wpAgenticWriter.apiUrl + '/brave-search', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': wpAgenticWriter.nonce,
},
body: JSON.stringify({
query: query,
postId: postId,
category: category,
useCache: true,
}),
});
if (!response.ok) {
throw new Error('Brave Search failed');
}
const data = await response.json();
// Track cost
if (data.cost) {
setCost(prev => ({ ...prev, session: prev.session + data.cost }));
}
return data;
} catch (error) {
console.error('Brave Search error:', error);
return null;
}
};
// Modify article generation to use selected provider
// In sendMessage() or executePlanFromCard(), check provider:
if (webSearchProvider === 'brave' && braveSearchEnabled) {
// Use Brave Search flow
// Perform searches, then pass results to agent
} else {
// Use OpenRouter :online models
// Existing flow
}
Agent Integration
Research Planning
Create helper class for automatic search planning:
New File: includes/class-research-planner.php
<?php
/**
* Research Planner
*
* Determines what searches to perform based on article topic.
*
* @package WP_Agentic_Writer
*/
class WP_Agentic_Writer_Research_Planner {
/**
* Plan searches for a topic
*
* @param string $topic Article topic
* @param string $depth 'basic', 'medium', 'deep'
* @return array Array of search queries with metadata
*/
public static function plan_searches( $topic, $depth = 'medium' ) {
$searches = array();
// Core definition search (always)
$searches[] = array(
'query' => "$topic what is overview",
'category' => 'definition',
'priority' => 'critical',
);
if ( 'medium' === $depth || 'deep' === $depth ) {
// Current state
$searches[] = array(
'query' => "$topic latest news 2024",
'category' => 'news',
'priority' => 'high',
);
// Pricing/features
$searches[] = array(
'query' => "$topic pricing features comparison",
'category' => 'pricing',
'priority' => 'high',
);
}
if ( 'deep' === $depth ) {
// Use cases
$searches[] = array(
'query' => "$topic use cases examples",
'category' => 'examples',
'priority' => 'medium',
);
// Technical details
$searches[] = array(
'query' => "$topic documentation api guide",
'category' => 'technical',
'priority' => 'medium',
);
}
return $searches;
}
}
Citation Management
New File: includes/class-citation-manager.php
<?php
/**
* Citation Manager
*
* Extracts citations from article content and links to sources.
*
* @package WP_Agentic_Writer
*/
class WP_Agentic_Writer_Citation_Manager {
/**
* Process citations in article content
*
* @param int $post_id Post ID
* @param string $content Article content with citation markers
* @param array $search_results Array of search results
* @return array Processed content with citations
*/
public static function process_citations( $post_id, $content, $search_results ) {
$citations = array();
$citation_number = 1;
// Find citation markers: [source_marker]
preg_match_all( '/\[([a-z0-9_]+)\]/i', $content, $matches );
foreach ( $matches[1] as $marker ) {
// Find matching source
$source = self::find_source_by_marker( $marker, $search_results );
if ( ! $source ) {
continue;
}
// Store citation
self::store_citation( $post_id, $citation_number, $source );
// Replace marker with number
$content = str_replace(
"[$marker]",
"[$citation_number]",
$content
);
$citations[] = array(
'number' => $citation_number,
'source' => $source,
);
$citation_number++;
}
// Add References section
$references = self::generate_references_section( $citations );
$content .= "\n\n" . $references;
return array(
'content' => $content,
'citations' => $citations,
);
}
/**
* Find source in search results
*/
private static function find_source_by_marker( $marker, $search_results ) {
// Match marker to search result URL/title
foreach ( $search_results as $search ) {
if ( empty( $search['results']['web']['results'] ) ) {
continue;
}
foreach ( $search['results']['web']['results'] as $result ) {
$domain = parse_url( $result['url'], PHP_URL_HOST );
if ( stripos( $domain, $marker ) !== false ||
stripos( $result['title'], $marker ) !== false ) {
return array(
'url' => $result['url'],
'title' => $result['title'],
'domain' => $domain,
'snippet' => $result['description'] ?? '',
);
}
}
}
return null;
}
/**
* Store citation in database
*/
private static function store_citation( $post_id, $citation_number, $source ) {
global $wpdb;
$wpdb->insert(
$wpdb->prefix . 'wpaw_citations',
array(
'post_id' => $post_id,
'citation_number' => $citation_number,
'source_url' => $source['url'],
'source_title' => $source['title'],
'source_domain' => $source['domain'],
'added_by' => 'agent_automatic',
)
);
}
/**
* Generate References section HTML
*/
private static function generate_references_section( $citations ) {
if ( empty( $citations ) ) {
return '';
}
$html = "## References\n\n";
foreach ( $citations as $citation ) {
$source = $citation['source'];
$html .= sprintf(
"%d. [%s](%s) - %s\n",
$citation['number'],
$source['title'],
$source['url'],
$source['domain']
);
}
return $html;
}
}
Cost Tracking Integration
Brave Search costs are automatically tracked via the existing cost tracking system using the wp_aw_after_api_request action hook (already implemented in the provider class).
Display in Sidebar
Update cost display to show Brave Search costs separately:
// In sidebar.js cost display section
<div className="wpaw-cost-breakdown">
<div>AI Models: ${cost.session.toFixed(4)}</div>
{braveSearchCost > 0 && (
<div>Web Search: ${braveSearchCost.toFixed(4)}</div>
)}
<div><strong>Total: ${(cost.session + braveSearchCost).toFixed(4)}</strong></div>
</div>
Implementation Phases
Phase 1: Foundation (Week 1)
Goal: Basic Brave Search integration
- Create database tables (activation hook)
- Create
class-brave-search-provider.php - Add settings fields to
class-settings.php - Add UI to
tab-models.php - Test API connectivity
Deliverable: Can perform Brave searches via settings panel test button
Phase 2: REST API (Week 2)
Goal: Frontend can call Brave Search
- Add REST endpoints to
class-gutenberg-sidebar.php - Add frontend functions to
sidebar.js - Test search from frontend
- Implement caching logic
- Test cache hit/miss scenarios
Deliverable: Frontend can perform searches and see cached results
Phase 3: Agent Integration (Week 3)
Goal: Agent uses Brave Search during article generation
- Create
class-research-planner.php - Create
class-citation-manager.php - Integrate into article generation flow
- Add provider selection logic
- Test end-to-end: topic → searches → article with citations
Deliverable: Can generate articles with Brave Search and citations
Phase 4: Analytics & Polish (Week 4)
Goal: Admin dashboard and optimization
- Add search analytics tab to settings
- Display cost breakdown
- Show cache performance
- Add budget alerts
- Implement cache cleanup cron
- Add error handling and logging
Deliverable: Complete admin experience with analytics
Phase 5: Testing & Documentation (Week 5)
Goal: Production-ready
- Test with free tier limits
- Test with paid tiers
- Test budget enforcement
- Test cache expiry
- Write user documentation
- Create migration guide
Deliverable: Production-ready feature with docs
File Structure
New Files to Create
includes/
├── class-brave-search-provider.php ← Main provider class
├── class-research-planner.php ← Auto search planning
└── class-citation-manager.php ← Citation extraction
views/settings/
└── tab-brave-analytics.php ← Analytics dashboard (optional)
Files to Modify
includes/
├── class-settings.php ← Add Brave settings
├── class-gutenberg-sidebar.php ← Add REST endpoints
└── wp-agentic-writer.php ← Add table creation
views/settings/
└── tab-models.php ← Add Brave UI
assets/js/
└── sidebar.js ← Add Brave search functions
CREATE_TABLE.sql ← Add table schemas
Testing Strategy
Unit Tests
-
Provider Tests
- API authentication
- Search query formatting
- Response parsing
- Error handling
-
Cache Tests
- Cache hit/miss
- Expiry logic
- Cost savings calculation
-
Budget Tests
- Monthly limit enforcement
- Alert triggering
- Cost tracking accuracy
Integration Tests
-
Settings Flow
- Save Brave API key
- Switch providers
- Enable/disable features
-
Search Flow
- Perform search
- Cache result
- Reuse cached result
- Track cost
-
Article Generation Flow
- Plan searches
- Execute searches
- Generate article with citations
- Add References section
User Acceptance Tests
-
Free Tier User
- Set up with free API key
- Generate article (2-3 searches)
- Verify cost tracking
- Test monthly limit
-
Paid Tier User
- Set up with paid API key
- Generate multiple articles
- Verify cache reuse
- Check cost savings
-
OpenRouter User
- Keep using OpenRouter :online
- Verify no breaking changes
- Test switching between providers
Migration & Compatibility
Backward Compatibility
- No breaking changes: Existing OpenRouter functionality remains unchanged
- Opt-in feature: Brave Search only activates when API key is set
- Default behavior: If no Brave API key, falls back to OpenRouter :online
- Settings migration: No migration needed - new settings are additive
Rollout Strategy
- Beta Phase: Release to select users for testing
- Documentation: Create setup guide and comparison chart
- Announcement: Blog post explaining benefits
- Support: Monitor for issues and provide quick fixes
Cost Comparison Example
Scenario: Generate 10 articles with web research
OpenRouter :online (Perplexity Sonar Pro)
- Model:
perplexity/sonar-pro - Cost: ~$15 per 1M tokens
- Average: 50K tokens per article with research
- Total: 500K tokens = $7.50
Brave Search API + Standard Model
- Brave: 3 searches per article × 10 articles = 30 searches
- Brave cost: 30 × $0.005 = $0.15
- AI model:
google/gemini-2.0-flash-exp(free or $0.075/1M) - AI tokens: 30K tokens per article (no search overhead)
- AI cost: 300K tokens = $0.02
- Total: $0.17 (97% cheaper!)
With Caching (60% hit rate)
- Brave: 12 fresh + 18 cached = 12 × $0.005 = $0.06
- AI cost: $0.02
- Total: $0.08 (99% cheaper!)
Next Steps
- Review this plan with stakeholders
- Set up Brave Search API account (free tier for testing)
- Begin Phase 1 implementation (database + provider class)
- Create test environment with sample searches
- Iterate based on feedback
Questions & Decisions Needed
-
Should we support both providers simultaneously?
- Current plan: User chooses one provider per article
- Alternative: Use both (Brave for facts, OpenRouter for reasoning)
-
Citation format preference?
- Current plan: Numbered [1], [2]... with References section
- Alternative: Inline links, footnotes, or custom format
-
Cache invalidation strategy?
- Current plan: 30-day automatic expiry
- Alternative: Manual invalidation, topic-based expiry
-
Budget alert method?
- Current plan: Email to admin
- Alternative: Dashboard notification, Slack webhook
Document Status: Ready for Implementation
Last Updated: January 29, 2026
Version: 1.0