deepseeker-code 1.0.56 → 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 +223 -31
  2. package/package.json +4 -3
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 || "",
@@ -913,10 +913,11 @@ var init_ticket = __esm({
913
913
  }
914
914
  const tools = fm["tools"] !== void 0 ? parseToolsValue(fm["tools"]) : void 0;
915
915
  if (tools && tools.length === 0) return { ok: false, error: ticketMsg(locale2, "badTools") };
916
+ const setup = (fm["setup"] ?? "").trim() || void 0;
916
917
  const body = bodyRaw.trim();
917
918
  if (!body) return { ok: false, error: ticketMsg(locale2, "emptyBody") };
918
919
  const title = (fm["title"] ?? "").trim() || body.split(/\r?\n/).find((l) => l.trim())?.slice(0, 40) || ticketMsg(locale2, "untitled");
919
- return { ok: true, ticket: { title, accept, budgetYuan, timeoutMin, tools: tools && tools.length > 0 ? tools : void 0, body } };
920
+ return { ok: true, ticket: { title, accept, setup, budgetYuan, timeoutMin, tools: tools && tools.length > 0 ? tools : void 0, body } };
920
921
  };
921
922
  readBatchTicket = async (filePath, locale2 = "zh") => {
922
923
  let source;
@@ -1069,6 +1070,7 @@ var init_batchStrings = __esm({
1069
1070
  ---
1070
1071
  title: \u4FEE\u590D\u65E5\u671F\u89E3\u6790 bug
1071
1072
  accept: npm test
1073
+ setup: pnpm install # \u53EF\u9009\uFF1Aagent \u524D\u5728 worktree \u5185\u5907\u73AF\u5883\uFF08\u5E72\u51C0\u68C0\u51FA\u65E0 node_modules\uFF0C\u9A8C\u6536\u4F9D\u8D56\u73AF\u5883\u65F6\u5148\u88C5\uFF09
1072
1074
  budget-yuan: 0.5 # \u53EF\u9009\uFF1A\u672C\u4EFB\u52A1\u82B1\u8D39\u4E0A\u9650\uFF08\u5143\uFF09
1073
1075
  timeout-min: 30 # \u53EF\u9009\uFF1Aagent \u9636\u6BB5\u8D85\u65F6\uFF08\u5206\u949F\uFF0C\u7F3A\u7701 30\uFF09
1074
1076
  tools: run_command # \u53EF\u9009\uFF1A\u663E\u5F0F\u89E3\u9501\u9AD8\u5371\u5DE5\u5177\uFF08\u7F3A\u7701\u7981 run_command/delete_path\uFF09
@@ -1091,6 +1093,8 @@ var init_batchStrings = __esm({
1091
1093
  // —— batchRunner 控制台进度 ——
1092
1094
  ticketRejected: (id, err) => `\u{1F6AB} [${id}] \u5DE5\u5355\u88AB\u62D2\uFF1A${err}`,
1093
1095
  taskMeta: (budget, timeoutMin, accept) => ` \u9884\u7B97 ${budget !== void 0 ? `\xA5${budget}` : "\u4E0D\u9650"} \xB7 \u8D85\u65F6 ${timeoutMin}min \xB7 \u9A8C\u6536 \`${accept}\``,
1096
+ setupLine: (cmd) => `\u{1F6E0}\uFE0F setup\uFF1A\`${cmd}\`\uFF08worktree \u5185\u5907\u73AF\u5883\uFF09`,
1097
+ setupFail: (cmd, code) => `\u73AF\u5883\u51C6\u5907\u5931\u8D25\uFF08setup \`${cmd}\` exit ${code ?? "null"}\uFF09\u2014\u2014\u8DF3\u8FC7 agent \u9636\u6BB5\uFF0C\u4EFB\u52A1\u8BB0\u5931\u8D25`,
1094
1098
  roundLine: (ts2, round) => ` [${ts2}] \u8F6E ${round}`,
1095
1099
  wtCreateFail: (e) => `\u521B\u5EFA worktree \u5931\u8D25\uFF08\u9700\u8981\u4E3B\u5DE5\u4F5C\u533A\u662F git \u4ED3\u5E93\uFF09\uFF1A${e}`,
1096
1100
  agentErrorLine: (id, err) => `\u{1F4A5} [${id}] agent \u5F02\u5E38\uFF1A${err}`,
@@ -1114,6 +1118,7 @@ var init_batchStrings = __esm({
1114
1118
  "accept-failed": "\u274C \u9A8C\u6536\u672A\u8FC7",
1115
1119
  timeout: "\u23F1\uFE0F \u8D85\u65F6\u622A\u65AD",
1116
1120
  error: "\u{1F4A5} agent \u5F02\u5E38",
1121
+ "setup-failed": "\u{1F6E0}\uFE0F \u73AF\u5883\u51C6\u5907\u5931\u8D25",
1117
1122
  rejected: "\u{1F6AB} \u5DE5\u5355\u88AB\u62D2"
1118
1123
  })[s],
1119
1124
  // —— 验收命令执行(输出嵌入报告) ——
@@ -1127,10 +1132,10 @@ var init_batchStrings = __esm({
1127
1132
  const money = c.costYuan !== null ? `\xA5${c.costYuan.toFixed(4)}` : "\uFF08\u65E0\u4EF7\u6A21\u578B\uFF0C\u4E0D\u8BA1\u94B1\uFF09";
1128
1133
  const hit = c.promptTokens > 0 ? Math.round(c.cachedTokens / c.promptTokens * 100) : 0;
1129
1134
  const mins = Math.round(c.durationMs / 6e3) / 10;
1130
- 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)}` : ""}`;
1131
1136
  },
1132
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",
1133
- 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)}` : ""}`,
1134
1139
  // —— 报告骨架 ——
1135
1140
  rejectedTitle: "# \u6302\u673A\u4EFB\u52A1\u88AB\u62D2",
1136
1141
  reportTitle: (title) => `# \u6302\u673A\u4EFB\u52A1\u62A5\u544A\uFF1A${title}`,
@@ -1168,6 +1173,7 @@ Ticket format (Markdown; frontmatter \`accept\` is required \u2014 no acceptance
1168
1173
  ---
1169
1174
  title: Fix date parsing bug
1170
1175
  accept: npm test # required: acceptance command (exit 0 = pass)
1176
+ setup: pnpm install # optional: prep env inside the worktree before the agent (fresh checkout has no node_modules)
1171
1177
  budget-yuan: 0.5 # optional: per-task cost cap (yuan)
1172
1178
  timeout-min: 30 # optional: agent-phase timeout (minutes, default 30)
1173
1179
  tools: run_command # optional: unlock risky tools (run_command/delete_path denied by default)
@@ -1190,6 +1196,8 @@ changes are harvested onto an isolated git worktree (temp branch), kept for manu
1190
1196
  reportMissing: "(report missing)",
1191
1197
  ticketRejected: (id, err) => `\u{1F6AB} [${id}] Ticket rejected: ${err}`,
1192
1198
  taskMeta: (budget, timeoutMin, accept) => ` Budget ${budget !== void 0 ? `\xA5${budget}` : "uncapped"} \xB7 timeout ${timeoutMin}min \xB7 accept \`${accept}\``,
1199
+ setupLine: (cmd) => `\u{1F6E0}\uFE0F setup: \`${cmd}\` (preparing the environment inside the worktree)`,
1200
+ setupFail: (cmd, code) => `Setup failed (\`${cmd}\` exit ${code ?? "null"}) \u2014 agent phase skipped, task marked failed`,
1193
1201
  roundLine: (ts2, round) => ` [${ts2}] round ${round}`,
1194
1202
  wtCreateFail: (e) => `Failed to create worktree (the main workspace must be a git repo): ${e}`,
1195
1203
  agentErrorLine: (id, err) => `\u{1F4A5} [${id}] agent error: ${err}`,
@@ -1212,6 +1220,7 @@ changes are harvested onto an isolated git worktree (temp branch), kept for manu
1212
1220
  "accept-failed": "\u274C acceptance failed",
1213
1221
  timeout: "\u23F1\uFE0F timed out",
1214
1222
  error: "\u{1F4A5} agent error",
1223
+ "setup-failed": "\u{1F6E0}\uFE0F setup failed",
1215
1224
  rejected: "\u{1F6AB} ticket rejected"
1216
1225
  })[s],
1217
1226
  spawnFail: (e) => `Spawn failed: ${e}`,
@@ -1223,10 +1232,10 @@ changes are harvested onto an isolated git worktree (temp branch), kept for manu
1223
1232
  const money = c.costYuan !== null ? `\xA5${c.costYuan.toFixed(4)}` : "(unpriced model, no money counted)";
1224
1233
  const hit = c.promptTokens > 0 ? Math.round(c.cachedTokens / c.promptTokens * 100) : 0;
1225
1234
  const mins = Math.round(c.durationMs / 6e3) / 10;
1226
- 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)}` : ""}`;
1227
1236
  },
1228
1237
  noReceipt: "(no receipt \u2014 the run was truncated before finishing; cost per the usageLog aggregate above)",
1229
- 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)}` : ""}`,
1230
1239
  rejectedTitle: "# Batch task rejected",
1231
1240
  reportTitle: (title) => `# Batch task report: ${title}`,
1232
1241
  statusEntry: (v) => `- Status: ${v}`,
@@ -8326,12 +8335,56 @@ function decodeEntities(s) {
8326
8335
  return cp > 0 && cp <= 1114111 && !(cp >= 55296 && cp <= 57343) ? String.fromCodePoint(cp) : "";
8327
8336
  }).replace(/&[a-z#0-9]+;/gi, (e) => named[e.toLowerCase()] ?? e);
8328
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
+ }
8329
8381
  function pickSearchProvider() {
8330
8382
  const forced = (process.env.SEARCH_PROVIDER || "").toLowerCase();
8383
+ if (forced === "deepseek" && dsApiKey()) return "deepseek";
8331
8384
  if (forced === "tavily" && appConfig.tavilyApiKey) return "tavily";
8332
8385
  if (forced === "ddg" || forced === "duckduckgo") return "duckduckgo";
8333
8386
  if (forced === "bing") return "bing";
8334
- return appConfig.tavilyApiKey ? "tavily" : "bing";
8387
+ return dsApiKey() ? "deepseek" : appConfig.tavilyApiKey ? "tavily" : "bing";
8335
8388
  }
8336
8389
  async function searchTavily(query, maxResults, signal) {
8337
8390
  const apiKey = appConfig.tavilyApiKey;
@@ -8410,10 +8463,18 @@ async function searchDuckDuckGo(query, maxResults, signal) {
8410
8463
  return { answer: "", results };
8411
8464
  }
8412
8465
  function formatSearchResults(query, provider, resp) {
8413
- 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;
8414
8475
  const lines = resp.results.map((r, i) => `${i + 1}. ${r.title}
8415
8476
  ${r.url}
8416
- ${r.snippet.slice(0, 240)}`);
8477
+ ${r.snippet.slice(0, snippetCap)}`);
8417
8478
  const body = (resp.answer ? `\u{1F4A1} \u6458\u8981: ${resp.answer}
8418
8479
 
8419
8480
  ` : "") + lines.join("\n\n");
@@ -8464,7 +8525,7 @@ async function safeFetchFollow(startUrl, baseHeaders, signals, allowPrivate = fa
8464
8525
  return res;
8465
8526
  }
8466
8527
  }
8467
- 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;
8468
8529
  var init_web = __esm({
8469
8530
  "../core/src/tool/registry/web.ts"() {
8470
8531
  "use strict";
@@ -8494,6 +8555,12 @@ var init_web = __esm({
8494
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`;
8495
8556
  }
8496
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;
8497
8564
  MAX_REDIRECTS = 5;
8498
8565
  pinnedSsrfDispatcher = new Agent({
8499
8566
  connect: {
@@ -8574,13 +8641,14 @@ var init_web = __esm({
8574
8641
  planAllowed: true,
8575
8642
  primaryArg: "url",
8576
8643
  autoApproval: "aggressive",
8577
- 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',
8578
8645
  parameters: {
8579
8646
  type: "object",
8580
8647
  properties: {
8581
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" },
8582
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` },
8583
- 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" }
8584
8652
  },
8585
8653
  required: ["url"]
8586
8654
  },
@@ -8593,6 +8661,44 @@ var init_web = __esm({
8593
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`,
8594
8662
  async execute(args, ctx) {
8595
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
+ }
8596
8702
  const allowPrivate = args.allow_private ?? appConfig.webFetchAllowPrivate;
8597
8703
  let parsed;
8598
8704
  try {
@@ -8657,7 +8763,7 @@ ${body}${truncNote}`;
8657
8763
  planAllowed: true,
8658
8764
  primaryArg: "query",
8659
8765
  autoApproval: "aggressive",
8660
- 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",
8661
8767
  parameters: {
8662
8768
  type: "object",
8663
8769
  properties: {
@@ -8680,7 +8786,17 @@ ${body}${truncNote}`;
8680
8786
  if (ctx?.abortSignal) signals.push(ctx.abortSignal);
8681
8787
  try {
8682
8788
  const signal = AbortSignal.any(signals);
8683
- 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
+ }
8684
8800
  return formatSearchResults(args.query, provider, resp);
8685
8801
  } catch (error) {
8686
8802
  if (error?.name === "TimeoutError") {
@@ -8689,7 +8805,8 @@ ${body}${truncNote}`;
8689
8805
  if (error?.name === "AbortError") {
8690
8806
  return `\u23F9\uFE0F [\u5DF2\u4E2D\u6B62]\uFF1A\u7528\u6237\u4E2D\u65AD\u4E86\u641C\u7D22\u3002`;
8691
8807
  }
8692
- 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}`);
8693
8810
  }
8694
8811
  }
8695
8812
  }
@@ -11799,7 +11916,8 @@ var init_toolExecution = __esm({
11799
11916
  modelWindow,
11800
11917
  parentSystemPrompt,
11801
11918
  readTracker,
11802
- reportCost
11919
+ reportCost,
11920
+ toolCostSink
11803
11921
  } = ctx;
11804
11922
  let calledName = "";
11805
11923
  let calledArgs = {};
@@ -11837,7 +11955,7 @@ var init_toolExecution = __esm({
11837
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 } });
11838
11956
  return { toolCallId: toolCall.id, calledName, calledArgs, resultForModel: note, resultForUser: note, ok: false };
11839
11957
  }
11840
- 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 };
11841
11959
  let result = "";
11842
11960
  let resultStatus = "success";
11843
11961
  let errorCategory;
@@ -12815,13 +12933,34 @@ var init_usageLog = __esm({
12815
12933
  });
12816
12934
 
12817
12935
  // ../core/src/config/prices.ts
12818
- var MODEL_PRICES, envPrice, ENV_OVERRIDE, priceFor, calcCostYuan;
12936
+ var MODEL_PRICES, PRICES_VERIFIED_AT, PRICES_STALE_DAYS, pricesAgeDays, isBeijingPeak, envPrice, ENV_OVERRIDE, priceFor, calcCostYuan;
12819
12937
  var init_prices = __esm({
12820
12938
  "../core/src/config/prices.ts"() {
12821
12939
  "use strict";
12822
12940
  MODEL_PRICES = {
12823
- "deepseek-flash": { input: 1, output: 2, cacheHit: 0.02 },
12824
- "deepseek-v4-pro": { input: 3, output: 6, cacheHit: 0.025 }
12941
+ "deepseek-flash": {
12942
+ idle: { input: 1, output: 4, cacheHit: 0.02 },
12943
+ peak: { input: 2, output: 8, cacheHit: 0.04 }
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
+ },
12950
+ "deepseek-v4-pro": {
12951
+ idle: { input: 4.5, output: 13.5, cacheHit: 0.15 },
12952
+ peak: { input: 9, output: 27, cacheHit: 0.3 }
12953
+ }
12954
+ };
12955
+ PRICES_VERIFIED_AT = "2026-10-19";
12956
+ PRICES_STALE_DAYS = 30;
12957
+ pricesAgeDays = () => Math.max(0, Math.floor((Date.now() - Date.parse(`${PRICES_VERIFIED_AT}T00:00:00Z`)) / 864e5));
12958
+ isBeijingPeak = (ts2 = /* @__PURE__ */ new Date()) => {
12959
+ const bj = new Date(ts2.getTime() + 8 * 36e5);
12960
+ const day = bj.getUTCDay();
12961
+ if (day === 0 || day === 6) return false;
12962
+ const h = bj.getUTCHours();
12963
+ return h >= 9 && h < 12 || h >= 14 && h < 18;
12825
12964
  };
12826
12965
  envPrice = (name) => {
12827
12966
  const raw = process.env[name];
@@ -12836,8 +12975,9 @@ var init_prices = __esm({
12836
12975
  if (input === void 0 && output === void 0 && cacheHit === void 0) return null;
12837
12976
  return { input, output, cacheHit };
12838
12977
  })();
12839
- priceFor = (model) => {
12840
- const base = MODEL_PRICES[model];
12978
+ priceFor = (model, ts2) => {
12979
+ const tier = MODEL_PRICES[model];
12980
+ const base = tier ? isBeijingPeak(ts2 ?? /* @__PURE__ */ new Date()) ? tier.peak : tier.idle : void 0;
12841
12981
  if (!ENV_OVERRIDE) return base ?? null;
12842
12982
  const input = ENV_OVERRIDE.input ?? base?.input;
12843
12983
  const output = ENV_OVERRIDE.output ?? base?.output;
@@ -12845,8 +12985,8 @@ var init_prices = __esm({
12845
12985
  if (input === void 0 || output === void 0 || cacheHit === void 0) return null;
12846
12986
  return { input, output, cacheHit };
12847
12987
  };
12848
- calcCostYuan = (model, u) => {
12849
- const p = priceFor(model);
12988
+ calcCostYuan = (model, u, ts2) => {
12989
+ const p = priceFor(model, ts2);
12850
12990
  if (!p) return null;
12851
12991
  const cached2 = Math.max(0, u.cachedTokens ?? 0);
12852
12992
  const miss = Math.max(0, (u.promptTokens ?? 0) - cached2);
@@ -12934,6 +13074,13 @@ async function* runAgent(message, options) {
12934
13074
  budgetSpentYuan += cost;
12935
13075
  }
12936
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;
12937
13084
  const toolCounts = {};
12938
13085
  await appendEvent(sessionId, { dscEvent: "run.start", runId, depth });
12939
13086
  try {
@@ -13115,7 +13262,10 @@ async function* runAgent(message, options) {
13115
13262
  // 子 run 结束时把总花费记进本 run 预算累计器——父预算自动继承约束(子花费计入父 spent)。
13116
13263
  reportCost: (yuan) => {
13117
13264
  if (Number.isFinite(yuan) && yuan > 0) budgetSpentYuan += yuan;
13118
- }
13265
+ },
13266
+ // ★ 按次计费工具成本上报(DeepSeek Search/Fetch):web 工具经 toolCtx.toolCostSink 拿到闭包,
13267
+ // 成功调用后按次上报进本 run 累计器。
13268
+ toolCostSink
13119
13269
  };
13120
13270
  const scheduleResult = yield* scheduleToolCalls(assistantMessage, message, toolCallCtx);
13121
13271
  if (scheduleResult.kind === "aborted") {
@@ -13165,12 +13315,15 @@ async function* runAgent(message, options) {
13165
13315
  totalTokens: usageSum?.total_tokens,
13166
13316
  cachedTokens: usageSum?.cached_tokens,
13167
13317
  costYuan: costKnown ? Math.round(costYuanAcc * 1e6) / 1e6 : void 0,
13318
+ toolCostYuan: toolCostYuanAcc > 0 ? Math.round(toolCostYuanAcc * 1e6) / 1e6 : void 0,
13319
+ // 按次计费工具花费(Search/Fetch)
13168
13320
  tools: toolCounts
13169
13321
  });
13170
13322
  } catch {
13171
13323
  }
13172
13324
  try {
13173
- 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);
13174
13327
  } catch {
13175
13328
  }
13176
13329
  try {
@@ -13184,6 +13337,8 @@ async function* runAgent(message, options) {
13184
13337
  cachedTokens: usageSum.cached_tokens ?? 0,
13185
13338
  cacheHitRate: prompt > 0 ? (usageSum.cached_tokens ?? 0) / prompt : 0,
13186
13339
  costYuan: costKnown ? Math.round(costYuanAcc * 1e6) / 1e6 : null,
13340
+ toolCostYuan: toolCostYuanAcc > 0 ? Math.round(toolCostYuanAcc * 1e6) / 1e6 : void 0,
13341
+ // 按次计费工具花费(undefined 自动省略)
13187
13342
  durationMs: Math.round(performance.now() - startTime),
13188
13343
  budgetTruncated: budgetTruncated || void 0
13189
13344
  // 预算截断标注(E2;undefined 自动省略)
@@ -15544,13 +15699,14 @@ var init_useChatState = __esm({
15544
15699
  const durMs = obj.durationMs ?? 0;
15545
15700
  const durText = durMs >= 6e4 ? `${Math.floor(durMs / 6e4)}m${Math.round(durMs % 6e4 / 1e3)}s` : `${(durMs / 1e3).toFixed(1)}s`;
15546
15701
  const cost = obj.costYuan;
15702
+ const toolCost = typeof obj.toolCostYuan === "number" && obj.toolCostYuan > 0 ? obj.toolCostYuan : null;
15547
15703
  const tokTotal = promptT + completionT;
15548
15704
  const tokText = tokTotal >= 1e4 ? `${(tokTotal / 1e3).toFixed(1)}k` : String(tokTotal);
15549
15705
  const costText = typeof cost === "number" ? `\xA5${cost.toFixed(cost < 5e-3 ? 4 : 3)}` : `${tokText} tok`;
15550
15706
  setRows((prev) => [...prev, {
15551
15707
  id: newRowId(),
15552
15708
  kind: "meta",
15553
- 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" : ""}`
15554
15710
  }]);
15555
15711
  lastRunCostRef.current = typeof cost === "number" ? cost : void 0;
15556
15712
  sessionCostRef.current = typeof cost === "number" ? (sessionCostRef.current ?? 0) + cost : sessionCostRef.current;
@@ -17163,6 +17319,7 @@ var init_inspect = __esm({
17163
17319
  usageTopTools: (tools) => ` \u5E38\u7528\u5DE5\u5177: ${tools.join("\u3001") || "\uFF08\u65E0\uFF09"}`,
17164
17320
  usageWeekCost: (cost, saved, unpriced) => ` \u8FD1 7 \u5929\u82B1\u8D39 \xA5${cost}\uFF08\u7F13\u5B58\u547D\u4E2D\u5DF2\u7701 \xA5${saved}\uFF09${unpriced > 0 ? ` \xB7 ${unpriced} \u6761\u65E0\u4EF7\u8BB0\u5F55\u672A\u8BA1` : ""}`,
17165
17321
  usageTopSessions: (tops) => ` \u8D39\u7528 top \u4F1A\u8BDD: ${tops}`,
17322
+ usagePriceStale: (days, at) => ` \u26A0\uFE0F \u4EF7\u683C\u8868 ${at} \u6838\u5B9E\u540E\u5DF2 ${days} \u5929\u672A\u590D\u6838\u2014\u2014\u5B98\u7F51\u82E5\u5DF2\u8C03\u4EF7\uFF0C\u4E0A\u65B9\u82B1\u8D39\u4F1A\u5931\u771F\uFF08\u53EF\u8BBE DEEP_SEEK_PRICE_* \u4E34\u65F6\u6821\u51C6\uFF09\u3002`,
17166
17323
  usageTip: " \u63D0\u793A\uFF1A\u5E76\u884C workflow \u4F1A\u653E\u5927\u5B50 agent \u7528\u91CF\uFF1B\u7F13\u5B58\u547D\u4E2D\u7387\u8D8A\u9AD8\uFF0C\u8F93\u5165\u6210\u672C\u8D8A\u4F4E\u3002",
17167
17324
  ctxHead: "\u{1F9E0} \u4E0A\u4E0B\u6587\u6CBB\u7406",
17168
17325
  ctxWindow: (window, threshold, ratio, keep) => ` \u7A97\u53E3\u4E0A\u9650: ${window} | \u538B\u7F29\u9608\u503C: ${threshold}\uFF08${ratio}\xD7\uFF09 | \u4FDD\u7559\u6700\u8FD1: ${keep} \u5BF9\u8BDD\u5355\u5143`,
@@ -17209,6 +17366,7 @@ var init_inspect = __esm({
17209
17366
  usageTopTools: (tools) => ` Top tools: ${tools.join(", ") || "(none)"}`,
17210
17367
  usageWeekCost: (cost, saved, unpriced) => ` 7-day spend \xA5${cost} (cache hits saved \xA5${saved})${unpriced > 0 ? ` \xB7 ${unpriced} unpriced records excluded` : ""}`,
17211
17368
  usageTopSessions: (tops) => ` Costliest sessions: ${tops}`,
17369
+ usagePriceStale: (days, at) => ` \u26A0\uFE0F Price table verified on ${at}, ${days} days ago \u2014 if official pricing changed, costs above may drift (set DEEP_SEEK_PRICE_* to correct).`,
17212
17370
  usageTip: " Tip: parallel workflows multiply sub-agent usage; higher cache hit rate means lower input cost.",
17213
17371
  ctxHead: "\u{1F9E0} Context governance",
17214
17372
  ctxWindow: (window, threshold, ratio, keep) => ` Window cap: ${window} | Compact threshold: ${threshold} (${ratio}\xD7) | Keep recent: ${keep} dialogue units`,
@@ -17319,12 +17477,13 @@ var init_inspect = __esm({
17319
17477
  const bySession = /* @__PURE__ */ new Map();
17320
17478
  let weekCost = 0, weekSaved = 0, pricedRuns = 0;
17321
17479
  for (const r of weekRecords) {
17322
- const c = r.costYuan ?? calcCostYuan(r.model, r);
17480
+ const at = Number.isNaN(Date.parse(String(r.ts))) ? void 0 : new Date(r.ts);
17481
+ const c = r.costYuan ?? calcCostYuan(r.model, r, at);
17323
17482
  if (c == null) continue;
17324
17483
  pricedRuns++;
17325
17484
  weekCost += c;
17326
17485
  bySession.set(r.sessionId, (bySession.get(r.sessionId) ?? 0) + c);
17327
- const p = priceFor(r.model);
17486
+ const p = priceFor(r.model, at);
17328
17487
  if (p) weekSaved += (r.cachedTokens ?? 0) * (p.input - p.cacheHit) / 1e6;
17329
17488
  }
17330
17489
  if (pricedRuns > 0) {
@@ -17334,6 +17493,8 @@ var init_inspect = __esm({
17334
17493
  if (tops) lines.push(t("usageTopSessions")(tops));
17335
17494
  }
17336
17495
  }
17496
+ const staleDays = pricesAgeDays();
17497
+ if (staleDays >= PRICES_STALE_DAYS) lines.push(t("usagePriceStale")(staleDays, PRICES_VERIFIED_AT));
17337
17498
  lines.push(t("usageTip"));
17338
17499
  return lines.join("\n");
17339
17500
  };
@@ -18236,7 +18397,7 @@ ${lines.slice(-ACCEPT_TAIL_LINES).join("\n")}` : text;
18236
18397
  };
18237
18398
  collectSessionCost = async (sessionId) => {
18238
18399
  const recs = (await readUsageRecords(2)).filter((r) => r.sessionId === sessionId);
18239
- 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 };
18240
18401
  for (const r of recs) {
18241
18402
  c.rounds += r.rounds || 0;
18242
18403
  if (r.depth === 0) c.mainRounds += r.rounds || 0;
@@ -18245,6 +18406,7 @@ ${lines.slice(-ACCEPT_TAIL_LINES).join("\n")}` : text;
18245
18406
  c.completionTokens += r.completionTokens ?? 0;
18246
18407
  c.cachedTokens += r.cachedTokens ?? 0;
18247
18408
  c.durationMs += r.durationMs || 0;
18409
+ c.toolCostYuan += r.toolCostYuan ?? 0;
18248
18410
  if (typeof r.costYuan === "number") c.costYuan = (c.costYuan ?? 0) + r.costYuan;
18249
18411
  else c.partialPrice = true;
18250
18412
  }
@@ -18310,7 +18472,7 @@ ${diff}
18310
18472
  const source = await fs49.promises.readFile(task.path, "utf-8");
18311
18473
  const parsed = parseBatchTicket(source, getLocale());
18312
18474
  if (!parsed.ok) {
18313
- 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 };
18314
18476
  await finishTicket(task.id, `---
18315
18477
  id: ${task.id}
18316
18478
  title: ${task.id}
@@ -18357,6 +18519,36 @@ ${parsed.error}`);
18357
18519
  return { status: "error", cost: cost2, budgetTruncated: false };
18358
18520
  }
18359
18521
  setSessionWorktree(sessionId, wt);
18522
+ if (t.setup) {
18523
+ emit2(T.setupLine(t.setup));
18524
+ const s = await runAcceptCommand(t.setup, wt.path);
18525
+ if (s.code !== 0 || s.timedOut) {
18526
+ const msg = `${T.setupFail(t.setup, s.code)}${s.timedOut ? T.acceptTimeoutSuffix : ""}
18527
+
18528
+ \`\`\`
18529
+ ${s.output}
18530
+ \`\`\``;
18531
+ await killBackgroundTasksUnder(wt.path).catch(() => {
18532
+ });
18533
+ const cost2 = await collectSessionCost(sessionId);
18534
+ await finishTicket(task.id, buildReport({
18535
+ id: task.id,
18536
+ ticket: t,
18537
+ status: "setup-failed",
18538
+ budgetTruncated: false,
18539
+ cost: cost2,
18540
+ receipt: void 0,
18541
+ finalText: "",
18542
+ accept: null,
18543
+ deniedTools: [],
18544
+ diff: T.diffNone,
18545
+ wt,
18546
+ errorMessage: msg
18547
+ }));
18548
+ emit2(`\u{1F6E0}\uFE0F [${task.id}] ${T.setupFail(t.setup, s.code)}`);
18549
+ return { status: "setup-failed", cost: cost2, budgetTruncated: false };
18550
+ }
18551
+ }
18360
18552
  const ac = new AbortController();
18361
18553
  const timer = setTimeout(() => ac.abort(new Error(T.timeoutAbort(timeoutMin))), timeoutMin * 6e4);
18362
18554
  let receipt;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepseeker-code",
3
- "version": "1.0.56",
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": {
@@ -40,10 +40,11 @@
40
40
  "node": ">=20"
41
41
  },
42
42
  "dependencies": {
43
- "ink": "5",
44
- "react": "18",
43
+ "ajv": "^8.20.0",
45
44
  "ignore": "^7.0.5",
45
+ "ink": "5",
46
46
  "openai": "^6.42.0",
47
+ "react": "18",
47
48
  "undici": "^8.8.0",
48
49
  "vscode-ripgrep": "^1.13.2"
49
50
  },