@oevortex/ddg_search 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -1
- package/README.md +0 -11
- package/babel.config.js +1 -1
- package/bin/cli.js +6 -12
- package/package.json +2 -2
- package/src/index.js +1 -6
- package/src/index.ts +1 -6
- package/test.setup.js +0 -47
- package/src/tools/braveTool.js +0 -63
- package/src/utils/search_brave_ai.js +0 -167
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [1.3.0] - 2026-02-09
|
|
6
|
+
|
|
7
|
+
### Removed
|
|
8
|
+
- Brave AI Search provider and tool
|
|
9
|
+
- Brave AI search utility with streaming response parsing
|
|
10
|
+
- Brave AI tool tests and MCP integration coverage
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Updated package.json version to 1.3.0
|
|
14
|
+
|
|
4
15
|
## [1.2.1] - 2026-01-19
|
|
5
16
|
### Added
|
|
6
17
|
- Brave AI Search provider and tool with research mode toggle
|
package/README.md
CHANGED
|
@@ -211,15 +211,6 @@ Or if installed globally:
|
|
|
211
211
|
</ul>
|
|
212
212
|
<i>Example: Search Monica AI for "Latest advancements in AI"</i>
|
|
213
213
|
</div>
|
|
214
|
-
<div style="margin-bottom: 1.5em;">
|
|
215
|
-
<b>🛡️ Brave AI Search Tool</b><br/>
|
|
216
|
-
<code>brave-search</code><br/>
|
|
217
|
-
<ul>
|
|
218
|
-
<li><b>query</b> (string, required): The search query or question</li>
|
|
219
|
-
<li><b>enableResearch</b> (boolean, optional, default: false): Enable deep research mode</li>
|
|
220
|
-
</ul>
|
|
221
|
-
<i>Example: Search Brave AI for "Summarize the latest AI safety research"</i>
|
|
222
|
-
</div>
|
|
223
214
|
</div>
|
|
224
215
|
|
|
225
216
|
---
|
|
@@ -235,13 +226,11 @@ src/
|
|
|
235
226
|
searchTool.js
|
|
236
227
|
iaskTool.js
|
|
237
228
|
monicaTool.js
|
|
238
|
-
braveTool.js
|
|
239
229
|
utils/
|
|
240
230
|
search.js # Search and URL utilities
|
|
241
231
|
user_agents.js
|
|
242
232
|
search_monica.js
|
|
243
233
|
search_iask.js # IAsk AI search utilities
|
|
244
|
-
search_brave_ai.js
|
|
245
234
|
package.json
|
|
246
235
|
README.md
|
|
247
236
|
```
|
package/babel.config.js
CHANGED
package/bin/cli.js
CHANGED
|
@@ -14,13 +14,12 @@ async function startServer() {
|
|
|
14
14
|
const { searchToolDefinition, searchToolHandler } = await import(`${modulePath}/tools/searchTool.js`);
|
|
15
15
|
const { iaskToolDefinition, iaskToolHandler } = await import(`${modulePath}/tools/iaskTool.js`);
|
|
16
16
|
const { monicaToolDefinition, monicaToolHandler } = await import(`${modulePath}/tools/monicaTool.js`);
|
|
17
|
-
const { braveToolDefinition, braveToolHandler } = await import(`${modulePath}/tools/braveTool.js`);
|
|
18
17
|
|
|
19
18
|
// Create the MCP server
|
|
20
19
|
const server = new Server({
|
|
21
20
|
id: 'ddg-search-mcp',
|
|
22
|
-
name: 'DuckDuckGo, IAsk AI
|
|
23
|
-
description: 'A Model Context Protocol server for web search using DuckDuckGo, IAsk AI,
|
|
21
|
+
name: 'DuckDuckGo, IAsk AI & Monica AI Search MCP',
|
|
22
|
+
description: 'A Model Context Protocol server for web search using DuckDuckGo, IAsk AI, and Monica AI',
|
|
24
23
|
version: '1.1.8'
|
|
25
24
|
}, {
|
|
26
25
|
capabilities: {
|
|
@@ -34,8 +33,7 @@ async function startServer() {
|
|
|
34
33
|
let availableTools = [
|
|
35
34
|
searchToolDefinition,
|
|
36
35
|
iaskToolDefinition,
|
|
37
|
-
monicaToolDefinition
|
|
38
|
-
braveToolDefinition
|
|
36
|
+
monicaToolDefinition
|
|
39
37
|
];
|
|
40
38
|
|
|
41
39
|
// Define available tools
|
|
@@ -58,7 +56,7 @@ async function startServer() {
|
|
|
58
56
|
const { name, arguments: args } = request.params;
|
|
59
57
|
|
|
60
58
|
// Validate tool name
|
|
61
|
-
const validTools = ['web-search', 'iask-search', 'monica-search'
|
|
59
|
+
const validTools = ['web-search', 'iask-search', 'monica-search'];
|
|
62
60
|
if (!validTools.includes(name)) {
|
|
63
61
|
throw new Error(`Unknown tool: ${name}`);
|
|
64
62
|
}
|
|
@@ -74,9 +72,6 @@ async function startServer() {
|
|
|
74
72
|
case 'monica-search':
|
|
75
73
|
return await monicaToolHandler(args);
|
|
76
74
|
|
|
77
|
-
case 'brave-search':
|
|
78
|
-
return await braveToolHandler(args);
|
|
79
|
-
|
|
80
75
|
default:
|
|
81
76
|
throw new Error(`Tool not found: ${name}`);
|
|
82
77
|
}
|
|
@@ -106,7 +101,7 @@ async function startServer() {
|
|
|
106
101
|
// Start the server with stdio transport
|
|
107
102
|
const transport = new StdioServerTransport();
|
|
108
103
|
await server.connect(transport);
|
|
109
|
-
console.error('DuckDuckGo, IAsk AI
|
|
104
|
+
console.error('DuckDuckGo, IAsk AI & Monica AI Search MCP server started and listening on stdio');
|
|
110
105
|
} catch (error) {
|
|
111
106
|
console.error('Failed to start server:', error);
|
|
112
107
|
process.exit(1);
|
|
@@ -120,7 +115,7 @@ const versionFlag = args.includes('--version') || args.includes('-v');
|
|
|
120
115
|
|
|
121
116
|
if (helpFlag) {
|
|
122
117
|
console.log(`
|
|
123
|
-
DuckDuckGo, IAsk AI
|
|
118
|
+
DuckDuckGo, IAsk AI & Monica AI Search MCP - A Model Context Protocol server for web search
|
|
124
119
|
|
|
125
120
|
Usage:
|
|
126
121
|
npx -y @oevortex/ddg_search@latest [options]
|
|
@@ -133,7 +128,6 @@ This MCP server provides the following tools:
|
|
|
133
128
|
- web-search: Search the web using DuckDuckGo
|
|
134
129
|
- iask-search: Search using IAsk AI for AI-generated responses
|
|
135
130
|
- monica-search: Search using Monica AI for AI-generated responses
|
|
136
|
-
- brave-search: Search using Brave AI for AI-generated responses
|
|
137
131
|
|
|
138
132
|
Created by @OEvortex
|
|
139
133
|
Subscribe to youtube.com/@OEvortex for more tools and tutorials!
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oevortex/ddg_search",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "A Model Context Protocol server for web search using DuckDuckGo and
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "A Model Context Protocol server for web search using DuckDuckGo, IAsk AI, and Monica AI",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"module": "src/index.ts",
|
|
7
7
|
"exports": {
|
package/src/index.js
CHANGED
|
@@ -5,7 +5,6 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprot
|
|
|
5
5
|
import { searchToolDefinition, searchToolHandler } from './tools/searchTool.js';
|
|
6
6
|
import { iaskToolDefinition, iaskToolHandler } from './tools/iaskTool.js';
|
|
7
7
|
import { monicaToolDefinition, monicaToolHandler } from './tools/monicaTool.js';
|
|
8
|
-
import { braveToolDefinition, braveToolHandler } from './tools/braveTool.js';
|
|
9
8
|
|
|
10
9
|
// Required: Export default createServer function for Smithery
|
|
11
10
|
export default function createServer({ config } = {}) {
|
|
@@ -15,8 +14,7 @@ export default function createServer({ config } = {}) {
|
|
|
15
14
|
const availableTools = [
|
|
16
15
|
searchToolDefinition,
|
|
17
16
|
iaskToolDefinition,
|
|
18
|
-
monicaToolDefinition
|
|
19
|
-
braveToolDefinition
|
|
17
|
+
monicaToolDefinition
|
|
20
18
|
];
|
|
21
19
|
|
|
22
20
|
console.log('Available tools:', availableTools.map(t => t.name));
|
|
@@ -58,9 +56,6 @@ export default function createServer({ config } = {}) {
|
|
|
58
56
|
case 'monica-search':
|
|
59
57
|
return await monicaToolHandler(args);
|
|
60
58
|
|
|
61
|
-
case 'brave-search':
|
|
62
|
-
return await braveToolHandler(args);
|
|
63
|
-
|
|
64
59
|
default:
|
|
65
60
|
throw new Error(`Tool not found: ${name}`);
|
|
66
61
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,7 +5,6 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprot
|
|
|
5
5
|
import { searchToolDefinition, searchToolHandler } from './tools/searchTool.js';
|
|
6
6
|
import { iaskToolDefinition, iaskToolHandler } from './tools/iaskTool.js';
|
|
7
7
|
import { monicaToolDefinition, monicaToolHandler } from './tools/monicaTool.js';
|
|
8
|
-
import { braveToolDefinition, braveToolHandler } from './tools/braveTool.js';
|
|
9
8
|
|
|
10
9
|
// Required: Export default createServer function for Smithery
|
|
11
10
|
export default function createServer({ config }: { config?: any } = {}) {
|
|
@@ -15,8 +14,7 @@ export default function createServer({ config }: { config?: any } = {}) {
|
|
|
15
14
|
const availableTools = [
|
|
16
15
|
searchToolDefinition,
|
|
17
16
|
iaskToolDefinition,
|
|
18
|
-
monicaToolDefinition
|
|
19
|
-
braveToolDefinition
|
|
17
|
+
monicaToolDefinition
|
|
20
18
|
];
|
|
21
19
|
|
|
22
20
|
console.log('Available tools:', availableTools.map(t => t.name));
|
|
@@ -58,9 +56,6 @@ export default function createServer({ config }: { config?: any } = {}) {
|
|
|
58
56
|
case 'monica-search':
|
|
59
57
|
return await monicaToolHandler(args);
|
|
60
58
|
|
|
61
|
-
case 'brave-search':
|
|
62
|
-
return await braveToolHandler(args);
|
|
63
|
-
|
|
64
59
|
default:
|
|
65
60
|
throw new Error(`Tool not found: ${name}`);
|
|
66
61
|
}
|
package/test.setup.js
CHANGED
|
@@ -68,53 +68,6 @@ jest.mock('axios-cookiejar-support', () => ({
|
|
|
68
68
|
wrapper: jest.fn((axios) => axios)
|
|
69
69
|
}));
|
|
70
70
|
|
|
71
|
-
jest.mock('crypto', () => ({
|
|
72
|
-
randomUUID: jest.fn(() => 'mock-uuid-12345')
|
|
73
|
-
}));
|
|
74
|
-
|
|
75
|
-
// Mock axios
|
|
76
|
-
const mockAxios = jest.fn(() => Promise.resolve({
|
|
77
|
-
status: 200,
|
|
78
|
-
data: '<html><body>Mock Response</body></html>',
|
|
79
|
-
config: { url: 'http://example.com' },
|
|
80
|
-
request: { res: { responseUrl: 'http://example.com' } }
|
|
81
|
-
}));
|
|
82
|
-
|
|
83
|
-
mockAxios.get = jest.fn();
|
|
84
|
-
mockAxios.post = jest.fn();
|
|
85
|
-
|
|
86
|
-
jest.mock('axios', () => mockAxios);
|
|
87
|
-
|
|
88
|
-
// Mock external modules
|
|
89
|
-
jest.mock('cheerio', () => ({
|
|
90
|
-
load: jest.fn(() => ({
|
|
91
|
-
find: jest.fn(() => ({
|
|
92
|
-
each: jest.fn(),
|
|
93
|
-
text: jest.fn(() => 'Mock Title'),
|
|
94
|
-
attr: jest.fn(() => 'http://example.com')
|
|
95
|
-
})),
|
|
96
|
-
html: jest.fn(() => '<div>Mock Content</div>'),
|
|
97
|
-
text: jest.fn(() => 'Mock Text Content')
|
|
98
|
-
}))
|
|
99
|
-
}));
|
|
100
|
-
|
|
101
|
-
jest.mock('ws', () => jest.fn());
|
|
102
|
-
|
|
103
|
-
jest.mock('turndown', () => jest.fn(() => ({
|
|
104
|
-
turndown: jest.fn((html) => html.replace(/<[^>]*>/g, ''))
|
|
105
|
-
})));
|
|
106
|
-
|
|
107
|
-
jest.mock('tough-cookie', () => ({
|
|
108
|
-
CookieJar: jest.fn(() => ({
|
|
109
|
-
getCookies: jest.fn(() => []),
|
|
110
|
-
setCookie: jest.fn()
|
|
111
|
-
}))
|
|
112
|
-
}));
|
|
113
|
-
|
|
114
|
-
jest.mock('axios-cookiejar-support', () => ({
|
|
115
|
-
wrapper: jest.fn((axios) => axios)
|
|
116
|
-
}));
|
|
117
|
-
|
|
118
71
|
jest.mock('crypto', () => ({
|
|
119
72
|
randomUUID: jest.fn(() => 'mock-uuid-12345')
|
|
120
73
|
}));
|
package/src/tools/braveTool.js
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { searchBraveAI } from '../utils/search_brave_ai.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Brave AI search tool definition
|
|
5
|
-
*/
|
|
6
|
-
export const braveToolDefinition = {
|
|
7
|
-
name: 'brave-search',
|
|
8
|
-
title: 'Brave AI Search',
|
|
9
|
-
description: 'AI-powered search using Brave Search AI Chat. Returns AI-generated responses based on web content.',
|
|
10
|
-
inputSchema: {
|
|
11
|
-
type: 'object',
|
|
12
|
-
properties: {
|
|
13
|
-
query: {
|
|
14
|
-
type: 'string',
|
|
15
|
-
description: 'The search query or question.'
|
|
16
|
-
},
|
|
17
|
-
enableResearch: {
|
|
18
|
-
type: 'boolean',
|
|
19
|
-
description: 'Enable deep research mode for more comprehensive responses.',
|
|
20
|
-
default: false
|
|
21
|
-
}
|
|
22
|
-
},
|
|
23
|
-
required: ['query']
|
|
24
|
-
},
|
|
25
|
-
annotations: {
|
|
26
|
-
readOnlyHint: true,
|
|
27
|
-
openWorldHint: false
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Brave AI search tool handler
|
|
33
|
-
* @param {Object} params - The tool parameters
|
|
34
|
-
* @returns {Promise<Object>} - The tool result
|
|
35
|
-
*/
|
|
36
|
-
export async function braveToolHandler(params) {
|
|
37
|
-
const { query, enableResearch = false } = params;
|
|
38
|
-
|
|
39
|
-
console.log(`Searching Brave AI for: "${query}" (research: ${enableResearch})`);
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
const result = await searchBraveAI(query, { enableResearch });
|
|
43
|
-
return {
|
|
44
|
-
content: [
|
|
45
|
-
{
|
|
46
|
-
type: 'text',
|
|
47
|
-
text: result || 'No results found.'
|
|
48
|
-
}
|
|
49
|
-
]
|
|
50
|
-
};
|
|
51
|
-
} catch (error) {
|
|
52
|
-
console.error(`Error in Brave AI search: ${error.message}`);
|
|
53
|
-
return {
|
|
54
|
-
isError: true,
|
|
55
|
-
content: [
|
|
56
|
-
{
|
|
57
|
-
type: 'text',
|
|
58
|
-
text: `Error searching Brave AI: ${error.message}`
|
|
59
|
-
}
|
|
60
|
-
]
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
}
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
|
-
import { randomBytes } from 'crypto';
|
|
3
|
-
import { getRandomUserAgent } from './user_agents.js';
|
|
4
|
-
|
|
5
|
-
const BASE_URL = 'https://search.brave.com/api/tap/v1';
|
|
6
|
-
const DEFAULT_TIMEOUT = 30000;
|
|
7
|
-
|
|
8
|
-
function generateKeyB64() {
|
|
9
|
-
const key = randomBytes(32);
|
|
10
|
-
const k = key.toString('base64url');
|
|
11
|
-
const jwk = {
|
|
12
|
-
alg: 'A256GCM',
|
|
13
|
-
ext: true,
|
|
14
|
-
k,
|
|
15
|
-
key_ops: ['encrypt', 'decrypt'],
|
|
16
|
-
kty: 'oct'
|
|
17
|
-
};
|
|
18
|
-
return Buffer.from(JSON.stringify(jwk)).toString('base64');
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function buildHeaders() {
|
|
22
|
-
return {
|
|
23
|
-
accept: 'application/json',
|
|
24
|
-
'accept-language': 'en-US,en;q=0.9',
|
|
25
|
-
'user-agent': getRandomUserAgent(),
|
|
26
|
-
'sec-ch-ua': '"Chromium";v="127", "Not)A;Brand";v="99"',
|
|
27
|
-
'sec-ch-ua-mobile': '?0',
|
|
28
|
-
'sec-ch-ua-platform': '"Windows"',
|
|
29
|
-
'sec-fetch-dest': 'empty',
|
|
30
|
-
'sec-fetch-mode': 'cors',
|
|
31
|
-
'sec-fetch-site': 'same-origin',
|
|
32
|
-
referer: 'https://search.brave.com/ask'
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function parseStream(stream) {
|
|
37
|
-
return new Promise((resolve, reject) => {
|
|
38
|
-
let buffer = '';
|
|
39
|
-
let text = '';
|
|
40
|
-
|
|
41
|
-
stream.on('data', (chunk) => {
|
|
42
|
-
buffer += chunk.toString();
|
|
43
|
-
const lines = buffer.split('\n');
|
|
44
|
-
buffer = lines.pop() ?? '';
|
|
45
|
-
|
|
46
|
-
for (const line of lines) {
|
|
47
|
-
const trimmed = line.trim();
|
|
48
|
-
if (!trimmed) {
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
const payload = JSON.parse(trimmed);
|
|
54
|
-
if (payload?.type === 'text_delta') {
|
|
55
|
-
text += payload.delta ?? '';
|
|
56
|
-
}
|
|
57
|
-
} catch (error) {
|
|
58
|
-
// Ignore malformed lines
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
stream.on('end', () => resolve(text));
|
|
64
|
-
stream.on('error', (error) => reject(error));
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Search using Brave AI Search.
|
|
70
|
-
* @param {string} prompt - The search query.
|
|
71
|
-
* @param {object} [options] - Search options.
|
|
72
|
-
* @param {boolean} [options.enableResearch=false] - Enable deep research mode.
|
|
73
|
-
* @param {number} [options.timeout=30000] - Request timeout in ms.
|
|
74
|
-
* @param {string} [options.language='en'] - Language code.
|
|
75
|
-
* @param {string} [options.country='US'] - Country code.
|
|
76
|
-
* @param {string} [options.uiLang='en-us'] - UI language.
|
|
77
|
-
* @param {string|null} [options.geoloc=null] - Geolocation coordinates.
|
|
78
|
-
* @returns {Promise<string>} AI-generated response text.
|
|
79
|
-
*/
|
|
80
|
-
export async function searchBraveAI(
|
|
81
|
-
prompt,
|
|
82
|
-
{
|
|
83
|
-
enableResearch = false,
|
|
84
|
-
timeout = DEFAULT_TIMEOUT,
|
|
85
|
-
language = 'en',
|
|
86
|
-
country = 'US',
|
|
87
|
-
uiLang = 'en-us',
|
|
88
|
-
geoloc = null
|
|
89
|
-
} = {}
|
|
90
|
-
) {
|
|
91
|
-
if (!prompt || typeof prompt !== 'string') {
|
|
92
|
-
throw new Error('Invalid prompt: must be a non-empty string');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (prompt.length > 5000) {
|
|
96
|
-
throw new Error('Invalid prompt: too long (maximum 5000 characters)');
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const symmetricKey = generateKeyB64();
|
|
100
|
-
const client = axios.create({
|
|
101
|
-
timeout,
|
|
102
|
-
headers: buildHeaders(),
|
|
103
|
-
validateStatus: (status) => status >= 200 && status < 500
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
const newParams = {
|
|
107
|
-
language,
|
|
108
|
-
country,
|
|
109
|
-
ui_lang: uiLang,
|
|
110
|
-
symmetric_key: symmetricKey,
|
|
111
|
-
source: enableResearch ? 'home' : 'llmSuggest',
|
|
112
|
-
query: prompt,
|
|
113
|
-
enable_research: enableResearch ? 'true' : 'false'
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
if (geoloc) {
|
|
117
|
-
newParams.geoloc = geoloc;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
try {
|
|
121
|
-
const newResponse = await client.get(`${BASE_URL}/new`, { params: newParams });
|
|
122
|
-
if (newResponse.status !== 200) {
|
|
123
|
-
throw new Error(`Brave AI failed to initialize chat: HTTP ${newResponse.status}`);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const chatId = newResponse.data?.id;
|
|
127
|
-
if (!chatId) {
|
|
128
|
-
throw new Error('Brave AI failed to initialize chat: missing conversation id');
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const streamParams = {
|
|
132
|
-
id: chatId,
|
|
133
|
-
query: prompt,
|
|
134
|
-
symmetric_key: symmetricKey,
|
|
135
|
-
language,
|
|
136
|
-
country,
|
|
137
|
-
ui_lang: uiLang,
|
|
138
|
-
enable_research: enableResearch ? 'true' : 'false',
|
|
139
|
-
enable_followups: enableResearch ? 'true' : 'false'
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const referer = `https://search.brave.com/ask?q=${encodeURIComponent(prompt)}&conversation=${chatId}`;
|
|
143
|
-
const streamResponse = await client.get(`${BASE_URL}/stream`, {
|
|
144
|
-
params: streamParams,
|
|
145
|
-
responseType: 'stream',
|
|
146
|
-
headers: {
|
|
147
|
-
referer
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
if (streamResponse.status !== 200) {
|
|
152
|
-
throw new Error(`Brave AI stream failed: HTTP ${streamResponse.status}`);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return await parseStream(streamResponse.data);
|
|
156
|
-
} catch (error) {
|
|
157
|
-
if (error.response?.status === 429) {
|
|
158
|
-
throw new Error('Brave AI rate limit: too many requests');
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (error.code === 'ECONNABORTED') {
|
|
162
|
-
throw new Error('Brave AI request timeout: took too long');
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
throw new Error(`Brave AI search failed for "${prompt}": ${error.message}`);
|
|
166
|
-
}
|
|
167
|
-
}
|