@lynn123411/dsh-llm-agentrouter 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @lynn123411/dsh-llm-agentrouter
2
2
 
3
- AgentRouter 中转聚合插件:把同一家中转的多个模型收拢为一条 pi-ai 路由(模型选择器只出现一个 AgentRouter 分组),国内 / 国际端点在「设置 - API中转 - AgentRouter 中转站」一键切换、下一请求即生效,外加出站请求的 User-Agent 改写与 402 配额耗尽提示围栏。分叉自 `aqiu817/dsh-llm-agentrouter`,适配 DSH `0.1.2-alpha.5` 起的新 settings/slots API,已在 `0.1.5-rc.1` 逐项实证兼容。
3
+ AgentRouter 中转聚合插件:把同一家中转的多个模型收拢为一条 pi-ai 路由(模型选择器只出现一个 AgentRouter 分组),国内 / 国际端点在「设置 - API中转 - AgentRouter 中转站」一键切换、下一请求即生效,外加出站请求的 User-Agent 改写与 402 配额耗尽提示围栏。模型列表在卡片内直接编辑,写入内核「设置 - 模型」页所编辑的同一份 `llm-pi-ai` 路由数据。
4
4
 
5
5
  ## 特性
6
6
 
@@ -9,7 +9,7 @@ AgentRouter 中转聚合插件:把同一家中转的多个模型收拢为一
9
9
  - **端点一键切换**:设置卡片两个大选项(国内端点 / 国际端点,附带真实 host 展示),点击即写即生效,无需重启;API Key 只存 `$DSH_HOME/.credentials.yaml` 的 `AGENTROUTER_API_KEY` 引用。
10
10
  - **共享「API中转」设置页**:本插件的端点卡片与 `dsh-a6api` 的面板挂在同一个设置页里(`settings.section` id `relay` 的页内两个 tab,参与者可增删)。内核不允许一页被多个插件共同声明,因此每个参与者各持一份运行时逻辑一致的页壳,**先加载者当选页面宿主**、其余只注册卡片;当选者被卸载后下次启动自动改选。做法与约束见仓库 `docs/rules/shared-settings-page.md`。
11
11
  - **请求围栏**:重写 relay 要求的 `User-Agent`(适配器强制署名下沉到 `fetch` 层替换),哨兵 host(`.internal` 不可解析)兜底防裸奔,402 配额耗尽的 JSON 错误体改写为可读提示。
12
- - **alpha.5 兼容**:宿主沿用 `ctx.settings.installSection` + `ctx.inject(['settings'])`,客户端卡片改挂共享设置页的子 slot;沿用 `llm-agentrouter` 设置命名空间,老配置无缝继承。
12
+ - **内核设置接入**:宿主用 `ctx.settings.installSection` 注册设置段(`ctx.inject(['settings'])`),客户端卡片挂在共享设置页的子 slot;设置命名空间为 `llm-agentrouter`。
13
13
 
14
14
  ## 安装
15
15
 
package/cordis.patch.yml CHANGED
@@ -60,11 +60,11 @@
60
60
  #
61
61
  # WHOLE-OBJECT OVERWRITE, NOT A DEEP MERGE — RE-CHECK ON EVERY dsh-base BUMP
62
62
  # The loader applies a patch entry as `target[key] = value`
63
- # (applyEntryPatches, 0.1.5-rc.1), so the `config:` below REPLACES the
63
+ # (applyEntryPatches, 0.1.6-alpha.2), so the `config:` below REPLACES the
64
64
  # llm-pi-ai row's whole config object: nothing a default config carried survives,
65
65
  # at any depth. That is safe today only because dsh-base mounts
66
66
  # `- id: llm-pi-ai` with a `name:` and no `config:` at all
67
- # (dsh-base/cordis.patch.yml:107), so there is nothing to lose. If a future
67
+ # (dsh-base/cordis.patch.yml:114-115), so there is nothing to lose. If a future
68
68
  # dsh-base gives that row a default config, this patch swallows it SILENTLY —
69
69
  # diff the shipped llm-pi-ai row against this file on every dsh-base bump and
70
70
  # fold whatever it adds into the `providers:` map below.
package/lib/client.js CHANGED
@@ -6,6 +6,7 @@ window.__ModuleLoader__.load({
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  let jsx = require("react/jsx-runtime");
8
8
  let react = require("react");
9
+ const { Button, Checkbox, Input } = require("@deepseek-ai/dsh-client-ui-primitives");
9
10
 
10
11
  //#region styles
11
12
  /*
@@ -16,44 +17,40 @@ window.__ModuleLoader__.load({
16
17
  */
17
18
  const CSS = [
18
19
  ".dshAr_card{display:flex;flex-direction:column;gap:10px}",
19
- ".dshAr_lead{margin:0;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:20px}",
20
+ ".dshAr_lead{margin:0;color:var(--dsw-alias-label-tertiary);font:var(--dsw-font-xs-13)}",
20
21
  ".dshAr_choices{display:flex;gap:10px;margin:0;padding:0;border:0;flex-wrap:wrap}",
21
- ".dshAr_choice{flex:1 1 200px;min-width:0;display:flex;gap:9px;align-items:flex-start;padding:11px 13px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-3);cursor:pointer}",
22
+ ".dshAr_choice{flex:1 1 200px;min-width:0;display:flex;gap:9px;align-items:flex-start;padding:11px 13px;border:0.5px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-3);cursor:pointer;transition:background-color var(--ds-transition-duration) var(--ds-ease-in-out),border-color var(--ds-transition-duration) var(--ds-ease-in-out)}",
22
23
  ".dshAr_choice:hover{background:var(--dsw-alias-interactive-bg-hover)}",
23
24
  ".dshAr_choice[data-selected=true]{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 1px var(--dsw-alias-state-business-primary) inset}",
24
25
  ".dshAr_choice[data-disabled=true]{cursor:default;opacity:.55}",
25
- ".dshAr_choice input{margin:3px 0 0}",
26
+ ".dshAr_choice input{margin:3px 0 0;accent-color:var(--dsw-alias-brand-primary)}",
27
+ ".dshAr_choice input:focus-visible{outline:none;box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary)}",
26
28
  ".dshAr_choiceText{display:flex;flex-direction:column;gap:2px;min-width:0}",
27
- ".dshAr_choiceTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:600;line-height:20px}",
28
- ".dshAr_choiceHost{overflow-wrap:anywhere;color:var(--dsw-alias-label-tertiary);font-family:var(--ds-font-family-code);font-size:12px;line-height:17px}",
29
- ".dshAr_choiceHint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:17px}",
30
- ".dshAr_status{margin:0;min-height:18px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}",
29
+ ".dshAr_choiceTitle{color:var(--dsw-alias-label-primary);font:var(--dsw-font-s-strong-14)}",
30
+ ".dshAr_choiceHost{overflow-wrap:anywhere;color:var(--dsw-alias-label-tertiary);font:var(--dsw-font-xxs-12);font-family:var(--ds-font-family-code)}",
31
+ ".dshAr_choiceHint{color:var(--dsw-alias-label-tertiary);font:var(--dsw-font-xxs-12)}",
32
+ ".dshAr_status{margin:0;min-height:18px;color:var(--dsw-alias-label-tertiary);font:var(--dsw-font-xxs-12)}",
31
33
  ".dshAr_status[data-kind=error]{color:var(--dsw-alias-state-error-primary)}",
32
- ".dshAr_models{display:flex;flex-direction:column;gap:8px;margin-top:4px;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l2)}",
34
+ ".dshAr_models{display:flex;flex-direction:column;gap:8px;margin-top:4px;padding-top:12px;border-top:0.5px solid var(--dsw-alias-border-l2)}",
33
35
  ".dshAr_modelsHead{display:flex;align-items:center;gap:10px;flex-wrap:wrap}",
34
- ".dshAr_modelsHead h4{margin:0;color:var(--dsw-alias-label-primary);font-size:14px;font-weight:600;line-height:20px}",
36
+ ".dshAr_modelsHead h4{margin:0;color:var(--dsw-alias-label-primary);font:var(--dsw-font-s-strong-14)}",
35
37
  ".dshAr_actions{display:flex;gap:8px;flex-wrap:wrap}",
36
38
  ".dshAr_modelsHead .dshAr_actions{margin-left:auto}",
37
- ".dshAr_button{appearance:none;padding:5px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;line-height:18px;cursor:pointer}",
38
- ".dshAr_button:hover:enabled{background:var(--dsw-alias-interactive-bg-hover)}",
39
- ".dshAr_button:disabled{cursor:default;opacity:.5}",
40
39
  ".dshAr_modelsList{display:flex;flex-direction:column;gap:6px;margin:0;padding:0;list-style:none}",
41
- ".dshAr_model{margin:0;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-3)}",
42
- ".dshAr_modelHead{display:flex;gap:8px;align-items:baseline;cursor:pointer;color:var(--dsw-alias-label-primary);font-size:13px;line-height:19px}",
40
+ ".dshAr_model{margin:0;padding:8px 10px;border:0.5px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-3)}",
41
+ ".dshAr_modelHead{display:flex;gap:8px;align-items:baseline;cursor:pointer;color:var(--dsw-alias-label-primary);font:var(--dsw-font-xs-13)}",
43
42
  ".dshAr_modelName{font-weight:600}",
44
- ".dshAr_modelId{overflow-wrap:anywhere;color:var(--dsw-alias-label-tertiary);font-family:var(--ds-font-family-code);font-size:11px;line-height:17px}",
43
+ ".dshAr_modelId{overflow-wrap:anywhere;color:var(--dsw-alias-label-tertiary);font:var(--dsw-font-xxxs-11);font-family:var(--ds-font-family-code)}",
45
44
  ".dshAr_fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:8px;margin-top:10px}",
46
45
  ".dshAr_field{display:flex;flex-direction:column;gap:3px;min-width:0}",
47
- ".dshAr_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:11px;line-height:15px}",
48
- ".dshAr_input{min-width:0;padding:4px 7px;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;line-height:18px}",
49
- ".dshAr_input:disabled{opacity:.55}",
46
+ ".dshAr_fieldLabel{color:var(--dsw-alias-label-secondary);font:var(--dsw-font-xxxs-11)}",
50
47
  ".dshAr_inputs{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:10px}",
51
- ".dshAr_toggle{display:flex;gap:5px;align-items:center;color:var(--dsw-alias-label-primary);font-size:12px;line-height:18px}",
52
48
  ".dshAr_efforts{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:6px;margin-top:10px}",
53
- ".dshAr_effort{display:flex;gap:6px;align-items:center;color:var(--dsw-alias-label-primary);font-size:12px;line-height:18px}",
54
- ".dshAr_effortLevel{min-width:38px;color:var(--dsw-alias-label-secondary)}",
55
- ".dshAr_effortWire{flex:1 1 60px}",
49
+ ".dshAr_effort{display:flex;gap:6px;align-items:center;min-width:0}",
50
+ ".dshAr_effortLevel{min-width:76px}",
51
+ ".dshAr_effortWire{flex:1 1 60px;min-width:0}",
56
52
  ".dshAr_modelActions{display:flex;justify-content:flex-end;margin-top:10px}",
53
+ "@media (prefers-reduced-motion: reduce){.dshAr_choice{transition:none}}",
57
54
  ].join("");
58
55
  const CSS_TAG_ID = "dsh-llm-agentrouter/EndpointCard.css";
59
56
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(CSS_TAG_ID) + "]") === null) {
@@ -108,6 +105,7 @@ window.__ModuleLoader__.load({
108
105
  modalityText: "文本",
109
106
  modalityImage: "图片",
110
107
  wireNone: "留空 = 不发送",
108
+ wireLabel: "「%s」档位发送给中转站的取值",
111
109
  levelOff: "off",
112
110
  levelMinimal: "minimal",
113
111
  levelLow: "low",
@@ -164,6 +162,7 @@ window.__ModuleLoader__.load({
164
162
  modalityText: "Text",
165
163
  modalityImage: "Image",
166
164
  wireNone: "empty = send nothing",
165
+ wireLabel: "Wire value sent for the %s level",
167
166
  levelOff: "off",
168
167
  levelMinimal: "minimal",
169
168
  levelLow: "low",
@@ -560,8 +559,7 @@ window.__ModuleLoader__.load({
560
559
  className: "dshAr_field",
561
560
  children: [
562
561
  jsx.jsx("span", { className: "dshAr_fieldLabel", children: t(label) }),
563
- jsx.jsx("input", {
564
- className: "dshAr_input",
562
+ jsx.jsx(Input, {
565
563
  type: "text",
566
564
  value: row[name],
567
565
  disabled: readOnly,
@@ -605,25 +603,21 @@ window.__ModuleLoader__.load({
605
603
  className: "dshAr_inputs",
606
604
  children: [
607
605
  jsx.jsx("span", { className: "dshAr_fieldLabel", children: t("fieldInput") }),
608
- ...MODALITIES.map((modality) => jsx.jsxs(
609
- "label",
606
+ ...MODALITIES.map((modality) => jsx.jsx(
607
+ "span",
610
608
  {
611
- className: "dshAr_toggle",
612
- children: [
613
- jsx.jsx("input", {
614
- type: "checkbox",
615
- checked: row.input.includes(modality),
616
- disabled: readOnly,
617
- "data-model": String(index),
618
- "data-modality": modality,
619
- onChange: () => onPatch(index, {
620
- input: row.input.includes(modality)
621
- ? row.input.filter((entry) => entry !== modality)
622
- : [...row.input, modality],
623
- }),
609
+ "data-modality": modality,
610
+ "data-model": String(index),
611
+ children: jsx.jsx(Checkbox, {
612
+ checked: row.input.includes(modality),
613
+ disabled: readOnly,
614
+ label: t(MODALITY_LABELS[modality]),
615
+ onChange: (next) => onPatch(index, {
616
+ input: next
617
+ ? [...row.input, modality]
618
+ : row.input.filter((entry) => entry !== modality),
624
619
  }),
625
- jsx.jsx("span", { children: t(MODALITY_LABELS[modality]) }),
626
- ],
620
+ }),
627
621
  },
628
622
  modality,
629
623
  )),
@@ -635,30 +629,28 @@ window.__ModuleLoader__.load({
635
629
  children: EFFORT_LEVELS.map((level) => {
636
630
  const entry = row.efforts.find((candidate) => candidate.level === level)
637
631
  ?? { level, on: false, wire: "" };
632
+ const levelKey = "level" + level.charAt(0).toUpperCase() + level.slice(1);
638
633
  return jsx.jsxs(
639
- "label",
634
+ "div",
640
635
  {
641
636
  className: "dshAr_effort",
642
637
  "data-level": level,
638
+ "data-model": String(index),
643
639
  children: [
644
- jsx.jsx("input", {
645
- type: "checkbox",
640
+ jsx.jsx(Checkbox, {
646
641
  checked: entry.on,
647
642
  disabled: readOnly,
648
- "data-model": String(index),
649
- "data-effort": level,
650
- onChange: () => onEffort(index, level, { on: !entry.on }),
651
- }),
652
- jsx.jsx("span", {
653
643
  className: "dshAr_effortLevel",
654
- children: t("level" + level.charAt(0).toUpperCase() + level.slice(1)),
644
+ label: t(levelKey),
645
+ onChange: (next) => onEffort(index, level, { on: next }),
655
646
  }),
656
- jsx.jsx("input", {
657
- className: "dshAr_input dshAr_effortWire",
647
+ jsx.jsx(Input, {
648
+ className: "dshAr_effortWire",
658
649
  type: "text",
659
650
  value: entry.wire,
660
651
  placeholder: level === "off" ? t("wireNone") : level,
661
652
  disabled: readOnly || !entry.on,
653
+ "aria-label": fill(t("wireLabel"), [t(levelKey)]),
662
654
  "data-model": String(index),
663
655
  "data-wire": level,
664
656
  onChange: (event) => onEffort(index, level, { wire: event.target.value }),
@@ -671,9 +663,9 @@ window.__ModuleLoader__.load({
671
663
  }),
672
664
  jsx.jsx("div", {
673
665
  className: "dshAr_modelActions",
674
- children: jsx.jsx("button", {
675
- type: "button",
676
- className: "dshAr_button",
666
+ children: jsx.jsx(Button, {
667
+ variant: "ghost",
668
+ size: "sm",
677
669
  "data-action": "remove",
678
670
  disabled: readOnly,
679
671
  onClick: () => onRemove(index),
@@ -869,9 +861,9 @@ window.__ModuleLoader__.load({
869
861
  const shown = status === null
870
862
  ? { kind: "info", text: "" }
871
863
  : { kind: status.kind, text: fill(t(status.key), status.values) };
872
- const action = (name, label, disabled, onClick) => jsx.jsx("button", {
873
- type: "button",
874
- className: "dshAr_button",
864
+ const action = (name, label, disabled, onClick) => jsx.jsx(Button, {
865
+ variant: name === "save" ? "primary" : "outline",
866
+ size: "sm",
875
867
  "data-action": name,
876
868
  disabled,
877
869
  onClick,
@@ -985,13 +977,11 @@ window.__ModuleLoader__.load({
985
977
  position: "relative",
986
978
  padding: "7px 1px 11px",
987
979
  cursor: "pointer",
988
- font: "inherit",
989
- fontSize: "13px",
990
- lineHeight: "20px",
991
- color: "var(--dsw-alias-label-tertiary, inherit)",
980
+ font: "var(--dsw-font-xs-13)",
981
+ color: "var(--dsw-alias-label-tertiary)",
992
982
  };
993
983
  const TAB_ACTIVE_STYLE = Object.assign({}, TAB_STYLE, {
994
- color: "var(--dsw-alias-label-primary, inherit)",
984
+ color: "var(--dsw-alias-label-primary)",
995
985
  });
996
986
  /** The kernel's own tab marker: a 2px rounded bar under the active label. */
997
987
  const TAB_MARKER_STYLE = {
@@ -1001,7 +991,7 @@ window.__ModuleLoader__.load({
1001
991
  bottom: 0,
1002
992
  height: "2px",
1003
993
  borderRadius: "2px 2px 0 0",
1004
- background: "var(--dsw-alias-label-primary, currentColor)",
994
+ background: "var(--dsw-alias-label-primary)",
1005
995
  };
1006
996
  /**
1007
997
  * Panels stay mounted (hidden) so each card keeps its local state. `display:
package/lib/index.js CHANGED
@@ -293,7 +293,7 @@ function apply(ctx, config) {
293
293
  if (config.announce) {
294
294
  const table = routingTable(config)
295
295
  ctx.logger.info(
296
- 'llm-agentrouter: endpoint %c (%c), sending %c',
296
+ 'llm-agentrouter: endpoint %s (%s), sending %s',
297
297
  config.endpoint,
298
298
  table.get(config.sentinel.trim().toLowerCase()) ?? 'unrouted',
299
299
  config.userAgent,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lynn123411/dsh-llm-agentrouter",
3
- "version": "0.3.0",
4
- "description": "AgentRouter 中转聚合:一条 pi-ai 路由承载多模型(设置卡片内可从中转站拉取、逐个编辑模型与参数)+ 国内/国际端点一键切换 + 出站请求 User-Agent/402-配额改写围栏。aqiu817/dsh-llm-agentrouter 分叉,适配 0.1.2-alpha.5 起新 settings/slots API,已在 0.1.5-rc.1 逐项实证兼容。",
3
+ "version": "0.3.1",
4
+ "description": "AgentRouter 中转聚合:一条 pi-ai 路由承载多模型(设置卡片内可从中转站拉取、逐个编辑模型与参数)+ 国内/国际端点一键切换 + 出站请求 User-Agent/402-配额改写围栏。模型列表写入内核「设置 - 模型」页所编辑的同一份 llm-pi-ai 路由数据。",
5
5
  "keywords": [
6
6
  "deepseek-harness",
7
7
  "dsh-plugin",
@@ -33,6 +33,7 @@
33
33
  "client": {
34
34
  "inject": [
35
35
  "@deepseek-ai/dsh-client-locale",
36
+ "@deepseek-ai/dsh-client-ui-primitives",
36
37
  "@deepseek-ai/dsh-client-ui-settings"
37
38
  ],
38
39
  "platform": "web"
@@ -41,7 +42,6 @@
41
42
  "files": [
42
43
  "lib/",
43
44
  "cordis.patch.yml",
44
- "LICENSE",
45
45
  "README.md"
46
46
  ],
47
47
  "publishConfig": {
@@ -51,7 +51,8 @@
51
51
  "test": "node --test test/*.test.mjs"
52
52
  },
53
53
  "engines": {
54
- "node": ">=22"
54
+ "node": ">=22",
55
+ "dsh": "0.1.6-alpha.2"
55
56
  },
56
57
  "dependencies": {
57
58
  "@deepseek-ai/schemastery": "^3.18.2"
package/LICENSE DELETED
@@ -1,22 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 aqiu817
4
- Copyright (c) 2026 lynn123411 (alpha.5 compatibility fork: settings/slots API migration, package rename)
5
-
6
- Permission is hereby granted, free of charge, to any person obtaining a copy
7
- of this software and associated documentation files (the "Software"), to deal
8
- in the Software without restriction, including without limitation the rights
9
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
- copies of the Software, and to permit persons to whom the Software is
11
- furnished to do so, subject to the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be included in all
14
- copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
- SOFTWARE.