@shgroup/dsh-serenity-hooks 1.48.0 → 1.48.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-serenity-hooks",
3
- "version": "1.48.0",
3
+ "version": "1.48.1",
4
4
  "main": "lib/index.js",
5
5
  "description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——11 个工具(container_fs/container_trajectory/dashboard/container_git/msm/praxis/handyman/localstore/container_admin/im-bridge/acc-diag;后两个按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 轨迹日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/trajectory 唤醒注册表。适配 DSH 0.1.5-rc.2(deepseek-ai/deepseek-harness)。",
6
6
  "engines": {
package/lib/index.js CHANGED
@@ -17031,6 +17031,7 @@ function opencodeRouteHeaders() {
17031
17031
  const OPENCODE_EXTRA_MODELS = { "deepseek-v4.1-flash": {
17032
17032
  name: "DeepSeek V4.1 Flash",
17033
17033
  api: "openai-completions",
17034
+ baseUrl: "https://opencode.ai/zen/go/v1",
17034
17035
  contextWindow: 1e6,
17035
17036
  maxTokens: 384e3,
17036
17037
  input: ["text"],
@@ -17074,41 +17075,104 @@ const OPENCODE_GO_NON_COMPLETIONS = /* @__PURE__ */ new Set([
17074
17075
  "muse-spark-1.2-contributor"
17075
17076
  ]);
17076
17077
  /**
17077
- * 往**已有显式 `models` 列表**的路由里追加目录未收录的模型(**纯函数**)。
17078
+ * 把 {@link OPENCODE_EXTRA_MODELS} 落到一个**已有显式 `models` 列表**的路由上(**纯函数**)。
17078
17079
  *
17079
- * 三条拒绝条件(每条都对应一个会让配置变坏的真实形态):
17080
- * ① **没有显式 `models` 列表** ⇒ 该路由此刻服务的是 pi-ai 目录本体;
17081
- * 一旦我们写出 `models`,**整个目录会被顶掉**(宿主的 `models` 是"整体替换"语义)
17082
- * ⇒ 宁可不动。
17080
+ * 两种落法(v1.48.1 起):
17081
+ * · **追加**(模型不在表里)—— 目录里没有它,连条目都得我们造。
17082
+ * · **补规格**(模型已在表里、但缺 `reasoningEfforts`)—— 🔴 **这是"手加过"的形态**:
17083
+ * 用户把模型抄进列表了,可没抄推理档 ⇒ 宿主 `resolveModelReasoning` 走 `base?.reasoning ?? false`
17084
+ * ⇒ **能用但永远不出 thinking**。只补"缺失的模型"是补不到这一形态的。
17085
+ *
17086
+ * 拒绝条件(每条都对应一个会让配置变坏的真实形态;宁可不动也不硬来):
17087
+ * ① **没有显式 `models` 列表**(含空表)⇒ 该路由此刻服务的是 pi-ai 目录本体;
17088
+ * 一旦我们写出 `models`,**整个目录会被顶掉**(宿主的 `models` 是"整体替换"语义)。
17083
17089
  * ② **列表里出现已知异协议模型** ⇒ 设路由级 `api` 会覆盖它们的 `base.api`,把它们配坏。
17084
17090
  * ③ **路由已显式设了别的 `api`** ⇒ 同上,且那是用户的显式选择,不覆盖。
17091
+ * ④ **要补规格却判不出协议**(路由没写 `api`,又不追加任何模型)⇒ 无法保证 compat 合法。
17085
17092
  */
17086
17093
  function planExtraModels(profile) {
17087
17094
  const raw = profile.models;
17088
17095
  if (!Array.isArray(raw) || raw.length === 0) return {
17089
17096
  addedIds: [],
17097
+ enrichedIds: [],
17090
17098
  skip: "catalog-served"
17091
17099
  };
17092
- const ids = /* @__PURE__ */ new Set();
17093
- for (const entry of raw) if (isPlainObject$1(entry) && typeof entry.id === "string") ids.add(entry.id);
17094
- const missing = Object.entries(OPENCODE_EXTRA_MODELS).filter(([id]) => !ids.has(id));
17095
- if (missing.length === 0) return { addedIds: [] };
17096
- for (const id of ids) if (OPENCODE_GO_NON_COMPLETIONS.has(id)) return {
17100
+ const byId = /* @__PURE__ */ new Map();
17101
+ for (const entry of raw) if (isPlainObject$1(entry) && typeof entry.id === "string") byId.set(entry.id, entry);
17102
+ for (const id of byId.keys()) if (OPENCODE_GO_NON_COMPLETIONS.has(id)) return {
17097
17103
  addedIds: [],
17104
+ enrichedIds: [],
17098
17105
  skip: "mixed-protocol"
17099
17106
  };
17100
- const apis = new Set(missing.map(([, model]) => model.api));
17101
- if (apis.size !== 1) return {
17107
+ const missing = Object.entries(OPENCODE_EXTRA_MODELS).filter(([id]) => !byId.has(id));
17108
+ const existingApi = typeof profile.api === "string" && profile.api !== "" ? profile.api : void 0;
17109
+ let pendingApi;
17110
+ if (missing.length > 0) {
17111
+ const apis = new Set(missing.map(([, model]) => model.api));
17112
+ if (apis.size !== 1) return {
17113
+ addedIds: [],
17114
+ enrichedIds: [],
17115
+ skip: "mixed-protocol"
17116
+ };
17117
+ pendingApi = [...apis][0];
17118
+ }
17119
+ if (existingApi !== void 0 && pendingApi !== void 0 && existingApi !== pendingApi) return {
17102
17120
  addedIds: [],
17103
- skip: "mixed-protocol"
17121
+ enrichedIds: [],
17122
+ skip: "api-conflict"
17104
17123
  };
17105
- const api = [...apis][0];
17106
- const existingApi = profile.api;
17107
- if (typeof existingApi === "string" && existingApi !== api) return {
17124
+ /** 写完之后该路由的**有效协议**(第三方条目也会按它解析) */
17125
+ const effectiveApi = existingApi ?? pendingApi;
17126
+ const toEnrich = [];
17127
+ for (const [id, model] of Object.entries(OPENCODE_EXTRA_MODELS)) {
17128
+ const entry = byId.get(id);
17129
+ if (entry === void 0) continue;
17130
+ if (entry.reasoningEfforts !== void 0) continue;
17131
+ if (effectiveApi === void 0) return {
17132
+ addedIds: [],
17133
+ enrichedIds: [],
17134
+ skip: "api-missing"
17135
+ };
17136
+ if (effectiveApi !== model.api) return {
17137
+ addedIds: [],
17138
+ enrichedIds: [],
17139
+ skip: "api-conflict"
17140
+ };
17141
+ toEnrich.push([
17142
+ id,
17143
+ model,
17144
+ entry
17145
+ ]);
17146
+ }
17147
+ if (missing.length === 0 && toEnrich.length === 0) return {
17108
17148
  addedIds: [],
17109
- skip: "api-conflict"
17149
+ enrichedIds: []
17110
17150
  };
17111
- const appended = missing.map(([id, model]) => ({
17151
+ let baseUrl;
17152
+ if (!(typeof profile.baseURL === "string" && profile.baseURL !== "")) {
17153
+ const want = new Set([...missing.map(([, m]) => m), ...toEnrich.map(([, m]) => m)].map((m) => m.baseUrl));
17154
+ if (want.size !== 1) return {
17155
+ addedIds: [],
17156
+ enrichedIds: [],
17157
+ skip: "mixed-protocol"
17158
+ };
17159
+ baseUrl = [...want][0];
17160
+ }
17161
+ const enrichById = new Map(toEnrich.map(([id, model]) => [id, model]));
17162
+ const models = raw.map((entry) => {
17163
+ if (!isPlainObject$1(entry) || typeof entry.id !== "string") return entry;
17164
+ const model = enrichById.get(entry.id);
17165
+ if (model === void 0) return entry;
17166
+ return {
17167
+ ...entry,
17168
+ reasoningEfforts: { ...model.reasoningEfforts },
17169
+ compat: {
17170
+ ...model.compat,
17171
+ ...isPlainObject$1(entry.compat) ? entry.compat : {}
17172
+ }
17173
+ };
17174
+ });
17175
+ for (const [id, model] of missing) models.push({
17112
17176
  id,
17113
17177
  name: model.name,
17114
17178
  contextWindow: model.contextWindow,
@@ -17116,11 +17180,13 @@ function planExtraModels(profile) {
17116
17180
  input: [...model.input],
17117
17181
  reasoningEfforts: { ...model.reasoningEfforts },
17118
17182
  compat: { ...model.compat }
17119
- }));
17183
+ });
17120
17184
  return {
17121
- models: [...raw, ...appended],
17122
- ...existingApi === api ? {} : { api },
17123
- addedIds: missing.map(([id]) => id)
17185
+ models,
17186
+ ...pendingApi !== void 0 && existingApi === void 0 ? { api: pendingApi } : {},
17187
+ ...baseUrl === void 0 ? {} : { baseUrl },
17188
+ addedIds: missing.map(([id]) => id),
17189
+ enrichedIds: toEnrich.map(([id]) => id)
17124
17190
  };
17125
17191
  }
17126
17192
  function isPlainObject$1(value) {
@@ -17198,16 +17264,24 @@ function planOpencodeAutoConfig(input) {
17198
17264
  route,
17199
17265
  reason: extra.skip
17200
17266
  });
17201
- else if (extra.addedIds.length > 0) actions.push({
17202
- kind: "add-models",
17203
- route,
17204
- ids: extra.addedIds,
17205
- api: extra.api ?? null
17206
- });
17267
+ else {
17268
+ if (extra.addedIds.length > 0) actions.push({
17269
+ kind: "add-models",
17270
+ route,
17271
+ ids: extra.addedIds,
17272
+ api: extra.api ?? null
17273
+ });
17274
+ if (extra.enrichedIds.length > 0) actions.push({
17275
+ kind: "enrich-models",
17276
+ route,
17277
+ ids: extra.enrichedIds
17278
+ });
17279
+ }
17207
17280
  const routePatch = {};
17208
17281
  if (keys.length > 0) routePatch.headers = missing;
17209
17282
  if (extra.models !== void 0) routePatch.models = extra.models;
17210
17283
  if (extra.api !== void 0) routePatch.api = extra.api;
17284
+ if (extra.baseUrl !== void 0) routePatch.baseURL = extra.baseUrl;
17211
17285
  if (keys.length === 0 && Object.keys(routePatch).length === 0) {
17212
17286
  actions.push({
17213
17287
  kind: "skip-route",
@@ -17249,7 +17323,8 @@ function describeOpencodeAction(action) {
17249
17323
  case "create-route": return `创建 ${action.route} 路由(检测到 ${OPENCODE_API_KEY_ENV})+ 路由头`;
17250
17324
  case "skip-route": return `${action.route} 路由头已齐备(跳过)`;
17251
17325
  case "add-models": return `为 ${action.route} 补目录未收录的模型:${action.ids.join(", ")}` + (action.api === null ? "" : `(并设路由 api=${action.api})`);
17252
- case "skip-models": return `${action.route} 未补模型(${action.reason === "catalog-served" ? "该路由未声明 models 列表,补 models 会顶掉整个目录" : action.reason === "mixed-protocol" ? "该路由含异协议模型,设路由 api 会把它们配坏" : "该路由已显式设了别的 api"})`;
17326
+ case "enrich-models": return `为 ${action.route} 已列出的模型补推理档:${action.ids.join(", ")}(此前「能用但没 thinking」)`;
17327
+ case "skip-models": return `${action.route} 未补模型(${action.reason === "catalog-served" ? "该路由未声明 models 列表,补 models 会顶掉整个目录" : action.reason === "mixed-protocol" ? "该路由含异协议模型,设路由 api 会把它们配坏" : action.reason === "api-missing" ? "该模型已列出但缺推理档,而路由没写 api ⇒ 判不出该用哪套 compat" : "该路由的 api 与本表规格不一致"})`;
17253
17328
  case "idle": return `无需动作(${action.reason})`;
17254
17329
  }
17255
17330
  }
@@ -94,6 +94,16 @@ export interface OpencodeExtraModel {
94
94
  readonly name: string;
95
95
  /** 该模型说的 wire 协议;见 {@link OPENCODE_GO_NON_COMPLETIONS} 的安全闸 */
96
96
  readonly api: string;
97
+ /**
98
+ * 该模型的端点基址 —— 🔴 **目录外模型的必需品**(v1.48.1 补)。
99
+ *
100
+ * 宿主 `resolveRouteModels` 的兜底链是 `request.baseURL ?? base?.baseUrl ?? providerBaseUrl`;
101
+ * 目录里没有它 ⇒ `base` 为 undefined,而 pi-ai 的 `opencodeGoProvider()` **也没有 provider 级
102
+ * baseUrl** ⇒ 三级全空 ⇒ 宿主**严格写入直接把整条补丁拒掉**(v1.48.0 真机实测原文:
103
+ * `model "deepseek-v4.1-flash" needs a baseURL; the installed catalog does not describe this route`)。
104
+ * 故本字段是**唯一可控的那一环**:路由自身没写 `baseURL` 时,必须由我们补上。
105
+ */
106
+ readonly baseUrl: string;
97
107
  readonly contextWindow: number;
98
108
  readonly maxTokens: number;
99
109
  readonly input: readonly string[];
@@ -135,10 +145,14 @@ type OpencodeAutoConfigAction = {
135
145
  route: string;
136
146
  ids: readonly string[];
137
147
  api: string | null;
148
+ } | {
149
+ kind: 'enrich-models';
150
+ route: string;
151
+ ids: readonly string[];
138
152
  } | {
139
153
  kind: 'skip-models';
140
154
  route: string;
141
- reason: 'catalog-served' | 'mixed-protocol' | 'api-conflict';
155
+ reason: 'catalog-served' | 'mixed-protocol' | 'api-conflict' | 'api-missing';
142
156
  } | {
143
157
  kind: 'idle';
144
158
  reason: 'namespace-unregistered' | 'no-route';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shgroup/dsh-serenity-hooks",
3
- "version": "1.48.0",
3
+ "version": "1.48.1",
4
4
  "description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「AI 工作区」:11 个工具(container_fs/container_trajectory/dashboard/container_git/msm/praxis/handyman/localstore/container_admin/im-bridge/acc-diag)+ 机械约束(安全模式/工作区围墙/密钥守卫/对外输出守卫)+ 工作日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/trajectory 唤醒注册表。适配 DSH 0.1.7-rc.1。",
5
5
  "license": "MIT",
6
6
  "repository": {