Skip to main content

@tokenring-ai/serper

Overview

The @tokenring-ai/serper package provides seamless integration with Serper.dev's Google Search and News APIs, enabling Token Ring agents and applications to perform real-time web searches, fetch news articles, and extract web page content without direct Google API integration. This package extends the @tokenring-ai/websearch module to provide a complete web search provider implementation.

Key Features

  • Google Search Integration: Perform organic web searches with knowledge graphs, related searches, and "people also ask" results
  • Google News Search: Access real-time news articles with source, date, and snippet information (last hour by default)
  • Web Page Fetching: Extract markdown content and metadata from web pages using Serper's scraping service
  • Location-Based Search: Support for geographic targeting through gl and location parameters
  • Language Support: Multi-language search capabilities through hl parameter
  • Retry Logic: Built-in retry mechanism with exponential backoff via doFetchWithRetry
  • Type Safety: Full TypeScript support with Zod schema validation
  • Comprehensive Error Handling: Detailed error messages with hints for common issues
  • Plugin Architecture: Automatic registration with Token Ring applications via websearch service

Installation

bun install @tokenring-ai/serper

Chat Commands

This package does not include chat commands. Searches should be performed through the websearch service provided by @tokenring-ai/websearch or by using the SerperWebSearchProvider directly.

Core Components

SerperWebSearchProvider

The main provider class that extends WebSearchProvider from @tokenring-ai/websearch.

Constructor

constructor(config: SerperWebSearchProviderOptions)

Parameters:

  • apiKey (required): Your Serper.dev API key
  • defaults (optional): Default search parameters
    • gl: Geographic region code (e.g., 'us', 'uk')
    • hl: Language code (e.g., 'en', 'de')
    • location: Specific location for localized results
    • num: Number of results to return
    • page: Page number for pagination

Throws: Error if apiKey is not provided

Public Methods

searchWeb
async searchWeb(query: string, options?: WebSearchProviderOptions): Promise<WebSearchResult>

Performs a Google web search and returns organic results, knowledge graphs, related searches, and "people also ask" results.

Parameters:

  • query (required): Search query string
  • options (optional): Search options
    • countryCode: Geographic region code (e.g., 'us', 'uk')
    • language: Language code (e.g., 'en', 'de')
    • location: Specific location for localized results
    • num: Number of results to return
    • page: Page number for pagination

Returns: Promise<WebSearchResult> containing:

  • organic: Array of organic search results
  • knowledgeGraph: Knowledge graph information if available
  • peopleAlsoAsk: Array of related questions if available
  • relatedSearches: Array of related search queries if available

Note: This method uses doFetchWithRetry for automatic retry with exponential backoff.

Example:

import SerperWebSearchProvider from '@tokenring-ai/serper';

const provider = new SerperWebSearchProvider({
apiKey: process.env.SERPER_API_KEY!,
defaults: {
gl: 'us',
hl: 'en',
num: 10
}
});

const results = await provider.searchWeb('Token Ring AI framework');
console.log(results.organic); // Array of organic search results
console.log(results.knowledgeGraph); // Knowledge graph if available
console.log(results.peopleAlsoAsk); // Array of related questions if available
console.log(results.relatedSearches); // Array of related search queries if available
searchNews
async searchNews(query: string, options?: WebSearchProviderOptions): Promise<NewsSearchResult>

Performs a Google News search and returns recent news articles.

Parameters:

  • query (required): News search query string
  • options (optional): Search options
    • countryCode: Geographic region code
    • language: Language code
    • location: Specific location for localized results
    • num: Number of results to return
    • page: Page number for pagination

Returns: Promise<NewsSearchResult> containing:

  • news: Array of news articles with title, link, snippet, date, source, and position

Note: The news search includes a hardcoded date filter for the last hour (tbs: "qdr:h"). This is currently not configurable.

Note: This method uses doFetchWithRetry for automatic retry with exponential backoff.

Example:

const news = await provider.searchNews('artificial intelligence breakthroughs', {
countryCode: 'us',
num: 5,
page: 1
});

news.news.forEach(article => {
console.log(`Title: ${article.title}`);
console.log(`Source: ${article.source}`);
console.log(`Date: ${article.date}`);
console.log(`Snippet: ${article.snippet}`);
console.log(`Link: ${article.link}`);
});
fetchPage
async fetchPage(url: string, options?: WebPageOptions): Promise<WebPageResult>

Fetches and extracts content from a web page using Serper's scraping service.

Parameters:

  • url (required): URL of the webpage to fetch
  • options (optional): Fetch options
    • timeout: Request timeout in milliseconds

Endpoint: POST https://scrape.serper.dev

Returns: Promise<WebPageResult> containing:

  • markdown: Extracted markdown content
  • metadata: Page metadata including title, description, OpenGraph properties

Note: This method uses direct fetch without retry logic. It includes timeout support via AbortController.

Example:

const page = await provider.fetchPage('https://example.com', {
timeout: 10000
});

console.log(page.markdown); // Extracted markdown content
console.log(page.metadata); // Page metadata including title, description, OpenGraph properties

Private Methods

googleSearch
private async googleSearch(query: string, opts?: SerperSearchOptions): Promise<SerperSearchResponse>

Internal method for performing Google searches via the Serper API.

Endpoint: POST https://google.serper.dev/search

Parameters:

  • query: Search query string
  • opts: Search options including gl, hl, location, num, page, autocorrect, type, and extraParams
googleNews
private async googleNews(query: string, opts?: SerperNewsOptions): Promise<SerperNewsResponse>

Internal method for performing Google News searches via the Serper API.

Endpoint: POST https://google.serper.dev/news

Parameters:

  • query: News search query string
  • opts: Search options including gl, hl, location, num, page, type, and extraParams

Note: Uses hardcoded tbs: "qdr:h" for last hour results.

buildPayload
private buildPayload(query: string, opts?: Record<string, unknown>): Record<string, unknown>

Builds the request payload by merging query, defaults, and options.

Parameters:

  • query: Search query string (required)
  • opts: Additional search options

Returns: Request payload object with undefined/null values removed

Throws: Error if query is empty

parseJsonOrThrow
private async parseJsonOrThrow<T>(res: Response, context: string): Promise<T>

Parses JSON response or throws an error with detailed information.

Parameters:

  • res: Response object
  • context: Context string for error messages

Returns: Parsed JSON object

Throws: Error with status, hint, and details properties if response is not OK

Services

WebSearchService Integration

The SerperWebSearchProvider implements the WebSearchProvider interface and integrates with the WebSearchService from @tokenring-ai/websearch.

Service Registration:

When using the plugin, the provider is automatically registered with the websearch service:

import TokenRingApp from '@tokenring-ai/app';
import { WebSearchService } from '@tokenring-ai/websearch';

const app = new TokenRingApp({
websearch: {
providers: {
serper: {
type: 'serper',
apiKey: process.env.SERPER_API_KEY!,
defaults: {
gl: 'us',
hl: 'en',
num: 10
}
}
}
}
});

// Access the provider through the websearch service
const websearchService = app.requireService(WebSearchService);
const results = await websearchService.search('your query', 'serper');

Provider Documentation

SerperWebSearchProvider

This package provides a web search provider implementation for the Serper API.

Provider Interface

The provider implements the following interface methods from WebSearchProvider:

  • searchWeb(query: string, options?: WebSearchProviderOptions): Promise<WebSearchResult>
  • searchNews(query: string, options?: WebSearchProviderOptions): Promise<NewsSearchResult>
  • fetchPage(url: string, options?: WebPageOptions): Promise<WebPageResult>

Provider Configuration

type SerperWebSearchProviderOptions = {
apiKey: string;
defaults?: {
gl?: string;
hl?: string;
location?: string;
num?: number;
page?: number;
};
};

Registration Patterns

Plugin-based Registration:

import TokenRingApp from '@tokenring-ai/app';

const app = new TokenRingApp({
websearch: {
providers: {
serper: {
type: 'serper',
apiKey: process.env.SERPER_API_KEY!,
defaults: {
gl: 'us',
hl: 'en'
}
}
}
}
});

Programmatic Registration:

import { WebSearchService } from '@tokenring-ai/websearch';
import SerperWebSearchProvider from '@tokenring-ai/serper';

const provider = new SerperWebSearchProvider({
apiKey: process.env.SERPER_API_KEY!,
defaults: {
gl: 'us',
hl: 'en'
}
});

// Register with websearch service
websearchService.registerProvider('serper', provider);

RPC Endpoints

This package does not define RPC endpoints.

Configuration

Configuration Schema

import { z } from 'zod';

export const SerperDefaultsSchema = z.object({
gl: z.string().optional(),
hl: z.string().optional(),
location: z.string().optional(),
num: z.number().optional(),
page: z.number().optional(),
});

export const SerperWebSearchProviderOptionsSchema = z.object({
apiKey: z.string(),
defaults: SerperDefaultsSchema.optional(),
});

export const packageConfigSchema = z.object({
websearch: z.object({
providers: z.record(z.object({
type: z.literal('serper'),
apiKey: z.string(),
defaults: SerperDefaultsSchema.optional(),
})).optional(),
}).optional(),
});

Configuration Example

import TokenRingApp from '@tokenring-ai/app';

const app = new TokenRingApp({
websearch: {
providers: {
serper: {
type: 'serper',
apiKey: process.env.SERPER_API_KEY!,
defaults: {
gl: 'us',
hl: 'en',
num: 10
}
}
}
}
});

Configuration Options

OptionTypeRequiredDescription
apiKeystringYesSerper.dev API key
defaults.glstringNoGeographic region code
defaults.hlstringNoLanguage code
defaults.locationstringNoSpecific location
defaults.numnumberNoNumber of results
defaults.pagenumberNoPage number

Environment Variables

  • SERPER_API_KEY: Your Serper.dev API key

Integration

Plugin Registration

This package integrates with Token Ring applications through the plugin system. When installed, it automatically registers the Serper provider with the websearch service.

Plugin Installation

import TokenRingApp from '@tokenring-ai/app';
import serperPlugin from '@tokenring-ai/serper/plugin';

const app = new TokenRingApp();

await app.installPlugin(serperPlugin, {
websearch: {
providers: {
serper: {
type: 'serper',
apiKey: process.env.SERPER_API_KEY!,
defaults: {
gl: 'us',
hl: 'en'
}
}
}
}
});

Agent Integration

The provider can be used as a tool for agents:

import { Agent } from '@tokenring-ai/agent';
import SerperWebSearchProvider from '@tokenring-ai/serper';

const provider = new SerperWebSearchProvider({
apiKey: process.env.SERPER_API_KEY!
});

const agent = new Agent({
name: 'Researcher',
tools: [
{
name: 'searchWeb',
description: 'Search the web using Google',
handler: async (query: string) => {
return await provider.searchWeb(query);
}
},
{
name: 'searchNews',
description: 'Search for news articles',
handler: async (query: string) => {
return await provider.searchNews(query);
}
}
]
});

Usage Examples

import SerperWebSearchProvider from '@tokenring-ai/serper';

const provider = new SerperWebSearchProvider({
apiKey: process.env.SERPER_API_KEY!,
defaults: {
gl: 'us',
hl: 'en',
num: 10
}
});

// Perform a web search
const results = await provider.searchWeb('Token Ring AI framework');
console.log('Organic results:', results.organic.length);

// Access different result types
if (results.knowledgeGraph) {
console.log('Knowledge Graph:', results.knowledgeGraph.title);
}

if (results.peopleAlsoAsk) {
console.log('People also ask:', results.peopleAlsoAsk.length);
}

if (results.relatedSearches) {
console.log('Related searches:', results.relatedSearches.map(r => r.query));
}
// Search for recent news (last hour by default)
const news = await provider.searchNews('artificial intelligence breakthroughs', {
countryCode: 'us',
num: 5,
page: 1
});

news.news.forEach(article => {
console.log(`Title: ${article.title}`);
console.log(`Source: ${article.source}`);
console.log(`Date: ${article.date}`);
console.log(`Snippet: ${article.snippet}`);
console.log(`Link: ${article.link}`);
});

Web Page Fetching

// Fetch and extract content from a webpage
const page = await provider.fetchPage('https://tokenring.ai', {
timeout: 10000
});

console.log('Page title:', page.metadata.title);
console.log('Description:', page.metadata.description);
console.log('Markdown content:', page.markdown.substring(0, 200) + '...');

Integration with Token Ring Agents

import TokenRingApp from '@tokenring-ai/app';
import { WebSearchService } from '@tokenring-ai/websearch';

const app = new TokenRingApp({
websearch: {
providers: {
serper: {
type: 'serper',
apiKey: process.env.SERPER_API_KEY!,
defaults: {
gl: 'us',
hl: 'en'
}
}
}
}
});

// Access the provider through the websearch service
const websearchService = app.requireService(WebSearchService);
const results = await websearchService.search('your query', 'serper');

Best Practices

  1. API Key Security: Store your Serper API key in environment variables and never commit it to version control
  2. Rate Limiting: Implement appropriate delays between requests to avoid rate limiting
  3. Caching: Consider caching repeated search queries to reduce API usage
  4. Error Handling: Always handle potential errors from search operations
  5. Configuration Defaults: Set reasonable default values for search parameters to ensure consistent behavior
  6. Timeout Management: Configure appropriate timeouts for page fetching operations (note: fetchPage does not have retry logic)
  7. Query Validation: Validate search queries before sending to the API
  8. Result Processing: Handle cases where results may be empty or incomplete
  9. News Search Limitation: Be aware that news search is hardcoded to return results from the last hour only

Error Handling

The package provides comprehensive error handling with helpful hints:

try {
const results = await provider.searchWeb('query');
} catch (error) {
if (error instanceof Error) {
if (error.status === 401) {
console.log('Invalid API key - check SERPER_API_KEY');
} else if (error.status === 429) {
console.log('Rate limit exceeded - reduce request frequency');
} else {
console.log('Search failed:', error.message);
if (error.hint) {
console.log('Hint:', error.hint);
}
if (error.details) {
console.log('Details:', error.details.slice(0, 500));
}
}
}
}

Error Object Structure

Error responses include:

  • status: HTTP status code (400, 401, 429, etc.)
  • message: Human-readable error message
  • hint: Suggestion for resolving the error (optional)
  • details: Raw response snippet (optional)

Common error responses:

  • 401: Invalid API key - check SERPER_API_KEY
  • 429: Rate limit exceeded - reduce request frequency
  • 400: Invalid request parameters (missing required fields)

Rate Limits and Credits

  • Credit Tracking: The raw Serper API response includes a credits field for news searches, but this is filtered out in the returned NewsSearchResult from searchNews. Similarly, for fetchPage, the credits field is not included in the returned WebPageResult.
  • Rate Limiting: Automatic retries with exponential backoff for 429 and 5xx errors.
  • Error Hints: Clear messages for common issues such as invalid API key (401) or rate limits (429).

State Management

This package does not implement state management. The provider is stateless and does not persist any data between requests.

Testing

Testing Setup

The package uses vitest for unit testing:

bun run test
bun run test:coverage

Testing Examples

import { describe, it, expect, beforeEach, vi } from 'vitest';
import SerperWebSearchProvider from '@tokenring-ai/serper';

describe('SerperWebSearchProvider', () => {
let provider: SerperWebSearchProvider;

beforeEach(() => {
provider = new SerperWebSearchProvider({
apiKey: 'test-api-key',
});
});

it('should be created with valid apiKey', () => {
expect(provider).toBeDefined();
});

it('should throw error without apiKey', () => {
expect(() => {
new SerperWebSearchProvider({ apiKey: '' });
}).toThrow('SerperWebSearchProvider requires apiKey');
});
});

Build and Development

# Install dependencies
bun install

# Run tests
bun run test

# Run tests in watch mode
bun run test:watch

# Generate coverage report
bun run test:coverage

# Type check
bun run build

Dependencies

Production Dependencies

PackageVersionDescription
@tokenring-ai/app0.2.0Application framework for plugin integration
@tokenring-ai/agent0.2.0Agent framework
@tokenring-ai/websearch0.2.0Web search provider base class
@tokenring-ai/utility0.2.0Utility functions (pick, doFetchWithRetry)
zod^4.3.6Runtime type validation

Development Dependencies

PackageVersionDescription
vitest^4.1.0Testing framework
@vitest/coverage-v8^4.1.0Coverage tooling
typescript^5.9.3TypeScript compiler
  • @tokenring-ai/websearch: Web search provider interface and service
  • @tokenring-ai/app: Token Ring application framework
  • @tokenring-ai/agent: Agent orchestration system
  • @tokenring-ai/utility: Shared utilities and helpers

Type Definitions

Request Types

SerperSearchRequest

Request payload structure for Google search

{
q: string;
gl?: string;
hl?: string;
location?: string;
num?: number;
page?: number;
autocorrect?: boolean;
type?: "search";
}

SerperNewsRequest

Request payload structure for Google News search

{
q: string;
gl?: string;
location?: string;
num?: number;
page?: number;
type?: "news";
}

SerperSearchParameters

Search parameters in response

{
q: string;
gl?: string;
hl?: string;
autocorrect?: boolean;
page?: number;
type?: string;
}

Response Types

SerperSearchResponse

Response structure for Google search

{
searchParameters: SerperSearchParameters;
knowledgeGraph?: SerperKnowledgeGraph;
organic: SerperOrganicResult[];
peopleAlsoAsk?: SerperPeopleAlsoAsk[];
relatedSearches?: SerperRelatedSearch[];
}

SerperNewsResponse

Response structure for news search

{
searchParameters: SerperSearchParameters;
news: SerperNewsResult[];
credits?: number;
}

SerperPageResponse

Response structure for page fetch

{
text: string;
markdown: string;
metadata: {
title?: string;
description?: string;
"og:title"?: string;
"og:description"?: string;
"og:url"?: string;
"og:image"?: string;
"og:type"?: string;
"og:site_name"?: string;
[key: string]: any;
};
credits?: number;
}

Result Types

SerperKnowledgeGraph

{
title: string;
type: string;
website?: string;
imageUrl?: string;
description?: string;
descriptionSource?: string;
descriptionLink?: string;
attributes?: Record<string, string>;
}
{
title: string;
link: string;
}

SerperOrganicResult

{
title: string;
link: string;
snippet: string;
date?: string;
position: number;
attributes?: Record<string, string>;
sitelinks?: SerperSitelink[];
}

SerperPeopleAlsoAsk

{
question: string;
snippet: string;
title: string;
link: string;
}

SerperRelatedSearch

{
query: string;
}

SerperNewsResult

{
title: string;
link: string;
snippet: string;
date: string;
source: string;
position: number;
}

Limitations

  • Relies on Serper.dev quotas and rate limits
  • Only supports text/HTML content (no binary content)
  • Web page fetching limited to Serper's scraping capabilities
  • News search includes automatic date filtering (tbs="qdr:h")

License

MIT License - Copyright (c) 2025 Mark Dierolf

See LICENSE for details.