@mangoc/dsh-sub2api-sync 0.0.0-stage → 0.1.0
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 +155 -2
- package/README.zh.md +124 -0
- package/cordis.patch.yml +10 -0
- package/data/models-dev.snapshot.json +1 -0
- package/lib/client.cjs +2416 -0
- package/lib/client.d.ts +49 -0
- package/lib/client.js +2425 -0
- package/lib/config.d.ts +89 -0
- package/lib/engine.d.ts +129 -0
- package/lib/enrich.d.ts +104 -0
- package/lib/icon.d.ts +50 -0
- package/lib/index.d.ts +31 -0
- package/lib/index.js +2836 -0
- package/lib/oauth.d.ts +128 -0
- package/lib/panel.d.ts +203 -0
- package/lib/protocols.d.ts +119 -0
- package/lib/routes.d.ts +263 -0
- package/lib/types.d.ts +130 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +124 -3
package/README.md
CHANGED
|
@@ -1,3 +1,156 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @mangoc/dsh-sub2api-sync
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Sync one [sub2api](https://github.com/Wei-Shaw/sub2api) instance's groups, keys, and models into [`dsh-llm-pi-ai`](https://github.com/deepseek-ai/deepseek-harness) provider routes: log in once, integrate automatically, keep syncing.
|
|
4
|
+
|
|
5
|
+
Design Agent Note: see [the design notes](../../../docs/design/2026-09-04-sub2api-provider-sync.md) and [Chinese version](../../../docs/design/2026-09-04-sub2api-provider-sync.zh.md).
|
|
6
|
+
|
|
7
|
+
## Status: Phase 1 (key-first, Mode B)
|
|
8
|
+
|
|
9
|
+
Phase 1 ships the key-first path: declare groups by hand in the settings card, paste each group's relay key, and sync. The panel-driven path (Mode A: email/password login, 2FA, automatic key creation, group discovery, models.dev metadata enrichment, scheduled resync, the `sub2api_sync` tool) lands in Phase 2.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
### User install (release channels)
|
|
14
|
+
|
|
15
|
+
The harness loads this package through its `main`/`exports` entry, which is the
|
|
16
|
+
**built** `lib/index.js`. Tarballs from any channel below ship `lib/` already —
|
|
17
|
+
no local build needed:
|
|
18
|
+
|
|
19
|
+
**npm** — published under two names simultaneously: same code, same version.
|
|
20
|
+
The scoped name is canonical; the unscoped short name is an alias. Either
|
|
21
|
+
installs as a first-class plugin:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
dsh plugin --profile web add @mangoc/dsh-sub2api-sync # canonical (scoped)
|
|
25
|
+
dsh plugin --profile web add dsh-sub2api-sync # alias (unscoped short name)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**GitHub Releases tarball** (no npm involved; the URLs always point to the
|
|
29
|
+
latest release; both variants are equivalent):
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
dsh plugin --profile web add https://github.com/Shuai-S/dsh-sub2api-sync/releases/latest/download/mangoc-dsh-sub2api-sync.tgz
|
|
33
|
+
# or the short-name variant:
|
|
34
|
+
dsh plugin --profile web add https://github.com/Shuai-S/dsh-sub2api-sync/releases/latest/download/dsh-sub2api-sync.tgz
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
You can also copy a version-pinned tarball URL (e.g.
|
|
38
|
+
`mangoc-dsh-sub2api-sync-0.1.0.tgz` / `dsh-sub2api-sync-0.1.0.tgz`) from the
|
|
39
|
+
[Releases page](https://github.com/Shuai-S/dsh-sub2api-sync/releases).
|
|
40
|
+
|
|
41
|
+
**From source** (pnpm runs the `prepare` build on git dependencies; needs a
|
|
42
|
+
local toolchain and is slower on first install):
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
dsh plugin --profile web add github:Shuai-S/dsh-sub2api-sync#path:packages/llm/sub2api-sync
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
After installing, restart the app or toggle the plugin off/on so the entry is
|
|
49
|
+
imported again; a failed entry is not retried on its own.
|
|
50
|
+
|
|
51
|
+
### Release (maintainers)
|
|
52
|
+
|
|
53
|
+
Every release publishes **both names in lockstep**
|
|
54
|
+
([scripts/pack-all.mjs](scripts/pack-all.mjs)): one tarball for the scoped
|
|
55
|
+
canonical name and one for the unscoped alias, sharing the same version. Each
|
|
56
|
+
variant embeds its own plugin name at pack time (client-bundle `PKG_NAME`,
|
|
57
|
+
ModuleLoader wrapper id, `cordis.patch.yml`), so both install independently.
|
|
58
|
+
CI ([.github/workflows/release.yml](../../../.github/workflows/release.yml))
|
|
59
|
+
runs the whole flow: bump `version` in
|
|
60
|
+
`packages/llm/sub2api-sync/package.json` → commit →
|
|
61
|
+
`git tag vX.Y.Z && git push origin vX.Y.Z` → `pnpm check` → two tarballs →
|
|
62
|
+
GitHub Release (4 assets: 2 versioned + 2 stable-name copies) → npm dual
|
|
63
|
+
publish (requires the repo secret `NPM_TOKEN`; `publishConfig.registry` is
|
|
64
|
+
pinned to the official npmjs registry, immune to mirror configs). Manual dual
|
|
65
|
+
publish from a local checkout:
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
pnpm pack:all # emits both tarballs into dist/; repo files restored afterwards
|
|
69
|
+
npm publish dist/mangoc-dsh-sub2api-sync-*.tgz --registry=https://registry.npmjs.org --access public
|
|
70
|
+
npm publish dist/dsh-sub2api-sync-*.tgz --registry=https://registry.npmjs.org --access public
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Publishing only the canonical package also works:
|
|
74
|
+
`pnpm --filter @mangoc/dsh-sub2api-sync publish`.
|
|
75
|
+
|
|
76
|
+
### Local development (linked checkout)
|
|
77
|
+
|
|
78
|
+
`dsh plugin add` only symlinks a local directory (`link:`); it does **not**
|
|
79
|
+
install or build the target, so building first is a hard precondition — not
|
|
80
|
+
just a development convenience:
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
pnpm install # installs deps (runs the `prepare` build)
|
|
84
|
+
pnpm build # tsdown + client wrapper; emits lib/index.js and lib/client.js
|
|
85
|
+
dsh plugin --profile web add ./packages/llm/sub2api-sync
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
After rebuilding, restart the app or toggle the plugin off/on so the entry is
|
|
89
|
+
imported again; a failed entry is not retried on its own.
|
|
90
|
+
|
|
91
|
+
## Configuration
|
|
92
|
+
|
|
93
|
+
Open **Settings → Sub2API 同步** (or edit `$DSH_HOME/settings.yaml` under the `sub2api-sync:` section; the cordis.yml entry is the base layer):
|
|
94
|
+
|
|
95
|
+
```yaml
|
|
96
|
+
sub2api-sync:
|
|
97
|
+
baseURL: https://sub2api.example.com
|
|
98
|
+
manualGroups:
|
|
99
|
+
"12":
|
|
100
|
+
name: Claude 拼车
|
|
101
|
+
platform: anthropic
|
|
102
|
+
"15":
|
|
103
|
+
platform: openai
|
|
104
|
+
# Optional:
|
|
105
|
+
enabledGroups: [] # empty = all declared groups
|
|
106
|
+
protocolOverrides:
|
|
107
|
+
"15": openai-completions # force a wire protocol per group
|
|
108
|
+
modelListOverrides:
|
|
109
|
+
"15": [gpt-x, gpt-y] # fallback when /v1/models is unreachable
|
|
110
|
+
compatTemplate:
|
|
111
|
+
compat:
|
|
112
|
+
supportsDeveloperRole: false # inherited by every synced model entry
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Relay keys never live in settings: paste each group's key once in the card; it is stored through the harness credential seam under the deterministic reference `DSH_SUB2API_KEY_<group-id>`.
|
|
116
|
+
|
|
117
|
+
## What a sync does
|
|
118
|
+
|
|
119
|
+
For every declared group with a stored key, the engine:
|
|
120
|
+
|
|
121
|
+
1. calls `GET {instance}/v1/models` with that group's key (falling back to `modelListOverrides` on failure);
|
|
122
|
+
2. maps the group's platform onto a wire protocol — `anthropic` → `anthropic-messages`, the OpenAI family (`openai`/`grok`/`kimi`/`zhipu`/`deepseek`) → `openai-responses`, the rest (`gemini`/`antigravity`/`composite`) → `openai-completions` — overridable per group;
|
|
123
|
+
3. derives the profile `baseURL` per protocol (OpenAI-family SDKs get `{instance}/v1`; the Anthropic SDK gets the bare host, since it appends `/v1/messages` itself);
|
|
124
|
+
4. writes a hand-declared `llm-pi-ai` provider profile at `providers.sub2api-<10-hex>` through a scoped `settings.mutate` carrying `expectedRevision` (three conflict-retry attempts), with a lenient retry policy (5 retries / 120 s backoff cap) tuned for proxied upstream 429 windows.
|
|
125
|
+
|
|
126
|
+
Routes take effect on the next request. The route suffix is a SHA-256 truncation over (instance baseURL, panel key id) — stable, collision-free across instances, and it deliberately carries no trace of the panel's sequential key numbering (a shared config screenshot reveals nothing about how many keys the site has issued). Entries only ever cover the `sub2api-<10-hex>` (current) and `sub2api-<digits>` (legacy, migrated on the next sync) shapes — the ownership boundary. Anything else in `llm-pi-ai.providers` (your hand-entered routes, other plugins' routes) is never touched. Keys that disappear from the selection lose their entry and their credential; the remote key itself is only revoked when `deleteOrphanKeys` is enabled. Each **registered-route row** in the sync card carries a **Remove** button — it deletes the local route and credential only (**no panel calls**, so it works even with an empty balance) and unchecks the key so the next sync cannot resurrect it; the report below stays a read-only hint of the last run. Logout, definitive auth loss, and instance switches purge the plugin's sync state site-wide — owned routes, relay-key credentials, and the last sync report go together (a report describing state that no longer exists must not keep rendering).
|
|
127
|
+
|
|
128
|
+
## Model Experience
|
|
129
|
+
|
|
130
|
+
- **Context blocks (short form)**: one provider per sub2api group, one relay key per provider, model catalog discovered from the instance; per-model `compat` inherits the user template. Reasoning efforts (`reasoningEfforts`) follow an explicit template when set (including `false`), and otherwise auto-fill per model from the models.dev enrichment.
|
|
131
|
+
- **Reasoning-effort auto-fill**: the models.dev `reasoning_options` effort enumeration maps onto pi-ai's `reasoningEfforts` (`none` → `off: null`, same-name passthrough otherwise). Merging is first-party first: a vendor-official entry (openai/anthropic/google/zhipuai/zai/deepseek/moonshotai/xai/alibaba, vendor-governed cloud hosting) wins outright — including when it carries no effort data (budget-only claude/gemini), which silences aggregator-invented vocabularies; with no first-party entry, aggregator sources intersect. Toggle/budget-only reasoning (claude, gemini, deepseek-v3.2, kimi-k2.x) has no effort-string wire shape and stays template/`compat`-owned.
|
|
132
|
+
- **Display-name enrichment**: the models.dev `name` lands on each pi-ai model entry's `name` field — the display name the model selector and the model-catalog editor (显示名称 column) show. The models.dev " (latest)" family marker is stripped, a name repeating the id is dropped, merging is first-party first (official name overrides, aggregator names fill absences), and a name can be an id's only enriched fact.
|
|
133
|
+
- Model ids come verbatim from the instance; the provider display name is the `providerNameTemplate` rendering of the key name (fallback `Group <id>`).
|
|
134
|
+
|
|
135
|
+
## Known Limitations and Deferred Work
|
|
136
|
+
|
|
137
|
+
- **No invariant surface**: this package publishes no `./invariant`. It has no owned relation whose independent observations can diverge — the sync is a settings write with the result readable back from the same service that accepted it.
|
|
138
|
+
- Mode A (panel login, 2FA, automatic key creation, group discovery) is Phase 2; until then groups are declared by hand (paste a key — that's all).
|
|
139
|
+
- The relay key cannot be reverse-mapped to its group: `/v1/sub2api/billing` and `/v1/usage` carry no group id/name/platform. The card never asks for a group id — the route name derives from the key's FNV-1a fingerprint (`sub2api-<fingerprint>`, idempotent re-add per key); the platform is guessed from the model list (manual override available); a subscription group's name is backfilled from `/v1/usage` `planName`.
|
|
140
|
+
- Capacity metadata (context window, max output, image input) follows the models.dev enrichment, with route-level defaults for ids the catalog does not know — expect generous defaults rather than exact figures for unmatched models.
|
|
141
|
+
- Usage lookup (`GET /v1/usage`) is a decided non-goal — consult the sub2api console.
|
|
142
|
+
- The panel API is a private frontend contract without a stability promise; responses are validated for required fields and fail loudly with context.
|
|
143
|
+
- Coexistence with the community plugin `GodD6366/dsh-sub2api`: route names do not collide (ours are `sub2api-<10-hex>` slugs, plus legacy `sub2api-<digits>` cleanup), but each plugin manages its own entries and credential refs — do not expect shared state.
|
|
144
|
+
- Requires a dsh release where `dsh-llm-pi-ai` attaches zero usage to replayed assistant messages (multi-turn pi-ai crash fixed on current master / the 0.1.2-rc.1 line).
|
|
145
|
+
- REAL composition, disposal, and snapshot tests against a live harness composition are pending; Phase 1 is verified by unit tests plus a live smoke test.
|
|
146
|
+
|
|
147
|
+
## Development
|
|
148
|
+
|
|
149
|
+
```sh
|
|
150
|
+
pnpm install
|
|
151
|
+
pnpm --filter @mangoc/dsh-sub2api-sync typecheck
|
|
152
|
+
pnpm --filter @mangoc/dsh-sub2api-sync test
|
|
153
|
+
pnpm --filter @mangoc/dsh-sub2api-sync build
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
MIT
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# @mangoc/dsh-sub2api-sync
|
|
2
|
+
|
|
3
|
+
把一个 [sub2api](https://github.com/Wei-Shaw/sub2api) 实例的分组、密钥、模型同步为 [`dsh-llm-pi-ai`](https://github.com/deepseek-ai/deepseek-harness) 的 provider 路由:登录一次、自动集成、持续同步。
|
|
4
|
+
|
|
5
|
+
设计 Agent Note:[Phase 1 设计](../../../docs/design/2026-09-04-sub2api-provider-sync.zh.md) 与 [客户端 UI 改造](../../../docs/design/2026-09-30-sub2api-sync-client-ui.zh.md)(当前实现基准)。
|
|
6
|
+
|
|
7
|
+
## 状态:Mode A(面板驱动)+ 客户端 UI
|
|
8
|
+
|
|
9
|
+
- **登录通道**:面板密码+2FA(C3)已交付;网页授权(C2,OAuth 授权码 + PKCE)待 sub2api 侧部署后接入。PAT(C1)已随本插件移除——sub2api 上游从未部署对应端点,该通道无令牌来源;
|
|
10
|
+
- **Keys 驱动同步**:Keys 卡列出面板已有 relay key(缺省全不选)→ **勾选多把**(可跨分组)→「同步所选」→ **一把 key 一个供应商**(路由 `sub2api-<10-hex>`,不含顺序 id)→ 模型下一请求即可用;需要新 key 用「创建 key」弹窗(默认与分组同名);
|
|
11
|
+
- **供应商命名**:`providerNameTemplate` 渲染,缺省 `{siteName}({keyName})`——即 `<站点名称>(<key 名>)`,站点名称来自实例探测;
|
|
12
|
+
- **UI**:设置页顶级卡片(`settings.section`)+ 插件页调参卡(`plugins.bundle.config`),样式对齐 `ui-settings-account`,文案全双语(zh/en);
|
|
13
|
+
- **数据缝**:配置读写走平台 `configForms`(字段级、revision 围栏、schema 校验);宿主路由只承载动作/秘密/状态投影;
|
|
14
|
+
- 指纹路由(旧 Mode B)与分组驱动同步(rev.5)均已退役——同步锚定真实面板 key id。
|
|
15
|
+
|
|
16
|
+
## 安装
|
|
17
|
+
|
|
18
|
+
### 用户安装(发布渠道)
|
|
19
|
+
|
|
20
|
+
harness 通过包的 `main`/`exports` 入口加载插件,即**构建产物** `lib/index.js`。以下任一渠道装到的 tarball 都自带 `lib/`,无需本地构建:
|
|
21
|
+
|
|
22
|
+
**npm**——两个包名同步发布:同一份代码、同一版本号,scoped 为 canonical,无 scope 短名为别名,任选其一安装:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
dsh plugin --profile web add @mangoc/dsh-sub2api-sync # canonical(scoped)
|
|
26
|
+
dsh plugin --profile web add dsh-sub2api-sync # 别名(无 scope 短名)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**GitHub Releases tarball**(不依赖 npm;地址始终指向最新版,两种变体内容等价):
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
dsh plugin --profile web add https://github.com/Shuai-S/dsh-sub2api-sync/releases/latest/download/mangoc-dsh-sub2api-sync.tgz
|
|
33
|
+
# 或短名变体:
|
|
34
|
+
dsh plugin --profile web add https://github.com/Shuai-S/dsh-sub2api-sync/releases/latest/download/dsh-sub2api-sync.tgz
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
也可以从 [Releases 页](https://github.com/Shuai-S/dsh-sub2api-sync/releases) 复制带版本号的 tarball URL(形如 `mangoc-dsh-sub2api-sync-0.1.0.tgz` / `dsh-sub2api-sync-0.1.0.tgz`)以锁定版本。
|
|
38
|
+
|
|
39
|
+
**从源码安装**(pnpm 会对 git 依赖自动执行 `prepare` 构建;需要本机构建链,首次较慢):
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
dsh plugin --profile web add github:Shuai-S/dsh-sub2api-sync#path:packages/llm/sub2api-sync
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
装好后重启应用、或在插件页把开关关掉再打开,让入口重新 import 一次;失败的条目不会自动重试。
|
|
46
|
+
|
|
47
|
+
### 发布(维护者)
|
|
48
|
+
|
|
49
|
+
每次发版**双包同步发布**([scripts/pack-all.mjs](scripts/pack-all.mjs)):scoped canonical 与无 scope 别名各出一个 tarball,版本号完全一致;客户端 bundle 内嵌的插件名(挂载 key、详情页匹配、ModuleLoader id)与 `cordis.patch.yml` 在打包时按包名注入,两个变体都可独立安装。CI([.github/workflows/release.yml](../../../.github/workflows/release.yml))自动完成全流程:bump `packages/llm/sub2api-sync/package.json` 的 `version` → 提交 → `git tag vX.Y.Z && git push origin vX.Y.Z` → `pnpm check` → 双 tarball → GitHub Release(4 个资产:2 个版本号 tarball + 2 个固定名副本)→ npm 双发布(需要仓库 secret `NPM_TOKEN`;`publishConfig.registry` 已锁定 npmjs 官方源,不受 npmmirror 镜像影响)。本地手动双发布:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
pnpm pack:all # 产出 dist/ 下两个 tarball,仓库文件自动还原
|
|
53
|
+
npm publish dist/mangoc-dsh-sub2api-sync-*.tgz --registry=https://registry.npmjs.org --access public
|
|
54
|
+
npm publish dist/dsh-sub2api-sync-*.tgz --registry=https://registry.npmjs.org --access public
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
只发 canonical 亦可:`pnpm --filter @mangoc/dsh-sub2api-sync publish`。
|
|
58
|
+
|
|
59
|
+
### 本地开发(link 调试)
|
|
60
|
+
|
|
61
|
+
`dsh plugin add` 对本地目录只做软链(`link:`),**不会**为目标目录安装依赖或执行构建,所以先构建是硬性前置条件,而不是「开发时的可选步骤」:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
pnpm install # 安装依赖(会触发 prepare 构建)
|
|
65
|
+
pnpm build # tsdown + 客户端包装;产出 lib/index.js 与 lib/client.js
|
|
66
|
+
dsh plugin --profile web add ./packages/llm/sub2api-sync
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
重新构建后需要重启应用、或在插件页把开关关掉再打开,让入口重新 import 一次;失败的条目不会自动重试。
|
|
70
|
+
|
|
71
|
+
## 使用
|
|
72
|
+
|
|
73
|
+
1. 点「登录」,在弹窗里填**实例地址**(如 `https://cn.api.nexarelay.com`,可带 `/v1`)+ 凭据(面板邮箱/密码,TOTP 账号按提示输入一次性验证码;密码会存入凭据存储,之后免输入);
|
|
74
|
+
2. **Keys 卡**列出该实例已有的 relay key(最新创建在前,首屏 5 条,底部「加载更多」翻页)。每一行前面的勾选框 = 选中用于同步;右上角「**创建 key**」弹窗可新建:选分组 → 名称默认与分组同名 → 创建即自动勾选;
|
|
75
|
+
3. 勾选好要用的 key(可多选、可跨分组),点「**同步所选(N)**」——引擎为**每一把勾选的 key 建一个供应商**,各自发现模型、按所属分组平台映射协议(anthropic → anthropic-messages,OpenAI 家族 → openai-responses,其余 → openai-completions,可按分组覆盖),写入 `llm-pi-ai.providers`;
|
|
76
|
+
4. 模型选择器下一请求即可用。主卡下方的「同步」块也能触发一次全量同步并查看完整报告;报告的每个条目带「**移除**」按钮——只删本地路由与凭据(**不调面板**,余额不足也可用),并自动取消该 key 的勾选,下次同步不会复活;可选高级参数在 **插件页 → Sub2API Sync** 调参卡。
|
|
77
|
+
|
|
78
|
+
## 安全
|
|
79
|
+
|
|
80
|
+
- 一切秘密(面板 token、密码、各 relay key)只进 harness 凭据存储(确定性引用 `DSH_SUB2API_TOKEN_<实例哈希>`、`DSH_SUB2API_KEY_<key-id>`、`DSH_SUB2API_PASSWORD_<实例哈希>`),不进 settings、不进日志、不进会话;
|
|
81
|
+
- `GET /keys` 只回 key 元数据(id/名称/分组/状态),**不回明文**;明文只用于引擎解析该 key 并即时写入凭据存储;
|
|
82
|
+
- 宿主动作路由只接受回环同源请求;跨设备访问 web UI 时配置只读(平台 `memory` 模式),动作需在 `cordis.yml` 显式开启 `allowRemoteActions`(默认关闭)。
|
|
83
|
+
|
|
84
|
+
## 同步做什么
|
|
85
|
+
|
|
86
|
+
对每一把勾选的 key,引擎(幂等 diff + 单飞 + 乐观并发):
|
|
87
|
+
|
|
88
|
+
1. 取该 key 的明文(面板列表返回则用之,否则回退凭据存储:先读新式 `DSH_SUB2API_KEY_<10-hex>` 引用,再读旧式 `DSH_SUB2API_KEY_<key-id>` 迁移存底,最后尝试 OAuth reveal 接口),并保持凭据与面板一致;
|
|
89
|
+
2. 携该 key 调 `GET {instance}/v1/models`(失败回退 `modelListOverrides`);
|
|
90
|
+
3. 按**其所属分组**的 `platform` 映射 wire 协议并推导条目 `baseURL`(Anthropic SDK 给裸 host,OpenAI 系给 `/v1` 根);
|
|
91
|
+
4. 经携带 `expectedRevision` 的定点 `settings.mutate` 写入 `providers.sub2api-<10-hex>`,附宽松重试策略(5 次 / 120s 退避上限)。
|
|
92
|
+
|
|
93
|
+
路由后缀是 **SHA-256(实例地址, key id) 的 10 位十六进制截断**——稳定、跨实例不碰撞,且**刻意不含面板的顺序 key id**:路由名不暴露站点发过多少 key,截图分享配置也不会泄露。条目只覆盖 `sub2api-<10-hex>`(现行)与 `sub2api-<纯数字>`(旧版遗留,下次同步自动迁移清理)两种形态(**一把 key 一个供应商**,显示名由 `providerNameTemplate` 渲染 key 名)。从勾选集消失的 key 会连带移除条目与凭据;远端 key 仅在 `deleteOrphanKeys` 开启时撤销(key id 由面板 key 列表反查,旧数字路由直接自带)。登出、授权失效与切换实例会**整站清除**本插件的同步状态——owned 路由、relay key 凭据与**上次同步的报告**一并清除(报告描述的状态已不存在,不得继续展示)。
|
|
94
|
+
|
|
95
|
+
## Model Experience
|
|
96
|
+
|
|
97
|
+
- 每分组一个 provider、一把 relay key;模型目录来自实例发现;逐模型 `compat` 继承用户模板;推理档位 `reasoningEfforts` 模板显式设置优先,未设置时按模型自动填充(见下);
|
|
98
|
+
- **模型元数据补全(C4)**:同步时按模型 id 从 models.dev 目录补全**显示名称**(`name`,即模型目录编辑器「显示名称」列)/`contextWindow`/`maxTokens`/图片输入/推理档位——`enrichModelMeta` 开启(缺省)时联网拉取(24h 进程内缓存),失败自动降级到**随包内置快照**(`data/models-dev.snapshot.json`,`pnpm --filter @mangoc/dsh-sub2api-sync models:refresh` 重新生成);关闭则只用内置快照(离线确定);两者都未命中的模型保持路由级缺省 128000/8192/纯文本;
|
|
99
|
+
- **推理档位自动填充**:取 models.dev 条目的 `reasoning_options` 中 `effort` 枚举,映射为 pi-ai 的 `reasoningEfforts`(`none` → `off: null`,其余同名直传)。合并按 **first-party 优先**:官方厂商条目(openai/anthropic/google/zhipuai/zai/deepseek/moonshotai/xai/alibaba 等及官方云托管)存在即胜出——官方条目无 effort 数据(如 claude/gemini 的 budget-only)同样压制聚合商的虚报词表;无官方条目时聚合商来源取**交集**。优先级:**模板显式设置(含 `false`)> 每模型自动填充 > 不写字段**;显示名 = `providerNameTemplate` 渲染的 key 名。
|
|
100
|
+
- **显示名称富化**:取 models.dev 条目的 `name` 写入 pi-ai 模型条目的 `name` 字段(模型选择器与「已自定义模型目录」编辑器的显示名);剥离 models.dev 的 " (latest)" 家族标记,名称与模型 id 相同(无信息量)时不写;合并同样 first-party 优先(官方名称覆盖,聚合商名称仅补缺),纯名称条目也参与富化。
|
|
101
|
+
|
|
102
|
+
## Known Limitations and Deferred Work
|
|
103
|
+
|
|
104
|
+
- **面板掩码明文**:若你的面板账号看不到 key 明文(非管理员),同步会失败并提示用管理员账号登录——这是刻意的不静默降级。
|
|
105
|
+
- **无 invariant 面**:本包不发布 `./invariant`(同步是一次 settings 写入,可从同一服务读回验证)。
|
|
106
|
+
- **网页授权(C2)待 sub2api 侧 OAuth 端点部署**(改造说明见设计 Note §12);部署前 UI 自动隐藏该通道。
|
|
107
|
+
- **dsh 版本下限:0.2.0-rc.2**(客户端半部依赖 `configForms`/locale 服务;0.1.x 代 app 会响亮失败)。
|
|
108
|
+
- **客户端半部只允许基线外部模块**(React / Cordis / ui-primitives / ui-slots / client-store / ui-dockkit)——客户端代码不得值导入 `config.ts`(会把 schemastery 拽进浏览器 bundle,动态模块表无此行,加载即静默失败);`test/client-bundle.test.ts` 是产物守卫。
|
|
109
|
+
- 实例前置 Cloudflare 时,需在 zone 放行 `/api/v1/*` 的宿主侧调用(WAF skip 规则)。
|
|
110
|
+
- 元数据补全会向 models.dev 发起一次公开目录请求(不带任何凭据与对话内容);`enrichModelMeta: false` 可完全离线(仅内置快照)。
|
|
111
|
+
- 推理档位自动填充只覆盖 models.dev 提供 `effort` 枚举的模型(OpenAI gpt-5 系/o3、GLM 5.x、Qwen3.8、DeepSeek V4 系等);`toggle`/`budget_tokens` 型推理(claude 系、gemini 系、deepseek-v3.2、kimi-k2.x 等)的线上形态不是 effort 字符串,`reasoningEfforts` 无法表达,仍由模板/`compat` 管辖。
|
|
112
|
+
- 用量查询(`GET /v1/usage`)是已决策的非目标。
|
|
113
|
+
- 与社区插件 `GodD6366/dsh-sub2api` 同装:路由名不冲突(本插件 `sub2api-<10-hex>` 摘要形态),各自管理条目与凭据引用。
|
|
114
|
+
|
|
115
|
+
## 开发
|
|
116
|
+
|
|
117
|
+
```sh
|
|
118
|
+
pnpm install
|
|
119
|
+
pnpm --filter @mangoc/dsh-sub2api-sync typecheck
|
|
120
|
+
pnpm --filter @mangoc/dsh-sub2api-sync test
|
|
121
|
+
pnpm --filter @mangoc/dsh-sub2api-sync build
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
MIT
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Sub2API provider routes for the harness LLM seam.
|
|
2
|
+
# One sub2api instance, one provider route per group. Groups, keys, and models
|
|
3
|
+
# are synced from the sub2api panel/gateway into the `llm-pi-ai:` settings
|
|
4
|
+
# section; routes register the moment the section lands.
|
|
5
|
+
#
|
|
6
|
+
# Does not override agent-default-model.
|
|
7
|
+
|
|
8
|
+
- insert:
|
|
9
|
+
- id: sub2api-sync
|
|
10
|
+
name: '@mangoc/dsh-sub2api-sync'
|