dsh-free-search 0.7.1 → 0.7.2

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/README.md CHANGED
@@ -71,9 +71,9 @@ dsh 默认的搜索 provider 依赖 DeepSeek 官方 API key(`DEEPSEEK_API_KEY`
71
71
  | `kimi` | Kimi(Moonshot)联网搜索 | 付费 | 需 `MOONSHOT_API_KEY`(basic 约 ¥0.01/次),返回带正文 chunks 的中文结果 |
72
72
  | `aliyun` | 阿里云百炼 EnhancedSearch | 付费 | 需 `DASHSCOPE_API_KEY`(MCP search_pro,约 ¥0.03/次,新用户 200 次免费包);中文全网带来源 hostname |
73
73
  | `doubao` | 火山引擎联网搜索(豆包搜索) | 免费额度 | 需 `DOUBAO_SEARCH_API_KEY`(联网搜索控制台开通,**每月 500 次免费**,超出按量付费);中文时效内容强,支持时间/站点过滤,返回千字级摘要 |
74
- | `openai` | OpenAI 模型内置搜索 | 付费 | 需 `OPENAI_API_KEY`;走 Responses API 的 `web_search` 工具,返回带引用的回答。**按次计费,仅在显式选中时使用**(不参与自动回退/auto);模型与端点可配(`openaiModel` / `openaiBaseUrl`) |
75
- | `gemini` | Gemini Grounding with Google Search | 付费 | 需 `GEMINI_API_KEY`(兼容 `GOOGLE_API_KEY`);返回 grounding 来源。**按次计费,仅在显式选中时使用**;模型与端点可配(`geminiModel` / `geminiBaseUrl`) |
76
- | `claude` | Claude 服务端 web_search | 付费 | 需 `ANTHROPIC_API_KEY`;结果来自 `web_search_tool_result`。**按次计费,仅在显式选中时使用**;模型与端点可配(`claudeModel` / `claudeBaseUrl`) |
74
+ | `openai` | OpenAI 模型内置搜索 | 付费 | 需 `OPENAI_API_KEY`;走 Responses API 的 `web_search` 工具,返回带引用的回答。**按次计费,仅在显式选中时使用**(不参与自动回退/auto);默认 `gpt-6-luna`,模型与端点可配(`openaiModel` / `openaiBaseUrl`) |
75
+ | `gemini` | Gemini Grounding with Google Search | 付费 | 需 `GEMINI_API_KEY`(兼容 `GOOGLE_API_KEY`);走官方推荐的 **Interactions API**(`POST /v1beta/interactions`,`tools:[{type:"google_search"}]`),从 `steps[].content[].annotations` 取引用。**按次计费,仅在显式选中时使用**;默认 `gemini-3.8-flash`,模型与端点可配(`geminiModel` / `geminiBaseUrl`) |
76
+ | `claude` | Claude 服务端 web_search | 付费 | 需 `ANTHROPIC_API_KEY`;结果来自 `web_search_tool_result`。**按次计费,仅在显式选中时使用**;默认 `claude-sonnet-5-5`,模型与端点可配(`claudeModel` / `claudeBaseUrl`) |
77
77
 
78
78
  - **默认引擎为 `bing`**(免费且最稳定),安装后开箱即用。
79
79
  - **自动回退**:任何引擎失败(免费限流/反爬,付费缺 key/无效/网络错误)都会自动轮流尝试下一个引擎——先试其他已配 key 的付费引擎,再试免费引擎(Bing/AnySearch 等),并在结果中附带回退提示——搜索不会因引擎问题直接失败。
@@ -419,9 +419,9 @@ If this plugin has been helpful, a ⭐ on [GitHub](https://github.com/DDDMUC/dsh
419
419
  | `kimi` | Kimi (Moonshot) Web Search | Paid | Requires `MOONSHOT_API_KEY` (basic ~¥0.01/call); returns Chinese results with body chunks |
420
420
  | `aliyun` | Aliyun Bailian EnhancedSearch | Paid | Requires `DASHSCOPE_API_KEY` (MCP search_pro, ~¥0.03/call, 200 free calls for new users); Chinese web-wide with source hostnames |
421
421
  | `doubao` | Volcano Web Search (Doubao) | Free quota | Requires `DOUBAO_SEARCH_API_KEY` (open it in the Web Search console; **500 free queries/month**, pay-as-you-go beyond); strong for fresh Chinese content, supports time/site filters, returns long summaries |
422
- | `openai` | OpenAI built-in web search | Paid | Requires `OPENAI_API_KEY`; uses the Responses API `web_search` tool and returns a cited answer. **Billed per search, explicit selection only** (never picked by the fallback chain or Auto); model/endpoint configurable (`openaiModel` / `openaiBaseUrl`) |
423
- | `gemini` | Gemini grounding with Google Search | Paid | Requires `GEMINI_API_KEY` (falls back to `GOOGLE_API_KEY`); returns grounding sources. **Billed per search, explicit selection only**; model/endpoint configurable (`geminiModel` / `geminiBaseUrl`) |
424
- | `claude` | Claude server-side web_search | Paid | Requires `ANTHROPIC_API_KEY`; results come from `web_search_tool_result`. **Billed per search, explicit selection only**; model/endpoint configurable (`claudeModel` / `claudeBaseUrl`) |
422
+ | `openai` | OpenAI built-in web search | Paid | Requires `OPENAI_API_KEY`; uses the Responses API `web_search` tool and returns a cited answer. **Billed per search, explicit selection only** (never picked by the fallback chain or Auto); default `gpt-6-luna`, model/endpoint configurable (`openaiModel` / `openaiBaseUrl`) |
423
+ | `gemini` | Gemini grounding with Google Search | Paid | Requires `GEMINI_API_KEY` (falls back to `GOOGLE_API_KEY`); uses the recommended **Interactions API** (`POST /v1beta/interactions`, `tools:[{type:"google_search"}]`) and reads citations from `steps[].content[].annotations`. **Billed per search, explicit selection only**; default `gemini-3.8-flash`, model/endpoint configurable (`geminiModel` / `geminiBaseUrl`) |
424
+ | `claude` | Claude server-side web_search | Paid | Requires `ANTHROPIC_API_KEY`; results come from `web_search_tool_result`. **Billed per search, explicit selection only**; default `claude-sonnet-5-5`, model/endpoint configurable (`claudeModel` / `claudeBaseUrl`) |
425
425
 
426
426
  - **Default engine is `bing`** (free and most stable), ready to use out of the box after installation.
427
427
  - **Auto-failover**: any engine failure (rate-limited free engine, or missing/invalid paid key, network error) automatically tries the next engine — the configured engine first, then other engines (exa/tavily/keenable/firecrawl/parallel are tried even without a key because they have built-in keyless quota), then the remaining free engines (Bing/AnySearch etc.) — with a note attached to the results naming the engine that actually served them (e.g. `Note: perplexity unavailable or failed, using exa.`). Search never fails outright because of engine issues.
package/lib/client.js CHANGED
@@ -312,7 +312,7 @@ window.__ModuleLoader__.load({
312
312
  };
313
313
  const tt = (lang) => I18N[lang === "en" ? "en" : "zh"];
314
314
  // 当前插件版本(与 lib/index.js 的 PLUGIN_VERSION 保持一致)
315
- const PLUGIN_VERSION = "0.7.1";
315
+ const PLUGIN_VERSION = "0.7.2";
316
316
  const ENGINES = [
317
317
  { id: "auto", label: "Auto · 智能路由", badge: "AUTO", link: null },
318
318
  { id: "multi", label: "Multi Search · 多引擎", badge: "MULTI", link: null },
package/lib/index.js CHANGED
@@ -191,7 +191,7 @@ function routeEngines(query, { timeRange, disabledEngines = [], fallbackOrder =
191
191
  }
192
192
 
193
193
  // 当前插件版本(发布时与 package.json 同步)
194
- const PLUGIN_VERSION = "0.7.1";
194
+ const PLUGIN_VERSION = "0.7.2";
195
195
  // 检查更新的 npm registry 元数据地址(dsh-free-search 是 npmjs 上的公开包)
196
196
  const NPM_REGISTRY_URL = "https://registry.npmjs.org/dsh-free-search/latest";
197
197
  const PLUGIN_NPM_URL = "https://www.npmjs.com/package/dsh-free-search";
@@ -1888,7 +1888,7 @@ async function searchAliyun(query, maxResults, apiKey, timeRange, signal) {
1888
1888
  // 因此只在用户显式选中该引擎时运行(见 EXPLICIT_ONLY_ENGINES)。模型名与 Base URL
1889
1889
  // 都可配置,便于走兼容网关。
1890
1890
 
1891
- const OPENAI_DEFAULT_MODEL = "gpt-4o-mini";
1891
+ const OPENAI_DEFAULT_MODEL = "gpt-6-luna";
1892
1892
  const OPENAI_DEFAULT_BASE_URL = "https://api.openai.com/v1";
1893
1893
 
1894
1894
  function openaiResponsesUrl(baseUrl) {
@@ -1954,18 +1954,18 @@ async function searchOpenai(query, maxResults, apiKey, options, signal) {
1954
1954
  return { sources: uniqueSources(sources, maxResults ?? 10), truncated: false };
1955
1955
  }
1956
1956
 
1957
- const GEMINI_DEFAULT_MODEL = "gemini-2.0-flash";
1957
+ const GEMINI_DEFAULT_MODEL = "gemini-3.8-flash";
1958
1958
  const GEMINI_DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
1959
1959
 
1960
- function geminiGenerateUrl(baseUrl, model) {
1960
+ function geminiInteractionsUrl(baseUrl) {
1961
1961
  const base = (typeof baseUrl === "string" && baseUrl.trim() ? baseUrl.trim() : GEMINI_DEFAULT_BASE_URL).replace(/\/+$/, "");
1962
- return base.endsWith(":generateContent") ? base : base + "/models/" + encodeURIComponent(model) + ":generateContent";
1962
+ return base.endsWith("/interactions") ? base : base + "/interactions";
1963
1963
  }
1964
1964
 
1965
1965
  async function searchGemini(query, maxResults, apiKey, options, signal) {
1966
1966
  if (!apiKey) throw new Error("Gemini search requires GEMINI_API_KEY");
1967
1967
  const model = (typeof options?.geminiModel === "string" && options.geminiModel.trim()) || GEMINI_DEFAULT_MODEL;
1968
- const response = await fetch(geminiGenerateUrl(options?.geminiBaseUrl, model), {
1968
+ const response = await fetch(geminiInteractionsUrl(options?.geminiBaseUrl), {
1969
1969
  method: "POST",
1970
1970
  redirect: "error",
1971
1971
  headers: {
@@ -1974,8 +1974,9 @@ async function searchGemini(query, maxResults, apiKey, options, signal) {
1974
1974
  "user-agent": "deepseek-harness/free-search",
1975
1975
  },
1976
1976
  body: JSON.stringify({
1977
- contents: [{ role: "user", parts: [{ text: String(query) }] }],
1978
- tools: [{ google_search: {} }],
1977
+ model,
1978
+ input: String(query),
1979
+ tools: [{ type: "google_search" }],
1979
1980
  }),
1980
1981
  signal: AbortSignal.any([...(signal !== undefined ? [signal] : []), AbortSignal.timeout(45000)]),
1981
1982
  });
@@ -1993,26 +1994,30 @@ async function searchGemini(query, maxResults, apiKey, options, signal) {
1993
1994
  if (data?.error) {
1994
1995
  throw new Error("Gemini response failed: " + (data.error.message ?? JSON.stringify(data.error).slice(0, 200)));
1995
1996
  }
1996
- const candidate = data?.candidates?.[0];
1997
- const answer = (candidate?.content?.parts ?? [])
1998
- .map((part) => (typeof part?.text === "string" ? part.text : ""))
1999
- .filter(Boolean)
2000
- .join("\n");
2001
- // groundingChunks[].web = { uri, title } 就是搜索命中的来源
1997
+ // Interactions API:结果在 steps[] 里;model_output 步骤的 text 块带 url_citation 注解
1998
+ const steps = Array.isArray(data?.steps) ? data.steps : [];
1999
+ let answer = typeof data?.output_text === "string" ? data.output_text : "";
2002
2000
  const sources = [];
2003
- for (const chunk of candidate?.groundingMetadata?.groundingChunks ?? []) {
2004
- const web = chunk?.web;
2005
- if (!web || typeof web.uri !== "string" || !web.uri) continue;
2006
- sources.push({
2007
- url: web.uri,
2008
- ...(web.title ? { title: String(web.title) } : {}),
2009
- ...(answer.trim() ? { snippet: stripTags(answer).slice(0, 300) } : {}),
2010
- });
2001
+ for (const step of steps) {
2002
+ if (!step || step.type !== "model_output" || !Array.isArray(step.content)) continue;
2003
+ for (const block of step.content) {
2004
+ if (!block || block.type !== "text") continue;
2005
+ const text = typeof block.text === "string" ? block.text : "";
2006
+ if (!answer && text.trim()) answer = text;
2007
+ for (const annotation of block.annotations ?? []) {
2008
+ if (annotation?.type !== "url_citation" || typeof annotation.url !== "string" || !annotation.url) continue;
2009
+ sources.push({
2010
+ url: annotation.url,
2011
+ ...(annotation.title ? { title: String(annotation.title) } : {}),
2012
+ ...(text.trim() ? { snippet: stripTags(text).slice(0, 300) } : {}),
2013
+ });
2014
+ }
2015
+ }
2011
2016
  }
2012
2017
  return { sources: uniqueSources(sources, maxResults ?? 10), truncated: false };
2013
2018
  }
2014
2019
 
2015
- const CLAUDE_DEFAULT_MODEL = "claude-3-5-sonnet-latest";
2020
+ const CLAUDE_DEFAULT_MODEL = "claude-sonnet-5-5";
2016
2021
  const CLAUDE_DEFAULT_BASE_URL = "https://api.anthropic.com/v1";
2017
2022
  const CLAUDE_DEFAULT_VERSION = "2023-06-01";
2018
2023
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-free-search",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "Free web search for DeepSeek Harness: 21 engines (Bing/DuckDuckGo/AnySearch/SearXNG/Exa/Tavily/Keenable/Firecrawl/Parallel keyless; Perplexity/SerpBase/DeepSeek/You.com/Baidu/Kimi/Aliyun/Doubao with key; OpenAI/Gemini/Claude model built-in search, explicit-only) + auto smart routing + global engine enable/disable & fallback priority + multi mode (parallel top-3 merge) + multi_search cross-source tool + failure-aware fallback (quota/auth cooldown, transient retry, fallbackOn) + time filtering + platform search + web_fetch, with web settings UI.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",