@dsh-plus/llm-pi 0.2.1 → 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 +30 -10
- package/lib/index.js +88 -27
- package/package.json +2 -2
- package/src/auth-inline.ts +21 -8
- package/src/client/api.ts +8 -1
- package/src/client/i18n.ts +4 -2
- package/src/client/root-fields.tsx +29 -5
- package/src/resolve-dsh.ts +106 -42
- package/src/service.ts +14 -8
package/lib/client.js
CHANGED
|
@@ -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)(
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3022
|
+
runtimeFormNotes: "Packaging notes",
|
|
3023
|
+
runtimeDiagnostics: "Degradations",
|
|
3004
3024
|
runtimeVersionUnknown: "unknown",
|
|
3005
3025
|
runtimeCatalogUnit: "providers /",
|
|
3006
3026
|
runtimeCatalogModels: "models",
|
package/lib/index.js
CHANGED
|
@@ -2466,6 +2466,20 @@ function createOfficialCopyWriter(deps) {
|
|
|
2466
2466
|
status: () => status
|
|
2467
2467
|
};
|
|
2468
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
|
+
*/
|
|
2469
2483
|
/** 官方 jsonImage:pi-ai 凭据里显式 undefined 成员 JSON 序列化为缺省/数组 null。 */
|
|
2470
2484
|
function jsonImage(value) {
|
|
2471
2485
|
if (Array.isArray(value)) return value.map((entry) => entry === void 0 ? null : jsonImage(entry));
|
|
@@ -2504,12 +2518,26 @@ function writableStore(ctx) {
|
|
|
2504
2518
|
if (credentials === void 0) throw new LlmError("llm-pi: 当前组合未挂载凭据服务(dsh-credentials-local),登录产生的凭据无处存储", "NO_CREDENTIAL_STORE");
|
|
2505
2519
|
return credentials;
|
|
2506
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
|
+
}
|
|
2507
2533
|
/**
|
|
2508
2534
|
* pi-ai `CredentialStore`(官方 credentialStoreFrom 等价实现)。
|
|
2509
2535
|
* 路由键任意而记录 id 受语法约束:语法外的 id 读答"未存储"、删除无事可做、
|
|
2510
|
-
* modify 拒绝(写不落地不能报成功)。
|
|
2536
|
+
* modify 拒绝(写不落地不能报成功)。list() 的归属判定用现场推导的作用域
|
|
2537
|
+
* ({@link recordScopeOf}),不比对硬编码常量。
|
|
2511
2538
|
*/
|
|
2512
2539
|
function credentialStoreFrom(ctx, recordKeyFor) {
|
|
2540
|
+
const recordScope = recordScopeOf(recordKeyFor);
|
|
2513
2541
|
return {
|
|
2514
2542
|
async read(providerId) {
|
|
2515
2543
|
const credentials = ctx.get("credentials");
|
|
@@ -2521,7 +2549,7 @@ function credentialStoreFrom(ctx, recordKeyFor) {
|
|
|
2521
2549
|
const stored = await ctx.get("credentials")?.listRecords() ?? [];
|
|
2522
2550
|
const mine = [];
|
|
2523
2551
|
for (const entry of stored) {
|
|
2524
|
-
if (credentialKeyScope(entry.key) !==
|
|
2552
|
+
if (credentialKeyScope(entry.key) !== recordScope) continue;
|
|
2525
2553
|
mine.push({
|
|
2526
2554
|
providerId: credentialKeyId(entry.key),
|
|
2527
2555
|
type: entry.kind === "api-key" ? "api_key" : "oauth"
|
|
@@ -2591,14 +2619,39 @@ function authContextFrom(ctx) {
|
|
|
2591
2619
|
* 超出已验证区间也只是提示(kit-meta.ts),永不阻断。
|
|
2592
2620
|
*
|
|
2593
2621
|
* 仅 src 子路径导出、包根不导出的两块面:
|
|
2594
|
-
* - resolveProfiles(config.ts
|
|
2622
|
+
* - resolveProfiles(config.ts):官方解析链,源码布局可经 src 子路径复用;
|
|
2595
2623
|
* - credentialStoreFrom/authContextFrom(auth.ts):PiAiAdapter 必需 auth 注入。
|
|
2596
2624
|
* npm 发布形态(lib/index.js 单 bundle + 无 src/)两者都拿不到:dsh 树优先
|
|
2597
2625
|
* 探测 src/*(含 lib/* 候选),未命中时 resolveProfiles 走插件等价实现
|
|
2598
|
-
* (profiles.ts
|
|
2599
|
-
* (auth-inline.ts,recordKeyFor
|
|
2626
|
+
* (profiles.ts,语义对齐官方 config.ts:resolveProfiles)、auth 走内联等价实现
|
|
2627
|
+
* (auth-inline.ts,recordKeyFor 自包根导出同源注入、记录作用域由它现场推导)。
|
|
2628
|
+
* 这是**打包形态的恒定事实而非降级**,故按 info 档记入诊断(missingSourceNote),
|
|
2629
|
+
* 免得淹没同面板里真正的降级;等价实现由形状自检与单测兜底。
|
|
2600
2630
|
* @module llm-pi/resolve-dsh
|
|
2601
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
|
+
}
|
|
2602
2655
|
/** kit 必备形状清单:缺失即视为上游不兼容。 */
|
|
2603
2656
|
function assertKitShape(kit, origin) {
|
|
2604
2657
|
const problems = [];
|
|
@@ -2624,6 +2677,7 @@ function assertKitShape(kit, origin) {
|
|
|
2624
2677
|
if (typeof kit.resolveProfiles !== "function") problems.push("resolveProfiles 缺失");
|
|
2625
2678
|
if (typeof kit.resolveImageAttachmentAccess !== "function") problems.push("resolveImageAttachmentAccess 缺失");
|
|
2626
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 缺失");
|
|
2627
2681
|
if (problems.length > 0) throw new Error(`llm-pi: ${origin} 来源的运行时套件形状不兼容:${problems.join(";")}`);
|
|
2628
2682
|
}
|
|
2629
2683
|
/** deepseek 模块形状自检;返回问题清单(空 = 可用),由调用方决定降级。 */
|
|
@@ -2728,7 +2782,7 @@ async function loadFallbackTreeFactories(root, diagnostics) {
|
|
|
2728
2782
|
if (factory !== void 0) out[api] = factory;
|
|
2729
2783
|
} catch {}
|
|
2730
2784
|
}
|
|
2731
|
-
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 的形态`));
|
|
2732
2786
|
return out;
|
|
2733
2787
|
}
|
|
2734
2788
|
/**
|
|
@@ -2741,25 +2795,25 @@ async function loadTreeProtocols(root, derived, diagnostics) {
|
|
|
2741
2795
|
for (const api of derived) {
|
|
2742
2796
|
const file = join(dir, `${api}.lazy.js`);
|
|
2743
2797
|
if (!existsSync(file)) {
|
|
2744
|
-
diagnostics.push(`pi-ai 未提供协议 "${api}" 的惰性实现(缺 ${file});该协议不可用,其余协议不受影响`);
|
|
2798
|
+
diagnostics.push(degradation(`pi-ai 未提供协议 "${api}" 的惰性实现(缺 ${file});该协议不可用,其余协议不受影响`));
|
|
2745
2799
|
continue;
|
|
2746
2800
|
}
|
|
2747
2801
|
try {
|
|
2748
2802
|
const factory = protocolFactoryOf(await import(pathToFileURL(file).href));
|
|
2749
2803
|
if (factory === void 0) {
|
|
2750
|
-
diagnostics.push(`协议 "${api}" 的实现模块未导出唯一 *Api 工厂;该协议不可用`);
|
|
2804
|
+
diagnostics.push(degradation(`协议 "${api}" 的实现模块未导出唯一 *Api 工厂;该协议不可用`));
|
|
2751
2805
|
continue;
|
|
2752
2806
|
}
|
|
2753
2807
|
out[api] = factory;
|
|
2754
2808
|
} catch (error) {
|
|
2755
|
-
diagnostics.push(`协议 "${api}" 的实现加载失败(${error instanceof Error ? error.message : String(error)});该协议不可用`);
|
|
2809
|
+
diagnostics.push(degradation(`协议 "${api}" 的实现加载失败(${error instanceof Error ? error.message : String(error)});该协议不可用`));
|
|
2756
2810
|
}
|
|
2757
2811
|
}
|
|
2758
2812
|
if (Object.keys(out).length > 0) return {
|
|
2759
2813
|
protocols: derived.filter((api) => out[api] !== void 0),
|
|
2760
2814
|
protocolFactories: out
|
|
2761
2815
|
};
|
|
2762
|
-
diagnostics.push("官方声明的协议一个都加载不到;回退内置三元组");
|
|
2816
|
+
diagnostics.push(degradation("官方声明的协议一个都加载不到;回退内置三元组"));
|
|
2763
2817
|
const fallback = await loadFallbackTreeFactories(root, diagnostics);
|
|
2764
2818
|
return {
|
|
2765
2819
|
protocols: fallbackProtocols().filter((api) => fallback[api] !== void 0),
|
|
@@ -2805,7 +2859,7 @@ function installOfficialSurface(options) {
|
|
|
2805
2859
|
try {
|
|
2806
2860
|
const gates = deriveGates(readFileSync(bundlePath, "utf8"));
|
|
2807
2861
|
if (gates === void 0) {
|
|
2808
|
-
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 打包形态)`));
|
|
2809
2863
|
installCompatTable(FALLBACK_TABLE);
|
|
2810
2864
|
} else installCompatTable({
|
|
2811
2865
|
gates,
|
|
@@ -2817,13 +2871,13 @@ function installOfficialSurface(options) {
|
|
|
2817
2871
|
});
|
|
2818
2872
|
} catch (error) {
|
|
2819
2873
|
const reason = error instanceof Error ? error.message : String(error);
|
|
2820
|
-
diagnostics.push(`${compatLabel}推导失败(${reason});使用内置快照(未知字段放行)`);
|
|
2874
|
+
diagnostics.push(degradation(`${compatLabel}推导失败(${reason});使用内置快照(未知字段放行)`));
|
|
2821
2875
|
installCompatTable(FALLBACK_TABLE);
|
|
2822
2876
|
}
|
|
2823
2877
|
const derivedProtocols = deriveProtocols(adapterModule);
|
|
2824
|
-
if (derivedProtocols === void 0) diagnostics.push(`${origin}:官方 supportedProtocols() 不可用;协议集合回退内置三元组 ${fallbackProtocols().join("/")}`);
|
|
2878
|
+
if (derivedProtocols === void 0) diagnostics.push(degradation(`${origin}:官方 supportedProtocols() 不可用;协议集合回退内置三元组 ${fallbackProtocols().join("/")}`));
|
|
2825
2879
|
const fields = deriveModelEntryFields(adapterModule);
|
|
2826
|
-
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("/")}(官方新增字段可能不被继承)`));
|
|
2827
2881
|
const generatedAt = catalogGeneratedAtOf(options.catalogModule);
|
|
2828
2882
|
return {
|
|
2829
2883
|
protocols: derivedProtocols ?? fallbackProtocols(),
|
|
@@ -2865,7 +2919,8 @@ function kitFromTree(options) {
|
|
|
2865
2919
|
credentialStoreFrom: officialCredentialStoreFrom !== void 0 ? officialCredentialStoreFrom : (ctx) => credentialStoreFrom(ctx, recordKeyFor),
|
|
2866
2920
|
authContextFrom: officialAuthContextFrom !== void 0 ? officialAuthContextFrom : authContextFrom
|
|
2867
2921
|
},
|
|
2868
|
-
resolveImageAttachmentAccess: mods.llm["resolveImageAttachmentAccess"]
|
|
2922
|
+
resolveImageAttachmentAccess: mods.llm["resolveImageAttachmentAccess"],
|
|
2923
|
+
recordKeyFor
|
|
2869
2924
|
};
|
|
2870
2925
|
}
|
|
2871
2926
|
/** vendored 兜底副本的 deepseek 模块(形状自检不过时返回 undefined)。 */
|
|
@@ -2899,7 +2954,7 @@ function vendoredProtocolFactories() {
|
|
|
2899
2954
|
* npm 发布形态不携带 src/、lib 仅 index.js/invariant.js——resolveProfiles 与
|
|
2900
2955
|
* auth 助手在此无条件走插件等价实现(内联,见文件头说明);compat 门控表则
|
|
2901
2956
|
* 仍从 vendored 副本的 bundle + Config schema 现场推导(与 dsh 树同一推导链)。
|
|
2902
|
-
* @param diagnostics
|
|
2957
|
+
* @param diagnostics 可选诊断收集器(回退原因、形态说明与逐项降级)。
|
|
2903
2958
|
*/
|
|
2904
2959
|
function loadVendoredKit(diagnostics = []) {
|
|
2905
2960
|
const staticFactories = vendoredProtocolFactories();
|
|
@@ -2945,9 +3000,11 @@ function loadVendoredKit(diagnostics = []) {
|
|
|
2945
3000
|
authContextFrom
|
|
2946
3001
|
},
|
|
2947
3002
|
resolveImageAttachmentAccess: vendoredLlm.resolveImageAttachmentAccess,
|
|
3003
|
+
recordKeyFor: vendoredPiAiAdapter.recordKeyFor,
|
|
2948
3004
|
...deepseek === void 0 ? {} : { deepseek }
|
|
2949
3005
|
};
|
|
2950
3006
|
assertKitShape(kit, "vendored");
|
|
3007
|
+
diagnostics.push(missingSourceNote(["profile 解析", "认证助手"]));
|
|
2951
3008
|
return kit;
|
|
2952
3009
|
}
|
|
2953
3010
|
/**
|
|
@@ -2971,7 +3028,8 @@ function dshTreeAnchor(installAnchor, argvEntry = process.argv[1]) {
|
|
|
2971
3028
|
/**
|
|
2972
3029
|
* 解析运行时套件:优先 dsh 安装树(自动跟随上游),失败回退 vendored 副本;
|
|
2973
3030
|
* 两者都过不了形状自检时抛错(调用方应记日志并放弃注册 route)。
|
|
2974
|
-
* 返回的 diagnostics
|
|
3031
|
+
* 返回的 diagnostics 记录回退原因、打包形态说明(info)与逐项降级(degradation),
|
|
3032
|
+
* 供配置卡片与日志分档展示。
|
|
2975
3033
|
* @param installAnchor profile 的 dsh 安装锚点(`ctx.profileContext.installAnchor`)。
|
|
2976
3034
|
*/
|
|
2977
3035
|
async function resolveDshKit(installAnchor) {
|
|
@@ -3001,8 +3059,10 @@ async function resolveDshKit(installAnchor) {
|
|
|
3001
3059
|
versions: collectTreeVersions(anchor)
|
|
3002
3060
|
});
|
|
3003
3061
|
assertKitShape(kit, "dsh-tree");
|
|
3004
|
-
|
|
3005
|
-
if (config === void 0)
|
|
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));
|
|
3006
3066
|
const tree = await importTreeDeepseek(anchor);
|
|
3007
3067
|
if (tree.kit !== void 0) return {
|
|
3008
3068
|
kit: {
|
|
@@ -3011,17 +3071,17 @@ async function resolveDshKit(installAnchor) {
|
|
|
3011
3071
|
},
|
|
3012
3072
|
diagnostics
|
|
3013
3073
|
};
|
|
3014
|
-
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 路由不受影响`));
|
|
3015
3075
|
return {
|
|
3016
3076
|
kit,
|
|
3017
3077
|
diagnostics
|
|
3018
3078
|
};
|
|
3019
3079
|
} catch (error) {
|
|
3020
|
-
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 副本`));
|
|
3021
3081
|
}
|
|
3022
|
-
} else diagnostics.push("未能从 profileContext.installAnchor / process.argv[1] 定位 dsh 安装树;回退 vendored 副本");
|
|
3082
|
+
} else diagnostics.push(degradation("未能从 profileContext.installAnchor / process.argv[1] 定位 dsh 安装树;回退 vendored 副本"));
|
|
3023
3083
|
const kit = loadVendoredKit(diagnostics);
|
|
3024
|
-
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 不可用"));
|
|
3025
3085
|
return {
|
|
3026
3086
|
kit,
|
|
3027
3087
|
diagnostics
|
|
@@ -3048,10 +3108,11 @@ function makeResolveApiKey(ctx, kit) {
|
|
|
3048
3108
|
throw new kit.LlmError(`llm-pi: provider route "${provider}" 的凭据引用 ${String(ref)} 未解析到值——请经凭据服务(web Models 页)存放或导出环境变量;仅当该 provider 应使用 pi-ai 自有环境发现时才移除 apiKeyEnv`, "MISSING_CREDENTIAL");
|
|
3049
3109
|
};
|
|
3050
3110
|
}
|
|
3051
|
-
/**
|
|
3111
|
+
/** 解析运行期套件并落启动日志(来源/生效版本/协议/形态说明与逐项降级)。 */
|
|
3052
3112
|
async function loadRuntimeKit(ctx, logger) {
|
|
3053
3113
|
const { kit, diagnostics } = await resolveDshKit(ctx.get("profileContext")?.installAnchor);
|
|
3054
|
-
for (const
|
|
3114
|
+
for (const item of diagnostics) if (item.level === "info") logger.info(item.message);
|
|
3115
|
+
else logger.warn(item.message);
|
|
3055
3116
|
logger.info(`运行时套件来源:${kit.source};pi-ai ${kit.versions.piAi ?? "?"} / dsh-llm-pi-ai ${kit.versions.piAiAdapter ?? "?"};协议 ${kit.protocols.join("/")}`);
|
|
3056
3117
|
const versionNotice = piAiVersionNotice(kit.versions.piAi);
|
|
3057
3118
|
if (versionNotice !== void 0) logger.warn(versionNotice);
|
|
@@ -3317,8 +3378,8 @@ async function startRuntime(ctx, rawConfig) {
|
|
|
3317
3378
|
officialCopy: copy,
|
|
3318
3379
|
diagnostics: [
|
|
3319
3380
|
...diagnostics,
|
|
3320
|
-
...copy.warnings.map((warning) => `应急副本:${warning}`),
|
|
3321
|
-
...copy.error === void 0 ? [] : [`应急副本:生成失败(${copy.error})`]
|
|
3381
|
+
...copy.warnings.map((warning) => degradation(`应急副本:${warning}`)),
|
|
3382
|
+
...copy.error === void 0 ? [] : [degradation(`应急副本:生成失败(${copy.error})`)]
|
|
3322
3383
|
]
|
|
3323
3384
|
};
|
|
3324
3385
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-plus/llm-pi",
|
|
3
|
-
"version": "0.2.
|
|
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
|
+
"@dsh-plus/shared": "0.1.37"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@deepseek-ai/cordis": "^4.0.5-alpha.1",
|
package/src/auth-inline.ts
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 官方 credentialStoreFrom/authContextFrom 仅在 @deepseek-ai/dsh-llm-pi-ai 的
|
|
5
5
|
* src/auth.ts 子路径导出(包根只导出 recordKeyFor),而 npm 发布形态不携带
|
|
6
|
-
* src/(lib/index.js 单 bundle
|
|
7
|
-
*
|
|
6
|
+
* src/(lib/index.js 单 bundle)——源码布局优先经 src 子路径走官方实现
|
|
7
|
+
* (见 resolve-dsh.ts 探测),本文件在其余形态(npm 布局的 dsh 树 /
|
|
8
8
|
* vendored 兜底)下按官方语义逐行等价实现,并经 assertKitShape 自检兜底。
|
|
9
9
|
* recordKeyFor 由调用方注入(与 PiAiAdapter 同源模块的包根导出,保证记录
|
|
10
|
-
*
|
|
10
|
+
* 键格式与官方写入方一致);记录作用域不手抄,由它现场推导(见 recordScopeOf)。
|
|
11
11
|
* @module llm-pi/auth-inline
|
|
12
12
|
*/
|
|
13
13
|
import { access } from 'node:fs/promises'
|
|
@@ -36,9 +36,6 @@ import type {
|
|
|
36
36
|
CredentialStore,
|
|
37
37
|
} from '@earendil-works/pi-ai'
|
|
38
38
|
|
|
39
|
-
/** 官方 RECORD_SCOPE 常量(llm-pi-ai/src/auth.ts)——读写作用域必须与官方一致。 */
|
|
40
|
-
export const INLINE_RECORD_SCOPE = 'llm-pi-ai'
|
|
41
|
-
|
|
42
39
|
/** 官方 jsonImage:pi-ai 凭据里显式 undefined 成员 JSON 序列化为缺省/数组 null。 */
|
|
43
40
|
function jsonImage(value: unknown): unknown {
|
|
44
41
|
if (Array.isArray(value))
|
|
@@ -94,15 +91,31 @@ function writableStore(ctx: Context): CredentialProvider {
|
|
|
94
91
|
return credentials
|
|
95
92
|
}
|
|
96
93
|
|
|
94
|
+
/** 作用域推导的探针 provider id(合法记录段,故 recordKeyFor 给出完整键)。 */
|
|
95
|
+
const RECORD_SCOPE_PROBE_ID = 'record-scope-probe'
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 记录作用域的现场推导(**不手抄**官方常量):`recordKeyFor` 由与 PiAiAdapter
|
|
99
|
+
* 同源的官方包根注入,其产物 `scope/id` 的 scope 段就是官方写入方使用的作用域。
|
|
100
|
+
* 官方改作用域时此处自动跟随——硬编码镜像一旦落后,{@link credentialStoreFrom}
|
|
101
|
+
* 的 list() 会把本插件自己的登录记录判成他人的而凭空消失,且无任何报错
|
|
102
|
+
* (与 compat 门控表手抄镜像同一类故障,见 docs/repo/事故记录.md)。
|
|
103
|
+
*/
|
|
104
|
+
function recordScopeOf(recordKeyFor: (providerId: string) => CredentialKey): string {
|
|
105
|
+
return credentialKeyScope(recordKeyFor(RECORD_SCOPE_PROBE_ID))
|
|
106
|
+
}
|
|
107
|
+
|
|
97
108
|
/**
|
|
98
109
|
* pi-ai `CredentialStore`(官方 credentialStoreFrom 等价实现)。
|
|
99
110
|
* 路由键任意而记录 id 受语法约束:语法外的 id 读答"未存储"、删除无事可做、
|
|
100
|
-
* modify 拒绝(写不落地不能报成功)。
|
|
111
|
+
* modify 拒绝(写不落地不能报成功)。list() 的归属判定用现场推导的作用域
|
|
112
|
+
* ({@link recordScopeOf}),不比对硬编码常量。
|
|
101
113
|
*/
|
|
102
114
|
export function credentialStoreFrom(
|
|
103
115
|
ctx: Context,
|
|
104
116
|
recordKeyFor: (providerId: string) => CredentialKey,
|
|
105
117
|
): CredentialStore {
|
|
118
|
+
const recordScope = recordScopeOf(recordKeyFor)
|
|
106
119
|
return {
|
|
107
120
|
async read(providerId) {
|
|
108
121
|
const credentials = ctx.get('credentials')
|
|
@@ -114,7 +127,7 @@ export function credentialStoreFrom(
|
|
|
114
127
|
const stored = (await ctx.get('credentials')?.listRecords()) ?? []
|
|
115
128
|
const mine: CredentialInfo[] = []
|
|
116
129
|
for (const entry of stored) {
|
|
117
|
-
if (credentialKeyScope(entry.key) !==
|
|
130
|
+
if (credentialKeyScope(entry.key) !== recordScope) continue
|
|
118
131
|
mine.push({
|
|
119
132
|
providerId: credentialKeyId(entry.key),
|
|
120
133
|
type: entry.kind === 'api-key' ? 'api_key' : 'oauth',
|
package/src/client/api.ts
CHANGED
|
@@ -26,7 +26,14 @@ export interface WireKitInfo {
|
|
|
26
26
|
warnings: string[]
|
|
27
27
|
error?: string
|
|
28
28
|
}
|
|
29
|
-
diagnostics:
|
|
29
|
+
diagnostics: WireDiagnostic[]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 套件诊断条目(服务端 KitDiagnostic 的镜像)。 */
|
|
33
|
+
export interface WireDiagnostic {
|
|
34
|
+
/** info = 打包形态说明(非缺陷);degradation = 回退与逐项降级。 */
|
|
35
|
+
level: 'info' | 'degradation'
|
|
36
|
+
message: string
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
/** 内置 provider 条目。 */
|
package/src/client/i18n.ts
CHANGED
|
@@ -27,7 +27,8 @@ const ownZh = {
|
|
|
27
27
|
officialCopyPending: '尚未生成',
|
|
28
28
|
officialCopyFailed: '生成失败:',
|
|
29
29
|
officialCopyRoutes: '个 route',
|
|
30
|
-
|
|
30
|
+
runtimeFormNotes: '形态说明',
|
|
31
|
+
runtimeDiagnostics: '降级诊断',
|
|
31
32
|
runtimeVersionUnknown: '未知',
|
|
32
33
|
runtimeCatalogUnit: '个 provider /',
|
|
33
34
|
runtimeCatalogModels: '个模型',
|
|
@@ -168,7 +169,8 @@ const ownEn = {
|
|
|
168
169
|
officialCopyPending: 'not generated yet',
|
|
169
170
|
officialCopyFailed: 'generation failed: ',
|
|
170
171
|
officialCopyRoutes: 'routes',
|
|
171
|
-
|
|
172
|
+
runtimeFormNotes: 'Packaging notes',
|
|
173
|
+
runtimeDiagnostics: 'Degradations',
|
|
172
174
|
runtimeVersionUnknown: 'unknown',
|
|
173
175
|
runtimeCatalogUnit: 'providers /',
|
|
174
176
|
runtimeCatalogModels: 'models',
|
|
@@ -3,15 +3,16 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 状态行回答的是"我现在到底在跑哪一份 pi-ai"——插件按文件路径动态加载已装 dsh 树
|
|
5
5
|
* 里的套件(见 resolve-dsh.ts),manifest 上写的版本**不是**生效版本。这里显示
|
|
6
|
-
* 生效版本、安装树、目录规模与数据日期、可服务协议、compat
|
|
7
|
-
*
|
|
6
|
+
* 生效版本、安装树、目录规模与数据日期、可服务协议、compat 表来源,以及诊断
|
|
7
|
+
* (按「形态说明 / 降级诊断」分块,避免恒定事实淹没真实降级);版本超出验证区间
|
|
8
|
+
* 时给出提示(提示而非阻断)。
|
|
8
9
|
* @module llm-pi/client/root-fields
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
12
|
import { CheckRow } from '@dsh-plus/shared/client'
|
|
12
13
|
import type { ReactElement } from 'react'
|
|
13
14
|
|
|
14
|
-
import type { WireKitInfo } from './api.ts'
|
|
15
|
+
import type { WireDiagnostic, WireKitInfo } from './api.ts'
|
|
15
16
|
import type { Draft } from './draft.ts'
|
|
16
17
|
import type { Translate } from './i18n.ts'
|
|
17
18
|
|
|
@@ -44,6 +45,25 @@ function officialCopyText(kit: WireKitInfo, t: Translate): string {
|
|
|
44
45
|
return `${copy.path} · ${copy.routes} ${t('officialCopyRoutes')} · ${formatTime(copy.updatedAt, t('runtimeVersionUnknown'))}`
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
/**
|
|
49
|
+
* 诊断分块:形态说明与降级分开渲染——「官方 src 不随 npm 发布」这类恒定事实
|
|
50
|
+
* 永不会消失,混在一起会把真正的降级淹掉。空块不渲染。
|
|
51
|
+
*/
|
|
52
|
+
function DiagnosticBlock(props: {
|
|
53
|
+
label: string
|
|
54
|
+
level: WireDiagnostic['level']
|
|
55
|
+
items: readonly WireDiagnostic[]
|
|
56
|
+
}): ReactElement | null {
|
|
57
|
+
const messages = props.items.filter((item) => item.level === props.level).map((m) => m.message)
|
|
58
|
+
if (messages.length === 0) return null
|
|
59
|
+
return (
|
|
60
|
+
<>
|
|
61
|
+
<p className="lpc-catDetailLabel">{props.label}</p>
|
|
62
|
+
<pre className="lpc-catJson">{messages.join('\n')}</pre>
|
|
63
|
+
</>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
47
67
|
export interface RootFieldsProps {
|
|
48
68
|
t: Translate
|
|
49
69
|
draft: Draft
|
|
@@ -96,8 +116,12 @@ export function RootFields(props: RootFieldsProps): ReactElement {
|
|
|
96
116
|
) : null}
|
|
97
117
|
{kit.diagnostics.length > 0 ? (
|
|
98
118
|
<>
|
|
99
|
-
<
|
|
100
|
-
<
|
|
119
|
+
<DiagnosticBlock label={t('runtimeFormNotes')} level="info" items={kit.diagnostics} />
|
|
120
|
+
<DiagnosticBlock
|
|
121
|
+
label={t('runtimeDiagnostics')}
|
|
122
|
+
level="degradation"
|
|
123
|
+
items={kit.diagnostics}
|
|
124
|
+
/>
|
|
101
125
|
</>
|
|
102
126
|
) : null}
|
|
103
127
|
</>
|
package/src/resolve-dsh.ts
CHANGED
|
@@ -20,12 +20,14 @@
|
|
|
20
20
|
* 超出已验证区间也只是提示(kit-meta.ts),永不阻断。
|
|
21
21
|
*
|
|
22
22
|
* 仅 src 子路径导出、包根不导出的两块面:
|
|
23
|
-
* - resolveProfiles(config.ts
|
|
23
|
+
* - resolveProfiles(config.ts):官方解析链,源码布局可经 src 子路径复用;
|
|
24
24
|
* - credentialStoreFrom/authContextFrom(auth.ts):PiAiAdapter 必需 auth 注入。
|
|
25
25
|
* npm 发布形态(lib/index.js 单 bundle + 无 src/)两者都拿不到:dsh 树优先
|
|
26
26
|
* 探测 src/*(含 lib/* 候选),未命中时 resolveProfiles 走插件等价实现
|
|
27
|
-
* (profiles.ts
|
|
28
|
-
* (auth-inline.ts,recordKeyFor
|
|
27
|
+
* (profiles.ts,语义对齐官方 config.ts:resolveProfiles)、auth 走内联等价实现
|
|
28
|
+
* (auth-inline.ts,recordKeyFor 自包根导出同源注入、记录作用域由它现场推导)。
|
|
29
|
+
* 这是**打包形态的恒定事实而非降级**,故按 info 档记入诊断(missingSourceNote),
|
|
30
|
+
* 免得淹没同面板里真正的降级;等价实现由形状自检与单测兜底。
|
|
29
31
|
* @module llm-pi/resolve-dsh
|
|
30
32
|
*/
|
|
31
33
|
import { existsSync, readFileSync, realpathSync } from 'node:fs'
|
|
@@ -106,6 +108,43 @@ export interface AuthHelpers {
|
|
|
106
108
|
authContextFrom(ctx: Context): AuthContext
|
|
107
109
|
}
|
|
108
110
|
|
|
111
|
+
/**
|
|
112
|
+
* 套件诊断条目,配置页按档分块展示:
|
|
113
|
+
* - `info`:当前打包形态下的**恒定事实**,不是缺陷——官方 `src/` 只经子路径导出、
|
|
114
|
+
* npm 发布形态不携带,本插件因此走等价实现(见 {@link missingSourceNote});
|
|
115
|
+
* - `degradation`:推导/加载真的降级了(回退快照、协议缺失、树不可用等)。
|
|
116
|
+
* 分档是为了不让恒定事实淹没真实降级。
|
|
117
|
+
*/
|
|
118
|
+
export interface KitDiagnostic {
|
|
119
|
+
level: 'info' | 'degradation'
|
|
120
|
+
message: string
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** 生成一条降级诊断(推导失败 / 加载回退)。 */
|
|
124
|
+
export function degradation(message: string): KitDiagnostic {
|
|
125
|
+
return { level: 'degradation', message }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 官方 `src/` 子路径未随 npm 发布携带时的**形态说明**(info 档)。
|
|
130
|
+
*
|
|
131
|
+
* 官方 `resolveProfiles`(src/config.ts)与认证助手(src/auth.ts)只在子路径导出、
|
|
132
|
+
* 包根不导出,而发布清单只含 `lib/index.js` + `lib/types/**`——任何 npm 安装形态都
|
|
133
|
+
* 拿不到,故这两块常走插件等价实现。此处逐面列出实际走等价实现的部分,并点明
|
|
134
|
+
* **仍取自官方现场推导**的部分(compat 门控 / 协议 / 模型字段集),避免被读成
|
|
135
|
+
* "自动跟随失败"。
|
|
136
|
+
* @param surfaces 走等价实现的官方面名称(如 `profile 解析`)。
|
|
137
|
+
*/
|
|
138
|
+
export function missingSourceNote(surfaces: readonly string[]): KitDiagnostic {
|
|
139
|
+
return {
|
|
140
|
+
level: 'info',
|
|
141
|
+
message:
|
|
142
|
+
'官方 @deepseek-ai/dsh-llm-pi-ai 只经 src/ 子路径导出、npm 发布形态不携带 src/:' +
|
|
143
|
+
`${surfaces.join('、')}使用插件等价实现;` +
|
|
144
|
+
'compat 门控 / 协议集合 / 模型条目字段集仍取自官方安装副本现场推导',
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
109
148
|
/** 插件运行期所需的全部上游模块表面(单一来源,内部一致)。 */
|
|
110
149
|
export interface DshKit {
|
|
111
150
|
/** 解析来源:dsh 安装树 / 插件 vendored 兜底副本。 */
|
|
@@ -140,16 +179,22 @@ export interface DshKit {
|
|
|
140
179
|
getBuiltinProviders: typeof vendoredCatalog.getBuiltinProviders
|
|
141
180
|
getBuiltinModels: typeof vendoredCatalog.getBuiltinModels
|
|
142
181
|
/**
|
|
143
|
-
* profile
|
|
144
|
-
* resolveProfiles;npm
|
|
182
|
+
* profile 解析链:官方源码布局(`src/config.ts` 存在,如上游工作区)时为官方
|
|
183
|
+
* resolveProfiles;npm 发布形态(不携带 src/,包根也不导出)与 vendored 兜底时
|
|
184
|
+
* 为插件等价实现——该形态事实按 info 档记入诊断(见 {@link missingSourceNote})。
|
|
145
185
|
*/
|
|
146
186
|
resolveProfiles: ResolveProfiles
|
|
147
187
|
/**
|
|
148
|
-
* PiAiAdapter 必需 auth
|
|
188
|
+
* PiAiAdapter 必需 auth 注入:官方源码布局(`src/auth.ts` 存在)时为官方
|
|
149
189
|
* credentialStoreFrom/authContextFrom;其余形态为内联等价实现
|
|
150
|
-
* (auth-inline.ts
|
|
190
|
+
* (auth-inline.ts,记录键与作用域均取自官方 recordKeyFor)。
|
|
151
191
|
*/
|
|
152
192
|
auth: AuthHelpers
|
|
193
|
+
/**
|
|
194
|
+
* 官方凭据记录键格式器(包根导出)。auth 助手按它对齐记录键,并**现场推导**
|
|
195
|
+
* 记录作用域(不再手抄官方常量,见 auth-inline.ts);缺失即形状不兼容。
|
|
196
|
+
*/
|
|
197
|
+
recordKeyFor: (providerId: string) => CredentialKey
|
|
153
198
|
/** 附件引用 → 当前模型工具执行世界的只读路径桥(官方 dsh-llm 导出)。 */
|
|
154
199
|
resolveImageAttachmentAccess: typeof DshLlm.resolveImageAttachmentAccess
|
|
155
200
|
/** deepseek 文件通道路由所需模块;同源自检失败时为 undefined(见 diagnostics)。 */
|
|
@@ -207,6 +252,7 @@ function assertKitShape(kit: DshKit, origin: string): void {
|
|
|
207
252
|
) {
|
|
208
253
|
problems.push('auth 助手(credentialStoreFrom/authContextFrom)缺失')
|
|
209
254
|
}
|
|
255
|
+
if (typeof kit.recordKeyFor !== 'function') problems.push('recordKeyFor 缺失')
|
|
210
256
|
if (problems.length > 0) {
|
|
211
257
|
throw new Error(`llm-pi: ${origin} 来源的运行时套件形状不兼容:${problems.join(';')}`)
|
|
212
258
|
}
|
|
@@ -343,7 +389,7 @@ function piAiApiDir(root: string): string {
|
|
|
343
389
|
/** 内置三元组的静态加载(协议推导失败或全部工厂加载失败时的兜底)。 */
|
|
344
390
|
async function loadFallbackTreeFactories(
|
|
345
391
|
root: string,
|
|
346
|
-
diagnostics:
|
|
392
|
+
diagnostics: KitDiagnostic[],
|
|
347
393
|
): Promise<Record<string, () => unknown>> {
|
|
348
394
|
const dir = piAiApiDir(root)
|
|
349
395
|
const out: Record<string, () => unknown> = {}
|
|
@@ -359,8 +405,10 @@ async function loadFallbackTreeFactories(
|
|
|
359
405
|
}
|
|
360
406
|
if (Object.keys(out).length < fallbackProtocols().length) {
|
|
361
407
|
diagnostics.push(
|
|
362
|
-
|
|
363
|
-
|
|
408
|
+
degradation(
|
|
409
|
+
`pi-ai 协议兜底三元组未能全部加载(${Object.keys(out).join('/') || '空'});` +
|
|
410
|
+
'请检查 dsh 安装树里 @earendil-works/pi-ai/dist/api 的形态',
|
|
411
|
+
),
|
|
364
412
|
)
|
|
365
413
|
}
|
|
366
414
|
return out
|
|
@@ -373,7 +421,7 @@ async function loadFallbackTreeFactories(
|
|
|
373
421
|
async function loadTreeProtocols(
|
|
374
422
|
root: string,
|
|
375
423
|
derived: string[],
|
|
376
|
-
diagnostics:
|
|
424
|
+
diagnostics: KitDiagnostic[],
|
|
377
425
|
): Promise<{ protocols: string[]; protocolFactories: Record<string, () => unknown> }> {
|
|
378
426
|
const dir = piAiApiDir(root)
|
|
379
427
|
const out: Record<string, () => unknown> = {}
|
|
@@ -381,27 +429,31 @@ async function loadTreeProtocols(
|
|
|
381
429
|
const file = join(dir, `${api}.lazy.js`)
|
|
382
430
|
if (!existsSync(file)) {
|
|
383
431
|
diagnostics.push(
|
|
384
|
-
|
|
432
|
+
degradation(
|
|
433
|
+
`pi-ai 未提供协议 "${api}" 的惰性实现(缺 ${file});该协议不可用,其余协议不受影响`,
|
|
434
|
+
),
|
|
385
435
|
)
|
|
386
436
|
continue
|
|
387
437
|
}
|
|
388
438
|
try {
|
|
389
439
|
const factory = protocolFactoryOf((await import(pathToFileURL(file).href)) as never)
|
|
390
440
|
if (factory === undefined) {
|
|
391
|
-
diagnostics.push(`协议 "${api}" 的实现模块未导出唯一 *Api 工厂;该协议不可用`)
|
|
441
|
+
diagnostics.push(degradation(`协议 "${api}" 的实现模块未导出唯一 *Api 工厂;该协议不可用`))
|
|
392
442
|
continue
|
|
393
443
|
}
|
|
394
444
|
out[api] = factory
|
|
395
445
|
} catch (error) {
|
|
396
446
|
diagnostics.push(
|
|
397
|
-
|
|
447
|
+
degradation(
|
|
448
|
+
`协议 "${api}" 的实现加载失败(${error instanceof Error ? error.message : String(error)});该协议不可用`,
|
|
449
|
+
),
|
|
398
450
|
)
|
|
399
451
|
}
|
|
400
452
|
}
|
|
401
453
|
if (Object.keys(out).length > 0) {
|
|
402
454
|
return { protocols: derived.filter((api) => out[api] !== undefined), protocolFactories: out }
|
|
403
455
|
}
|
|
404
|
-
diagnostics.push('官方声明的协议一个都加载不到;回退内置三元组')
|
|
456
|
+
diagnostics.push(degradation('官方声明的协议一个都加载不到;回退内置三元组'))
|
|
405
457
|
const fallback = await loadFallbackTreeFactories(root, diagnostics)
|
|
406
458
|
return {
|
|
407
459
|
protocols: fallbackProtocols().filter((api) => fallback[api] !== undefined),
|
|
@@ -465,7 +517,7 @@ function installOfficialSurface(options: {
|
|
|
465
517
|
adapterModule: Record<string, unknown>
|
|
466
518
|
catalogModule: Record<string, unknown>
|
|
467
519
|
origin: string
|
|
468
|
-
diagnostics:
|
|
520
|
+
diagnostics: KitDiagnostic[]
|
|
469
521
|
}): OfficialSurface {
|
|
470
522
|
const { bundlePath, adapterModule, origin, diagnostics } = options
|
|
471
523
|
const compatLabel = 'compat 门控表'
|
|
@@ -477,8 +529,10 @@ function installOfficialSurface(options: {
|
|
|
477
529
|
// 快照模式下未知 compat 键**放行**(交给官方自身校验),只拦快照里明确的
|
|
478
530
|
// withhold——表落后时"误拒官方新字段"比"多放一个键"严重得多。
|
|
479
531
|
diagnostics.push(
|
|
480
|
-
|
|
481
|
-
|
|
532
|
+
degradation(
|
|
533
|
+
`${compatLabel}未能从官方 bundle 解析 COMPAT_GATES(${bundlePath});` +
|
|
534
|
+
`使用内置快照(未知字段放行、withhold 仍拒绝;请检查 dsh-llm-pi-ai 打包形态)`,
|
|
535
|
+
),
|
|
482
536
|
)
|
|
483
537
|
installCompatTable(FALLBACK_TABLE)
|
|
484
538
|
} else {
|
|
@@ -488,21 +542,27 @@ function installOfficialSurface(options: {
|
|
|
488
542
|
}
|
|
489
543
|
} catch (error) {
|
|
490
544
|
const reason = error instanceof Error ? error.message : String(error)
|
|
491
|
-
diagnostics.push(
|
|
545
|
+
diagnostics.push(
|
|
546
|
+
degradation(`${compatLabel}推导失败(${reason});使用内置快照(未知字段放行)`),
|
|
547
|
+
)
|
|
492
548
|
installCompatTable(FALLBACK_TABLE)
|
|
493
549
|
}
|
|
494
550
|
|
|
495
551
|
const derivedProtocols = deriveProtocols(adapterModule)
|
|
496
552
|
if (derivedProtocols === undefined) {
|
|
497
553
|
diagnostics.push(
|
|
498
|
-
|
|
554
|
+
degradation(
|
|
555
|
+
`${origin}:官方 supportedProtocols() 不可用;协议集合回退内置三元组 ${fallbackProtocols().join('/')}`,
|
|
556
|
+
),
|
|
499
557
|
)
|
|
500
558
|
}
|
|
501
559
|
const fields = deriveModelEntryFields(adapterModule)
|
|
502
560
|
if (fields === undefined) {
|
|
503
561
|
diagnostics.push(
|
|
504
|
-
|
|
505
|
-
|
|
562
|
+
degradation(
|
|
563
|
+
`${origin}:官方 Config schema 的模型条目字段集不可推导;` +
|
|
564
|
+
`继承字段回退已知键 ${FALLBACK_MODEL_ENTRY_FIELDS.join('/')}(官方新增字段可能不被继承)`,
|
|
565
|
+
),
|
|
506
566
|
)
|
|
507
567
|
}
|
|
508
568
|
const generatedAt = catalogGeneratedAtOf(options.catalogModule)
|
|
@@ -570,6 +630,7 @@ function kitFromTree(options: {
|
|
|
570
630
|
resolveImageAttachmentAccess: mods.llm[
|
|
571
631
|
'resolveImageAttachmentAccess'
|
|
572
632
|
] as DshKit['resolveImageAttachmentAccess'],
|
|
633
|
+
recordKeyFor,
|
|
573
634
|
}
|
|
574
635
|
}
|
|
575
636
|
|
|
@@ -607,9 +668,9 @@ function vendoredProtocolFactories(): Record<string, () => unknown> {
|
|
|
607
668
|
* npm 发布形态不携带 src/、lib 仅 index.js/invariant.js——resolveProfiles 与
|
|
608
669
|
* auth 助手在此无条件走插件等价实现(内联,见文件头说明);compat 门控表则
|
|
609
670
|
* 仍从 vendored 副本的 bundle + Config schema 现场推导(与 dsh 树同一推导链)。
|
|
610
|
-
* @param diagnostics
|
|
671
|
+
* @param diagnostics 可选诊断收集器(回退原因、形态说明与逐项降级)。
|
|
611
672
|
*/
|
|
612
|
-
export function loadVendoredKit(diagnostics:
|
|
673
|
+
export function loadVendoredKit(diagnostics: KitDiagnostic[] = []): DshKit {
|
|
613
674
|
const staticFactories = vendoredProtocolFactories()
|
|
614
675
|
const surface = installOfficialSurface({
|
|
615
676
|
bundlePath: vendoredAdapterBundlePath(),
|
|
@@ -658,9 +719,11 @@ export function loadVendoredKit(diagnostics: string[] = []): DshKit {
|
|
|
658
719
|
authContextFrom: inlineAuthContextFrom,
|
|
659
720
|
},
|
|
660
721
|
resolveImageAttachmentAccess: vendoredLlm.resolveImageAttachmentAccess,
|
|
722
|
+
recordKeyFor: vendoredPiAiAdapter.recordKeyFor,
|
|
661
723
|
...(deepseek === undefined ? {} : { deepseek }),
|
|
662
724
|
}
|
|
663
725
|
assertKitShape(kit, 'vendored')
|
|
726
|
+
diagnostics.push(missingSourceNote(['profile 解析', '认证助手']))
|
|
664
727
|
return kit
|
|
665
728
|
}
|
|
666
729
|
|
|
@@ -689,14 +752,15 @@ export function dshTreeAnchor(
|
|
|
689
752
|
/**
|
|
690
753
|
* 解析运行时套件:优先 dsh 安装树(自动跟随上游),失败回退 vendored 副本;
|
|
691
754
|
* 两者都过不了形状自检时抛错(调用方应记日志并放弃注册 route)。
|
|
692
|
-
* 返回的 diagnostics
|
|
755
|
+
* 返回的 diagnostics 记录回退原因、打包形态说明(info)与逐项降级(degradation),
|
|
756
|
+
* 供配置卡片与日志分档展示。
|
|
693
757
|
* @param installAnchor profile 的 dsh 安装锚点(`ctx.profileContext.installAnchor`)。
|
|
694
758
|
*/
|
|
695
759
|
export async function resolveDshKit(installAnchor?: string | undefined): Promise<{
|
|
696
760
|
kit: DshKit
|
|
697
|
-
diagnostics:
|
|
761
|
+
diagnostics: KitDiagnostic[]
|
|
698
762
|
}> {
|
|
699
|
-
const diagnostics:
|
|
763
|
+
const diagnostics: KitDiagnostic[] = []
|
|
700
764
|
const anchor = dshTreeAnchor(installAnchor)
|
|
701
765
|
if (anchor !== undefined) {
|
|
702
766
|
const treePkgDir = join(anchor, 'node_modules', '@deepseek-ai', 'dsh-llm-pi-ai')
|
|
@@ -729,39 +793,39 @@ export async function resolveDshKit(installAnchor?: string | undefined): Promise
|
|
|
729
793
|
versions: collectTreeVersions(anchor),
|
|
730
794
|
})
|
|
731
795
|
assertKitShape(kit, 'dsh-tree')
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
}
|
|
737
|
-
if (config === undefined) {
|
|
738
|
-
diagnostics.push(
|
|
739
|
-
'dsh 树不含 dsh-llm-pi-ai/src(npm 发布形态);profile 解析使用插件等价实现(compat 门控对齐官方 catalog.ts)',
|
|
740
|
-
)
|
|
741
|
-
}
|
|
796
|
+
const missingSeams: string[] = []
|
|
797
|
+
if (config === undefined) missingSeams.push('profile 解析')
|
|
798
|
+
if (auth === undefined) missingSeams.push('认证助手')
|
|
799
|
+
if (missingSeams.length > 0) diagnostics.push(missingSourceNote(missingSeams))
|
|
742
800
|
const tree = await importTreeDeepseek(anchor)
|
|
743
801
|
if (tree.kit !== undefined) {
|
|
744
802
|
return { kit: { ...kit, deepseek: tree.kit }, diagnostics }
|
|
745
803
|
}
|
|
746
804
|
diagnostics.push(
|
|
747
|
-
|
|
748
|
-
|
|
805
|
+
degradation(
|
|
806
|
+
`dsh 安装树的 dsh-llm-deepseek 不可用(${tree.problem ?? '未知原因'});` +
|
|
807
|
+
'adapter: deepseek 的 route 不可用,pi 路由不受影响',
|
|
808
|
+
),
|
|
749
809
|
)
|
|
750
810
|
return { kit, diagnostics }
|
|
751
811
|
} catch (error) {
|
|
752
812
|
diagnostics.push(
|
|
753
|
-
|
|
813
|
+
degradation(
|
|
814
|
+
`dsh 安装树套件不可用(${anchor}):${error instanceof Error ? error.message : String(error)};回退 vendored 副本`,
|
|
815
|
+
),
|
|
754
816
|
)
|
|
755
817
|
}
|
|
756
818
|
} else {
|
|
757
819
|
diagnostics.push(
|
|
758
|
-
|
|
820
|
+
degradation(
|
|
821
|
+
'未能从 profileContext.installAnchor / process.argv[1] 定位 dsh 安装树;回退 vendored 副本',
|
|
822
|
+
),
|
|
759
823
|
)
|
|
760
824
|
}
|
|
761
825
|
const kit = loadVendoredKit(diagnostics)
|
|
762
826
|
if (kit.deepseek === undefined) {
|
|
763
827
|
diagnostics.push(
|
|
764
|
-
'vendored 副本的 dsh-llm-deepseek 形状不兼容;adapter: deepseek 的 route 不可用',
|
|
828
|
+
degradation('vendored 副本的 dsh-llm-deepseek 形状不兼容;adapter: deepseek 的 route 不可用'),
|
|
765
829
|
)
|
|
766
830
|
}
|
|
767
831
|
return { kit, diagnostics }
|
package/src/service.ts
CHANGED
|
@@ -47,7 +47,7 @@ import {
|
|
|
47
47
|
} from './official-copy-writer.ts'
|
|
48
48
|
import { assertServiceable, buildProfiles, isDraftRoute } from './profiles.ts'
|
|
49
49
|
import { buildDeepseekRoutes, type ResolvedDeepseekRoute } from './profiles-deepseek.ts'
|
|
50
|
-
import { type DshKit, resolveDshKit } from './resolve-dsh.ts'
|
|
50
|
+
import { type DshKit, degradation, type KitDiagnostic, resolveDshKit } from './resolve-dsh.ts'
|
|
51
51
|
|
|
52
52
|
/** 配置页「运行期状态」所需的事实(纯 JSON,可直接进 HTTP 响应)。 */
|
|
53
53
|
export interface LlmPiKitInfo {
|
|
@@ -71,8 +71,11 @@ export interface LlmPiKitInfo {
|
|
|
71
71
|
compatSource: string
|
|
72
72
|
/** 官方应急副本状态(路径/生成时间/覆盖 route/警告;error = 最近生成失败)。 */
|
|
73
73
|
officialCopy: OfficialCopyStatus
|
|
74
|
-
/**
|
|
75
|
-
|
|
74
|
+
/**
|
|
75
|
+
* 套件诊断(分档,界面分块展示):`info` = 打包形态说明(恒定事实、非缺陷),
|
|
76
|
+
* `degradation` = 回退与逐项降级。
|
|
77
|
+
*/
|
|
78
|
+
diagnostics: KitDiagnostic[]
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
export interface LlmPiRuntime {
|
|
@@ -132,13 +135,16 @@ function makeResolveApiKey(ctx: Context, kit: DshKit) {
|
|
|
132
135
|
}
|
|
133
136
|
}
|
|
134
137
|
|
|
135
|
-
/**
|
|
138
|
+
/** 解析运行期套件并落启动日志(来源/生效版本/协议/形态说明与逐项降级)。 */
|
|
136
139
|
async function loadRuntimeKit(
|
|
137
140
|
ctx: Context,
|
|
138
141
|
logger: PluginLogger,
|
|
139
|
-
): Promise<{ kit: DshKit; diagnostics:
|
|
142
|
+
): Promise<{ kit: DshKit; diagnostics: KitDiagnostic[] }> {
|
|
140
143
|
const { kit, diagnostics } = await resolveDshKit(ctx.get('profileContext')?.installAnchor)
|
|
141
|
-
for (const
|
|
144
|
+
for (const item of diagnostics) {
|
|
145
|
+
if (item.level === 'info') logger.info(item.message)
|
|
146
|
+
else logger.warn(item.message)
|
|
147
|
+
}
|
|
142
148
|
logger.info(
|
|
143
149
|
`运行时套件来源:${kit.source};pi-ai ${kit.versions.piAi ?? '?'} / ` +
|
|
144
150
|
`dsh-llm-pi-ai ${kit.versions.piAiAdapter ?? '?'};协议 ${kit.protocols.join('/')}`,
|
|
@@ -479,8 +485,8 @@ export async function startRuntime(
|
|
|
479
485
|
officialCopy: copy,
|
|
480
486
|
diagnostics: [
|
|
481
487
|
...diagnostics,
|
|
482
|
-
...copy.warnings.map((warning) => `应急副本:${warning}`),
|
|
483
|
-
...(copy.error === undefined ? [] : [`应急副本:生成失败(${copy.error})`]),
|
|
488
|
+
...copy.warnings.map((warning) => degradation(`应急副本:${warning}`)),
|
|
489
|
+
...(copy.error === undefined ? [] : [degradation(`应急副本:生成失败(${copy.error})`)]),
|
|
484
490
|
],
|
|
485
491
|
}
|
|
486
492
|
}
|