Skip to content

Commit 2c57dcb

Browse files
committed
feat(tool): add duckduckgo search
1 parent 98a7481 commit 2c57dcb

File tree

8 files changed

+399
-0
lines changed

8 files changed

+399
-0
lines changed
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# @agent-infra/duckduckgo-search
2+
3+
<p>
4+
<a href="https://npmjs.com/package/@agent-infra/duckduckgo-search?activeTab=readme"><img src="https://img.shields.io/npm/v/@agent-infra/duckduckgo-search?style=flat-square&colorA=564341&colorB=EDED91" alt="npm version" /></a>
5+
<a href="https://npmcharts.com/compare/@agent-infra/duckduckgo-search?minimal=true"><img src="https://img.shields.io/npm/dm/@agent-infra/duckduckgo-search.svg?style=flat-square&colorA=564341&colorB=EDED91" alt="downloads" /></a>
6+
<a href="https://nodejs.org/en/about/previous-releases"><img src="https://img.shields.io/node/v/@agent-infra/duckduckgo-search.svg?style=flat-square&colorA=564341&colorB=EDED91" alt="node version"></a>
7+
<a href="https://github.com/web-infra-dev/rsbuild/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square&colorA=564341&colorB=EDED91" alt="license" /></a>
8+
</p>
9+
10+
A lightweight TypeScript client for DuckDuckGo Search, designed for AI applications.
11+
12+
## Features
13+
14+
- **Type-safe**: Full TypeScript support with comprehensive type definitions
15+
- **Configurable**: Customizable search parameters and API settings
16+
- **Minimal**: Zero external runtime dependencies
17+
- **Developer-friendly**: Clean API with Promise-based interface
18+
19+
## Installation
20+
21+
```bash
22+
npm install @agent-infra/duckduckgo-search
23+
# or
24+
yarn add @agent-infra/duckduckgo-search
25+
# or
26+
pnpm add @agent-infra/duckduckgo-search
27+
```
28+
29+
## Usage
30+
31+
### Basic Search
32+
33+
```typescript
34+
import { DuckDuckGoSearchClient } from '@agent-infra/duckduckgo-search';
35+
36+
const client = new DuckDuckGoSearchClient({});
37+
38+
const results = await client.search({
39+
query: 'climate change',
40+
count: 5,
41+
});
42+
43+
console.log(results.webPages?.value);
44+
```
45+
46+
### With Custom Logger
47+
48+
```typescript
49+
import { ConsoleLogger } from '@agent-infra/logger';
50+
import { DuckDuckGoSearchClient } from '@agent-infra/duckduckgo-search';
51+
52+
const logger = new ConsoleLogger('[DuckDuckGoSearch]');
53+
const client = new DuckDuckGoSearchClient({
54+
logger,
55+
});
56+
57+
const results = await client.search({ query: 'machine learning' });
58+
```
59+
60+
## API Reference
61+
62+
### DuckDuckGoSearchClient
63+
64+
```typescript
65+
constructor(config?: Partial<DuckDuckGoSearchClientConfig>)
66+
```
67+
68+
Configuration options:
69+
70+
```typescript
71+
interface DuckDuckGoSearchClientConfig {
72+
logger?: Logger;
73+
}
74+
```
75+
76+
### Search Method
77+
78+
```typescript
79+
async search(params: DuckDuckGoSearchOptions): Promise<DuckDuckGoSearchResponse>
80+
```
81+
82+
Search options:
83+
84+
```typescript
85+
interface DuckDuckGoSearchOptions {
86+
query: string; // Search query (required)
87+
count?: number; // Number of results to return
88+
/** The safe search type of the search. */
89+
safeSearch?: SafeSearchType;
90+
/** The time range of the searches, can be a SearchTimeType or a date range ("2021-03-16..2021-03-30") */
91+
time?: SearchTimeType | string;
92+
/** The locale(?) of the search. Defaults to "en-us". */
93+
locale?: string;
94+
/** The region of the search. Defaults to "wt-wt" or all regions. */
95+
region?: string;
96+
/** The market region(?) of the search. Defaults to "US". */
97+
marketRegion?: string;
98+
/** The number to offset the results to. */
99+
offset?: number;
100+
/**
101+
* The string that acts like a key to a search.
102+
* Set this if you made a search with the same query.
103+
*/
104+
vqd?: string;
105+
[key: string]: any; // Additional parameters
106+
}
107+
```
108+
109+
### Response Types
110+
111+
```typescript
112+
interface DuckDuckGoSearchResponse {
113+
/** Whether there were no results found. */
114+
noResults: boolean;
115+
/** The VQD of the search query. */
116+
vqd: string;
117+
/** The web results of the search. */
118+
results: SearchResult[];
119+
// Additional response fields
120+
}
121+
```
122+
123+
## Examples
124+
125+
See [examples](./examples/).
126+
127+
128+
## License
129+
130+
Copyright (c) 2025 ByteDance, Inc. and its affiliates.
131+
132+
Licensed under the Apache License, Version 2.0.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Copyright (c) 2025 Bytedance, Inc. and its affiliates.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
import { ConsoleLogger } from '@agent-infra/logger';
6+
import { DuckDuckGoSearchClient } from '../src';
7+
8+
async function runExample() {
9+
const logger = new ConsoleLogger('[BingSearch]');
10+
try {
11+
const client = new DuckDuckGoSearchClient();
12+
const searchResults = await client.search({
13+
query: 'UI-TARS',
14+
count: 5,
15+
});
16+
17+
console.log(JSON.stringify(searchResults, null, 2));
18+
console.log(`Found ${searchResults.results.length || 0} results`);
19+
} catch (error) {
20+
console.error('Search with options failed:', error);
21+
}
22+
}
23+
24+
if (require.main === module) {
25+
runExample().catch(console.error);
26+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"name": "@agent-infra/duckduckgo-search",
3+
"version": "0.0.1",
4+
"main": "dist/index.js",
5+
"module": "dist/index.mjs",
6+
"types": "dist/index.d.ts",
7+
"exports": {
8+
".": {
9+
"import": "./dist/index.mjs",
10+
"require": "./dist/index.js",
11+
"types": "./dist/index.d.ts"
12+
}
13+
},
14+
"files": [
15+
"dist"
16+
],
17+
"scripts": {
18+
"dev": "rslib build --watch",
19+
"build": "rslib build",
20+
"prepare": "npm run build",
21+
"prepublishOnly": "pnpm run build",
22+
"test": "vitest run",
23+
"test:watch": "vitest",
24+
"test:e2e": "vitest --config vitest.e2e.config.ts",
25+
"coverage": "vitest run --coverage",
26+
"run:example": "ts-node scripts/example.ts"
27+
},
28+
"dependencies": {
29+
"duck-duck-scrape": "^2.2.7",
30+
"@agent-infra/logger": "workspace:*"
31+
},
32+
"devDependencies": {
33+
"@types/node": "20.14.8",
34+
"rimraf": "4.1.0",
35+
"typescript": "4.9.4",
36+
"vitest": "3.0.7",
37+
"@vitest/coverage-v8": "3.0.7",
38+
"ts-node": "10.9.2",
39+
"@rslib/core": "0.5.3"
40+
}
41+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* Copyright (c) 2025 Bytedance, Inc. and its affiliates.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
import { defineConfig } from '@rslib/core';
6+
7+
const BANNER = `/**
8+
* Copyright (c) 2025 Bytedance, Inc. and its affiliates.
9+
* SPDX-License-Identifier: Apache-2.0
10+
*/`;
11+
12+
export default defineConfig({
13+
source: {
14+
entry: {
15+
index: ['src/**/*.ts', '!src/**/*.{test,bench}.ts'],
16+
},
17+
},
18+
lib: [
19+
{
20+
format: 'esm',
21+
syntax: 'es2021',
22+
bundle: false,
23+
dts: true,
24+
banner: { js: BANNER },
25+
},
26+
{
27+
format: 'cjs',
28+
syntax: 'es2021',
29+
bundle: false,
30+
dts: true,
31+
banner: { js: BANNER },
32+
},
33+
],
34+
output: {
35+
target: 'web',
36+
cleanDistPath: true,
37+
sourceMap: true,
38+
},
39+
});
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/**
2+
* Copyright (c) 2025 Bytedance, Inc. and its affiliates.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
/* eslint-disable @typescript-eslint/no-explicit-any */
6+
import { Logger, defaultLogger } from '@agent-infra/logger';
7+
import * as DDG from 'duck-duck-scrape';
8+
import { SearchOptions, SearchResults } from 'duck-duck-scrape';
9+
10+
export interface DuckDuckGoSearchResponse extends SearchResults {}
11+
12+
/**
13+
* Options for performing a DuckDuckGo search
14+
*/
15+
export interface DuckDuckGoSearchOptions extends SearchOptions {
16+
query: string;
17+
/**
18+
* Number of results to return
19+
* @default 5
20+
*/
21+
count?: number;
22+
}
23+
24+
interface DuckDuckGoSearchClientConfig {
25+
logger?: Logger;
26+
}
27+
28+
/**
29+
* Client for interacting with DuckDuckGo Search API
30+
*/
31+
export class DuckDuckGoSearchClient {
32+
private logger: Logger;
33+
private config?: DuckDuckGoSearchClientConfig;
34+
35+
/**
36+
* Creates a new instance of DuckDuckGoSearchClient
37+
*
38+
* @param config - Configuration options for the client
39+
*/
40+
constructor(config?: DuckDuckGoSearchClientConfig) {
41+
this.config = config;
42+
this.logger = config?.logger ?? defaultLogger;
43+
}
44+
45+
/**
46+
* Perform DuckDuckGo search
47+
* @param params Search parameters
48+
* @returns Search results
49+
*/
50+
async search(
51+
params: DuckDuckGoSearchOptions,
52+
): Promise<DuckDuckGoSearchResponse> {
53+
this.logger.log('search params', params);
54+
55+
try {
56+
const { query, count = 5, ...restParams } = params;
57+
58+
const result = await DDG.search(query, {
59+
safeSearch: DDG.SafeSearchType.STRICT,
60+
...restParams,
61+
});
62+
console.log('result', result);
63+
return result
64+
? {
65+
noResults: result.noResults,
66+
vqd: result.vqd,
67+
results: result.results.slice(0, count),
68+
}
69+
: {
70+
noResults: true,
71+
vqd: '',
72+
results: [],
73+
};
74+
} catch (error) {
75+
this.logger.error('Error performing DuckDuckGo search:', error);
76+
throw error;
77+
}
78+
}
79+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Copyright (c) 2025 Bytedance, Inc. and its affiliates.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
export {
6+
DuckDuckGoSearchClient,
7+
type DuckDuckGoSearchOptions,
8+
type DuckDuckGoSearchResponse,
9+
} from './api-client';
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"compilerOptions": {
3+
"target": "esnext",
4+
"module": "commonjs",
5+
"moduleResolution": "node",
6+
"declaration": true,
7+
"declarationMap": true,
8+
"sourceMap": true,
9+
"outDir": "./dist",
10+
"strict": true,
11+
"esModuleInterop": true,
12+
"forceConsistentCasingInFileNames": true,
13+
"resolveJsonModule": true,
14+
"isolatedModules": true,
15+
"skipLibCheck": true
16+
},
17+
"include": ["src/**/*"],
18+
"exclude": ["node_modules", "dist"]
19+
}

0 commit comments

Comments
 (0)