Skip to content

feat: add Bing free web search provider#172

Merged
ZhouChaunge merged 10 commits into
mainfrom
feat/free-websearch
May 28, 2026
Merged

feat: add Bing free web search provider#172
ZhouChaunge merged 10 commits into
mainfrom
feat/free-websearch

Conversation

@YSMsimon
Copy link
Copy Markdown
Collaborator

变更说明 / Summary

新增 Bing 免费网络搜索后端,无需 API Key 开箱即用。用户可在设置面板的 Web Search 区域选择搜索提供商:Tavily(需要 Key,质量更高)或 Bing(无需 Key)。Bing 使用 RSS 端点(?format=rss)而非 HTML 抓取,规避了 bot 检测问题,结果稳定可靠。原有 Tavily 功能完全保留,无破坏性变更。

变更类型 / Type of change

  • Bug 修复
  • [x ] 新功能
  • 重构 / 性能优化
  • 文档 / 注释
  • CI / 构建脚本
  • 其他:

影响范围 / Affected areas

  • src/api/**(DeepSeek 接入)
  • src/chat/**(对话核心)
  • src/tools/**(工具执行,安全敏感)
  • [x ] src/webview/**(UI)
  • src/prompts/**
  • [x ] package.json / 依赖
  • CI / .github/**
  • 其他:

自检 / Checklist

  • [x ] 本地手动跑过受影响功能
  • [x ] 没有引入新的安全风险(密钥、命令注入、路径穿越)
  • [x ] 没有写入个人 / 测试用 API key
  • [x ] 修改的 webview 资源遵守了 CSP
  • 必要时已更新 README / Release Notes

截图 / 录屏(可选)

关联 Issue

Closes #129

@YSMsimon YSMsimon requested a review from ZhouChaunge as a code owner May 27, 2026 03:26
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review by ChatGPT

Comment thread media/chat.js
Comment thread media/chat.js
Comment thread media/chat.js
Comment thread media/chat.js
Comment thread package.json
Comment thread src/chat/provider.js
Comment thread src/tools/web-search.js
Comment thread src/tools/web-search.js
Comment thread src/tools/web-search.js
Comment thread src/webview/html.js
Comment thread src/tools/web-search.js Fixed
…nescaping'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review by ChatGPT

Comment thread src/tools/web-search.js
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review by ChatGPT

Comment thread src/chat/provider.js
@YSMsimon
Copy link
Copy Markdown
Collaborator Author

@ZhouChaunge 代码已测试正常,可以合并

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new “Bing (no API key)” web search backend and exposes a Web Search provider selector in the settings modal, so users can use web search out of the box without configuring Tavily.

Changes:

  • Add deepseekAgent.webSearchProvider setting (Tavily/Bing) and persist it from the webview settings modal.
  • Implement a Bing-backed search path in web_search (via Bing RSS endpoint) alongside the existing Tavily path.
  • Update the settings UI to show/hide Tavily key fields based on the selected web search provider.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/webview/html.js Adds Web Search provider selector and wraps Tavily-only fields in a toggleable section.
src/tools/web-search.js Refactors web_search into a dispatcher and adds Bing RSS search implementation.
src/chat/provider.js Loads/saves webSearchProvider between extension config and webview.
package.json Contributes deepseekAgent.webSearchProvider setting to VS Code.
media/chat.js Wires settings modal UI state to include web search provider selection and hides Tavily section when not needed.

Comment thread src/tools/web-search.js Outdated
Comment on lines +4 to +8
// - Bing (no API key required, HTML scrape via DuckDuckGo-lite endpoint)
// - DuckDuckGo (no API key required, HTML scrape)
//
// Active backend is determined by the 'deepseekAgent.webSearchProvider' setting.
// Falls back to Tavily when configured.
Comment thread src/tools/web-search.js Outdated
Comment on lines +42 to +45
async function _tavilySearch(query, { apiKey, max = 5, depth = 'basic', abortSignal } = {}) {
const body = JSON.stringify({
api_key: apiKey, query, max_results: max,
search_depth: depth, include_answer: true,
Comment thread src/tools/web-search.js Outdated
const apiKey = await secrets.get('deepseekAgent.tavilyKey');
if (!apiKey) {
return 'Error: Tavily API key not configured. Run command "Deep Copilot: Set Tavily API Key" (or visit https://app.tavily.com to get a free key), then retry.';
return 'Error: Tavily API key not configured. Run command "Deep Copilot: Set Tavily API Key" or switch to Bing/DuckDuckGo in settings (no key required).';
Comment thread package.json Outdated
Comment on lines +282 to +284
"Bing — no API key required, HTML scrape"
],
"description": "Web search backend. Bing and DuckDuckGo require no API key and work out of the box."
Comment thread media/chat.js Outdated
Comment on lines +2310 to +2314
function _stgUpdateWsProvider(){
var v = stgWsProvider ? stgWsProvider.value : 'tavily';
if (stgTvSection) stgTvSection.style.display = v === 'tavily' ? '' : 'none';
}
if (stgWsProvider) stgWsProvider.addEventListener('change', _stgUpdateWsProvider);
- web-search.js: update header comment to match actual backends (Tavily + Bing RSS)

- web-search.js: wire args.include_answer through to Tavily payload (default true)

- web-search.js: drop DuckDuckGo from missing-key error hint

- package.json: fix webSearchProvider enumDescriptions/description (Bing uses RSS, not HTML scrape)

- chat.js: track webSearchProvider in settings dirty detection and restore on Discard
@ZhouChaunge ZhouChaunge requested a review from Copilot May 28, 2026 16:16
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review by ChatGPT

Comment thread media/chat.js
var _stgOrigWsProvider = 'tavily';
var _stgTestTimers = {};

var stgOverlay = document.getElementById('settings-overlay');
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

新增的变量 _stgOrigWsProvider 需要确保其值的来源是安全的,避免潜在的密钥泄露或不当信息暴露。建议对其进行验证,确保不会引入安全漏洞。

Comment thread media/chat.js
(stgWsProvider && stgWsProvider.value !== _stgOrigWsProvider);
}
function _stgUpdateDirtyBar(){
if (stgDirtyBar) stgDirtyBar.style.display = _stgIsDirty() ? 'flex' : 'none';
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在条件判断中新增了对 stgWsProvider 的检查,确保其存在性是必要的,但需要注意该变量的值是否经过验证,避免引入不安全的输入。

Comment thread media/chat.js
});

function openSettingsModal(){
if (_stgOpen) return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在事件监听器中调用了两个函数,建议在调用 _stgUpdateWsProvider_stgUpdateDirtyBar 之前进行空值检查,以防止潜在的空指针异常。

Comment thread media/chat.js
_stgOrigWsProvider = 'tavily';
_stgDsKeySet = false; _stgTvKeySet = false;
if (stgDirtyBar) stgDirtyBar.style.display = 'none';
_stgSetResult(stgDsResult, 'ds', '', '');
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在重置设置时,确保 _stgOrigWsProvider 的值被正确重置,避免在后续使用中出现不一致的状态。

Comment thread media/chat.js
}
closeSettingsModal(true);
});
stgDsKey && stgDsKey.addEventListener('input', _stgUpdateDirtyBar);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在设置 stgWsProvider 的值时,建议在赋值前进行非空检查,并确保 _stgOrigWsProvider 的值是有效的,避免引入未处理的异常。

Comment thread package.json
"description": "Web search backend. Tavily requires an API key; Bing works out of the box without an API key."
},
"deepseekAgent.postEditDiagnostics": {
"type": "boolean",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在修改描述时,建议明确指出 Bing RSS 的使用方式和潜在的安全性问题。虽然不需要 API 密钥,但 HTML 抓取可能会引入安全漏洞,如 XSS 攻击。此外,建议在描述中提及对用户数据的处理方式,以确保符合隐私保护标准。

Comment thread src/tools/web-search.js
}
return truncate(lines.join('\n'));
} catch (e) { return `Error: ${e.message || String(e)}`; }
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在处理 secrets 时,如果 secretsnullundefined,直接返回错误信息是合理的,但在获取 apiKey 后未对其进行有效性检查。如果 apiKey 是一个空字符串,也会导致后续请求失败。建议在获取 apiKey 后增加对其有效性的检查,确保其不为空。

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 3 comments.

Comment thread src/tools/web-search.js Outdated
Comment on lines +20 to +24
const req = mod.request(opts, (res) => {
let chunks = '';
res.setEncoding('utf8');
res.on('data', (c) => { chunks += c; });
res.on('end', () => {
if (res.statusCode < 200 || res.statusCode >= 300)
return reject(new Error(`Tavily HTTP ${res.statusCode}: ${chunks.slice(0, 500)}`));
try { resolve(JSON.parse(chunks)); }
catch (e) { reject(new Error(`Tavily JSON parse failed: ${e.message}`)); }
});
res.on('end', () => resolve({ status: res.statusCode, body: chunks }));
Comment thread src/tools/web-search.js Outdated
if (res.status < 200 || res.status >= 300)
throw new Error(`Tavily HTTP ${res.status}: ${res.body.slice(0, 200)}`);

const data = JSON.parse(res.body);
Comment thread src/tools/web-search.js
Comment on lines 18 to +20
if (abortSignal && abortSignal.aborted) return reject(new Error('aborted'));
const body = JSON.stringify(payload);
const req = https.request({
method: 'POST',
hostname: 'api.tavily.com',
path: '/search',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(body),
},
timeout: timeoutMs,
}, (res) => {
const mod = opts.protocol === 'http:' ? http : https;
const req = mod.request(opts, (res) => {
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 3 comments.

Comment thread src/tools/web-search.js Outdated

const data = JSON.parse(res.body);
const lines = [`Query: ${query}`];
if (data.answer) lines.push('', '## Synthesized answer', data.answer);
Comment thread src/tools/web-search.js Outdated
Comment on lines +77 to +82
return String(s || '')
.replace(/&lt;/g, '<').replace(/&gt;/g, '>')
.replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&apos;/g, "'")
.replace(/&#(\d+);/g, (_, n) => String.fromCharCode(Number(n)))
.replace(/&amp;/g, '&')
.replace(/<[^>]+>/g, ' ').replace(/\s{2,}/g, ' ').trim();
Comment thread src/chat/provider.js Outdated
const tvKey = await this._context.secrets.get('deepseekAgent.tavilyKey') || '';
const baseUrl = cfg.get('apiBaseUrl') || '';
const provider = cfg.get('provider') || 'deepseek';
const wsProvider = cfg.get('webSearchProvider') || 'tavily';
- provider.js: sanitize webSearchProvider before sending settingsLoaded to webview

- web-search.js: gate Tavily 'synthesized answer' on include_answer flag (fix behavior regression)

- web-search.js: enforce 2 MiB response body cap in _request to bound memory usage

- web-search.js: drop unreachable http branch (all endpoints are https)

- web-search.js: wrap JSON.parse with descriptive error + body preview

- web-search.js: decode &amp; first (with fixed-point loop) and support hex numeric entities in Bing RSS
@ZhouChaunge ZhouChaunge requested a review from Copilot May 28, 2026 16:28
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review by ChatGPT

Comment thread src/chat/provider.js
tvKeyHint: maskKey(tvKey),
baseUrl: baseUrl,
provider: provider,
type: 'settingsLoaded',
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在这一段代码中,rawWsProvider 直接从配置中获取,而没有进行任何验证或清洗。虽然后续使用了 includes 方法来检查其有效性,但如果配置中存在恶意输入,可能会导致安全漏洞。此外,建议在获取敏感信息(如 deepseekAgent.tavilyKey)时,确保其存在性和有效性,以防止空指针异常。

Comment thread src/tools/web-search.js
if (include_answer && data.answer) lines.push('', '## Synthesized answer', data.answer);
const results = Array.isArray(data.results) ? data.results : [];
if (!results.length) {
lines.push('', '(No results.)');
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在解析 JSON 响应时,增加了异常处理,这是一个好的改动,可以防止非 JSON 响应导致的崩溃。然而,建议在捕获异常时,除了抛出错误外,还可以考虑记录日志,以便后续调试和监控。此外,include_answer 变量的使用需要确保在函数参数中定义并传递,否则可能导致未定义错误。

Comment thread src/tools/web-search.js
return out.replace(/\s{2,}/g, ' ').trim();
}

function _parseBingRss(xml) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_decodeXmlEntities 函数中,增加了对 HTML 标签的处理,这是一个良好的改进,可以防止潜在的 XSS 攻击。然而,建议在处理 XML 实体时,确保输入字符串是安全的,避免潜在的命令注入风险。此外,建议在替换过程中添加更多的注释,以提高代码的可读性和可维护性。

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 3 comments.

Comment thread src/tools/web-search.js Outdated
Comment on lines 20 to 23
const req = mod.request(opts, (res) => {
let chunks = '';
res.setEncoding('utf8');
res.on('data', (c) => { chunks += c; });
Comment thread src/tools/web-search.js Outdated
Comment on lines +41 to +45
async function _tavilySearch(query, { apiKey, max = 5, depth = 'basic', include_answer = true, abortSignal } = {}) {
const body = JSON.stringify({
api_key: apiKey, query, max_results: max,
search_depth: depth, include_answer,
include_raw_content: false, include_images: false,
Comment thread src/webview/html.js
Comment on lines +196 to +202
<div class="settings-section-label">Web Search</div>
<div class="settings-field">
<label class="settings-label" for="s-tv-key">API Key <span class="settings-optional">(optional)</span></label>
<div class="settings-input-row">
<input type="password" id="s-tv-key" class="settings-input" placeholder="tvly-..." autocomplete="off" spellcheck="false"/>
<button class="settings-eye-btn" id="s-tv-key-eye" title="Show / hide" aria-label="Toggle key visibility">👁</button>
</div>
<div class="settings-test-row">
<button class="settings-test-btn" id="s-tv-test">▶ Test connection</button>
<span class="settings-test-result" id="s-tv-result"></span>
<label class="settings-label" for="s-ws-provider">Provider</label>
<select id="s-ws-provider" class="settings-input settings-select">
<option value="tavily">Tavily (needs API key, best quality)</option>
<option value="bing">Bing (no API key required)</option>
</select>
- web-search.js: add res.on('error', reject) in _request to avoid unhandled
  response-stream errors crashing the extension host (HIGH)

- web-search.js: rename local include_answer -> includeAnswer for camelCase
  consistency; only the Tavily API payload key stays snake_case
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review by ChatGPT

Comment thread src/tools/web-search.js
search_depth: depth, include_answer: includeAnswer,
include_raw_content: false, include_images: false,
});
const res = await _request({
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在函数参数中将 include_answer 改为 includeAnswer 是一个好的改动,但需要确保所有调用该函数的地方都进行了相应的更新,以避免潜在的未定义行为。

Comment thread src/tools/web-search.js
if (includeAnswer && data.answer) lines.push('', '## Synthesized answer', data.answer);
const results = Array.isArray(data.results) ? data.results : [];
if (!results.length) {
lines.push('', '(No results.)');
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同样,include_answer 的修改为 includeAnswer 需要确保在整个代码库中保持一致性,避免出现因变量名不一致导致的错误。

Comment thread src/tools/web-search.js
}
return truncate(lines.join('\n'));
} catch (e) { return `Error: ${e.message || String(e)}`; }
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在这里,include_answer 的逻辑修改为 includeAnswer 是合理的,但需要确保在调用 _tavilySearch 的地方也进行了相应的更新,以避免潜在的错误。

@ZhouChaunge ZhouChaunge requested a review from Copilot May 28, 2026 16:37
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.

Comment thread src/tools/web-search.js
Comment on lines +93 to +98
out = out
.replace(/&lt;/g, '<').replace(/&gt;/g, '>')
.replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&apos;/g, "'")
.replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCodePoint(parseInt(h, 16)))
.replace(/&#(\d+);/g, (_, n) => String.fromCodePoint(Number(n)));
return out.replace(/\s{2,}/g, ' ').trim();
- web-search.js: guard _decodeXmlEntities() numeric-entity decoding against
  RangeError from String.fromCodePoint. Invalid (NaN / negative / >0x10FFFF /
  surrogate-half) code points are now dropped instead of throwing, so a single
  malformed entity in remote Bing RSS can no longer kill the whole search.
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review by ChatGPT

Comment thread src/tools/web-search.js
.replace(/&#(\d+);/g, (_, n) => fromCp(Number(n)));
return out.replace(/\s{2,}/g, ' ').trim();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在这个代码段中,增加了对数值代码点的安全转换,防止了不合法的代码点导致的错误,这是一个积极的改动。然而,catch 语句没有指定异常变量,虽然在这个上下文中可能不会影响功能,但为了代码的可读性和维护性,建议明确捕获异常并进行处理。此外,fromCp 函数的逻辑在处理超出范围的值时是合理的,但在实际应用中,建议增加日志记录,以便在出现不合法实体时能够追踪问题。

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 3 comments.

Comment thread src/tools/web-search.js
Comment on lines +16 to 29
function _request(opts, body, timeoutMs = 20000, abortSignal = null) {
return new Promise((resolve, reject) => {
if (abortSignal && abortSignal.aborted) return reject(new Error('aborted'));
const body = JSON.stringify(payload);
const req = https.request({
method: 'POST',
hostname: 'api.tavily.com',
path: '/search',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(body),
},
timeout: timeoutMs,
}, (res) => {
const mod = opts.protocol === 'http:' ? http : https;
const req = mod.request(opts, (res) => {
// Without this, an error on the response stream (e.g. socket reset
// mid-body) would surface as an unhandled 'error' event and could
// crash the extension host process.
res.on('error', reject);
let chunks = '';
res.setEncoding('utf8');
res.on('data', (c) => { chunks += c; });
res.on('end', () => {
if (res.statusCode < 200 || res.statusCode >= 300)
return reject(new Error(`Tavily HTTP ${res.statusCode}: ${chunks.slice(0, 500)}`));
try { resolve(JSON.parse(chunks)); }
catch (e) { reject(new Error(`Tavily JSON parse failed: ${e.message}`)); }
});
res.on('end', () => resolve({ status: res.statusCode, body: chunks }));
});
Comment thread src/tools/web-search.js
Comment on lines +68 to +72
lines.push('', `## Top ${results.length} result(s)`);
results.forEach((r, i) => {
lines.push('', `### ${i + 1}. ${(r.title || '').replace(/\s+/g, ' ').trim()}`);
if (r.url) lines.push(r.url);
if (r.content) lines.push(r.content.replace(/\s+/g, ' ').trim());
Comment thread src/tools/web-search.js
Comment on lines +1 to +7
// web_search: multi-backend web search.
// Backends:
// - Tavily (requires API key, best quality)
// - Bing (no API key required, uses Bing RSS endpoint)
//
// Active backend is determined by the 'deepseekAgent.webSearchProvider' setting.
// Supported providers in this module are Tavily and Bing.
- web-search.js: enforce 2 MiB cap on _request() response body; abort and
  reject if exceeded so an oversized external response can't blow up the
  extension host memory.

- web-search.js: fall back to URL hostname (or "(no title)") for Tavily results
  with empty/missing title, so we never emit a bare "### 1. " heading.

- schema.js: update web_search tool description to document multi-backend
  behavior (Tavily + Bing) and that no key is required when the user picks
  Bing, so the model doesn't avoid the tool when no Tavily key is configured.
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review by ChatGPT

Comment thread src/tools/schema.js
description: 'Search the live web for up-to-date information. Use ONLY when the user asks about recent events, current versions, latest documentation, news, or facts that may have changed after your training data. Do NOT use for code that lives in the workspace (use grep_search/read_file). Returns a list of {title, url, content} snippets and (Tavily only) an optional synthesized answer, in Markdown. Backend is chosen by the user via the "deepseekAgent.webSearchProvider" setting: "tavily" (default, requires API key set via command "Deep Copilot: Set Tavily API Key") or "bing" (no key required, uses Bing RSS). Safe to call even when no Tavily key is configured — the user can switch to Bing in settings.',
parameters: {
type: 'object',
properties: {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在描述中添加了关于使用 Bing 的信息,但没有明确说明在没有 Tavily API 密钥的情况下,如何确保安全性。建议在描述中强调即使没有配置 Tavily 密钥,调用该功能也不会导致安全漏洞。此外,建议对用户在切换搜索提供者时的潜在风险进行说明,以避免误用。

Comment thread src/tools/web-search.js
});
req.on('error', reject);
req.on('timeout', () => { req.destroy(new Error('Tavily request timeout')); });
req.on('timeout', () => { req.destroy(new Error('Request timeout')); });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在处理响应数据时,增加了对响应体大小的限制,这样可以有效防止内存溢出的问题。建议在 reject 的时候,提供更详细的错误信息,以便于后续的调试和日志记录。此外,req.destroy() 的调用应该在 catch 中处理异常,以确保不会因为异常导致未捕获的错误。

Comment thread src/tools/web-search.js
lines.push('', `### ${i + 1}. ${title}`);
if (r.url) lines.push(r.url);
if (r.content) lines.push(r.content.replace(/\s+/g, ' ').trim());
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在处理标题时,增加了对空标题的处理逻辑,这样可以避免因标题为空而导致的潜在问题。建议在捕获异常时,明确指定捕获的异常类型,以提高代码的可读性和可维护性。

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 7 changed files in this pull request and generated 1 comment.

Comment thread src/tools/schema.js Outdated
function: {
name: 'web_search',
description: 'Search the live web (Tavily) for up-to-date information. Use ONLY when the user asks about recent events, current versions, latest documentation, news, or facts that may have changed after your training data. Do NOT use for code that lives in the workspace (use grep_search/read_file). Returns a list of {title, url, content} snippets and an optional synthesized answer. Results are returned in Markdown format. Requires the user to have configured a Tavily API key (command: "Deep Copilot: Set Tavily API Key").',
description: 'Search the live web for up-to-date information. Use ONLY when the user asks about recent events, current versions, latest documentation, news, or facts that may have changed after your training data. Do NOT use for code that lives in the workspace (use grep_search/read_file). Returns a list of {title, url, content} snippets and (Tavily only) an optional synthesized answer, in Markdown. Backend is chosen by the user via the "deepseekAgent.webSearchProvider" setting: "tavily" (default, requires API key set via command "Deep Copilot: Set Tavily API Key") or "bing" (no key required, uses Bing RSS). Safe to call even when no Tavily key is configured — the user can switch to Bing in settings.',
- schema.js: clarify web_search return shape. The tool returns a single
  Markdown-formatted text report (query + numbered title/URL/snippet list,
  optionally preceded by a "## Synthesized answer" section for Tavily), not
  a JSON array of {title, url, content} objects. The old wording could
  mislead tool-calling agents into parsing fields that don't exist.
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review by ChatGPT

Comment thread src/tools/schema.js
description: 'Search the live web for up-to-date information. Use ONLY when the user asks about recent events, current versions, latest documentation, news, or facts that may have changed after your training data. Do NOT use for code that lives in the workspace (use grep_search/read_file). Returns a single Markdown-formatted text report (the query line, then a numbered list of results with title, URL and a content/snippet excerpt; with Tavily, an optional "## Synthesized answer" section may precede the results). It is NOT a JSON array of objects. Backend is chosen by the user via the "deepseekAgent.webSearchProvider" setting: "tavily" (default, requires API key set via command "Deep Copilot: Set Tavily API Key") or "bing" (no key required, uses Bing RSS). Safe to call even when no Tavily key is configured — the user can switch to Bing in settings.',
parameters: {
type: 'object',
properties: {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在描述中,原文提到返回的是一个 JSON 数组的对象,而修改后的描述则表明返回的是一个 Markdown 格式的文本报告。这可能会导致使用该函数的开发者产生误解,尤其是在处理返回值时。建议确保描述与实际返回值一致,以避免潜在的解析错误。此外,确保在描述中没有遗漏对安全性和使用限制的说明,特别是关于 API 密钥的使用和配置。

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 7 changed files in this pull request and generated 1 comment.

Comment thread src/tools/schema.js
function: {
name: 'web_search',
description: 'Search the live web (Tavily) for up-to-date information. Use ONLY when the user asks about recent events, current versions, latest documentation, news, or facts that may have changed after your training data. Do NOT use for code that lives in the workspace (use grep_search/read_file). Returns a list of {title, url, content} snippets and an optional synthesized answer. Results are returned in Markdown format. Requires the user to have configured a Tavily API key (command: "Deep Copilot: Set Tavily API Key").',
description: 'Search the live web for up-to-date information. Use ONLY when the user asks about recent events, current versions, latest documentation, news, or facts that may have changed after your training data. Do NOT use for code that lives in the workspace (use grep_search/read_file). Returns a single Markdown-formatted text report (the query line, then a numbered list of results with title, URL and a content/snippet excerpt; with Tavily, an optional "## Synthesized answer" section may precede the results). It is NOT a JSON array of objects. Backend is chosen by the user via the "deepseekAgent.webSearchProvider" setting: "tavily" (default, requires API key set via command "Deep Copilot: Set Tavily API Key") or "bing" (no key required, uses Bing RSS). Safe to call even when no Tavily key is configured — the user can switch to Bing in settings.',
@ZhouChaunge ZhouChaunge merged commit a1c9b7a into main May 28, 2026
18 checks passed
@ZhouChaunge ZhouChaunge deleted the feat/free-websearch branch May 28, 2026 17:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 添加免 API Key 的 Web Search 支持

4 participants