@mofeng2223/dsh-claude-provider 0.1.0 → 0.2.3

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,72 +1,105 @@
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
5
- serialize reasoning as `thinking.type: enabled` with `budget_tokens`. For the
6
- configured provider/model pairs, it converts the outgoing request to
7
- `thinking.type: adaptive` plus `output_config.effort`.
8
-
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.
14
-
15
- The same package supplies both the Host request adapter and the Web settings
16
- client. The client marks its model-directory request with a private discovery
17
- protocol handled by the Host half. Only that request uses Anthropic's native
18
- `GET /v1/models` endpoint, with `x-api-key` and `anthropic-version` headers.
19
- It supports Anthropic cursor pagination and reads `display_name`,
20
- `max_input_tokens`, and `max_tokens` when the provider returns them. No
21
- OpenAI-compatible listing or Bearer-auth fallback is attempted.
3
+ English | [简体中文](./README.zh-CN.md)
4
+
5
+ DeepSeek Harness's generic reasoning controls do not fully match the request parameters expected by newer Claude models. A selected level can therefore fail with HTTP 400, or be silently mapped to another effective level, such as Max behaving as High. `@mofeng2223/dsh-claude-provider` adds an explicit Claude provider type and sends the reasoning parameters that each configured Claude model expects.
6
+
7
+ ## What this plugin does
8
+
9
+ 1. **Adds a dedicated Claude provider type**
10
+
11
+ The Models settings page gains a separate **Claude Provider** form. You can create multiple Provider IDs under this type without mixing them with generic custom providers.
12
+
13
+ <p align="center">
14
+ <img src="./docs/images/provider-entry.en.jpg" alt="Add Claude Provider entry in DeepSeek Harness" width="580">
15
+ </p>
16
+
17
+ Selecting **Add Claude Provider** opens the dedicated Anthropic Messages form:
18
+
19
+ <p align="center">
20
+ <img src="./docs/images/claude-provider-form.en.jpg" alt="Claude Provider form in DeepSeek Harness" width="580">
21
+ </p>
22
+
23
+ 2. **Adds model-specific reasoning modes**
24
+
25
+ Each model can use one of three configurable mode sets:
26
+
27
+ - Five levels: Low, Medium, High, XHigh, and Max
28
+ - Four levels: Low, Medium, High, and Max
29
+ - Toggle: On or Off
30
+
31
+ New models default to five levels. Claude providers default to High, while toggle-only models default to On. For adaptive-thinking models, the plugin converts the selection to Claude's `thinking.type: adaptive` and `output_config.effort` request format instead of allowing the adapter to collapse or reject the selected level.
32
+
33
+ <p align="center">
34
+ <img src="./docs/images/model-defaults.en.jpg" alt="Claude model capacity defaults and reasoning modes" width="580">
35
+ </p>
36
+
37
+ 3. **Adds native Anthropic model discovery**
38
+
39
+ DeepSeek Harness's generic custom provider cannot list models for the `anthropic-messages` protocol. This plugin adds model discovery for Claude providers through the provider's native Anthropic-compatible `GET /v1/models` endpoint, including cursor pagination.
40
+
41
+ 4. **Fills known Claude model defaults after discovery**
42
+
43
+ When a discovered model matches a recorded Claude model ID, the plugin automatically fills its context window, maximum output length, and reasoning-mode set. Models entered manually remain fully editable and are not overwritten by this lookup.
44
+
45
+ 5. **Leaves every other provider unchanged**
46
+
47
+ All discovery, defaults, reasoning controls, and request rewriting are limited to Provider IDs explicitly created as **Claude Providers**. DeepSeek Harness's built-in providers and ordinary custom providers keep their original behavior, even when they use `anthropic-messages` or expose a `claude-*` model ID.
22
48
 
23
49
  ## Install
24
50
 
25
- Build the Web client and refresh the single distributable archive after
26
- changing the plugin source:
51
+ ### Published package
52
+
53
+ Install the Web profile for the browser interface:
27
54
 
28
55
  ```sh
56
+ npx @deepseek-ai/dsh plugin --profile web add @mofeng2223/dsh-claude-provider
57
+ ```
58
+
59
+ Install the Headless profile for command-line runs without the Web interface:
60
+
61
+ ```sh
62
+ npx @deepseek-ai/dsh plugin --profile headless add @mofeng2223/dsh-claude-provider
63
+ ```
64
+
65
+ Restart the corresponding DeepSeek Harness process after installation.
66
+
67
+ ### From source
68
+
69
+ Build and package the repository first:
70
+
71
+ ```sh
72
+ git clone https://github.com/MoFeng2223/dsh-claude-provider.git
73
+ cd dsh-claude-provider
74
+ npm install
29
75
  npm run build
76
+ mkdir -p dist
30
77
  npm pack --pack-destination dist
31
78
  ```
32
79
 
33
- Install the published package directly:
80
+ Install the generated package into the Web profile:
34
81
 
35
82
  ```sh
36
- npx @deepseek-ai/dsh plugin --profile web add @mofeng2223/dsh-claude-provider
37
- npx @deepseek-ai/dsh plugin --profile headless add @mofeng2223/dsh-claude-provider
83
+ npx @deepseek-ai/dsh plugin --profile web add ./dist/mofeng2223-dsh-claude-provider-*.tgz
38
84
  ```
39
85
 
40
- Restart the corresponding Harness process after installation.
86
+ Or install it into the Headless profile:
41
87
 
42
- Do not use the source directory as the normal installation target. That creates
43
- a pnpm `link:` dependency, and current DSH/pnpm builds can leave the top-level
44
- link behind after the dependency and bundle registration have been removed.
45
- Archive installs are package-manager-owned and uninstall without those stale
46
- links.
88
+ ```sh
89
+ npx @deepseek-ai/dsh plugin --profile headless add ./dist/mofeng2223-dsh-claude-provider-*.tgz
90
+ ```
47
91
 
48
- Set `DSH_CLAUDE_PROVIDER_DEBUG=1` temporarily to print one redacted line for
49
- each rewritten request. The diagnostic contains only provider, model, and
50
- selected effort.
92
+ ## Uninstall
51
93
 
52
- ## Remove
94
+ Run the command for each profile where the plugin was installed:
53
95
 
54
96
  ```sh
55
97
  npx @deepseek-ai/dsh plugin --profile web remove @mofeng2223/dsh-claude-provider
56
98
  npx @deepseek-ai/dsh plugin --profile headless remove @mofeng2223/dsh-claude-provider
57
99
  ```
58
100
 
59
- Uninstall intentionally leaves `~/.dsh/settings.yaml` and stored credentials
60
- alone. Existing custom providers therefore remain visible as ordinary custom
61
- `anthropic-messages` routes, but lose this plugin's Claude badge, per-model
62
- thinking-mode controls, native Anthropic model discovery, and adaptive request
63
- rewrite.
101
+ Uninstalling the plugin does not delete `~/.dsh/settings.yaml` or stored credentials. Existing Claude providers remain configured as ordinary custom `anthropic-messages` routes, but lose the Claude-specific interface, model discovery, defaults, reasoning controls, and adaptive request conversion supplied by this plugin.
64
102
 
65
- The plugin never stores or logs API keys. Model discovery uses the one-shot key
66
- typed into the form or resolves the existing route's credential through DSH;
67
- 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.
103
+ ## License
70
104
 
71
- The generated `.tgz` package is portable. Copy it to Windows and install it by
72
- its local path; no source build or platform-specific dependency is required.
105
+ [MIT](./LICENSE)
@@ -0,0 +1,105 @@
1
+ # DSH Claude Provider
2
+
3
+ [English](./README.md) | 简体中文
4
+
5
+ DeepSeek Harness 的通用推理强度控制与新版 Claude 模型要求的请求参数并不完全匹配,因此选择某个档位后,可能直接出现 HTTP 400,也可能被适配器静默映射成另一个实际档位,例如界面选择 Max,实际请求却只相当于 High。`@mofeng2223/dsh-claude-provider` 增加了一个明确的 Claude 提供方类型,并按照每个 Claude 模型的配置发送正确的推理参数。
6
+
7
+ ## 插件功能
8
+
9
+ 1. **增加独立的 Claude 提供方类型**
10
+
11
+ 模型设置页面会增加单独的“Claude 提供方”表单。你可以在这个类型下创建多个 Provider ID,不会与普通自定义提供方混在一起。
12
+
13
+ <p align="center">
14
+ <img src="./docs/images/provider-entry.jpg" alt="DeepSeek Harness 中新增的 Claude 提供方入口" width="580">
15
+ </p>
16
+
17
+ 点击“添加 Claude 提供方”后,会打开独立的 Anthropic Messages 配置表单:
18
+
19
+ <p align="center">
20
+ <img src="./docs/images/claude-provider-form.jpg" alt="DeepSeek Harness 中的 Claude 提供方表单" width="580">
21
+ </p>
22
+
23
+ 2. **增加按模型设置的推理模式**
24
+
25
+ 每个模型可以单独选择以下三种档位配置之一:
26
+
27
+ - 五档:Low、Medium、High、XHigh、Max
28
+ - 四档:Low、Medium、High、Max
29
+ - 开关:On、Off
30
+
31
+ 新增模型默认使用五档。Claude 提供方默认选择 High,仅支持开关的模型默认选择 On。对于 adaptive thinking 模型,插件会把所选档位转换成 Claude 使用的 `thinking.type: adaptive` 与 `output_config.effort` 请求格式,避免适配器拒绝请求或把档位降级。
32
+
33
+ <p align="center">
34
+ <img src="./docs/images/model-defaults.jpg" alt="Claude 模型容量预填与思考模式设置" width="580">
35
+ </p>
36
+
37
+ 3. **补充 Anthropic 原生模型探测**
38
+
39
+ DeepSeek Harness 的普通自定义提供方在使用 `anthropic-messages` 协议时无法获取模型列表。本插件为 Claude 提供方增加了基于 Anthropic 兼容 `GET /v1/models` 接口的模型探测,并支持游标分页。
40
+
41
+ 4. **探测后自动填写常用 Claude 模型参数**
42
+
43
+ 探测到的模型 ID 如果与插件已经记录的 Claude 模型匹配,插件会自动填写上下文窗口、最大输出长度和推理模式。用户手动添加的模型仍然可以自由编辑,不会被这项自动匹配覆盖。
44
+
45
+ 5. **不影响其他提供方**
46
+
47
+ 模型探测、参数预填、推理档位和请求转换只对明确创建为“Claude 提供方”的 Provider ID 生效。DeepSeek Harness 的内置提供方和普通自定义提供方会保持原有行为,即使它们同样使用 `anthropic-messages` 协议,或者包含 `claude-*` 模型 ID,也不会被插件修改。
48
+
49
+ ## 安装
50
+
51
+ ### 安装已发布的版本
52
+
53
+ 浏览器界面使用 Web Profile:
54
+
55
+ ```sh
56
+ npx @deepseek-ai/dsh plugin --profile web add @mofeng2223/dsh-claude-provider
57
+ ```
58
+
59
+ 不使用 Web 界面的命令行运行使用 Headless Profile:
60
+
61
+ ```sh
62
+ npx @deepseek-ai/dsh plugin --profile headless add @mofeng2223/dsh-claude-provider
63
+ ```
64
+
65
+ 安装后需要重启对应的 DeepSeek Harness 进程。
66
+
67
+ ### 从源码安装
68
+
69
+ 先克隆、构建并打包项目:
70
+
71
+ ```sh
72
+ git clone https://github.com/MoFeng2223/dsh-claude-provider.git
73
+ cd dsh-claude-provider
74
+ npm install
75
+ npm run build
76
+ mkdir -p dist
77
+ npm pack --pack-destination dist
78
+ ```
79
+
80
+ 将生成的包安装到 Web Profile:
81
+
82
+ ```sh
83
+ npx @deepseek-ai/dsh plugin --profile web add ./dist/mofeng2223-dsh-claude-provider-*.tgz
84
+ ```
85
+
86
+ 或者安装到 Headless Profile:
87
+
88
+ ```sh
89
+ npx @deepseek-ai/dsh plugin --profile headless add ./dist/mofeng2223-dsh-claude-provider-*.tgz
90
+ ```
91
+
92
+ ## 卸载
93
+
94
+ 插件安装在哪个 Profile,就执行对应的卸载命令:
95
+
96
+ ```sh
97
+ npx @deepseek-ai/dsh plugin --profile web remove @mofeng2223/dsh-claude-provider
98
+ npx @deepseek-ai/dsh plugin --profile headless remove @mofeng2223/dsh-claude-provider
99
+ ```
100
+
101
+ 卸载插件不会删除 `~/.dsh/settings.yaml` 或已经保存的凭据。已有的 Claude 提供方会继续作为普通的 `anthropic-messages` 自定义路由保留,但会失去本插件提供的 Claude 专用界面、模型探测、参数预填、推理档位和 adaptive 请求转换。
102
+
103
+ ## 许可证
104
+
105
+ [MIT](./LICENSE)
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
@@ -68,67 +68,67 @@ window.__ModuleLoader__.load({
68
68
  document.head.appendChild(tag);
69
69
  }
70
70
  var ModelsSection_module_css_default = {
71
- "iconButtonDanger": "zGbnIq_iconButtonDanger",
72
- "editorActions": "zGbnIq_editorActions",
73
- "error": "zGbnIq_error",
74
- "modelRow": "zGbnIq_modelRow",
71
+ "editorRoute": "zGbnIq_editorRoute",
75
72
  "section": "zGbnIq_section",
76
- "linkButton": "zGbnIq_linkButton",
77
- "hiddenLabel": "zGbnIq_hiddenLabel",
78
- "rows": "zGbnIq_rows",
79
- "candidateLabel": "zGbnIq_candidateLabel",
80
- "input": "zGbnIq_input",
81
- "notice": "zGbnIq_notice",
82
- "fetchDialog": "zGbnIq_fetchDialog",
83
- "candidate": "zGbnIq_candidate",
84
- "customizedBody": "zGbnIq_customizedBody",
85
- "modelListHead": "zGbnIq_modelListHead",
86
73
  "deleteDialog": "zGbnIq_deleteDialog",
87
- "modelEntry": "zGbnIq_modelEntry",
74
+ "editorHeader": "zGbnIq_editorHeader",
88
75
  "addModelButton": "zGbnIq_addModelButton",
89
- "modelCatalogTitle": "zGbnIq_modelCatalogTitle",
90
- "editorRoute": "zGbnIq_editorRoute",
91
- "rowCard": "zGbnIq_rowCard",
92
- "rowName": "zGbnIq_rowName",
93
- "modelAdvanced": "zGbnIq_modelAdvanced",
76
+ "fieldLabel": "zGbnIq_fieldLabel",
77
+ "deleteConfirm": "zGbnIq_deleteConfirm",
78
+ "addButton": "zGbnIq_addButton",
94
79
  "primaryButton": "zGbnIq_primaryButton",
95
- "intro": "zGbnIq_intro",
96
- "rowIdentity": "zGbnIq_rowIdentity",
80
+ "advancedHint": "zGbnIq_advancedHint",
97
81
  "modelCatalog": "zGbnIq_modelCatalog",
98
- "editorHeader": "zGbnIq_editorHeader",
99
- "credentialDot": "zGbnIq_credentialDot",
100
- "dangerButton": "zGbnIq_dangerButton",
101
- "modelFieldLabel": "zGbnIq_modelFieldLabel",
102
- "field": "zGbnIq_field",
82
+ "modelRow": "zGbnIq_modelRow",
83
+ "fetchDialog": "zGbnIq_fetchDialog",
84
+ "modelAdvanced": "zGbnIq_modelAdvanced",
85
+ "editorActions": "zGbnIq_editorActions",
103
86
  "credentialDotConfigured": "zGbnIq_credentialDotConfigured",
104
87
  "addBlock": "zGbnIq_addBlock",
105
- "deleteConfirm": "zGbnIq_deleteConfirm",
106
- "modelEmpty": "zGbnIq_modelEmpty",
107
- "editor": "zGbnIq_editor",
108
- "addButton": "zGbnIq_addButton",
109
- "candidateList": "zGbnIq_candidateList",
110
- "secondaryButton": "zGbnIq_secondaryButton",
111
- "modelCatalogMeta": "zGbnIq_modelCatalogMeta",
112
- "modelField": "zGbnIq_modelField",
113
- "customized": "zGbnIq_customized",
88
+ "dangerButton": "zGbnIq_dangerButton",
89
+ "rowCard": "zGbnIq_rowCard",
90
+ "credentialDot": "zGbnIq_credentialDot",
114
91
  "selectInput": "zGbnIq_selectInput",
92
+ "error": "zGbnIq_error",
93
+ "secondaryButton": "zGbnIq_secondaryButton",
94
+ "modelCatalogHeading": "zGbnIq_modelCatalogHeading",
95
+ "modelList": "zGbnIq_modelList",
96
+ "modelEntry": "zGbnIq_modelEntry",
97
+ "candidate": "zGbnIq_candidate",
98
+ "modelFieldLabel": "zGbnIq_modelFieldLabel",
99
+ "editor": "zGbnIq_editor",
100
+ "intro": "zGbnIq_intro",
101
+ "modelCatalogTitle": "zGbnIq_modelCatalogTitle",
102
+ "input": "zGbnIq_input",
103
+ "rowTag": "zGbnIq_rowTag",
104
+ "notice": "zGbnIq_notice",
105
+ "modelEmpty": "zGbnIq_modelEmpty",
106
+ "addCard": "zGbnIq_addCard",
115
107
  "iconButton": "zGbnIq_iconButton",
108
+ "linkButton": "zGbnIq_linkButton",
109
+ "hiddenLabel": "zGbnIq_hiddenLabel",
110
+ "candidateId": "zGbnIq_candidateId",
116
111
  "setupCard": "zGbnIq_setupCard",
117
- "fieldLabel": "zGbnIq_fieldLabel",
112
+ "iconButtonDanger": "zGbnIq_iconButtonDanger",
113
+ "title": "zGbnIq_title",
114
+ "rowIdentity": "zGbnIq_rowIdentity",
115
+ "savedNotice": "zGbnIq_savedNotice",
116
+ "candidateLabel": "zGbnIq_candidateLabel",
118
117
  "rowActions": "zGbnIq_rowActions",
119
- "addCard": "zGbnIq_addCard",
120
- "candidateId": "zGbnIq_candidateId",
121
- "modelCatalogHeading": "zGbnIq_modelCatalogHeading",
122
- "credentialDotMissing": "zGbnIq_credentialDotMissing",
123
- "rowTag": "zGbnIq_rowTag",
124
- "customizedSummary": "zGbnIq_customizedSummary",
125
118
  "addActions": "zGbnIq_addActions",
119
+ "candidateList": "zGbnIq_candidateList",
120
+ "rows": "zGbnIq_rows",
121
+ "rowName": "zGbnIq_rowName",
122
+ "modelField": "zGbnIq_modelField",
123
+ "customized": "zGbnIq_customized",
124
+ "modelListHead": "zGbnIq_modelListHead",
126
125
  "editorTitle": "zGbnIq_editorTitle",
127
- "savedNotice": "zGbnIq_savedNotice",
128
- "modelList": "zGbnIq_modelList",
129
- "advancedHint": "zGbnIq_advancedHint",
130
- "title": "zGbnIq_title",
131
- "rowHead": "zGbnIq_rowHead"
126
+ "rowHead": "zGbnIq_rowHead",
127
+ "field": "zGbnIq_field",
128
+ "modelCatalogMeta": "zGbnIq_modelCatalogMeta",
129
+ "credentialDotMissing": "zGbnIq_credentialDotMissing",
130
+ "customizedSummary": "zGbnIq_customizedSummary",
131
+ "customizedBody": "zGbnIq_customizedBody"
132
132
  };
133
133
  //#endregion
134
134
  //#region lib/types/client/EditorFooter.js
@@ -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
  }),
@@ -2230,10 +2235,10 @@ window.__ModuleLoader__.load({
2230
2235
  document.head.appendChild(tag);
2231
2236
  }
2232
2237
  var OnboardingModal_module_css_default = {
2233
- "body": "jLrgrW_body",
2234
- "title": "jLrgrW_title",
2238
+ "content": "jLrgrW_content",
2235
2239
  "dialog": "jLrgrW_dialog",
2236
- "content": "jLrgrW_content"
2240
+ "title": "jLrgrW_title",
2241
+ "body": "jLrgrW_body"
2237
2242
  };
2238
2243
  //#endregion
2239
2244
  //#region lib/types/client/OnboardingModal.js
@@ -2383,10 +2388,10 @@ window.__ModuleLoader__.load({
2383
2388
  document.head.appendChild(tag);
2384
2389
  }
2385
2390
  var WelcomeNotice_module_css_default = {
2386
- "error": "t1T8VW_error",
2387
- "primary": "t1T8VW_primary",
2388
2391
  "copy": "t1T8VW_copy",
2389
- "actions": "t1T8VW_actions"
2392
+ "actions": "t1T8VW_actions",
2393
+ "primary": "t1T8VW_primary",
2394
+ "error": "t1T8VW_error"
2390
2395
  };
2391
2396
  //#endregion
2392
2397
  //#region lib/types/client/WelcomeNotice.js
@@ -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.3",
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.7",
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": {
@@ -35,7 +48,7 @@
35
48
  "node": ">=22.19"
36
49
  },
37
50
  "devDependencies": {
38
- "@deepseek-ai/dsh-client-ui-settings-models": "0.1.0-rc.6"
51
+ "@deepseek-ai/dsh-client-ui-settings-models": "0.1.0-rc.7"
39
52
  },
40
53
  "dsh": {
41
54
  "bundle": {
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 }
262
255
  }
263
256
 
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('*'))
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
+ }
282
+ }
283
+
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. */
@@ -275,6 +304,61 @@ export function shouldUseAdaptiveThinking(modelInfo) {
275
304
  return ids.has('low') && ids.has('medium') && ids.has('high') && ids.has('max')
276
305
  }
277
306
 
307
+ function hasAdaptiveEffortMap(map) {
308
+ if (map === null || typeof map !== 'object' || Array.isArray(map)) return false
309
+ return ['low', 'medium', 'high', 'max'].every(level => typeof map[level] === 'string' && map[level].length > 0)
310
+ }
311
+
312
+ /** Whether a pi-ai catalog model should be serialized with adaptive thinking. */
313
+ export function piModelHasAdaptiveEfforts(model) {
314
+ return hasAdaptiveEffortMap(model?.thinkingLevelMap)
315
+ }
316
+
317
+ /**
318
+ * Stamp `compat.forceAdaptiveThinking` onto a pi-ai model so the Anthropic
319
+ * adapter builds `thinking.type: adaptive` before `@anthropic-ai/sdk` sees the
320
+ * payload. That is what removes the SDK deprecation warning for Opus 4.6.
321
+ */
322
+ export function withForcedAdaptiveThinking(model, provider, settings) {
323
+ if (!isClaudeProviderType(settings, provider)) return model
324
+ if (model === null || typeof model !== 'object' || Array.isArray(model)) return model
325
+ if (model.compat?.forceAdaptiveThinking === true) return model
326
+ if (!piModelHasAdaptiveEfforts(model)) return model
327
+ return {
328
+ ...model,
329
+ compat: { ...model.compat, forceAdaptiveThinking: true },
330
+ }
331
+ }
332
+
333
+ function installForceAdaptiveThinking(ctx, providerTypes) {
334
+ const llm = ctx.llm
335
+ if (typeof llm.listProviders !== 'function' || typeof llm.registration !== 'function') return
336
+
337
+ const patched = new WeakSet()
338
+ const wrap = () => {
339
+ for (const provider of llm.listProviders()) {
340
+ const id = provider?.id
341
+ if (typeof id !== 'string' || id.length === 0) continue
342
+ let adapter
343
+ try {
344
+ adapter = llm.registration(id).adapter
345
+ } catch {
346
+ continue
347
+ }
348
+ if (adapter === null || typeof adapter !== 'object' || typeof adapter.modelOf !== 'function') continue
349
+ if (patched.has(adapter)) continue
350
+ const original = adapter.modelOf.bind(adapter)
351
+ adapter.modelOf = (snapshot, providerId, modelId) => (
352
+ withForcedAdaptiveThinking(original(snapshot, providerId, modelId), providerId, providerTypes.get())
353
+ )
354
+ patched.add(adapter)
355
+ }
356
+ }
357
+
358
+ wrap()
359
+ ctx.effect(() => ctx.on('llm/adapters-updated', wrap), 'claude-provider: force adaptive thinking')
360
+ }
361
+
278
362
  function effortFromBudget(budget) {
279
363
  if (typeof budget !== 'number' || !Number.isFinite(budget)) return 'high'
280
364
  if (budget <= 2048) return 'low'
@@ -418,6 +502,19 @@ async function* modelAwareStream(ctx, storage, resolved, options, next) {
418
502
 
419
503
  export function apply(ctx, config) {
420
504
  const resolved = resolveConfig(config)
505
+ const providerTypes = ctx.settings.register(
506
+ CLAUDE_PROVIDER_SETTINGS_NS,
507
+ ProviderTypeSettings,
508
+ { base: { providerTypes: {} } },
509
+ )
510
+ ctx.llm.registerConfigurableProviders([{
511
+ provider: CLAUDE_PROVIDER_DIRECTORY_SENTINEL,
512
+ displayName: 'Claude Provider Type',
513
+ settingsNs: CLAUDE_PROVIDER_SETTINGS_NS,
514
+ settingsPath: ['providerTypes'],
515
+ }])
516
+ installClaudeModelInfoDefaults(ctx, providerTypes)
517
+ installForceAdaptiveThinking(ctx, providerTypes)
421
518
  installClaudeModelDiscovery(ctx)
422
519
  const storage = new AsyncLocalStorage()
423
520
  const upstreamFetch = globalThis.fetch
@@ -447,7 +544,7 @@ export function apply(ctx, config) {
447
544
  })
448
545
 
449
546
  ctx.on('llm/stream', (options, next) => {
450
- if (!isTarget(resolved.targets, options.provider, options.model)) return next()
547
+ if (!isClaudeProviderType(providerTypes.get(), options.provider)) return next()
451
548
  return modelAwareStream(ctx, storage, resolved, options, next)
452
549
  })
453
550
  }