dsh-codearts 1.0.1 → 1.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/CHANGELOG.md CHANGED
@@ -3,6 +3,32 @@
3
3
  本文件记录本插件的重要改动。格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
4
4
  版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
5
5
 
6
+ ## [1.1.1] - 2026-10-22
7
+
8
+ ### 修复
9
+
10
+ - **带思考档位的模型(GLM-5.2 / GLM-5.2-ArkTS-SPARK)每轮必失败,报
11
+ `Message role cannot empty`**:1.1.0 给这些模型声明了 `reasoning`,宿主 pi-ai 于是
12
+ 把系统提示从 `role:"system"` 换成 OpenAI 的新叫法 `role:"developer"` 发出来,
13
+ 华为网关只认 system/user/assistant/tool,认不出来就回这一句黑话。出口处统一把
14
+ `developer` 归一回 `system`,与档位无关(Default/High/Max 之前都必现)。
15
+ - 新增 `GET /api/codearts/debug`:回最近 8 次出站请求的**形状**摘要(消息条数、
16
+ 每条的 role 名、工具数),只含 role 名不含任何内容。上游的参数错误全是同一句
17
+ 黑话,没有这个就分不清是宿主发的还是插件拼的。
18
+
19
+ ## [1.1.0] - 2026-10-21
20
+
21
+ ### 新增
22
+
23
+ - 思考强度:模型发现时拉 `thinking-variants`,只给上游声明过档位的模型(GLM-5.2 /
24
+ GLM-5.2-ArkTS-SPARK:high / max)露出推理选择器,请求以 `reasoning_effort` 透传;
25
+ 这路失败不影响目录。
26
+ - 上游排队:发请求前先向 `/api/v1/queue/status` 挂号并轮询(每 5 秒一次,最多替
27
+ 用户等 20 秒),轮到才开流;等待期间对话底部积分条显示「排队第 N 位」。20 秒
28
+ 还没轮到就不挂请求,直接回一句上游自己的排队文案(第几位 / 共多少条)加
29
+ 「请稍后重试」;结束、取消、放弃都会 `DELETE` 清号,不留幽灵排队。
30
+ - 新接口 `GET /api/codearts/queue`,给卡片读排队快照。
31
+
6
32
  ## [1.0.1] - 2026-10-21
7
33
 
8
34
  ### 文档
package/lib/bin.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { A as isBenefitModel, N as codeartsAccountsPath, j as CodeArtsAccountStore, n as codeartsUsagePath, r as discoverModels, t as CodeArtsUsageTracker, u as fetchCallerIdentity } from "./usage-BEoMNRwo.mjs";
2
+ import { F as codeartsAccountsPath, M as isBenefitModel, N as CodeArtsAccountStore, d as fetchCallerIdentity, n as codeartsUsagePath, r as discoverModels, t as CodeArtsUsageTracker } from "./usage-zgF0QuQ4.mjs";
3
3
  import { existsSync, statSync } from "node:fs";
4
4
  //#region src/version.ts
5
- const CODEARTS_VERSION = "1.0.1";
5
+ const CODEARTS_VERSION = "1.1.1";
6
6
  //#endregion
7
7
  //#region src/bin.ts
8
8
  /**
@@ -85,6 +85,8 @@ interface CodeArtsSettingsKey {
85
85
  creditsNeedSignIn: string;
86
86
  creditsDock: string;
87
87
  creditsDockTitle: string;
88
+ queueDock: string;
89
+ queueDockTitle: string;
88
90
  claimButton: string;
89
91
  claimButtonDone: string;
90
92
  claimPending: string;
@@ -23,7 +23,7 @@ function stripModelSuffixes(name) {
23
23
  }
24
24
  //#endregion
25
25
  //#region \0dsh-css:/home/runner/work/dsh-codearts/dsh-codearts/src/client/CodeArtsPluginCard.module.css.mjs
26
- const css = ".xoziZG_card{border:.5px solid var(--dsw-alias-settings-card-stroke);border-radius:var(--dsw-radius-lg);background:var(--dsw-alias-settings-card-fill);max-width:720px}.xoziZG_header{align-items:flex-start;gap:12px;padding:14px 16px;display:flex}.xoziZG_headText{flex-direction:column;flex:1;gap:6px;min-width:0;display:flex}.xoziZG_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:22px}.xoziZG_desc{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:20px}.xoziZG_statusLine{color:var(--dsw-alias-label-primary);align-items:center;gap:6px;font-size:14px;font-weight:500;line-height:22px;display:inline-flex}.xoziZG_meta{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;font-size:12px;line-height:18px}.xoziZG_body{border-top:.5px solid var(--dsw-alias-border-l2);margin:0 16px;padding:4px 0 16px}.xoziZG_tabs{margin-top:12px}.xoziZG_tabPanel{padding-top:2px}.xoziZG_accountToolbar{gap:8px;margin-top:12px;display:flex}.xoziZG_accountToolbar>*{flex:1}.xoziZG_manualForm{border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-md);margin-top:12px;padding:12px}.xoziZG_accountCard{flex-direction:column;gap:10px;margin-top:12px;display:flex}.xoziZG_accountEntry{border:.5px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-2)}.xoziZG_accountRow{align-items:center;gap:10px;padding:12px;display:flex}.xoziZG_claimAllRow{border-top:.5px solid var(--dsw-alias-border-l2);justify-content:flex-end;padding:10px 12px 0;display:flex}.xoziZG_avatar{background:var(--dsw-alias-bg-layer-3);width:32px;height:32px;color:var(--dsw-alias-label-primary);border-radius:50%;flex:none;justify-content:center;align-items:center;font-size:14px;font-weight:600;display:inline-flex}.xoziZG_accountInfo{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.xoziZG_accountNameRow{align-items:center;gap:6px;min-width:0;display:flex}.xoziZG_accountName{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-size:14px;font-weight:600;line-height:20px;overflow:hidden}.xoziZG_accountRowActions{flex:none;gap:6px;display:flex}.xoziZG_accountHint{color:var(--dsw-alias-label-tertiary);margin:0;padding:0 12px 12px;font-size:12px;line-height:18px}.xoziZG_creditBlock{border-top:.5px solid var(--dsw-alias-border-l2);padding:12px}.xoziZG_balanceRow{flex-wrap:wrap;align-items:baseline;gap:6px;display:flex}.xoziZG_balance{color:var(--dsw-alias-label-primary);letter-spacing:-.02em;font-variant-numeric:tabular-nums;font-size:28px;font-weight:600;line-height:34px}.xoziZG_balanceUnit{color:var(--dsw-alias-label-secondary);font-size:12px}.xoziZG_balanceMeta{color:var(--dsw-alias-label-tertiary);margin-left:2px;font-size:12px}.xoziZG_updated{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;margin-left:auto;font-size:12px}.xoziZG_creditRows{flex-direction:column;gap:4px;margin-top:8px;display:flex}.xoziZG_creditRow{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;justify-content:space-between;align-items:baseline;gap:12px;font-size:12px;line-height:18px;display:flex}.xoziZG_usageBar{background:var(--dsw-alias-border-l2);border-radius:2px;height:4px;margin-top:4px;display:block;overflow:hidden}.xoziZG_usageFill{background:var(--dsw-alias-brand-primary);border-radius:2px;height:100%;transition:width .2s;display:block}.xoziZG_expiryRow{margin-top:2px}.xoziZG_expiryLabel{color:var(--dsw-alias-state-warn-label)}.xoziZG_bar{background:var(--dsw-alias-bg-layer-3);border-radius:2px;height:4px;margin-top:6px;display:block;overflow:hidden}.xoziZG_barFill{background:var(--dsw-alias-state-warn-primary);border-radius:2px;height:100%;display:block}.xoziZG_creditLink{margin-top:8px;display:inline-block}.xoziZG_creditFooter{border-top:.5px solid var(--dsw-alias-border-l2);justify-content:space-between;align-items:center;gap:8px;margin-top:12px;padding-top:10px;display:flex}.xoziZG_creditHint{color:var(--dsw-alias-label-tertiary);margin:8px 0 0;font-size:12px;line-height:18px}.xoziZG_section{margin-top:16px}.xoziZG_sectionHead{justify-content:space-between;align-items:baseline;gap:12px;display:flex}.xoziZG_sectionLabel{color:var(--dsw-alias-label-tertiary);font-size:11px;font-weight:500}.xoziZG_count{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;flex:none;font-size:12px}.xoziZG_hint{color:var(--dsw-alias-label-secondary);margin:8px 0 0;font-size:13px;line-height:20px}.xoziZG_error{color:var(--dsw-alias-state-error-primary);margin:10px 0 0;font-size:13px;line-height:20px}.xoziZG_actions{flex-wrap:wrap;align-items:center;gap:8px;margin-top:12px;display:flex}.xoziZG_danger{color:var(--dsw-alias-state-error-primary)}.xoziZG_link{appearance:none;color:var(--dsw-alias-brand-text);font:inherit;text-align:left;word-break:break-all;cursor:pointer;background:0 0;border:none;padding:0;font-size:12px}.xoziZG_link:hover{text-decoration:underline}.xoziZG_choices{flex-direction:column;gap:2px;margin-top:8px;display:flex}.xoziZG_choiceRow{border-radius:var(--dsw-radius-sm);align-items:center;gap:8px;margin:0 -6px;padding:5px 6px;display:flex}.xoziZG_choiceRow:hover{background:var(--dsw-alias-interactive-bg-hover)}.xoziZG_modelChart{flex-direction:column;gap:2px;margin-top:8px;display:flex}.xoziZG_modelRow{font-variant-numeric:tabular-nums;grid-template-columns:minmax(0,1.2fr) minmax(48px,1fr) auto auto auto;align-items:center;gap:8px;padding:4px 0;display:grid}.xoziZG_modelName{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:18px;overflow:hidden}.xoziZG_modelTrack{background:var(--dsw-alias-border-l2);border-radius:4px;height:8px;display:block;overflow:hidden}.xoziZG_modelFill{background:var(--dsw-alias-brand-primary);border-radius:4px;height:100%;transition:width .16s;display:block}.xoziZG_modelTokens,.xoziZG_modelShare,.xoziZG_modelRequests{color:var(--dsw-alias-label-secondary);text-align:right;white-space:nowrap;font-size:11px;line-height:16px}.xoziZG_modelRequests{color:var(--dsw-alias-label-tertiary)}.xoziZG_check{flex:1;min-width:0}.xoziZG_check>span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.xoziZG_manual{border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-md);margin-top:12px;padding:0 12px}.xoziZG_manual[open]{padding-bottom:12px}.xoziZG_manualSummary{color:var(--dsw-alias-label-secondary);cursor:pointer;align-items:center;gap:6px;padding:9px 0;font-size:13px;list-style:none;display:flex}.xoziZG_manualSummary::-webkit-details-marker{display:none}.xoziZG_field{flex-direction:column;gap:5px;margin-top:10px;display:flex}.xoziZG_fieldLabel{color:var(--dsw-alias-label-tertiary);font-size:12px}.xoziZG_area{box-sizing:border-box;resize:vertical;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-1);width:100%;color:var(--dsw-alias-label-primary);padding:6px 8px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:13px;line-height:20px}.xoziZG_area::placeholder{color:var(--dsw-alias-label-dimmed)}.xoziZG_area:focus{border-color:var(--dsw-alias-state-business-primary);outline:none}";
26
+ const css = ".xoziZG_card{border:.5px solid var(--dsw-alias-settings-card-stroke);border-radius:var(--dsw-radius-lg);background:var(--dsw-alias-settings-card-fill);max-width:720px}.xoziZG_header{align-items:flex-start;gap:12px;padding:14px 16px;display:flex}.xoziZG_headText{flex-direction:column;flex:1;gap:6px;min-width:0;display:flex}.xoziZG_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:22px}.xoziZG_desc{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:20px}.xoziZG_statusLine{color:var(--dsw-alias-label-primary);align-items:center;gap:6px;font-size:14px;font-weight:500;line-height:22px;display:inline-flex}.xoziZG_meta{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;font-size:12px;line-height:18px}.xoziZG_body{border-top:.5px solid var(--dsw-alias-border-l2);margin:0 16px;padding:4px 0 16px}.xoziZG_tabs{margin-top:12px}.xoziZG_tabPanel{padding-top:2px}.xoziZG_accountToolbar{gap:8px;margin-top:12px;display:flex}.xoziZG_accountToolbar>*{flex:1}.xoziZG_manualForm{border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-md);margin-top:12px;padding:12px}.xoziZG_accountCard{flex-direction:column;gap:10px;margin-top:12px;display:flex}.xoziZG_accountEntry{border:.5px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-2)}.xoziZG_accountRow{align-items:center;gap:10px;padding:12px;display:flex}.xoziZG_claimAllRow{border-top:.5px solid var(--dsw-alias-border-l2);justify-content:flex-end;padding:10px 12px 0;display:flex}.xoziZG_avatar{background:var(--dsw-alias-bg-layer-3);width:32px;height:32px;color:var(--dsw-alias-label-primary);border-radius:50%;flex:none;justify-content:center;align-items:center;font-size:14px;font-weight:600;display:inline-flex}.xoziZG_accountInfo{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.xoziZG_accountNameRow{align-items:center;gap:6px;min-width:0;display:flex}.xoziZG_accountName{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-size:14px;font-weight:600;line-height:20px;overflow:hidden}.xoziZG_accountRowActions{flex:none;gap:6px;display:flex}.xoziZG_accountHint{color:var(--dsw-alias-label-tertiary);margin:0;padding:0 12px 12px;font-size:12px;line-height:18px}.xoziZG_creditBlock{border-top:.5px solid var(--dsw-alias-border-l2);padding:12px}.xoziZG_balanceRow{flex-wrap:wrap;align-items:baseline;gap:6px;display:flex}.xoziZG_balance{color:var(--dsw-alias-label-primary);letter-spacing:-.02em;font-variant-numeric:tabular-nums;font-size:28px;font-weight:600;line-height:34px}.xoziZG_balanceUnit{color:var(--dsw-alias-label-secondary);font-size:12px}.xoziZG_balanceMeta{color:var(--dsw-alias-label-tertiary);margin-left:2px;font-size:12px}.xoziZG_updated{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;margin-left:auto;font-size:12px}.xoziZG_creditRows{flex-direction:column;gap:4px;margin-top:8px;display:flex}.xoziZG_creditRow{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;justify-content:space-between;align-items:baseline;gap:12px;font-size:12px;line-height:18px;display:flex}.xoziZG_usageBar{background:var(--dsw-alias-border-l2);border-radius:2px;height:4px;margin-top:4px;display:block;overflow:hidden}.xoziZG_usageFill{background:var(--dsw-alias-brand-primary);border-radius:2px;height:100%;transition:width .2s;display:block}.xoziZG_expiryRow{margin-top:2px}.xoziZG_expiryLabel{color:var(--dsw-alias-state-warn-label)}.xoziZG_bar{background:var(--dsw-alias-bg-layer-3);border-radius:2px;height:4px;margin-top:6px;display:block;overflow:hidden}.xoziZG_barFill{background:var(--dsw-alias-state-warn-primary);border-radius:2px;height:100%;display:block}.xoziZG_creditLink{margin-top:8px;display:inline-block}.xoziZG_creditFooter{border-top:.5px solid var(--dsw-alias-border-l2);justify-content:space-between;align-items:center;gap:8px;margin-top:12px;padding-top:10px;display:flex}.xoziZG_creditHint{color:var(--dsw-alias-label-tertiary);margin:8px 0 0;font-size:12px;line-height:18px}.xoziZG_section{margin-top:16px}.xoziZG_sectionHead{justify-content:space-between;align-items:baseline;gap:12px;display:flex}.xoziZG_sectionLabel{color:var(--dsw-alias-label-tertiary);font-size:11px;font-weight:500}.xoziZG_count{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;flex:none;font-size:12px}.xoziZG_hint{color:var(--dsw-alias-label-secondary);margin:8px 0 0;font-size:13px;line-height:20px}.xoziZG_error{color:var(--dsw-alias-state-error-primary);margin:10px 0 0;font-size:13px;line-height:20px}.xoziZG_actions{flex-wrap:wrap;align-items:center;gap:8px;margin-top:12px;display:flex}.xoziZG_danger{color:var(--dsw-alias-state-error-primary)}.xoziZG_link{appearance:none;color:var(--dsw-alias-brand-text);font:inherit;text-align:left;word-break:break-all;cursor:pointer;background:0 0;border:none;padding:0;font-size:12px}.xoziZG_link:hover{text-decoration:underline}.xoziZG_choices{flex-direction:column;gap:2px;margin-top:8px;display:flex}.xoziZG_choiceRow{border-radius:var(--dsw-radius-sm);align-items:center;gap:8px;margin:0 -6px;padding:5px 6px;display:flex}.xoziZG_choiceRow:hover{background:var(--dsw-alias-interactive-bg-hover)}.xoziZG_modelChart{flex-direction:column;gap:2px;margin-top:8px;display:flex}.xoziZG_modelRow{font-variant-numeric:tabular-nums;grid-template-columns:minmax(0,1.2fr) minmax(48px,1fr) 56px 52px 64px;align-items:center;gap:8px;padding:4px 0;display:grid}.xoziZG_modelName{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:18px;overflow:hidden}.xoziZG_modelTrack{background:var(--dsw-alias-border-l2);border-radius:4px;height:8px;display:block;overflow:hidden}.xoziZG_modelFill{background:var(--dsw-alias-brand-primary);border-radius:4px;height:100%;transition:width .16s;display:block}.xoziZG_modelTokens,.xoziZG_modelShare,.xoziZG_modelRequests{color:var(--dsw-alias-label-secondary);text-align:right;white-space:nowrap;font-size:11px;line-height:16px}.xoziZG_modelRequests{color:var(--dsw-alias-label-tertiary)}.xoziZG_check{flex:1;min-width:0}.xoziZG_check>span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.xoziZG_manual{border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-md);margin-top:12px;padding:0 12px}.xoziZG_manual[open]{padding-bottom:12px}.xoziZG_manualSummary{color:var(--dsw-alias-label-secondary);cursor:pointer;align-items:center;gap:6px;padding:9px 0;font-size:13px;list-style:none;display:flex}.xoziZG_manualSummary::-webkit-details-marker{display:none}.xoziZG_field{flex-direction:column;gap:5px;margin-top:10px;display:flex}.xoziZG_fieldLabel{color:var(--dsw-alias-label-tertiary);font-size:12px}.xoziZG_area{box-sizing:border-box;resize:vertical;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-1);width:100%;color:var(--dsw-alias-label-primary);padding:6px 8px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:13px;line-height:20px}.xoziZG_area::placeholder{color:var(--dsw-alias-label-dimmed)}.xoziZG_area:focus{border-color:var(--dsw-alias-state-business-primary);outline:none}";
27
27
  const tagId = "dsh-codearts/CodeArtsPluginCard.module.css";
28
28
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
29
29
  const tag = document.createElement("style");
@@ -33,69 +33,69 @@ if (typeof document !== "undefined" && document.querySelector("style[data-plugin
33
33
  document.head.appendChild(tag);
34
34
  }
35
35
  var CodeArtsPluginCard_module_css_default = {
36
- "accountCard": "xoziZG_accountCard",
37
36
  "accountRow": "xoziZG_accountRow",
38
- "creditBlock": "xoziZG_creditBlock",
39
- "section": "xoziZG_section",
40
- "accountRowActions": "xoziZG_accountRowActions",
41
- "creditLink": "xoziZG_creditLink",
42
- "choiceRow": "xoziZG_choiceRow",
43
- "danger": "xoziZG_danger",
44
- "modelName": "xoziZG_modelName",
45
- "manual": "xoziZG_manual",
46
- "modelChart": "xoziZG_modelChart",
47
- "creditHint": "xoziZG_creditHint",
48
- "avatar": "xoziZG_avatar",
49
- "card": "xoziZG_card",
50
- "fieldLabel": "xoziZG_fieldLabel",
51
- "updated": "xoziZG_updated",
52
- "name": "xoziZG_name",
53
- "barFill": "xoziZG_barFill",
54
- "expiryLabel": "xoziZG_expiryLabel",
55
- "area": "xoziZG_area",
56
- "manualForm": "xoziZG_manualForm",
57
- "sectionLabel": "xoziZG_sectionLabel",
37
+ "modelTrack": "xoziZG_modelTrack",
38
+ "accountNameRow": "xoziZG_accountNameRow",
58
39
  "choices": "xoziZG_choices",
59
- "body": "xoziZG_body",
60
- "accountInfo": "xoziZG_accountInfo",
61
- "balanceRow": "xoziZG_balanceRow",
62
- "check": "xoziZG_check",
63
- "tabPanel": "xoziZG_tabPanel",
64
- "modelShare": "xoziZG_modelShare",
65
- "accountName": "xoziZG_accountName",
66
- "link": "xoziZG_link",
67
40
  "modelFill": "xoziZG_modelFill",
68
- "modelTrack": "xoziZG_modelTrack",
69
- "desc": "xoziZG_desc",
41
+ "claimAllRow": "xoziZG_claimAllRow",
42
+ "manual": "xoziZG_manual",
43
+ "creditBlock": "xoziZG_creditBlock",
70
44
  "header": "xoziZG_header",
45
+ "meta": "xoziZG_meta",
46
+ "balanceMeta": "xoziZG_balanceMeta",
47
+ "accountEntry": "xoziZG_accountEntry",
48
+ "barFill": "xoziZG_barFill",
71
49
  "hint": "xoziZG_hint",
72
- "accountToolbar": "xoziZG_accountToolbar",
73
- "balanceUnit": "xoziZG_balanceUnit",
74
- "claimAllRow": "xoziZG_claimAllRow",
75
- "creditRow": "xoziZG_creditRow",
76
50
  "modelTokens": "xoziZG_modelTokens",
77
- "bar": "xoziZG_bar",
78
- "expiryRow": "xoziZG_expiryRow",
51
+ "fieldLabel": "xoziZG_fieldLabel",
52
+ "desc": "xoziZG_desc",
53
+ "section": "xoziZG_section",
54
+ "check": "xoziZG_check",
55
+ "avatar": "xoziZG_avatar",
56
+ "creditRows": "xoziZG_creditRows",
57
+ "creditLink": "xoziZG_creditLink",
58
+ "tabPanel": "xoziZG_tabPanel",
59
+ "accountCard": "xoziZG_accountCard",
79
60
  "statusLine": "xoziZG_statusLine",
80
- "accountHint": "xoziZG_accountHint",
61
+ "modelRow": "xoziZG_modelRow",
62
+ "modelShare": "xoziZG_modelShare",
81
63
  "sectionHead": "xoziZG_sectionHead",
82
- "tabs": "xoziZG_tabs",
83
- "balanceMeta": "xoziZG_balanceMeta",
84
- "meta": "xoziZG_meta",
85
- "usageBar": "xoziZG_usageBar",
86
- "error": "xoziZG_error",
64
+ "updated": "xoziZG_updated",
65
+ "bar": "xoziZG_bar",
66
+ "card": "xoziZG_card",
87
67
  "field": "xoziZG_field",
68
+ "accountName": "xoziZG_accountName",
69
+ "balanceRow": "xoziZG_balanceRow",
70
+ "expiryRow": "xoziZG_expiryRow",
71
+ "actions": "xoziZG_actions",
88
72
  "balance": "xoziZG_balance",
89
- "creditFooter": "xoziZG_creditFooter",
90
- "creditRows": "xoziZG_creditRows",
73
+ "modelName": "xoziZG_modelName",
74
+ "modelRequests": "xoziZG_modelRequests",
75
+ "modelChart": "xoziZG_modelChart",
91
76
  "headText": "xoziZG_headText",
92
- "actions": "xoziZG_actions",
77
+ "accountRowActions": "xoziZG_accountRowActions",
78
+ "usageBar": "xoziZG_usageBar",
79
+ "creditHint": "xoziZG_creditHint",
93
80
  "count": "xoziZG_count",
94
- "accountNameRow": "xoziZG_accountNameRow",
81
+ "area": "xoziZG_area",
82
+ "accountHint": "xoziZG_accountHint",
83
+ "expiryLabel": "xoziZG_expiryLabel",
84
+ "accountInfo": "xoziZG_accountInfo",
85
+ "error": "xoziZG_error",
86
+ "accountToolbar": "xoziZG_accountToolbar",
87
+ "manualForm": "xoziZG_manualForm",
88
+ "body": "xoziZG_body",
89
+ "creditFooter": "xoziZG_creditFooter",
90
+ "balanceUnit": "xoziZG_balanceUnit",
91
+ "name": "xoziZG_name",
92
+ "tabs": "xoziZG_tabs",
95
93
  "usageFill": "xoziZG_usageFill",
96
- "accountEntry": "xoziZG_accountEntry",
97
- "modelRow": "xoziZG_modelRow",
98
- "modelRequests": "xoziZG_modelRequests",
94
+ "choiceRow": "xoziZG_choiceRow",
95
+ "creditRow": "xoziZG_creditRow",
96
+ "link": "xoziZG_link",
97
+ "sectionLabel": "xoziZG_sectionLabel",
98
+ "danger": "xoziZG_danger",
99
99
  "manualSummary": "xoziZG_manualSummary"
100
100
  };
101
101
  //#endregion
@@ -1203,8 +1203,11 @@ function CodeArtsPluginCard({ t }) {
1203
1203
  /** 与 src/adapter.ts 的 CODEARTS_PROVIDER 对齐 —— client 不能 import host 模块。 */
1204
1204
  const CODEARTS_PROVIDER = "codearts";
1205
1205
  const STATUS_PATH = "/api/codearts/status";
1206
+ const QUEUE_PATH = "/api/codearts/queue";
1206
1207
  /** 轮询间隔:与缓存 TTL(60s)对齐,再短也只是反复命中缓存。 */
1207
1208
  const REFRESH_INTERVAL_MS = 6e4;
1209
+ /** 排队位置变得快:官方客户端也是 5 秒一次;这是本机路由,打不疼上游。 */
1210
+ const QUEUE_INTERVAL_MS = 5e3;
1208
1211
  function readSelection(useProjection) {
1209
1212
  if (useProjection === void 0) return void 0;
1210
1213
  try {
@@ -1220,6 +1223,7 @@ function CodeArtsCreditDock({ useProjection, t }) {
1220
1223
  const active = isCodeArts(readSelection(useProjection));
1221
1224
  const [remain, setRemain] = (0, react.useState)(null);
1222
1225
  const [total, setTotal] = (0, react.useState)(0);
1226
+ const [queued, setQueued] = (0, react.useState)(null);
1223
1227
  const mounted = (0, react.useRef)(true);
1224
1228
  (0, react.useEffect)(() => {
1225
1229
  mounted.current = true;
@@ -1257,13 +1261,40 @@ function CodeArtsCreditDock({ useProjection, t }) {
1257
1261
  if (timer !== void 0) window.clearInterval(timer);
1258
1262
  };
1259
1263
  }, [active]);
1260
- if (!active || remain === null) return null;
1261
- const text = t("creditsDock", { remain: new Intl.NumberFormat().format(remain) });
1262
- const title = t("creditsDockTitle", {
1263
- remain,
1264
+ (0, react.useEffect)(() => {
1265
+ if (!active) {
1266
+ setQueued(null);
1267
+ return;
1268
+ }
1269
+ let timer;
1270
+ const refresh = async () => {
1271
+ try {
1272
+ const res = await fetch(QUEUE_PATH, {
1273
+ headers: { Accept: "application/json" },
1274
+ credentials: "same-origin"
1275
+ });
1276
+ if (!res.ok) return;
1277
+ const data = await res.json().catch(() => void 0);
1278
+ if (mounted.current) setQueued(data?.queue?.[0] ?? null);
1279
+ } catch {}
1280
+ };
1281
+ refresh();
1282
+ timer = window.setInterval(refresh, QUEUE_INTERVAL_MS);
1283
+ return () => {
1284
+ if (timer !== void 0) window.clearInterval(timer);
1285
+ };
1286
+ }, [active]);
1287
+ if (!active || remain === null && queued === null) return null;
1288
+ const text = remain === null ? void 0 : t("creditsDock", { remain: new Intl.NumberFormat().format(remain) });
1289
+ const title = queued !== null ? t("queueDockTitle", {
1290
+ model: queued.model,
1291
+ position: queued.position,
1292
+ length: queued.length
1293
+ }) : t("creditsDockTitle", {
1294
+ remain: remain ?? 0,
1264
1295
  total
1265
1296
  });
1266
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1297
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1267
1298
  style: {
1268
1299
  display: "inline-flex",
1269
1300
  alignItems: "center",
@@ -1277,7 +1308,7 @@ function CodeArtsCreditDock({ useProjection, t }) {
1277
1308
  minWidth: "max-content"
1278
1309
  },
1279
1310
  title,
1280
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: text })
1311
+ children: [queued !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("queueDock", { position: queued.position }) }), text !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: text })]
1281
1312
  });
1282
1313
  }
1283
1314
  //#endregion
@@ -1367,6 +1398,8 @@ const zh = {
1367
1398
  creditsNeedSignIn: "这个账号需要重新登录",
1368
1399
  creditsDock: "积分 {remain}",
1369
1400
  creditsDockTitle: "CodeArts 剩余积分 {remain} / {total}",
1401
+ queueDock: "排队第 {position} 位",
1402
+ queueDockTitle: "上游排队:{model} · 第 {position} 位 / 共 {length} 条",
1370
1403
  claimButton: "签到领积分",
1371
1404
  claimButtonDone: "今日已领",
1372
1405
  claimPending: "领取中…",
@@ -1462,6 +1495,8 @@ const en = {
1462
1495
  creditsNeedSignIn: "This account needs signing in again",
1463
1496
  creditsDock: "Credits {remain}",
1464
1497
  creditsDockTitle: "CodeArts credits left {remain} / {total}",
1498
+ queueDock: "Queued #{position}",
1499
+ queueDockTitle: "Upstream queue: {model} · position {position} of {length}",
1465
1500
  claimButton: "Claim daily credits",
1466
1501
  claimButtonDone: "Claimed today",
1467
1502
  claimPending: "Claiming…",
package/lib/index.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  import z from "@deepseek-ai/schemastery";
2
+ import "@earendil-works/pi-ai";
2
3
  import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
3
4
  import { SettingsNamespace } from "@deepseek-ai/dsh-settings";
4
5
  import { Context } from "@deepseek-ai/cordis";
@@ -23,12 +24,25 @@ import { AttachmentStore } from "@deepseek-ai/dsh-attachment";
23
24
  * @module dsh-codearts/catalog
24
25
  */
25
26
  /** One model the adapter exposes to DSH. */
27
+ interface CodeArtsModelThinking {
28
+ /** 上游提供的思考强度(`thinking-variants` 的 `strengths`,如 `['high','max']`)。 */
29
+ strengths: readonly string[];
30
+ /** 上游默认强度(`default_strength`)。宿主不强制使用,只作诊断。 */
31
+ default?: string;
32
+ }
26
33
  interface CodeArtsModelInfo {
27
34
  id: string;
28
35
  name: string;
29
36
  contextWindow: number;
30
37
  maxTokens: number;
31
38
  supportsImages: boolean;
39
+ /**
40
+ * 该模型可选的思考强度。
41
+ *
42
+ * 由动态发现填入(`POST /v1/model/thinking-variants`,按账号授予):空/缺省表示
43
+ * 上游没给这条模型开关,宿主就不该显示推理档位选择器。
44
+ */
45
+ thinking?: CodeArtsModelThinking;
32
46
  /**
33
47
  * 积分消耗倍率(上游 `credit[].ratio_display` 的原值,如 `0.18x`)。
34
48
  *
@@ -148,7 +162,9 @@ interface PreparedChatBody {
148
162
  * (两者任一超限,上游都只会流内回 InferHub.001001005.400
149
163
  * "The request param is invalid, Please check it",不告诉我们是哪一个)
150
164
  * 3. 丢弃非白名单字段(stream_options / store / logprobs 等虽实测可容忍,
151
- * 但按 codearts2api 观察到的真实流量收窄,避免网关后续收紧)
165
+ * 但按 codearts2api 观察到的真实流量收窄,避免网关后续收紧);
166
+ * 例外是 `reasoning_effort`(DSH 的思考档位):官方内核同样以这个顶层字段
167
+ * 透传 variant,见 kernel 的 `inferhub getArgs`。
152
168
  * 4. tools[].function 去掉 strict(OpenAI 专有)
153
169
  * 5. 注入 CodeArts 特有字段:chat_id(按对话派生,见 conversationChatId)、
154
170
  * prompt_cache_key、tool_stream
@@ -178,8 +194,11 @@ interface CallerIdentity {
178
194
  userName: string;
179
195
  domainId: string;
180
196
  }
181
- /** 模型清单的三路来源。任一路失败都不阻断,只有三路全失败才算失败。 */
182
- type ModelSource = 'builtin' | 'benefit' | 'agent';
197
+ /**
198
+ * 模型清单的来源。前三路是目录本身(任一路失败都不阻断,只有三路全失败才算失败),
199
+ * `thinking` 只是目录之上的可选增强:它失败时模型照常可用,只是没有思考档位选择器。
200
+ */
201
+ type ModelSource = 'builtin' | 'benefit' | 'agent' | 'thinking';
183
202
  interface ModelDiscoveryResult {
184
203
  /** 合并后的清单(已去重、已排序)。 */
185
204
  models: readonly CodeArtsModelInfo[];
@@ -191,6 +210,13 @@ interface ModelDiscoveryResult {
191
210
  message: string;
192
211
  }[];
193
212
  }
213
+ /** 一条请求在上游队列里的位置。 */
214
+ interface CodeArtsQueueStatus {
215
+ status: string;
216
+ queueLength: number;
217
+ queuePosition: number;
218
+ message: string;
219
+ }
194
220
  /**
195
221
  * 动态发现当前账号可用的模型清单(三路合并)。
196
222
  *
@@ -405,6 +431,77 @@ declare class CodeArtsAccountPool {
405
431
  hasUsable(): boolean;
406
432
  }
407
433
  //#endregion
434
+ //#region src/queue.d.ts
435
+ interface CodeArtsQueueEntry {
436
+ /** chat_id,与上游 task_id 的尾段一致。 */
437
+ id: string;
438
+ model: string;
439
+ position: number;
440
+ length: number;
441
+ status: string;
442
+ message?: string;
443
+ updatedAt: number;
444
+ }
445
+ type CodeArtsQueueOutcome =
446
+ /** 轮到了(或模型本来就不排队),可以发请求。 */
447
+ {
448
+ kind: 'working';
449
+ } |
450
+ /** 等了 `maxWaitMs` 还在队里:把位置报给用户,让他稍后再发。 */
451
+ {
452
+ kind: 'queued';
453
+ position: number;
454
+ length: number;
455
+ message: string;
456
+ } |
457
+ /** 队列本身已满,上游让用户换模型/升级套餐。 */
458
+ {
459
+ kind: 'queue_full';
460
+ message: string;
461
+ } |
462
+ /** 排队接口连续失败,放弃排队并报出原因。 */
463
+ {
464
+ kind: 'error';
465
+ message: string;
466
+ } |
467
+ /** 用户取消或插件退出。 */
468
+ {
469
+ kind: 'aborted';
470
+ };
471
+ interface CodeArtsQueueGateOptions {
472
+ /** 两次查询之间的间隔(官方客户端是 5 秒)。 */
473
+ intervalMs?: number;
474
+ /** 最多替用户等多久。超过就回 `queued`,别把请求挂着。 */
475
+ maxWaitMs?: number;
476
+ /** 连续失败几次就放弃。 */
477
+ maxErrors?: number;
478
+ /** 注入查询函数(测试用)。 */
479
+ fetchStatus?: (credential: CodeArtsResolved, chatId: string, model: string, signal: AbortSignal) => Promise<CodeArtsQueueStatus>;
480
+ /** 注入清号函数(测试用)。 */
481
+ clearStatus?: (credential: CodeArtsResolved, chatId: string, model: string) => Promise<void>;
482
+ now?: () => number;
483
+ }
484
+ declare class CodeArtsQueueGate {
485
+ private readonly entries;
486
+ private readonly controllers;
487
+ private readonly intervalMs;
488
+ private readonly maxWaitMs;
489
+ private readonly maxErrors;
490
+ private readonly query;
491
+ private readonly clear;
492
+ private readonly now;
493
+ constructor(options?: CodeArtsQueueGateOptions);
494
+ /**
495
+ * 占号并**最多等 `maxWaitMs`** 轮到。调用方拿到 `working` 后才发 chat 请求;
496
+ * 其余结果(含 `queued`)都不要再发 —— `queued` 是要把位置原样告诉用户。
497
+ */
498
+ wait(credential: CodeArtsResolved, chatId: string, model: string, signal?: AbortSignal): Promise<CodeArtsQueueOutcome>;
499
+ /** 正在排队的条目(给底部条)。 */
500
+ snapshot(): readonly CodeArtsQueueEntry[];
501
+ dispose(): void;
502
+ private pause;
503
+ }
504
+ //#endregion
408
505
  //#region src/usage.d.ts
409
506
  /**
410
507
  * 用量账本:把聊天流里的 usage 帧累计成「今天用了多少」。
@@ -520,6 +617,11 @@ interface CodeArtsShimOptions {
520
617
  pool?: CodeArtsAccountPool;
521
618
  /** 用量账本。给了就在转发流的同时把 usage 帧记下来(卡片上的「今日用量」)。 */
522
619
  usage?: CodeArtsUsageTracker;
620
+ /**
621
+ * 上游排队闸门。给了就在发请求前占号、轮询到轮到再开流
622
+ * (底部积分条旁边展示「排队第 N 位」)。
623
+ */
624
+ queue?: CodeArtsQueueGate;
523
625
  logger?: ShimLogger;
524
626
  }
525
627
  declare function createCodeArtsShim(options: CodeArtsShimOptions): CodeArtsShim;
package/lib/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { A as isBenefitModel, C as upstreamErrorMessage, D as creditRatioLabel, E as clearBenefitBlocked, F as startLogin, M as CodeArtsCredentialStore, N as codeartsAccountsPath, O as filterEnabledModels, P as codeartsAuthPath, S as signedGet, T as FALLBACK_CODEARTS_MODELS, _ as isWelfareClaimedStatus, a as SNAP_HOST, b as prepareChatBody, c as claimDailyWelfare, d as fetchModels, f as fetchWelfareCampaigns, g as isTpmLimit, h as isQueueableRate, i as refreshCatalog, j as CodeArtsAccountStore, k as isBenefitBlocked, l as degradeTokenCeiling, m as isParamInvalidFrame, n as codeartsUsagePath, o as canonicalModel, p as isConcurrencyCap, r as discoverModels, s as chatStream, t as CodeArtsUsageTracker, u as fetchCallerIdentity, v as maxTokensCeilingFor, w as CodeArtsCatalog, x as rateLimitFrame, y as pickDailyWelfareCampaign } from "./usage-BEoMNRwo.mjs";
1
+ import { A as filterEnabledModels, C as rateLimitFrame, D as FALLBACK_CODEARTS_MODELS, E as CodeArtsCatalog, F as codeartsAccountsPath, I as codeartsAuthPath, L as startLogin, M as isBenefitModel, N as CodeArtsAccountStore, O as clearBenefitBlocked, P as CodeArtsCredentialStore, S as prepareChatBody, T as upstreamErrorMessage, _ as isQueueableRate, a as SNAP_HOST, b as maxTokensCeilingFor, c as claimDailyWelfare, d as fetchCallerIdentity, f as fetchModels, g as isParamInvalidFrame, h as isConcurrencyCap, i as refreshCatalog, j as isBenefitBlocked, k as creditRatioLabel, l as clearQueueStatus, m as fetchWelfareCampaigns, n as codeartsUsagePath, o as canonicalModel, p as fetchQueueStatus, r as discoverModels, s as chatStream, t as CodeArtsUsageTracker, u as degradeTokenCeiling, v as isTpmLimit, w as signedGet, x as pickDailyWelfareCampaign, y as isWelfareClaimedStatus } from "./usage-zgF0QuQ4.mjs";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import { randomBytes, timingSafeEqual } from "node:crypto";
4
4
  import { createServer } from "node:http";
@@ -136,6 +136,155 @@ var CodeArtsAccountPool = class {
136
136
  }
137
137
  };
138
138
  //#endregion
139
+ //#region src/queue.ts
140
+ /**
141
+ * 上游排队的「闸门」。
142
+ *
143
+ * 26.10.0 起上游有 `GET /api/v1/queue/status`,但它**不是只读查询**:GET 本身会把
144
+ * 这个 task 挂进队列、返回队尾位置,`DELETE` 才把它清掉(2026-10-21 实测)。真正
145
+ * 的 chat 请求并不看这个号 —— 拿着一张第 535 位的票去 POST,942ms 就开流了。
146
+ * 官方客户端的做法是:只对 `enable_queue` 的模型,先轮询排队接口,等状态变成
147
+ * `working` 才发聊天请求(见 vscode-codebot 的 `startQueuePolling`)。
148
+ *
149
+ * 这里**不照搬官方那种「一直等到轮到」**:宿主的流空闲超时只有 90 秒(等待期间
150
+ * 一个字节都发不出去,SSE 注释帧也不算事件,实测每 90 秒就被掐一次重来),而
151
+ * flash 高峰期队尾有 500+ 号、要等二十几分钟 —— 没有人会挂着对话等它。所以
152
+ * `wait()` 只等 `maxWaitMs`(默认 20 秒):
153
+ * - 等到 working → 放行;
154
+ * - 还在队里 → 返回 `queued` 带上位置和队长度,由 shim 直接回一句「排队中,
155
+ * 稍后再试」,让用户自己决定要不要再发一次;
156
+ * - 队列已满 → `queue_full`;连续失败 → `error`。
157
+ *
158
+ * 三种收尾都会 DELETE 清号,别把幽灵排队留在上游把队列越撑越长。
159
+ *
160
+ * @module dsh-codearts/queue
161
+ */
162
+ const CLEAR_TIMEOUT_MS = 5e3;
163
+ var CodeArtsQueueGate = class {
164
+ entries = /* @__PURE__ */ new Map();
165
+ controllers = /* @__PURE__ */ new Set();
166
+ intervalMs;
167
+ maxWaitMs;
168
+ maxErrors;
169
+ query;
170
+ clear;
171
+ now;
172
+ constructor(options = {}) {
173
+ this.intervalMs = options.intervalMs ?? 5e3;
174
+ this.maxWaitMs = options.maxWaitMs ?? 2e4;
175
+ this.maxErrors = options.maxErrors ?? 3;
176
+ this.query = options.fetchStatus ?? fetchQueueStatus;
177
+ this.clear = options.clearStatus ?? ((credential, chatId, model) => clearQueueStatus(credential, chatId, model, AbortSignal.timeout(CLEAR_TIMEOUT_MS)));
178
+ this.now = options.now ?? Date.now;
179
+ }
180
+ /**
181
+ * 占号并**最多等 `maxWaitMs`** 轮到。调用方拿到 `working` 后才发 chat 请求;
182
+ * 其余结果(含 `queued`)都不要再发 —— `queued` 是要把位置原样告诉用户。
183
+ */
184
+ async wait(credential, chatId, model, signal) {
185
+ if (chatId === "") return { kind: "working" };
186
+ if (signal?.aborted === true) return { kind: "aborted" };
187
+ const controller = new AbortController();
188
+ const onAbort = () => controller.abort();
189
+ signal?.addEventListener("abort", onAbort, { once: true });
190
+ this.controllers.add(controller);
191
+ const entry = {
192
+ id: chatId,
193
+ model,
194
+ position: 0,
195
+ length: 0,
196
+ status: "",
197
+ updatedAt: this.now()
198
+ };
199
+ this.entries.set(chatId, entry);
200
+ let errors = 0;
201
+ const deadlineAt = this.now() + this.maxWaitMs;
202
+ try {
203
+ for (;;) {
204
+ if (controller.signal.aborted) return { kind: "aborted" };
205
+ let status;
206
+ try {
207
+ status = await this.query(credential, chatId, model, controller.signal);
208
+ errors = 0;
209
+ } catch (error) {
210
+ if (controller.signal.aborted) return { kind: "aborted" };
211
+ errors += 1;
212
+ if (errors >= this.maxErrors) return {
213
+ kind: "error",
214
+ message: `查询上游排队状态连续失败 ${errors} 次:${errorText(error)}`
215
+ };
216
+ await this.pause(controller.signal);
217
+ continue;
218
+ }
219
+ entry.status = status.status;
220
+ entry.position = status.queuePosition;
221
+ entry.length = status.queueLength;
222
+ entry.message = status.message;
223
+ entry.updatedAt = this.now();
224
+ const verdict = verdictOf(status);
225
+ if (verdict !== void 0) return verdict;
226
+ if (this.now() >= deadlineAt) return {
227
+ kind: "queued",
228
+ position: status.queuePosition,
229
+ length: status.queueLength,
230
+ message: status.message
231
+ };
232
+ await this.pause(controller.signal, Math.min(this.intervalMs, deadlineAt - this.now()));
233
+ }
234
+ } finally {
235
+ this.controllers.delete(controller);
236
+ signal?.removeEventListener("abort", onAbort);
237
+ if (this.entries.get(chatId) === entry) this.entries.delete(chatId);
238
+ await this.clear(credential, chatId, model).catch(() => {});
239
+ }
240
+ }
241
+ /** 正在排队的条目(给底部条)。 */
242
+ snapshot() {
243
+ return [...this.entries.values()].filter((entry) => entry.position > 0 || entry.status !== "" && entry.status !== "working").map((entry) => ({
244
+ id: entry.id,
245
+ model: entry.model,
246
+ position: entry.position,
247
+ length: entry.length,
248
+ status: entry.status,
249
+ ...entry.message === "" ? {} : { message: entry.message },
250
+ updatedAt: entry.updatedAt
251
+ }));
252
+ }
253
+ dispose() {
254
+ for (const controller of [...this.controllers]) controller.abort();
255
+ }
256
+ pause(signal, delay = this.intervalMs) {
257
+ return new Promise((resolve) => {
258
+ const timer = setTimeout(finish, Math.max(0, delay));
259
+ timer.unref?.();
260
+ function finish() {
261
+ clearTimeout(timer);
262
+ signal.removeEventListener("abort", finish);
263
+ resolve();
264
+ }
265
+ signal.addEventListener("abort", finish, { once: true });
266
+ });
267
+ }
268
+ };
269
+ /**
270
+ * 这个状态还要不要继续等。
271
+ *
272
+ * `working` = 轮到;`queue_full` = 队列已满,上游建议换模型;其余(waiting 等)继续。
273
+ * 空状态且位置为 0 说明这个模型根本没排队(如 GLM),直接放行。
274
+ */
275
+ function verdictOf(status) {
276
+ const text = status.status.trim().toLowerCase();
277
+ if (text === "working") return { kind: "working" };
278
+ if (text === "queue_full") return {
279
+ kind: "queue_full",
280
+ message: status.message
281
+ };
282
+ if (text === "" && status.queuePosition === 0 && status.queueLength === 0) return { kind: "working" };
283
+ }
284
+ function errorText(error) {
285
+ return error instanceof Error ? error.message : String(error);
286
+ }
287
+ //#endregion
139
288
  //#region src/credits.ts
140
289
  /**
141
290
  * CodeArts 积分与额度。
@@ -392,7 +541,8 @@ var CodeArtsCreditCache = class {
392
541
  * MVP simplifications vs dsh-codebuddy-cli:
393
542
  * - only the default-slot credit multiplier goes into the model name (`(x0.18)`);
394
543
  * per-request cost is not modelled (subscription quota, cost reported as zero)
395
- * - no per-model reasoning ladder (models are surfaced as plain chat models)
544
+ * - reasoning levels are surfaced only where upstream's thinking-variants names
545
+ * them (`GLM-5.2` → high/max); everything else stays a plain chat model
396
546
  *
397
547
  * @module dsh-codearts/adapter
398
548
  */
@@ -438,8 +588,38 @@ const INERT_AUTH = {
438
588
  }
439
589
  }
440
590
  };
591
+ /** pi-ai's selectable levels, in escalation order. */
592
+ const REASONING_LEVELS = [
593
+ "off",
594
+ "minimal",
595
+ "low",
596
+ "medium",
597
+ "high",
598
+ "xhigh",
599
+ "max"
600
+ ];
601
+ /**
602
+ * Turn upstream's thinking strengths into pi-ai reasoning metadata.
603
+ *
604
+ * Every level upstream does not declare is pinned to `null`: pi-ai treats a
605
+ * missing entry as supported, so leaving them out would offer Off/Minimal/Low/
606
+ * Medium for a model that only knows high/max. `off` is pinned as well — the
607
+ * gateway's default is already high, and claiming Off would send no effort
608
+ * field and still think.
609
+ */
610
+ function thinkingFields(strengths) {
611
+ const offered = new Set(strengths ?? []);
612
+ if (!REASONING_LEVELS.some((level) => level !== "off" && offered.has(level))) return { reasoning: false };
613
+ const map = {};
614
+ for (const level of REASONING_LEVELS) map[level] = offered.has(level) ? level : null;
615
+ return {
616
+ reasoning: true,
617
+ thinkingLevelMap: map
618
+ };
619
+ }
441
620
  /** Build one pi-ai model descriptor pointing at the loopback shim. */
442
621
  function toPiModel(info, baseUrl) {
622
+ const thinking = thinkingFields(info.thinking?.strengths);
443
623
  return {
444
624
  id: info.id,
445
625
  name: info.name,
@@ -447,7 +627,8 @@ function toPiModel(info, baseUrl) {
447
627
  provider: CODEARTS_PROVIDER,
448
628
  baseUrl,
449
629
  input: info.supportsImages === true ? ["text", "image"] : ["text"],
450
- reasoning: false,
630
+ reasoning: thinking.reasoning,
631
+ ...thinking.thinkingLevelMap === void 0 ? {} : { thinkingLevelMap: thinking.thinkingLevelMap },
451
632
  cost: NO_COST,
452
633
  contextWindow: info.contextWindow,
453
634
  maxTokens: info.maxTokens
@@ -627,6 +808,14 @@ function writeOpenAIError(res, status, kind, message) {
627
808
  code: kind
628
809
  } });
629
810
  }
811
+ /**
812
+ * 排队没等到时回给用户的那句:优先用上游自己的文案(它就是写给人看的,带位置和
813
+ * 「建议切换模型/升级套餐」),上游没给才自己拼。
814
+ */
815
+ function queuedMessage(model, gate) {
816
+ const upstream = gate.message.trim();
817
+ return `${upstream === "" ? `上游排队中:${model} 第 ${gate.position} 位(共 ${gate.length} 条)` : upstream} 插件没有一直等下去,请稍后重试。`;
818
+ }
630
819
  function readBody$1(req) {
631
820
  return new Promise((resolve, reject) => {
632
821
  const chunks = [];
@@ -646,7 +835,6 @@ function readBody$1(req) {
646
835
  }
647
836
  /** 一次请求最多试几个账号(与 Go 参考的 MaxRotate 一致)。 */
648
837
  const MAX_ROTATE = 3;
649
- /** 一次请求最多把 max_tokens 降几档(65536 → 32768 → 16384 → 8192)。 */
650
838
  const MAX_CEILING_HEALS = 3;
651
839
  /**
652
840
  * 所有账号都撞上并发上限时,回落到「在同一账号上原地重发」的预算。
@@ -666,10 +854,40 @@ const SERVER_COOLDOWN_MS = 3e4;
666
854
  /** 零散错误累计到阈值后冷却该账号。 */
667
855
  const ERROR_COOLDOWN_MS = 6e4;
668
856
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
857
+ const recentShapes = [];
858
+ /** 最近的请求形状,最新在前。 */
859
+ function recentRequestShapes() {
860
+ return [...recentShapes].reverse();
861
+ }
862
+ function noteRequestShape(raw, model) {
863
+ let parsed;
864
+ try {
865
+ parsed = JSON.parse(raw);
866
+ } catch {
867
+ return;
868
+ }
869
+ if (parsed === null || typeof parsed !== "object") return;
870
+ const list = Array.isArray(parsed.messages) ? parsed.messages : [];
871
+ const roles = list.map((message) => {
872
+ if (message === null || typeof message !== "object") return "";
873
+ const role = message.role;
874
+ return typeof role === "string" ? role : "";
875
+ });
876
+ recentShapes.push({
877
+ at: Date.now(),
878
+ model,
879
+ messages: list.length,
880
+ roles,
881
+ emptyRoleAt: roles.map((role, index) => role === "" ? index : -1).filter((index) => index >= 0),
882
+ tools: Array.isArray(parsed.tools) ? parsed.tools.length : 0
883
+ });
884
+ if (recentShapes.length > 8) recentShapes.shift();
885
+ }
669
886
  function createCodeArtsShim(options) {
670
887
  const { store, catalog } = options;
671
888
  const logger = options.logger;
672
889
  const usage = options.usage;
890
+ const queue = options.queue;
673
891
  const pool = options.pool ?? new CodeArtsAccountPool([]);
674
892
  const SHARED_SECRET = randomBytes(32).toString("base64url");
675
893
  function bearerOk(req) {
@@ -860,6 +1078,7 @@ function createCodeArtsShim(options) {
860
1078
  if (typeof inbound.model === "string") contextWindow = contextWindowFor(canonicalModel(inbound.model));
861
1079
  } catch {}
862
1080
  const checked = prepareChatBody(raw, contextWindow);
1081
+ noteRequestShape(raw, checked.model);
863
1082
  if (checked.error !== void 0) {
864
1083
  writeOpenAIError(res, 400, "context_overflow", checked.error);
865
1084
  return;
@@ -898,6 +1117,18 @@ function createCodeArtsShim(options) {
898
1117
  pool.disable(id, "no credential");
899
1118
  continue;
900
1119
  }
1120
+ if (queue !== void 0) {
1121
+ const gate = await queue.wait(credential, checked.chatId, checked.model, controller.signal);
1122
+ if (controller.signal.aborted) return;
1123
+ if (gate.kind === "queued") {
1124
+ writeOpenAIError(res, 400, "queued", queuedMessage(checked.model, gate));
1125
+ return;
1126
+ }
1127
+ if (gate.kind === "queue_full" || gate.kind === "error") {
1128
+ writeOpenAIError(res, gate.kind === "queue_full" ? 429 : 502, gate.kind, gate.message);
1129
+ return;
1130
+ }
1131
+ }
901
1132
  const opened = await openWithCeilingHeal(credential, raw, contextWindow, controller.signal);
902
1133
  const { prepared, result } = opened;
903
1134
  if (controller.signal.aborted) {
@@ -1068,6 +1299,10 @@ const CODEARTS_ACCOUNTS_PATH = "/api/codearts/accounts";
1068
1299
  const CODEARTS_CLAIM_PATH = "/api/codearts/claim";
1069
1300
  /** POST /api/codearts/refresh-models — 立刻重拉一次模型目录,并清掉「福利今日不可用」标记。 */
1070
1301
  const CODEARTS_REFRESH_MODELS_PATH = "/api/codearts/refresh-models";
1302
+ /** GET /api/codearts/queue — 上游排队位置(只在等开流期间有值,底部积分条旁边展示)。 */
1303
+ const CODEARTS_QUEUE_PATH = "/api/codearts/queue";
1304
+ /** 最近出站请求的**形状**摘要(只有 role 名与计数,不含内容),排障用。 */
1305
+ const CODEARTS_DEBUG_PATH = "/api/codearts/debug";
1071
1306
  /**
1072
1307
  * 由完整目录 + 已存 enabled 计算 selection。
1073
1308
  * - enabled 为空/未存 => 全部勾选(restricted=false)
@@ -1507,6 +1742,34 @@ function refreshModelsHandler(deps) {
1507
1742
  }
1508
1743
  };
1509
1744
  }
1745
+ /** GET /api/codearts/queue — 排队位置快照(无跟踪器/无排队时给空数组)。 */
1746
+ function queueHandler(deps) {
1747
+ return async (req, res) => {
1748
+ if (req.method !== "GET") {
1749
+ json(res, 405, { error: "method not allowed" });
1750
+ return;
1751
+ }
1752
+ if (!loopbackRequest(req)) {
1753
+ json(res, 403, { error: "request-not-trusted" });
1754
+ return;
1755
+ }
1756
+ json(res, 200, { queue: deps.queue?.() ?? [] });
1757
+ };
1758
+ }
1759
+ /** GET /api/codearts/debug — 最近出站请求的形状摘要(只有 role 名与计数)。 */
1760
+ function debugHandler(deps) {
1761
+ return async (req, res) => {
1762
+ if (req.method !== "GET") {
1763
+ json(res, 405, { error: "method not allowed" });
1764
+ return;
1765
+ }
1766
+ if (!loopbackRequest(req)) {
1767
+ json(res, 403, { error: "request-not-trusted" });
1768
+ return;
1769
+ }
1770
+ json(res, 200, { requests: deps.recentRequests?.() ?? [] });
1771
+ };
1772
+ }
1510
1773
  /** Mount the status / models / token routes. */
1511
1774
  function registerCodeArtsStatusRoute(ctx, deps) {
1512
1775
  ctx.effect(() => {
@@ -1558,6 +1821,16 @@ function registerCodeArtsStatusRoute(ctx, deps) {
1558
1821
  path: CODEARTS_REFRESH_MODELS_PATH,
1559
1822
  handler: refreshModelsHandler(deps)
1560
1823
  });
1824
+ const disposeQueue = ctx.webServer.register({
1825
+ kind: "exact",
1826
+ path: CODEARTS_QUEUE_PATH,
1827
+ handler: queueHandler(deps)
1828
+ });
1829
+ const disposeDebug = ctx.webServer.register({
1830
+ kind: "exact",
1831
+ path: CODEARTS_DEBUG_PATH,
1832
+ handler: debugHandler(deps)
1833
+ });
1561
1834
  return () => {
1562
1835
  disposeStatus();
1563
1836
  disposeModels();
@@ -1568,6 +1841,8 @@ function registerCodeArtsStatusRoute(ctx, deps) {
1568
1841
  disposeAccounts();
1569
1842
  disposeClaim();
1570
1843
  disposeRefresh();
1844
+ disposeQueue();
1845
+ disposeDebug();
1571
1846
  for (const s of activeLogins.values()) s.close();
1572
1847
  activeLogins.clear();
1573
1848
  };
@@ -1740,6 +2015,7 @@ function apply(ctx, rawConfig) {
1740
2015
  const pool = new CodeArtsAccountPool([]);
1741
2016
  const catalog = new CodeArtsCatalog();
1742
2017
  const usage = new CodeArtsUsageTracker({ path: codeartsUsagePath() });
2018
+ const queue = new CodeArtsQueueGate();
1743
2019
  const credits = new CodeArtsCreditCache();
1744
2020
  /** /status 是卡片与对话底部都会轮询的接口,积分慢请求不能把它拖成转圈。 */
1745
2021
  const CREDIT_TIMEOUT_MS = 8e3;
@@ -1809,6 +2085,8 @@ function apply(ctx, rawConfig) {
1809
2085
  setEnabledModels,
1810
2086
  pool: () => pool.list(),
1811
2087
  usage: () => usage.snapshot(),
2088
+ queue: () => queue.snapshot(),
2089
+ recentRequests: () => recentRequestShapes(),
1812
2090
  credits: (signal) => loadCredits(signal),
1813
2091
  claim: async (accountId) => {
1814
2092
  const ids = accountId === void 0 ? await store.ids() : [accountId];
@@ -1886,6 +2164,7 @@ function apply(ctx, rawConfig) {
1886
2164
  let stopped = false;
1887
2165
  ctx.effect(() => () => {
1888
2166
  stopped = true;
2167
+ queue.dispose();
1889
2168
  usage.flush().catch(() => {});
1890
2169
  shim.close().catch(() => {});
1891
2170
  });
@@ -1917,6 +2196,7 @@ function apply(ctx, rawConfig) {
1917
2196
  catalog,
1918
2197
  pool,
1919
2198
  usage,
2199
+ queue,
1920
2200
  logger: ctx.logger
1921
2201
  });
1922
2202
  usage.load().catch(() => {});
@@ -1079,6 +1079,7 @@ function richerModel(current, next) {
1079
1079
  const keep = richerThan(next, current) ? next : current;
1080
1080
  const drop = keep === next ? current : next;
1081
1081
  const ratio = keep.ratio ?? drop.ratio;
1082
+ const thinking = keep.thinking ?? drop.thinking;
1082
1083
  return {
1083
1084
  id: keep.id,
1084
1085
  name: keep.name || drop.name || keep.id,
@@ -1086,6 +1087,7 @@ function richerModel(current, next) {
1086
1087
  maxTokens: keep.maxTokens > 0 ? keep.maxTokens : drop.maxTokens,
1087
1088
  supportsImages: keep.supportsImages || drop.supportsImages,
1088
1089
  ...ratio === void 0 ? {} : { ratio },
1090
+ ...thinking === void 0 ? {} : { thinking },
1089
1091
  ...benefit ? { benefit: true } : {}
1090
1092
  };
1091
1093
  }
@@ -1116,6 +1118,7 @@ function backfillParams(discovered, params = FALLBACK_CODEARTS_MODELS) {
1116
1118
  maxTokens: current.maxTokens > 0 ? current.maxTokens : fallback.maxTokens,
1117
1119
  supportsImages: current.supportsImages || fallback.supportsImages,
1118
1120
  ...current.ratio === void 0 ? {} : { ratio: current.ratio },
1121
+ ...current.thinking === void 0 ? {} : { thinking: current.thinking },
1119
1122
  ...current.benefit === true ? { benefit: true } : {}
1120
1123
  });
1121
1124
  }
@@ -1232,10 +1235,12 @@ var upstream_exports = /* @__PURE__ */ __exportAll({
1232
1235
  chatStream: () => chatStream,
1233
1236
  claimBenefit: () => claimBenefit,
1234
1237
  claimDailyWelfare: () => claimDailyWelfare,
1238
+ clearQueueStatus: () => clearQueueStatus,
1235
1239
  degradeTokenCeiling: () => degradeTokenCeiling,
1236
1240
  estimateTokens: () => estimateTokens,
1237
1241
  fetchCallerIdentity: () => fetchCallerIdentity,
1238
1242
  fetchModels: () => fetchModels,
1243
+ fetchQueueStatus: () => fetchQueueStatus,
1239
1244
  fetchWelfareCampaigns: () => fetchWelfareCampaigns,
1240
1245
  isBenefitMissing: () => isBenefitMissing,
1241
1246
  isConcurrencyCap: () => isConcurrencyCap,
@@ -1259,6 +1264,13 @@ const EP_BENEFIT_CONFIG = "/api/v1/gateway/config";
1259
1264
  const EP_AGENT_LIST = "/v1/agent-center/agents/useragents";
1260
1265
  const EP_AGENT_DETAIL = "/v1/agent-center/agents/detail";
1261
1266
  /**
1267
+ * 模型的思考强度清单(26.10.0 新增)。**必须带 `Agent-Type: PromptCenter`**:
1268
+ * 不带头时上游回 `TM.00001001 请求头Agent-Type为空`(实测)。
1269
+ */
1270
+ const EP_THINKING_VARIANTS = "/v1/model/thinking-variants";
1271
+ /** 排队位置(26.10.0 新增):按 chat_id 查这条请求在上游队里的排位。 */
1272
+ const EP_QUEUE_STATUS = "/api/v1/queue/status";
1273
+ /**
1262
1274
  * 当前登录身份。**必须带 `/snap-manager` 前缀**:2026-09-30 实测
1263
1275
  * `/v1/current/user` 是 APIG.0101(未发布),`/snap-manager/v1/current/user`
1264
1276
  * 才返回 `{user_id, user_name, domain_id}`。codearts2api 的 const.go 里少了这段前缀
@@ -1425,7 +1437,9 @@ function sessionIdFor(chatId) {
1425
1437
  * (两者任一超限,上游都只会流内回 InferHub.001001005.400
1426
1438
  * "The request param is invalid, Please check it",不告诉我们是哪一个)
1427
1439
  * 3. 丢弃非白名单字段(stream_options / store / logprobs 等虽实测可容忍,
1428
- * 但按 codearts2api 观察到的真实流量收窄,避免网关后续收紧)
1440
+ * 但按 codearts2api 观察到的真实流量收窄,避免网关后续收紧);
1441
+ * 例外是 `reasoning_effort`(DSH 的思考档位):官方内核同样以这个顶层字段
1442
+ * 透传 variant,见 kernel 的 `inferhub getArgs`。
1429
1443
  * 4. tools[].function 去掉 strict(OpenAI 专有)
1430
1444
  * 5. 注入 CodeArts 特有字段:chat_id(按对话派生,见 conversationChatId)、
1431
1445
  * prompt_cache_key、tool_stream
@@ -1459,8 +1473,13 @@ function prepareChatBody(rawJson, contextWindow = 0) {
1459
1473
  "tools",
1460
1474
  "tool_choice",
1461
1475
  "user",
1462
- "stream_options"
1476
+ "stream_options",
1477
+ "reasoning_effort"
1463
1478
  ]) if (parsed[key] !== void 0) body[key] = parsed[key];
1479
+ if (Array.isArray(body.messages)) body.messages = body.messages.map((message) => message?.role === "developer" ? {
1480
+ ...message,
1481
+ role: "system"
1482
+ } : message);
1464
1483
  if (body.stream_options === void 0 || typeof body.stream_options !== "object") body.stream_options = { include_usage: true };
1465
1484
  else body.stream_options.include_usage = true;
1466
1485
  if (body.max_tokens === void 0 && typeof parsed.max_completion_tokens === "number") body.max_tokens = parsed.max_completion_tokens;
@@ -1994,6 +2013,128 @@ async function fetchAgentModels(credential, signal) {
1994
2013
  return out;
1995
2014
  }
1996
2015
  /**
2016
+ * 思考强度清单:`POST /v1/model/thinking-variants`,body `{ model_names }`。
2017
+ *
2018
+ * 实测响应 `{ variants: { "GLM-5.2": { strengths: ["high","max"],
2019
+ * default_strength: "high" }, ... } }`,键是请求里的名字(大小写原样),
2020
+ * 不支持的模型给空 `strengths`。官方客户端限 50 个名字,这里照搬。
2021
+ *
2022
+ * 这是**可选增强**:上游没给(旧账号/接口漂移)时模型照常可用,只是宿主不显示
2023
+ * 推理档位选择器。失败由调用方记进 errors,不抛断发现。
2024
+ */
2025
+ async function fetchThinkingVariants(credential, modelIds, signal) {
2026
+ const out = /* @__PURE__ */ new Map();
2027
+ const modelNames = modelIds.slice(0, 50);
2028
+ if (modelNames.length === 0) return out;
2029
+ const bodyBuf = Buffer.from(JSON.stringify({ model_names: modelNames }));
2030
+ const req = new Request(`${SNAP_HOST}${EP_THINKING_VARIANTS}`, {
2031
+ method: "POST",
2032
+ headers: {
2033
+ "Content-Type": "application/json",
2034
+ "x-auth-token": credential.token,
2035
+ "X-Security-Token": credential.securityToken,
2036
+ "x-snap-traceid": randomTraceId(),
2037
+ "X-Language": "zh-cn",
2038
+ "Agent-Type": "PromptCenter"
2039
+ },
2040
+ body: bodyBuf
2041
+ });
2042
+ if (credential.accessKeyId && credential.secretAccessKey) signRequest(req, bodyBuf, {
2043
+ accessKeyId: credential.accessKeyId,
2044
+ secretAccessKey: credential.secretAccessKey,
2045
+ securityToken: credential.securityToken
2046
+ });
2047
+ const resp = await fetch(req, { signal });
2048
+ if (!resp.ok) {
2049
+ const text = await resp.text().catch(() => "");
2050
+ throw new Error(`thinking-variants HTTP ${resp.status} ${text.slice(0, 200)}`);
2051
+ }
2052
+ const body = asRecord(parseJson(await resp.text()));
2053
+ for (const [name, raw] of Object.entries(asRecord(body.variants))) {
2054
+ const variant = asRecord(raw);
2055
+ const strengths = asArray(variant.strengths).map((value) => firstString(value)).filter((value) => value !== "");
2056
+ if (strengths.length === 0) continue;
2057
+ const fallback = firstString(variant.default_strength);
2058
+ out.set(name.trim().toLowerCase(), {
2059
+ strengths,
2060
+ ...fallback === "" ? {} : { default: fallback }
2061
+ });
2062
+ }
2063
+ return out;
2064
+ }
2065
+ /**
2066
+ * 查一条请求的排队位置:`GET /api/v1/queue/status?task_id=<chat_id>&model=<model>`。
2067
+ *
2068
+ * ⚠ 这个 GET 是**占号**,不是只读查询:每次调用都会把 task 挂进队列、返回队尾位置,
2069
+ * 用 `clearQueueStatus` 才清得掉(2026-10-21 实测)。队列空闲的模型(如 GLM)直接
2070
+ * 回 `status:"working"`、位置 0;拥堵的模型(如 deepseek-v4.1-flash)对新号一律
2071
+ * 回 `waiting` + 当前队尾。真正的 chat 请求不看这张票,所以要不要等由调用方决定
2072
+ * (见 `src/queue.ts`)。
2073
+ */
2074
+ async function fetchQueueStatus(credential, chatId, model, signal) {
2075
+ const url = new URL(`${SNAP_HOST}${EP_QUEUE_STATUS}`);
2076
+ url.searchParams.set("task_id", chatId);
2077
+ url.searchParams.set("model", model);
2078
+ const req = new Request(url, {
2079
+ method: "GET",
2080
+ headers: {
2081
+ "Content-Type": "application/json",
2082
+ "x-auth-token": credential.token,
2083
+ "X-Security-Token": credential.securityToken,
2084
+ "x-snap-traceid": randomTraceId(),
2085
+ "X-Language": "zh-cn"
2086
+ }
2087
+ });
2088
+ if (credential.accessKeyId && credential.secretAccessKey) signRequest(req, Buffer.alloc(0), {
2089
+ accessKeyId: credential.accessKeyId,
2090
+ secretAccessKey: credential.secretAccessKey,
2091
+ securityToken: credential.securityToken
2092
+ });
2093
+ const resp = await fetch(req, { signal });
2094
+ if (!resp.ok) {
2095
+ const text = await resp.text().catch(() => "");
2096
+ throw new Error(`queue status HTTP ${resp.status} ${text.slice(0, 200)}`);
2097
+ }
2098
+ const body = asRecord(parseJson(await resp.text()));
2099
+ return {
2100
+ status: firstString(body.status),
2101
+ queueLength: positive(body.queue_length),
2102
+ queuePosition: positive(body.queue_position),
2103
+ message: firstString(body.message)
2104
+ };
2105
+ }
2106
+ /**
2107
+ * 清掉 `fetchQueueStatus` 挂上去的占号:`DELETE /api/v1/queue/status?task_id=&model=`。
2108
+ *
2109
+ * 官方客户端在回答结束、用户取消、排队出错时都会调;不清的话每次查询都会在上游
2110
+ * 队列里留一个幽灵任务,队列只会越来越长。
2111
+ */
2112
+ async function clearQueueStatus(credential, chatId, model, signal) {
2113
+ const url = new URL(`${SNAP_HOST}${EP_QUEUE_STATUS}`);
2114
+ url.searchParams.set("task_id", chatId);
2115
+ url.searchParams.set("model", model);
2116
+ const req = new Request(url, {
2117
+ method: "DELETE",
2118
+ headers: {
2119
+ "Content-Type": "application/json",
2120
+ "x-auth-token": credential.token,
2121
+ "X-Security-Token": credential.securityToken,
2122
+ "x-snap-traceid": randomTraceId(),
2123
+ "X-Language": "zh-cn"
2124
+ }
2125
+ });
2126
+ if (credential.accessKeyId && credential.secretAccessKey) signRequest(req, Buffer.alloc(0), {
2127
+ accessKeyId: credential.accessKeyId,
2128
+ secretAccessKey: credential.secretAccessKey,
2129
+ securityToken: credential.securityToken
2130
+ });
2131
+ const resp = await fetch(req, { signal });
2132
+ if (!resp.ok) {
2133
+ const text = await resp.text().catch(() => "");
2134
+ throw new Error(`queue clear HTTP ${resp.status} ${text.slice(0, 200)}`);
2135
+ }
2136
+ }
2137
+ /**
1997
2138
  * 动态发现当前账号可用的模型清单(三路合并)。
1998
2139
  *
1999
2140
  * 与 codearts2api 的 `Client.FetchModels` 等价:内置、限时福利、agent-center 三路
@@ -2046,12 +2187,25 @@ async function fetchModels(credential, signal) {
2046
2187
  if (sources.length === 0) throw new Error(`model discovery failed on every source: ${errors.map((e) => `${e.source}: ${e.message}`).join("; ")}`);
2047
2188
  const merged = backfillParams(mergeModelLists(discovered));
2048
2189
  if (merged.length === 0) throw new Error("model discovery returned an empty catalog");
2190
+ let variants = /* @__PURE__ */ new Map();
2191
+ try {
2192
+ variants = await fetchThinkingVariants(credential, merged.map((model) => model.id), signal);
2193
+ } catch (error) {
2194
+ errors.push({
2195
+ source: "thinking",
2196
+ message: error instanceof Error ? error.message : String(error)
2197
+ });
2198
+ }
2049
2199
  return {
2050
- models: merged.map((model) => ({
2051
- ...model,
2052
- contextWindow: model.contextWindow > 0 ? model.contextWindow : UNKNOWN_CONTEXT_WINDOW,
2053
- maxTokens: Math.min(model.maxTokens > 0 ? model.maxTokens : UNKNOWN_MAX_TOKENS, maxTokensCeilingFor(model.id))
2054
- })),
2200
+ models: merged.map((model) => {
2201
+ const thinking = variants.get(model.id.toLowerCase());
2202
+ return {
2203
+ ...model,
2204
+ ...thinking === void 0 ? {} : { thinking },
2205
+ contextWindow: model.contextWindow > 0 ? model.contextWindow : UNKNOWN_CONTEXT_WINDOW,
2206
+ maxTokens: Math.min(model.maxTokens > 0 ? model.maxTokens : UNKNOWN_MAX_TOKENS, maxTokensCeilingFor(model.id))
2207
+ };
2208
+ }),
2055
2209
  sources,
2056
2210
  errors
2057
2211
  };
@@ -2586,4 +2740,4 @@ function sumOf(store) {
2586
2740
  return out;
2587
2741
  }
2588
2742
  //#endregion
2589
- export { isBenefitModel as A, upstreamErrorMessage as C, creditRatioLabel as D, clearBenefitBlocked as E, startLogin as F, CodeArtsCredentialStore as M, codeartsAccountsPath as N, filterEnabledModels as O, codeartsAuthPath as P, signedGet as S, FALLBACK_CODEARTS_MODELS as T, isWelfareClaimedStatus as _, SNAP_HOST as a, prepareChatBody as b, claimDailyWelfare as c, fetchModels as d, fetchWelfareCampaigns as f, isTpmLimit as g, isQueueableRate as h, refreshCatalog as i, CodeArtsAccountStore as j, isBenefitBlocked as k, degradeTokenCeiling as l, isParamInvalidFrame as m, codeartsUsagePath as n, canonicalModel as o, isConcurrencyCap as p, discoverModels as r, chatStream as s, CodeArtsUsageTracker as t, fetchCallerIdentity as u, maxTokensCeilingFor as v, CodeArtsCatalog as w, rateLimitFrame as x, pickDailyWelfareCampaign as y };
2743
+ export { filterEnabledModels as A, rateLimitFrame as C, FALLBACK_CODEARTS_MODELS as D, CodeArtsCatalog as E, codeartsAccountsPath as F, codeartsAuthPath as I, startLogin as L, isBenefitModel as M, CodeArtsAccountStore as N, clearBenefitBlocked as O, CodeArtsCredentialStore as P, prepareChatBody as S, upstreamErrorMessage as T, isQueueableRate as _, SNAP_HOST as a, maxTokensCeilingFor as b, claimDailyWelfare as c, fetchCallerIdentity as d, fetchModels as f, isParamInvalidFrame as g, isConcurrencyCap as h, refreshCatalog as i, isBenefitBlocked as j, creditRatioLabel as k, clearQueueStatus as l, fetchWelfareCampaigns as m, codeartsUsagePath as n, canonicalModel as o, fetchQueueStatus as p, discoverModels as r, chatStream as s, CodeArtsUsageTracker as t, degradeTokenCeiling as u, isTpmLimit as v, signedGet as w, pickDailyWelfareCampaign as x, isWelfareClaimedStatus as y };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-codearts",
3
3
  "displayName": "DSH CodeArts Connect",
4
4
  "description": "把华为云 CodeArts 接入 DeepSeek Harness —— 内置 OAuth2 登录(PKCE + DPoP),登录后即可在 DSH 对话窗口使用 CodeArts 的模型。",
5
- "version": "1.0.1",
5
+ "version": "1.1.1",
6
6
  "packageManager": "pnpm@11.24.0",
7
7
  "author": "elegant01",
8
8
  "keywords": [