@hcmai/sdk 0.3.11 → 0.3.12

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/dist/index.js CHANGED
@@ -1129,6 +1129,167 @@ async function describe(http, model, _opts = {}) {
1129
1129
  };
1130
1130
  }
1131
1131
 
1132
+ // src/fde/fingerprint.ts
1133
+ import { createHash } from "crypto";
1134
+ var GATE_COMPONENTS = [
1135
+ "tenantMetaManifestDigest",
1136
+ "extensionSource",
1137
+ "modelListHash"
1138
+ ];
1139
+ var BLIND_SPOTS = "\u4EA7\u54C1\u5347\u7EA7\uFF1Amanifest \u53EA\u8BFB\u79DF\u6237 workspace \u7684 metas/ \u76EE\u5F55\uFF0C\u4EA7\u54C1\u5347\u7EA7\u6539\u7684\u662F\u5B89\u88C5\u5305\u5185 _metas/\uFF0C\u56DB\u5143\u7EC4\u4E0D\u4F1A\u53D8\u2014\u2014\u8BE5\u573A\u666F\u7684\u9632\u7EBF\u53EA\u6709\u884C\u4E3A\u63A2\u9488\u4E00\u6761\u3002 | \u540C\u5B57\u8282\u6570\u5185\u5BB9\u66FF\u6362\uFF1A\u6E05\u5355\u53EA\u7ED9 name/size/lastModified\uFF0C\u628A\u6807\u7B7E\u300C\u90E8\u95E8\u300D\u6539\u6210\u300C\u673A\u6784\u300D\u5728 UTF-8 \u4E0B\u6070\u597D\u90FD\u662F 6 \u5B57\u8282\uFF0C\u6458\u8981\u4E0D\u53D8\u3002\u21D2 \u5B57\u6BB5\u540D\u53EB ManifestDigest \u800C\u975E Fingerprint\uFF0C\u540D\u5B57\u5FC5\u987B\u8BF4\u5B9E\u8BDD\u3002";
1140
+ var MODELS_PATH = "/api/system/models";
1141
+ var MANIFEST_PATH = "/api/tenant-meta/list";
1142
+ var VERSION_PATH = "/api/version/backend";
1143
+ var META_PATH = "/api/models/{model}/meta";
1144
+ var EXTENSION_SOURCE_HEADER = "x-hcm-extension-source";
1145
+ var MAX_SOURCE_PROBES = 50;
1146
+ var MAX_MANIFEST_PAGES = 200;
1147
+ var GATE_SOURCES = [MODELS_PATH, MANIFEST_PATH, META_PATH];
1148
+ function modelEntries(body) {
1149
+ if (!isPlainObject2(body)) return [];
1150
+ const raw = body.models;
1151
+ if (!Array.isArray(raw)) return [];
1152
+ return raw.filter(isPlainObject2);
1153
+ }
1154
+ function modelKeys(body) {
1155
+ return modelEntries(body).map((m) => typeof m.modelKey === "string" ? m.modelKey : "").filter((k) => k !== "").sort();
1156
+ }
1157
+ async function probeExtensionSource(probe, keys) {
1158
+ let attempted = 0;
1159
+ let answered = 0;
1160
+ for (const key of [...keys].sort().slice(0, MAX_SOURCE_PROBES)) {
1161
+ attempted += 1;
1162
+ const out = await probe.get(META_PATH.replace("{model}", key));
1163
+ if (out.error !== "ok") continue;
1164
+ answered += 1;
1165
+ const value = out.headers[EXTENSION_SOURCE_HEADER];
1166
+ if (value) return { sourceKey: value, probeModelKey: key, attempted, answered };
1167
+ }
1168
+ return { sourceKey: "", probeModelKey: "", attempted, answered };
1169
+ }
1170
+ async function fetchManifestRows(probe, failures) {
1171
+ const rows = [];
1172
+ let page = 0;
1173
+ const pageSize = 200;
1174
+ for (; ; ) {
1175
+ const out = await probe.get(MANIFEST_PATH, { page, pageSize });
1176
+ if (out.error !== "ok" || !isPlainObject2(out.body)) {
1177
+ failures.push(MANIFEST_PATH);
1178
+ break;
1179
+ }
1180
+ const body = out.body;
1181
+ const chunk = body.data ?? [];
1182
+ if (!Array.isArray(chunk)) {
1183
+ failures.push(MANIFEST_PATH);
1184
+ break;
1185
+ }
1186
+ rows.push(...chunk.filter(isPlainObject2));
1187
+ const totalPages = typeof body.totalPages === "number" ? body.totalPages : 0;
1188
+ page += 1;
1189
+ if (page >= totalPages || chunk.length === 0) break;
1190
+ if (page >= MAX_MANIFEST_PAGES) {
1191
+ failures.push(`${MANIFEST_PATH}#page-cap`);
1192
+ break;
1193
+ }
1194
+ }
1195
+ return rows;
1196
+ }
1197
+ function manifestDigest(rows) {
1198
+ const lines = rows.map((r) => `${r.name}|${r.size}|${r.lastModified}`).sort();
1199
+ return sha256(lines.join("\n"));
1200
+ }
1201
+ async function computeReconSourceKey(probe) {
1202
+ const out = await probe.get(MODELS_PATH);
1203
+ const p = await probeExtensionSource(probe, modelKeys(out.body));
1204
+ return p.sourceKey || "unknown";
1205
+ }
1206
+ async function computeFingerprint(probe, opts = {}) {
1207
+ const ttlDays = opts.ttlDays ?? 30;
1208
+ const now = opts.now ?? (() => /* @__PURE__ */ new Date());
1209
+ const failures = [];
1210
+ const modelsOut = await probe.get(MODELS_PATH);
1211
+ const modelsOk = modelsOut.error === "ok" && isPlainObject2(modelsOut.body);
1212
+ if (!modelsOk) failures.push(MODELS_PATH);
1213
+ const models = modelsOk ? modelEntries(modelsOut.body) : [];
1214
+ const keys = modelsOk ? modelKeys(modelsOut.body) : [];
1215
+ if (modelsOut.error === "ok" && keys.length === 0) {
1216
+ failures.push(`${MODELS_PATH}#empty-model-list`);
1217
+ }
1218
+ const p = await probeExtensionSource(probe, keys);
1219
+ let sourceKey = p.sourceKey;
1220
+ if (!sourceKey) {
1221
+ failures.push(
1222
+ p.answered > 0 ? `${META_PATH}#X-HCM-Extension-Source` : `${META_PATH}#no-meta-answered`
1223
+ );
1224
+ sourceKey = "unknown";
1225
+ }
1226
+ const hashLines = models.map((m) => `${m.modelKey ?? ""}|${m.type ?? ""}`).sort();
1227
+ const modelHash = sha256(hashLines.join("\n"));
1228
+ const rows = await fetchManifestRows(probe, failures);
1229
+ const versionOut = await probe.get(VERSION_PATH);
1230
+ let backendVersion;
1231
+ if (versionOut.error === "ok" && versionOut.body !== null && versionOut.body !== void 0) {
1232
+ backendVersion = versionOut.body;
1233
+ } else {
1234
+ failures.push(VERSION_PATH);
1235
+ backendVersion = { version: "unavailable" };
1236
+ }
1237
+ return {
1238
+ tenantMetaManifestDigest: manifestDigest(rows),
1239
+ manifestEntryCount: rows.length,
1240
+ extensionSource: sourceKey,
1241
+ // 🔴 记下**是从哪个模型探到的**:让「这个值是谁给的」在产物里留痕。
1242
+ extensionSourceProbe: p.probeModelKey,
1243
+ // 🔴 没有这两个数,`extensionSource: "unknown"` 就是一个**不可追问的结论**。
1244
+ extensionSourceProbeAttempted: p.attempted,
1245
+ extensionSourceProbeAnswered: p.answered,
1246
+ modelListHash: modelHash,
1247
+ modelCount: models.length,
1248
+ backendVersion,
1249
+ capabilities: probe.capabilities,
1250
+ createdAt: `${now().toISOString().slice(0, 19)}Z`,
1251
+ ttlDays,
1252
+ coverageNotes: {
1253
+ gateComponents: [...GATE_COMPONENTS],
1254
+ blindSpots: BLIND_SPOTS,
1255
+ // 空数组 = 三个端点都真取到了;非空 = 这份四元组是**残的**,
1256
+ // 上面那些 0 和空摘要不代表环境真的空。
1257
+ fetchFailures: failures
1258
+ }
1259
+ };
1260
+ }
1261
+ function compareFingerprint(recorded, current) {
1262
+ const diffs = GATE_COMPONENTS.filter((c) => recorded[c] !== current[c]);
1263
+ if (recorded.capabilities !== current.capabilities) diffs.push("capabilities");
1264
+ for (const [fp, tag] of [
1265
+ [recorded, "recorded"],
1266
+ [current, "current"]
1267
+ ]) {
1268
+ const notes = fp.coverageNotes;
1269
+ if (!isPlainObject2(notes) || !("fetchFailures" in notes)) {
1270
+ diffs.push(`incomplete:${tag}:coverageNotes-missing`);
1271
+ continue;
1272
+ }
1273
+ const raw = notes.fetchFailures;
1274
+ const list = Array.isArray(raw) ? raw : [];
1275
+ const gateFailures = [...new Set(list.map(String))].filter((x) => GATE_SOURCES.some((s) => x.startsWith(s))).sort();
1276
+ for (const f of gateFailures) diffs.push(`incomplete:${tag}:${f}`);
1277
+ }
1278
+ return diffs;
1279
+ }
1280
+ function sha256(s) {
1281
+ return createHash("sha256").update(s, "utf8").digest("hex");
1282
+ }
1283
+ function isPlainObject2(v) {
1284
+ return typeof v === "object" && v !== null && !Array.isArray(v);
1285
+ }
1286
+
1287
+ // src/meta/models.ts
1288
+ async function listModels(http) {
1289
+ const resp = await http.get(MODELS_PATH);
1290
+ return modelEntries(resp.data?.data ?? resp.data);
1291
+ }
1292
+
1132
1293
  // src/models/create.ts
1133
1294
  async function create(http, model, data) {
1134
1295
  const resp = await http.post(`/api/models/${model}`, data);
@@ -1378,6 +1539,10 @@ var HcmClient = class _HcmClient {
1378
1539
  action(model, name, input) {
1379
1540
  return action(this.http, model, name, input);
1380
1541
  }
1542
+ /** 列出目标环境有哪些 Model —— `describe` 的上一步。 */
1543
+ listModels() {
1544
+ return listModels(this.http);
1545
+ }
1381
1546
  describe(model, opts) {
1382
1547
  return describe(this.http, model, opts);
1383
1548
  }
@@ -4707,7 +4872,7 @@ function buildPushPlan(context, manifest2, localFiles, remoteFiles) {
4707
4872
  for (const entry of status) {
4708
4873
  const remote = remoteByLocal.get(entry.localPath);
4709
4874
  const base = baseByLocal.get(entry.localPath);
4710
- const remoteSha = remote ? sha256(remote.content.content) : void 0;
4875
+ const remoteSha = remote ? sha2562(remote.content.content) : void 0;
4711
4876
  if (entry.status === "added") {
4712
4877
  if (remote) {
4713
4878
  conflicts.push(conflict(entry, "remote file already exists but was not in local snapshot"));
@@ -4840,7 +5005,7 @@ async function walk(root, relativeDir, files) {
4840
5005
  localPath,
4841
5006
  absolutePath,
4842
5007
  content,
4843
- sha256: sha256(content),
5008
+ sha256: sha2562(content),
4844
5009
  size: Buffer.byteLength(content, "utf-8")
4845
5010
  });
4846
5011
  }
@@ -4953,7 +5118,7 @@ function localPathFromRemote(remotePrefix, remotePath) {
4953
5118
  function isDirectoryMarkerPath(relativePath) {
4954
5119
  return relativePath.endsWith("/.keep") || relativePath === ".keep";
4955
5120
  }
4956
- function sha256(content) {
5121
+ function sha2562(content) {
4957
5122
  return crypto.createHash("sha256").update(content, "utf-8").digest("hex");
4958
5123
  }
4959
5124
  async function writeText(file, content) {
@@ -5267,7 +5432,7 @@ function parseSettingAssignment(raw) {
5267
5432
  }
5268
5433
 
5269
5434
  // src/fde/layout.ts
5270
- import { createHash as createHash2 } from "crypto";
5435
+ import { createHash as createHash3 } from "crypto";
5271
5436
  import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
5272
5437
  import * as path6 from "path";
5273
5438
  var UNSAFE = /[^A-Za-z0-9._-]/g;
@@ -5276,7 +5441,7 @@ function slug(value) {
5276
5441
  }
5277
5442
  function safeSegment(value) {
5278
5443
  const raw = value || "unknown";
5279
- const digest = createHash2("sha256").update(raw, "utf8").digest("hex").slice(0, 10);
5444
+ const digest = createHash3("sha256").update(raw, "utf8").digest("hex").slice(0, 10);
5280
5445
  return `${slug(raw)}-${digest}`;
5281
5446
  }
5282
5447
  function tenantSegment(value) {
@@ -5391,161 +5556,6 @@ var LayoutContainmentError = class extends Error {
5391
5556
  }
5392
5557
  };
5393
5558
 
5394
- // src/fde/fingerprint.ts
5395
- import { createHash as createHash3 } from "crypto";
5396
- var GATE_COMPONENTS = [
5397
- "tenantMetaManifestDigest",
5398
- "extensionSource",
5399
- "modelListHash"
5400
- ];
5401
- var BLIND_SPOTS = "\u4EA7\u54C1\u5347\u7EA7\uFF1Amanifest \u53EA\u8BFB\u79DF\u6237 workspace \u7684 metas/ \u76EE\u5F55\uFF0C\u4EA7\u54C1\u5347\u7EA7\u6539\u7684\u662F\u5B89\u88C5\u5305\u5185 _metas/\uFF0C\u56DB\u5143\u7EC4\u4E0D\u4F1A\u53D8\u2014\u2014\u8BE5\u573A\u666F\u7684\u9632\u7EBF\u53EA\u6709\u884C\u4E3A\u63A2\u9488\u4E00\u6761\u3002 | \u540C\u5B57\u8282\u6570\u5185\u5BB9\u66FF\u6362\uFF1A\u6E05\u5355\u53EA\u7ED9 name/size/lastModified\uFF0C\u628A\u6807\u7B7E\u300C\u90E8\u95E8\u300D\u6539\u6210\u300C\u673A\u6784\u300D\u5728 UTF-8 \u4E0B\u6070\u597D\u90FD\u662F 6 \u5B57\u8282\uFF0C\u6458\u8981\u4E0D\u53D8\u3002\u21D2 \u5B57\u6BB5\u540D\u53EB ManifestDigest \u800C\u975E Fingerprint\uFF0C\u540D\u5B57\u5FC5\u987B\u8BF4\u5B9E\u8BDD\u3002";
5402
- var MODELS_PATH = "/api/system/models";
5403
- var MANIFEST_PATH = "/api/tenant-meta/list";
5404
- var VERSION_PATH = "/api/version/backend";
5405
- var META_PATH = "/api/models/{model}/meta";
5406
- var EXTENSION_SOURCE_HEADER = "x-hcm-extension-source";
5407
- var MAX_SOURCE_PROBES = 50;
5408
- var MAX_MANIFEST_PAGES = 200;
5409
- var GATE_SOURCES = [MODELS_PATH, MANIFEST_PATH, META_PATH];
5410
- function modelEntries(body) {
5411
- if (!isPlainObject2(body)) return [];
5412
- const raw = body.models;
5413
- if (!Array.isArray(raw)) return [];
5414
- return raw.filter(isPlainObject2);
5415
- }
5416
- function modelKeys(body) {
5417
- return modelEntries(body).map((m) => typeof m.modelKey === "string" ? m.modelKey : "").filter((k) => k !== "").sort();
5418
- }
5419
- async function probeExtensionSource(probe, keys) {
5420
- let attempted = 0;
5421
- let answered = 0;
5422
- for (const key of [...keys].sort().slice(0, MAX_SOURCE_PROBES)) {
5423
- attempted += 1;
5424
- const out = await probe.get(META_PATH.replace("{model}", key));
5425
- if (out.error !== "ok") continue;
5426
- answered += 1;
5427
- const value = out.headers[EXTENSION_SOURCE_HEADER];
5428
- if (value) return { sourceKey: value, probeModelKey: key, attempted, answered };
5429
- }
5430
- return { sourceKey: "", probeModelKey: "", attempted, answered };
5431
- }
5432
- async function fetchManifestRows(probe, failures) {
5433
- const rows = [];
5434
- let page = 0;
5435
- const pageSize = 200;
5436
- for (; ; ) {
5437
- const out = await probe.get(MANIFEST_PATH, { page, pageSize });
5438
- if (out.error !== "ok" || !isPlainObject2(out.body)) {
5439
- failures.push(MANIFEST_PATH);
5440
- break;
5441
- }
5442
- const body = out.body;
5443
- const chunk = body.data ?? [];
5444
- if (!Array.isArray(chunk)) {
5445
- failures.push(MANIFEST_PATH);
5446
- break;
5447
- }
5448
- rows.push(...chunk.filter(isPlainObject2));
5449
- const totalPages = typeof body.totalPages === "number" ? body.totalPages : 0;
5450
- page += 1;
5451
- if (page >= totalPages || chunk.length === 0) break;
5452
- if (page >= MAX_MANIFEST_PAGES) {
5453
- failures.push(`${MANIFEST_PATH}#page-cap`);
5454
- break;
5455
- }
5456
- }
5457
- return rows;
5458
- }
5459
- function manifestDigest(rows) {
5460
- const lines = rows.map((r) => `${r.name}|${r.size}|${r.lastModified}`).sort();
5461
- return sha2562(lines.join("\n"));
5462
- }
5463
- async function computeReconSourceKey(probe) {
5464
- const out = await probe.get(MODELS_PATH);
5465
- const p = await probeExtensionSource(probe, modelKeys(out.body));
5466
- return p.sourceKey || "unknown";
5467
- }
5468
- async function computeFingerprint(probe, opts = {}) {
5469
- const ttlDays = opts.ttlDays ?? 30;
5470
- const now = opts.now ?? (() => /* @__PURE__ */ new Date());
5471
- const failures = [];
5472
- const modelsOut = await probe.get(MODELS_PATH);
5473
- const modelsOk = modelsOut.error === "ok" && isPlainObject2(modelsOut.body);
5474
- if (!modelsOk) failures.push(MODELS_PATH);
5475
- const models = modelsOk ? modelEntries(modelsOut.body) : [];
5476
- const keys = modelsOk ? modelKeys(modelsOut.body) : [];
5477
- if (modelsOut.error === "ok" && keys.length === 0) {
5478
- failures.push(`${MODELS_PATH}#empty-model-list`);
5479
- }
5480
- const p = await probeExtensionSource(probe, keys);
5481
- let sourceKey = p.sourceKey;
5482
- if (!sourceKey) {
5483
- failures.push(
5484
- p.answered > 0 ? `${META_PATH}#X-HCM-Extension-Source` : `${META_PATH}#no-meta-answered`
5485
- );
5486
- sourceKey = "unknown";
5487
- }
5488
- const hashLines = models.map((m) => `${m.modelKey ?? ""}|${m.type ?? ""}`).sort();
5489
- const modelHash = sha2562(hashLines.join("\n"));
5490
- const rows = await fetchManifestRows(probe, failures);
5491
- const versionOut = await probe.get(VERSION_PATH);
5492
- let backendVersion;
5493
- if (versionOut.error === "ok" && versionOut.body !== null && versionOut.body !== void 0) {
5494
- backendVersion = versionOut.body;
5495
- } else {
5496
- failures.push(VERSION_PATH);
5497
- backendVersion = { version: "unavailable" };
5498
- }
5499
- return {
5500
- tenantMetaManifestDigest: manifestDigest(rows),
5501
- manifestEntryCount: rows.length,
5502
- extensionSource: sourceKey,
5503
- // 🔴 记下**是从哪个模型探到的**:让「这个值是谁给的」在产物里留痕。
5504
- extensionSourceProbe: p.probeModelKey,
5505
- // 🔴 没有这两个数,`extensionSource: "unknown"` 就是一个**不可追问的结论**。
5506
- extensionSourceProbeAttempted: p.attempted,
5507
- extensionSourceProbeAnswered: p.answered,
5508
- modelListHash: modelHash,
5509
- modelCount: models.length,
5510
- backendVersion,
5511
- capabilities: probe.capabilities,
5512
- createdAt: `${now().toISOString().slice(0, 19)}Z`,
5513
- ttlDays,
5514
- coverageNotes: {
5515
- gateComponents: [...GATE_COMPONENTS],
5516
- blindSpots: BLIND_SPOTS,
5517
- // 空数组 = 三个端点都真取到了;非空 = 这份四元组是**残的**,
5518
- // 上面那些 0 和空摘要不代表环境真的空。
5519
- fetchFailures: failures
5520
- }
5521
- };
5522
- }
5523
- function compareFingerprint(recorded, current) {
5524
- const diffs = GATE_COMPONENTS.filter((c) => recorded[c] !== current[c]);
5525
- if (recorded.capabilities !== current.capabilities) diffs.push("capabilities");
5526
- for (const [fp, tag] of [
5527
- [recorded, "recorded"],
5528
- [current, "current"]
5529
- ]) {
5530
- const notes = fp.coverageNotes;
5531
- if (!isPlainObject2(notes) || !("fetchFailures" in notes)) {
5532
- diffs.push(`incomplete:${tag}:coverageNotes-missing`);
5533
- continue;
5534
- }
5535
- const raw = notes.fetchFailures;
5536
- const list = Array.isArray(raw) ? raw : [];
5537
- const gateFailures = [...new Set(list.map(String))].filter((x) => GATE_SOURCES.some((s) => x.startsWith(s))).sort();
5538
- for (const f of gateFailures) diffs.push(`incomplete:${tag}:${f}`);
5539
- }
5540
- return diffs;
5541
- }
5542
- function sha2562(s) {
5543
- return createHash3("sha256").update(s, "utf8").digest("hex");
5544
- }
5545
- function isPlainObject2(v) {
5546
- return typeof v === "object" && v !== null && !Array.isArray(v);
5547
- }
5548
-
5549
5559
  // src/fde/doctor.ts
5550
5560
  var DOCTOR_ANCHORS = [
5551
5561
  "connectivity",
@@ -9275,6 +9285,7 @@ export {
9275
9285
  isServerSlidingSession,
9276
9286
  listEnvs,
9277
9287
  listIdentities,
9288
+ listModels,
9278
9289
  listProfiles,
9279
9290
  listTenantMeta,
9280
9291
  listWorkspaceFiles,