@mofeng2223/dsh-claude-provider 0.1.0 → 0.2.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
@@ -1,16 +1,28 @@
1
1
  # DSH Claude Provider
2
2
 
3
- `@mofeng2223/dsh-claude-provider` adds a visible Custom Claude provider type to
4
- DeepSeek Harness and fixes Anthropic Messages routes that still
3
+ English | [简体中文](./README.zh-CN.md)
4
+
5
+ `@mofeng2223/dsh-claude-provider` adds a visible, reusable Claude provider type
6
+ to DeepSeek Harness and fixes its routes when an Anthropic Messages adapter still
5
7
  serialize reasoning as `thinking.type: enabled` with `budget_tokens`. For the
6
- configured provider/model pairs, it converts the outgoing request to
8
+ explicitly registered provider/model pairs, it converts the outgoing request to
7
9
  `thinking.type: adaptive` plus `output_config.effort`.
8
10
 
9
- The bundled defaults recognize every `claude-*` model on every provider route.
10
- At request time the plugin reads the model's configured effort list: four- and
11
- five-level profiles use adaptive thinking, while an On/Off profile keeps the
12
- legacy budget-based path. This lets future Claude model IDs work without a
13
- plugin update.
11
+ Use **Add Claude provider** to create any number of Provider IDs under this one
12
+ type. Membership is stored explicitly in the plugin's own settings namespace.
13
+ The plugin never infers membership from the `anthropic-messages` protocol, a
14
+ `claude-*` model ID, or a default reasoning value. Generic custom providers and
15
+ built-in providers are therefore untouched even when they use the same wire
16
+ protocol.
17
+
18
+ Only explicitly typed providers receive the Claude badge, native model
19
+ discovery, recorded capacity defaults, per-model thinking modes, and adaptive
20
+ request conversion. At request time the plugin reads the selected model's
21
+ configured effort list: four- and five-level profiles use adaptive thinking,
22
+ while an On/Off profile keeps the legacy budget-based path. Newly entered
23
+ models default to five levels, so future model IDs can be configured without a
24
+ plugin update. Claude providers select High by default; toggle-only models are
25
+ shown as On/Off and default to On, without an extra provider-default choice.
14
26
 
15
27
  The same package supplies both the Host request adapter and the Web settings
16
28
  client. The client marks its model-directory request with a private discovery
@@ -57,7 +69,7 @@ npx @deepseek-ai/dsh plugin --profile headless remove @mofeng2223/dsh-claude-pro
57
69
  ```
58
70
 
59
71
  Uninstall intentionally leaves `~/.dsh/settings.yaml` and stored credentials
60
- alone. Existing custom providers therefore remain visible as ordinary custom
72
+ alone. Existing Claude-type providers therefore remain visible as ordinary custom
61
73
  `anthropic-messages` routes, but lose this plugin's Claude badge, per-model
62
74
  thinking-mode controls, native Anthropic model discovery, and adaptive request
63
75
  rewrite.
@@ -65,8 +77,8 @@ rewrite.
65
77
  The plugin never stores or logs API keys. Model discovery uses the one-shot key
66
78
  typed into the form or resolves the existing route's credential through DSH;
67
79
  the value is sent only to that route's Anthropic Models API. Its request
68
- transformer runs only inside matching `llm/stream` calls and leaves nonmatching
69
- providers and models untouched.
80
+ transformer runs only inside `llm/stream` calls for Provider IDs explicitly
81
+ registered under this plugin's type and leaves every other provider untouched.
70
82
 
71
83
  The generated `.tgz` package is portable. Copy it to Windows and install it by
72
84
  its local path; no source build or platform-specific dependency is required.
@@ -0,0 +1,56 @@
1
+ # DSH Claude Provider
2
+
3
+ [English](./README.md) | 简体中文
4
+
5
+ `@mofeng2223/dsh-claude-provider` 为 DeepSeek Harness 增加一个可见且可复用的“Claude 提供方”类型,并修正其 Anthropic Messages 适配器仍将思考参数序列化为 `thinking.type: enabled` 与 `budget_tokens` 的问题。对于明确登记在此类型下的提供方和模型组合,插件会将请求转换为 `thinking.type: adaptive` 与 `output_config.effort`。
6
+
7
+ 通过“添加 Claude 提供方”可以在同一个类型下创建任意多个 Provider ID。类型归属会明确保存在插件自己的设置命名空间中。插件不会根据 `anthropic-messages` 协议、`claude-*` 模型 ID 或默认思考强度推断类型。因此,即使通用自定义提供方或内置提供方使用相同协议,插件也不会修改它们。
8
+
9
+ 只有明确登记为此类型的提供方才会获得 Claude 标识、原生模型发现、已记录容量预填、按模型设置思考模式以及 adaptive 请求转换。发送请求时,插件会读取所选模型配置的思考档位:四档和五档配置使用 adaptive thinking;开启/关闭配置继续使用旧版基于预算的思考方式。手动新增的模型默认使用五档,因此未来新增的模型 ID 也可以直接配置。Claude 提供方默认选择 High;仅支持开关的模型显示为 On/Off,并默认选择 On,不再额外显示 Default。
10
+
11
+ 同一个 npm 包同时提供 Host 请求适配器和 Web 设置客户端。客户端会使用内部发现协议标记“获取模型目录”请求,再由 Host 部分处理。只有这个请求会调用 Anthropic 原生的 `GET /v1/models` 接口,并使用 `x-api-key` 和 `anthropic-version` 请求头。
12
+
13
+ 插件支持 Anthropic 游标分页,并会在提供方返回相关字段时读取 `display_name`、`max_input_tokens` 和 `max_tokens`。插件不会尝试使用 OpenAI 兼容的模型列表接口,也不会回退到 Bearer 鉴权。
14
+
15
+ ## 安装
16
+
17
+ 直接安装 npm 上发布的插件:
18
+
19
+ ```sh
20
+ npx @deepseek-ai/dsh plugin --profile web add @mofeng2223/dsh-claude-provider
21
+ npx @deepseek-ai/dsh plugin --profile headless add @mofeng2223/dsh-claude-provider
22
+ ```
23
+
24
+ 安装完成后,重启对应的 Harness 进程。
25
+
26
+ 修改插件源码后,可以重新构建 Web 客户端并生成单一分发包:
27
+
28
+ ```sh
29
+ npm run build
30
+ npm pack --pack-destination dist
31
+ ```
32
+
33
+ 日常安装不建议直接使用源码目录。直接安装源码目录会创建 pnpm `link:` 依赖;在目前的 DSH/pnpm 版本中,移除依赖和 bundle 注册后,顶层符号链接仍有可能残留。通过 npm 或 `.tgz` 归档安装时,文件由包管理器管理,卸载后不会留下这类链接。
34
+
35
+ ## 调试
36
+
37
+ 临时设置 `DSH_CLAUDE_PROVIDER_DEBUG=1`,可以为每个被改写的请求输出一行经过脱敏的诊断信息。诊断内容只包含提供方、模型和所选思考强度,不包含 API 密钥。
38
+
39
+ ## 卸载
40
+
41
+ ```sh
42
+ npx @deepseek-ai/dsh plugin --profile web remove @mofeng2223/dsh-claude-provider
43
+ npx @deepseek-ai/dsh plugin --profile headless remove @mofeng2223/dsh-claude-provider
44
+ ```
45
+
46
+ 卸载插件时会保留 `~/.dsh/settings.yaml` 和已经保存的凭据。现有的 Claude 类型提供方仍会作为普通的 `anthropic-messages` 自定义路由显示,但会失去本插件提供的 Claude 标识、按模型配置思考模式、Anthropic 原生模型发现以及 adaptive 请求改写功能。
47
+
48
+ ## 安全与隐私
49
+
50
+ 插件不会存储或记录 API 密钥。获取模型列表时,插件会使用表单中临时输入的密钥,或者通过 DSH 解析现有路由保存的凭据。密钥只会发送到该路由对应的 Anthropic Models API。
51
+
52
+ 请求转换器只会在明确登记到本插件类型的 Provider ID 所对应的 `llm/stream` 调用中运行,不会修改任何其他提供方。
53
+
54
+ ## Windows 支持
55
+
56
+ 发布到 npm 的插件和生成的 `.tgz` 包均可跨平台使用。Windows 可以直接通过 npm 包名或本地 `.tgz` 路径安装,无需在目标电脑上重新构建,也不依赖平台专用的原生模块。
package/cordis.patch.yml CHANGED
@@ -3,10 +3,6 @@
3
3
  name: '@mofeng2223/dsh-claude-provider'
4
4
  config:
5
5
  debug: !!js process.env.DSH_CLAUDE_PROVIDER_DEBUG === '1'
6
- routes:
7
- - provider: '*'
8
- models: &adaptive_claude_models
9
- - claude-*
10
6
  effortMap:
11
7
  minimal: low
12
8
  low: low
package/lib/client.js CHANGED
@@ -542,7 +542,7 @@ window.__ModuleLoader__.load({
542
542
  const [providersResponse, settingsResponse] = await Promise.all([this.api.llm.providers({}), this.api.settings.describe({})]);
543
543
  if (!providersResponse.result.ok) throw new Error(providersResponse.result.error.message);
544
544
  if (!settingsResponse.result.ok) throw new Error(settingsResponse.result.error.message);
545
- providers = providersResponse.result.value.providers;
545
+ providers = providersResponse.result.value.providers.filter((entry) => entry.provider !== CLAUDE_PROVIDER_DIRECTORY_SENTINEL);
546
546
  writable = settingsResponse.result.value.writable;
547
547
  views = settingsResponse.result.value.namespaces;
548
548
  } catch (error) {
@@ -1088,9 +1088,9 @@ window.__ModuleLoader__.load({
1088
1088
  const [route, setRoute] = (0, react.useState)("");
1089
1089
  const [displayName, setDisplayName] = (0, react.useState)("");
1090
1090
  const [baseURL, setBaseURL] = (0, react.useState)("");
1091
- const [providerType, setProviderType] = (0, react.useState)("generic");
1092
- const isAdaptiveClaude = providerType === "claude-adaptive";
1093
- const [protocol, setProtocol] = (0, react.useState)(protocols[0] ?? "");
1091
+ const providerType = props.providerType ?? "generic";
1092
+ const isAdaptiveClaude = providerType === CLAUDE_PROVIDER_TYPE;
1093
+ const [protocol, setProtocol] = (0, react.useState)(isAdaptiveClaude ? "anthropic-messages" : protocols[0] ?? "");
1094
1094
  const [keyDraft, setKeyDraft] = (0, react.useState)("");
1095
1095
  const [models, setModels] = (0, react.useState)([]);
1096
1096
  const [busy, setBusy] = (0, react.useState)(false);
@@ -1136,6 +1136,13 @@ window.__ModuleLoader__.load({
1136
1136
  if (!response.result.ok) return response.result.error.message;
1137
1137
  setCommitted(true);
1138
1138
  }
1139
+ if (isAdaptiveClaude) {
1140
+ const typed = await api.settings.mutate({
1141
+ ns: CLAUDE_PROVIDER_SETTINGS_NS,
1142
+ ops: [{ op: "set", path: ["providerTypes", route], value: CLAUDE_PROVIDER_TYPE }]
1143
+ });
1144
+ if (!typed.result.ok) return typed.result.error.message;
1145
+ }
1139
1146
  if (storesKey) {
1140
1147
  const stored = await api.credentials.set({
1141
1148
  ref: keyRef,
@@ -1167,30 +1174,9 @@ window.__ModuleLoader__.load({
1167
1174
  className: ModelsSection_module_css_default["editorHeader"],
1168
1175
  children: (0, react_jsx_runtime.jsx)("span", {
1169
1176
  className: ModelsSection_module_css_default["editorTitle"],
1170
- children: t("customTitle")
1177
+ children: t(isAdaptiveClaude ? "claudeCustomTitle" : "customTitle")
1171
1178
  })
1172
1179
  }),
1173
- (0, react_jsx_runtime.jsxs)("div", {
1174
- className: ModelsSection_module_css_default["field"],
1175
- children: [(0, react_jsx_runtime.jsx)("span", {
1176
- className: ModelsSection_module_css_default["fieldLabel"],
1177
- children: t("customProviderType")
1178
- }), (0, react_jsx_runtime.jsxs)("select", {
1179
- className: `${ModelsSection_module_css_default["input"]} ${ModelsSection_module_css_default["selectInput"]}`,
1180
- value: providerType,
1181
- "aria-label": t("customProviderType"),
1182
- disabled: profileDisabled,
1183
- onChange: (event) => {
1184
- const nextType = event.target.value;
1185
- setProviderType(nextType);
1186
- if (nextType === "claude-adaptive") {
1187
- setProtocol("anthropic-messages");
1188
- setModels((current) => current.map(withDefaultClaudeReasoning));
1189
- }
1190
- },
1191
- children: [(0, react_jsx_runtime.jsx)("option", { value: "generic", children: t("customProviderTypeGeneric") }), (0, react_jsx_runtime.jsx)("option", { value: "claude-adaptive", children: t("customProviderTypeClaude") })]
1192
- })]
1193
- }),
1194
1180
  isAdaptiveClaude ? (0, react_jsx_runtime.jsxs)("p", {
1195
1181
  className: ModelsSection_module_css_default["savedNotice"],
1196
1182
  role: "status",
@@ -1662,7 +1648,7 @@ window.__ModuleLoader__.load({
1662
1648
  ...catalogProps,
1663
1649
  probe,
1664
1650
  probeBlocked: keyFailure,
1665
- thinkingPresets: probeApi === "anthropic-messages",
1651
+ thinkingPresets: props.thinkingPresets === true,
1666
1652
  api
1667
1653
  })
1668
1654
  ]
@@ -1761,6 +1747,11 @@ window.__ModuleLoader__.load({
1761
1747
  }]
1762
1748
  });
1763
1749
  if (!response.result.ok) return response.result.error.message;
1750
+ const typeResponse = await api.settings.mutate({
1751
+ ns: CLAUDE_PROVIDER_SETTINGS_NS,
1752
+ ops: [{ op: "unset", path: ["providerTypes", target.provider] }]
1753
+ });
1754
+ if (!typeResponse.result.ok) return typeResponse.result.error.message;
1764
1755
  } catch (error) {
1765
1756
  return messageOf$1(error);
1766
1757
  }
@@ -1793,6 +1784,9 @@ window.__ModuleLoader__.load({
1793
1784
  };
1794
1785
  }
1795
1786
  const CLAUDE_DISCOVERY_API = "mofeng-anthropic-models";
1787
+ const CLAUDE_PROVIDER_SETTINGS_NS = "dsh-claude-provider";
1788
+ const CLAUDE_PROVIDER_TYPE = "claude-adaptive";
1789
+ const CLAUDE_PROVIDER_DIRECTORY_SENTINEL = "dsh-claude-provider-type";
1796
1790
  const CLAUDE_FIVE_EFFORTS = Object.freeze({ low: "low", medium: "medium", high: "high", xhigh: "xhigh", max: "max" });
1797
1791
  const CLAUDE_FOUR_EFFORTS = Object.freeze({ low: "low", medium: "medium", high: "high", max: "max" });
1798
1792
  const CLAUDE_TOGGLE_EFFORTS = Object.freeze({ off: null, high: "high" });
@@ -1881,11 +1875,8 @@ window.__ModuleLoader__.load({
1881
1875
  });
1882
1876
  }
1883
1877
  function isClaudeProvider(row, state) {
1884
- if (row.entry.settingsNs !== "llm-pi-ai") return false;
1885
- const namespace = state.namespaces.get("llm-pi-ai");
1886
- if (namespace === void 0) return false;
1887
- const profile = (0, _deepseek_ai_dsh_client_schema_form.getPath)(namespace.value, row.entry.settingsPath);
1888
- return profile !== null && typeof profile === "object" && profile.api === "anthropic-messages" && Array.isArray(profile.models) && profile.models.length > 0;
1878
+ const namespace = state.namespaces.get(CLAUDE_PROVIDER_SETTINGS_NS);
1879
+ return namespace?.value?.providerTypes?.[row.entry.provider] === CLAUDE_PROVIDER_TYPE;
1889
1880
  }
1890
1881
  /** Stable visible and accessible identity for one provider target. */
1891
1882
  function providerTargetLabel(target) {
@@ -1919,7 +1910,7 @@ window.__ModuleLoader__.load({
1919
1910
  const [deleting, setDeleting] = (0, react.useState)(false);
1920
1911
  const [deleteFailure, setDeleteFailure] = (0, react.useState)(void 0);
1921
1912
  const [savedTarget, setSavedTarget] = (0, react.useState)(void 0);
1922
- const [declaring, setDeclaring] = (0, react.useState)(false);
1913
+ const [declaringType, setDeclaringType] = (0, react.useState)(void 0);
1923
1914
  const [dismissedSetup, setDismissedSetup] = (0, react.useState)(() => /* @__PURE__ */ new Set());
1924
1915
  const announceSaved = (target) => {
1925
1916
  controller.load().then(() => {
@@ -1929,7 +1920,7 @@ window.__ModuleLoader__.load({
1929
1920
  const closeEditor = (changed, target) => {
1930
1921
  setEditing(void 0);
1931
1922
  setAdding(false);
1932
- setDeclaring(false);
1923
+ setDeclaringType(void 0);
1933
1924
  if (changed) announceSaved(target);
1934
1925
  };
1935
1926
  /**
@@ -2026,6 +2017,7 @@ window.__ModuleLoader__.load({
2026
2017
  children: renderProviderEditor({
2027
2018
  target,
2028
2019
  namespace,
2020
+ thinkingPresets: isClaudeProvider(row, state),
2029
2021
  api,
2030
2022
  t,
2031
2023
  readOnly: !state.writable,
@@ -2055,7 +2047,7 @@ window.__ModuleLoader__.load({
2055
2047
  }) : null,
2056
2048
  adaptiveClaude ? (0, react_jsx_runtime.jsx)("span", {
2057
2049
  className: ModelsSection_module_css_default["rowTag"],
2058
- children: t("adaptiveClaudeTag")
2050
+ children: t("claudeTypeTag")
2059
2051
  }) : null,
2060
2052
  credentialConfigured ? (0, react_jsx_runtime.jsx)("span", {
2061
2053
  className: `${ModelsSection_module_css_default["credentialDot"]} ${ModelsSection_module_css_default["credentialDotConfigured"]}`,
@@ -2077,7 +2069,7 @@ window.__ModuleLoader__.load({
2077
2069
  "aria-label": providerCopy(t("editProvider"), target),
2078
2070
  onClick: () => {
2079
2071
  setSavedTarget(void 0);
2080
- setDeclaring(false);
2072
+ setDeclaringType(void 0);
2081
2073
  setAdding(false);
2082
2074
  setEditing(open ? void 0 : target);
2083
2075
  },
@@ -2098,6 +2090,7 @@ window.__ModuleLoader__.load({
2098
2090
  }), open ? renderProviderEditor({
2099
2091
  target,
2100
2092
  namespace,
2093
+ thinkingPresets: isClaudeProvider(row, state),
2101
2094
  api,
2102
2095
  t,
2103
2096
  readOnly: !state.writable,
@@ -2145,18 +2138,19 @@ window.__ModuleLoader__.load({
2145
2138
  closeEditor(changed, addTarget);
2146
2139
  }
2147
2140
  }, addTarget.provider)]
2148
- }) : declaring ? (0, react_jsx_runtime.jsx)("div", {
2141
+ }) : declaringType !== void 0 ? (0, react_jsx_runtime.jsx)("div", {
2149
2142
  className: ModelsSection_module_css_default["addCard"],
2150
2143
  children: (0, react_jsx_runtime.jsx)(CustomProviderCard, {
2151
2144
  taken: state.rows.map((row) => row.entry.provider),
2152
2145
  protocols,
2146
+ providerType: declaringType,
2153
2147
  /* v8 ignore next -- the card only opens from a button disabled without this namespace */
2154
2148
  revision: state.namespaces.get("llm-pi-ai")?.revision ?? 0,
2155
2149
  api,
2156
2150
  t,
2157
2151
  readOnly: !state.writable,
2158
2152
  onClose: (changed) => {
2159
- setDeclaring(false);
2153
+ setDeclaringType(void 0);
2160
2154
  if (changed) controller.load();
2161
2155
  }
2162
2156
  })
@@ -2171,7 +2165,7 @@ window.__ModuleLoader__.load({
2171
2165
  /* v8 ignore next -- the button is disabled while nothing is addable */
2172
2166
  if (first === void 0) return;
2173
2167
  setSavedTarget(void 0);
2174
- setDeclaring(false);
2168
+ setDeclaringType(void 0);
2175
2169
  setAdding(true);
2176
2170
  setEditing(targetOf(first));
2177
2171
  },
@@ -2184,9 +2178,20 @@ window.__ModuleLoader__.load({
2184
2178
  setSavedTarget(void 0);
2185
2179
  setAdding(false);
2186
2180
  setEditing(void 0);
2187
- setDeclaring(true);
2181
+ setDeclaringType("generic");
2188
2182
  },
2189
2183
  children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 14 }), t("customAdd")]
2184
+ }), (0, react_jsx_runtime.jsxs)("button", {
2185
+ type: "button",
2186
+ className: ModelsSection_module_css_default["addButton"],
2187
+ disabled: !state.namespaces.has(CLAUDE_PROVIDER_SETTINGS_NS) || !state.writable,
2188
+ onClick: () => {
2189
+ setSavedTarget(void 0);
2190
+ setAdding(false);
2191
+ setEditing(void 0);
2192
+ setDeclaringType(CLAUDE_PROVIDER_TYPE);
2193
+ },
2194
+ children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 14 }), t("claudeCustomAdd")]
2190
2195
  })]
2191
2196
  })
2192
2197
  }),
@@ -2662,9 +2667,11 @@ window.__ModuleLoader__.load({
2662
2667
  customAdd: "Add a custom provider",
2663
2668
  customTitle: "Custom provider",
2664
2669
  customTag: "Custom",
2665
- customProviderType: "Provider type",
2666
- customProviderTypeGeneric: "Generic custom provider",
2667
- customProviderTypeClaude: "Custom Claude provider",
2670
+ claudeCustomAdd: "Add Claude provider",
2671
+ claudeCustomTitle: "Claude provider",
2672
+ claudeTypeUnavailable: "Claude provider type storage is unavailable.",
2673
+ claudeTypeTag: "Claude provider",
2674
+ claudeTypeIntro: "Only this provider type enables Claude model discovery, recorded capacities, thinking modes, and adaptive request conversion.",
2668
2675
  adaptiveClaudeEnabled: "Choose a thinking mode for each model. New models default to five levels.",
2669
2676
  adaptiveClaudeHelpLabel: "Thinking mode help",
2670
2677
  adaptiveClaudeHelp: "Five (low / medium / high / xhigh / max): Fable 5, Opus 5, Opus 4.8, Opus 4.7, Sonnet 5\nFour (low / medium / high / max): Opus 4.6, Sonnet 4.6\nOn / Off: Haiku 4.5",
@@ -2769,9 +2776,11 @@ window.__ModuleLoader__.load({
2769
2776
  customAdd: "添加自定义提供方",
2770
2777
  customTitle: "自定义提供方",
2771
2778
  customTag: "自定义",
2772
- customProviderType: "供应商类型",
2773
- customProviderTypeGeneric: "通用自定义供应商",
2774
- customProviderTypeClaude: "自定义 Claude 供应商",
2779
+ claudeCustomAdd: "添加 Claude 提供方",
2780
+ claudeCustomTitle: "Claude 提供方",
2781
+ claudeTypeUnavailable: "Claude 提供方类型存储不可用。",
2782
+ claudeTypeTag: "Claude 提供方",
2783
+ claudeTypeIntro: "只有此提供方类型会启用 Claude 模型发现、已记录容量、思考模式和 adaptive 请求转换。",
2775
2784
  adaptiveClaudeEnabled: "每个模型可单独选择思考模式;新增模型默认使用五档。",
2776
2785
  adaptiveClaudeHelpLabel: "思考模式说明",
2777
2786
  adaptiveClaudeHelp: "五档(低 / 中 / 高 / 超高 / 最大):Fable 5、Opus 5、Opus 4.8、Opus 4.7、Sonnet 5\n四档(低 / 中 / 高 / 最大):Opus 4.6、Sonnet 4.6\n开启 / 关闭:Haiku 4.5",
package/package.json CHANGED
@@ -1,9 +1,17 @@
1
1
  {
2
2
  "name": "@mofeng2223/dsh-claude-provider",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Custom Claude provider support for DeepSeek Harness",
5
5
  "author": "mofeng2223",
6
6
  "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/MoFeng2223/dsh-claude-provider.git"
10
+ },
11
+ "homepage": "https://github.com/MoFeng2223/dsh-claude-provider#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/MoFeng2223/dsh-claude-provider/issues"
14
+ },
7
15
  "keywords": [
8
16
  "deepseek-harness",
9
17
  "dsh-plugin",
@@ -15,6 +23,10 @@
15
23
  "registry": "https://registry.npmjs.org/"
16
24
  },
17
25
  "type": "module",
26
+ "dependencies": {
27
+ "@deepseek-ai/dsh-settings": "0.1.0-rc.6",
28
+ "@deepseek-ai/schemastery": "3.18.1"
29
+ },
18
30
  "exports": {
19
31
  ".": "./src/index.js",
20
32
  "./client": "./lib/client.js",
@@ -25,6 +37,7 @@
25
37
  "lib/client.js",
26
38
  "cordis.patch.yml",
27
39
  "README.md",
40
+ "README.zh-CN.md",
28
41
  "LICENSE"
29
42
  ],
30
43
  "scripts": {
package/src/index.js CHANGED
@@ -1,7 +1,17 @@
1
1
  import { AsyncLocalStorage } from 'node:async_hooks'
2
+ import z from '@deepseek-ai/schemastery'
3
+ import { settingsNamespace } from '@deepseek-ai/dsh-settings'
2
4
 
3
5
  export const name = 'claude-provider'
4
- export const inject = ['llm']
6
+ export const inject = ['llm', 'settings']
7
+
8
+ export const CLAUDE_PROVIDER_TYPE = 'claude-adaptive'
9
+ export const CLAUDE_PROVIDER_SETTINGS_NS = settingsNamespace('dsh-claude-provider')
10
+ export const CLAUDE_PROVIDER_DIRECTORY_SENTINEL = 'dsh-claude-provider-type'
11
+
12
+ const ProviderTypeSettings = z.object({
13
+ providerTypes: z.dict(z.const(CLAUDE_PROVIDER_TYPE)).default({}),
14
+ })
5
15
 
6
16
  const DEFAULT_EFFORT_MAP = Object.freeze({
7
17
  minimal: 'low',
@@ -235,36 +245,55 @@ function installClaudeModelDiscovery(ctx) {
235
245
  }
236
246
 
237
247
  function resolveConfig(config = {}) {
238
- if (!Array.isArray(config.routes) || config.routes.length === 0) {
239
- throw new Error('claude-provider: routes must contain at least one provider')
240
- }
241
-
242
- const targets = new Map()
243
- for (const [routeIndex, route] of config.routes.entries()) {
244
- const provider = requireNonEmptyString(route?.provider, `routes[${routeIndex}].provider`)
245
- if (!Array.isArray(route.models) || route.models.length === 0) {
246
- throw new Error(`claude-provider: route "${provider}" must contain at least one model`)
247
- }
248
- if (targets.has(provider)) {
249
- throw new Error(`claude-provider: duplicate provider route "${provider}"`)
250
- }
251
- targets.set(provider, new Set(route.models.map((model, modelIndex) =>
252
- requireNonEmptyString(model, `routes[${routeIndex}].models[${modelIndex}]`))))
253
- }
254
-
255
248
  const effortMap = { ...DEFAULT_EFFORT_MAP, ...config.effortMap }
256
249
  for (const [level, effort] of Object.entries(effortMap)) {
257
250
  requireNonEmptyString(level, 'effortMap key')
258
251
  requireNonEmptyString(effort, `effortMap.${level}`)
259
252
  }
260
253
 
261
- return { targets, effortMap, debug: config.debug === true }
254
+ return { effortMap, debug: config.debug === true }
255
+ }
256
+
257
+ /** Whether an explicitly registered provider belongs to this plugin's Claude type. */
258
+ export function isClaudeProviderType(settings, provider) {
259
+ if (typeof provider !== 'string' || provider.length === 0) return false
260
+ return settings?.providerTypes?.[provider] === CLAUDE_PROVIDER_TYPE
261
+ }
262
+
263
+ /** Give typed Claude routes a concrete default and readable labels for toggle-only models. */
264
+ export function withClaudeProviderReasoningDefaults(modelInfo) {
265
+ const reasoning = modelInfo?.reasoning
266
+ if (reasoning === undefined || !Array.isArray(reasoning.efforts)) return modelInfo
267
+ const ids = new Set(reasoning.efforts.map(effort => String(effort.id)))
268
+ const toggleOnly = ids.size === 2 && ids.has('off') && ids.has('high')
269
+ return {
270
+ ...modelInfo,
271
+ reasoning: {
272
+ ...reasoning,
273
+ defaultEffort: 'high',
274
+ efforts: toggleOnly
275
+ ? reasoning.efforts.map(effort => ({
276
+ ...effort,
277
+ name: effort.id === 'off' ? 'Off' : effort.id === 'high' ? 'On' : effort.name,
278
+ }))
279
+ : reasoning.efforts,
280
+ },
281
+ }
262
282
  }
263
283
 
264
- function isTarget(targets, provider, model) {
265
- const matches = patterns => patterns !== undefined && [...patterns].some(pattern =>
266
- pattern === '*' || pattern === model || pattern.endsWith('*') && model.startsWith(pattern.slice(0, -1)))
267
- return matches(targets.get(provider)) || matches(targets.get('*'))
284
+ function installClaudeModelInfoDefaults(ctx, providerTypes) {
285
+ const llm = ctx.llm
286
+ const upstream = llm.resolveModelInfo
287
+ const wrapped = async function (provider, model, signal) {
288
+ const info = await upstream.call(this, provider, model, signal)
289
+ return isClaudeProviderType(providerTypes.get(), provider)
290
+ ? withClaudeProviderReasoningDefaults(info)
291
+ : info
292
+ }
293
+ llm.resolveModelInfo = wrapped
294
+ ctx.effect(() => () => {
295
+ if (llm.resolveModelInfo === wrapped) llm.resolveModelInfo = upstream
296
+ })
268
297
  }
269
298
 
270
299
  /** Four- and five-level model profiles are adaptive; the two-level toggle is legacy budget thinking. */
@@ -418,6 +447,18 @@ async function* modelAwareStream(ctx, storage, resolved, options, next) {
418
447
 
419
448
  export function apply(ctx, config) {
420
449
  const resolved = resolveConfig(config)
450
+ const providerTypes = ctx.settings.register(
451
+ CLAUDE_PROVIDER_SETTINGS_NS,
452
+ ProviderTypeSettings,
453
+ { base: { providerTypes: {} } },
454
+ )
455
+ ctx.llm.registerConfigurableProviders([{
456
+ provider: CLAUDE_PROVIDER_DIRECTORY_SENTINEL,
457
+ displayName: 'Claude Provider Type',
458
+ settingsNs: CLAUDE_PROVIDER_SETTINGS_NS,
459
+ settingsPath: ['providerTypes'],
460
+ }])
461
+ installClaudeModelInfoDefaults(ctx, providerTypes)
421
462
  installClaudeModelDiscovery(ctx)
422
463
  const storage = new AsyncLocalStorage()
423
464
  const upstreamFetch = globalThis.fetch
@@ -447,7 +488,7 @@ export function apply(ctx, config) {
447
488
  })
448
489
 
449
490
  ctx.on('llm/stream', (options, next) => {
450
- if (!isTarget(resolved.targets, options.provider, options.model)) return next()
491
+ if (!isClaudeProviderType(providerTypes.get(), options.provider)) return next()
451
492
  return modelAwareStream(ctx, storage, resolved, options, next)
452
493
  })
453
494
  }