dsh-llm-verifier 0.1.0 → 0.1.1

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
@@ -17,29 +17,50 @@
17
17
 
18
18
  ## 安装与启用 (Installation & Usage)
19
19
 
20
- ### 1. 使用 DSH 插件命令安装
20
+ ### 1. 使用 `dsh plugin` 安装
21
21
 
22
- 通过 DSH 提供的 `dsh plugin` 命令将本插件安装至对应的 Profile(如 `web` 或 `tui`):
22
+ DeepSeek Harness(DSH)通过 profile 独立管理各个运行环境的插件依赖。请使用 `dsh plugin` 命令将插件安装至目标 profile(如 `web`):
23
23
 
24
24
  ```bash
25
- # Web Profile 安装插件
25
+ # 方式 A:从 npm 官方 Registry 安装(推荐)
26
26
  dsh plugin --profile web add dsh-llm-verifier
27
27
  ```
28
28
 
29
- ### 2. 载入与可视化配置
29
+ > [!NOTE]
30
+ > `dsh plugin add` 安装成功后,DSH 会自动识别包内的 `dsh.bundle` 声明并完成插件层自动对齐(Reconcile),**无需手动修改任何配置文件**。
31
+
32
+ ### 2. 启动与配置
33
+
34
+ 启动 DSH Web 客户端:
35
+
36
+ ```bash
37
+ dsh web
38
+ # 或
39
+ dsh --profile web
40
+ ```
30
41
 
31
- - **常规启动**:安装完成后直接启动对应 Profile(如 `dsh web`),插件将自动载入。
32
- - **自定义 Patch Overlay(可选)**:若在自定义 Profile 清单中手动声明,可在 `cordis.patch.yml` 中配置:
33
- ```yaml
34
- - insert:
35
- - id: llm-verifier
36
- name: 'dsh-llm-verifier'
37
- ```
38
- - **配置面板**:启动 DSH 后打开 **`设置 → LLM Verifier`** 即可可视化配置裁判所使用的 Provider、Model、推理强度、并发限制与缓存策略。
42
+ 启动后进入前端界面,打开 **`设置 LLM Verifier`** 即可可视化配置裁判所使用的 Provider、Model、推理强度(Reasoning Effort)、最大并发与缓存策略。
39
43
 
40
- ### 3. 作为独立库引用(可选)
44
+ ### 3. 常用管理命令
45
+
46
+ ```bash
47
+ # 更新插件至最新版本
48
+ dsh plugin --profile web update dsh-llm-verifier
41
49
 
42
- 你也可以在 TypeScript / JavaScript 项目中直接引用核心算法与评分函数:
50
+ # 卸载插件
51
+ dsh plugin --profile web remove dsh-llm-verifier
52
+
53
+ # 查看当前 Profile 已安装的插件与依赖列表
54
+ dsh plugin --profile web list
55
+ ```
56
+
57
+ ### 4. 作为独立库引用(可选)
58
+
59
+ 如果你在其它 TypeScript / JavaScript 项目中需要复用核心评分标尺与锦标赛算法,可直接作为普通 npm 依赖安装并引入:
60
+
61
+ ```bash
62
+ pnpm add dsh-llm-verifier
63
+ ```
43
64
 
44
65
  ```typescript
45
66
  import {
@@ -143,6 +164,7 @@ flowchart LR
143
164
 
144
165
  | 配置项 | 说明 |
145
166
  |---|---|
167
+ | **启用工具 (Enabled)** | 是否允许 Agent 调用 verifier 工具;关闭后调用会立即返回错误,不产生任何模型请求(设置页「工具开关」按钮可切换) |
146
168
  | **供应商 (Provider)** | 从 DSH 当前已配置且可路由的 Provider 列表中选择 |
147
169
  | **模型 (Model)** | 从所选 Provider 的模型目录中指定具体裁判模型 |
148
170
  | **推理强度 (Reasoning Effort)** | 使用 Adapter 为该模型声明的思考强度,或保留模型默认值 |
@@ -215,4 +237,4 @@ software is provided “AS IS”, without warranty. See the upstream LICENSE
215
237
  for the complete legal text.
216
238
  ```
217
239
 
218
- 衷心感谢上游项目的作者及贡献者开源了细粒度 A–T 奖励机制、Token 对数概率期望打分、任务进度验证、Bradley–Terry 软胜率模型以及 Probabilistic Pivot Tournament 锦标赛算法。本 DSH 插件将这一系列优秀成果移植并深度整合至 TypeScript 与 DeepSeek Harness 生态中;本插件不对上述理论算法声明原创所有权。
240
+ 衷心感谢上游项目的作者及贡献者开源了细粒度 A–T 奖励机制、Token 对数概率期望打分、任务进度验证、Bradley–Terry 软胜率模型以及 Probabilistic Pivot Tournament 锦标赛算法。本 DSH 插件将这一系列优秀成果移植并深度整合至 TypeScript 与 DeepSeek Harness 生态中;本插件不对上述理论算法声明原创所有权。
package/lib/client.js CHANGED
@@ -56,6 +56,7 @@ window.__ModuleLoader__.load({
56
56
  function values(view) {
57
57
  const v = record(view.value);
58
58
  return {
59
+ enabled: v.enabled !== false,
59
60
  provider: String(v.provider ?? ""),
60
61
  model: String(v.model ?? ""),
61
62
  ...typeof v.reasoningEffort === "string" ? { reasoningEffort: v.reasoningEffort } : {},
@@ -178,6 +179,44 @@ window.__ModuleLoader__.load({
178
179
  },
179
180
  children: "选择任意已在 DSH「模型」页配置并启用的模型作为独立裁判。设置实时生效。"
180
181
  })] }),
182
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
183
+ style: card,
184
+ children: [
185
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
186
+ style: sectionTitle,
187
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: draft.enabled ? "done" : "error" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "工具开关" })]
188
+ }),
189
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
190
+ style: row,
191
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Label, {
192
+ title: "启用 Verifier 工具",
193
+ help: draft.enabled ? "四个 verifier 工具可被 Agent 调用,每次调用会向裁判模型发起请求。" : "已停用:Agent 调用 verifier 工具会立即返回错误,不产生任何模型请求。"
194
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
195
+ onClick: () => patch("enabled", !draft.enabled),
196
+ style: {
197
+ width: 80,
198
+ height: 32,
199
+ borderRadius: 16,
200
+ border: "1px solid var(--dsw-alias-border-l2, rgba(255,255,255,0.16))",
201
+ background: draft.enabled ? "var(--dsw-success, #2f9e5b)" : "var(--dsw-surface-sunken)",
202
+ color: "#fff",
203
+ cursor: "pointer",
204
+ fontSize: 13,
205
+ fontWeight: 600
206
+ },
207
+ children: draft.enabled ? "已启用" : "已停用"
208
+ })]
209
+ }),
210
+ !draft.enabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
211
+ style: {
212
+ padding: "0 0 14px",
213
+ fontSize: 12,
214
+ color: "var(--dsw-state-warn-primary, #d9a441)"
215
+ },
216
+ children: "⚠ 当前停用 verifier_compare / verifier_select / verifier_track / verifier_current_session 四个工具"
217
+ })
218
+ ]
219
+ }),
181
220
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
182
221
  style: card,
183
222
  children: [
package/lib/index.js CHANGED
@@ -9,6 +9,7 @@ import { dirname, isAbsolute, join, resolve } from "node:path";
9
9
  //#region src/config.ts
10
10
  const VERIFIER_SETTINGS_NAMESPACE = settingsNamespace("llm-verifier");
11
11
  const Config = z.object({
12
+ enabled: z.boolean().default(true),
12
13
  provider: z.string().default("deepseek-official"),
13
14
  model: z.string().default("deepseek-v4-flash"),
14
15
  reasoningEffort: z.string(),
@@ -44,6 +45,7 @@ function resolveConfig(config = {}) {
44
45
  if (![estimatedInputUsdPerMillion, estimatedOutputUsdPerMillion].every((value) => Number.isFinite(value) && value >= 0)) throw new Error("llm-verifier: estimated token prices must be finite non-negative numbers");
45
46
  const reasoningEffort = config.reasoningEffort?.trim();
46
47
  return {
48
+ enabled: config.enabled ?? true,
47
49
  provider,
48
50
  model,
49
51
  ...reasoningEffort ? { reasoningEffort } : {},
@@ -673,6 +675,9 @@ function apply(ctx, config = {}) {
673
675
  provider: selected.provider,
674
676
  model: selected.model
675
677
  });
678
+ const requireEnabled = () => {
679
+ if (!current().enabled) throw new Error("llm-verifier: verifier tools are disabled — enable them in Settings → LLM Verifier");
680
+ };
676
681
  ctx.tools.register(defineTool({
677
682
  name: "verifier_compare",
678
683
  description: "Use autonomously when exactly two substantive answers, patches, plans, or execution trajectories need an independent evidence-based comparison and the choice is consequential or uncertain. Do not use for trivial deterministic questions or when there is only one candidate. Uses the verifier model selected in DSH Settings, with top-logprob A–T expectations when supported and explicit-tag fallback otherwise.",
@@ -740,6 +745,7 @@ function apply(ctx, config = {}) {
740
745
  },
741
746
  timeoutMs: entry.timeoutMs * 20,
742
747
  async execute(args, exec) {
748
+ requireEnabled();
743
749
  const { verifier, selected } = await engine();
744
750
  return {
745
751
  ...await verifier.compare({
@@ -825,6 +831,7 @@ function apply(ctx, config = {}) {
825
831
  },
826
832
  timeoutMs: entry.timeoutMs * 100,
827
833
  async execute(args, exec) {
834
+ requireEnabled();
828
835
  const { verifier, selected } = await engine();
829
836
  return {
830
837
  ...await verifier.select({
@@ -901,6 +908,7 @@ function apply(ctx, config = {}) {
901
908
  },
902
909
  timeoutMs: entry.timeoutMs * 20,
903
910
  async execute(args, exec) {
911
+ requireEnabled();
904
912
  const { verifier, selected } = await engine();
905
913
  return {
906
914
  ...await verifier.track(args.problem, args.steps, args.checkpoints, positive(args.repeats, 2, "repeats"), exec.signal, await images(args.images, exec.signal)),
@@ -986,6 +994,7 @@ function apply(ctx, config = {}) {
986
994
  },
987
995
  timeoutMs: entry.timeoutMs * 20,
988
996
  async execute(args, exec) {
997
+ requireEnabled();
989
998
  const agent = exec.agent ?? ctx.agents.currentInitiator();
990
999
  if (agent === void 0) throw new Error("llm-verifier: verifier_current_session requires an agent-owned tool call");
991
1000
  const extracted = await extractSession(agent, async (ref) => {
@@ -8,7 +8,7 @@ const sectionTitle = { display: 'flex', gap: 10, alignItems: 'center', padding:
8
8
  const row = { display: 'grid', gridTemplateColumns: 'minmax(150px, 1fr) minmax(220px, 1.4fr)', gap: 18, alignItems: 'center', padding: '14px 0', borderBottom: '1px solid var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.16))' };
9
9
  const selectStyle = { width: '100%', minHeight: 38, padding: '0 12px', borderRadius: 10, color: 'var(--dsw-text-primary)', background: 'var(--dsw-surface-sunken)', border: '1px solid var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.16))' };
10
10
  function record(value) { return typeof value === 'object' && value !== null && !Array.isArray(value) ? value : {}; }
11
- function values(view) { const v = record(view.value); return { provider: String(v.provider ?? ''), model: String(v.model ?? ''), ...(typeof v.reasoningEffort === 'string' ? { reasoningEffort: v.reasoningEffort } : {}), maxTokens: Number(v.maxTokens ?? 32768), maxConcurrency: Number(v.maxConcurrency ?? 8), maxRetries: Number(v.maxRetries ?? 3), timeoutMs: Number(v.timeoutMs ?? 300000), cacheMaxEntries: Number(v.cacheMaxEntries ?? 10000), estimatedInputUsdPerMillion: Number(v.estimatedInputUsdPerMillion ?? 0), estimatedOutputUsdPerMillion: Number(v.estimatedOutputUsdPerMillion ?? 0) }; }
11
+ function values(view) { const v = record(view.value); return { enabled: v.enabled !== false, provider: String(v.provider ?? ''), model: String(v.model ?? ''), ...(typeof v.reasoningEffort === 'string' ? { reasoningEffort: v.reasoningEffort } : {}), maxTokens: Number(v.maxTokens ?? 32768), maxConcurrency: Number(v.maxConcurrency ?? 8), maxRetries: Number(v.maxRetries ?? 3), timeoutMs: Number(v.timeoutMs ?? 300000), cacheMaxEntries: Number(v.cacheMaxEntries ?? 10000), estimatedInputUsdPerMillion: Number(v.estimatedInputUsdPerMillion ?? 0), estimatedOutputUsdPerMillion: Number(v.estimatedOutputUsdPerMillion ?? 0) }; }
12
12
  function message(error) { return error instanceof Error ? error.message : String(error); }
13
13
  function Label({ title, help }) { return _jsxs("div", { children: [_jsx("div", { style: { fontWeight: 600 }, children: title }), _jsx("div", { style: { fontSize: 12, color: 'var(--dsw-text-secondary)', marginTop: 3 }, children: help })] }); }
14
14
  function VerifierSettings({ api }) {
@@ -59,7 +59,7 @@ function VerifierSettings({ api }) {
59
59
  if (!loaded || !draft)
60
60
  return _jsxs("div", { style: shell, children: [_jsx("h2", { children: "LLM Verifier" }), _jsx("p", { children: error ?? '正在读取 DSH 模型和设置…' }), error && _jsx(Button, { onClick: () => void load(), children: "\u91CD\u8BD5" })] });
61
61
  const numeric = (key, min = 0) => _jsx(Input, { type: "number", min: min, value: String(draft[key]), onChange: e => patch(key, Number(e.target.value)) });
62
- return _jsxs("div", { style: shell, children: [_jsxs("div", { children: [_jsx("h2", { style: { margin: '0 0 6px' }, children: "LLM Verifier" }), _jsx("p", { style: { margin: 0, color: 'var(--dsw-text-secondary)' }, children: "\u9009\u62E9\u4EFB\u610F\u5DF2\u5728 DSH\u300C\u6A21\u578B\u300D\u9875\u914D\u7F6E\u5E76\u542F\u7528\u7684\u6A21\u578B\u4F5C\u4E3A\u72EC\u7ACB\u88C1\u5224\u3002\u8BBE\u7F6E\u5B9E\u65F6\u751F\u6548\u3002" })] }), _jsxs("div", { style: card, children: [_jsxs("div", { style: sectionTitle, children: [_jsx(StateDot, { state: "done" }), _jsx("strong", { children: "\u88C1\u5224\u6A21\u578B" })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: "\u4F9B\u5E94\u5546", help: "\u53EA\u663E\u793A\u5F53\u524D DSH \u4E2D\u53EF\u8DEF\u7531\u7684\u4F9B\u5E94\u5546" }), _jsx("select", { style: selectStyle, value: draft.provider, onChange: e => { const provider = e.target.value; const first = loaded.groups.find(g => g.id === provider)?.models[0]; setDraft({ ...draft, provider, ...(first ? { model: first.id, reasoningEffort: first.reasoning?.defaultEffort } : {}) }); }, children: loaded.groups.map(g => _jsxs("option", { value: g.id, children: [g.name, " \u00B7 ", g.id] }, g.id)) })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: "\u6A21\u578B", help: "\u6A21\u578B\u76EE\u5F55\u6765\u81EA DSH adapter\uFF0C\u9009\u62E9\u7ED3\u679C\u4F1A\u6301\u4E45\u5316" }), _jsx("select", { style: selectStyle, value: draft.model, onChange: e => { const model = e.target.value; const found = models.find(m => m.id === model); setDraft({ ...draft, model, ...(found?.reasoning?.defaultEffort ? { reasoningEffort: found.reasoning.defaultEffort } : { reasoningEffort: undefined }) }); }, children: models.map(m => _jsxs("option", { value: m.id, children: [m.name, " \u00B7 ", m.id] }, m.id)) })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: "\u63A8\u7406\u5F3A\u5EA6", help: "\u7531\u6240\u9009\u6A21\u578B adapter \u58F0\u660E\uFF1B\u7559\u7A7A\u4F7F\u7528\u6A21\u578B\u9ED8\u8BA4\u503C" }), _jsxs("select", { style: selectStyle, value: draft.reasoningEffort ?? '', onChange: e => patch('reasoningEffort', e.target.value || undefined), children: [_jsx("option", { value: "", children: "\u6A21\u578B\u9ED8\u8BA4" }), efforts.map(e => _jsx("option", { value: e.id, children: e.name }, e.id))] })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: "\u6700\u5927\u8F93\u51FA Token", help: "\u6BCF\u4E2A\u88C1\u5224\u8BF7\u6C42\u7684\u8F93\u51FA\u4E0A\u9650" }), numeric('maxTokens', 1)] })] }), _jsxs("div", { style: card, children: [_jsx("div", { style: sectionTitle, children: _jsx("strong", { children: "\u6267\u884C\u63A7\u5236" }) }), _jsxs("div", { style: row, children: [_jsx(Label, { title: "\u6700\u5927\u5E76\u53D1", help: "\u6240\u6709 verifier \u5DE5\u5177\u5171\u4EAB\u7684\u8BF7\u6C42\u5E76\u53D1\u4E0A\u9650" }), numeric('maxConcurrency', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: "\u6700\u591A\u91CD\u8BD5", help: "\u77ED\u6682\u7F51\u7EDC\u3001\u9650\u6D41\u548C\u670D\u52A1\u7AEF\u9519\u8BEF\u7684\u91CD\u8BD5\u6B21\u6570" }), numeric('maxRetries', 0)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: "\u8BF7\u6C42\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09", help: "\u5355\u4E2A\u6A21\u578B\u8BF7\u6C42\u7684\u8D85\u65F6\u65F6\u95F4" }), numeric('timeoutMs', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: "\u7F13\u5B58\u6761\u76EE\u4E0A\u9650", help: "\u6301\u4E45\u8BC4\u5206\u7F13\u5B58\u4FDD\u7559\u7684\u6700\u5927\u6761\u76EE\u6570" }), numeric('cacheMaxEntries', 1)] })] }), _jsxs("div", { style: card, children: [_jsx("div", { style: sectionTitle, children: _jsx("strong", { children: "\u8D39\u7528\u4F30\u7B97\uFF08\u6BCF\u767E\u4E07 Token\uFF0CUSD\uFF09" }) }), _jsxs("div", { style: row, children: [_jsx(Label, { title: "\u8F93\u5165\u4EF7\u683C", help: "\u4EC5\u7528\u4E8E\u7ED3\u679C\u4E2D\u7684 estimatedCostUsd" }), numeric('estimatedInputUsdPerMillion', 0)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: "\u8F93\u51FA\u4EF7\u683C", help: "\u4EC5\u7528\u4E8E\u7ED3\u679C\u4E2D\u7684 estimatedCostUsd" }), numeric('estimatedOutputUsdPerMillion', 0)] })] }), loaded.failures.length > 0 && _jsxs("div", { style: { ...card, borderColor: 'var(--dsw-alias-state-warn-primary, #d9a441)', paddingBottom: 16 }, children: [_jsx("strong", { children: "\u90E8\u5206\u6A21\u578B\u76EE\u5F55\u8BFB\u53D6\u5931\u8D25" }), loaded.failures.map(x => _jsx("div", { children: x }, x))] }), error && _jsx("div", { style: { color: 'var(--dsw-danger)' }, children: error }), saved && _jsx("div", { style: { color: 'var(--dsw-success)' }, children: "\u5DF2\u4FDD\u5B58\uFF0C\u540E\u7EED verifier \u8C03\u7528\u5C06\u4F7F\u7528\u65B0\u8BBE\u7F6E\u3002" }), _jsxs("div", { style: { display: 'flex', gap: 10 }, children: [_jsx(Button, { disabled: busy || !loaded.writable, onClick: () => void save(), children: busy ? '保存中…' : '保存设置' }), _jsx(Button, { variant: "outline", disabled: busy, onClick: () => void load(), children: "\u91CD\u65B0\u8F7D\u5165" })] })] });
62
+ return _jsxs("div", { style: shell, children: [_jsxs("div", { children: [_jsx("h2", { style: { margin: '0 0 6px' }, children: "LLM Verifier" }), _jsx("p", { style: { margin: 0, color: 'var(--dsw-text-secondary)' }, children: "\u9009\u62E9\u4EFB\u610F\u5DF2\u5728 DSH\u300C\u6A21\u578B\u300D\u9875\u914D\u7F6E\u5E76\u542F\u7528\u7684\u6A21\u578B\u4F5C\u4E3A\u72EC\u7ACB\u88C1\u5224\u3002\u8BBE\u7F6E\u5B9E\u65F6\u751F\u6548\u3002" })] }), _jsxs("div", { style: card, children: [_jsxs("div", { style: sectionTitle, children: [_jsx(StateDot, { state: draft.enabled ? 'done' : 'error' }), _jsx("strong", { children: "\u5DE5\u5177\u5F00\u5173" })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: "\u542F\u7528 Verifier \u5DE5\u5177", help: draft.enabled ? '四个 verifier 工具可被 Agent 调用,每次调用会向裁判模型发起请求。' : '已停用:Agent 调用 verifier 工具会立即返回错误,不产生任何模型请求。' }), _jsx("button", { onClick: () => patch('enabled', !draft.enabled), style: { width: 80, height: 32, borderRadius: 16, border: '1px solid var(--dsw-alias-border-l2, rgba(255,255,255,0.16))', background: draft.enabled ? 'var(--dsw-success, #2f9e5b)' : 'var(--dsw-surface-sunken)', color: '#fff', cursor: 'pointer', fontSize: 13, fontWeight: 600 }, children: draft.enabled ? '已启用' : '已停用' })] }), !draft.enabled && _jsx("div", { style: { padding: '0 0 14px', fontSize: 12, color: 'var(--dsw-state-warn-primary, #d9a441)' }, children: "\u26A0 \u5F53\u524D\u505C\u7528 verifier_compare / verifier_select / verifier_track / verifier_current_session \u56DB\u4E2A\u5DE5\u5177" })] }), _jsxs("div", { style: card, children: [_jsxs("div", { style: sectionTitle, children: [_jsx(StateDot, { state: "done" }), _jsx("strong", { children: "\u88C1\u5224\u6A21\u578B" })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: "\u4F9B\u5E94\u5546", help: "\u53EA\u663E\u793A\u5F53\u524D DSH \u4E2D\u53EF\u8DEF\u7531\u7684\u4F9B\u5E94\u5546" }), _jsx("select", { style: selectStyle, value: draft.provider, onChange: e => { const provider = e.target.value; const first = loaded.groups.find(g => g.id === provider)?.models[0]; setDraft({ ...draft, provider, ...(first ? { model: first.id, reasoningEffort: first.reasoning?.defaultEffort } : {}) }); }, children: loaded.groups.map(g => _jsxs("option", { value: g.id, children: [g.name, " \u00B7 ", g.id] }, g.id)) })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: "\u6A21\u578B", help: "\u6A21\u578B\u76EE\u5F55\u6765\u81EA DSH adapter\uFF0C\u9009\u62E9\u7ED3\u679C\u4F1A\u6301\u4E45\u5316" }), _jsx("select", { style: selectStyle, value: draft.model, onChange: e => { const model = e.target.value; const found = models.find(m => m.id === model); setDraft({ ...draft, model, ...(found?.reasoning?.defaultEffort ? { reasoningEffort: found.reasoning.defaultEffort } : { reasoningEffort: undefined }) }); }, children: models.map(m => _jsxs("option", { value: m.id, children: [m.name, " \u00B7 ", m.id] }, m.id)) })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: "\u63A8\u7406\u5F3A\u5EA6", help: "\u7531\u6240\u9009\u6A21\u578B adapter \u58F0\u660E\uFF1B\u7559\u7A7A\u4F7F\u7528\u6A21\u578B\u9ED8\u8BA4\u503C" }), _jsxs("select", { style: selectStyle, value: draft.reasoningEffort ?? '', onChange: e => patch('reasoningEffort', e.target.value || undefined), children: [_jsx("option", { value: "", children: "\u6A21\u578B\u9ED8\u8BA4" }), efforts.map(e => _jsx("option", { value: e.id, children: e.name }, e.id))] })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: "\u6700\u5927\u8F93\u51FA Token", help: "\u6BCF\u4E2A\u88C1\u5224\u8BF7\u6C42\u7684\u8F93\u51FA\u4E0A\u9650" }), numeric('maxTokens', 1)] })] }), _jsxs("div", { style: card, children: [_jsx("div", { style: sectionTitle, children: _jsx("strong", { children: "\u6267\u884C\u63A7\u5236" }) }), _jsxs("div", { style: row, children: [_jsx(Label, { title: "\u6700\u5927\u5E76\u53D1", help: "\u6240\u6709 verifier \u5DE5\u5177\u5171\u4EAB\u7684\u8BF7\u6C42\u5E76\u53D1\u4E0A\u9650" }), numeric('maxConcurrency', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: "\u6700\u591A\u91CD\u8BD5", help: "\u77ED\u6682\u7F51\u7EDC\u3001\u9650\u6D41\u548C\u670D\u52A1\u7AEF\u9519\u8BEF\u7684\u91CD\u8BD5\u6B21\u6570" }), numeric('maxRetries', 0)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: "\u8BF7\u6C42\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09", help: "\u5355\u4E2A\u6A21\u578B\u8BF7\u6C42\u7684\u8D85\u65F6\u65F6\u95F4" }), numeric('timeoutMs', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: "\u7F13\u5B58\u6761\u76EE\u4E0A\u9650", help: "\u6301\u4E45\u8BC4\u5206\u7F13\u5B58\u4FDD\u7559\u7684\u6700\u5927\u6761\u76EE\u6570" }), numeric('cacheMaxEntries', 1)] })] }), _jsxs("div", { style: card, children: [_jsx("div", { style: sectionTitle, children: _jsx("strong", { children: "\u8D39\u7528\u4F30\u7B97\uFF08\u6BCF\u767E\u4E07 Token\uFF0CUSD\uFF09" }) }), _jsxs("div", { style: row, children: [_jsx(Label, { title: "\u8F93\u5165\u4EF7\u683C", help: "\u4EC5\u7528\u4E8E\u7ED3\u679C\u4E2D\u7684 estimatedCostUsd" }), numeric('estimatedInputUsdPerMillion', 0)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: "\u8F93\u51FA\u4EF7\u683C", help: "\u4EC5\u7528\u4E8E\u7ED3\u679C\u4E2D\u7684 estimatedCostUsd" }), numeric('estimatedOutputUsdPerMillion', 0)] })] }), loaded.failures.length > 0 && _jsxs("div", { style: { ...card, borderColor: 'var(--dsw-alias-state-warn-primary, #d9a441)', paddingBottom: 16 }, children: [_jsx("strong", { children: "\u90E8\u5206\u6A21\u578B\u76EE\u5F55\u8BFB\u53D6\u5931\u8D25" }), loaded.failures.map(x => _jsx("div", { children: x }, x))] }), error && _jsx("div", { style: { color: 'var(--dsw-danger)' }, children: error }), saved && _jsx("div", { style: { color: 'var(--dsw-success)' }, children: "\u5DF2\u4FDD\u5B58\uFF0C\u540E\u7EED verifier \u8C03\u7528\u5C06\u4F7F\u7528\u65B0\u8BBE\u7F6E\u3002" }), _jsxs("div", { style: { display: 'flex', gap: 10 }, children: [_jsx(Button, { disabled: busy || !loaded.writable, onClick: () => void save(), children: busy ? '保存中…' : '保存设置' }), _jsx(Button, { variant: "outline", disabled: busy, onClick: () => void load(), children: "\u91CD\u65B0\u8F7D\u5165" })] })] });
63
63
  }
64
64
  export const inject = ['slots', 'connection'];
65
65
  export function apply(ctx) { const connection = ctx.get('connection'); ctx.slots.inject('settings.section', () => ctx.slots.register({ name: 'settings.section', id: 'llm-verifier', order: 35, label: 'LLM Verifier', inject: () => ({ api: connection.api }) }, VerifierSettings)); }
@@ -2,6 +2,7 @@ import type { Context } from '@deepseek-ai/cordis';
2
2
  import z from 'schemastery';
3
3
  export declare const VERIFIER_SETTINGS_NAMESPACE: import("@deepseek-ai/dsh-settings").SettingsNamespace;
4
4
  export interface Config {
5
+ enabled?: boolean;
5
6
  provider?: string;
6
7
  model?: string;
7
8
  reasoningEffort?: string;
@@ -16,6 +17,7 @@ export interface Config {
16
17
  estimatedOutputUsdPerMillion?: number;
17
18
  }
18
19
  export interface ResolvedConfig {
20
+ enabled: boolean;
19
21
  provider: string;
20
22
  model: string;
21
23
  reasoningEffort?: string;
@@ -2,6 +2,7 @@ import { installSettingsSection, settingsNamespace } from '@deepseek-ai/dsh-sett
2
2
  import z from 'schemastery';
3
3
  export const VERIFIER_SETTINGS_NAMESPACE = settingsNamespace('llm-verifier');
4
4
  export const Config = z.object({
5
+ enabled: z.boolean().default(true),
5
6
  provider: z.string().default('deepseek-official'),
6
7
  model: z.string().default('deepseek-v4-flash'),
7
8
  reasoningEffort: z.string(),
@@ -43,7 +44,7 @@ export function resolveConfig(config = {}) {
43
44
  if (![estimatedInputUsdPerMillion, estimatedOutputUsdPerMillion].every(value => Number.isFinite(value) && value >= 0))
44
45
  throw new Error('llm-verifier: estimated token prices must be finite non-negative numbers');
45
46
  const reasoningEffort = config.reasoningEffort?.trim();
46
- return { provider, model, ...(reasoningEffort ? { reasoningEffort } : {}), maxRetries, cacheDir, estimatedInputUsdPerMillion, estimatedOutputUsdPerMillion, ...values };
47
+ return { enabled: config.enabled ?? true, provider, model, ...(reasoningEffort ? { reasoningEffort } : {}), maxRetries, cacheDir, estimatedInputUsdPerMillion, estimatedOutputUsdPerMillion, ...values };
47
48
  }
48
49
  export function installVerifierSettings(ctx, entry, onChange) {
49
50
  let source = () => entry;
@@ -33,10 +33,12 @@ export function apply(ctx, config = {}) {
33
33
  };
34
34
  const images = (values, signal) => loadVerifierImages(values, signal);
35
35
  const route = (selected) => ({ provider: selected.provider, model: selected.model });
36
- ctx.tools.register(defineTool({ name: 'verifier_compare', description: 'Use autonomously when exactly two substantive answers, patches, plans, or execution trajectories need an independent evidence-based comparison and the choice is consequential or uncertain. Do not use for trivial deterministic questions or when there is only one candidate. Uses the verifier model selected in DSH Settings, with top-logprob A–T expectations when supported and explicit-tag fallback otherwise.', parameters: { problem: { type: 'string', required: true }, candidate_a: { type: 'string', required: true }, candidate_b: { type: 'string', required: true }, ...commonParams }, output: { schema: { type: 'object', additionalProperties: false, properties: { scoreA: { type: 'number', required: true }, scoreB: { type: 'number', required: true }, winner: { type: 'string', enum: ['A', 'B', 'tie'], required: true }, criteria: { type: 'array', items: criterionResultSchema, required: true }, calls: { type: 'integer', required: true }, stats: { ...statsSchema, required: true }, provider: { type: 'string', required: true }, model: { type: 'string', required: true } } }, render: (_args, value) => renderJson(value) }, timeoutMs: entry.timeoutMs * 20, async execute(args, exec) { const { verifier, selected } = await engine(); const result = await verifier.compare({ problem: args.problem, candidateA: args.candidate_a, candidateB: args.candidate_b, criteria: normalizeCriteria(args.criteria), repeats: positive(args.repeats, 2, 'repeats'), images: await images(args.images, exec.signal) }, exec.signal); return { ...result, ...route(selected) }; } }));
37
- ctx.tools.register(defineTool({ name: 'verifier_select', description: 'Use autonomously when three or more substantive candidate answers, patches, plans, or trajectories must be ranked and an independent choice is valuable. Use verifier_compare for exactly two candidates; do not generate extra candidates merely to invoke this tool. Uses the configured DSH verifier model and the O(Nk) Probabilistic Pivot Tournament.', parameters: { problem: { type: 'string', required: true }, candidates: { type: 'array', items: { type: 'string' }, required: true }, ...commonParams, pivots: { type: 'integer' }, seed: { type: 'integer' } }, output: { schema: { type: 'object', additionalProperties: false, properties: { index: { type: 'integer', required: true }, best: { type: 'string', required: true }, scores: { type: 'array', items: { type: 'number' }, required: true }, ranking: { type: 'array', items: { type: 'integer' }, required: true }, pivots: { type: 'array', items: { type: 'integer' }, required: true }, comparisons: { type: 'integer', required: true }, calls: { type: 'integer', required: true }, stats: { ...statsSchema, required: true }, provider: { type: 'string', required: true }, model: { type: 'string', required: true } } }, render: (_args, value) => renderJson(value) }, timeoutMs: entry.timeoutMs * 100, async execute(args, exec) { const { verifier, selected } = await engine(); const result = await verifier.select({ problem: args.problem, candidates: args.candidates, criteria: normalizeCriteria(args.criteria), repeats: positive(args.repeats, 2, 'repeats'), pivots: positive(args.pivots, 2, 'pivots'), seed: args.seed ?? 0, images: await images(args.images, exec.signal) }, exec.signal); return { ...result, ...route(selected) }; } }));
38
- ctx.tools.register(defineTool({ name: 'verifier_track', description: 'Use autonomously for a genuinely multi-step task when progress at explicit checkpoints is uncertain or needs evidence-based measurement. Do not use for a single completed answer or invent checkpoints that were not supplied by the task history. Trusts observed output rather than narration.', parameters: { problem: { type: 'string', required: true }, steps: { type: 'array', items: { type: 'string' }, required: true }, checkpoints: { type: 'array', items: { type: 'integer' }, required: true }, repeats: commonParams.repeats, images: commonParams.images }, output: { schema: { type: 'object', additionalProperties: false, properties: { scores: { type: 'array', items: { type: 'number' }, required: true }, perRepeat: { type: 'array', items: { type: 'array', items: { type: 'number' } }, required: true }, calls: { type: 'integer', required: true }, stats: { ...statsSchema, required: true }, provider: { type: 'string', required: true }, model: { type: 'string', required: true } } }, render: (_args, value) => renderJson(value) }, timeoutMs: entry.timeoutMs * 20, async execute(args, exec) { const { verifier, selected } = await engine(); const result = await verifier.track(args.problem, args.steps, args.checkpoints, positive(args.repeats, 2, 'repeats'), exec.signal, await images(args.images, exec.signal)); return { ...result, ...route(selected) }; } }));
39
- ctx.tools.register(defineTool({ name: 'verifier_current_session', description: 'Use autonomously near the end of a non-trivial coding, debugging, migration, deployment, or operations task when independent completion verification would materially reduce risk and the session contains real tool evidence. Do not use for routine conversation, simple factual answers, or every turn. Extracts the current DSH session, applies secret redaction, bounds and truncation, then sends the evidence to the verifier model selected in Settings.', parameters: { from_seq: { type: 'integer' }, to_seq: { type: 'integer' }, include_assistant_text: { type: 'boolean' }, redact_patterns: { type: 'array', items: { type: 'string' } }, max_chars: { type: 'integer' }, repeats: { type: 'integer' } }, output: { schema: { type: 'object', additionalProperties: false, properties: { sessionId: { type: 'string', required: true }, problem: { type: 'string', required: true }, score: { type: 'number', required: true }, baselineScore: { type: 'number', required: true }, winner: { type: 'string', enum: ['A', 'B', 'tie'], required: true }, fromSeq: { type: 'integer', required: true }, toSeq: { type: 'integer', required: true }, omittedCharacters: { type: 'integer', required: true }, calls: { type: 'integer', required: true }, stats: { ...statsSchema, required: true }, provider: { type: 'string', required: true }, model: { type: 'string', required: true } } }, render: (_args, value) => renderJson(value) }, timeoutMs: entry.timeoutMs * 20, async execute(args, exec) { const agent = exec.agent ?? ctx.agents.currentInitiator(); if (agent === undefined)
36
+ const requireEnabled = () => { if (!current().enabled)
37
+ throw new Error('llm-verifier: verifier tools are disabled enable them in Settings LLM Verifier'); };
38
+ ctx.tools.register(defineTool({ name: 'verifier_compare', description: 'Use autonomously when exactly two substantive answers, patches, plans, or execution trajectories need an independent evidence-based comparison and the choice is consequential or uncertain. Do not use for trivial deterministic questions or when there is only one candidate. Uses the verifier model selected in DSH Settings, with top-logprob A–T expectations when supported and explicit-tag fallback otherwise.', parameters: { problem: { type: 'string', required: true }, candidate_a: { type: 'string', required: true }, candidate_b: { type: 'string', required: true }, ...commonParams }, output: { schema: { type: 'object', additionalProperties: false, properties: { scoreA: { type: 'number', required: true }, scoreB: { type: 'number', required: true }, winner: { type: 'string', enum: ['A', 'B', 'tie'], required: true }, criteria: { type: 'array', items: criterionResultSchema, required: true }, calls: { type: 'integer', required: true }, stats: { ...statsSchema, required: true }, provider: { type: 'string', required: true }, model: { type: 'string', required: true } } }, render: (_args, value) => renderJson(value) }, timeoutMs: entry.timeoutMs * 20, async execute(args, exec) { requireEnabled(); const { verifier, selected } = await engine(); const result = await verifier.compare({ problem: args.problem, candidateA: args.candidate_a, candidateB: args.candidate_b, criteria: normalizeCriteria(args.criteria), repeats: positive(args.repeats, 2, 'repeats'), images: await images(args.images, exec.signal) }, exec.signal); return { ...result, ...route(selected) }; } }));
39
+ ctx.tools.register(defineTool({ name: 'verifier_select', description: 'Use autonomously when three or more substantive candidate answers, patches, plans, or trajectories must be ranked and an independent choice is valuable. Use verifier_compare for exactly two candidates; do not generate extra candidates merely to invoke this tool. Uses the configured DSH verifier model and the O(Nk) Probabilistic Pivot Tournament.', parameters: { problem: { type: 'string', required: true }, candidates: { type: 'array', items: { type: 'string' }, required: true }, ...commonParams, pivots: { type: 'integer' }, seed: { type: 'integer' } }, output: { schema: { type: 'object', additionalProperties: false, properties: { index: { type: 'integer', required: true }, best: { type: 'string', required: true }, scores: { type: 'array', items: { type: 'number' }, required: true }, ranking: { type: 'array', items: { type: 'integer' }, required: true }, pivots: { type: 'array', items: { type: 'integer' }, required: true }, comparisons: { type: 'integer', required: true }, calls: { type: 'integer', required: true }, stats: { ...statsSchema, required: true }, provider: { type: 'string', required: true }, model: { type: 'string', required: true } } }, render: (_args, value) => renderJson(value) }, timeoutMs: entry.timeoutMs * 100, async execute(args, exec) { requireEnabled(); const { verifier, selected } = await engine(); const result = await verifier.select({ problem: args.problem, candidates: args.candidates, criteria: normalizeCriteria(args.criteria), repeats: positive(args.repeats, 2, 'repeats'), pivots: positive(args.pivots, 2, 'pivots'), seed: args.seed ?? 0, images: await images(args.images, exec.signal) }, exec.signal); return { ...result, ...route(selected) }; } }));
40
+ ctx.tools.register(defineTool({ name: 'verifier_track', description: 'Use autonomously for a genuinely multi-step task when progress at explicit checkpoints is uncertain or needs evidence-based measurement. Do not use for a single completed answer or invent checkpoints that were not supplied by the task history. Trusts observed output rather than narration.', parameters: { problem: { type: 'string', required: true }, steps: { type: 'array', items: { type: 'string' }, required: true }, checkpoints: { type: 'array', items: { type: 'integer' }, required: true }, repeats: commonParams.repeats, images: commonParams.images }, output: { schema: { type: 'object', additionalProperties: false, properties: { scores: { type: 'array', items: { type: 'number' }, required: true }, perRepeat: { type: 'array', items: { type: 'array', items: { type: 'number' } }, required: true }, calls: { type: 'integer', required: true }, stats: { ...statsSchema, required: true }, provider: { type: 'string', required: true }, model: { type: 'string', required: true } } }, render: (_args, value) => renderJson(value) }, timeoutMs: entry.timeoutMs * 20, async execute(args, exec) { requireEnabled(); const { verifier, selected } = await engine(); const result = await verifier.track(args.problem, args.steps, args.checkpoints, positive(args.repeats, 2, 'repeats'), exec.signal, await images(args.images, exec.signal)); return { ...result, ...route(selected) }; } }));
41
+ ctx.tools.register(defineTool({ name: 'verifier_current_session', description: 'Use autonomously near the end of a non-trivial coding, debugging, migration, deployment, or operations task when independent completion verification would materially reduce risk and the session contains real tool evidence. Do not use for routine conversation, simple factual answers, or every turn. Extracts the current DSH session, applies secret redaction, bounds and truncation, then sends the evidence to the verifier model selected in Settings.', parameters: { from_seq: { type: 'integer' }, to_seq: { type: 'integer' }, include_assistant_text: { type: 'boolean' }, redact_patterns: { type: 'array', items: { type: 'string' } }, max_chars: { type: 'integer' }, repeats: { type: 'integer' } }, output: { schema: { type: 'object', additionalProperties: false, properties: { sessionId: { type: 'string', required: true }, problem: { type: 'string', required: true }, score: { type: 'number', required: true }, baselineScore: { type: 'number', required: true }, winner: { type: 'string', enum: ['A', 'B', 'tie'], required: true }, fromSeq: { type: 'integer', required: true }, toSeq: { type: 'integer', required: true }, omittedCharacters: { type: 'integer', required: true }, calls: { type: 'integer', required: true }, stats: { ...statsSchema, required: true }, provider: { type: 'string', required: true }, model: { type: 'string', required: true } } }, render: (_args, value) => renderJson(value) }, timeoutMs: entry.timeoutMs * 20, async execute(args, exec) { requireEnabled(); const agent = exec.agent ?? ctx.agents.currentInitiator(); if (agent === undefined)
40
42
  throw new Error('llm-verifier: verifier_current_session requires an agent-owned tool call'); const extracted = await extractSession(agent, async (ref) => { const stored = await ctx.attachments.readImage(ref, exec.signal); return { data: stored.data, mediaType: stored.ref.mediaType }; }, { fromSeq: args.from_seq, toSeq: args.to_seq, includeAssistantText: args.include_assistant_text, redactPatterns: args.redact_patterns, maxChars: args.max_chars }); const { verifier, selected } = await engine(); const result = await verifier.compare({ problem: extracted.problem, candidateA: extracted.trace, candidateB: '(No useful work or verification was performed.)', repeats: positive(args.repeats, 2, 'repeats'), images: extracted.images }, exec.signal); return { sessionId: extracted.sessionId, problem: extracted.problem, score: result.scoreA, baselineScore: result.scoreB, winner: result.winner, fromSeq: extracted.fromSeq, toSeq: extracted.toSeq, omittedCharacters: extracted.omittedCharacters, calls: result.calls, stats: result.stats, ...route(selected) }; } }));
41
43
  }
42
44
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-llm-verifier",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Configurable DSH-native LLM verifier with a Web settings page",
5
5
  "repository": {
6
6
  "type": "git",
@@ -59,40 +59,40 @@
59
59
  },
60
60
  "peerDependencies": {
61
61
  "@deepseek-ai/cordis": "^4.0.1",
62
- "@deepseek-ai/dsh-tools": "^0.1.0-rc.7",
63
- "@deepseek-ai/dsh-agent": "^0.1.0-rc.7",
64
- "@deepseek-ai/dsh-session": "^0.1.0-rc.7",
65
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.7",
66
- "@deepseek-ai/dsh-attachment": "^0.1.0-rc.7",
67
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.7",
68
- "@deepseek-ai/dsh-credentials": "^0.1.0-rc.7"
62
+ "@deepseek-ai/dsh-tools": "^0.1.0-rc.8",
63
+ "@deepseek-ai/dsh-agent": "^0.1.0-rc.8",
64
+ "@deepseek-ai/dsh-session": "^0.1.0-rc.8",
65
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.8",
66
+ "@deepseek-ai/dsh-attachment": "^0.1.0-rc.8",
67
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.8",
68
+ "@deepseek-ai/dsh-credentials": "^0.1.0-rc.8"
69
69
  },
70
70
  "dependencies": {
71
71
  "schemastery": "^3.18.0"
72
72
  },
73
73
  "devDependencies": {
74
74
  "@deepseek-ai/cordis": "^4.0.1",
75
- "@deepseek-ai/dsh-tools": "^0.1.0-rc.7",
76
- "@deepseek-ai/dsh-agent": "^0.1.0-rc.7",
77
- "@deepseek-ai/dsh-session": "^0.1.0-rc.7",
78
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.7",
79
- "@deepseek-ai/dsh-attachment": "^0.1.0-rc.7",
75
+ "@deepseek-ai/dsh-tools": "^0.1.0-rc.8",
76
+ "@deepseek-ai/dsh-agent": "^0.1.0-rc.8",
77
+ "@deepseek-ai/dsh-session": "^0.1.0-rc.8",
78
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.8",
79
+ "@deepseek-ai/dsh-attachment": "^0.1.0-rc.8",
80
80
  "@types/node": "^22.20.0",
81
81
  "tsdown": "0.22.2",
82
82
  "typescript": "~5.7.2",
83
83
  "vitest": "^3.0.0",
84
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.7",
85
- "@deepseek-ai/dsh-api-remotes": "^0.1.0-rc.7",
86
- "@deepseek-ai/dsh-client-connection": "^0.1.0-rc.7",
87
- "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.7",
88
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.7",
89
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.7",
90
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.7",
84
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.8",
85
+ "@deepseek-ai/dsh-api-remotes": "^0.1.0-rc.8",
86
+ "@deepseek-ai/dsh-client-connection": "^0.1.0-rc.8",
87
+ "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.8",
88
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.8",
89
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.8",
90
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.8",
91
91
  "@deepseek-ai/dsh-client-web-react": "^0.1.0-rc.7",
92
- "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.7",
92
+ "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.8",
93
93
  "react": "^18.2.0",
94
94
  "@types/react": "~18.3.1",
95
- "@deepseek-ai/dsh-credentials": "^0.1.0-rc.7"
95
+ "@deepseek-ai/dsh-credentials": "^0.1.0-rc.8"
96
96
  },
97
97
  "files": [
98
98
  "lib/**/*.js",
package/src/client.tsx CHANGED
@@ -6,7 +6,7 @@ import { Button, Input, StateDot } from '@deepseek-ai/dsh-client-ui-primitives'
6
6
  import { useEffect, useMemo, useState } from 'react'
7
7
 
8
8
  const NS = 'llm-verifier'
9
- interface Values { provider: string; model: string; reasoningEffort?: string; maxTokens: number; maxConcurrency: number; maxRetries: number; timeoutMs: number; cacheMaxEntries: number; estimatedInputUsdPerMillion: number; estimatedOutputUsdPerMillion: number }
9
+ interface Values { enabled: boolean; provider: string; model: string; reasoningEffort?: string; maxTokens: number; maxConcurrency: number; maxRetries: number; timeoutMs: number; cacheMaxEntries: number; estimatedInputUsdPerMillion: number; estimatedOutputUsdPerMillion: number }
10
10
  interface Loaded { groups: ModelProviderGroup[]; settings: SettingsNamespaceView; writable: boolean; failures: string[] }
11
11
  const shell: React.CSSProperties = { display: 'flex', flexDirection: 'column', gap: 18, padding: '8px 4px 32px', color: 'var(--dsw-text-primary)' }
12
12
  const card: React.CSSProperties = { display: 'flex', flexDirection: 'column', gap: 0, padding: '16px 16px 0', border: '1px solid var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.16))', borderRadius: 12, background: 'var(--dsw-alias-bg-module, rgba(20, 31, 57, 0.42))', overflow: 'hidden' }
@@ -14,7 +14,7 @@ const sectionTitle: React.CSSProperties = { display: 'flex', gap: 10, alignItems
14
14
  const row: React.CSSProperties = { display: 'grid', gridTemplateColumns: 'minmax(150px, 1fr) minmax(220px, 1.4fr)', gap: 18, alignItems: 'center', padding: '14px 0', borderBottom: '1px solid var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.16))' }
15
15
  const selectStyle: React.CSSProperties = { width: '100%', minHeight: 38, padding: '0 12px', borderRadius: 10, color: 'var(--dsw-text-primary)', background: 'var(--dsw-surface-sunken)', border: '1px solid var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.16))' }
16
16
  function record(value: unknown): Record<string, unknown> { return typeof value === 'object' && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : {} }
17
- function values(view: SettingsNamespaceView): Values { const v=record(view.value); return { provider:String(v.provider??''),model:String(v.model??''),...(typeof v.reasoningEffort==='string'?{reasoningEffort:v.reasoningEffort}:{}),maxTokens:Number(v.maxTokens??32768),maxConcurrency:Number(v.maxConcurrency??8),maxRetries:Number(v.maxRetries??3),timeoutMs:Number(v.timeoutMs??300000),cacheMaxEntries:Number(v.cacheMaxEntries??10000),estimatedInputUsdPerMillion:Number(v.estimatedInputUsdPerMillion??0),estimatedOutputUsdPerMillion:Number(v.estimatedOutputUsdPerMillion??0) } }
17
+ function values(view: SettingsNamespaceView): Values { const v=record(view.value); return { enabled:v.enabled!==false,provider:String(v.provider??''),model:String(v.model??''),...(typeof v.reasoningEffort==='string'?{reasoningEffort:v.reasoningEffort}:{}),maxTokens:Number(v.maxTokens??32768),maxConcurrency:Number(v.maxConcurrency??8),maxRetries:Number(v.maxRetries??3),timeoutMs:Number(v.timeoutMs??300000),cacheMaxEntries:Number(v.cacheMaxEntries??10000),estimatedInputUsdPerMillion:Number(v.estimatedInputUsdPerMillion??0),estimatedOutputUsdPerMillion:Number(v.estimatedOutputUsdPerMillion??0) } }
18
18
  function message(error: unknown): string { return error instanceof Error ? error.message : String(error) }
19
19
  function Label({title,help}:{title:string;help:string}) { return <div><div style={{fontWeight:600}}>{title}</div><div style={{fontSize:12,color:'var(--dsw-text-secondary)',marginTop:3}}>{help}</div></div> }
20
20
 
@@ -30,6 +30,10 @@ function VerifierSettings({ api }:{api:any}) {
30
30
  const numeric=(key:keyof Values,min=0)=><Input type="number" min={min} value={String(draft[key])} onChange={e=>patch(key,Number(e.target.value) as never)} />
31
31
  return <div style={shell}>
32
32
  <div><h2 style={{margin:'0 0 6px'}}>LLM Verifier</h2><p style={{margin:0,color:'var(--dsw-text-secondary)'}}>选择任意已在 DSH「模型」页配置并启用的模型作为独立裁判。设置实时生效。</p></div>
33
+ <div style={card}><div style={sectionTitle}><StateDot state={draft.enabled?'done':'error'}/><strong>工具开关</strong></div>
34
+ <div style={row}><Label title="启用 Verifier 工具" help={draft.enabled?'四个 verifier 工具可被 Agent 调用,每次调用会向裁判模型发起请求。':'已停用:Agent 调用 verifier 工具会立即返回错误,不产生任何模型请求。'}/><button onClick={()=>patch('enabled',!draft.enabled)} style={{width:80,height:32,borderRadius:16,border:'1px solid var(--dsw-alias-border-l2, rgba(255,255,255,0.16))',background:draft.enabled?'var(--dsw-success, #2f9e5b)':'var(--dsw-surface-sunken)',color:'#fff',cursor:'pointer',fontSize:13,fontWeight:600}}>{draft.enabled?'已启用':'已停用'}</button></div>
35
+ {!draft.enabled&&<div style={{padding:'0 0 14px',fontSize:12,color:'var(--dsw-state-warn-primary, #d9a441)'}}>⚠ 当前停用 verifier_compare / verifier_select / verifier_track / verifier_current_session 四个工具</div>}
36
+ </div>
33
37
  <div style={card}><div style={sectionTitle}><StateDot state="done"/><strong>裁判模型</strong></div>
34
38
  <div style={row}><Label title="供应商" help="只显示当前 DSH 中可路由的供应商"/><select style={selectStyle} value={draft.provider} onChange={e=>{const provider=e.target.value;const first=loaded.groups.find(g=>g.id===provider)?.models[0];setDraft({...draft,provider,...(first?{model:first.id,reasoningEffort:first.reasoning?.defaultEffort}:{})})}}>{loaded.groups.map(g=><option key={g.id} value={g.id}>{g.name} · {g.id}</option>)}</select></div>
35
39
  <div style={row}><Label title="模型" help="模型目录来自 DSH adapter,选择结果会持久化"/><select style={selectStyle} value={draft.model} onChange={e=>{const model=e.target.value;const found=models.find(m=>m.id===model);setDraft({...draft,model,...(found?.reasoning?.defaultEffort?{reasoningEffort:found.reasoning.defaultEffort}:{reasoningEffort:undefined})})}}>{models.map(m=><option key={m.id} value={m.id}>{m.name} · {m.id}</option>)}</select></div>
package/src/config.ts CHANGED
@@ -5,6 +5,7 @@ import z from 'schemastery'
5
5
  export const VERIFIER_SETTINGS_NAMESPACE = settingsNamespace('llm-verifier')
6
6
 
7
7
  export interface Config {
8
+ enabled?: boolean
8
9
  provider?: string
9
10
  model?: string
10
11
  reasoningEffort?: string
@@ -20,6 +21,7 @@ export interface Config {
20
21
  }
21
22
 
22
23
  export interface ResolvedConfig {
24
+ enabled: boolean
23
25
  provider: string
24
26
  model: string
25
27
  reasoningEffort?: string
@@ -35,6 +37,7 @@ export interface ResolvedConfig {
35
37
  }
36
38
 
37
39
  export const Config: z<Config> = z.object({
40
+ enabled: z.boolean().default(true),
38
41
  provider: z.string().default('deepseek-official'),
39
42
  model: z.string().default('deepseek-v4-flash'),
40
43
  reasoningEffort: z.string(),
@@ -70,7 +73,7 @@ export function resolveConfig(config: Config = {}): ResolvedConfig {
70
73
  const estimatedOutputUsdPerMillion = config.estimatedOutputUsdPerMillion ?? 0
71
74
  if (![estimatedInputUsdPerMillion, estimatedOutputUsdPerMillion].every(value => Number.isFinite(value) && value >= 0)) throw new Error('llm-verifier: estimated token prices must be finite non-negative numbers')
72
75
  const reasoningEffort = config.reasoningEffort?.trim()
73
- return { provider, model, ...(reasoningEffort ? { reasoningEffort } : {}), maxRetries, cacheDir, estimatedInputUsdPerMillion, estimatedOutputUsdPerMillion, ...values }
76
+ return { enabled: config.enabled ?? true, provider, model, ...(reasoningEffort ? { reasoningEffort } : {}), maxRetries, cacheDir, estimatedInputUsdPerMillion, estimatedOutputUsdPerMillion, ...values }
74
77
  }
75
78
 
76
79
  export function installVerifierSettings(ctx: Context, entry: ResolvedConfig, onChange: () => void): () => ResolvedConfig {
package/src/index.ts CHANGED
@@ -37,12 +37,13 @@ export function apply(ctx: Context, config: Config = {}): void {
37
37
  }
38
38
  const images = (values: readonly string[] | undefined, signal: AbortSignal) => loadVerifierImages(values, signal)
39
39
  const route = (selected: { provider: string; model: string }) => ({ provider: selected.provider, model: selected.model })
40
+ const requireEnabled = () => { if (!current().enabled) throw new Error('llm-verifier: verifier tools are disabled — enable them in Settings → LLM Verifier') }
40
41
 
41
- ctx.tools.register(defineTool({ name: 'verifier_compare', description: 'Use autonomously when exactly two substantive answers, patches, plans, or execution trajectories need an independent evidence-based comparison and the choice is consequential or uncertain. Do not use for trivial deterministic questions or when there is only one candidate. Uses the verifier model selected in DSH Settings, with top-logprob A–T expectations when supported and explicit-tag fallback otherwise.', parameters: { problem: { type: 'string', required: true }, candidate_a: { type: 'string', required: true }, candidate_b: { type: 'string', required: true }, ...commonParams }, output: { schema: { type: 'object', additionalProperties: false, properties: { scoreA: { type: 'number', required: true }, scoreB: { type: 'number', required: true }, winner: { type: 'string', enum: ['A', 'B', 'tie'], required: true }, criteria: { type: 'array', items: criterionResultSchema, required: true }, calls: { type: 'integer', required: true }, stats: { ...statsSchema, required: true }, provider: { type: 'string', required: true }, model: { type: 'string', required: true } } }, render: (_args, value) => renderJson(value) }, timeoutMs: entry.timeoutMs * 20, async execute(args, exec) { const { verifier, selected } = await engine(); const result = await verifier.compare({ problem: args.problem, candidateA: args.candidate_a, candidateB: args.candidate_b, criteria: normalizeCriteria(args.criteria), repeats: positive(args.repeats, 2, 'repeats'), images: await images(args.images, exec.signal) }, exec.signal); return { ...result, ...route(selected) } } }))
42
+ ctx.tools.register(defineTool({ name: 'verifier_compare', description: 'Use autonomously when exactly two substantive answers, patches, plans, or execution trajectories need an independent evidence-based comparison and the choice is consequential or uncertain. Do not use for trivial deterministic questions or when there is only one candidate. Uses the verifier model selected in DSH Settings, with top-logprob A–T expectations when supported and explicit-tag fallback otherwise.', parameters: { problem: { type: 'string', required: true }, candidate_a: { type: 'string', required: true }, candidate_b: { type: 'string', required: true }, ...commonParams }, output: { schema: { type: 'object', additionalProperties: false, properties: { scoreA: { type: 'number', required: true }, scoreB: { type: 'number', required: true }, winner: { type: 'string', enum: ['A', 'B', 'tie'], required: true }, criteria: { type: 'array', items: criterionResultSchema, required: true }, calls: { type: 'integer', required: true }, stats: { ...statsSchema, required: true }, provider: { type: 'string', required: true }, model: { type: 'string', required: true } } }, render: (_args, value) => renderJson(value) }, timeoutMs: entry.timeoutMs * 20, async execute(args, exec) { requireEnabled(); const { verifier, selected } = await engine(); const result = await verifier.compare({ problem: args.problem, candidateA: args.candidate_a, candidateB: args.candidate_b, criteria: normalizeCriteria(args.criteria), repeats: positive(args.repeats, 2, 'repeats'), images: await images(args.images, exec.signal) }, exec.signal); return { ...result, ...route(selected) } } }))
42
43
 
43
- ctx.tools.register(defineTool({ name: 'verifier_select', description: 'Use autonomously when three or more substantive candidate answers, patches, plans, or trajectories must be ranked and an independent choice is valuable. Use verifier_compare for exactly two candidates; do not generate extra candidates merely to invoke this tool. Uses the configured DSH verifier model and the O(Nk) Probabilistic Pivot Tournament.', parameters: { problem: { type: 'string', required: true }, candidates: { type: 'array', items: { type: 'string' }, required: true }, ...commonParams, pivots: { type: 'integer' }, seed: { type: 'integer' } }, output: { schema: { type: 'object', additionalProperties: false, properties: { index: { type: 'integer', required: true }, best: { type: 'string', required: true }, scores: { type: 'array', items: { type: 'number' }, required: true }, ranking: { type: 'array', items: { type: 'integer' }, required: true }, pivots: { type: 'array', items: { type: 'integer' }, required: true }, comparisons: { type: 'integer', required: true }, calls: { type: 'integer', required: true }, stats: { ...statsSchema, required: true }, provider: { type: 'string', required: true }, model: { type: 'string', required: true } } }, render: (_args, value) => renderJson(value) }, timeoutMs: entry.timeoutMs * 100, async execute(args, exec) { const { verifier, selected } = await engine(); const result = await verifier.select({ problem: args.problem, candidates: args.candidates, criteria: normalizeCriteria(args.criteria), repeats: positive(args.repeats, 2, 'repeats'), pivots: positive(args.pivots, 2, 'pivots'), seed: args.seed ?? 0, images: await images(args.images, exec.signal) }, exec.signal); return { ...result, ...route(selected) } } }))
44
+ ctx.tools.register(defineTool({ name: 'verifier_select', description: 'Use autonomously when three or more substantive candidate answers, patches, plans, or trajectories must be ranked and an independent choice is valuable. Use verifier_compare for exactly two candidates; do not generate extra candidates merely to invoke this tool. Uses the configured DSH verifier model and the O(Nk) Probabilistic Pivot Tournament.', parameters: { problem: { type: 'string', required: true }, candidates: { type: 'array', items: { type: 'string' }, required: true }, ...commonParams, pivots: { type: 'integer' }, seed: { type: 'integer' } }, output: { schema: { type: 'object', additionalProperties: false, properties: { index: { type: 'integer', required: true }, best: { type: 'string', required: true }, scores: { type: 'array', items: { type: 'number' }, required: true }, ranking: { type: 'array', items: { type: 'integer' }, required: true }, pivots: { type: 'array', items: { type: 'integer' }, required: true }, comparisons: { type: 'integer', required: true }, calls: { type: 'integer', required: true }, stats: { ...statsSchema, required: true }, provider: { type: 'string', required: true }, model: { type: 'string', required: true } } }, render: (_args, value) => renderJson(value) }, timeoutMs: entry.timeoutMs * 100, async execute(args, exec) { requireEnabled(); const { verifier, selected } = await engine(); const result = await verifier.select({ problem: args.problem, candidates: args.candidates, criteria: normalizeCriteria(args.criteria), repeats: positive(args.repeats, 2, 'repeats'), pivots: positive(args.pivots, 2, 'pivots'), seed: args.seed ?? 0, images: await images(args.images, exec.signal) }, exec.signal); return { ...result, ...route(selected) } } }))
44
45
 
45
- ctx.tools.register(defineTool({ name: 'verifier_track', description: 'Use autonomously for a genuinely multi-step task when progress at explicit checkpoints is uncertain or needs evidence-based measurement. Do not use for a single completed answer or invent checkpoints that were not supplied by the task history. Trusts observed output rather than narration.', parameters: { problem: { type: 'string', required: true }, steps: { type: 'array', items: { type: 'string' }, required: true }, checkpoints: { type: 'array', items: { type: 'integer' }, required: true }, repeats: commonParams.repeats, images: commonParams.images }, output: { schema: { type: 'object', additionalProperties: false, properties: { scores: { type: 'array', items: { type: 'number' }, required: true }, perRepeat: { type: 'array', items: { type: 'array', items: { type: 'number' } }, required: true }, calls: { type: 'integer', required: true }, stats: { ...statsSchema, required: true }, provider: { type: 'string', required: true }, model: { type: 'string', required: true } } }, render: (_args, value) => renderJson(value) }, timeoutMs: entry.timeoutMs * 20, async execute(args, exec) { const { verifier, selected } = await engine(); const result = await verifier.track(args.problem, args.steps, args.checkpoints, positive(args.repeats, 2, 'repeats'), exec.signal, await images(args.images, exec.signal)); return { ...result, ...route(selected) } } }))
46
+ ctx.tools.register(defineTool({ name: 'verifier_track', description: 'Use autonomously for a genuinely multi-step task when progress at explicit checkpoints is uncertain or needs evidence-based measurement. Do not use for a single completed answer or invent checkpoints that were not supplied by the task history. Trusts observed output rather than narration.', parameters: { problem: { type: 'string', required: true }, steps: { type: 'array', items: { type: 'string' }, required: true }, checkpoints: { type: 'array', items: { type: 'integer' }, required: true }, repeats: commonParams.repeats, images: commonParams.images }, output: { schema: { type: 'object', additionalProperties: false, properties: { scores: { type: 'array', items: { type: 'number' }, required: true }, perRepeat: { type: 'array', items: { type: 'array', items: { type: 'number' } }, required: true }, calls: { type: 'integer', required: true }, stats: { ...statsSchema, required: true }, provider: { type: 'string', required: true }, model: { type: 'string', required: true } } }, render: (_args, value) => renderJson(value) }, timeoutMs: entry.timeoutMs * 20, async execute(args, exec) { requireEnabled(); const { verifier, selected } = await engine(); const result = await verifier.track(args.problem, args.steps, args.checkpoints, positive(args.repeats, 2, 'repeats'), exec.signal, await images(args.images, exec.signal)); return { ...result, ...route(selected) } } }))
46
47
 
47
- ctx.tools.register(defineTool({ name: 'verifier_current_session', description: 'Use autonomously near the end of a non-trivial coding, debugging, migration, deployment, or operations task when independent completion verification would materially reduce risk and the session contains real tool evidence. Do not use for routine conversation, simple factual answers, or every turn. Extracts the current DSH session, applies secret redaction, bounds and truncation, then sends the evidence to the verifier model selected in Settings.', parameters: { from_seq: { type: 'integer' }, to_seq: { type: 'integer' }, include_assistant_text: { type: 'boolean' }, redact_patterns: { type: 'array', items: { type: 'string' } }, max_chars: { type: 'integer' }, repeats: { type: 'integer' } }, output: { schema: { type: 'object', additionalProperties: false, properties: { sessionId: { type: 'string', required: true }, problem: { type: 'string', required: true }, score: { type: 'number', required: true }, baselineScore: { type: 'number', required: true }, winner: { type: 'string', enum: ['A', 'B', 'tie'], required: true }, fromSeq: { type: 'integer', required: true }, toSeq: { type: 'integer', required: true }, omittedCharacters: { type: 'integer', required: true }, calls: { type: 'integer', required: true }, stats: { ...statsSchema, required: true }, provider: { type: 'string', required: true }, model: { type: 'string', required: true } } }, render: (_args, value) => renderJson(value) }, timeoutMs: entry.timeoutMs * 20, async execute(args, exec) { const agent = exec.agent ?? ctx.agents.currentInitiator(); if (agent === undefined) throw new Error('llm-verifier: verifier_current_session requires an agent-owned tool call'); const extracted = await extractSession(agent, async (ref: ImageAttachmentRef) => { const stored = await ctx.attachments.readImage(ref, exec.signal); return { data: stored.data, mediaType: stored.ref.mediaType } }, { fromSeq: args.from_seq, toSeq: args.to_seq, includeAssistantText: args.include_assistant_text, redactPatterns: args.redact_patterns, maxChars: args.max_chars }); const { verifier, selected } = await engine(); const result = await verifier.compare({ problem: extracted.problem, candidateA: extracted.trace, candidateB: '(No useful work or verification was performed.)', repeats: positive(args.repeats, 2, 'repeats'), images: extracted.images }, exec.signal); return { sessionId: extracted.sessionId, problem: extracted.problem, score: result.scoreA, baselineScore: result.scoreB, winner: result.winner, fromSeq: extracted.fromSeq, toSeq: extracted.toSeq, omittedCharacters: extracted.omittedCharacters, calls: result.calls, stats: result.stats, ...route(selected) } } }))
48
+ ctx.tools.register(defineTool({ name: 'verifier_current_session', description: 'Use autonomously near the end of a non-trivial coding, debugging, migration, deployment, or operations task when independent completion verification would materially reduce risk and the session contains real tool evidence. Do not use for routine conversation, simple factual answers, or every turn. Extracts the current DSH session, applies secret redaction, bounds and truncation, then sends the evidence to the verifier model selected in Settings.', parameters: { from_seq: { type: 'integer' }, to_seq: { type: 'integer' }, include_assistant_text: { type: 'boolean' }, redact_patterns: { type: 'array', items: { type: 'string' } }, max_chars: { type: 'integer' }, repeats: { type: 'integer' } }, output: { schema: { type: 'object', additionalProperties: false, properties: { sessionId: { type: 'string', required: true }, problem: { type: 'string', required: true }, score: { type: 'number', required: true }, baselineScore: { type: 'number', required: true }, winner: { type: 'string', enum: ['A', 'B', 'tie'], required: true }, fromSeq: { type: 'integer', required: true }, toSeq: { type: 'integer', required: true }, omittedCharacters: { type: 'integer', required: true }, calls: { type: 'integer', required: true }, stats: { ...statsSchema, required: true }, provider: { type: 'string', required: true }, model: { type: 'string', required: true } } }, render: (_args, value) => renderJson(value) }, timeoutMs: entry.timeoutMs * 20, async execute(args, exec) { requireEnabled(); const agent = exec.agent ?? ctx.agents.currentInitiator(); if (agent === undefined) throw new Error('llm-verifier: verifier_current_session requires an agent-owned tool call'); const extracted = await extractSession(agent, async (ref: ImageAttachmentRef) => { const stored = await ctx.attachments.readImage(ref, exec.signal); return { data: stored.data, mediaType: stored.ref.mediaType } }, { fromSeq: args.from_seq, toSeq: args.to_seq, includeAssistantText: args.include_assistant_text, redactPatterns: args.redact_patterns, maxChars: args.max_chars }); const { verifier, selected } = await engine(); const result = await verifier.compare({ problem: extracted.problem, candidateA: extracted.trace, candidateB: '(No useful work or verification was performed.)', repeats: positive(args.repeats, 2, 'repeats'), images: extracted.images }, exec.signal); return { sessionId: extracted.sessionId, problem: extracted.problem, score: result.scoreA, baselineScore: result.scoreB, winner: result.winner, fromSeq: extracted.fromSeq, toSeq: extracted.toSeq, omittedCharacters: extracted.omittedCharacters, calls: result.calls, stats: result.stats, ...route(selected) } } }))
48
49
  }
@@ -1,324 +0,0 @@
1
- import { settingsNamespace } from "@deepseek-ai/dsh-settings";
2
- import { BlockAssembler, ReasoningEffortId, createUserMessage, deepFreeze } from "@deepseek-ai/dsh-llm";
3
- import { credentialRef } from "@deepseek-ai/dsh-credentials";
4
- //#region src/top-logprobs.ts
5
- var TopLogprobsUnsupportedError = class extends Error {
6
- constructor(message) {
7
- super(message);
8
- this.name = "TopLogprobsUnsupportedError";
9
- }
10
- };
11
- function object(value) {
12
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
13
- }
14
- function text(value) {
15
- return typeof value === "string" && value.trim() ? value.trim() : void 0;
16
- }
17
- function endpoint(baseURL) {
18
- return baseURL.replace(/\/+$/, "") + "/chat/completions";
19
- }
20
- function dataUrl(image) {
21
- return "data:" + image.mediaType + ";base64," + Buffer.from(image.data.buffer, image.data.byteOffset, image.data.byteLength).toString("base64");
22
- }
23
- async function credential(ctx, name) {
24
- if (!name) return void 0;
25
- return (await ctx.get("credentials")?.resolve(credentialRef(name)))?.value;
26
- }
27
- async function resolveTopLogprobRoute(ctx, provider) {
28
- const settings = ctx.get("settings");
29
- if (!settings) return void 0;
30
- if (provider === "deepseek-official") {
31
- const value = object(settings.get(settingsNamespace("llm-deepseek"))) ?? {};
32
- const apiKey = await credential(ctx, text(value.apiKeyEnv) ?? "DEEPSEEK_API_KEY");
33
- if (!apiKey) return void 0;
34
- return {
35
- baseURL: text(value.baseURL) ?? "https://api.deepseek.com",
36
- apiKey,
37
- deepSeekThinking: true
38
- };
39
- }
40
- const profile = object(object(object(settings.get(settingsNamespace("llm-pi-ai")))?.providers)?.[provider]);
41
- if (!profile || profile.api !== "openai-completions") return void 0;
42
- const baseURL = text(profile.baseURL);
43
- if (!baseURL || !/^https:\/\//i.test(baseURL)) return void 0;
44
- const apiKey = await credential(ctx, text(profile.apiKeyEnv));
45
- const rawHeaders = object(profile.headers);
46
- const headers = rawHeaders === void 0 ? void 0 : Object.fromEntries(Object.entries(rawHeaders).filter((entry) => typeof entry[1] === "string"));
47
- return {
48
- baseURL,
49
- ...apiKey ? { apiKey } : {},
50
- ...headers ? { headers } : {},
51
- deepSeekThinking: false
52
- };
53
- }
54
- async function callTopLogprobs(route, model, prompt, maxTokens, reasoningEffort, signal, images) {
55
- const content = images?.length ? [{
56
- type: "text",
57
- text: prompt
58
- }, ...images.map((image) => ({
59
- type: "image_url",
60
- image_url: { url: dataUrl(image) }
61
- }))] : prompt;
62
- const thinking = route.deepSeekThinking && reasoningEffort ? reasoningEffort === "off" ? { thinking: { type: "disabled" } } : {
63
- thinking: { type: "enabled" },
64
- reasoning_effort: reasoningEffort
65
- } : {};
66
- const response = await fetch(endpoint(route.baseURL), {
67
- method: "POST",
68
- redirect: "error",
69
- signal,
70
- headers: {
71
- "content-type": "application/json",
72
- ...route.apiKey ? { authorization: "Bearer " + route.apiKey } : {},
73
- ...route.headers
74
- },
75
- body: JSON.stringify({
76
- model,
77
- messages: [{
78
- role: "user",
79
- content
80
- }],
81
- max_tokens: maxTokens,
82
- temperature: 1,
83
- logprobs: true,
84
- top_logprobs: 20,
85
- ...thinking
86
- })
87
- });
88
- const raw = await response.text();
89
- if (!response.ok) {
90
- const excerpt = raw.slice(0, 1e3);
91
- if ([
92
- 400,
93
- 404,
94
- 405,
95
- 415,
96
- 422
97
- ].includes(response.status) && /logprob|unsupported|unknown|invalid|not support/i.test(excerpt)) throw new TopLogprobsUnsupportedError("provider rejected top_logprobs: HTTP " + response.status + " " + excerpt);
98
- throw new Error("llm-verifier: top_logprobs request failed with HTTP " + response.status + ": " + excerpt);
99
- }
100
- let body;
101
- try {
102
- body = object(JSON.parse(raw)) ?? {};
103
- } catch {
104
- throw new Error("llm-verifier: top_logprobs endpoint returned invalid JSON");
105
- }
106
- const choice = object((Array.isArray(body.choices) ? body.choices : [])[0]);
107
- const message = object(choice?.message);
108
- const answer = typeof message?.content === "string" ? message.content : "";
109
- const logprobs = object(choice?.logprobs);
110
- const rows = Array.isArray(logprobs?.content) ? logprobs.content : [];
111
- if (!rows.length) throw new TopLogprobsUnsupportedError("provider returned no token logprobs");
112
- const tokens = [];
113
- const positions = [];
114
- for (const rawRow of rows) {
115
- const row = object(rawRow) ?? {};
116
- const token = typeof row.token === "string" ? row.token : "";
117
- tokens.push(token);
118
- const alternatives = (Array.isArray(row.top_logprobs) ? row.top_logprobs : []).flatMap((value) => {
119
- const item = object(value);
120
- return item && typeof item.token === "string" && typeof item.logprob === "number" ? [{
121
- token: item.token,
122
- logprob: item.logprob
123
- }] : [];
124
- });
125
- if (!alternatives.length && typeof row.logprob === "number") alternatives.push({
126
- token,
127
- logprob: row.logprob
128
- });
129
- positions.push(alternatives);
130
- }
131
- const rawUsage = object(body.usage) ?? {};
132
- const promptDetails = object(rawUsage.prompt_tokens_details) ?? {};
133
- const completionDetails = object(rawUsage.completion_tokens_details) ?? {};
134
- const cached = Number(rawUsage.prompt_cache_hit_tokens ?? promptDetails.cached_tokens ?? 0) || 0;
135
- const input = Number(rawUsage.prompt_tokens ?? 0) || 0;
136
- return {
137
- text: answer,
138
- tokens,
139
- positions,
140
- scoringMode: "top-logprobs",
141
- usage: {
142
- calls: 1,
143
- attempts: 1,
144
- retries: 0,
145
- inputTokens: Math.max(0, input - cached),
146
- cachedInputTokens: cached,
147
- outputTokens: Number(rawUsage.completion_tokens ?? 0) || 0,
148
- reasoningTokens: Number(completionDetails.reasoning_tokens ?? 0) || 0
149
- }
150
- };
151
- }
152
- var TopLogprobCapabilityCache = class {
153
- unsupported = /* @__PURE__ */ new Set();
154
- isUnsupported(provider, model) {
155
- return this.unsupported.has(provider + "\0" + model);
156
- }
157
- markUnsupported(provider, model) {
158
- this.unsupported.add(provider + "\0" + model);
159
- }
160
- };
161
- //#endregion
162
- //#region src/caller.ts
163
- function failureMessage(finish) {
164
- if (finish.kind === "error" || finish.kind === "aborted") return finish.failure.message;
165
- if (finish.kind === "max-tokens") return "verifier response reached max tokens before completing its answer";
166
- }
167
- async function delay(ms, signal) {
168
- if (signal?.aborted) throw signal.reason;
169
- await new Promise((resolve, reject) => {
170
- const timer = setTimeout(resolve, ms);
171
- const abort = () => {
172
- clearTimeout(timer);
173
- reject(signal?.reason);
174
- };
175
- signal?.addEventListener("abort", abort, { once: true });
176
- });
177
- }
178
- function usage(attempts, value = {}) {
179
- return {
180
- calls: 1,
181
- attempts,
182
- retries: attempts - 1,
183
- inputTokens: value.inputTokens ?? 0,
184
- cachedInputTokens: (value.cacheReadTokens ?? 0) + (value.cacheWriteTokens ?? 0),
185
- outputTokens: value.outputTokens ?? 0,
186
- reasoningTokens: value.reasoningTokens ?? 0
187
- };
188
- }
189
- async function callExplicitTag(config, prompt, signal, images) {
190
- let attempt = 0;
191
- while (true) {
192
- attempt += 1;
193
- const controller = new AbortController();
194
- const timeout = setTimeout(() => controller.abort(/* @__PURE__ */ new Error("llm-verifier: request timed out")), config.timeoutMs);
195
- const abort = () => controller.abort(signal?.reason);
196
- signal?.addEventListener("abort", abort, { once: true });
197
- try {
198
- const content = [{
199
- type: "text",
200
- text: prompt
201
- }];
202
- for (const image of images ?? []) {
203
- const ref = await config.attachments.saveImage({
204
- data: image.data,
205
- mediaType: image.mediaType
206
- });
207
- content.push({
208
- type: "image",
209
- attachment: ref
210
- });
211
- }
212
- const messages = [createUserMessage({
213
- content,
214
- source: {
215
- kind: "plugin",
216
- plugin: "dsh-llm-verifier"
217
- }
218
- })];
219
- const assembler = new BlockAssembler();
220
- const options = deepFreeze({
221
- provider: config.provider,
222
- model: config.model,
223
- ...config.reasoningEffort ? { reasoningEffort: ReasoningEffortId(config.reasoningEffort) } : {},
224
- messages,
225
- maxTokens: config.maxTokens,
226
- temperature: 1,
227
- signal: controller.signal
228
- });
229
- for await (const chunk of config.llm.stream(options)) assembler.push(chunk);
230
- const failed = failureMessage(assembler.finish);
231
- if (failed !== void 0) throw new Error("llm-verifier: model call failed: " + failed);
232
- const text = assembler.blocks().filter((block) => block.type === "text").map((block) => block.text).join("");
233
- if (!text.trim()) throw new Error("llm-verifier: selected DSH model produced no text");
234
- return {
235
- text,
236
- tokens: [],
237
- positions: [],
238
- scoringMode: "explicit-tag",
239
- usage: usage(attempt, assembler.usage)
240
- };
241
- } catch (error) {
242
- if (signal?.aborted) throw signal.reason;
243
- if (attempt > config.maxRetries || !(error instanceof Error) || !/rate|quota|timeout|timed out|temporar|network|fetch|socket|5dd/i.test(error.message)) throw error;
244
- await delay(Math.min(3e4, config.retryBaseDelayMs * 2 ** (attempt - 1) * (.8 + Math.random() * .4)), signal);
245
- } finally {
246
- clearTimeout(timeout);
247
- signal?.removeEventListener("abort", abort);
248
- }
249
- }
250
- }
251
- var RequestLimiter = class {
252
- limit;
253
- active = 0;
254
- queue = [];
255
- constructor(limit) {
256
- this.limit = limit;
257
- }
258
- async run(operation, signal) {
259
- if (this.active >= this.limit) await new Promise((resolve, reject) => {
260
- const enter = () => {
261
- signal?.removeEventListener("abort", abort);
262
- resolve();
263
- };
264
- const abort = () => {
265
- const index = this.queue.indexOf(enter);
266
- if (index >= 0) this.queue.splice(index, 1);
267
- reject(signal?.reason);
268
- };
269
- this.queue.push(enter);
270
- signal?.addEventListener("abort", abort, { once: true });
271
- });
272
- if (signal?.aborted) throw signal.reason;
273
- this.active += 1;
274
- try {
275
- return await operation();
276
- } finally {
277
- this.active -= 1;
278
- this.queue.shift()?.();
279
- }
280
- }
281
- };
282
- async function callAutomatic(config, prompt, signal, images) {
283
- if (!config.topLogprobCapabilities.isUnsupported(config.provider, config.model)) {
284
- const route = await resolveTopLogprobRoute(config.ctx, config.provider);
285
- if (route !== void 0) try {
286
- return await callTopLogprobs(route, config.model, prompt, config.maxTokens, config.reasoningEffort, signal, images);
287
- } catch (error) {
288
- if (!(error instanceof TopLogprobsUnsupportedError)) throw error;
289
- config.topLogprobCapabilities.markUnsupported(config.provider, config.model);
290
- }
291
- else config.topLogprobCapabilities.markUnsupported(config.provider, config.model);
292
- }
293
- return callExplicitTag(config, prompt, signal, images);
294
- }
295
- async function callVerifier(config, prompt, signal, images) {
296
- const invoke = () => callAutomatic(config, prompt, signal, images);
297
- return config.limiter === void 0 ? invoke() : config.limiter.run(invoke, signal);
298
- }
299
- function addUsage(target, source) {
300
- for (const key of [
301
- "calls",
302
- "attempts",
303
- "retries",
304
- "inputTokens",
305
- "cachedInputTokens",
306
- "outputTokens",
307
- "reasoningTokens"
308
- ]) target[key] += source[key];
309
- }
310
- function emptyUsage() {
311
- return {
312
- calls: 0,
313
- attempts: 0,
314
- retries: 0,
315
- inputTokens: 0,
316
- cachedInputTokens: 0,
317
- outputTokens: 0,
318
- reasoningTokens: 0
319
- };
320
- }
321
- //#endregion
322
- export { TopLogprobCapabilityCache as a, emptyUsage as i, addUsage as n, callVerifier as r, RequestLimiter as t };
323
-
324
- //# sourceMappingURL=caller-BgqCctCh.js.map