dsh-key-fallback
Verified@omdp/dsh-key-fallback · v3.3.3 · MIT · Web UI
API Key 回退轮换插件:多 key 池 + 自动轮换(触发码可配置,含 LlmError 标准码全集)+ 环境密钥编辑 + 明文揭示 + 短 ref 迁移。同时兼容 DSH 0.1.5-rc.3(settings.yaml 后端)与 0.1.7+(profile entry config 后端)。Settings → API Key 回退 UI. v7.
Install
dsh plugin add @omdp/dsh-key-fallback Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Published to npm without a public repository. Inspect the package contents before installing.
Readme
@omdp/dsh-key-fallback
English | 简体中文
为 DeepSeek Harness (DSH) 提供多 key 池 + 自动轮换——插件挂在 DSH 的凭证接缝 credentials.resolve 上:每次适配器取 key 时,若该 ref 属于已配置的池,就返回池的当前 key;遇到配置的触发错误时,把失败 key 标记为冷却(固定 cooldownMs,无指数退避)并前进到下一把。重发完全交给 DSH 自带的 dsh-llm-retry——本插件从不自行重发,只负责换 key,重试策略由 retry policy 决定。
当前版本:v3.3.2(v7 UI 代)。
v3.3.2 新增
- peer 声明改用三元组区间
>=0.2.0-rc.1 <0.2.1-0——代码零改动(2026-09-30)。 背景:DSH0.2.0-rc.2发布后,门禁把 3.3.1 拦下的方式与当初把 3.2.4 在0.2.0-rc.1上拦下 完全一致——已是连续第三轮同型故障(设置项消失、路由 404/405)。根因从来不是不兼容,而是 逐版本枚举在每次 rc 迭代下必然过时。核查依据:0.2.0-rc.1 → rc.2全量 20 包逐文件 SHA256 对比显示,本插件声明的六个 peer 只有package.json版本号变化;再用真门禁跑全版本矩阵——0.2.0-rc.1/rc.2/rc.3/rc.9/ 正式版0.2.0全 放行,0.2.1-rc.1/0.3.0-rc.1全 拦截。 ⚠️ 本条同时收窄支持面:不再声明0.1.7线,需要在 0.1.7 上运行请装 3.3.1。 ⚠️ 上界必须写<0.2.1-0而非<0.2.1:semver 里预发布排在正式版之前,0.2.1-rc.1 < 0.2.1成立,写<0.2.1会漏放行下一个三元组的 rc。
环境要求
- DeepSeek Harness 带
webprofile GUI(npx @deepseek-ai/dsh web) - Node.js
^22.19或>=24 - peer 范围用按三元组划分的区间(自 3.3.2 起;此前为逐版本枚举)——
@deepseek-ai/dsh/@deepseek-ai/dsh-credentials/@deepseek-ai/dsh-llm/@deepseek-ai/dsh-settings四条均声明>=0.2.0-rc.1 <0.2.1-0:实测该三元组的首个 rc 即自动放行其后续 rc(0.2.0-rc.2/rc.3…)与正式版0.2.0,跨三元组(如0.2.1-rc.1)仍会被门禁拦下、须重新核查。上界写-0是必须的:semver 里预发布排在正式版之前,写<0.2.1会漏放行下一个三元组的 rc(规范见AGENTS.md规范 3)。@deepseek-ai/cordis4.0.1 || 4.0.2 || 4.0.4、@deepseek-ai/schemastery3.18.1 || 3.18.2 || 3.18.4仍精确枚举(两者不进版本门禁——门禁只检查@deepseek-ai/dsh与dsh-*——且版本稀疏无预发布链)。插件只使用 credential-reference 半边(resolve/describe/set/unset/credentialRef,自0.1.0-rc.6起稳定)与agent/request+agent/request-errorwaterfall(载荷跨各版本未变);isCredentialRefName(rc.8 新增)本地实现兜底。注意0.1.7线自 3.3.2 起不再声明,需要它请装 3.3.1。
v3.3.1 新增
- 文档更正——代码零改动(2026-09-29)。v3.3.0 的发布说明断言「本插件从来没真正换过 key,因为写
process.env到不了凭证层」。该断言是错的,在此撤回。 真正把 key 交给 provider 的credentials.resolve包装自 v3.0.0 就存在 (git log -S 'credSvc.resolve =' -- dsh-key-fallback/lib/index.js→75ea76a);从 registry 下载 已发布的 3.2.4 tarball 核对,lib/index.js:298就是该包装、:305是它的pool-hit诊断。 也就是说:轮换一直在生效,那几处process.env写入是与可用包装并存的冗余,而不是机制本身。 v3.3.0 的真实内容见下一节的准确清单。 - 轮换生效的实机证据 —— 在真实桌面端(DSH
0.2.0-rc.1+ 插件 v3.3.0)会话走池中 provider 时, 取GET /dsh-key-fallback/diag:200 条(resolve57 /agent/request51 /request-error90), 池里两把 key 分别被用了 26 次 / 25 次,每次失败即交替:PICK key_fallback_sensenova_key1→RATE_LIMIT→marked key_fallback_sensenova_key1→PICK SENSENOVA_API_KEY→ … →pool-hit SENSENOVA_API_KEY -> key_fallback_sensenova_key1。 每条pool-hit都写明当时实际生效的那把 —— 正是适配器在网络上真正拿去鉴权的那把。
v3.3.0 新增
- 四项真实修复(2026-09-29)。注意:v3.3.0 最初的说明声称本版才让「换 key 第一次真正到达
provider」,那是错的——更正见上方 v3.3.1。v3.3.0 实际改的是:
activeRef不再报错当前 key。/pools原先用process.env[<池 env>]的值去池里反查成员、 只在没匹配到时才回退currentRef——所以可能高亮错误的那把。实机(0.2.0-rc.1)上它报activeRef=SENSENOVA_API_KEY,而currentRef是key_fallback_sensenova_key1。现在activeRef直接等于currentRef,两者永不矛盾。- 删除 5 处冗余的
process.env写入。 DSH 在启动时把{ ...process.env }冻结成「启动环境快照」 (dsh-app-boot),dsh-launch-environment把它拷进Map,dsh-credentials-local.inherited()只读这个快照——启动之后写process.env对凭证层不可见。因此这些写入对鉴权是死代码(真正供 key 的是resolve包装);删掉同时避免插件改写宿主进程环境。行为提示:继承process.env启的子进程 现在看到的是启动时的值而非轮换后的值——没有任何凭证消费者依赖它,因为它们全部都经credentials.resolve取值,而那正是包装拦截的地方。 - 失败归因加了守卫。
agent/request-error原先在无法确定当前 key 时会退化成归咎keyValues[0];现在只标记它真能指名的那把。 - 卸载会还原原始
resolve(经ctx.effect),并带身份校验,绝不覆盖「在我们之后安装的」包装层—— 热重载/fiber 重建不再层层叠加、留下持有过期 service 的闭包。
- 未改动:重发仍归 DSH 管。轮换处理只改池的当前 ref,并且仍然调用
next(),由dsh-llm-retry继续决定是否/重发几次。实测:每条轮换路径都调用了next()。 - 验证方式(全部实测,无推断):在进程内用真实的官方
@deepseek-ai/dsh-llm-pi-ai适配器 + 真实LocalCredentialProvider+ 本地 mock 上游,从真实出站 HTTPAuthorization头里抓 key —— 装插件前Bearer STORED-ENV-KEY,预选后Bearer KEY-ONE-111,RATE_LIMIT轮换后Bearer KEY-TWO-222,dispose()后回到STORED-ENV-KEY。另有两套边界用例(enabled:false、空池、 env 解析不到、useKeyRef锁定、HTTP 上activeRef/currentRef一致、dispose幂等、不匹配的错误码) ——14/14 与 9/9 全绿。实机时间线见上方 v3.3.1。
v3.2.4 新增
- peer 枚举追加 DSH
0.2.0-rc.1(2026-09-28)——代码零改动。DSH 桌面端升到0.2.0-rc.1后,0.1.7-rc.2的声明再次过时:门禁跳过 bundle(设置页里本插件整项消失、/dsh-key-fallback/*→ 404; 是优雅跳过而非崩溃)。声明前核查四步:- tarball 逐文件 diff(
0.1.7-rc.2→0.2.0-rc.1):dsh-credentials、dsh-llm、dsh-settings、dsh-shell、dsh-tools、dsh-mcp-client、dsh-web、dsh-agent的全部lib/文件 SHA1 一致—— 本插件用到的整个调用面(credentials.resolve/describe/set/unset、agent/request+agent/request-errorwaterfall、settings.describe/replace)逐字节未变(变化的只有dsh-session的附加式新增与dsh-app-boot门禁本体)。 - 门禁执行:用
0.2.0-rc.1的evaluatePluginCompatibility()跑新声明 → 放行; 跑 3.2.3 的旧声明 → 四条 peer 全部拦截。 - 真机装配:沙箱安装官方
@deepseek-ai/[email protected](542 包)+ 独立DSH_HOME,dsh --profile smoke --dump-config→ 插件入树、零门禁拦截、零错误。 - 发布件复核:
npm pack产物内package.json已含新枚举。
- tarball 逐文件 diff(
v3.2.3 新增
- peer 枚举追加 DSH
0.1.7-rc.2(2026-09-25)——代码零改动。DSH 桌面版(DeepSeek Harness desktop,运行时0.1.7-rc.2)上线后,精确到rc.1的声明一夜过时:门禁把 bundle 跳过 (skipping profile bundle,插件列表徽标「异常」)。声明前核查三步:- tarball 逐文件 diff(rc.1 → rc.2):
dsh-credentials的lib/逐字节一致(只改了 README 与 package.json);dsh-llm只新增内容类型与ACCOUNT_QUOTA错误码(本插件用到的agent/requestwaterfall、credential-reference API 签名均未变);dsh-shell/dsh-settings的lib/逐字节一致。 - 门禁执行:用 rc.2 的
evaluatePluginCompatibility()跑新声明 → 在0.1.7-rc.1与0.1.7-rc.2上均返回undefined(放行)。 - 真机回归:scratch profile(
@deepseek-ai/[email protected]+ 插件 3.2.2 + 精确版本豁免)真机启动 → 池页 HTTP 200(scratch profile 无池配置,[]属预期)。
- tarball 逐文件 diff(rc.1 → rc.2):
v3.2.2 新增
- 声明 DSH 版本支持(2026-09-24):新增
@deepseek-ai/dshpeer 条目(0.1.7-rc.1,逐版本枚举), 把「支持的运行时」写成显式契约。该声明由 DSH 自己的evaluatePluginCompatibility()(dsh-app-boot的 公开导出)在安装时与每次启动时校验:命中未实测的新运行时,bundle 会被优雅跳过 (stderr 打skipping profile bundle,DSH 照常启动),而不是以某种未定义方式崩掉。这个门禁 只有 DSH 0.1.7 起才存在,所以≤0.1.6的运行时只是看到一条不认识的 peer,不受影响 (本插件在那些版本上本来就走settings.yaml文件后端,一直可用)。只列实测版本,不用开放范围(仓库规范 3)。
v3.2.1 新增
- 救回滞留在
settings.yaml.imported里的池(2026-09-24)。0.1.7的一次性导入器会在写入任何东西之前 先把settings.yaml改名为settings.yaml.imported——所以某段导入失败时(典型:目标条目没声明 volatile 字段) 它永远不会重试,池子会永久留在.imported里。表现恰是:徽标显示 「尚未启用」、页面显示 「还没有任何池,点上方「启用新 provider 池」开始。」,尽管池子就在磁盘上。 注意文件回退在 v3.2.0 里本来就写了(readSettingsFromFile()会在settings.yaml缺失时退读settings.yaml.imported)——但在0.1.7上它永远轮不到:只要_liveRef非空,readSettings()第一行就从 live-ref 分支返回,而 ref 里是{}。真正缺的不是又一个读回退, 而是两个后端之间的一座桥。v3.2.1在首个 HTTP 请求时做一次性显式搬运(不在apply()里——原因见下): 当 live 配置里 provider 数为 0 且 旧文件里有池时,经settings.replace()写进 profile 条目。 标记文件(<DSH_HOME>/.key-fallback-migrated)保证「真正只做一次」,因此有意删光所有池后不会在下次启动被复活。- 为什么不能放在
apply():settings.replace()走configEditor.edit(),要求本 entry 的 fiber 已 ACTIVE——describe()会跳过fiber.state !== 2的条目,而apply()执行时 fiber 还在创建中, 调用必抛No configurable plugin entry。首个 UI 请求(/pools)才是最早的安全时机。 - 标记只在落盘成功之后才写,因此写失败会在下次启动重试。
- 为什么不能放在
v3.2.0 新增
- 声明支持 DSH
0.1.7-rc.1,同时保留0.1.5-rc.3(2026-09-24)。这是本插件首个需要改动代码的版本,原因是 DSH0.1.7改变了插件配置的存放位置——详见下节。
两种配置后端,同一份构建
自 DSH 0.1.7 起,插件配置不再位于 <DSH_HOME>/settings.yaml。首次启动时 0.1.7 会把该文件
改名成 settings.yaml.imported,并把每个段的内容写入 profile 补丁
(~/.dsh/profiles/<profile>/cordis.patch.yml)中对应 loader 条目的 config:;此后由 settings 服务托管,
插件通过注入的 config 参数读取,而不再自行读写该 YAML。rc.x 时代的 ctx.settings.get() /
register() / installSection() 在 0.1.7 上已不存在。
因此本插件在启动时自动判别后端,两条路径同时可用:
DSH 0.1.5-rc.3(及更早 rc.x) |
DSH 0.1.7-rc.1(及更新) |
|
|---|---|---|
| 池配置位置 | <DSH_HOME>/settings.yaml 的 key-fallback: 段 |
profile 补丁中 key-fallback 条目的 config |
| 插件如何读写 | 直接读写该 YAML 文件(附时间戳备份) | config.providers 是活的 volatile ref;写入走 settings.replace() |
| 判别方式 | 无 volatile schema 支持 ⇒ 文件后端 | Config 声明了 volatile 字段 ⇒ 后端来自 config |
移植该模式时有两个关键点:
- 必须声明
Config,否则迁移会静默丢弃配置。0.1.7的导入路径调用settings.update(ns, values),若该条目没有声明 volatile 字段就会抛错,仅打印settings: section ... was not imported,配置只残留在settings.yaml.imported里。因此v3.2.0导出Config = z.object({ providers: <dict>.volatile() })。 - volatile API 在两种 schemastery 构建上不同。
.volatile()在 schemastery3.18.4(0.1.7)上可用, 但在3.18.2(rc.3)上不存在——在那里调用会得到undefined,而若据此注册一个无 volatile 的 schema 就会改变 rc.3 的行为。故导出做了守卫:typeof field.volatile === 'function' ? z.object({ providers: field.volatile() }) : undefined—— rc.3 拿到 没有Config(行为与 v3.1.x 逐字节一致),0.1.7拿到含 volatile 的那一份。
config.providers 的值被 DSH 深度冻结,故 readSettings() 向调用方返回可变深拷贝;写入先乐观地更新内存副本
再经 settings.replace() 落盘(未决写入计数避免 UI 短暂读回写入前的旧树)。
两条后端均已在真实安装上端到端实测:读取(两个 provider 都出现在 /dsh-key-fallback/pools,含 env key 实时值)、
增删改(POST/DELETE 往返并落盘)、以及 rc.3 回归(settings.yaml 未被改名也未改内容、cordis.patch.yml 仍为
[]、备份照旧生成)。
v3.1.7 新增
- 声明支持 DSH
0.1.5-rc.2(2026-09-15)。源码核查:@deepseek-ai/dsh-credentials/dsh-llm/dsh-settings从0.1.5-rc.1到rc.2只有package.json变化——lib/文档逐文件 SHA256 全一致。运行时核查:对真实[email protected]web profile 严格模式安装 peer(不加--legacy-peer-deps)零告警通过,启动零浏览器控制台错误,/dsh-key-fallback/*路由服务,设置页「API Key 回退」渲染。 - 声明支持 DSH
0.1.6-alpha.1(2026-09-15,源码级逐文件哈希比对 + 真实运行时冒烟:在临时安装的0.1.6-alpha.1上以 web profile 实测加载)。未改动任何代码:@deepseek-ai/dsh-credentials与@deepseek-ai/dsh-settings的lib/相对0.1.5-rc.2逐字节一致(仅版本号/文档变化),@deepseek-ai/dsh-llm的删除项(priceImages、projectImagesForTextModel/request-image-offload 系列)均在本插件调用面之外——本插件调用的credentialRef、credentials.resolve/set/unset/describe、llm.listProviders/listConfigurableProviders、agent/request、agent/request-error、settings、webServer全部实测保留(webServer提供方dsh-host-webserver整包字节一致)。运行时验证:插件激活、/dsh-key-fallback/*路由服务、设置页「API Key 回退」渲染。旧版本继续兼容——0.1.5-rc.2与0.1.6-alpha.1均为追加进枚举,未删除任何版本。
v3.1.6 新增
- 声明支持 DSH
0.1.5-rc.1(2026-09-10 源码级兼容性核查)。未改动任何代码:@deepseek-ai/dsh-credentials与@deepseek-ai/dsh-settings相对0.1.2-rc.1只有package.json版本号变化(代码逐文件哈希一致),@deepseek-ai/dsh-llm的变化是附加式(新增FileBlock/fileHandleText/ assistant-stream 等),本插件调用的 API 全部保留:credentialRef、credentials.resolve/set/unset/describe、llm.listProviders/listConfigurableProviders、agent/request、agent/request-error、settings、webServer;agent/pre-stepwaterfall 载荷两版逐行相同。旧版本继续兼容——0.1.5-rc.1是追加进枚举,未删除任何版本。 - 注:DSH
0.1.5-rc.1移除了ctx.agent并调整 Inbox/Session API,本插件从未使用这些接口。
v3.1.4 新增
- env 名自动派生已消毒:为 id 含非法字符(
b-ai、B.AI等)的 provider 建池不再报env must be POSIX identifier——自动派生的 env 名(<PROVIDER>_API_KEY)现在会把-/.等转成_(b-ai→B_AI_API_KEY)。显式传入的env仍按原规则校验。修复"选择 LLM provider → 启用"对此类 provider 的 400 报错。
v3.1.3 提供什么
设置 → API Key 回退 —— 顶层设置页,全新 UI(状态点、徽章、渐变池卡片、逐 key 行):
- 可配置且真正生效的轮转触发码(
rotateOn):点选 chips 决定哪些错误码触发轮换。预设 chips 覆盖 DSHLlmError标准码全集——QUOTA/AUTH/RATE_LIMIT/TIMEOUT/TRANSPORT/SERVER/EMPTY_RESPONSE/INVALID_CREDENTIAL——也可添加非标准/自定义错误码(与 provider 的failure.code精确匹配)。你保存什么就执行什么:不存在"把选中的三个码偷偷变回六码超集"的魔法。ABORTED(用户取消)永不触发轮换。 - 真实当前使用 key 显示:页面显示当前真正在用哪把 key(池的当前 ref —— 正是凭证接缝返回给适配器的那把)——不是截断的哈希,不是猜的名字。
- 短 ref 命名:新 key 自动命名为
key_fallback_<provider>_key1、key_fallback_<provider>_key2、…,UI 显示干净的短名(key1、key2、…或你的自定义label)。已有旧长 ref 一次性、幂等地自动迁移(写新 ref → 持久化配置 → best-effort 删旧 ref;任何一步失败即中止、安全可重试)。 - 明文揭示:每行有眼睛开关(
👁/🙈),经GET /keys/plain显示真实值(仅限本池的 key 或本池 env key)。env key 行也会显示明文——不会被只读说明吞掉。 - 环境密钥可编辑:池的 env key(
AGNES_API_KEY等)在凭证文件可写时可直接在页面里改。若由启动环境提供(只读),UI 会说明并拒绝编辑(HTTP 400)。 - 逐 key 控制:更新值、设置"失败后→"下一把(
nextRef)、锁定到某把 key("设为当前")、删除 key(env key 不可删)。 - 池级控制:启用开关、冷却显示、"↺ 重置冷却"、池锁定/自动轮换、逐池状态(
live/cooling/recovered)。
轮换语义
- key 到底怎么被用上:DSH 的凭证接缝是
credentials.resolve(ref)。每个模型适配器都经它取 key(dsh-llm-pi-ai、dsh-llm-deepseek-api-key,以及任何解析凭证引用的插件),且每次请求都会调用一次。本插件包装的就是这一个方法:当解析的 ref 属于已配置的池时,包装层返回池的当前 key,而不是存储里的值。所以"换 key"就是改池的当前 ref —— 下一次请求立刻生效,无需重启。- 写
process.env不管用:凭证 provider 读的是 DSH 启动时冻结的「启动环境快照」(dsh-app-boot里的{...process.env},由dsh-launch-environment拷进Map),启动之后再改process.env对它不可见。DSH 自己在 provider 条目上的注释也写着 managed document "is never materialized into the process environment"。历史说明:3.2.4 及之前插件也会写process.env[<池 env>],但它始终与上面的包装并存——那个写入是冗余的,不是机制本身,所以轮换一直照常工作。v3.3.0 出于整洁删掉了这些写入。若你看到旧笔记声称轮换「从未生效」,那是错的,见 v3.3.1 一节。
- 写
- pick(
agent/request,预选):先尊重useKeyRef锁定(但冷却中的锁定 key 会跳过,避免池在某把坏 key 上死锁),否则按游标在 live key 上轮询。选中的 ref 成为池的当前 ref —— 也就是凭证接缝下一次 resolve 交给适配器的那把。 - fail(
agent/request-error,注册prepend保证先于dsh-llm-retry看到错误):仅当错误匹配本池rotateOn时才处理——按failure.code(精确)、按 HTTP 状态映射(429→RATE_LIMIT、401/403→AUTH、402→QUOTA、5xx→SERVER)、或按消息关键字。把失败 key 标记为固定cooldownMs(默认 30 s),然后切到nextRef(若配置)否则下一把 live key。 - re-send 完全交给 DSH 的
dsh-llm-retry与用户自己的 per-provider 重试策略。两者独立互补:重试插件决定"同一把 key 重发几次"(retryPolicy.retryableCodes默认EMPTY_RESPONSE/RATE_LIMIT/SERVER/TIMEOUT/TRANSPORT——注意不含 AUTH/QUOTA);本插件决定"换下一把 key"。例如AUTH失败(重试本来也不会重发,重发也白搭)仍会轮换到下一把——下一次请求就会用新 key 鉴权。
诊断
- client 会把运行时异常 POST 到
POST /dsh-key-fallback/diag。 GET /dsh-key-fallback/diag返回内存缓冲(最近 200 条)JSON。
安装
# 从 npm(推荐)
cd ~/.dsh/profiles/web
pnpm add @omdp/dsh-key-fallback
# 或从本地 checkout
dsh plugin --profile web add link:D:/WorkSpace/omdp/dsh-key-fallback
# 重启 DSH,然后打开 设置 → API Key 回退
注意:该 profile 目前从 npm(
^3.x)安装到~/.dsh/profiles/web/node_modules/@omdp/dsh-key-fallback;把新版lib/index.js+lib/client.js复制过去并重启 DSH 即可升级。包声明了dsh.bundle.patch,自动激活——无需手动改cordis.patch.yml。
HTTP API(host)
| 方法 | 路径 | 用途 |
|---|---|---|
| GET | /dsh-key-fallback/pools |
列池:live 状态、activeRef、env 可写性/来源、逐 key 状态 |
| POST | /dsh-key-fallback/pools |
创建/更新池(enabled、cooldownMs、rotateOn、useKeyRef、…) |
| DELETE | /dsh-key-fallback/pools?provider= |
删除池 + 其全部 key |
| POST | /dsh-key-fallback/keys |
加 key(自动短 ref key_fallback_<p>_keyN) |
| PATCH | /dsh-key-fallback/keys |
更新值 / label / nextRef / useKeyRef(env key 的值=改 env) |
| DELETE | /dsh-key-fallback/keys?provider=&ref= |
删 key(env key 拒绝) |
| GET | /dsh-key-fallback/keys/plain?provider=&ref= |
揭示真实值(仅本池 key / env key) |
| POST | /dsh-key-fallback/reset |
重置某池冷却状态 |
| GET/POST | /dsh-key-fallback/diag |
诊断缓冲 |
已知限制
- 冷却为每池固定
cooldownMs(无指数退避);冷却到期 key 自动恢复 live。 - 轮换/冷却状态为内存态 + 持久化池配置;DSH 重启会重读配置并重算 live 状态。在 DSH
0.1.5-rc.3及更早版本上,该配置是<DSH_HOME>/settings.yaml的key-fallback段;在0.1.7+ 上则是~/.dsh/profiles/<profile>/cordis.patch.yml中key-fallback条目的config。 - env key 不能通过 UI 删除(它是池的身份标识)。
License
MIT