deepseeker-code 1.0.57 → 1.0.58

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.
Files changed (2) hide show
  1. package/dist/cli.mjs +153 -22
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -539,7 +539,7 @@ var init_config = __esm({
539
539
  * - 'allow':照常明文备份(隐私差,谨慎使用)。
540
540
  */
541
541
  undoBackupSensitive: "skip",
542
- /** web_search 搜索后端:环境变量 SEARCH_PROVIDER 可强制 "tavily"|"bing"|"ddg";不设则自动——有 TAVILY_API_KEY 用 Tavily,否则用 Bing(免注册,中国/全球可达)。 */
542
+ /** web_search 搜索后端:环境变量 SEARCH_PROVIDER 可强制 "deepseek"|"tavily"|"bing"|"ddg";不设则自动——有 DEEP_SEEK_API_KEY 用 DeepSeek 自家搜索(¥0.005/次进结算单),否则有 TAVILY_API_KEY 用 Tavily,否则用 Bing(免注册,中国/全球可达)。 */
543
543
  searchProvider: process.env.SEARCH_PROVIDER || "",
544
544
  /** Tavily 搜索 API 密钥(可选升级后端)。从环境变量 TAVILY_API_KEY 读取;未配置时 web_search 自动回退到免注册的 DuckDuckGo。 */
545
545
  tavilyApiKey: process.env.TAVILY_API_KEY || "",
@@ -1132,10 +1132,10 @@ var init_batchStrings = __esm({
1132
1132
  const money = c.costYuan !== null ? `\xA5${c.costYuan.toFixed(4)}` : "\uFF08\u65E0\u4EF7\u6A21\u578B\uFF0C\u4E0D\u8BA1\u94B1\uFF09";
1133
1133
  const hit = c.promptTokens > 0 ? Math.round(c.cachedTokens / c.promptTokens * 100) : 0;
1134
1134
  const mins = Math.round(c.durationMs / 6e3) / 10;
1135
- return `${c.rounds} \u8F6E \xB7 ${money} \xB7 \u7F13\u5B58\u547D\u4E2D ${hit}% \xB7 ${mins}min${c.partialPrice ? "\uFF08\u90E8\u5206\u8BB0\u5F55\u65E0\u4EF7\uFF0C\u94B1\u4E3A\u90E8\u5206\u548C\uFF09" : ""}${c.subRuns > 0 ? ` \xB7 \u5B50 run ${c.subRuns} \u4E2A\u5DF2\u8BA1\u5165` : ""}`;
1135
+ return `${c.rounds} \u8F6E \xB7 ${money} \xB7 \u7F13\u5B58\u547D\u4E2D ${hit}% \xB7 ${mins}min${c.partialPrice ? "\uFF08\u90E8\u5206\u8BB0\u5F55\u65E0\u4EF7\uFF0C\u94B1\u4E3A\u90E8\u5206\u548C\uFF09" : ""}${c.subRuns > 0 ? ` \xB7 \u5B50 run ${c.subRuns} \u4E2A\u5DF2\u8BA1\u5165` : ""}${c.toolCostYuan > 0 ? ` \xB7 \u8054\u7F51 \xA5${c.toolCostYuan.toFixed(4)}` : ""}`;
1136
1136
  },
1137
1137
  noReceipt: "\uFF08\u65E0\u7ED3\u7B97\u5355\u2014\u2014run \u672A\u5230\u6536\u5C3E\u5373\u88AB\u622A\u65AD\uFF0C\u82B1\u8D39\u4EE5\u4E0A\u65B9 usageLog \u805A\u5408\u4E3A\u51C6\uFF09",
1138
- receiptLine: (r) => `${r.rounds} \u8F6E \xB7 ${r.costYuan !== null ? `\xA5${r.costYuan.toFixed(4)}` : "\u65E0\u4EF7"} \xB7 \u7F13\u5B58\u547D\u4E2D ${Math.round(r.cacheHitRate * 100)}% \xB7 ${(r.durationMs / 1e3).toFixed(0)}s`,
1138
+ receiptLine: (r) => `${r.rounds} \u8F6E \xB7 ${r.costYuan !== null ? `\xA5${r.costYuan.toFixed(4)}` : "\u65E0\u4EF7"} \xB7 \u7F13\u5B58\u547D\u4E2D ${Math.round(r.cacheHitRate * 100)}% \xB7 ${(r.durationMs / 1e3).toFixed(0)}s${r.toolCostYuan ? ` \xB7 \u8054\u7F51 \xA5${r.toolCostYuan.toFixed(4)}` : ""}`,
1139
1139
  // —— 报告骨架 ——
1140
1140
  rejectedTitle: "# \u6302\u673A\u4EFB\u52A1\u88AB\u62D2",
1141
1141
  reportTitle: (title) => `# \u6302\u673A\u4EFB\u52A1\u62A5\u544A\uFF1A${title}`,
@@ -1232,10 +1232,10 @@ changes are harvested onto an isolated git worktree (temp branch), kept for manu
1232
1232
  const money = c.costYuan !== null ? `\xA5${c.costYuan.toFixed(4)}` : "(unpriced model, no money counted)";
1233
1233
  const hit = c.promptTokens > 0 ? Math.round(c.cachedTokens / c.promptTokens * 100) : 0;
1234
1234
  const mins = Math.round(c.durationMs / 6e3) / 10;
1235
- return `${c.rounds} rounds \xB7 ${money} \xB7 cache hit ${hit}% \xB7 ${mins}min${c.partialPrice ? " (some records unpriced; money is a partial sum)" : ""}${c.subRuns > 0 ? ` \xB7 ${c.subRuns} sub-runs included` : ""}`;
1235
+ return `${c.rounds} rounds \xB7 ${money} \xB7 cache hit ${hit}% \xB7 ${mins}min${c.partialPrice ? " (some records unpriced; money is a partial sum)" : ""}${c.subRuns > 0 ? ` \xB7 ${c.subRuns} sub-runs included` : ""}${c.toolCostYuan > 0 ? ` \xB7 web \xA5${c.toolCostYuan.toFixed(4)}` : ""}`;
1236
1236
  },
1237
1237
  noReceipt: "(no receipt \u2014 the run was truncated before finishing; cost per the usageLog aggregate above)",
1238
- receiptLine: (r) => `${r.rounds} rounds \xB7 ${r.costYuan !== null ? `\xA5${r.costYuan.toFixed(4)}` : "unpriced"} \xB7 cache hit ${Math.round(r.cacheHitRate * 100)}% \xB7 ${(r.durationMs / 1e3).toFixed(0)}s`,
1238
+ receiptLine: (r) => `${r.rounds} rounds \xB7 ${r.costYuan !== null ? `\xA5${r.costYuan.toFixed(4)}` : "unpriced"} \xB7 cache hit ${Math.round(r.cacheHitRate * 100)}% \xB7 ${(r.durationMs / 1e3).toFixed(0)}s${r.toolCostYuan ? ` \xB7 web \xA5${r.toolCostYuan.toFixed(4)}` : ""}`,
1239
1239
  rejectedTitle: "# Batch task rejected",
1240
1240
  reportTitle: (title) => `# Batch task report: ${title}`,
1241
1241
  statusEntry: (v) => `- Status: ${v}`,
@@ -8335,12 +8335,56 @@ function decodeEntities(s) {
8335
8335
  return cp > 0 && cp <= 1114111 && !(cp >= 55296 && cp <= 57343) ? String.fromCodePoint(cp) : "";
8336
8336
  }).replace(/&[a-z#0-9]+;/gi, (e) => named[e.toLowerCase()] ?? e);
8337
8337
  }
8338
+ async function searchDeepseek(query, maxResults, signal) {
8339
+ const apiKey = dsApiKey();
8340
+ if (!apiKey) throw new Error("\u672A\u914D\u7F6E DEEP_SEEK_API_KEY\uFF08\u4E0E LLM \u8C03\u7528\u540C key\uFF0CDeepSeek \u63A7\u5236\u53F0\u7533\u8BF7\uFF09");
8341
+ const res = await fetch(`${dsToolsBase()}/tools/search`, {
8342
+ method: "POST",
8343
+ signal,
8344
+ headers: { "Content-Type": "application/json", "Authorization": `Bearer ${apiKey}` },
8345
+ body: JSON.stringify({ query, count: Math.max(1, Math.min(maxResults, 50)) })
8346
+ });
8347
+ if (!res.ok) {
8348
+ const t = await res.text().catch(() => "");
8349
+ throw new Error(`DeepSeek Search HTTP ${res.status} ${res.statusText}${t ? ` | ${t.slice(0, 200)}` : ""}`);
8350
+ }
8351
+ const data = await res.json();
8352
+ const results = [];
8353
+ let budget = DS_SNIPPET_BUDGET_CHARS;
8354
+ for (const r of Array.isArray(data?.results) ? data.results : []) {
8355
+ const title = (r?.title ?? "").toString().trim();
8356
+ const url = (r?.url ?? "").toString().trim();
8357
+ if (!title || !url) continue;
8358
+ const snippet = (r?.summary ?? r?.snippet ?? "").toString().trim();
8359
+ if (results.length > 0 && snippet.length > budget) break;
8360
+ results.push({ title, url, snippet });
8361
+ budget -= snippet.length;
8362
+ }
8363
+ return { answer: "", results };
8364
+ }
8365
+ async function fetchDeepseek(url, signal) {
8366
+ const apiKey = dsApiKey();
8367
+ if (!apiKey) throw new Error("\u672A\u914D\u7F6E DEEP_SEEK_API_KEY\uFF08\u4E0E LLM \u8C03\u7528\u540C key\uFF0CDeepSeek \u63A7\u5236\u53F0\u7533\u8BF7\uFF09");
8368
+ const res = await fetch(`${dsToolsBase()}/tools/fetch`, {
8369
+ method: "POST",
8370
+ signal,
8371
+ headers: { "Content-Type": "application/json", "Authorization": `Bearer ${apiKey}` },
8372
+ body: JSON.stringify({ url })
8373
+ });
8374
+ if (!res.ok) {
8375
+ const t = await res.text().catch(() => "");
8376
+ throw new Error(`DeepSeek Fetch HTTP ${res.status} ${res.statusText}${t ? ` | ${t.slice(0, 200)}` : ""}`);
8377
+ }
8378
+ const data = await res.json();
8379
+ return { title: (data?.title ?? "").toString().trim(), content: (data?.content ?? "").toString() };
8380
+ }
8338
8381
  function pickSearchProvider() {
8339
8382
  const forced = (process.env.SEARCH_PROVIDER || "").toLowerCase();
8383
+ if (forced === "deepseek" && dsApiKey()) return "deepseek";
8340
8384
  if (forced === "tavily" && appConfig.tavilyApiKey) return "tavily";
8341
8385
  if (forced === "ddg" || forced === "duckduckgo") return "duckduckgo";
8342
8386
  if (forced === "bing") return "bing";
8343
- return appConfig.tavilyApiKey ? "tavily" : "bing";
8387
+ return dsApiKey() ? "deepseek" : appConfig.tavilyApiKey ? "tavily" : "bing";
8344
8388
  }
8345
8389
  async function searchTavily(query, maxResults, signal) {
8346
8390
  const apiKey = appConfig.tavilyApiKey;
@@ -8419,10 +8463,18 @@ async function searchDuckDuckGo(query, maxResults, signal) {
8419
8463
  return { answer: "", results };
8420
8464
  }
8421
8465
  function formatSearchResults(query, provider, resp) {
8422
- if (resp.results.length === 0) return `[web_search | "${query}" | ${provider}]\uFF1A\u672A\u627E\u5230\u76F8\u5173\u7ED3\u679C\u3002`;
8466
+ if (resp.results.length === 0) {
8467
+ const siteArg = query.match(/site:(\S+)/i)?.[1];
8468
+ if (siteArg?.includes("/")) {
8469
+ const host = siteArg.split("/")[0];
8470
+ return `[web_search | "${query}" | ${provider}]\uFF1A0 \u547D\u4E2D\u2014\u2014site: \u540E\u5E26\u4E86\u8DEF\u5F84\uFF08site: \u53EA\u63A5\u57DF\u540D\uFF0C\u5E26\u8DEF\u5F84\u6052 0 \u6761\uFF09\u3002\u6539\u7528\uFF1Asite:${host} \u9875\u9762\u6807\u9898\u6216\u4E3B\u9898\u5173\u952E\u8BCD`;
8471
+ }
8472
+ return `[web_search | "${query}" | ${provider}]\uFF1A\u672A\u627E\u5230\u76F8\u5173\u7ED3\u679C\u3002`;
8473
+ }
8474
+ const snippetCap = provider === "deepseek" ? DS_SUMMARY_SLICE_CHARS : 240;
8423
8475
  const lines = resp.results.map((r, i) => `${i + 1}. ${r.title}
8424
8476
  ${r.url}
8425
- ${r.snippet.slice(0, 240)}`);
8477
+ ${r.snippet.slice(0, snippetCap)}`);
8426
8478
  const body = (resp.answer ? `\u{1F4A1} \u6458\u8981: ${resp.answer}
8427
8479
 
8428
8480
  ` : "") + lines.join("\n\n");
@@ -8473,7 +8525,7 @@ async function safeFetchFollow(startUrl, baseHeaders, signals, allowPrivate = fa
8473
8525
  return res;
8474
8526
  }
8475
8527
  }
8476
- var FETCH_TIMEOUT_MS2, SEARCH_TIMEOUT_MS, DEFAULT_MAX_CHARS, USER_AGENT, BROWSER_UA, TAVILY_ENDPOINT, formatHostBlockMessage, MAX_REDIRECTS, pinnedSsrfDispatcher, pinnedMetadataGuardDispatcher, readBodyCapped, webTools;
8528
+ var FETCH_TIMEOUT_MS2, SEARCH_TIMEOUT_MS, DEFAULT_MAX_CHARS, USER_AGENT, BROWSER_UA, TAVILY_ENDPOINT, formatHostBlockMessage, TOOL_CALL_PRICE_YUAN, dsToolsBase, dsApiKey, DS_SNIPPET_BUDGET_CHARS, DS_FETCH_TIMEOUT_MS, DS_SUMMARY_SLICE_CHARS, MAX_REDIRECTS, pinnedSsrfDispatcher, pinnedMetadataGuardDispatcher, readBodyCapped, webTools;
8477
8529
  var init_web = __esm({
8478
8530
  "../core/src/tool/registry/web.ts"() {
8479
8531
  "use strict";
@@ -8503,6 +8555,12 @@ var init_web = __esm({
8503
8555
  return `\u76EE\u6807\u4E3B\u673A ${h} DNS \u89E3\u6790\u5931\u8D25\uFF08${v.detail || "\u672A\u77E5\u9519\u8BEF"}\uFF09\uFF0C\u975E SSRF \u62E6\u622A\uFF1B\u5EFA\u8BAE\u68C0\u67E5\u7F51\u7EDC/DNS \u6216\u6539\u7528 web_search`;
8504
8556
  }
8505
8557
  };
8558
+ TOOL_CALL_PRICE_YUAN = { search: 5e-3, fetch: 5e-3 };
8559
+ dsToolsBase = () => (process.env.DEEP_SEEK_API_URL || "https://api.deepseek.com").replace(/\/+$/, "");
8560
+ dsApiKey = () => (process.env.DEEP_SEEK_API_KEY || "").trim();
8561
+ DS_SNIPPET_BUDGET_CHARS = 14e3;
8562
+ DS_FETCH_TIMEOUT_MS = 3e4;
8563
+ DS_SUMMARY_SLICE_CHARS = 3e3;
8506
8564
  MAX_REDIRECTS = 5;
8507
8565
  pinnedSsrfDispatcher = new Agent({
8508
8566
  connect: {
@@ -8583,13 +8641,14 @@ var init_web = __esm({
8583
8641
  planAllowed: true,
8584
8642
  primaryArg: "url",
8585
8643
  autoApproval: "aggressive",
8586
- description: "\u6293\u53D6\u6307\u5B9A\u516C\u5F00 URL \u7684\u7F51\u9875\u5185\u5BB9\u5E76\u8F6C\u4E3A\u5E72\u51C0\u7684 Markdown \u6587\u672C\uFF0C\u7528\u4E8E\u67E5\u9605 API \u6587\u6863\u3001\u62A5\u9519\u8BF4\u660E\u3001\u5B98\u65B9 changelog \u7B49\u5916\u90E8\u8D44\u6599\u3002\u4EC5\u652F\u6301 http/https\uFF0C\u9ED8\u8BA4\u62E6\u622A\u5185\u7F51/\u56DE\u73AF/\u94FE\u8DEF\u672C\u5730\u5730\u5740\uFF08\u9632 SSRF\uFF09\uFF1B\u672C\u5730\u5F00\u53D1/\u81EA\u52A8\u5316\u6D4B\u8BD5\u9700\u6293 localhost \u670D\u52A1\u6216\u5185\u7F51\u9875\u9762\u65F6\uFF0C\u4F20 allow_private=true \u653E\u884C\uFF08\u4E91\u5143\u6570\u636E\u7AEF\u70B9\u4ECD\u62E6\uFF09\u3002",
8644
+ description: '\u6293\u53D6\u6307\u5B9A\u516C\u5F00 URL \u7684\u7F51\u9875\u5185\u5BB9\u5E76\u8F6C\u4E3A\u5E72\u51C0\u7684 Markdown \u6587\u672C\uFF0C\u7528\u4E8E\u67E5\u9605 API \u6587\u6863\u3001\u62A5\u9519\u8BF4\u660E\u3001\u5B98\u65B9 changelog \u7B49\u5916\u90E8\u8D44\u6599\u3002\u4EC5\u652F\u6301 http/https\uFF0C\u9ED8\u8BA4\u62E6\u622A\u5185\u7F51/\u56DE\u73AF/\u94FE\u8DEF\u672C\u5730\u5730\u5740\uFF08\u9632 SSRF\uFF09\uFF1B\u672C\u5730\u5F00\u53D1/\u81EA\u52A8\u5316\u6D4B\u8BD5\u9700\u6293 localhost \u670D\u52A1\u6216\u5185\u7F51\u9875\u9762\u65F6\uFF0C\u4F20 allow_private=true \u653E\u884C\uFF08\u4E91\u5143\u6570\u636E\u7AEF\u70B9\u4ECD\u62E6\uFF09\u3002\u53EF\u9009 backend="deepseek" \u8D70 DeepSeek \u670D\u52A1\u7AEF\u6293\u53D6\uFF08\u8FD4\u56DE markdown \u6B63\u6587\uFF1B\u8BE5\u540E\u7AEF\u4E0B allow_private \u65E0\u6548\u2014\u2014\u6293\u53D6\u4E0D\u7ECF\u8FC7\u672C\u673A\u7F51\u7EDC\uFF0CSSRF \u9632\u62A4\u662F\u672C\u673A\u8BED\u4E49\uFF09\u3002',
8587
8645
  parameters: {
8588
8646
  type: "object",
8589
8647
  properties: {
8590
8648
  url: { type: "string", description: "\u8981\u6293\u53D6\u7684\u5B8C\u6574 http(s) URL" },
8649
+ backend: { type: "string", enum: ["local", "deepseek"], description: "\u6293\u53D6\u540E\u7AEF\uFF1Alocal=\u672C\u673A\u76F4\u6293\uFF08\u7F3A\u7701\uFF0C\u5E26\u5168\u5957 SSRF \u9632\u62A4\uFF09\uFF1Bdeepseek=DeepSeek \u670D\u52A1\u7AEF\u6293\u53D6\uFF08\u53CD\u722C\u53D7\u9650\uFF0C\u5931\u8D25\u65F6\u53EF\u8BD5 web_search \u7528 site: \u8BED\u6CD5\u4ECE\u7D22\u5F15\u4FA7\u62FF\u6B63\u6587\uFF09\u3002" },
8591
8650
  max_length: { type: "number", description: `\u8FD4\u56DE\u5185\u5BB9\u7684\u6700\u5927\u5B57\u7B26\u6570\uFF08\u9ED8\u8BA4 ${DEFAULT_MAX_CHARS}\uFF0C\u8D85\u51FA\u81EA\u52A8\u622A\u65AD\uFF09` },
8592
- allow_private: { type: "boolean", description: "\u662F\u5426\u5141\u8BB8\u8BBF\u95EE\u5185\u7F51/\u56DE\u73AF\u5730\u5740\uFF08\u5982 localhost:5173 \u672C\u5730\u670D\u52A1\u3001\u5185\u7F51\u6587\u6863\uFF09\u3002\u9ED8\u8BA4 false\u3002\u4E5F\u53EF\u7528\u73AF\u5883\u53D8\u91CF WEB_FETCH_ALLOW_PRIVATE=1 \u5168\u5C40\u5F00\u542F\u3002\u5373\u4FBF\u5F00\u542F\u4ECD\u62E6\u622A\u4E91\u5143\u6570\u636E\u7AEF\u70B9\uFF08169.254.169.254 \u7B49\uFF09\u3002" }
8651
+ allow_private: { type: "boolean", description: "\u662F\u5426\u5141\u8BB8\u8BBF\u95EE\u5185\u7F51/\u56DE\u73AF\u5730\u5740\uFF08\u5982 localhost:5173 \u672C\u5730\u670D\u52A1\u3001\u5185\u7F51\u6587\u6863\uFF09\u3002\u9ED8\u8BA4 false\u3002\u4E5F\u53EF\u7528\u73AF\u5883\u53D8\u91CF WEB_FETCH_ALLOW_PRIVATE=1 \u5168\u5C40\u5F00\u542F\u3002\u5373\u4FBF\u5F00\u542F\u4ECD\u62E6\u622A\u4E91\u5143\u6570\u636E\u7AEF\u70B9\uFF08169.254.169.254 \u7B49\uFF09\u3002\u4EC5 local \u540E\u7AEF\u6709\u6548\uFF1Bbackend=deepseek \u65F6\u5FFD\u7565\u3002" }
8593
8652
  },
8594
8653
  required: ["url"]
8595
8654
  },
@@ -8602,6 +8661,44 @@ var init_web = __esm({
8602
8661
  \uFF08\u5C06\u53D1\u8D77\u5BF9\u5916\u7F51\u7EDC\u8BF7\u6C42\uFF0C\u4E14\u6293\u53D6\u5230\u7684\u5185\u5BB9\u4F1A\u8FDB\u5165\u4E91\u7AEF\u6A21\u578B\u4E0A\u4E0B\u6587\uFF1B\u8BF7\u786E\u8BA4 URL \u6765\u6E90\u53EF\u4FE1\u3001\u4E0D\u542B\u654F\u611F\u56DE\u4F20\u6570\u636E\uFF09`,
8603
8662
  async execute(args, ctx) {
8604
8663
  const maxChars = args.max_length && args.max_length > 0 ? args.max_length : DEFAULT_MAX_CHARS;
8664
+ if (args.backend === "deepseek") {
8665
+ let dsUrl;
8666
+ try {
8667
+ dsUrl = new URL(args.url);
8668
+ } catch {
8669
+ return toolFailure(`[\u6293\u53D6\u5931\u8D25]\uFF1AURL \u683C\u5F0F\u4E0D\u5408\u6CD5\uFF1A${args.url}`);
8670
+ }
8671
+ if (dsUrl.protocol !== "http:" && dsUrl.protocol !== "https:") {
8672
+ return toolFailure(`[\u5B89\u5168\u7194\u65AD]\uFF1A\u4EC5\u5141\u8BB8 http/https \u534F\u8BAE\uFF0C\u62D2\u7EDD ${dsUrl.protocol}`);
8673
+ }
8674
+ const dsSignals = [AbortSignal.timeout(DS_FETCH_TIMEOUT_MS)];
8675
+ if (ctx?.abortSignal) dsSignals.push(ctx.abortSignal);
8676
+ try {
8677
+ const { title, content } = await fetchDeepseek(dsUrl.href, AbortSignal.any(dsSignals));
8678
+ ctx?.toolCostSink?.(TOOL_CALL_PRICE_YUAN.fetch);
8679
+ const body = title ? `# ${title}
8680
+
8681
+ ${content}` : content;
8682
+ if (body.length > maxChars) {
8683
+ return [
8684
+ `[web_fetch | deepseek | ${dsUrl.href} | \u5DF2\u622A\u65AD\u524D ${maxChars} \u5B57\u7B26\uFF0C\u539F\u6587\u5171 ${body.length} \u5B57\u7B26]`,
8685
+ body.slice(0, maxChars),
8686
+ `
8687
+ [... \u26A0\uFE0F \u5185\u5BB9\u8FC7\u957F\uFF0C\u5DF2\u9690\u85CF\u5269\u4F59 ${body.length - maxChars} \u5B57\u7B26\uFF0C\u53EF\u8C03\u5927 max_length ...]`
8688
+ ].join("\n");
8689
+ }
8690
+ return `[web_fetch | deepseek | ${dsUrl.href}]
8691
+ ${body}`;
8692
+ } catch (error) {
8693
+ if (error?.name === "TimeoutError") {
8694
+ return toolFailure(`[\u6293\u53D6\u8D85\u65F6]\uFF1A${DS_FETCH_TIMEOUT_MS / 1e3}s \u5185\u672A\u54CD\u5E94\uFF1A${dsUrl.href}`);
8695
+ }
8696
+ if (error?.name === "AbortError") {
8697
+ return `\u23F9\uFE0F [\u5DF2\u4E2D\u6B62]\uFF1A\u7528\u6237\u4E2D\u65AD\u4E86\u6293\u53D6\uFF1A${dsUrl.href}`;
8698
+ }
8699
+ return toolFailure(`[\u6293\u53D6\u5931\u8D25 | deepseek]\uFF1A${error.message}\uFF08\u8BE5\u540E\u7AEF\u5BF9\u53CD\u722C/\u56FD\u9645\u7AD9\u70B9\u540C\u6837\u53D7\u9650\uFF1B\u53EF\u6539\u7528 web_search \u4ECE\u7D22\u5F15\u4FA7\u62FF\u6B63\u6587\u2014\u2014site: \u53EA\u63A5\u57DF\u540D\u4E0D\u5E26\u8DEF\u5F84\uFF0C\u4F8B\uFF1Asite:${dsUrl.hostname} \u9875\u9762\u6807\u9898\u6216\u4E3B\u9898\u5173\u952E\u8BCD\uFF09`);
8700
+ }
8701
+ }
8605
8702
  const allowPrivate = args.allow_private ?? appConfig.webFetchAllowPrivate;
8606
8703
  let parsed;
8607
8704
  try {
@@ -8666,7 +8763,7 @@ ${body}${truncNote}`;
8666
8763
  planAllowed: true,
8667
8764
  primaryArg: "query",
8668
8765
  autoApproval: "aggressive",
8669
- description: "\u8054\u7F51\u641C\u7D22\u5916\u90E8\u8D44\u6599\uFF08API \u6587\u6863\u3001\u62A5\u9519\u89E3\u6CD5\u3001\u5E93\u7528\u6CD5\u3001\u6700\u65B0\u4FE1\u606F\u7B49\uFF09\uFF0C\u8FD4\u56DE\u7ED3\u679C\u5217\u8868\uFF08\u6807\u9898/URL/\u7247\u6BB5\uFF09\u3002\u9ED8\u8BA4\u7528 Bing\uFF08\u514D\u6CE8\u518C\u3001\u4E2D\u56FD/\u5168\u7403\u7686\u53EF\u7528\uFF09\uFF1B\u914D\u7F6E\u4E86 TAVILY_API_KEY \u4F1A\u81EA\u52A8\u5347\u7EA7\u5230 Tavily\uFF08\u7ED3\u679C\u66F4\u5E72\u51C0\u3001\u5E26\u6458\u8981\uFF09\uFF1B\u53EF\u7528\u73AF\u5883\u53D8\u91CF SEARCH_PROVIDER=bing|ddg|tavily \u5F3A\u5236\u6307\u5B9A\u3002\u67E5\u8BE2\u964C\u751F\u5E93\u6216\u62A5\u9519\u65F6\u4F18\u5148\u7528\u672C\u5DE5\u5177\u66FF\u4EE3\u51ED\u8BB0\u5FC6\u4F5C\u7B54\u3002\u7ED3\u679C\u6309\u957F\u5EA6\u7194\u65AD\u3002",
8766
+ description: "\u8054\u7F51\u641C\u7D22\u5916\u90E8\u8D44\u6599\uFF08API \u6587\u6863\u3001\u62A5\u9519\u89E3\u6CD5\u3001\u5E93\u7528\u6CD5\u3001\u6700\u65B0\u4FE1\u606F\u7B49\uFF09\uFF0C\u8FD4\u56DE\u7ED3\u679C\u5217\u8868\uFF08\u6807\u9898/URL/\u6458\u8981\uFF09\u3002\u540E\u7AEF\u81EA\u52A8\u9009\u62E9\uFF1A\u914D\u7F6E\u4E86 DEEP_SEEK_API_KEY\uFF08\u901A\u5E38\u5DF2\u914D\uFF09\u9ED8\u8BA4\u8D70 DeepSeek \u81EA\u5BB6\u641C\u7D22\u5F15\u64CE\uFF08\u957F\u6458\u8981\u8D28\u91CF\u6700\u4F73\uFF0C\xA50.005/\u6B21\u6309\u6B21\u8BA1\u8D39\u8FDB\u7ED3\u7B97\u5355\uFF09\uFF1B\u5426\u5219\u6709 TAVILY_API_KEY \u7528 Tavily\uFF0C\u518D\u5426\u5219 Bing\uFF08\u514D\u6CE8\u518C\uFF09\u3002\u53EF\u7528 SEARCH_PROVIDER=deepseek|tavily|bing|ddg \u5F3A\u5236\u6307\u5B9A\uFF08deepseek \u5F02\u5E38\u65F6\u53EF\u4E34\u65F6\u5207 bing \u56DE\u9000\uFF09\u3002\u67E5\u8BE2\u964C\u751F\u5E93\u6216\u62A5\u9519\u65F6\u4F18\u5148\u7528\u672C\u5DE5\u5177\u66FF\u4EE3\u51ED\u8BB0\u5FC6\u4F5C\u7B54\u3002\u7ED3\u679C\u6309\u957F\u5EA6\u7194\u65AD\u3002",
8670
8767
  parameters: {
8671
8768
  type: "object",
8672
8769
  properties: {
@@ -8689,7 +8786,17 @@ ${body}${truncNote}`;
8689
8786
  if (ctx?.abortSignal) signals.push(ctx.abortSignal);
8690
8787
  try {
8691
8788
  const signal = AbortSignal.any(signals);
8692
- const resp = provider === "tavily" ? await searchTavily(args.query, maxResults, signal) : provider === "duckduckgo" ? await searchDuckDuckGo(args.query, maxResults, signal) : await searchBing(args.query, maxResults, signal);
8789
+ let resp;
8790
+ if (provider === "deepseek") {
8791
+ resp = await searchDeepseek(args.query, maxResults, signal);
8792
+ ctx?.toolCostSink?.(TOOL_CALL_PRICE_YUAN.search);
8793
+ } else if (provider === "tavily") {
8794
+ resp = await searchTavily(args.query, maxResults, signal);
8795
+ } else if (provider === "duckduckgo") {
8796
+ resp = await searchDuckDuckGo(args.query, maxResults, signal);
8797
+ } else {
8798
+ resp = await searchBing(args.query, maxResults, signal);
8799
+ }
8693
8800
  return formatSearchResults(args.query, provider, resp);
8694
8801
  } catch (error) {
8695
8802
  if (error?.name === "TimeoutError") {
@@ -8698,7 +8805,8 @@ ${body}${truncNote}`;
8698
8805
  if (error?.name === "AbortError") {
8699
8806
  return `\u23F9\uFE0F [\u5DF2\u4E2D\u6B62]\uFF1A\u7528\u6237\u4E2D\u65AD\u4E86\u641C\u7D22\u3002`;
8700
8807
  }
8701
- return toolFailure(`[web_search \u5931\u8D25 | ${provider}]\uFF1A${error.message}`);
8808
+ const hint = provider === "deepseek" ? "\uFF08\u53EF\u8BBE SEARCH_PROVIDER=bing \u4E34\u65F6\u5207\u56DE\u672C\u5730\u722C\u866B\u540E\u7AEF\uFF09" : "";
8809
+ return toolFailure(`[web_search \u5931\u8D25 | ${provider}]\uFF1A${error.message}${hint}`);
8702
8810
  }
8703
8811
  }
8704
8812
  }
@@ -11808,7 +11916,8 @@ var init_toolExecution = __esm({
11808
11916
  modelWindow,
11809
11917
  parentSystemPrompt,
11810
11918
  readTracker,
11811
- reportCost
11919
+ reportCost,
11920
+ toolCostSink
11812
11921
  } = ctx;
11813
11922
  let calledName = "";
11814
11923
  let calledArgs = {};
@@ -11846,7 +11955,7 @@ var init_toolExecution = __esm({
11846
11955
  events({ sessionId, eventType: "tool.validation.failed", metadata: { depth, decisionSource: llmDecisionSource, durationMs: performance.now() - startTime, round, tools_id: toolCall.id, toolName: calledName, toolSource: "builtin", ok: false, attempt: round }, payload: { output: note } });
11847
11956
  return { toolCallId: toolCall.id, calledName, calledArgs, resultForModel: note, resultForUser: note, ok: false };
11848
11957
  }
11849
- const toolCtx = { sessionId, cwd: getActiveWorkspaceRoot(), abortSignal: signal, depth, keepRecentUnits, compactRatio, modelWindow, parentSystemPrompt, events, onUIEvent, requestApproval: hostRequestApproval, requestQuestion: hostRequestQuestion, ideAction, emitProgress: (m) => onUIEvent?.({ type: "tool.progress", toolsId: toolCall.id, toolName: calledName, message: m }), permissionMode, readTracker, reportCost };
11958
+ const toolCtx = { sessionId, cwd: getActiveWorkspaceRoot(), abortSignal: signal, depth, keepRecentUnits, compactRatio, modelWindow, parentSystemPrompt, events, onUIEvent, requestApproval: hostRequestApproval, requestQuestion: hostRequestQuestion, ideAction, emitProgress: (m) => onUIEvent?.({ type: "tool.progress", toolsId: toolCall.id, toolName: calledName, message: m }), permissionMode, readTracker, reportCost, toolCostSink };
11850
11959
  let result = "";
11851
11960
  let resultStatus = "success";
11852
11961
  let errorCategory;
@@ -12833,12 +12942,17 @@ var init_prices = __esm({
12833
12942
  idle: { input: 1, output: 4, cacheHit: 0.02 },
12834
12943
  peak: { input: 2, output: 8, cacheHit: 0.04 }
12835
12944
  },
12945
+ // 官网脚注明示旧名按 Flash 价格计费(2026-10-19 核实),非猜测——与 flash 同价条目
12946
+ "deepseek-flash-vision-exp": {
12947
+ idle: { input: 1, output: 4, cacheHit: 0.02 },
12948
+ peak: { input: 2, output: 8, cacheHit: 0.04 }
12949
+ },
12836
12950
  "deepseek-v4-pro": {
12837
12951
  idle: { input: 4.5, output: 13.5, cacheHit: 0.15 },
12838
12952
  peak: { input: 9, output: 27, cacheHit: 0.3 }
12839
12953
  }
12840
12954
  };
12841
- PRICES_VERIFIED_AT = "2026-10-16";
12955
+ PRICES_VERIFIED_AT = "2026-10-19";
12842
12956
  PRICES_STALE_DAYS = 30;
12843
12957
  pricesAgeDays = () => Math.max(0, Math.floor((Date.now() - Date.parse(`${PRICES_VERIFIED_AT}T00:00:00Z`)) / 864e5));
12844
12958
  isBeijingPeak = (ts2 = /* @__PURE__ */ new Date()) => {
@@ -12960,6 +13074,13 @@ async function* runAgent(message, options) {
12960
13074
  budgetSpentYuan += cost;
12961
13075
  }
12962
13076
  };
13077
+ let toolCostYuanAcc = 0;
13078
+ const addToolCost = (yuan) => {
13079
+ if (!Number.isFinite(yuan) || yuan <= 0) return;
13080
+ toolCostYuanAcc += yuan;
13081
+ budgetSpentYuan += yuan;
13082
+ };
13083
+ const toolCostSink = process.env.DEEP_SEEK_TOOL_COST === "0" ? void 0 : addToolCost;
12963
13084
  const toolCounts = {};
12964
13085
  await appendEvent(sessionId, { dscEvent: "run.start", runId, depth });
12965
13086
  try {
@@ -13141,7 +13262,10 @@ async function* runAgent(message, options) {
13141
13262
  // 子 run 结束时把总花费记进本 run 预算累计器——父预算自动继承约束(子花费计入父 spent)。
13142
13263
  reportCost: (yuan) => {
13143
13264
  if (Number.isFinite(yuan) && yuan > 0) budgetSpentYuan += yuan;
13144
- }
13265
+ },
13266
+ // ★ 按次计费工具成本上报(DeepSeek Search/Fetch):web 工具经 toolCtx.toolCostSink 拿到闭包,
13267
+ // 成功调用后按次上报进本 run 累计器。
13268
+ toolCostSink
13145
13269
  };
13146
13270
  const scheduleResult = yield* scheduleToolCalls(assistantMessage, message, toolCallCtx);
13147
13271
  if (scheduleResult.kind === "aborted") {
@@ -13191,12 +13315,15 @@ async function* runAgent(message, options) {
13191
13315
  totalTokens: usageSum?.total_tokens,
13192
13316
  cachedTokens: usageSum?.cached_tokens,
13193
13317
  costYuan: costKnown ? Math.round(costYuanAcc * 1e6) / 1e6 : void 0,
13318
+ toolCostYuan: toolCostYuanAcc > 0 ? Math.round(toolCostYuanAcc * 1e6) / 1e6 : void 0,
13319
+ // 按次计费工具花费(Search/Fetch)
13194
13320
  tools: toolCounts
13195
13321
  });
13196
13322
  } catch {
13197
13323
  }
13198
13324
  try {
13199
- if (options.costSink && costKnown && costYuanAcc > 0) options.costSink(Math.round(costYuanAcc * 1e6) / 1e6);
13325
+ const spentTotal = costYuanAcc + toolCostYuanAcc;
13326
+ if (options.costSink && spentTotal > 0) options.costSink(Math.round(spentTotal * 1e6) / 1e6);
13200
13327
  } catch {
13201
13328
  }
13202
13329
  try {
@@ -13210,6 +13337,8 @@ async function* runAgent(message, options) {
13210
13337
  cachedTokens: usageSum.cached_tokens ?? 0,
13211
13338
  cacheHitRate: prompt > 0 ? (usageSum.cached_tokens ?? 0) / prompt : 0,
13212
13339
  costYuan: costKnown ? Math.round(costYuanAcc * 1e6) / 1e6 : null,
13340
+ toolCostYuan: toolCostYuanAcc > 0 ? Math.round(toolCostYuanAcc * 1e6) / 1e6 : void 0,
13341
+ // 按次计费工具花费(undefined 自动省略)
13213
13342
  durationMs: Math.round(performance.now() - startTime),
13214
13343
  budgetTruncated: budgetTruncated || void 0
13215
13344
  // 预算截断标注(E2;undefined 自动省略)
@@ -15570,13 +15699,14 @@ var init_useChatState = __esm({
15570
15699
  const durMs = obj.durationMs ?? 0;
15571
15700
  const durText = durMs >= 6e4 ? `${Math.floor(durMs / 6e4)}m${Math.round(durMs % 6e4 / 1e3)}s` : `${(durMs / 1e3).toFixed(1)}s`;
15572
15701
  const cost = obj.costYuan;
15702
+ const toolCost = typeof obj.toolCostYuan === "number" && obj.toolCostYuan > 0 ? obj.toolCostYuan : null;
15573
15703
  const tokTotal = promptT + completionT;
15574
15704
  const tokText = tokTotal >= 1e4 ? `${(tokTotal / 1e3).toFixed(1)}k` : String(tokTotal);
15575
15705
  const costText = typeof cost === "number" ? `\xA5${cost.toFixed(cost < 5e-3 ? 4 : 3)}` : `${tokText} tok`;
15576
15706
  setRows((prev) => [...prev, {
15577
15707
  id: newRowId(),
15578
15708
  kind: "meta",
15579
- text: `\u{1F4B0} \u672C\u6B21 ${rounds} \u8F6E \xB7 ${costText} \xB7 \u7F13\u5B58\u547D\u4E2D ${hitPct}% \xB7 ${durText}${obj.budgetTruncated ? " \xB7 \u9884\u7B97\u622A\u65AD" : ""}`
15709
+ text: `\u{1F4B0} \u672C\u6B21 ${rounds} \u8F6E \xB7 ${costText}${toolCost != null ? ` \xB7 \u8054\u7F51 \xA5${toolCost.toFixed(3)}` : ""} \xB7 \u7F13\u5B58\u547D\u4E2D ${hitPct}% \xB7 ${durText}${obj.budgetTruncated ? " \xB7 \u9884\u7B97\u622A\u65AD" : ""}`
15580
15710
  }]);
15581
15711
  lastRunCostRef.current = typeof cost === "number" ? cost : void 0;
15582
15712
  sessionCostRef.current = typeof cost === "number" ? (sessionCostRef.current ?? 0) + cost : sessionCostRef.current;
@@ -18267,7 +18397,7 @@ ${lines.slice(-ACCEPT_TAIL_LINES).join("\n")}` : text;
18267
18397
  };
18268
18398
  collectSessionCost = async (sessionId) => {
18269
18399
  const recs = (await readUsageRecords(2)).filter((r) => r.sessionId === sessionId);
18270
- const c = { rounds: 0, mainRounds: 0, subRuns: 0, promptTokens: 0, completionTokens: 0, cachedTokens: 0, costYuan: null, partialPrice: false, durationMs: 0 };
18400
+ const c = { rounds: 0, mainRounds: 0, subRuns: 0, promptTokens: 0, completionTokens: 0, cachedTokens: 0, costYuan: null, toolCostYuan: 0, partialPrice: false, durationMs: 0 };
18271
18401
  for (const r of recs) {
18272
18402
  c.rounds += r.rounds || 0;
18273
18403
  if (r.depth === 0) c.mainRounds += r.rounds || 0;
@@ -18276,6 +18406,7 @@ ${lines.slice(-ACCEPT_TAIL_LINES).join("\n")}` : text;
18276
18406
  c.completionTokens += r.completionTokens ?? 0;
18277
18407
  c.cachedTokens += r.cachedTokens ?? 0;
18278
18408
  c.durationMs += r.durationMs || 0;
18409
+ c.toolCostYuan += r.toolCostYuan ?? 0;
18279
18410
  if (typeof r.costYuan === "number") c.costYuan = (c.costYuan ?? 0) + r.costYuan;
18280
18411
  else c.partialPrice = true;
18281
18412
  }
@@ -18341,7 +18472,7 @@ ${diff}
18341
18472
  const source = await fs49.promises.readFile(task.path, "utf-8");
18342
18473
  const parsed = parseBatchTicket(source, getLocale());
18343
18474
  if (!parsed.ok) {
18344
- const cost2 = { rounds: 0, mainRounds: 0, subRuns: 0, promptTokens: 0, completionTokens: 0, cachedTokens: 0, costYuan: 0, partialPrice: false, durationMs: 0 };
18475
+ const cost2 = { rounds: 0, mainRounds: 0, subRuns: 0, promptTokens: 0, completionTokens: 0, cachedTokens: 0, costYuan: 0, toolCostYuan: 0, partialPrice: false, durationMs: 0 };
18345
18476
  await finishTicket(task.id, `---
18346
18477
  id: ${task.id}
18347
18478
  title: ${task.id}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepseeker-code",
3
- "version": "1.0.57",
3
+ "version": "1.0.58",
4
4
  "description": "DeepSeeker-Code 终端客户端(React Ink,in-process 直驱 runAgent)",
5
5
  "homepage": "https://github.com/xknk/deepSeekCode#readme",
6
6
  "repository": {