@dsh-plus/llm-pi 0.2.0 → 0.2.2

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/lib/client.js CHANGED
@@ -184,7 +184,7 @@ window.__ModuleLoader__.load({
184
184
  unsaved: "未保存",
185
185
  loading: "加载中…",
186
186
  unavailable: "配置服务不可用。",
187
- readOnly: "当前部署无 settings provider,配置为只读;请编辑 settings.yaml。",
187
+ readOnly: "当前部署无 settings provider,配置为只读;请在 profile 的 cordis.patch.yml 行级配置。",
188
188
  invalidNumber: "请输入正整数。",
189
189
  collapse: "收起",
190
190
  expand: "展开",
@@ -201,7 +201,7 @@ window.__ModuleLoader__.load({
201
201
  unsaved: "Unsaved",
202
202
  loading: "Loading…",
203
203
  unavailable: "Settings service unavailable.",
204
- readOnly: "No settings provider in this deployment; config is read-only. Edit settings.yaml.",
204
+ readOnly: "No settings provider in this deployment; config is read-only. Configure it at the profile row in cordis.patch.yml.",
205
205
  invalidNumber: "Enter a positive integer.",
206
206
  collapse: "Collapse",
207
207
  expand: "Expand",
@@ -408,7 +408,7 @@ ${extra}
408
408
  //#region src/ns.ts
409
409
  /**
410
410
  * settings 命名空间字面量(纯常量,零依赖,浏览器半可安全引入)。
411
- * 服务端直接以字面量注册(0.1.2-alpha.2 起 dsh-settings 对命名空间做编译期
411
+ * 服务端直接以字面量注册(dsh-settings 对命名空间做编译期
412
412
  * 语法校验,见 config.ts);浏览器半把它作为配置卡片的注册键
413
413
  * (插件页配置卡片挂在 row/bundle config 槽位上,与命名空间解耦,
414
414
  * 见 shared/config-slots),
@@ -1125,8 +1125,9 @@ ${extra}
1125
1125
  *
1126
1126
  * 状态行回答的是"我现在到底在跑哪一份 pi-ai"——插件按文件路径动态加载已装 dsh 树
1127
1127
  * 里的套件(见 resolve-dsh.ts),manifest 上写的版本**不是**生效版本。这里显示
1128
- * 生效版本、安装树、目录规模与数据日期、可服务协议、compat 表来源,以及逐项降级
1129
- * 诊断;版本超出验证区间时给出提示(提示而非阻断)。
1128
+ * 生效版本、安装树、目录规模与数据日期、可服务协议、compat 表来源,以及诊断
1129
+ * (按「形态说明 / 降级诊断」分块,避免恒定事实淹没真实降级);版本超出验证区间
1130
+ * 时给出提示(提示而非阻断)。
1130
1131
  * @module llm-pi/client/root-fields
1131
1132
  */
1132
1133
  /** 时间戳 → 本地日期(秒级精度足够)。 */
@@ -1154,6 +1155,21 @@ ${extra}
1154
1155
  if (copy.updatedAt === void 0) return t("officialCopyPending");
1155
1156
  return `${copy.path} · ${copy.routes} ${t("officialCopyRoutes")} · ${formatTime(copy.updatedAt, t("runtimeVersionUnknown"))}`;
1156
1157
  }
1158
+ /**
1159
+ * 诊断分块:形态说明与降级分开渲染——「官方 src 不随 npm 发布」这类恒定事实
1160
+ * 永不会消失,混在一起会把真正的降级淹掉。空块不渲染。
1161
+ */
1162
+ function DiagnosticBlock(props) {
1163
+ const messages = props.items.filter((item) => item.level === props.level).map((m) => m.message);
1164
+ if (messages.length === 0) return null;
1165
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1166
+ className: "lpc-catDetailLabel",
1167
+ children: props.label
1168
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
1169
+ className: "lpc-catJson",
1170
+ children: messages.join("\n")
1171
+ })] });
1172
+ }
1157
1173
  function RootFields(props) {
1158
1174
  const { t, draft, disabled, kit, onEdit } = props;
1159
1175
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
@@ -1205,12 +1221,14 @@ ${extra}
1205
1221
  className: "lpc-invalid",
1206
1222
  children: `${t("runtimeNotice")}:${kit.versionNotice}`
1207
1223
  }) : null,
1208
- kit.diagnostics.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1209
- className: "lpc-catDetailLabel",
1210
- children: t("runtimeDiagnostics")
1211
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
1212
- className: "lpc-catJson",
1213
- children: kit.diagnostics.join("\n")
1224
+ kit.diagnostics.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DiagnosticBlock, {
1225
+ label: t("runtimeFormNotes"),
1226
+ level: "info",
1227
+ items: kit.diagnostics
1228
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DiagnosticBlock, {
1229
+ label: t("runtimeDiagnostics"),
1230
+ level: "degradation",
1231
+ items: kit.diagnostics
1214
1232
  })] }) : null
1215
1233
  ] })
1216
1234
  ] });
@@ -2866,7 +2884,8 @@ ${extra}
2866
2884
  officialCopyPending: "尚未生成",
2867
2885
  officialCopyFailed: "生成失败:",
2868
2886
  officialCopyRoutes: "个 route",
2869
- runtimeDiagnostics: "诊断",
2887
+ runtimeFormNotes: "形态说明",
2888
+ runtimeDiagnostics: "降级诊断",
2870
2889
  runtimeVersionUnknown: "未知",
2871
2890
  runtimeCatalogUnit: "个 provider /",
2872
2891
  runtimeCatalogModels: "个模型",
@@ -3000,7 +3019,8 @@ ${extra}
3000
3019
  officialCopyPending: "not generated yet",
3001
3020
  officialCopyFailed: "generation failed: ",
3002
3021
  officialCopyRoutes: "routes",
3003
- runtimeDiagnostics: "Diagnostics",
3022
+ runtimeFormNotes: "Packaging notes",
3023
+ runtimeDiagnostics: "Degradations",
3004
3024
  runtimeVersionUnknown: "unknown",
3005
3025
  runtimeCatalogUnit: "providers /",
3006
3026
  runtimeCatalogModels: "models",
package/lib/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import z from "@deepseek-ai/schemastery";
2
2
  import { VolatileFields } from "@dsh-plus/shared";
3
3
  import { Context } from "@deepseek-ai/cordis";
4
4
  //#region src/config.d.ts
5
- /** settings 命名空间(字面量即合法命名空间,0.1.2-alpha.2 起编译期校验;webui 配置卡片与插件运行期读取同一份)。 */
5
+ /** settings 命名空间(字面量即合法命名空间,编译期校验;webui 配置卡片与插件运行期读取同一份)。 */
6
6
  declare const SETTINGS_NS = "dsh-plus-llm-pi";
7
7
  /** 线协议标识;合法集合运行期推导,故此处是开放字符串而非字面量联合。 */
8
8
  type ProtocolId = string;
@@ -89,7 +89,7 @@ interface LlmPiConfig {
89
89
  * (2 个根字段均有 schema 默认值),解析输出仍是完整的 {@link LlmPiConfig}。
90
90
  */
91
91
  type LlmPiConfigInput = Partial<LlmPiConfig>;
92
- /** 活动字段形态(0.1.7 loader 解析产物:volatile 字段为活动引用)。 */
92
+ /** 活动字段形态(loader 解析产物:volatile 字段为活动引用)。 */
93
93
  type LlmPiConfigFields = VolatileFields<LlmPiConfig>;
94
94
  declare const Config: z<LlmPiConfigInput, LlmPiConfigFields>;
95
95
  //#endregion
package/lib/index.js CHANGED
@@ -24,7 +24,7 @@ import { homedir } from "node:os";
24
24
  //#region src/config.ts
25
25
  /**
26
26
  * 配置单一事实源:cordis 行级 Config(组合默认值)与 settings namespace
27
- *(用户层,经 dsh-settings-file 持久化到 $DSH_HOME/settings.yaml)共用同一
27
+ *(用户层=profile 行级覆盖层 cordis.patch.yml,热生效)共用同一
28
28
  * schemastery schema。无密钥字段(apiKeyEnv 是凭据引用名而非密钥本身)。
29
29
  *
30
30
  * 与官方 llm-pi-ai 的差异:
@@ -35,10 +35,10 @@ import { homedir } from "node:os";
35
35
  * 官方将来新增协议无需改本插件。
36
36
  * @module llm-pi/config
37
37
  */
38
- /** settings 命名空间(字面量即合法命名空间,0.1.2-alpha.2 起编译期校验;webui 配置卡片与插件运行期读取同一份)。 */
38
+ /** settings 命名空间(字面量即合法命名空间,编译期校验;webui 配置卡片与插件运行期读取同一份)。 */
39
39
  const SETTINGS_NS = "dsh-plus-llm-pi";
40
40
  /**
41
- * 官方适配器不可用/推导失败时的协议兜底三元组(自 0.1.2-alpha 线以来的稳定集)。
41
+ * 官方适配器不可用/推导失败时的协议兜底三元组。
42
42
  * 运行期生效集合来自已安装官方包的 `supportedProtocols()`(见 resolve-dsh.ts),
43
43
  * 本常量只用于 schema 描述、浏览器半首帧兜底与推导失败回退。
44
44
  */
@@ -68,8 +68,6 @@ const DEEPSEEK_REASONING_EFFORTS = [
68
68
  "high",
69
69
  "max"
70
70
  ];
71
- /** 旧版 imageDetail 枚举(0.1.2-alpha.1 已移除该字段;仅用于 schema 透传陷阱的取值集)。 */
72
- const DEEPSEEK_IMAGE_DETAILS = ["auto", "low"];
73
71
  /** dsh-timeout 的定时器上限(与官方 MAX_TIMER_DELAY_MS 对齐)。 */
74
72
  const MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
75
73
  /**
@@ -103,8 +101,7 @@ const modelEntry = z.object({
103
101
  reasoningEfforts: z.union([z.const(false), reasoningEfforts]).description("可选 reasoning 档位:false=非推理模型;dict=档位→线值映射;缺省继承内置目录能力"),
104
102
  compat: compatDict.description("模型级 compat(字段级合并,压过 route 级与继承值)"),
105
103
  imagePixelBudget: z.number().step(1).min(1).description("(仅 adapter: deepseek)单张请求图片的总像素预算;缺省继承官方目录"),
106
- imageMaxBytes: z.number().step(1).min(1).description("(仅 adapter: deepseek)单张请求图片的编码字节上限;缺省继承官方目录"),
107
- imageDetail: z.union(DEEPSEEK_IMAGE_DETAILS).description("(已移除)图片细节档位;0.1.2-alpha.1 起请改用 imagePixelBudget/imageMaxBytes")
104
+ imageMaxBytes: z.number().step(1).min(1).description("(仅 adapter: deepseek)单张请求图片的编码字节上限;缺省继承官方目录")
108
105
  });
109
106
  const providerProfile = z.object({
110
107
  adapter: z.union(ADAPTER_KINDS).description("路由适配器:pi=PiAiAdapter(默认,协议集合随已装官方适配器推导);deepseek=官方 DeepSeekAdapter(图片走 Files API 文件通道,失败自动降级 base64)").default("pi"),
@@ -134,7 +131,7 @@ const providerProfile = z.object({
134
131
  timeoutMs: z.natural().description("HTTP/provider 超时毫秒"),
135
132
  websocketConnectTimeoutMs: z.natural().description("WebSocket 连接超时毫秒"),
136
133
  streamIdleTimeoutMs: z.number().min(Number.MIN_VALUE).max(MAX_TIMER_DELAY_MS).description("单支流式读取的最大空闲间隔毫秒"),
137
- maxRequestImageBytes: z.natural().description("单请求 base64 图片载荷上限字节;缺省 20MiB(0.1.2-alpha.1 起官方必需字段,旧配置免改)"),
134
+ maxRequestImageBytes: z.natural().description("单请求 base64 图片载荷上限字节;缺省 20MiB(官方必需字段,旧配置免改)"),
138
135
  requestImagePixelBudget: z.number().step(1).min(1).description("单请求每个确定性内联图片版本的像素总预算;缺省 2048x2048(官方同值)"),
139
136
  requestImageMaxBytes: z.number().step(1).min(1).description("内联图片版本编码字节目标;缺省 1MiB(官方同值)"),
140
137
  retryPolicy: z.any().description("provider 重试策略(dsh-llm RetryPolicy 形状,构建期校验)"),
@@ -517,7 +514,7 @@ function browseModels(kit, query = {}) {
517
514
  * 3. **模型条目字段集**:Config schema 的 `providers.*.models` 键集——继承时按此
518
515
  * 透传 pi-ai 目录的同名字段,官方将来新增模型级字段即自动跟随。
519
516
  *
520
- * 历史教训(0.1.5-rc.1):compat 门控曾是官方 catalog.ts 的手抄镜像,官方扩容
517
+ * 历史教训(详见 docs/repo/事故记录.md):compat 门控曾是官方的手抄镜像,官方扩容
521
518
  * offer 字段而旧表漏收,导致官方可配字段被本插件**误拒**(静默功能缺失、无报错)。
522
519
  * 现改为现场推导,`tests/official-surface.test.ts` 直读官方副本守门推导正确性。
523
520
  *
@@ -652,10 +649,18 @@ const FALLBACK_MODEL_ENTRY_FIELDS = [
652
649
  function fallbackProtocols() {
653
650
  return [...FALLBACK_PROTOCOLS];
654
651
  }
655
- /** 官方门控表不可解析时的最后已知快照(0.1.5-rc.2 实测值)。 */
652
+ /**
653
+ * 官方门控表不可解析时的兜底快照(0.2.1-alpha.1 现场推导值)。
654
+ *
655
+ * 只在官方改打包形态、`COMPAT_GATES` 解析失败时启用(此时未知键放行、只拦快照里
656
+ * 明确 withhold 的键,见 compat.ts 的「语义分两档」)。快照过期会重新引入
657
+ * 「误拒官方新字段」那类静默故障,故由 tests/official-surface.test.ts 的
658
+ * 「兜底快照不得过期」用例与现场推导逐协议比对守门。
659
+ */
656
660
  const FALLBACK_TABLE = {
657
661
  source: "fallback",
658
662
  gates: {
663
+ "mistral-conversations": { supportsMidConvoSystemMessages: "withhold" },
659
664
  "openai-completions": {
660
665
  supportsStore: "offer",
661
666
  supportsDeveloperRole: "offer",
@@ -681,7 +686,8 @@ const FALLBACK_TABLE = {
681
686
  zaiToolStream: "withhold",
682
687
  supportsOpenAIGrammarTools: "withhold",
683
688
  sendSessionAffinityHeaders: "withhold",
684
- deferredToolsMode: "withhold",
689
+ supportsMidConvoSystemMessages: "withhold",
690
+ supportsMidConvoToolAdditions: "withhold",
685
691
  sessionAffinityFormat: "withhold"
686
692
  },
687
693
  "openai-responses": {
@@ -693,7 +699,32 @@ const FALLBACK_TABLE = {
693
699
  supportsOpenAIGrammarTools: "withhold",
694
700
  supportsAdditionalTools: "withhold",
695
701
  supportsToolSearch: "withhold",
696
- supportsExplicitPromptCacheMode: "withhold"
702
+ supportsExplicitPromptCacheMode: "withhold",
703
+ supportsMidConvoSystemMessages: "withhold"
704
+ },
705
+ "azure-openai-responses": {
706
+ supportsDeveloperRole: "offer",
707
+ supportsMaxOutputTokens: "offer",
708
+ supportsStrictMode: "offer",
709
+ supportsLongCacheRetention: "offer",
710
+ sessionAffinityFormat: "withhold",
711
+ supportsOpenAIGrammarTools: "withhold",
712
+ supportsAdditionalTools: "withhold",
713
+ supportsToolSearch: "withhold",
714
+ supportsExplicitPromptCacheMode: "withhold",
715
+ supportsMidConvoSystemMessages: "withhold"
716
+ },
717
+ "openai-codex-responses": {
718
+ supportsDeveloperRole: "offer",
719
+ supportsMaxOutputTokens: "offer",
720
+ supportsStrictMode: "offer",
721
+ supportsLongCacheRetention: "offer",
722
+ sessionAffinityFormat: "withhold",
723
+ supportsOpenAIGrammarTools: "withhold",
724
+ supportsAdditionalTools: "withhold",
725
+ supportsToolSearch: "withhold",
726
+ supportsExplicitPromptCacheMode: "withhold",
727
+ supportsMidConvoSystemMessages: "withhold"
697
728
  },
698
729
  "anthropic-messages": {
699
730
  supportsEagerToolInputStreaming: "offer",
@@ -704,10 +735,13 @@ const FALLBACK_TABLE = {
704
735
  allowEmptySignature: "offer",
705
736
  supportsStrictTools: "offer",
706
737
  sendSessionAffinityHeaders: "withhold",
707
- supportsToolReferences: "withhold",
738
+ sessionAffinityFormat: "withhold",
739
+ supportsMidConvoSystemMessages: "withhold",
740
+ supportsMidConvoToolChanges: "withhold",
708
741
  supportsMidConvoEffort: "withhold",
709
742
  allowedFallbackModels: "withhold"
710
- }
743
+ },
744
+ "bedrock-converse-stream": { supportsStrictMode: "offer" }
711
745
  },
712
746
  specs: {
713
747
  supportsStore: "boolean",
@@ -821,7 +855,7 @@ function checkValue(field, spec, value, where) {
821
855
  }
822
856
  /**
823
857
  * 校验一份 compat 字典对指定协议合法(对齐官方门控语义 + schema 值约束):
824
- * - 未知键/withhold 字段拒绝(官方写时拒绝,替代旧版静默丢弃);
858
+ * - 未知键/withhold 字段拒绝(与官方写时拒绝同款);
825
859
  * - 值类型/枚举按官方 schema 校验;
826
860
  * - 无值键(null/undefined)拒绝(官方 assertOfferedCompatFields 同款)。
827
861
  */
@@ -1527,7 +1561,6 @@ function declaredModalities(configured) {
1527
1561
  /** 物化单个模型目录条目:官方 base 在下,条目显式字段逐字段覆盖。 */
1528
1562
  function materializeModel$1(route, entry, deps) {
1529
1563
  if (entry.reasoningEfforts !== void 0 || nonEmptyDict(entry.compat)) invalid$1(route, `model "${entry.id}" 的 reasoningEfforts/compat 仅 adapter: pi 可用`);
1530
- if (entry.imageDetail !== void 0) invalid$1(route, `model "${entry.id}" 的 imageDetail 已随 0.1.2-alpha.1 移除;请改用 imagePixelBudget/imageMaxBytes(低细节可配更小的 imagePixelBudget)并删除 imageDetail`);
1531
1564
  const base = resolveOfficialBase(route, entry, deps);
1532
1565
  const input = declaredModalities(entry.input) ?? base.input;
1533
1566
  const imageFields = {
@@ -1588,7 +1621,7 @@ function rawAdapterConfig(profile, baseURL, models) {
1588
1621
  /** 物化单个 deepseek route;lenient 下失败告警并返回 null(调用方跳过注册)。 */
1589
1622
  function buildRoute(route, profile, deps) {
1590
1623
  try {
1591
- if (deps.kit.deepseek === void 0) invalid$1(route, "使用 adapter: deepseek,但当前运行时套件不含 dsh-llm-deepseek(0.1.2-alpha.1 基线)");
1624
+ if (deps.kit.deepseek === void 0) invalid$1(route, "使用 adapter: deepseek,但当前运行时套件不含 dsh-llm-deepseek(本仓基线的必需套件)");
1592
1625
  rejectPiOnlyFields(route, profile);
1593
1626
  if (profile.extends !== void 0 && profile.extends !== "deepseek") invalid$1(route, `的 extends ${JSON.stringify(profile.extends)} 非法:adapter: deepseek 仅支持 "deepseek"(官方目录)`);
1594
1627
  if (profile.apiKeyEnv === void 0 || profile.apiKeyEnv.length === 0) invalid$1(route, "需要 apiKeyEnv:DeepSeekAdapter 无环境自发现,凭据引用必须显式配置");
@@ -1625,7 +1658,7 @@ function buildDeepseekRoutes(providers, deps) {
1625
1658
  //#endregion
1626
1659
  //#region src/profiles.ts
1627
1660
  /**
1628
- * profile 构建器(0.1.2-alpha.2 复核兼容):插件 Config → 官方 schema 形状的
1661
+ * profile 构建器:插件 Config → 官方 schema 形状的
1629
1662
  * 「前置数据源归一化」→ 官方 resolveProfiles(或插件等价兜底)→
1630
1663
  * ResolvedPiAiProviderProfile。
1631
1664
  *
@@ -1740,7 +1773,6 @@ function reasoningEffortsFromBase(provider, entry, base) {
1740
1773
  * 返回 null(调用方跳过该模型)。
1741
1774
  */
1742
1775
  function materializeModel(route, profile, entry, base, routeApi, defaultInput, deps) {
1743
- if (entry.imageDetail !== void 0) invalid(route, `model "${entry.id}" 的 imageDetail 已随 0.1.2-alpha.1 移除;请改用 imagePixelBudget/imageMaxBytes(仅 adapter: deepseek)并删除 imageDetail`);
1744
1776
  const api = profile.api ?? base.api ?? routeApi;
1745
1777
  if (api === void 0) {
1746
1778
  if (deps.lenient) {
@@ -2434,6 +2466,20 @@ function createOfficialCopyWriter(deps) {
2434
2466
  status: () => status
2435
2467
  };
2436
2468
  }
2469
+ //#endregion
2470
+ //#region src/auth-inline.ts
2471
+ /**
2472
+ * PiAiAdapter 认证注入的内联实现(官方 llm-pi-ai/src/auth.ts 的等价移植)。
2473
+ *
2474
+ * 官方 credentialStoreFrom/authContextFrom 仅在 @deepseek-ai/dsh-llm-pi-ai 的
2475
+ * src/auth.ts 子路径导出(包根只导出 recordKeyFor),而 npm 发布形态不携带
2476
+ * src/(lib/index.js 单 bundle)——源码布局优先经 src 子路径走官方实现
2477
+ * (见 resolve-dsh.ts 探测),本文件在其余形态(npm 布局的 dsh 树 /
2478
+ * vendored 兜底)下按官方语义逐行等价实现,并经 assertKitShape 自检兜底。
2479
+ * recordKeyFor 由调用方注入(与 PiAiAdapter 同源模块的包根导出,保证记录
2480
+ * 键格式与官方写入方一致);记录作用域不手抄,由它现场推导(见 recordScopeOf)。
2481
+ * @module llm-pi/auth-inline
2482
+ */
2437
2483
  /** 官方 jsonImage:pi-ai 凭据里显式 undefined 成员 JSON 序列化为缺省/数组 null。 */
2438
2484
  function jsonImage(value) {
2439
2485
  if (Array.isArray(value)) return value.map((entry) => entry === void 0 ? null : jsonImage(entry));
@@ -2472,12 +2518,26 @@ function writableStore(ctx) {
2472
2518
  if (credentials === void 0) throw new LlmError("llm-pi: 当前组合未挂载凭据服务(dsh-credentials-local),登录产生的凭据无处存储", "NO_CREDENTIAL_STORE");
2473
2519
  return credentials;
2474
2520
  }
2521
+ /** 作用域推导的探针 provider id(合法记录段,故 recordKeyFor 给出完整键)。 */
2522
+ const RECORD_SCOPE_PROBE_ID = "record-scope-probe";
2523
+ /**
2524
+ * 记录作用域的现场推导(**不手抄**官方常量):`recordKeyFor` 由与 PiAiAdapter
2525
+ * 同源的官方包根注入,其产物 `scope/id` 的 scope 段就是官方写入方使用的作用域。
2526
+ * 官方改作用域时此处自动跟随——硬编码镜像一旦落后,{@link credentialStoreFrom}
2527
+ * 的 list() 会把本插件自己的登录记录判成他人的而凭空消失,且无任何报错
2528
+ * (与 compat 门控表手抄镜像同一类故障,见 docs/repo/事故记录.md)。
2529
+ */
2530
+ function recordScopeOf(recordKeyFor) {
2531
+ return credentialKeyScope(recordKeyFor(RECORD_SCOPE_PROBE_ID));
2532
+ }
2475
2533
  /**
2476
2534
  * pi-ai `CredentialStore`(官方 credentialStoreFrom 等价实现)。
2477
2535
  * 路由键任意而记录 id 受语法约束:语法外的 id 读答"未存储"、删除无事可做、
2478
- * modify 拒绝(写不落地不能报成功)。
2536
+ * modify 拒绝(写不落地不能报成功)。list() 的归属判定用现场推导的作用域
2537
+ * ({@link recordScopeOf}),不比对硬编码常量。
2479
2538
  */
2480
2539
  function credentialStoreFrom(ctx, recordKeyFor) {
2540
+ const recordScope = recordScopeOf(recordKeyFor);
2481
2541
  return {
2482
2542
  async read(providerId) {
2483
2543
  const credentials = ctx.get("credentials");
@@ -2489,7 +2549,7 @@ function credentialStoreFrom(ctx, recordKeyFor) {
2489
2549
  const stored = await ctx.get("credentials")?.listRecords() ?? [];
2490
2550
  const mine = [];
2491
2551
  for (const entry of stored) {
2492
- if (credentialKeyScope(entry.key) !== "llm-pi-ai") continue;
2552
+ if (credentialKeyScope(entry.key) !== recordScope) continue;
2493
2553
  mine.push({
2494
2554
  providerId: credentialKeyId(entry.key),
2495
2555
  type: entry.kind === "api-key" ? "api_key" : "oauth"
@@ -2558,15 +2618,40 @@ function authContextFrom(ctx) {
2558
2618
  * 模型条目字段集同样现场推导(official-surface.ts)。生效版本只用于状态行显示,
2559
2619
  * 超出已验证区间也只是提示(kit-meta.ts),永不阻断。
2560
2620
  *
2561
- * 0.1.2-alpha 线的新面(0.1.2-alpha.2 复核包根仍未导出、仅 src 子路径有):
2562
- * - resolveProfiles(config.ts):官方解析链,dev 布局树可经 src 子路径复用;
2621
+ * 仅 src 子路径导出、包根不导出的两块面:
2622
+ * - resolveProfiles(config.ts):官方解析链,源码布局可经 src 子路径复用;
2563
2623
  * - credentialStoreFrom/authContextFrom(auth.ts):PiAiAdapter 必需 auth 注入。
2564
2624
  * npm 发布形态(lib/index.js 单 bundle + 无 src/)两者都拿不到:dsh 树优先
2565
2625
  * 探测 src/*(含 lib/* 候选),未命中时 resolveProfiles 走插件等价实现
2566
- * (profiles.ts,门控表对齐官方 catalog.ts)、auth 走内联等价实现
2567
- * (auth-inline.ts,recordKeyFor 自包根导出同源注入)——形状自检兜底。
2626
+ * (profiles.ts,语义对齐官方 config.ts:resolveProfiles)、auth 走内联等价实现
2627
+ * (auth-inline.ts,recordKeyFor 自包根导出同源注入、记录作用域由它现场推导)。
2628
+ * 这是**打包形态的恒定事实而非降级**,故按 info 档记入诊断(missingSourceNote),
2629
+ * 免得淹没同面板里真正的降级;等价实现由形状自检与单测兜底。
2568
2630
  * @module llm-pi/resolve-dsh
2569
2631
  */
2632
+ /** 生成一条降级诊断(推导失败 / 加载回退)。 */
2633
+ function degradation(message) {
2634
+ return {
2635
+ level: "degradation",
2636
+ message
2637
+ };
2638
+ }
2639
+ /**
2640
+ * 官方 `src/` 子路径未随 npm 发布携带时的**形态说明**(info 档)。
2641
+ *
2642
+ * 官方 `resolveProfiles`(src/config.ts)与认证助手(src/auth.ts)只在子路径导出、
2643
+ * 包根不导出,而发布清单只含 `lib/index.js` + `lib/types/**`——任何 npm 安装形态都
2644
+ * 拿不到,故这两块常走插件等价实现。此处逐面列出实际走等价实现的部分,并点明
2645
+ * **仍取自官方现场推导**的部分(compat 门控 / 协议 / 模型字段集),避免被读成
2646
+ * "自动跟随失败"。
2647
+ * @param surfaces 走等价实现的官方面名称(如 `profile 解析`)。
2648
+ */
2649
+ function missingSourceNote(surfaces) {
2650
+ return {
2651
+ level: "info",
2652
+ message: `官方 @deepseek-ai/dsh-llm-pi-ai 只经 src/ 子路径导出、npm 发布形态不携带 src/:${surfaces.join("、")}使用插件等价实现;compat 门控 / 协议集合 / 模型条目字段集仍取自官方安装副本现场推导`
2653
+ };
2654
+ }
2570
2655
  /** kit 必备形状清单:缺失即视为上游不兼容。 */
2571
2656
  function assertKitShape(kit, origin) {
2572
2657
  const problems = [];
@@ -2592,6 +2677,7 @@ function assertKitShape(kit, origin) {
2592
2677
  if (typeof kit.resolveProfiles !== "function") problems.push("resolveProfiles 缺失");
2593
2678
  if (typeof kit.resolveImageAttachmentAccess !== "function") problems.push("resolveImageAttachmentAccess 缺失");
2594
2679
  if (typeof kit.auth?.credentialStoreFrom !== "function" || typeof kit.auth?.authContextFrom !== "function") problems.push("auth 助手(credentialStoreFrom/authContextFrom)缺失");
2680
+ if (typeof kit.recordKeyFor !== "function") problems.push("recordKeyFor 缺失");
2595
2681
  if (problems.length > 0) throw new Error(`llm-pi: ${origin} 来源的运行时套件形状不兼容:${problems.join(";")}`);
2596
2682
  }
2597
2683
  /** deepseek 模块形状自检;返回问题清单(空 = 可用),由调用方决定降级。 */
@@ -2696,7 +2782,7 @@ async function loadFallbackTreeFactories(root, diagnostics) {
2696
2782
  if (factory !== void 0) out[api] = factory;
2697
2783
  } catch {}
2698
2784
  }
2699
- if (Object.keys(out).length < fallbackProtocols().length) diagnostics.push(`pi-ai 协议兜底三元组未能全部加载(${Object.keys(out).join("/") || "空"});请检查 dsh 安装树里 @earendil-works/pi-ai/dist/api 的形态`);
2785
+ if (Object.keys(out).length < fallbackProtocols().length) diagnostics.push(degradation(`pi-ai 协议兜底三元组未能全部加载(${Object.keys(out).join("/") || "空"});请检查 dsh 安装树里 @earendil-works/pi-ai/dist/api 的形态`));
2700
2786
  return out;
2701
2787
  }
2702
2788
  /**
@@ -2709,25 +2795,25 @@ async function loadTreeProtocols(root, derived, diagnostics) {
2709
2795
  for (const api of derived) {
2710
2796
  const file = join(dir, `${api}.lazy.js`);
2711
2797
  if (!existsSync(file)) {
2712
- diagnostics.push(`pi-ai 未提供协议 "${api}" 的惰性实现(缺 ${file});该协议不可用,其余协议不受影响`);
2798
+ diagnostics.push(degradation(`pi-ai 未提供协议 "${api}" 的惰性实现(缺 ${file});该协议不可用,其余协议不受影响`));
2713
2799
  continue;
2714
2800
  }
2715
2801
  try {
2716
2802
  const factory = protocolFactoryOf(await import(pathToFileURL(file).href));
2717
2803
  if (factory === void 0) {
2718
- diagnostics.push(`协议 "${api}" 的实现模块未导出唯一 *Api 工厂;该协议不可用`);
2804
+ diagnostics.push(degradation(`协议 "${api}" 的实现模块未导出唯一 *Api 工厂;该协议不可用`));
2719
2805
  continue;
2720
2806
  }
2721
2807
  out[api] = factory;
2722
2808
  } catch (error) {
2723
- diagnostics.push(`协议 "${api}" 的实现加载失败(${error instanceof Error ? error.message : String(error)});该协议不可用`);
2809
+ diagnostics.push(degradation(`协议 "${api}" 的实现加载失败(${error instanceof Error ? error.message : String(error)});该协议不可用`));
2724
2810
  }
2725
2811
  }
2726
2812
  if (Object.keys(out).length > 0) return {
2727
2813
  protocols: derived.filter((api) => out[api] !== void 0),
2728
2814
  protocolFactories: out
2729
2815
  };
2730
- diagnostics.push("官方声明的协议一个都加载不到;回退内置三元组");
2816
+ diagnostics.push(degradation("官方声明的协议一个都加载不到;回退内置三元组"));
2731
2817
  const fallback = await loadFallbackTreeFactories(root, diagnostics);
2732
2818
  return {
2733
2819
  protocols: fallbackProtocols().filter((api) => fallback[api] !== void 0),
@@ -2773,7 +2859,7 @@ function installOfficialSurface(options) {
2773
2859
  try {
2774
2860
  const gates = deriveGates(readFileSync(bundlePath, "utf8"));
2775
2861
  if (gates === void 0) {
2776
- diagnostics.push(`${compatLabel}未能从官方 bundle 解析 COMPAT_GATES(${bundlePath});使用内置快照(未知字段放行、withhold 仍拒绝;请检查 dsh-llm-pi-ai 打包形态)`);
2862
+ diagnostics.push(degradation(`${compatLabel}未能从官方 bundle 解析 COMPAT_GATES(${bundlePath});使用内置快照(未知字段放行、withhold 仍拒绝;请检查 dsh-llm-pi-ai 打包形态)`));
2777
2863
  installCompatTable(FALLBACK_TABLE);
2778
2864
  } else installCompatTable({
2779
2865
  gates,
@@ -2785,13 +2871,13 @@ function installOfficialSurface(options) {
2785
2871
  });
2786
2872
  } catch (error) {
2787
2873
  const reason = error instanceof Error ? error.message : String(error);
2788
- diagnostics.push(`${compatLabel}推导失败(${reason});使用内置快照(未知字段放行)`);
2874
+ diagnostics.push(degradation(`${compatLabel}推导失败(${reason});使用内置快照(未知字段放行)`));
2789
2875
  installCompatTable(FALLBACK_TABLE);
2790
2876
  }
2791
2877
  const derivedProtocols = deriveProtocols(adapterModule);
2792
- if (derivedProtocols === void 0) diagnostics.push(`${origin}:官方 supportedProtocols() 不可用;协议集合回退内置三元组 ${fallbackProtocols().join("/")}`);
2878
+ if (derivedProtocols === void 0) diagnostics.push(degradation(`${origin}:官方 supportedProtocols() 不可用;协议集合回退内置三元组 ${fallbackProtocols().join("/")}`));
2793
2879
  const fields = deriveModelEntryFields(adapterModule);
2794
- if (fields === void 0) diagnostics.push(`${origin}:官方 Config schema 的模型条目字段集不可推导;继承字段回退已知键 ${FALLBACK_MODEL_ENTRY_FIELDS.join("/")}(官方新增字段可能不被继承)`);
2880
+ if (fields === void 0) diagnostics.push(degradation(`${origin}:官方 Config schema 的模型条目字段集不可推导;继承字段回退已知键 ${FALLBACK_MODEL_ENTRY_FIELDS.join("/")}(官方新增字段可能不被继承)`));
2795
2881
  const generatedAt = catalogGeneratedAtOf(options.catalogModule);
2796
2882
  return {
2797
2883
  protocols: derivedProtocols ?? fallbackProtocols(),
@@ -2833,7 +2919,8 @@ function kitFromTree(options) {
2833
2919
  credentialStoreFrom: officialCredentialStoreFrom !== void 0 ? officialCredentialStoreFrom : (ctx) => credentialStoreFrom(ctx, recordKeyFor),
2834
2920
  authContextFrom: officialAuthContextFrom !== void 0 ? officialAuthContextFrom : authContextFrom
2835
2921
  },
2836
- resolveImageAttachmentAccess: mods.llm["resolveImageAttachmentAccess"]
2922
+ resolveImageAttachmentAccess: mods.llm["resolveImageAttachmentAccess"],
2923
+ recordKeyFor
2837
2924
  };
2838
2925
  }
2839
2926
  /** vendored 兜底副本的 deepseek 模块(形状自检不过时返回 undefined)。 */
@@ -2867,7 +2954,7 @@ function vendoredProtocolFactories() {
2867
2954
  * npm 发布形态不携带 src/、lib 仅 index.js/invariant.js——resolveProfiles 与
2868
2955
  * auth 助手在此无条件走插件等价实现(内联,见文件头说明);compat 门控表则
2869
2956
  * 仍从 vendored 副本的 bundle + Config schema 现场推导(与 dsh 树同一推导链)。
2870
- * @param diagnostics 可选诊断收集器(回退原因与逐项降级说明)。
2957
+ * @param diagnostics 可选诊断收集器(回退原因、形态说明与逐项降级)。
2871
2958
  */
2872
2959
  function loadVendoredKit(diagnostics = []) {
2873
2960
  const staticFactories = vendoredProtocolFactories();
@@ -2913,9 +3000,11 @@ function loadVendoredKit(diagnostics = []) {
2913
3000
  authContextFrom
2914
3001
  },
2915
3002
  resolveImageAttachmentAccess: vendoredLlm.resolveImageAttachmentAccess,
3003
+ recordKeyFor: vendoredPiAiAdapter.recordKeyFor,
2916
3004
  ...deepseek === void 0 ? {} : { deepseek }
2917
3005
  };
2918
3006
  assertKitShape(kit, "vendored");
3007
+ diagnostics.push(missingSourceNote(["profile 解析", "认证助手"]));
2919
3008
  return kit;
2920
3009
  }
2921
3010
  /**
@@ -2939,7 +3028,8 @@ function dshTreeAnchor(installAnchor, argvEntry = process.argv[1]) {
2939
3028
  /**
2940
3029
  * 解析运行时套件:优先 dsh 安装树(自动跟随上游),失败回退 vendored 副本;
2941
3030
  * 两者都过不了形状自检时抛错(调用方应记日志并放弃注册 route)。
2942
- * 返回的 diagnostics 记录回退原因与逐项降级说明,供配置卡片与日志展示。
3031
+ * 返回的 diagnostics 记录回退原因、打包形态说明(info)与逐项降级(degradation),
3032
+ * 供配置卡片与日志分档展示。
2943
3033
  * @param installAnchor profile 的 dsh 安装锚点(`ctx.profileContext.installAnchor`)。
2944
3034
  */
2945
3035
  async function resolveDshKit(installAnchor) {
@@ -2969,8 +3059,10 @@ async function resolveDshKit(installAnchor) {
2969
3059
  versions: collectTreeVersions(anchor)
2970
3060
  });
2971
3061
  assertKitShape(kit, "dsh-tree");
2972
- if (auth === void 0) diagnostics.push("dsh 树不含 dsh-llm-pi-ai/src(npm 发布形态);认证助手使用插件内联等价实现");
2973
- if (config === void 0) diagnostics.push("dsh 树不含 dsh-llm-pi-ai/src(npm 发布形态);profile 解析使用插件等价实现(compat 门控对齐官方 catalog.ts)");
3062
+ const missingSeams = [];
3063
+ if (config === void 0) missingSeams.push("profile 解析");
3064
+ if (auth === void 0) missingSeams.push("认证助手");
3065
+ if (missingSeams.length > 0) diagnostics.push(missingSourceNote(missingSeams));
2974
3066
  const tree = await importTreeDeepseek(anchor);
2975
3067
  if (tree.kit !== void 0) return {
2976
3068
  kit: {
@@ -2979,17 +3071,17 @@ async function resolveDshKit(installAnchor) {
2979
3071
  },
2980
3072
  diagnostics
2981
3073
  };
2982
- diagnostics.push(`dsh 安装树的 dsh-llm-deepseek 不可用(${tree.problem ?? "未知原因"});adapter: deepseek 的 route 不可用,pi 路由不受影响`);
3074
+ diagnostics.push(degradation(`dsh 安装树的 dsh-llm-deepseek 不可用(${tree.problem ?? "未知原因"});adapter: deepseek 的 route 不可用,pi 路由不受影响`));
2983
3075
  return {
2984
3076
  kit,
2985
3077
  diagnostics
2986
3078
  };
2987
3079
  } catch (error) {
2988
- diagnostics.push(`dsh 安装树套件不可用(${anchor}):${error instanceof Error ? error.message : String(error)};回退 vendored 副本`);
3080
+ diagnostics.push(degradation(`dsh 安装树套件不可用(${anchor}):${error instanceof Error ? error.message : String(error)};回退 vendored 副本`));
2989
3081
  }
2990
- } else diagnostics.push("未能从 profileContext.installAnchor / process.argv[1] 定位 dsh 安装树;回退 vendored 副本");
3082
+ } else diagnostics.push(degradation("未能从 profileContext.installAnchor / process.argv[1] 定位 dsh 安装树;回退 vendored 副本"));
2991
3083
  const kit = loadVendoredKit(diagnostics);
2992
- if (kit.deepseek === void 0) diagnostics.push("vendored 副本的 dsh-llm-deepseek 形状不兼容;adapter: deepseek 的 route 不可用");
3084
+ if (kit.deepseek === void 0) diagnostics.push(degradation("vendored 副本的 dsh-llm-deepseek 形状不兼容;adapter: deepseek 的 route 不可用"));
2993
3085
  return {
2994
3086
  kit,
2995
3087
  diagnostics
@@ -3016,10 +3108,11 @@ function makeResolveApiKey(ctx, kit) {
3016
3108
  throw new kit.LlmError(`llm-pi: provider route "${provider}" 的凭据引用 ${String(ref)} 未解析到值——请经凭据服务(web Models 页)存放或导出环境变量;仅当该 provider 应使用 pi-ai 自有环境发现时才移除 apiKeyEnv`, "MISSING_CREDENTIAL");
3017
3109
  };
3018
3110
  }
3019
- /** 解析运行期套件并落启动日志(来源/生效版本/协议/逐项降级诊断)。 */
3111
+ /** 解析运行期套件并落启动日志(来源/生效版本/协议/形态说明与逐项降级)。 */
3020
3112
  async function loadRuntimeKit(ctx, logger) {
3021
3113
  const { kit, diagnostics } = await resolveDshKit(ctx.get("profileContext")?.installAnchor);
3022
- for (const line of diagnostics) logger.warn(line);
3114
+ for (const item of diagnostics) if (item.level === "info") logger.info(item.message);
3115
+ else logger.warn(item.message);
3023
3116
  logger.info(`运行时套件来源:${kit.source};pi-ai ${kit.versions.piAi ?? "?"} / dsh-llm-pi-ai ${kit.versions.piAiAdapter ?? "?"};协议 ${kit.protocols.join("/")}`);
3024
3117
  const versionNotice = piAiVersionNotice(kit.versions.piAi);
3025
3118
  if (versionNotice !== void 0) logger.warn(versionNotice);
@@ -3285,8 +3378,8 @@ async function startRuntime(ctx, rawConfig) {
3285
3378
  officialCopy: copy,
3286
3379
  diagnostics: [
3287
3380
  ...diagnostics,
3288
- ...copy.warnings.map((warning) => `应急副本:${warning}`),
3289
- ...copy.error === void 0 ? [] : [`应急副本:生成失败(${copy.error})`]
3381
+ ...copy.warnings.map((warning) => degradation(`应急副本:${warning}`)),
3382
+ ...copy.error === void 0 ? [] : [degradation(`应急副本:生成失败(${copy.error})`)]
3290
3383
  ]
3291
3384
  };
3292
3385
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-plus/llm-pi",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "dsh-plus service+ui plugin: 基于 PiAiAdapter 的自定义 LLM 路由(全量 compat、模型继承、pi-ai 内置目录;协议/compat/字段集现场推导,自动跟随已装 dsh)",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "yaml": "^2.9.0",
35
- "@dsh-plus/shared": "0.1.35"
35
+ "@dsh-plus/shared": "0.1.37"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@deepseek-ai/cordis": "^4.0.5-alpha.1",