@cloudbase/cloudbase-mcp 2.24.1 → 2.25.0

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.cjs CHANGED
@@ -340,7 +340,7 @@ class CloudRunService {
340
340
  */
341
341
  async deploy(params) {
342
342
  var _a;
343
- const { serverName, targetPath = process.cwd(), serverConfig } = params;
343
+ const { serverName, targetPath = process.cwd(), serverConfig, vpcInfo, imageUrl } = params;
344
344
  /**
345
345
  * 参数校验和默认值设置
346
346
  */
@@ -350,30 +350,41 @@ class CloudRunService {
350
350
  // 获取当前环境配置(包含EnvId)
351
351
  const envConfig = this.environment.lazyEnvironmentConfig;
352
352
  /**
353
- * 获取部署包上传信息
353
+ * 根据部署类型构建 deployInfo
354
354
  */
355
- const { UploadUrl: uploadUrl, UploadHeaders: uploadHeaders, PackageName: packageName, PackageVersion: packageVersion } = await this.tcbService.request('DescribeCloudBaseBuildService', {
356
- EnvId: envConfig.EnvId,
357
- ServiceName: serverName
358
- });
359
- const deployInfo = {
360
- DeployType: 'package',
361
- PackageName: packageName,
362
- PackageVersion: packageVersion
363
- };
364
- /**
365
- * 上传部署包
366
- */
367
- const zipFile = await codeToZip(targetPath, { installDependency: (serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.InstallDependency) !== undefined ? serverConfig.InstallDependency : true });
368
- await (0, utils_1.upload)({
369
- url: uploadUrl,
370
- file: zipFile,
371
- headers: (uploadHeaders || []).reduce((map, item) => {
372
- map[item.Key] = item.Value;
373
- return map;
374
- }, {}) || {},
375
- method: 'PUT'
376
- });
355
+ let deployInfo;
356
+ if (imageUrl) {
357
+ // 镜像部署模式:直接使用镜像地址,无需上传代码包
358
+ deployInfo = {
359
+ DeployType: 'image',
360
+ ImageUrl: imageUrl
361
+ };
362
+ }
363
+ else {
364
+ // 代码包部署模式:获取上传信息 → 打包 → 上传
365
+ const { UploadUrl: uploadUrl, UploadHeaders: uploadHeaders, PackageName: packageName, PackageVersion: packageVersion } = await this.tcbService.request('DescribeCloudBaseBuildService', {
366
+ EnvId: envConfig.EnvId,
367
+ ServiceName: serverName
368
+ });
369
+ deployInfo = {
370
+ DeployType: 'package',
371
+ PackageName: packageName,
372
+ PackageVersion: packageVersion
373
+ };
374
+ /**
375
+ * 上传部署包
376
+ */
377
+ const zipFile = await codeToZip(targetPath, { installDependency: (serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.InstallDependency) !== undefined ? serverConfig.InstallDependency : true });
378
+ await (0, utils_1.upload)({
379
+ url: uploadUrl,
380
+ file: zipFile,
381
+ headers: (uploadHeaders || []).reduce((map, item) => {
382
+ map[item.Key] = item.Value;
383
+ return map;
384
+ }, {}) || {},
385
+ method: 'PUT'
386
+ });
387
+ }
377
388
  /**
378
389
  * 执行部署
379
390
  */
@@ -398,18 +409,18 @@ class CloudRunService {
398
409
  else {
399
410
  // 创建
400
411
  /**
401
- * 判断是容器器还是函数型
412
+ * 判断是容器还是函数型(镜像部署固定为 container)
402
413
  */
403
- let type = 'function';
404
- if (serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.Dockerfile) {
414
+ let type = imageUrl ? 'container' : 'function';
415
+ if (!imageUrl && (serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.Dockerfile)) {
405
416
  type = 'container';
406
417
  }
407
- else {
418
+ else if (!imageUrl) {
408
419
  if (await (0, fs_extra_1.pathExists)(path_1.default.join(targetPath, 'Dockerfile'))) {
409
420
  type = 'container';
410
421
  }
411
422
  }
412
- if (type === 'function') {
423
+ if (type === 'function' && !imageUrl) {
413
424
  deployInfo.BuildPacks = {
414
425
  LanguageVersion: '20.18',
415
426
  RepoLanguage: 'Node.js'
@@ -425,7 +436,8 @@ class CloudRunService {
425
436
  return this._upsertFunction(true, {
426
437
  name: serverName,
427
438
  deployInfo,
428
- serverConfig: _serverConfig
439
+ serverConfig: _serverConfig,
440
+ vpcInfo
429
441
  });
430
442
  }
431
443
  }
@@ -488,15 +500,20 @@ class CloudRunService {
488
500
  }
489
501
  }
490
502
  _upsertFunction(isNew, data) {
491
- const { name, deployInfo, serverConfig } = data;
503
+ const { name, deployInfo, serverConfig, vpcInfo } = data;
492
504
  const envConfig = this.environment.lazyEnvironmentConfig;
493
505
  const Items = parseObjectToDiffConfigItem(serverConfig);
494
- return this.tcbrService.request(isNew ? 'CreateCloudRunServer' : 'UpdateCloudRunServer', {
506
+ const basePayload = {
495
507
  EnvId: envConfig.EnvId,
496
508
  ServerName: name,
497
509
  DeployInfo: deployInfo,
498
510
  Items,
499
- });
511
+ };
512
+ // CreateCloudRunServer 支持传入 VpcInfo(仅创建时有效)
513
+ if (isNew && vpcInfo) {
514
+ basePayload.VpcInfo = vpcInfo;
515
+ }
516
+ return this.tcbrService.request(isNew ? 'CreateCloudRunServer' : 'UpdateCloudRunServer', basePayload);
500
517
  }
501
518
  }
502
519
  exports.CloudRunService = CloudRunService;
@@ -640,6 +657,15 @@ function parseObjectToDiffConfigItem(data) {
640
657
  else if (['TimerScale'].includes(k)) {
641
658
  Items.push({ Key, TimerScale: v });
642
659
  }
660
+ else if (['VpcConf'].includes(k)) {
661
+ Items.push({ Key, VpcConf: v });
662
+ }
663
+ else if (['VolumesConf'].includes(k)) {
664
+ Items.push({ Key, VolumesConf: v });
665
+ }
666
+ else if (['PublicNetConf'].includes(k)) {
667
+ Items.push({ Key, PublicNetConf: v });
668
+ }
643
669
  });
644
670
  return Items;
645
671
  }
@@ -2415,8 +2441,8 @@ async function enrichEnvInfoWithRuntimeMode(result, manager) {
2415
2441
  : "No legacy NoSQL Database/Storage observed in this env.",
2416
2442
  MysqlNotAvailable: hasMysql
2417
2443
  ? "MySQL instance(s) detected — see EnvInfo.MysqlInstances."
2418
- : "No MySQL instance is provisioned for this env. Do NOT use `manageMysqlDatabase` / `queryMysqlDatabase` (those are MySQL-specific) and do NOT read the `relational-database-tool` skill — that family targets MySQL, not CloudBase PG.",
2419
- RecommendedSkills: "Read `postgresql-development` first for new PG code. `no-sql-web-sdk` is still applicable for existing NoSQL collections in this env. Skip `relational-database-tool` (MySQL-only) entirely.",
2444
+ : "No MySQL instance is provisioned for this env. Do NOT use `manageMysqlDatabase` / `queryMysqlDatabase` (those are MySQL-specific) and do NOT read the `relational-database-mcp-cloudbase` skill — that family targets MySQL, not CloudBase PG.",
2445
+ RecommendedSkills: "Read `postgresql-development-cloudbase` first for new PG code. `cloudbase-document-database-web-sdk` is still applicable for existing NoSQL collections in this env. Skip `relational-database-mcp-cloudbase` (MySQL-only) entirely.",
2420
2446
  }
2421
2447
  : {
2422
2448
  PrimaryBackend: "PostgreSQL is NOT provisioned in this env — this is a legacy NoSQL CloudBase backend.",
@@ -2425,8 +2451,8 @@ async function enrichEnvInfoWithRuntimeMode(result, manager) {
2425
2451
  Storage: "Browser uploads use `app.uploadFile()` against the bucket exposed in `EnvInfo.Storages[].Bucket`.",
2426
2452
  MysqlNotAvailable: hasMysql
2427
2453
  ? "MySQL instance(s) detected — see EnvInfo.MysqlInstances."
2428
- : "No MySQL instance in this env. `manageMysqlDatabase` / `queryMysqlDatabase` and the `relational-database-tool` skill are not applicable.",
2429
- RecommendedSkills: "Read `no-sql-web-sdk` (and `cloud-storage-web` for uploads). `postgresql-development` and `relational-database-tool` are not applicable to this env.",
2454
+ : "No MySQL instance in this env. `manageMysqlDatabase` / `queryMysqlDatabase` and the `relational-database-mcp-cloudbase` skill are not applicable.",
2455
+ RecommendedSkills: "Read `cloudbase-document-database-web-sdk` (and `cloud-storage-web` for uploads). `postgresql-development-cloudbase` and `relational-database-mcp-cloudbase` are not applicable to this env.",
2430
2456
  };
2431
2457
  return {
2432
2458
  ...result,
@@ -3347,7 +3373,7 @@ function registerEnvTools(server) {
3347
3373
  // Register primary tool name (queryEnv)
3348
3374
  const queryEnvToolSchema = {
3349
3375
  title: "CloudBase 环境查询",
3350
- description: "查询 CloudBase 环境相关信息,支持查询环境列表、指定环境详情和安全域名。(曾用名:envQuery、listEnvs、getEnvInfo、getEnvAuthDomains)当 action=list 时,会按 DescribeEnvs 语义做列表/筛选,标准返回字段为 EnvId、Alias、Status、EnvType、Region、PackageId、PackageName、IsDefault,并支持通过 fields 白名单裁剪这些字段;aliasExact=true 时会按别名精确筛选,避免把前缀相近的环境误当作候选;即使传入 envId,action=list 也只返回摘要,不会返回完整资源明细或 expiry。如需查询某个已知 EnvId 对应环境的详细信息(包括资源字段和计费信息),必须使用 action=info 并传入目标环境的 envId 参数。action=info 会在可用时补充 BillingInfo(如 ExpireTime、PayMode、IsAutoRenew 等计费字段)。\n\n🔍 action=info 还会派生三个用于后端选型的字段:\n- `EnvInfo.RuntimeMode`:'postgresql' 或 'nosql',表示新业务建议默认使用的后端(PG 已开通时为 postgresql,否则为 nosql)。\n- `EnvInfo.RuntimeBackends`:`{postgresql, nosql, mysql}` 三个布尔值,描述当前环境实际并存的后端。\n- `EnvInfo.RuntimeModeHints`:每个后端对应的 API/工具/skill 提示。\n\nAI 在写业务/权限/存储代码前必须先看这三项:PG 模式下新业务推荐 `app.rdb()` + RLS(`managePgDatabase action=execute` 跑 `CREATE POLICY`)+ pgstore;已存在的 NoSQL 集合 / 旧 storage / `managePermissions(resourceType=\"noSqlDatabase\")` 在 PG 环境下仍然有效。真正不适用的是 MySQL:当 `RuntimeBackends.mysql === false` 时,`manageMysqlDatabase` / `queryMysqlDatabase` / `relational-database-tool` skill 都不该使用。",
3376
+ description: "查询 CloudBase 环境相关信息,支持查询环境列表、指定环境详情和安全域名。(曾用名:envQuery、listEnvs、getEnvInfo、getEnvAuthDomains)当 action=list 时,会按 DescribeEnvs 语义做列表/筛选,标准返回字段为 EnvId、Alias、Status、EnvType、Region、PackageId、PackageName、IsDefault,并支持通过 fields 白名单裁剪这些字段;aliasExact=true 时会按别名精确筛选,避免把前缀相近的环境误当作候选;即使传入 envId,action=list 也只返回摘要,不会返回完整资源明细或 expiry。如需查询某个已知 EnvId 对应环境的详细信息(包括资源字段和计费信息),必须使用 action=info 并传入目标环境的 envId 参数。action=info 会在可用时补充 BillingInfo(如 ExpireTime、PayMode、IsAutoRenew 等计费字段)。\n\n🔍 action=info 还会派生三个用于后端选型的字段:\n- `EnvInfo.RuntimeMode`:'postgresql' 或 'nosql',表示新业务建议默认使用的后端(PG 已开通时为 postgresql,否则为 nosql)。\n- `EnvInfo.RuntimeBackends`:`{postgresql, nosql, mysql}` 三个布尔值,描述当前环境实际并存的后端。\n- `EnvInfo.RuntimeModeHints`:每个后端对应的 API/工具/skill 提示。\n\nAI 在写业务/权限/存储代码前必须先看这三项:PG 模式下新业务推荐 `app.rdb()` + RLS(`managePgDatabase action=execute` 跑 `CREATE POLICY`)+ pgstore;已存在的 NoSQL 集合 / 旧 storage / `managePermissions(resourceType=\"noSqlDatabase\")` 在 PG 环境下仍然有效。真正不适用的是 MySQL:当 `RuntimeBackends.mysql === false` 时,`manageMysqlDatabase` / `queryMysqlDatabase` / `relational-database-mcp-cloudbase` skill 都不该使用。",
3351
3377
  inputSchema: {
3352
3378
  action: zod_1.z
3353
3379
  .enum(["list", "info", "domains"])
@@ -80012,6 +80038,32 @@ function parallel(list, iterator, callback)
80012
80038
  }
80013
80039
 
80014
80040
 
80041
+ /***/ }),
80042
+
80043
+ /***/ 18944:
80044
+ /***/ ((__unused_webpack_module, exports) => {
80045
+
80046
+ "use strict";
80047
+
80048
+ /**
80049
+ * 集成中心(UserKey)类型定义
80050
+ *
80051
+ * 字段与 dev-platform(weda-alternative/apps/dev-platform)对齐:
80052
+ * - 云 API 出入参为 PascalCase(EnvId / KeyID / AuthTypeCode / Ext / NeedPwd 等)
80053
+ * - Ext 为 JSON 字符串,承载动态配置字段(含加密后的敏感值)
80054
+ */
80055
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
80056
+ exports.UserKeyBindServiceType = void 0;
80057
+ /** 密钥对绑定的服务类型 */
80058
+ var UserKeyBindServiceType;
80059
+ (function (UserKeyBindServiceType) {
80060
+ UserKeyBindServiceType["MCP"] = "Mcp";
80061
+ UserKeyBindServiceType["APIS"] = "Apis";
80062
+ UserKeyBindServiceType["OTHER"] = "Other";
80063
+ UserKeyBindServiceType["CLOUD_FUNCTION"] = "CloudFunction";
80064
+ })(UserKeyBindServiceType || (exports.UserKeyBindServiceType = UserKeyBindServiceType = {}));
80065
+
80066
+
80015
80067
  /***/ }),
80016
80068
 
80017
80069
  /***/ 18959:
@@ -84596,6 +84648,102 @@ exports.colors = {
84596
84648
  })));
84597
84649
 
84598
84650
 
84651
+ /***/ }),
84652
+
84653
+ /***/ 22573:
84654
+ /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
84655
+
84656
+ "use strict";
84657
+
84658
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
84659
+ exports.getAesKey = getAesKey;
84660
+ exports.__clearAesKeyCache = __clearAesKeyCache;
84661
+ /**
84662
+ * 集成中心 AES key 获取(七彩石)
84663
+ *
84664
+ * 通过 QueryRainbowConfig 拉取 group=tcb-config(国内)/
84665
+ * frontend.tcb-config(国际)下字段 dev-platform-apis 的值作为 AES-256-CBC 密钥。
84666
+ *
84667
+ * 安全约束:
84668
+ * - key 仅在进程内存使用,按 group 维度缓存;
84669
+ * - 绝不写入任何本地文件、绝不打印日志。
84670
+ */
84671
+ const http_request_1 = __webpack_require__(72088);
84672
+ const error_1 = __webpack_require__(40430);
84673
+ /** 七彩石配置 */
84674
+ const RAINBOW_CONFIG = {
84675
+ ENV_TYPE: 'Default',
84676
+ GROUP_CN: 'tcb-config',
84677
+ GROUP_INTL: 'frontend.tcb-config',
84678
+ /** AES key 在 kvs 中的字段名 */
84679
+ CRYPTO_KEY_FIELD: 'dev-platform-apis'
84680
+ };
84681
+ const RAINBOW_HOST_CN = 'wedaapi.tcbautomation.cn';
84682
+ const RAINBOW_HOST_INTL = 'gray.decisiontcb.tencentcloud.com';
84683
+ /** 无 EnvId 时的占位 Uin */
84684
+ const FALLBACK_UIN = '3309033';
84685
+ /** 进程内缓存(按 group 维度),key 不落盘、不打印 */
84686
+ const keyCache = {};
84687
+ /**
84688
+ * 获取集成中心 AES key(进程内缓存)
84689
+ * @throws CloudBaseError 拉取或解析失败
84690
+ */
84691
+ async function getAesKey(params = {}) {
84692
+ var _a, _b;
84693
+ const { envId, isIntl = false } = params;
84694
+ const group = isIntl ? RAINBOW_CONFIG.GROUP_INTL : RAINBOW_CONFIG.GROUP_CN;
84695
+ if (keyCache[group]) {
84696
+ return keyCache[group];
84697
+ }
84698
+ const host = isIntl ? RAINBOW_HOST_INTL : RAINBOW_HOST_CN;
84699
+ const url = `https://${host}/Graydecision/V1/QueryRainbowConfig?group=${group}`;
84700
+ const payload = {
84701
+ EnvType: RAINBOW_CONFIG.ENV_TYPE,
84702
+ Group: group
84703
+ };
84704
+ if (envId) {
84705
+ payload.EnvId = envId;
84706
+ }
84707
+ else {
84708
+ payload.Uin = FALLBACK_UIN;
84709
+ }
84710
+ let resJson;
84711
+ try {
84712
+ resJson = await (0, http_request_1.fetch)(url, {
84713
+ method: 'POST',
84714
+ headers: { 'content-type': 'application/json' },
84715
+ body: JSON.stringify(payload)
84716
+ });
84717
+ }
84718
+ catch (e) {
84719
+ throw new error_1.CloudBaseError('获取集成加解密密钥失败:七彩石配置拉取请求异常');
84720
+ }
84721
+ const data = (_a = resJson === null || resJson === void 0 ? void 0 : resJson.Response) === null || _a === void 0 ? void 0 : _a.Data;
84722
+ if (!data || data.ErrorCode !== '0') {
84723
+ throw new error_1.CloudBaseError(`获取集成加解密密钥失败:七彩石返回异常(ErrorCode=${data === null || data === void 0 ? void 0 : data.ErrorCode})`);
84724
+ }
84725
+ let config;
84726
+ try {
84727
+ config = JSON.parse(data.ConfigValue)[0];
84728
+ }
84729
+ catch (e) {
84730
+ throw new error_1.CloudBaseError('获取集成加解密密钥失败:七彩石配置内容解析失败');
84731
+ }
84732
+ const kvs = ((_b = config === null || config === void 0 ? void 0 : config.kvs) === null || _b === void 0 ? void 0 : _b.kvs) || [];
84733
+ const target = kvs.find(kv => kv.key === RAINBOW_CONFIG.CRYPTO_KEY_FIELD);
84734
+ const key = target === null || target === void 0 ? void 0 : target.value;
84735
+ if (!key) {
84736
+ throw new error_1.CloudBaseError(`获取集成加解密密钥失败:七彩石配置中缺少 ${RAINBOW_CONFIG.CRYPTO_KEY_FIELD} 字段`);
84737
+ }
84738
+ keyCache[group] = key;
84739
+ return key;
84740
+ }
84741
+ /** 清空进程内缓存(仅供测试使用) */
84742
+ function __clearAesKeyCache() {
84743
+ Object.keys(keyCache).forEach(k => delete keyCache[k]);
84744
+ }
84745
+
84746
+
84599
84747
  /***/ }),
84600
84748
 
84601
84749
  /***/ 22738:
@@ -87688,6 +87836,116 @@ __decorate([
87688
87836
  ], PermissionService.prototype, "modifyEnvAuthzConfig", null);
87689
87837
 
87690
87838
 
87839
+ /***/ }),
87840
+
87841
+ /***/ 25068:
87842
+ /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
87843
+
87844
+ "use strict";
87845
+
87846
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
87847
+ if (k2 === undefined) k2 = k;
87848
+ var desc = Object.getOwnPropertyDescriptor(m, k);
87849
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
87850
+ desc = { enumerable: true, get: function() { return m[k]; } };
87851
+ }
87852
+ Object.defineProperty(o, k2, desc);
87853
+ }) : (function(o, m, k, k2) {
87854
+ if (k2 === undefined) k2 = k;
87855
+ o[k2] = m[k];
87856
+ }));
87857
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
87858
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
87859
+ }) : function(o, v) {
87860
+ o["default"] = v;
87861
+ });
87862
+ var __importStar = (this && this.__importStar) || (function () {
87863
+ var ownKeys = function(o) {
87864
+ ownKeys = Object.getOwnPropertyNames || function (o) {
87865
+ var ar = [];
87866
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
87867
+ return ar;
87868
+ };
87869
+ return ownKeys(o);
87870
+ };
87871
+ return function (mod) {
87872
+ if (mod && mod.__esModule) return mod;
87873
+ var result = {};
87874
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
87875
+ __setModuleDefault(result, mod);
87876
+ return result;
87877
+ };
87878
+ })();
87879
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
87880
+ exports.AESCrypto = void 0;
87881
+ exports.aesEncryptWithIv = aesEncryptWithIv;
87882
+ /**
87883
+ * 集成中心 AES 加解密
87884
+ *
87885
+ * 与 dev-platform 控制台的 AESCrypto(crypto-js)完全对齐,保证密文可互相解出:
87886
+ * - 算法:AES-256-CBC
87887
+ * - key:32 字节(按 UTF-8 解析)
87888
+ * - IV:每次随机 16 字节
87889
+ * - padding:PKCS7
87890
+ * - 密文格式:base64(IV) + base64(ciphertext)
87891
+ * (IV 固定 16 字节 → base64 长度恒为 24,前 24 字符即 IV)
87892
+ *
87893
+ * 使用 Node 内置 crypto 复刻,无需引入额外依赖。
87894
+ */
87895
+ const crypto = __importStar(__webpack_require__(55511));
87896
+ const ALGORITHM = 'aes-256-cbc';
87897
+ const KEY_BYTE_LENGTH = 32;
87898
+ const IV_BYTE_LENGTH = 16;
87899
+ /** base64(16 字节) 的固定长度 */
87900
+ const IV_BASE64_LENGTH = 24;
87901
+ function assertKey(key) {
87902
+ if (Buffer.byteLength(key, 'utf8') !== KEY_BYTE_LENGTH) {
87903
+ throw new Error('AES key 必须是 32 字节长度');
87904
+ }
87905
+ }
87906
+ /**
87907
+ * 使用指定 IV 加密(供测试与内部使用,保证可重现)
87908
+ */
87909
+ function aesEncryptWithIv(plaintext, key, iv) {
87910
+ assertKey(key);
87911
+ if (iv.length !== IV_BYTE_LENGTH) {
87912
+ throw new Error('IV 必须是 16 字节长度');
87913
+ }
87914
+ const cipher = crypto.createCipheriv(ALGORITHM, Buffer.from(key, 'utf8'), iv);
87915
+ const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
87916
+ return iv.toString('base64') + encrypted.toString('base64');
87917
+ }
87918
+ class AESCryptoImpl {
87919
+ /**
87920
+ * AES-256-CBC 加密,返回 base64(IV)+base64(密文)
87921
+ */
87922
+ encrypt(plaintext, key) {
87923
+ assertKey(key);
87924
+ const iv = crypto.randomBytes(IV_BYTE_LENGTH);
87925
+ return aesEncryptWithIv(plaintext, key, iv);
87926
+ }
87927
+ /**
87928
+ * 解密 base64(IV)+base64(密文) 格式的密文
87929
+ */
87930
+ decrypt(encryptedData, key) {
87931
+ assertKey(key);
87932
+ const ivBase64 = encryptedData.slice(0, IV_BASE64_LENGTH);
87933
+ const ciphertextBase64 = encryptedData.slice(IV_BASE64_LENGTH);
87934
+ const iv = Buffer.from(ivBase64, 'base64');
87935
+ if (iv.length !== IV_BYTE_LENGTH) {
87936
+ throw new Error('密文格式错误:IV 长度非法');
87937
+ }
87938
+ const decipher = crypto.createDecipheriv(ALGORITHM, Buffer.from(key, 'utf8'), iv);
87939
+ const decrypted = Buffer.concat([
87940
+ decipher.update(Buffer.from(ciphertextBase64, 'base64')),
87941
+ decipher.final()
87942
+ ]);
87943
+ return decrypted.toString('utf8');
87944
+ }
87945
+ }
87946
+ exports.AESCrypto = new AESCryptoImpl();
87947
+
87948
+
87691
87949
  /***/ }),
87692
87950
 
87693
87951
  /***/ 25177:
@@ -89896,8 +90154,12 @@ exports.MANAGE_FUNCTION_ACTIONS = [
89896
90154
  "incrementalDeployFunction", // 增量部署,需通过 pluginOptions.functions 注入实现
89897
90155
  ];
89898
90156
  const VPC_SCHEMA = zod_1.z.object({
89899
- vpcId: zod_1.z.string(),
89900
- subnetId: zod_1.z.string(),
90157
+ vpcId: zod_1.z
90158
+ .string()
90159
+ .describe("VPC ID from the real database/network console (e.g. vpc-xxxxxxxx). Required for non-native TCP DB access. Do NOT invent or use placeholders."),
90160
+ subnetId: zod_1.z
90161
+ .string()
90162
+ .describe("Subnet ID in the same VPC as the private DB endpoint (e.g. subnet-xxxxxxxx). Do NOT invent or use placeholders."),
89901
90163
  });
89902
90164
  // 镜像部署值,对应 SCF Runtime=CustomImage
89903
90165
  exports.CUSTOM_IMAGE_RUNTIME = "CustomImage";
@@ -89981,8 +90243,11 @@ const CREATE_FUNCTION_SCHEMA = zod_1.z.object({
89981
90243
  })
89982
90244
  .optional(),
89983
90245
  timeout: zod_1.z.number().optional().describe("函数超时时间"),
89984
- envVariables: zod_1.z.record(zod_1.z.string()).optional().describe("环境变量"),
89985
- vpc: VPC_SCHEMA.optional().describe("私有网络配置"),
90246
+ envVariables: zod_1.z
90247
+ .record(zod_1.z.string())
90248
+ .optional()
90249
+ .describe("环境变量。若包含 DATABASE_URL / MYSQL_* / POSTGRES_* / REDIS_* 等传统 TCP 连库变量,必须同时配置 vpc(vpcId+subnetId),且 ID 必须来自真实库/网络信息,禁止猜测。原生 app.rdb()/app.database() 不需要 VPC。"),
90250
+ vpc: VPC_SCHEMA.optional().describe("私有网络配置(出网)。非原生 SDK、用 TCP 访问 VPC 内 MySQL/PostgreSQL/Redis 时必填。vpcId/subnetId 必须与数据库内网 VPC 一致;未知时先查控制台或询问用户,禁止填占位符。"),
89986
90251
  runtime: zod_1.z
89987
90252
  .string()
89988
90253
  .optional()
@@ -90236,7 +90501,7 @@ function registerFunctionTools(server) {
90236
90501
  },
90237
90502
  {
90238
90503
  tool: "queryGateway",
90239
- action: "getAccess",
90504
+ action: "getRoute",
90240
90505
  reason: "查看该函数是否已暴露网关访问入口",
90241
90506
  },
90242
90507
  ]);
@@ -90546,7 +90811,7 @@ function registerFunctionTools(server) {
90546
90811
  raw: imageResult,
90547
90812
  }, `已基于镜像 ${createImageConfig.imageUri} 创建 HTTP 函数 ${functionName}。` +
90548
90813
  `请确认 TCR、SCF 与构建管道处于同一地域;如需通过 URL 访问,请显式调用 ` +
90549
- `manageGateway(action="createAccess", type="HTTP") 并按需调整函数安全规则。` +
90814
+ `manageGateway(action="createRoute", type="HTTP") 并按需调整函数安全规则。` +
90550
90815
  `部署后可用 queryFunctions(action="getFunctionDetail") 确认函数已就绪。`, [
90551
90816
  {
90552
90817
  tool: "queryFunctions",
@@ -90560,8 +90825,8 @@ function registerFunctionTools(server) {
90560
90825
  },
90561
90826
  {
90562
90827
  tool: "manageGateway",
90563
- action: "createAccess",
90564
- reason: "如需通过 URL 访问镜像 HTTP 函数,显式创建网关访问入口并传 type=\"HTTP\"",
90828
+ action: "createRoute",
90829
+ reason: "如需通过 URL 访问镜像 HTTP 函数,显式创建 Domain/Route 访问入口并传 type=\"HTTP\"(映射 WEB_SCF)",
90565
90830
  },
90566
90831
  ]);
90567
90832
  }
@@ -90616,12 +90881,12 @@ function registerFunctionTools(server) {
90616
90881
  if (func.type === "HTTP") {
90617
90882
  nextActions.push({
90618
90883
  tool: "manageGateway",
90619
- action: "createAccess",
90620
- reason: "如果需要通过 URL 访问 HTTP 函数,请调用 manageGateway(action=\"createAccess\") 并显式传 type=\"HTTP\",再按实际路径和鉴权需求创建访问入口,不要默认假设 /函数名 已存在",
90884
+ action: "createRoute",
90885
+ reason: "如果需要通过 URL 访问 HTTP 函数,请调用 manageGateway(action=\"createRoute\") 并显式传 type=\"HTTP\"(映射 WEB_SCF),再按实际路径和鉴权需求创建访问入口,不要默认假设 /函数名 已存在",
90621
90886
  });
90622
90887
  nextActions.push({
90623
90888
  tool: "queryGateway",
90624
- action: "getAccess",
90889
+ action: "getRoute",
90625
90890
  reason: "交付前确认 HTTP 访问路径是否已存在并已生效",
90626
90891
  });
90627
90892
  nextActions.push({
@@ -90636,7 +90901,7 @@ function registerFunctionTools(server) {
90636
90901
  });
90637
90902
  }
90638
90903
  const message = func.type === "HTTP"
90639
- ? `已创建 HTTP 函数 ${functionName}。如果后续需要通过 URL 访问,请显式调用 manageGateway(action="createAccess"),并把 type="HTTP" 一起传入,再按实际路径和鉴权需求创建访问入口。评测或其他外部调用方可能会以匿名身份访问,而且失败后不一定会把 EXCEED_AUTHORITY 再反馈给 AI;交付前请主动确认访问路径和函数安全规则,若已出现 EXCEED_AUTHORITY,请先调用 queryPermissions(action="getResourcePermission", resourceType="function", resourceId="${functionName}") 查看当前规则,再按需要使用 managePermissions(action="updateResourcePermission") 调整权限。`
90904
+ ? `已创建 HTTP 函数 ${functionName}。如果后续需要通过 URL 访问,请显式调用 manageGateway(action="createRoute"),并把 type="HTTP" 一起传入(映射 UpstreamResourceType=WEB_SCF),再按实际路径和鉴权需求创建访问入口。评测或其他外部调用方可能会以匿名身份访问,而且失败后不一定会把 EXCEED_AUTHORITY 再反馈给 AI;交付前请主动确认访问路径和函数安全规则,若已出现 EXCEED_AUTHORITY,请先调用 queryPermissions(action="getResourcePermission", resourceType="function", resourceId="${functionName}") 查看当前规则,再按需要使用 managePermissions(action="updateResourcePermission") 调整权限。`
90640
90905
  : `已创建函数 ${functionName}`;
90641
90906
  return buildEnvelope({
90642
90907
  action: input.action,
@@ -91124,8 +91389,11 @@ function registerFunctionTools(server) {
91124
91389
  zipFile: zod_1.z.string().optional().describe("仅兼容特殊场景:预先准备好的代码包 base64 编码。普通 createFunction/updateFunctionCode 默认不要先压缩 zip,优先使用 functionRootPath。"),
91125
91390
  handler: zod_1.z.string().optional().describe("函数入口"),
91126
91391
  timeout: zod_1.z.number().optional().describe("配置更新时的超时时间"),
91127
- envVariables: zod_1.z.record(zod_1.z.string()).optional().describe("配置更新时要合并的环境变量"),
91128
- vpc: VPC_SCHEMA.optional().describe("配置更新时的 VPC 信息"),
91392
+ envVariables: zod_1.z
91393
+ .record(zod_1.z.string())
91394
+ .optional()
91395
+ .describe("配置更新时要合并的环境变量。若含 DATABASE_URL / MYSQL_* / POSTGRES_* / REDIS_* 等 TCP 连库变量,必须同时提供真实 vpc(或函数已绑定完整 VPC)。禁止猜测 vpcId/subnetId。"),
91396
+ vpc: VPC_SCHEMA.optional().describe("配置更新时的 VPC 信息。非原生 TCP 连库场景必填真实 vpcId+subnetId;不要用占位符。"),
91129
91397
  params: zod_1.z.record(zod_1.z.any()).optional().describe("invokeFunction 的调用参数"),
91130
91398
  triggers: zod_1.z
91131
91399
  .array(TRIGGER_SCHEMA)
@@ -93397,7 +93665,7 @@ class EnvService {
93397
93665
  * @returns {Promise<IEnvInfoRes>}
93398
93666
  */
93399
93667
  async getEnvInfo() {
93400
- var _a;
93668
+ var _a, _b;
93401
93669
  // 使用 DescribeEnvInfo 进行单环境查询
93402
93670
  const params = {
93403
93671
  EnvId: this.envId
@@ -93405,6 +93673,7 @@ class EnvService {
93405
93673
  const response = await this.describeEnvInfo(params);
93406
93674
  const envBaseInfo = (_a = response === null || response === void 0 ? void 0 : response.EnvInfo) === null || _a === void 0 ? void 0 : _a.EnvBaseInfo;
93407
93675
  // 将 EnvBaseInfo 完整映射到 EnvInfo 类型,避免新增字段被丢弃
93676
+ // UserInfo 位于 response.EnvInfo 顶层(非 EnvBaseInfo),单独映射
93408
93677
  const envInfo = {
93409
93678
  EnvId: envBaseInfo.EnvId,
93410
93679
  Alias: envBaseInfo.Alias,
@@ -93434,7 +93703,8 @@ class EnvService {
93434
93703
  EnvPreferences: envBaseInfo.EnvPreferences,
93435
93704
  Meta: envBaseInfo.Meta,
93436
93705
  PostgreSQL: envBaseInfo.PostgreSQL,
93437
- EnvStatus: envBaseInfo.EnvStatus
93706
+ EnvStatus: envBaseInfo.EnvStatus,
93707
+ UserInfo: (_b = response === null || response === void 0 ? void 0 : response.EnvInfo) === null || _b === void 0 ? void 0 : _b.UserInfo
93438
93708
  };
93439
93709
  return {
93440
93710
  EnvInfo: envInfo,
@@ -113963,6 +114233,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
113963
114233
  };
113964
114234
  Object.defineProperty(exports, "__esModule", ({ value: true }));
113965
114235
  exports.CLOUDRUN_ACCESS_TYPES = exports.CLOUDRUN_SERVICE_TYPES = void 0;
114236
+ exports.detectCloudRunDbNetworkRisk = detectCloudRunDbNetworkRisk;
113966
114237
  exports.registerCloudRunTools = registerCloudRunTools;
113967
114238
  const child_process_1 = __webpack_require__(79646);
113968
114239
  const fs_1 = __importDefault(__webpack_require__(29021));
@@ -114005,7 +114276,7 @@ const ManageCloudRunInputSchema = {
114005
114276
  })).optional().describe('扩缩容配置数组,用于配置服务的自动扩缩容策略。可配置多个扩缩容策略'),
114006
114277
  CustomLogs: zod_1.z.string().optional().describe('自定义日志配置,用于配置服务的日志收集和存储策略'),
114007
114278
  Port: zod_1.z.number().min(1).max(65535).optional().describe('服务监听端口配置。函数型服务固定为3000,容器型服务可自定义。服务代码必须监听此端口才能正常接收请求'),
114008
- EnvParams: zod_1.z.string().optional().describe('环境变量配置,JSON字符串格式。用于传递配置信息给服务代码,如\'{"DATABASE_URL":"mysql://...","NODE_ENV":"production"}\'。SDK v5.6.1+ 会自动对传入的环境变量进行 AES-256-CBC 加密传输'),
114279
+ EnvParams: zod_1.z.string().optional().describe('环境变量配置,JSON字符串格式。用于传递配置信息给服务代码,如\'{"DATABASE_URL":"postgres://user:pass@10.x.x.x:5432/db","NODE_ENV":"production"}\'。SDK v5.6.1+ 会自动对传入的环境变量进行 AES-256-CBC 加密传输。⚠️ 若 EnvParams 含 DATABASE_URL / MYSQL_* / POSTGRES_* / REDIS_* 等传统 TCP 连库变量,必须同时配置 VpcConf,否则实例通常无法访问 VPC 内数据库'),
114009
114280
  Dockerfile: zod_1.z.string().optional().describe('Dockerfile文件名配置,仅容器型服务需要。指定用于构建容器镜像的Dockerfile文件路径,默认为项目根目录下的Dockerfile'),
114010
114281
  BuildDir: zod_1.z.string().optional().describe('构建目录配置,指定代码构建的目录路径。当代码结构与标准不同时使用,默认为项目根目录'),
114011
114282
  InternalAccess: zod_1.z.string().optional().describe('内网访问开关配置,控制是否启用内网访问。true=启用内网访问(可通过云开发SDK直接调用),false=关闭内网访问(仅公网访问)'),
@@ -114029,9 +114300,9 @@ const ManageCloudRunInputSchema = {
114029
114300
  ReplicaNum: zod_1.z.number().min(0).describe('定时扩缩容的目标副本数,最小值0(缩容到0)')
114030
114301
  })).optional().describe('定时扩缩容配置数组,用于配置服务的定时自动扩缩容策略。可配置多个时间段的扩缩容计划,支持每日/每周/每月循环'),
114031
114302
  VpcConf: zod_1.z.object({
114032
- VpcId: zod_1.z.string().describe('VPC网络ID,指定服务所在的私有网络'),
114033
- SubnetId: zod_1.z.string().describe('子网ID,指定服务所在的子网'),
114034
- }).optional().describe('VPC网络配置,用于将云托管服务部署到指定的私有网络中。需要提前创建好VPC和子网'),
114303
+ VpcId: zod_1.z.string().describe('VPC网络ID,格式如 vpc-xxxxxxxx。必须与目标数据库/Redis 处于同一地域,并优先选择同一 VPC。禁止猜测或使用占位符;须来自数据库控制台、已有资源详情、callCloudApi 或用户确认。建议首次创建即配置;已存在服务也可在 deploy 时传入,部署后必须用 queryCloudRun detail 复核是否生效'),
114304
+ SubnetId: zod_1.z.string().describe('子网ID,格式如 subnet-xxxxxxxx。云托管实例将占用该子网 IP,需确保有足够可用 IP'),
114305
+ }).optional().describe('VPC网络配置(实例出网/私有网络)。用于让云托管实例接入指定 VPC,从而内网访问 MySQL/PostgreSQL/Redis/CVM 等资源。与 OpenAccessTypes(外部如何访问本服务)是不同概念。TCP 连库场景必须配置。禁止猜测 VpcId/SubnetId,须来自数据库控制台、已有资源详情、callCloudApi 或用户确认。MCP 在创建时会将 VpcConf 映射为 SDK vpcInfo(CreateType=2);更新后必须用 queryCloudRun detail 复核 ServerConfig.VpcConf,未生效再走控制台网络设置或删建兜底'),
114035
114306
  VolumesConf: zod_1.z.array(zod_1.z.object({
114036
114307
  VolumeName: zod_1.z.string().describe('存储卷名称'),
114037
114308
  VolumeType: zod_1.z.string().describe('存储卷类型,如CFS表示云文件存储'),
@@ -114135,6 +114406,74 @@ function buildManageCloudRunErrorMessage(action, serverName, error) {
114135
114406
  }
114136
114407
  return `[manageCloudRun/${action}] ${baseMessage}\n建议:${suggestions.join(" ")}`;
114137
114408
  }
114409
+ const CLOUDRUN_DB_ENV_KEY_PATTERN = /^(DATABASE_URL|DB_HOST|DB_PORT|DB_USER|DB_PASSWORD|DB_NAME|MYSQL_|POSTGRES_|PGHOST|PGPORT|PGUSER|PGPASSWORD|PGDATABASE|PG_|REDIS_|MONGO_|MONGODB_|SQLALCHEMY_DATABASE_URI|SPRING_DATASOURCE_)/i;
114410
+ const CLOUDRUN_DB_URL_PATTERN = /(mysql|mariadb|postgres|postgresql|mongodb(\+srv)?|redis|rediss):\/\//i;
114411
+ /**
114412
+ * Detect likely TCP database/cache env usage without VpcConf.
114413
+ * Soft signal for AI agents — does not block deploy.
114414
+ */
114415
+ function detectCloudRunDbNetworkRisk(options) {
114416
+ const vpcId = options.vpcConf?.VpcId?.trim();
114417
+ const subnetId = options.vpcConf?.SubnetId?.trim();
114418
+ if (vpcId && subnetId) {
114419
+ return null;
114420
+ }
114421
+ const rawEnv = options.envParams?.trim();
114422
+ if (!rawEnv) {
114423
+ return null;
114424
+ }
114425
+ let parsed;
114426
+ try {
114427
+ const value = JSON.parse(rawEnv);
114428
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
114429
+ return null;
114430
+ }
114431
+ parsed = value;
114432
+ }
114433
+ catch {
114434
+ // Non-JSON EnvParams still may embed connection URLs as plain text.
114435
+ if (!CLOUDRUN_DB_URL_PATTERN.test(rawEnv)) {
114436
+ return null;
114437
+ }
114438
+ return {
114439
+ code: "MISSING_VPC_FOR_DB_ENV",
114440
+ message: "EnvParams appears to include a database/cache connection URL, but serverConfig.VpcConf is missing. CloudRun instances usually cannot reach VPC-private MySQL/PostgreSQL/Redis without VpcConf.",
114441
+ matchedKeys: ["<non-json-envParams>"],
114442
+ remediation: [
114443
+ "Set serverConfig.VpcConf to the same region/VPC (and a subnet with free IPs) as the database.",
114444
+ "Do NOT invent VpcId/SubnetId. Resolve real IDs from the DB console, resource detail, callCloudApi, or the user.",
114445
+ "Use the database private/intranet hostname in EnvParams, not localhost or docker-compose service names.",
114446
+ "Ensure the DB security group allows the CloudRun subnet on the DB port.",
114447
+ "Re-deploy after VpcConf is set. OpenAccessTypes alone does not provide VPC egress to databases.",
114448
+ ],
114449
+ };
114450
+ }
114451
+ const matchedKeys = [];
114452
+ for (const [key, value] of Object.entries(parsed)) {
114453
+ if (CLOUDRUN_DB_ENV_KEY_PATTERN.test(key)) {
114454
+ matchedKeys.push(key);
114455
+ continue;
114456
+ }
114457
+ if (typeof value === "string" && CLOUDRUN_DB_URL_PATTERN.test(value)) {
114458
+ matchedKeys.push(key);
114459
+ }
114460
+ }
114461
+ if (matchedKeys.length === 0) {
114462
+ return null;
114463
+ }
114464
+ return {
114465
+ code: "MISSING_VPC_FOR_DB_ENV",
114466
+ message: "EnvParams includes database/cache connection settings, but serverConfig.VpcConf is missing. Deploy may succeed while runtime DB connections fail.",
114467
+ matchedKeys,
114468
+ remediation: [
114469
+ "Set serverConfig.VpcConf.VpcId and SubnetId to the database VPC/subnet (same region).",
114470
+ "Do NOT invent VpcId/SubnetId. Resolve real IDs from the DB console, resource detail, callCloudApi, or the user.",
114471
+ "Use the private DB endpoint in EnvParams.",
114472
+ "Confirm security group / allowlist permits CloudRun subnet access to the DB port.",
114473
+ "Do not confuse OpenAccessTypes (ingress) with VpcConf (egress to VPC resources).",
114474
+ ],
114475
+ };
114476
+ }
114138
114477
  function getCloudRunQueryServerName(input) {
114139
114478
  return input.detailServerName || input.serverName;
114140
114479
  }
@@ -114632,6 +114971,17 @@ for await (let x of res.textStream) {
114632
114971
  if (input.serverConfig) {
114633
114972
  deployParams.serverConfig = input.serverConfig;
114634
114973
  }
114974
+ // Manager SDK create path prefers top-level vpcInfo (CreateCloudRunServer.VpcInfo).
114975
+ // Map serverConfig.VpcConf → vpcInfo so first-time create actually binds VPC.
114976
+ // Update path still sends Items.VpcConf; platform may ignore VPC changes on existing services.
114977
+ const vpcConf = input.serverConfig?.VpcConf;
114978
+ if (vpcConf?.VpcId?.trim() && vpcConf?.SubnetId?.trim()) {
114979
+ deployParams.vpcInfo = {
114980
+ VpcId: vpcConf.VpcId.trim(),
114981
+ CreateType: 2,
114982
+ SubnetIds: [vpcConf.SubnetId.trim()],
114983
+ };
114984
+ }
114635
114985
  let result;
114636
114986
  try {
114637
114987
  result = await cloudrunService.deploy(deployParams);
@@ -114705,6 +115055,14 @@ for await (let x of res.textStream) {
114705
115055
  // Notification failure should not affect deployment flow
114706
115056
  // Error is already logged in sendDeployNotification
114707
115057
  }
115058
+ const dbNetworkRisk = detectCloudRunDbNetworkRisk({
115059
+ envParams: input.serverConfig?.EnvParams,
115060
+ vpcConf: input.serverConfig?.VpcConf,
115061
+ });
115062
+ const warnings = dbNetworkRisk ? [dbNetworkRisk] : [];
115063
+ const warningSuffix = dbNetworkRisk
115064
+ ? ` Warning: ${dbNetworkRisk.message} Set serverConfig.VpcConf before relying on DB connectivity.`
115065
+ : "";
114708
115066
  return {
114709
115067
  content: [
114710
115068
  {
@@ -114717,9 +115075,10 @@ for await (let x of res.textStream) {
114717
115075
  deployPath: targetPath,
114718
115076
  serverType: serverType,
114719
115077
  cloudbasercGenerated: true,
114720
- consoleUrl
115078
+ consoleUrl,
115079
+ ...(warnings.length > 0 ? { warnings } : {}),
114721
115080
  },
114722
- message: `Triggered deployment for ${serverType} service '${input.serverName}' from ${targetPath}. You can follow the progress in ${consoleUrl}`
115081
+ message: `Triggered deployment for ${serverType} service '${input.serverName}' from ${targetPath}. You can follow the progress in ${consoleUrl}.${warningSuffix}`
114723
115082
  }, null, 2)
114724
115083
  }
114725
115084
  ]
@@ -136914,7 +137273,7 @@ class TelemetryReporter {
136914
137273
  const nodeVersion = process.version; // Node.js版本
136915
137274
  const arch = os_1.default.arch(); // 系统架构
136916
137275
  // 从构建时注入的版本号获取MCP版本信息
136917
- const mcpVersion = process.env.npm_package_version || "2.24.1" || 0;
137276
+ const mcpVersion = process.env.npm_package_version || "2.25.0" || 0;
136918
137277
  return {
136919
137278
  userAgent: `${osType} ${osRelease} ${arch} ${nodeVersion} CloudBase-MCP/${mcpVersion}`,
136920
137279
  deviceId: this.deviceId,
@@ -172843,6 +173202,7 @@ const permission_1 = __webpack_require__(24786);
172843
173202
  const sandbox_1 = __webpack_require__(48488);
172844
173203
  const aiModel_1 = __webpack_require__(34800);
172845
173204
  const monitor_1 = __webpack_require__(8901);
173205
+ const integration_1 = __webpack_require__(72785);
172846
173206
  class Environment {
172847
173207
  constructor(context, envId) {
172848
173208
  this.inited = false;
@@ -172869,6 +173229,7 @@ class Environment {
172869
173229
  this.sandboxService = new sandbox_1.SandboxService(this);
172870
173230
  this.aiModelService = new aiModel_1.AiModelService(this);
172871
173231
  this.monitorService = new monitor_1.MonitorService(this);
173232
+ this.integrationService = new integration_1.IntegrationService(this);
172872
173233
  }
172873
173234
  async lazyInit() {
172874
173235
  if (!this.inited) {
@@ -172946,6 +173307,9 @@ class Environment {
172946
173307
  getMonitorService() {
172947
173308
  return this.monitorService;
172948
173309
  }
173310
+ getIntegrationService() {
173311
+ return this.integrationService;
173312
+ }
172949
173313
  getCommonService(serviceType = 'tcb', serviceVersion) {
172950
173314
  return new common_1.CommonService(this, serviceType, serviceVersion);
172951
173315
  }
@@ -197005,7 +197369,7 @@ ${envIdSection}
197005
197369
  ## 环境信息
197006
197370
  - 操作系统: ${os_1.default.type()} ${os_1.default.release()}
197007
197371
  - Node.js版本: ${process.version}
197008
- - MCP 版本:${process.env.npm_package_version || "2.24.1" || 0}
197372
+ - MCP 版本:${process.env.npm_package_version || "2.25.0" || 0}
197009
197373
  - 系统架构: ${os_1.default.arch()}
197010
197374
  - 时间: ${new Date().toISOString()}
197011
197375
  - 请求ID: ${requestId}
@@ -197086,7 +197450,7 @@ function createWrappedHandler(name, handler, server) {
197086
197450
  requestId: (typeof error === 'object' && error && 'requestId' in error) ? error.requestId : '',
197087
197451
  ide: server.ide || process.env.INTEGRATION_IDE || ''
197088
197452
  });
197089
- const mcpVersion = true ? "2.24.1" : 0;
197453
+ const mcpVersion = true ? "2.25.0" : 0;
197090
197454
  const requestIdSuffix = requestId ? `\n🆔 RequestId: ${requestId}` : '';
197091
197455
  const enhancedErrorMessage = `${errorMessage}${requestIdSuffix}\n\n📦 CloudBase MCP v${mcpVersion}\n🔗 遇到问题?请复制以下链接到浏览器打开\n即可自动携带错误详情快速创建 GitHub Issue:\n${issueLink}`;
197092
197456
  // 创建新的错误对象,保持原有的错误类型但更新消息
@@ -199117,6 +199481,7 @@ module.exports = cmp
199117
199481
 
199118
199482
  "use strict";
199119
199483
 
199484
+ /* eslint-disable @typescript-eslint/member-ordering */
199120
199485
  var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
199121
199486
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
199122
199487
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -199359,27 +199724,30 @@ class StorageService {
199359
199724
  getMetadataController.loadTasks(tasks);
199360
199725
  const files = await getMetadataController.run();
199361
199726
  // 对文件上传进行处理
199362
- const cos = this.getCos(parallel);
199363
- const uploadFiles = util_1.default.promisify(cos.uploadFiles).bind(cos);
199364
- const params = {
199365
- files,
199366
- SliceSize: BIG_FILE_SIZE,
199727
+ // 构造 files 数组(格式:{ localPath, cloudPath }),传给 uploadFilesCustom
199728
+ const filesForUpload = fileStatsList
199729
+ .filter(stats => !stats.isDir)
199730
+ .map(stats => ({
199731
+ localPath: stats.filePath,
199732
+ cloudPath: stats.cloudFileKey
199733
+ }));
199734
+ // 调用 uploadFilesCustom(智能混合模式:并行→串行)
199735
+ // 不再直接调用 uploadFilesWithRetry(纯并行重试,容易 socket hang up)
199736
+ return this.uploadFilesCustom({
199737
+ files: filesForUpload,
199738
+ bucket,
199739
+ region,
199740
+ ignore: [],
199367
199741
  onProgress,
199368
- onFileFinish
199369
- };
199370
- return this.uploadFilesWithRetry({
199371
- uploadFiles,
199372
- options: params,
199373
- times: retryCount,
199374
- interval: retryInterval,
199375
- failedFiles: []
199742
+ onFileFinish,
199743
+ fileId,
199744
+ parallel,
199745
+ retryCount,
199746
+ retryInterval
199376
199747
  });
199377
199748
  }
199378
- /**
199379
- * 批量上传文件
199380
- * @param options
199381
- */
199382
199749
  async uploadFilesCustom(options) {
199750
+ var _a;
199383
199751
  const { files, bucket, region, ignore, onProgress, onFileFinish, fileId = true, parallel = 20, retryCount = 0, retryInterval = 500 } = options;
199384
199752
  if (!files || !files.length) {
199385
199753
  return;
@@ -199393,6 +199761,146 @@ class StorageService {
199393
199761
  };
199394
199762
  })
199395
199763
  .filter(item => ((ignore === null || ignore === void 0 ? void 0 : ignore.length) ? !micromatch_1.default.isMatch(item.filePath, ignore) : true));
199764
+ const totalFiles = fileList.length;
199765
+ console.log(`[uploadFiles] 准备上传 ${totalFiles} 个文件`);
199766
+ // 模式 1:完全串行模式(最稳定)
199767
+ if (process.env.COS_UPLOAD_SERIAL === 'true') {
199768
+ console.log(`[uploadFiles] 串行安全模式已启用`);
199769
+ const serialResult = await this.uploadFilesSerial({
199770
+ fileList,
199771
+ bucket,
199772
+ region,
199773
+ fileId,
199774
+ retryCount,
199775
+ retryInterval,
199776
+ onFileFinish
199777
+ });
199778
+ if (serialResult.failedFiles.length > 0) {
199779
+ throw new error_1.CloudBaseError(`[uploadFiles] 串行上传仍失败 (${serialResult.failedFiles.length} 个):\n` +
199780
+ serialResult.failedFiles.map(f => `- ${f.cloudFileKey}: ${f.error}`).join('\n'), { code: 'HOSTING_DEPLOY_FAILED' });
199781
+ }
199782
+ return {
199783
+ successCount: serialResult.successCount,
199784
+ totalFiles,
199785
+ failedFiles: []
199786
+ };
199787
+ }
199788
+ // 模式 2:智能混合模式(默认)
199789
+ // - 先并行快速上传
199790
+ // - 失败的文件自动走串行重试
199791
+ console.log(`[uploadFiles] 智能混合模式:先并行快速上传,失败文件自动串行重试`);
199792
+ // 第一步:并行上传(分批)
199793
+ const parallelFailedFiles = await this.uploadFilesParallel({
199794
+ fileList,
199795
+ bucket,
199796
+ region,
199797
+ fileId,
199798
+ parallel,
199799
+ retryCount: 0, // 并行阶段不重试,失败后交给串行阶段
199800
+ retryInterval,
199801
+ onFileFinish
199802
+ });
199803
+ // 第二步:串行重试失败的文件
199804
+ if (parallelFailedFiles && parallelFailedFiles.length > 0) {
199805
+ console.warn(`[uploadFiles] 并行阶段 ${parallelFailedFiles.length} 个文件失败,自动切换串行重试...`);
199806
+ const serialResult = await this.uploadFilesSerial({
199807
+ fileList: parallelFailedFiles,
199808
+ bucket,
199809
+ region,
199810
+ fileId: false, // 串行重试不需要再获取 fileId
199811
+ retryCount,
199812
+ retryInterval,
199813
+ onFileFinish
199814
+ });
199815
+ // 检查是否还有失败
199816
+ if (((_a = serialResult === null || serialResult === void 0 ? void 0 : serialResult.failedFiles) === null || _a === void 0 ? void 0 : _a.length) > 0) {
199817
+ throw new error_1.CloudBaseError(`[uploadFiles] 串行重试后仍失败 (${serialResult.failedFiles.length} 个):\n` +
199818
+ serialResult.failedFiles.map(f => `- ${f.cloudFileKey}: ${f.error}`).join('\n'), { code: 'HOSTING_DEPLOY_FAILED' });
199819
+ }
199820
+ }
199821
+ console.log(`[uploadFiles] 全部完成!共处理 ${totalFiles} 个文件`);
199822
+ return {
199823
+ successCount: totalFiles,
199824
+ totalFiles,
199825
+ failedFiles: []
199826
+ };
199827
+ }
199828
+ /**
199829
+ * 串行安全模式:逐文件上传(类似自定义脚本 deploy.js 的方式)
199830
+ * 特点:
199831
+ * - 每个文件独立 HTTP 连接,不会出现 socket hang up
199832
+ * - 单线程串行,稳定性最高
199833
+ * - 适合大规模目录(>1000 文件)或有网络问题的场景
199834
+ */
199835
+ // eslint-disable-next-line @typescript-eslint/member-ordering
199836
+ async uploadFilesSerial(options) {
199837
+ const { fileList, bucket, region, fileId, retryCount, retryInterval, onFileFinish } = options;
199838
+ let uploadedCount = 0;
199839
+ let failedFiles = [];
199840
+ for (let i = 0; i < fileList.length; i++) {
199841
+ const file = fileList[i];
199842
+ let success = false;
199843
+ // 重试机制
199844
+ for (let attempt = 0; attempt <= retryCount; attempt++) {
199845
+ try {
199846
+ // 每个文件创建新的 COS 实例(确保连接隔离)
199847
+ const cos = this.getCos(1); // 严格单并发
199848
+ const putObject = util_1.default.promisify(cos.putObject).bind(cos);
199849
+ await putObject({
199850
+ Bucket: bucket,
199851
+ Region: region,
199852
+ Key: file.cloudFileKey,
199853
+ Body: fs_1.default.createReadStream(file.filePath),
199854
+ ContentLength: fs_1.default.statSync(file.filePath).size,
199855
+ });
199856
+ uploadedCount++;
199857
+ // 显示进度(每 100 个文件或最后 5 个)
199858
+ if (uploadedCount % 100 === 0 || fileList.length - i <= 5) {
199859
+ console.log(`[serial-upload] [${uploadedCount}/${fileList.length}] ${file.cloudFileKey}`);
199860
+ }
199861
+ success = true;
199862
+ // 回调成功
199863
+ if (onFileFinish) {
199864
+ onFileFinish(null, {}, file);
199865
+ }
199866
+ break;
199867
+ }
199868
+ catch (error) {
199869
+ const isLastAttempt = attempt === retryCount;
199870
+ if (isLastAttempt) {
199871
+ console.error(`[serial-upload] [${i + 1}/${fileList.length}] ${file.cloudFileKey}: ${error.message || error.code}`);
199872
+ // 记录失败的文件
199873
+ failedFiles.push(Object.assign(Object.assign({}, file), { error: error.message || String(error) }));
199874
+ // 回调失败
199875
+ if (onFileFinish) {
199876
+ onFileFinish(error, null, file);
199877
+ }
199878
+ }
199879
+ else {
199880
+ // 等待后重试
199881
+ await new Promise(resolve => { setTimeout(resolve, retryInterval); });
199882
+ }
199883
+ }
199884
+ }
199885
+ }
199886
+ console.log(`[serial-upload] 完成:成功 ${uploadedCount}/${fileList.length},失败 ${failedFiles.length}`);
199887
+ return {
199888
+ successCount: uploadedCount,
199889
+ failedFiles
199890
+ };
199891
+ }
199892
+ /**
199893
+ * 并发模式:使用 cos.uploadFiles() 批量上传
199894
+ * 特点:
199895
+ * - 速度快,利用 SDK 内部并行能力
199896
+ * - 分批上传 + 自动刷新凭证
199897
+ * - 适合小规模目录或网络良好的场景
199898
+ *
199899
+ * @returns 失败的文件列表(空的表示全部成功)
199900
+ */
199901
+ // eslint-disable-next-line @typescript-eslint/member-ordering
199902
+ async uploadFilesParallel(options) {
199903
+ const { fileList, bucket, region, fileId, parallel, retryCount, retryInterval, onProgress, onFileFinish } = options;
199396
199904
  // 生成上传文件属性
199397
199905
  const tasks = fileList.map(stats => async () => {
199398
199906
  let cosFileId;
@@ -199408,31 +199916,89 @@ class StorageService {
199408
199916
  'x-cos-meta-fileid': cosFileId
199409
199917
  };
199410
199918
  });
199411
- // 控制请求并发
199919
+ // 控制请求并发(获取 metadata 阶段)
199412
199920
  const asyncTaskController = new parallel_1.AsyncTaskParallelController(parallel, 50);
199413
199921
  asyncTaskController.loadTasks(tasks);
199414
- fileList = await asyncTaskController.run();
199415
- const cos = this.getCos(parallel);
199416
- const uploadFiles = util_1.default.promisify(cos.uploadFiles).bind(cos);
199417
- const params = {
199418
- files: fileList,
199419
- SliceSize: BIG_FILE_SIZE,
199420
- onProgress,
199421
- onFileFinish
199422
- };
199423
- // return uploadFiles({
199424
- // onProgress,
199425
- // onFileFinish,
199426
- // files: fileList,
199427
- // SliceSize: BIG_FILE_SIZE
199428
- // })
199429
- return this.uploadFilesWithRetry({
199430
- uploadFiles,
199431
- options: params,
199432
- times: retryCount,
199433
- interval: retryInterval,
199434
- failedFiles: []
199435
- });
199922
+ const processedFileList = await asyncTaskController.run();
199923
+ // 分批上传,避免单次上传时间过长导致 STS 临时凭证过期
199924
+ // 默认每批 500 个文件,可通过环境变量 COS_UPLOAD_BATCH_SIZE 自定义
199925
+ const BATCH_SIZE = process.env.COS_UPLOAD_BATCH_SIZE
199926
+ ? parseInt(process.env.COS_UPLOAD_BATCH_SIZE, 10)
199927
+ : 500;
199928
+ // 收集所有批次失败的文件
199929
+ let allFailedFiles = [];
199930
+ for (let batchStart = 0; batchStart < processedFileList.length; batchStart += BATCH_SIZE) {
199931
+ const batchEnd = Math.min(batchStart + BATCH_SIZE, processedFileList.length);
199932
+ const batchFiles = processedFileList.slice(batchStart, batchEnd);
199933
+ console.log(`[parallel-upload] 正在上传第 ${Math.floor(batchStart / BATCH_SIZE) + 1} 批次 (文件 ${batchStart + 1}-${batchEnd}/${processedFileList.length})`);
199934
+ // 每批次重新获取 COS 实例(刷新 STS 临时凭证)
199935
+ const cos = this.getCos(parallel);
199936
+ const uploadFiles = util_1.default.promisify(cos.uploadFiles).bind(cos);
199937
+ // 跟踪本批次中真正失败的文件(通过 onFileFinish 回调)
199938
+ const batchFailedFiles = [];
199939
+ const batchSuccessFiles = new Set();
199940
+ const params = {
199941
+ files: batchFiles,
199942
+ SliceSize: BIG_FILE_SIZE,
199943
+ onProgress,
199944
+ onFileFinish: (error, result, file) => {
199945
+ // 空值检查:防止 file 为 null/undefined
199946
+ if (!file) {
199947
+ console.warn('[parallel-upload] onFileFinish received null/undefined file');
199948
+ return;
199949
+ }
199950
+ const fileKey = file.Key || file.cloudFileKey;
199951
+ if (error) {
199952
+ batchFailedFiles.push({
199953
+ filePath: file.FilePath || file.filePath,
199954
+ cloudFileKey: fileKey,
199955
+ error: error.message || String(error)
199956
+ });
199957
+ }
199958
+ else {
199959
+ batchSuccessFiles.add(fileKey);
199960
+ }
199961
+ // 继续调用用户传入的回调(转换参数格式)
199962
+ // 注意:cloudFileKey 和 filePath 是 CLI 期望的格式
199963
+ onFileFinish === null || onFileFinish === void 0 ? void 0 : onFileFinish(error, result, Object.assign(Object.assign({}, file), { cloudFileKey: fileKey, filePath: file.FilePath || file.filePath }));
199964
+ }
199965
+ };
199966
+ try {
199967
+ await this.uploadFilesWithRetry({
199968
+ uploadFiles,
199969
+ options: params,
199970
+ times: retryCount,
199971
+ interval: retryInterval,
199972
+ failedFiles: []
199973
+ });
199974
+ console.log(`[parallel-upload] 第 ${Math.floor(batchStart / BATCH_SIZE) + 1} 批次上传成功`);
199975
+ }
199976
+ catch (error) {
199977
+ console.error(`[parallel-upload] 第 ${Math.floor(batchStart / BATCH_SIZE) + 1} 批次上传失败:`, error.message || error.code || error);
199978
+ // 只记录真正失败的文件(通过 onFileFinish 回调跟踪)
199979
+ // 如果 onFileFinish 没有被调用(如 socket hang up),则标记整个批次为失败
199980
+ if (batchFailedFiles.length === 0 && batchSuccessFiles.size === 0) {
199981
+ // onFileFinish 未被调用,整个批次失败
199982
+ allFailedFiles.push(...batchFiles.map(f => ({
199983
+ filePath: f.FilePath || f.filePath,
199984
+ cloudFileKey: f.Key || f.cloudFileKey,
199985
+ error: error.message || String(error)
199986
+ })));
199987
+ }
199988
+ else {
199989
+ // onFileFinish 已被调用,只记录失败的文件
199990
+ allFailedFiles.push(...batchFailedFiles);
199991
+ }
199992
+ }
199993
+ }
199994
+ // 返回失败文件列表(不抛异常,让调用方决定是否走串行重试)
199995
+ if (allFailedFiles.length > 0) {
199996
+ console.warn(`[parallel-upload] 共 ${allFailedFiles.length}/${processedFileList.length} 个文件并行上传失败,等待串行重试...`);
199997
+ }
199998
+ else {
199999
+ console.log(`[parallel-upload] 全部 ${processedFileList.length} 个文件并行上传成功`);
200000
+ }
200001
+ return allFailedFiles;
199436
200002
  }
199437
200003
  /**
199438
200004
  * 创建一个空的文件夹
@@ -201624,6 +202190,7 @@ class StorageService {
201624
202190
  }
201625
202191
  /**
201626
202192
  * 获取 COS 配置
202193
+ * @param parallel 并发数量
201627
202194
  */
201628
202195
  getCos(parallel = 20) {
201629
202196
  const internalEndpoint = this.environment.cloudBaseContext.isInternalEndpoint();
@@ -201869,6 +202436,16 @@ __decorate([
201869
202436
  ], StorageService.prototype, "uploadDirectoryCustom", null);
201870
202437
  __decorate([
201871
202438
  (0, utils_1.preLazy)()
202439
+ /**
202440
+ * 上传文件(支持精准重试失败文件)
202441
+ *
202442
+ * 智能混合模式(默认):
202443
+ * 1. 先并行快速上传(速度快)
202444
+ * 2. 失败的文件自动走串行重试(稳定性高)
202445
+ *
202446
+ * 可选模式:
202447
+ * - COS_UPLOAD_SERIAL=true:完全串行模式(最稳定但慢)
202448
+ */
201872
202449
  ], StorageService.prototype, "uploadFilesCustom", null);
201873
202450
  __decorate([
201874
202451
  (0, utils_1.preLazy)()
@@ -202679,6 +203256,367 @@ Promise.filter = function (promises, fn, options) {
202679
203256
  };
202680
203257
 
202681
203258
 
203259
+ /***/ }),
203260
+
203261
+ /***/ 72785:
203262
+ /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
203263
+
203264
+ "use strict";
203265
+
203266
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
203267
+ if (k2 === undefined) k2 = k;
203268
+ var desc = Object.getOwnPropertyDescriptor(m, k);
203269
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
203270
+ desc = { enumerable: true, get: function() { return m[k]; } };
203271
+ }
203272
+ Object.defineProperty(o, k2, desc);
203273
+ }) : (function(o, m, k, k2) {
203274
+ if (k2 === undefined) k2 = k;
203275
+ o[k2] = m[k];
203276
+ }));
203277
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
203278
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
203279
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
203280
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
203281
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
203282
+ };
203283
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
203284
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
203285
+ };
203286
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
203287
+ exports.IntegrationService = void 0;
203288
+ const error_1 = __webpack_require__(40430);
203289
+ const utils_1 = __webpack_require__(62358);
203290
+ const constants_1 = __webpack_require__(79640);
203291
+ const crypto_1 = __webpack_require__(25068);
203292
+ const secretKey_1 = __webpack_require__(22573);
203293
+ __exportStar(__webpack_require__(18944), exports);
203294
+ /**
203295
+ * 集成中心(UserKey)服务
203296
+ *
203297
+ * 复用现有 tcb 云 API(version 2018-06-08),统一携带 Business='integration'。
203298
+ *
203299
+ * 提供两种使用模式:
203300
+ * 1. 透传模式:直接传 `ext`(JSON 字符串),SDK 原样传递给云 API(向后兼容)
203301
+ * 2. 自动加解密模式:传 `envVariables`(明文键值对),SDK 自动识别模板中的
203302
+ * password 字段、拉取七彩石密钥完成加密后拼装 ext
203303
+ */
203304
+ class IntegrationService {
203305
+ constructor(environment) {
203306
+ this.environment = environment;
203307
+ this.tcbService = new utils_1.CloudService(environment.cloudBaseContext, 'tcb', '2018-06-08');
203308
+ }
203309
+ // =========== 只读 API ===========
203310
+ /** 列出集成模板(密钥对模版列表) */
203311
+ async listTemplates(params = {}) {
203312
+ var _a, _b, _c;
203313
+ const { envId } = this.getEnvInfo();
203314
+ return this.tcbService.request('GetUserKeyTemplateList', {
203315
+ EnvId: envId,
203316
+ Offset: (_a = params.offset) !== null && _a !== void 0 ? _a : 0,
203317
+ Limit: (_b = params.limit) !== null && _b !== void 0 ? _b : 100,
203318
+ AuthTypeCode: params.authTypeCode,
203319
+ AuthTypeName: params.authTypeName,
203320
+ Business: (_c = params.business) !== null && _c !== void 0 ? _c : constants_1.USER_KEY_BUSINESS // 默认 integration
203321
+ });
203322
+ }
203323
+ /** 列出集成实例(密钥对列表) */
203324
+ async listUserKeys(params = {}) {
203325
+ var _a, _b, _c;
203326
+ const { envId } = this.getEnvInfo();
203327
+ return this.tcbService.request('GetUserKeyList', {
203328
+ EnvId: envId,
203329
+ Offset: (_a = params.offset) !== null && _a !== void 0 ? _a : 0,
203330
+ Limit: (_b = params.limit) !== null && _b !== void 0 ? _b : 100,
203331
+ Name: params.name,
203332
+ AuthTypeCode: params.authTypeCode,
203333
+ Business: (_c = params.business) !== null && _c !== void 0 ? _c : constants_1.USER_KEY_BUSINESS // 默认 integration
203334
+ });
203335
+ }
203336
+ /**
203337
+ * 查询单个集成实例详情。
203338
+ *
203339
+ * 当 `decryptPwd=true` 时,SDK 自动拉取模板识别 password 字段并从七彩石
203340
+ * 获取 AES 密钥解密,Ext 中 password 字段返回明文。解密失败时保留密文原值,
203341
+ * 不中断流程。
203342
+ */
203343
+ async getUserKey(params) {
203344
+ var _a, _b, _c;
203345
+ this.validateRequiredParams(params, ['keyId']);
203346
+ const { envId } = this.getEnvInfo();
203347
+ const result = await this.tcbService.request('GetOneUserKey', {
203348
+ EnvId: envId,
203349
+ KeyID: params.keyId,
203350
+ NeedPwd: (_a = params.needPwd) !== null && _a !== void 0 ? _a : false,
203351
+ Business: (_b = params.business) !== null && _b !== void 0 ? _b : constants_1.USER_KEY_BUSINESS // 默认 integration
203352
+ });
203353
+ // 自动解密 password 字段
203354
+ if (params.decryptPwd && ((_c = result.UserKeyInfo) === null || _c === void 0 ? void 0 : _c.Ext)) {
203355
+ result.UserKeyInfo = await this.decryptExtIfNeeded(result.UserKeyInfo, envId);
203356
+ }
203357
+ return result;
203358
+ }
203359
+ // =========== 写 API ===========
203360
+ /**
203361
+ * 创建集成实例。
203362
+ *
203363
+ * 支持两种传参方式:
203364
+ * - 传 `ext`:透传模式,原样传递给云 API(向后兼容,调用方自行加密)
203365
+ * - 传 `envVariables`:自动加密模式,SDK 识别模板 password 字段并加密后拼装 ext
203366
+ * - 两者都传时以 `ext` 优先
203367
+ */
203368
+ async createUserKey(params) {
203369
+ var _a;
203370
+ this.validateRequiredParams(params, ['name', 'authTypeCode']);
203371
+ const { envId } = this.getEnvInfo();
203372
+ const ext = await this.buildExtFromParams(envId, params.authTypeCode, { ext: params.ext, envVariables: params.envVariables });
203373
+ return this.tcbService.request('CreateUserKey', {
203374
+ EnvId: envId,
203375
+ Name: params.name,
203376
+ KeyID: params.keyId,
203377
+ AuthTypeCode: params.authTypeCode,
203378
+ Description: params.description,
203379
+ Ext: ext,
203380
+ TicketID: params.ticketId,
203381
+ Business: (_a = params.business) !== null && _a !== void 0 ? _a : constants_1.USER_KEY_BUSINESS, // 默认 integration
203382
+ DemoCodeFunctionName: params.demoCodeFunctionName,
203383
+ RuntimeConfig: params.runtimeConfig
203384
+ });
203385
+ }
203386
+ /**
203387
+ * 更新集成实例。
203388
+ *
203389
+ * 支持两种传参方式:
203390
+ * - 传 `ext`:透传模式,原样传递给云 API(向后兼容)
203391
+ * - 传 `envVariables`:自动加密模式,与现有 Ext 合并后加密写入
203392
+ * - 两者都传时以 `ext` 优先
203393
+ * - 都未传时后端保留原有 Ext 不变
203394
+ */
203395
+ async updateUserKey(params) {
203396
+ var _a;
203397
+ this.validateRequiredParams(params, ['keyId']);
203398
+ const { envId } = this.getEnvInfo();
203399
+ let ext = params.ext;
203400
+ // 传了 envVariables 且未传 ext:需要合并现有 Ext 后加密
203401
+ if (params.envVariables && !params.ext) {
203402
+ ext = await this.buildMergedExtForUpdate(envId, params.keyId, { authTypeCode: params.authTypeCode, envVariables: params.envVariables });
203403
+ }
203404
+ return this.tcbService.request('UpdateUserKey', {
203405
+ EnvId: envId,
203406
+ KeyID: params.keyId,
203407
+ Name: params.name,
203408
+ AuthTypeCode: params.authTypeCode,
203409
+ Description: params.description,
203410
+ Ext: ext,
203411
+ DemoCodeFunctionName: params.demoCodeFunctionName,
203412
+ Business: (_a = params.business) !== null && _a !== void 0 ? _a : constants_1.USER_KEY_BUSINESS, // 默认 integration
203413
+ RuntimeConfig: params.runtimeConfig
203414
+ });
203415
+ }
203416
+ /** 删除集成实例 */
203417
+ async deleteUserKey(params) {
203418
+ this.validateRequiredParams(params, ['keyId']);
203419
+ const { envId } = this.getEnvInfo();
203420
+ return this.tcbService.request('DeleteUserKey', {
203421
+ EnvId: envId,
203422
+ KeyID: params.keyId
203423
+ });
203424
+ }
203425
+ // =========== 辅助方法 ===========
203426
+ /** 获取环境信息 */
203427
+ getEnvInfo() {
203428
+ const envConfig = this.environment.lazyEnvironmentConfig;
203429
+ return {
203430
+ envId: envConfig.EnvId
203431
+ };
203432
+ }
203433
+ /** 校验必填参数 */
203434
+ validateRequiredParams(params, fields) {
203435
+ for (const field of fields) {
203436
+ if (!params[field]) {
203437
+ throw new error_1.CloudBaseError(`${field} 是必填参数`);
203438
+ }
203439
+ }
203440
+ }
203441
+ /**
203442
+ * 根据 ext / envVariables 参数构造最终的 Ext JSON 字符串。
203443
+ * ext 优先;envVariables 由 SDK 自动加密 password 字段。
203444
+ */
203445
+ async buildExtFromParams(envId, authTypeCode, opts) {
203446
+ // ext 优先(透传模式)
203447
+ if (opts.ext !== undefined) {
203448
+ return opts.ext;
203449
+ }
203450
+ // 自动加密模式
203451
+ if (opts.envVariables && Object.keys(opts.envVariables).length > 0) {
203452
+ return this.encryptAndStringify(envId, authTypeCode, opts.envVariables);
203453
+ }
203454
+ return undefined;
203455
+ }
203456
+ /**
203457
+ * 更新场景:将 envVariables 与现有实例的 Ext 合并后加密。
203458
+ * 需要先查询现有实例获取当前的 authTypeCode 和 Ext。
203459
+ */
203460
+ async buildMergedExtForUpdate(envId, keyId, opts) {
203461
+ var _a, _b;
203462
+ const { envVariables, authTypeCode } = opts;
203463
+ if (!envVariables || Object.keys(envVariables).length === 0) {
203464
+ return undefined;
203465
+ }
203466
+ // 查询现有实例以合并 Ext
203467
+ let existingExtObj = {};
203468
+ let resolvedAuthTypeCode = authTypeCode;
203469
+ try {
203470
+ const existing = await this.getUserKey({
203471
+ keyId,
203472
+ needPwd: true,
203473
+ decryptPwd: true, // 解密后方便合并
203474
+ business: constants_1.USER_KEY_BUSINESS
203475
+ });
203476
+ const extObj = this.parseExt((_a = existing.UserKeyInfo) === null || _a === void 0 ? void 0 : _a.Ext);
203477
+ existingExtObj = Object.assign({}, extObj);
203478
+ resolvedAuthTypeCode = resolvedAuthTypeCode || ((_b = existing.UserKeyInfo) === null || _b === void 0 ? void 0 : _b.AuthTypeCode);
203479
+ }
203480
+ catch (_c) {
203481
+ // 查询失败时仅使用传入的 envVariables
203482
+ }
203483
+ if (!resolvedAuthTypeCode) {
203484
+ // 无法获取 authTypeCode,无法识别 password 字段,直接 JSON 序列化
203485
+ return JSON.stringify(Object.assign(Object.assign({}, existingExtObj), envVariables));
203486
+ }
203487
+ // 合并:现有 Ext 为基础,envVariables 覆盖
203488
+ const merged = Object.assign(Object.assign({}, existingExtObj), envVariables);
203489
+ return this.encryptAndStringify(envId, resolvedAuthTypeCode, merged);
203490
+ }
203491
+ /**
203492
+ * 加密 envVariables 中的 password 字段并返回 JSON 字符串。
203493
+ * 无 password 字段时直接 JSON.stringify,不请求七彩石。
203494
+ */
203495
+ async encryptAndStringify(envId, authTypeCode, envVariables) {
203496
+ // 1. 拉取模板,识别 password 字段
203497
+ const passwordFieldNames = await this.getPasswordFieldNames(authTypeCode);
203498
+ // 2. 判断是否有 password 字段需要加密
203499
+ const hasPasswordValue = passwordFieldNames.some(name => envVariables[name]);
203500
+ if (!hasPasswordValue) {
203501
+ return JSON.stringify(envVariables);
203502
+ }
203503
+ // 3. 拉取 AES key 并加密
203504
+ try {
203505
+ const aesKey = await (0, secretKey_1.getAesKey)({ envId });
203506
+ const encrypted = this.encryptPasswordFields(envVariables, passwordFieldNames, aesKey);
203507
+ return JSON.stringify(encrypted);
203508
+ }
203509
+ catch (_a) {
203510
+ // 获取密钥失败时以明文写入(与 CLI 行为一致)
203511
+ return JSON.stringify(envVariables);
203512
+ }
203513
+ }
203514
+ /**
203515
+ * 解密 Ext 中的 password 字段并返回新的 IUserKeyInfo。
203516
+ * 解密失败时保留原值,不中断流程。
203517
+ */
203518
+ async decryptExtIfNeeded(userKeyInfo, envId) {
203519
+ if (!userKeyInfo.Ext || !userKeyInfo.AuthTypeCode)
203520
+ return userKeyInfo;
203521
+ try {
203522
+ const passwordFieldNames = await this.getPasswordFieldNames(userKeyInfo.AuthTypeCode);
203523
+ if (passwordFieldNames.length === 0)
203524
+ return userKeyInfo;
203525
+ const extObj = this.parseExt(userKeyInfo.Ext);
203526
+ if (!Object.keys(extObj).length)
203527
+ return userKeyInfo;
203528
+ const hasPasswordValue = passwordFieldNames.some(name => extObj[name]);
203529
+ if (!hasPasswordValue)
203530
+ return userKeyInfo;
203531
+ const aesKey = await (0, secretKey_1.getAesKey)({ envId });
203532
+ const decrypted = this.decryptPasswordFields(extObj, passwordFieldNames, aesKey);
203533
+ return Object.assign(Object.assign({}, userKeyInfo), { Ext: JSON.stringify(decrypted) });
203534
+ }
203535
+ catch (_a) {
203536
+ return userKeyInfo;
203537
+ }
203538
+ }
203539
+ /** 从模板中获取 password 类型字段名列表 */
203540
+ async getPasswordFieldNames(authTypeCode) {
203541
+ var _a, _b;
203542
+ try {
203543
+ const res = await this.listTemplates({ authTypeCode, limit: 1 });
203544
+ const templateExt = (_b = (_a = res.KeyTemplateList) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.Ext;
203545
+ if (!templateExt)
203546
+ return [];
203547
+ const parsed = JSON.parse(templateExt);
203548
+ const fields = Array.isArray(parsed === null || parsed === void 0 ? void 0 : parsed.fields) ? parsed.fields : [];
203549
+ return fields
203550
+ .filter((f) => f && f.type === 'password' && typeof f.name === 'string')
203551
+ .map((f) => f.name);
203552
+ }
203553
+ catch (_c) {
203554
+ return [];
203555
+ }
203556
+ }
203557
+ /** 解析 Ext JSON 字符串为对象,失败返回空对象 */
203558
+ parseExt(ext) {
203559
+ if (!ext)
203560
+ return {};
203561
+ try {
203562
+ const parsed = JSON.parse(ext);
203563
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
203564
+ }
203565
+ catch (_a) {
203566
+ return {};
203567
+ }
203568
+ }
203569
+ /** 加密 ext 对象中的 password 字段(仅加密有值的字段),返回新对象 */
203570
+ encryptPasswordFields(extObj, passwordFieldNames, aesKey) {
203571
+ const result = Object.assign({}, extObj);
203572
+ passwordFieldNames.forEach(name => {
203573
+ const value = result[name];
203574
+ if (name && value) {
203575
+ result[name] = crypto_1.AESCrypto.encrypt(value, aesKey);
203576
+ }
203577
+ });
203578
+ return result;
203579
+ }
203580
+ /** 解密 ext 对象中的 password 字段,解密失败保留原值,返回新对象 */
203581
+ decryptPasswordFields(extObj, passwordFieldNames, aesKey) {
203582
+ const result = Object.assign({}, extObj);
203583
+ const MIN_CIPHERTEXT_LEN = 24; // base64(16 字节 IV) = 24 字符
203584
+ passwordFieldNames.forEach(name => {
203585
+ const encrypted = result[name];
203586
+ if (typeof encrypted !== 'string' || encrypted.length <= MIN_CIPHERTEXT_LEN) {
203587
+ return;
203588
+ }
203589
+ try {
203590
+ result[name] = crypto_1.AESCrypto.decrypt(encrypted, aesKey);
203591
+ }
203592
+ catch (_a) {
203593
+ // 解密失败保留原值,不中断流程
203594
+ }
203595
+ });
203596
+ return result;
203597
+ }
203598
+ }
203599
+ exports.IntegrationService = IntegrationService;
203600
+ __decorate([
203601
+ (0, utils_1.preLazy)()
203602
+ ], IntegrationService.prototype, "listTemplates", null);
203603
+ __decorate([
203604
+ (0, utils_1.preLazy)()
203605
+ ], IntegrationService.prototype, "listUserKeys", null);
203606
+ __decorate([
203607
+ (0, utils_1.preLazy)()
203608
+ ], IntegrationService.prototype, "getUserKey", null);
203609
+ __decorate([
203610
+ (0, utils_1.preLazy)()
203611
+ ], IntegrationService.prototype, "createUserKey", null);
203612
+ __decorate([
203613
+ (0, utils_1.preLazy)()
203614
+ ], IntegrationService.prototype, "updateUserKey", null);
203615
+ __decorate([
203616
+ (0, utils_1.preLazy)()
203617
+ ], IntegrationService.prototype, "deleteUserKey", null);
203618
+
203619
+
202682
203620
  /***/ }),
202683
203621
 
202684
203622
  /***/ 72923:
@@ -213100,7 +214038,7 @@ function registerSetupTools(server) {
213100
214038
  server.registerTool("downloadTemplate", {
213101
214039
  title: "下载项目模板",
213102
214040
  description: `自动下载并部署CloudBase项目模板。
213103
- **Note**: Call this tool when the user requests to create a new project using a CloudBase template.\n\n支持的模板:\n- react: React + CloudBase 全栈应用模板\n- vue: Vue + CloudBase 全栈应用模板\n- miniprogram: 微信小程序 + 云开发模板 \n- uniapp: UniApp + CloudBase 跨端应用模板\n- rules: 只包含AI编辑器配置文件(包含Cursor、WindSurf、CodeBuddy等所有主流编辑器配置),适合在已有项目中补充AI编辑器配置\n\n支持的IDE类型:\n- all: 下载所有IDE配置\n- cursor: Cursor AI编辑器\n- 其他IDE类型见下方列表\n\n注意:如果未传入 ide 参数且无法从环境变量检测到 IDE,将提示错误并要求传入 ide 参数\n- windsurf: WindSurf AI编辑器\n- codebuddy: CodeBuddy AI编辑器\n- claude-code: Claude Code AI编辑器\n- cline: Cline AI编辑器\n- gemini-cli: Gemini CLI\n- opencode: OpenCode AI编辑器\n- qwen-code: 通义灵码\n- baidu-comate: 百度Comate\n- openai-codex-cli: OpenAI Codex CLI\n- augment-code: Augment Code\n- github-copilot: GitHub Copilot\n- roocode: RooCode AI编辑器\n- tongyi-lingma: 通义灵码\n- trae: Trae AI编辑器\n- qoder: Qoder AI编辑器\n- antigravity: Google Antigravity AI编辑器\n- vscode: Visual Studio Code\n- kiro: Kiro AI编辑器\n- aider: Aider AI编辑器\n\n特别说明:\n- rules 模板会自动包含当前 mcp 版本号信息(版本号:${ true ? "2.24.1" : 0}),便于后续维护和版本追踪\n- 下载 rules 模板时,如果项目中已存在 README.md 文件,系统会自动保护该文件不被覆盖(除非设置 overwrite=true)`,
214041
+ **Note**: Call this tool when the user requests to create a new project using a CloudBase template.\n\n支持的模板:\n- react: React + CloudBase 全栈应用模板\n- vue: Vue + CloudBase 全栈应用模板\n- miniprogram: 微信小程序 + 云开发模板 \n- uniapp: UniApp + CloudBase 跨端应用模板\n- rules: 只包含AI编辑器配置文件(包含Cursor、WindSurf、CodeBuddy等所有主流编辑器配置),适合在已有项目中补充AI编辑器配置\n\n支持的IDE类型:\n- all: 下载所有IDE配置\n- cursor: Cursor AI编辑器\n- 其他IDE类型见下方列表\n\n注意:如果未传入 ide 参数且无法从环境变量检测到 IDE,将提示错误并要求传入 ide 参数\n- windsurf: WindSurf AI编辑器\n- codebuddy: CodeBuddy AI编辑器\n- claude-code: Claude Code AI编辑器\n- cline: Cline AI编辑器\n- gemini-cli: Gemini CLI\n- opencode: OpenCode AI编辑器\n- qwen-code: 通义灵码\n- baidu-comate: 百度Comate\n- openai-codex-cli: OpenAI Codex CLI\n- augment-code: Augment Code\n- github-copilot: GitHub Copilot\n- roocode: RooCode AI编辑器\n- tongyi-lingma: 通义灵码\n- trae: Trae AI编辑器\n- qoder: Qoder AI编辑器\n- antigravity: Google Antigravity AI编辑器\n- vscode: Visual Studio Code\n- kiro: Kiro AI编辑器\n- aider: Aider AI编辑器\n\n特别说明:\n- rules 模板会自动包含当前 mcp 版本号信息(版本号:${ true ? "2.25.0" : 0}),便于后续维护和版本追踪\n- 下载 rules 模板时,如果项目中已存在 README.md 文件,系统会自动保护该文件不被覆盖(除非设置 overwrite=true)`,
213104
214042
  inputSchema: {
213105
214043
  template: zod_1.z
213106
214044
  .enum(["react", "vue", "miniprogram", "uniapp", "rules"])
@@ -221481,6 +222419,22 @@ module.exports = typeof queueMicrotask === 'function'
221481
222419
  .catch(err => setTimeout(() => { throw err }, 0))
221482
222420
 
221483
222421
 
222422
+ /***/ }),
222423
+
222424
+ /***/ 79640:
222425
+ /***/ ((__unused_webpack_module, exports) => {
222426
+
222427
+ "use strict";
222428
+
222429
+ /**
222430
+ * 集成中心常量定义(与 dev-platform 对齐)
222431
+ */
222432
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
222433
+ exports.USER_KEY_BUSINESS = void 0;
222434
+ /** 业务类型标识:集成中心来源的密钥对 */
222435
+ exports.USER_KEY_BUSINESS = 'integration';
222436
+
222437
+
221484
222438
  /***/ }),
221485
222439
 
221486
222440
  /***/ 79646:
@@ -230184,21 +231138,23 @@ const zod_1 = __webpack_require__(21614);
230184
231138
  const cloudbase_manager_js_1 = __webpack_require__(3431);
230185
231139
  const json_content_js_1 = __webpack_require__(4365);
230186
231140
  const QUERY_GATEWAY_ACTIONS = [
230187
- "getAccess",
230188
- "listDomains",
230189
231141
  "listRoutes",
230190
231142
  "getRoute",
230191
231143
  "listCustomDomains",
230192
231144
  ];
230193
231145
  const MANAGE_GATEWAY_ACTIONS = [
230194
- "createAccess",
230195
231146
  "createRoute",
230196
231147
  "updateRoute",
230197
231148
  "deleteRoute",
230198
231149
  "bindCustomDomain",
230199
231150
  "deleteCustomDomain",
230200
- "deleteAccess",
230201
- "updatePathAuth",
231151
+ ];
231152
+ const UPSTREAM_RESOURCE_TYPES = [
231153
+ "SCF",
231154
+ "WEB_SCF",
231155
+ "CBR",
231156
+ "STATIC_STORE",
231157
+ "LH",
230202
231158
  ];
230203
231159
  function normalizeAccessPath(path) {
230204
231160
  if (!path) {
@@ -230229,24 +231185,11 @@ function registerGatewayTools(server) {
230229
231185
  return (0, json_content_js_1.jsonContent)(buildErrorEnvelope(error));
230230
231186
  }
230231
231187
  };
230232
- const listGatewayDomains = async () => {
230233
- const cloudbase = await getManager();
230234
- const result = await cloudbase.access.getDomainList();
230235
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
230236
- const domains = [
230237
- result.DefaultDomain,
230238
- ...(result.ServiceSet || []).map((item) => item.Domain),
230239
- ].filter(Boolean);
230240
- return {
230241
- domains,
230242
- enableService: result.EnableService,
230243
- raw: result,
230244
- };
230245
- };
230246
231188
  const listHttpServiceRoutes = async (domain) => {
230247
231189
  const cloudbase = await getManager();
230248
231190
  const result = await cloudbase.env.describeHttpServiceRoute({
230249
231191
  EnvId: await resolveEnvId(),
231192
+ Limit: 1000,
230250
231193
  ...(domain
230251
231194
  ? {
230252
231195
  Filters: [
@@ -230262,123 +231205,180 @@ function registerGatewayTools(server) {
230262
231205
  return result;
230263
231206
  };
230264
231207
  const flattenRoutes = (result) => (result.Domains ?? []).flatMap((domainItem) => (domainItem.Routes ?? []).map((route) => ({
230265
- Domain: domainItem.Domain,
231208
+ Domain: domainItem.Domain ?? "",
230266
231209
  DomainType: domainItem.DomainType,
230267
231210
  AccessType: domainItem.AccessType,
231211
+ IsDefault: domainItem.IsDefault,
230268
231212
  ...route,
230269
231213
  })));
231214
+ const buildRouteUrls = (routes) => Array.from(new Set(routes
231215
+ .filter((route) => route.Domain && route.Path)
231216
+ .map((route) => `https://${route.Domain}${normalizeAccessPath(String(route.Path))}`)));
231217
+ const resolveDefaultHttpDomain = async () => {
231218
+ const routeInfo = await listHttpServiceRoutes();
231219
+ const domains = routeInfo.Domains ?? [];
231220
+ const defaultCandidates = domains.filter((item) => item.IsDefault === true && item.Domain);
231221
+ const preferred = defaultCandidates.find((item) => item.Enable !== false && item.Status === "SUCCESS") ??
231222
+ defaultCandidates.find((item) => item.Enable !== false) ??
231223
+ defaultCandidates[0];
231224
+ if (!preferred?.Domain) {
231225
+ throw new Error("环境默认 HTTP 访问域名未就绪或未开通。请先在控制台开通 HTTP 访问服务,或用 queryGateway(action=\"listRoutes\") 确认 Domains 中是否存在 IsDefault=true 的域名;也可以显式传入 domain 后重试 createRoute/updateRoute/deleteRoute。");
231226
+ }
231227
+ return preferred.Domain;
231228
+ };
230270
231229
  const resolveRouteDomain = async (preferredDomain) => {
230271
231230
  if (preferredDomain) {
230272
231231
  return preferredDomain;
230273
231232
  }
230274
- const routeInfo = await listHttpServiceRoutes();
230275
- return routeInfo.OriginDomain;
231233
+ return resolveDefaultHttpDomain();
230276
231234
  };
230277
- const normalizeRoutePayload = async (route, fallback, domain) => {
230278
- const path = normalizeAccessPath(route?.path ?? fallback.path);
230279
- const serviceType = route?.serviceType ?? fallback.targetType;
230280
- const serviceName = route?.serviceName ?? fallback.targetName;
230281
- if (!serviceType || !serviceName) {
230282
- throw new Error("route.serviceType 和 route.serviceName 为必填项");
231235
+ const mapUpstreamResourceType = (input) => {
231236
+ if (input.upstreamResourceType) {
231237
+ return input.upstreamResourceType;
231238
+ }
231239
+ if (input.type === "HTTP") {
231240
+ return "WEB_SCF";
231241
+ }
231242
+ if (input.type === "Event") {
231243
+ return "SCF";
230283
231244
  }
231245
+ if (input.requiresFunctionType || input.targetType === "function") {
231246
+ throw new Error("为云函数创建/更新路由时必须显式提供 type(HTTP→WEB_SCF,Event→SCF)或 route.upstreamResourceType。省略会导致 HTTP 与 Event 函数互相误标,访问时可能返回 FUNCTION_PARAM_INVALID 或网关内部错误。");
231247
+ }
231248
+ throw new Error("必须提供 route.upstreamResourceType(SCF / WEB_SCF / CBR / STATIC_STORE / LH),或在函数场景提供 type=\"HTTP\"|\"Event\"。");
231249
+ };
231250
+ const normalizeRoutePayload = async (input) => {
231251
+ const upstreamResourceName = input.route?.serviceName ?? input.targetName;
231252
+ if (!upstreamResourceName) {
231253
+ throw new Error("必须提供 targetName 或 route.serviceName 作为上游资源名称(例如云函数名)");
231254
+ }
231255
+ const isFunctionRoute = input.targetType === "function" ||
231256
+ input.type !== undefined ||
231257
+ input.route?.upstreamResourceType === "SCF" ||
231258
+ input.route?.upstreamResourceType === "WEB_SCF" ||
231259
+ (!input.route?.upstreamResourceType && Boolean(input.targetName));
231260
+ const upstreamResourceType = mapUpstreamResourceType({
231261
+ type: input.type,
231262
+ targetType: input.targetType,
231263
+ upstreamResourceType: input.route?.upstreamResourceType,
231264
+ requiresFunctionType: isFunctionRoute,
231265
+ });
231266
+ const path = normalizeAccessPath(input.route?.path ?? input.path ?? `/${upstreamResourceName}`);
231267
+ const domain = await resolveRouteDomain(input.domain);
231268
+ const enableAuth = input.route?.auth !== undefined
231269
+ ? input.route.auth
231270
+ : input.auth !== undefined
231271
+ ? input.auth
231272
+ : undefined;
230284
231273
  return {
230285
231274
  EnvId: await resolveEnvId(),
230286
231275
  Domain: {
230287
- Domain: await resolveRouteDomain(domain),
231276
+ Domain: domain,
230288
231277
  Routes: [
230289
231278
  {
230290
231279
  Path: path,
230291
- UpstreamResourceType: serviceType === "function" ? "SCF" : "CBR",
230292
- UpstreamResourceName: serviceName,
230293
- EnableAuth: route?.auth !== undefined
230294
- ? route.auth
230295
- : fallback.auth !== undefined
230296
- ? fallback.auth
230297
- : undefined,
231280
+ UpstreamResourceType: upstreamResourceType,
231281
+ UpstreamResourceName: upstreamResourceName,
231282
+ EnableAuth: enableAuth,
230298
231283
  },
230299
231284
  ],
230300
231285
  },
231286
+ resolved: {
231287
+ domain,
231288
+ path,
231289
+ upstreamResourceType,
231290
+ upstreamResourceName,
231291
+ enableAuth,
231292
+ },
230301
231293
  };
230302
231294
  };
231295
+ const routeMutationNextActions = (targetName) => [
231296
+ {
231297
+ tool: "queryGateway",
231298
+ action: "getRoute",
231299
+ reason: "等待 30 秒到 3 分钟后再确认访问入口是否已生效",
231300
+ },
231301
+ {
231302
+ tool: "queryPermissions",
231303
+ action: "getResourcePermission",
231304
+ reason: "确认函数安全规则是否允许预期访问方;网关 EnableAuth/auth=false 不等于函数已允许匿名访问",
231305
+ },
231306
+ {
231307
+ tool: "managePermissions",
231308
+ action: "updateResourcePermission",
231309
+ reason: "只有在确认需要匿名或浏览器直连访问时,才按实际安全要求更新函数权限",
231310
+ },
231311
+ ];
230303
231312
  const handleQueryGateway = async (input) => {
230304
231313
  switch (input.action) {
230305
- case "listDomains": {
230306
- const result = await listGatewayDomains();
230307
- return buildEnvelope({
230308
- action: input.action,
230309
- domains: result.domains,
230310
- enableService: result.enableService,
230311
- raw: result.raw,
230312
- }, `已获取 ${result.domains.length} 个网关域名`, [
230313
- {
230314
- tool: "queryGateway",
230315
- action: "getAccess",
230316
- reason: "查看某个目标当前的访问入口",
230317
- },
230318
- ]);
230319
- }
230320
231314
  case "listCustomDomains": {
230321
- const result = await listGatewayDomains();
231315
+ const result = await listHttpServiceRoutes();
231316
+ const customDomains = (result.Domains ?? []).filter((item) => item.IsDefault !== true);
230322
231317
  return buildEnvelope({
230323
231318
  action: input.action,
230324
- domains: result.raw.ServiceSet ?? [],
230325
- total: (result.raw.ServiceSet ?? []).length,
230326
- raw: result.raw,
230327
- }, `已获取 ${(result.raw.ServiceSet ?? []).length} 个自定义域名`);
231319
+ domains: customDomains,
231320
+ total: customDomains.length,
231321
+ raw: result,
231322
+ }, `已获取 ${customDomains.length} 个自定义域名`);
230328
231323
  }
230329
231324
  case "listRoutes": {
230330
- const result = await listHttpServiceRoutes();
231325
+ const result = await listHttpServiceRoutes(input.domain);
230331
231326
  const routes = flattenRoutes(result);
230332
231327
  return buildEnvelope({
230333
231328
  action: input.action,
230334
231329
  routes,
231330
+ urls: buildRouteUrls(routes),
230335
231331
  total: result.TotalCount ?? routes.length,
230336
231332
  raw: result,
230337
231333
  }, `已获取 ${result.TotalCount ?? routes.length} 条 HTTP 路由`);
230338
231334
  }
230339
231335
  case "getRoute": {
230340
- const result = await listHttpServiceRoutes();
230341
- const route = flattenRoutes(result).find((item) => (input.routeId && item.RouteId === input.routeId) ||
230342
- (input.targetName && item.UpstreamResourceName === input.targetName)) ?? null;
231336
+ if (!input.routeId && !input.targetName && !input.path) {
231337
+ throw new Error("action=getRoute 时至少需要提供 routeId、targetName 或 path");
231338
+ }
231339
+ const result = await listHttpServiceRoutes(input.domain);
231340
+ const normalizedPath = input.path
231341
+ ? normalizeAccessPath(input.path)
231342
+ : undefined;
231343
+ const matches = flattenRoutes(result).filter((item) => {
231344
+ if (input.routeId && item.RouteId !== input.routeId) {
231345
+ return false;
231346
+ }
231347
+ if (input.domain && item.Domain !== input.domain) {
231348
+ return false;
231349
+ }
231350
+ if (normalizedPath &&
231351
+ normalizeAccessPath(String(item.Path ?? "")) !== normalizedPath) {
231352
+ return false;
231353
+ }
231354
+ if (input.targetName &&
231355
+ item.UpstreamResourceName !== input.targetName) {
231356
+ return false;
231357
+ }
231358
+ return Boolean(input.routeId || input.targetName || normalizedPath);
231359
+ });
231360
+ const route = matches.length === 1 ? matches[0] : null;
231361
+ const urls = buildRouteUrls(matches);
230343
231362
  return buildEnvelope({
230344
231363
  action: input.action,
230345
231364
  routeId: input.routeId ?? null,
231365
+ targetName: input.targetName ?? null,
231366
+ path: normalizedPath ?? null,
231367
+ domain: input.domain ?? null,
230346
231368
  route,
230347
- raw: result,
230348
- }, route ? "已获取路由详情" : "未找到对应路由");
230349
- }
230350
- case "getAccess": {
230351
- if (!input.targetName) {
230352
- throw new Error("getAccess 操作时,targetName 参数是必需的");
230353
- }
230354
- const cloudbase = await getManager();
230355
- const [accessList, domainInfo] = await Promise.all([
230356
- cloudbase.access.getAccessList({ name: input.targetName }),
230357
- listGatewayDomains(),
230358
- ]);
230359
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, accessList);
230360
- const urls = Array.from(new Set((accessList.APISet || []).flatMap((api) => domainInfo.domains.map((domain) => {
230361
- const normalizedPath = normalizeAccessPath(api.Path);
230362
- return `https://${domain}${normalizedPath}`;
230363
- }))));
230364
- return buildEnvelope({
230365
- action: input.action,
230366
- targetType: input.targetType ?? "function",
230367
- targetName: input.targetName,
230368
- apis: accessList.APISet || [],
230369
- total: accessList.Total || 0,
230370
- domains: domainInfo.domains,
231369
+ routes: matches,
231370
+ total: matches.length,
230371
231371
  urls,
230372
- enableService: accessList.EnableService ?? domainInfo.enableService ?? false,
230373
- raw: {
230374
- accessList,
230375
- domainList: domainInfo.raw,
230376
- },
230377
- }, `已获取目标 ${input.targetName} 的网关访问配置`, [
231372
+ raw: result,
231373
+ }, matches.length === 0
231374
+ ? "未找到对应路由"
231375
+ : matches.length === 1
231376
+ ? "已获取路由详情"
231377
+ : `匹配到 ${matches.length} 条路由,请补充 path 或 domain 精确定位`, [
230378
231378
  {
230379
231379
  tool: "manageGateway",
230380
- action: "createAccess",
230381
- reason: "为该目标新增访问路径",
231380
+ action: "createRoute",
231381
+ reason: "为该目标新增 Domain/Route 访问路径",
230382
231382
  },
230383
231383
  ]);
230384
231384
  }
@@ -230388,78 +231388,60 @@ function registerGatewayTools(server) {
230388
231388
  };
230389
231389
  const handleManageGateway = async (input) => {
230390
231390
  switch (input.action) {
230391
- case "createAccess": {
230392
- if (!input.targetName || !input.targetType) {
230393
- throw new Error("action=createAccess 时必须提供 targetType 和 targetName");
230394
- }
230395
- if (!input.type) {
230396
- throw new Error("action=createAccess 且 targetType=function 时必须显式提供 type。HTTP 云函数传 HTTP;Event 函数传 Event。省略会默认按 Event 路由处理,可能让 HTTP 云函数访问后返回 FUNCTION_PARAM_INVALID。");
230397
- }
231391
+ case "createRoute": {
230398
231392
  const cloudbase = await getManager();
230399
- const accessPath = normalizeAccessPath(input.path || `/${input.targetName}`);
231393
+ const payload = await normalizeRoutePayload(input);
230400
231394
  let result;
230401
231395
  try {
230402
- result = await cloudbase.access.createAccess({
230403
- name: input.targetName,
230404
- path: accessPath,
230405
- type: (input.type === "HTTP" ? 6 : 1),
230406
- auth: input.auth,
231396
+ result = await cloudbase.env.createHttpServiceRoute({
231397
+ EnvId: payload.EnvId,
231398
+ Domain: payload.Domain,
230407
231399
  });
230408
231400
  }
230409
231401
  catch (err) {
230410
- if (err.message && err.message.includes("An error has occurred")) {
230411
- let hint = "为目标资源配置访问路由失败(后端内部错误)。请确保:1) 目标云函数已成功创建并处于 Active 状态;2) 环境默认 HTTP 域名已完成初始化;3) 该访问路径未被占用。";
231402
+ const message = err instanceof Error ? err.message : String(err);
231403
+ if (message.includes("An error has occurred")) {
231404
+ let hint = "为目标资源配置访问路由失败(后端内部错误)。请确保:1) 目标云函数已成功创建并处于 Active 状态;2) 环境默认 HTTP 域名已完成初始化(IsDefault);3) 该访问路径未被占用。";
230412
231405
  if (input.type === "HTTP") {
230413
- hint += "此外注意:如果目标函数最初是作为 Event 函数创建的,这里 type 仍必须传 Event;误传 HTTP 会导致此错误。";
231406
+ hint +=
231407
+ "此外注意:如果目标函数最初是作为 Event 函数创建的,这里 type 仍必须传 Event(UpstreamResourceType=SCF);误传 HTTP/WEB_SCF 会导致此错误。";
230414
231408
  }
230415
- throw new Error(`${hint} 原始错误:${err.message}`);
231409
+ throw new Error(`${hint} 原始错误:${message}`);
230416
231410
  }
230417
231411
  throw err;
230418
231412
  }
230419
231413
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
230420
231414
  return buildEnvelope({
230421
231415
  action: input.action,
230422
- targetType: input.targetType,
230423
- targetName: input.targetName,
230424
- path: accessPath,
231416
+ model: "httpServiceRoute",
231417
+ targetType: input.targetType ?? null,
231418
+ targetName: payload.resolved.upstreamResourceName,
231419
+ domain: payload.resolved.domain,
231420
+ path: payload.resolved.path,
231421
+ upstreamResourceType: payload.resolved.upstreamResourceType,
231422
+ upstreamResourceName: payload.resolved.upstreamResourceName,
231423
+ auth: payload.resolved.enableAuth ?? null,
230425
231424
  raw: result,
230426
- }, `已为目标 ${input.targetName} 创建网关访问路径。注意:路由配置传播通常需要等待 30 秒到 3 分钟,请勿立即访问。该操作只创建网关入口,不会自动放开函数安全规则;若需要匿名或浏览器直接访问,请继续检查函数资源权限。`, [
230427
- {
230428
- tool: "queryGateway",
230429
- action: "getAccess",
230430
- reason: "等待 30 秒到 3 分钟后再确认访问入口是否已生效",
230431
- },
230432
- {
230433
- tool: "queryPermissions",
230434
- action: "getResourcePermission",
230435
- reason: "确认函数安全规则是否允许预期访问方;网关 auth=false 不等于函数已允许匿名访问",
230436
- },
230437
- {
230438
- tool: "managePermissions",
230439
- action: "updateResourcePermission",
230440
- reason: "只有在确认需要匿名或浏览器直连访问时,才按实际安全要求更新函数权限",
230441
- },
230442
- ]);
230443
- }
230444
- case "createRoute": {
230445
- const cloudbase = await getManager();
230446
- const result = await cloudbase.env.createHttpServiceRoute((await normalizeRoutePayload(input.route, input, input.domain)));
230447
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
230448
- return buildEnvelope({
230449
- action: input.action,
230450
- route: input.route ?? null,
230451
- raw: result,
230452
- }, "HTTP 路由创建成功");
231425
+ }, `已为目标 ${payload.resolved.upstreamResourceName} 在域名 ${payload.resolved.domain} 创建路由 ${payload.resolved.path}(${payload.resolved.upstreamResourceType})。注意:路由配置传播通常需要等待 30 秒到 3 分钟,请勿立即访问。该操作只创建网关入口,不会自动放开函数安全规则;若需要匿名或浏览器直接访问,请继续检查函数资源权限。`, routeMutationNextActions(payload.resolved.upstreamResourceName));
230453
231426
  }
230454
231427
  case "updateRoute": {
230455
231428
  const cloudbase = await getManager();
230456
- const result = await cloudbase.env.modifyHttpServiceRoute((await normalizeRoutePayload(input.route, input, input.domain)));
231429
+ const payload = await normalizeRoutePayload(input);
231430
+ const result = await cloudbase.env.modifyHttpServiceRoute({
231431
+ EnvId: payload.EnvId,
231432
+ Domain: payload.Domain,
231433
+ });
230457
231434
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
230458
231435
  return buildEnvelope({
230459
231436
  action: input.action,
230460
- route: input.route ?? null,
231437
+ model: "httpServiceRoute",
231438
+ domain: payload.resolved.domain,
231439
+ path: payload.resolved.path,
231440
+ upstreamResourceType: payload.resolved.upstreamResourceType,
231441
+ upstreamResourceName: payload.resolved.upstreamResourceName,
231442
+ auth: payload.resolved.enableAuth ?? null,
230461
231443
  raw: result,
230462
- }, "HTTP 路由更新成功");
231444
+ }, `HTTP 路由更新成功(${payload.resolved.domain}${payload.resolved.path})`, routeMutationNextActions(payload.resolved.upstreamResourceName));
230463
231445
  }
230464
231446
  case "deleteRoute": {
230465
231447
  const routePath = input.route?.path ?? input.path;
@@ -230468,16 +231450,18 @@ function registerGatewayTools(server) {
230468
231450
  }
230469
231451
  const cloudbase = await getManager();
230470
231452
  const domain = await resolveRouteDomain(input.domain);
231453
+ const normalizedPath = normalizeAccessPath(routePath);
230471
231454
  const result = await cloudbase.env.deleteHttpServiceRoute({
230472
231455
  EnvId: await resolveEnvId(),
230473
231456
  Domain: domain,
230474
- Paths: [normalizeAccessPath(routePath)],
231457
+ Paths: [normalizedPath],
230475
231458
  });
230476
231459
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
230477
231460
  return buildEnvelope({
230478
231461
  action: input.action,
231462
+ model: "httpServiceRoute",
230479
231463
  domain,
230480
- path: normalizeAccessPath(routePath),
231464
+ path: normalizedPath,
230481
231465
  raw: result,
230482
231466
  }, "HTTP 路由删除成功");
230483
231467
  }
@@ -230517,114 +231501,34 @@ function registerGatewayTools(server) {
230517
231501
  raw: result,
230518
231502
  }, "自定义域名删除成功");
230519
231503
  }
230520
- case "deleteAccess": {
230521
- const cloudbase = await getManager();
230522
- const accessPath = input.path ? normalizeAccessPath(input.path) : undefined;
230523
- let resolvedAccessId = input.accessId;
230524
- let resolvedName;
230525
- if (!resolvedAccessId) {
230526
- if (!input.targetName && !accessPath) {
230527
- throw new Error("action=deleteAccess 时至少需要提供 accessId,或提供 targetName/path 用于定位入口");
230528
- }
230529
- const accessList = await cloudbase.access.getAccessList({
230530
- ...(input.targetName ? { name: input.targetName } : {}),
230531
- ...(accessPath ? { path: accessPath } : {}),
230532
- });
230533
- const candidates = (accessList.APISet ?? []).filter((item) => {
230534
- if (!accessPath) {
230535
- return true;
230536
- }
230537
- return normalizeAccessPath(item.Path) === accessPath;
230538
- });
230539
- if (candidates.length === 0) {
230540
- throw new Error("未找到可删除的访问入口,请确认 targetName/path 或直接传 accessId");
230541
- }
230542
- if (candidates.length > 1 && !accessPath) {
230543
- throw new Error("匹配到多个访问入口,请补充 path 或直接传 accessId");
230544
- }
230545
- resolvedAccessId = candidates[0]?.APIId;
230546
- resolvedName = candidates[0]?.Name;
230547
- }
230548
- else {
230549
- // 仅传入 accessId 时,先查询获取 name 等信息,确保删除参数完整
230550
- try {
230551
- const accessList = await cloudbase.access.getAccessList({});
230552
- const match = (accessList.APISet ?? []).find((item) => item.APIId === resolvedAccessId);
230553
- if (match) {
230554
- resolvedName = match.Name;
230555
- }
230556
- }
230557
- catch {
230558
- // 查询失败不影响主流程,继续用 accessId 删除
230559
- }
230560
- }
230561
- if (!resolvedAccessId) {
230562
- throw new Error("无法解析访问入口 accessId,请改为显式传入 accessId");
230563
- }
230564
- const result = await cloudbase.access.deleteAccess({
230565
- ...(resolvedName ? { name: resolvedName } : {}),
230566
- apiId: resolvedAccessId,
230567
- });
230568
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
230569
- return buildEnvelope({
230570
- action: input.action,
230571
- targetName: resolvedName ?? input.targetName ?? null,
230572
- path: accessPath ?? null,
230573
- accessId: resolvedAccessId,
230574
- raw: result,
230575
- }, "网关访问入口删除成功");
230576
- }
230577
- case "updatePathAuth": {
230578
- if (!input.targetName && !input.path) {
230579
- throw new Error("action=updatePathAuth 时至少需要提供 targetName 或 path");
230580
- }
230581
- if (input.auth === undefined) {
230582
- throw new Error("action=updatePathAuth 时必须提供 auth");
230583
- }
230584
- const cloudbase = await getManager();
230585
- const accessPath = input.path ? normalizeAccessPath(input.path) : undefined;
230586
- const accessList = await cloudbase.access.getAccessList({
230587
- ...(input.targetName ? { name: input.targetName } : {}),
230588
- ...(accessPath ? { path: accessPath } : {}),
230589
- });
230590
- const apiIds = (accessList.APISet ?? []).map((item) => item.APIId).filter(Boolean);
230591
- if (apiIds.length === 0) {
230592
- throw new Error("未找到可更新鉴权状态的访问入口");
230593
- }
230594
- const result = await cloudbase.access.switchPathAuth({
230595
- apiIds,
230596
- auth: input.auth,
230597
- });
230598
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
230599
- return buildEnvelope({
230600
- action: input.action,
230601
- targetName: input.targetName ?? null,
230602
- path: accessPath ?? null,
230603
- auth: input.auth,
230604
- apiIds,
230605
- raw: result,
230606
- }, "网关路径鉴权更新成功");
230607
- }
230608
231504
  default:
230609
231505
  throw new Error(`不支持的操作类型: ${input.action}`);
230610
231506
  }
230611
231507
  };
230612
231508
  server.registerTool?.("queryGateway", {
230613
231509
  title: "查询 CloudBase 网关",
230614
- description: "CloudBase 网关统一只读入口。通过 action 查询网关域名、访问入口和目标暴露情况。",
231510
+ description: "CloudBase 网关统一只读入口(Domain/Route 模型)。通过 listRoutes / getRoute / listCustomDomains 查询域名与路由;主键为 Domain + Path,上游类型为 SCF / WEB_SCF / CBR / STATIC_STORE / LH。",
230615
231511
  inputSchema: {
230616
231512
  action: zod_1.z
230617
231513
  .enum(QUERY_GATEWAY_ACTIONS)
230618
- .describe("只读操作类型,例如 getAccess、listDomains"),
231514
+ .describe("只读操作类型:listRoutes、getRoute、listCustomDomains"),
230619
231515
  targetType: zod_1.z
230620
231516
  .enum(["function"])
230621
231517
  .optional()
230622
- .describe("目标资源类型。当前支持 function,后续可扩展"),
231518
+ .describe("目标资源类型。当前支持 function"),
230623
231519
  targetName: zod_1.z
230624
231520
  .string()
230625
231521
  .optional()
230626
- .describe("目标资源名称。getAccess 时必填"),
231522
+ .describe("上游资源名称。getRoute 时可按云函数名过滤"),
230627
231523
  routeId: zod_1.z.string().optional().describe("路由 ID。getRoute 时可选"),
231524
+ path: zod_1.z
231525
+ .string()
231526
+ .optional()
231527
+ .describe("路由路径。getRoute / listRoutes 过滤时可选"),
231528
+ domain: zod_1.z
231529
+ .string()
231530
+ .optional()
231531
+ .describe("域名。getRoute / listRoutes 过滤时可选"),
230628
231532
  },
230629
231533
  annotations: {
230630
231534
  readOnlyHint: true,
@@ -230634,19 +231538,19 @@ function registerGatewayTools(server) {
230634
231538
  }, async (input) => withEnvelope(() => handleQueryGateway(input)));
230635
231539
  server.registerTool?.("manageGateway", {
230636
231540
  title: "管理 CloudBase 网关",
230637
- description: "CloudBase 网关统一写入口。通过 action 创建目标访问入口,后续承接更通用的网关配置能力。为已存在的 HTTP 云函数补默认域名访问时,通常使用 createAccess 并提供 targetType=\"function\"、targetName、type=\"HTTP\" 与期望 path。注意 createAccess 只创建网关入口,不会自动修改函数资源权限。⚠️ 如需绑定带 SSL 证书的自定义域名供公网 HTTPS 访问,使用 action=\"bindCustomDomain\"(需要 domain 和 certificateId 参数);如需配置 CORS/安全域名(无证书),请使用 envDomainManagement 工具。",
231541
+ description: "CloudBase 网关统一写入口(Domain/Route 模型)。为已存在的 HTTP 云函数补默认域名访问时,使用 createRoute,并提供 targetType=\"function\"、targetName、type=\"HTTP\"(映射 WEB_SCF)与期望 path;Event 函数传 type=\"Event\"(映射 SCF)。未传 domain 时自动解析 IsDefault 默认域名。注意 createRoute 只创建网关入口,不会自动修改函数资源权限。更新鉴权用 updateRoute;删除用 deleteRoute(Domain+Path)。⚠️ 绑定带 SSL 证书的自定义域名用 bindCustomDomain;CORS/安全域名请使用 envDomainManagement。",
230638
231542
  inputSchema: {
230639
231543
  action: zod_1.z
230640
231544
  .enum(MANAGE_GATEWAY_ACTIONS)
230641
- .describe('写操作类型,例如 createAccess。为已有函数补默认域名访问入口时使用 createAccess;若 action=createAccess 且 targetType=function,必须显式提供 type。'),
231545
+ .describe("写操作类型。为已有函数补默认域名访问入口时使用 createRoute;函数场景必须显式提供 type(HTTP→WEB_SCF,Event→SCF)或 route.upstreamResourceType。"),
230642
231546
  targetType: zod_1.z
230643
231547
  .enum(["function"])
230644
231548
  .optional()
230645
- .describe("目标资源类型。当前支持 function,后续可扩展"),
231549
+ .describe("目标资源类型。当前支持 function"),
230646
231550
  targetName: zod_1.z
230647
231551
  .string()
230648
231552
  .optional()
230649
- .describe("目标资源名称。createAccess 到云函数时填写函数名"),
231553
+ .describe("目标资源名称。createRoute 到云函数时填写函数名"),
230650
231554
  path: zod_1.z
230651
231555
  .string()
230652
231556
  .optional()
@@ -230654,25 +231558,28 @@ function registerGatewayTools(server) {
230654
231558
  type: zod_1.z
230655
231559
  .enum(["Event", "HTTP"])
230656
231560
  .optional()
230657
- .describe("目标函数的运行时类型,不是接入协议。createAccess 到已创建的 HTTP 云函数时传 HTTP;给 Event 函数补网关访问时传 Event 或省略。省略会默认按 Event 路由处理,可能让 HTTP 云函数访问后返回 FUNCTION_PARAM_INVALID。"),
231561
+ .describe("目标函数运行时类型,不是接入协议。HTTP 云函数传 HTTP(UpstreamResourceType=WEB_SCF);Event 函数传 Event(SCF)。误标会导致 FUNCTION_PARAM_INVALID 或网关错误。函数路由场景必须显式提供 type 或 route.upstreamResourceType。"),
230658
231562
  auth: zod_1.z
230659
231563
  .boolean()
230660
231564
  .optional()
230661
- .describe("是否开启网关路径鉴权。若要走默认域名做匿名或浏览器访问,通常设为 false;该开关仅控制网关入口本身,不会修改函数资源权限,仍需检查并按需调整函数安全规则。"),
231565
+ .describe("是否开启网关路径鉴权(EnableAuth)。若要走默认域名做匿名或浏览器访问,通常设为 false;该开关仅控制网关入口本身,不会修改函数资源权限。"),
230662
231566
  route: zod_1.z
230663
231567
  .object({
230664
- routeId: zod_1.z.string().optional(),
230665
231568
  path: zod_1.z.string().optional(),
230666
- serviceType: zod_1.z.string().optional(),
230667
231569
  serviceName: zod_1.z.string().optional(),
231570
+ upstreamResourceType: zod_1.z.enum(UPSTREAM_RESOURCE_TYPES).optional(),
230668
231571
  auth: zod_1.z.boolean().optional(),
230669
231572
  })
230670
231573
  .optional()
230671
- .describe("HTTP 路由配置对象"),
230672
- domain: zod_1.z.string().optional().describe("自定义域名"),
230673
- certificateId: zod_1.z.string().optional().describe("证书 ID"),
230674
- accessName: zod_1.z.string().optional().describe("访问入口名称,保留字段"),
230675
- accessId: zod_1.z.string().optional().describe("访问入口 ID。deleteAccess 时可直接传入,避免参数歧义"),
231574
+ .describe("HTTP 路由配置。upstreamResourceType 可选 SCF / WEB_SCF / CBR / STATIC_STORE / LH"),
231575
+ domain: zod_1.z
231576
+ .string()
231577
+ .optional()
231578
+ .describe("域名。省略时自动使用环境 IsDefault 默认 HTTP 域名;自定义域名场景请显式传入"),
231579
+ certificateId: zod_1.z
231580
+ .string()
231581
+ .optional()
231582
+ .describe("证书 ID。bindCustomDomain 时必填"),
230676
231583
  },
230677
231584
  annotations: {
230678
231585
  readOnlyHint: false,
@@ -262997,6 +263904,17 @@ class CloudBase {
262997
263904
  get aiModel() {
262998
263905
  return this.currentEnvironment().getAiModelService();
262999
263906
  }
263907
+ /**
263908
+ * 集成中心服务(UserKey)
263909
+ * 提供集成模板/实例的增删改查与绑定云函数能力
263910
+ */
263911
+ get integration() {
263912
+ return this.currentEnvironment().getIntegrationService();
263913
+ }
263914
+ /**
263915
+ * 监控服务
263916
+ * 提供指标查询能力
263917
+ */
263000
263918
  get monitor() {
263001
263919
  return this.currentEnvironment().getMonitorService();
263002
263920
  }
@@ -264609,7 +265527,9 @@ function configToParams(options) {
264609
265527
  if ((_c = func === null || func === void 0 ? void 0 : func.layers) === null || _c === void 0 ? void 0 : _c.length) {
264610
265528
  params.Layers = func.layers.map(item => ({
264611
265529
  LayerName: item.name,
264612
- LayerVersion: item.version
265530
+ // item.version 可能是字符串(如 JSON 配置文件中的 "1" 或模板变量替换结果),
265531
+ // SCF API 要求 LayerVersion 为 int64,需显式转为 number
265532
+ LayerVersion: Number(item.version)
264613
265533
  }));
264614
265534
  }
264615
265535
  // 7. 代码保护
@@ -264648,8 +265568,8 @@ function configToParams(options) {
264648
265568
  params.Code.ImageConfig = buildImageConfig(imageConfig);
264649
265569
  }
264650
265570
  // 第三阶段:统一应用默认值
264651
- const runtime = params.Runtime || 'Nodejs18.15';
264652
- return Object.assign({ Handler: func.handler || 'index.main', Timeout: 10, Runtime: 'Nodejs18.15', MemorySize: 256, InstallDependency: isNodeFunction(runtime) ? 'TRUE' : 'FALSE' }, params);
265571
+ const runtime = params.Runtime || 'Nodejs20.19';
265572
+ return Object.assign({ Handler: func.handler || 'index.main', Runtime: 'Nodejs20.19', MemorySize: 256, InstallDependency: isNodeFunction(runtime) ? 'TRUE' : 'FALSE' }, params);
264653
265573
  }
264654
265574
  class FunctionService {
264655
265575
  constructor(environment) {
@@ -264705,7 +265625,7 @@ class FunctionService {
264705
265625
  async createFunction(funcParam) {
264706
265626
  var _a, _b, _c;
264707
265627
  const { namespace } = this.getFunctionConfig();
264708
- const { func, functionRootPath, force = false, base64Code, codeSecret, functionPath, deployMode } = funcParam;
265628
+ const { func, functionRootPath, force = false, base64Code, codeSecret, functionPath, deployMode, code } = funcParam;
264709
265629
  const funcName = func.name;
264710
265630
  const { TopicId, LogsetId } = this.getClsServiceConfig();
264711
265631
  const params = configToParams({
@@ -264719,20 +265639,30 @@ class FunctionService {
264719
265639
  ClsLogsetId: LogsetId
264720
265640
  }
264721
265641
  });
265642
+ const customCode = code;
265643
+ const customImageCode = customCode === null || customCode === void 0 ? void 0 : customCode.ImageConfig;
265644
+ const funcImageCode = (_a = params.Code) === null || _a === void 0 ? void 0 : _a.ImageConfig;
264722
265645
  // 根据部署方式处理 Code 参数
264723
- // 优先使用显式指定的 deployMode,如果未指定但存在 imageConfig 则认为是镜像部署
264724
- const isImageDeploy = deployMode === 'image' || (!deployMode && ((_a = params.Code) === null || _a === void 0 ? void 0 : _a.ImageConfig));
265646
+ // 优先级:deployMode=image > 显式传入 code > 原有自动打包逻辑(base64Code/functionPath/functionRootPath)
265647
+ const isImageDeploy = deployMode === 'image' || Boolean(customImageCode) || (!deployMode && Boolean(funcImageCode));
264725
265648
  if (isImageDeploy) {
264726
- // 镜像部署:Code 参数已在 configToParams 中通过 imageConfig 设置
265649
+ // 镜像部署优先使用显式 code.ImageConfig;未传时回退到 func.imageConfig(configToParams 生成)
265650
+ if (customImageCode) {
265651
+ params.Code = customCode;
265652
+ }
264727
265653
  if (!((_c = (_b = params.Code) === null || _b === void 0 ? void 0 : _b.ImageConfig) === null || _c === void 0 ? void 0 : _c.ImageUri)) {
264728
265654
  throw new error_1.CloudBaseError('镜像部署需要配置 imageConfig.imageUri');
264729
265655
  }
264730
- // 镜像部署的特殊配置
264731
- // 镜像函数不需要 Handler
265656
+ // 镜像函数不需要 Handler / InstallDependency
264732
265657
  delete params.Handler;
264733
- // 镜像函数不需要安装依赖
264734
265658
  delete params.InstallDependency;
264735
265659
  }
265660
+ else if (customCode) {
265661
+ if (base64Code || functionPath || functionRootPath) {
265662
+ console.warn(`[${funcName}] 检测到同时传入 code 与本地打包参数,已优先使用 code 参数`);
265663
+ }
265664
+ params.Code = customCode;
265665
+ }
264736
265666
  else {
264737
265667
  // 代码部署:通过 getCodeParams 获取代码参数
264738
265668
  params.Code = await this.getCodeParams({
@@ -264787,7 +265717,8 @@ class FunctionService {
264787
265717
  functionPath,
264788
265718
  functionRootPath,
264789
265719
  codeSecret: codeSecret,
264790
- deployMode: isImageDeploy ? 'image' : undefined
265720
+ deployMode: isImageDeploy ? 'image' : deployMode,
265721
+ code
264791
265722
  });
264792
265723
  // 等待函数状态正常
264793
265724
  await this.waitFunctionActive(funcName, codeSecret);
@@ -265235,7 +266166,9 @@ class FunctionService {
265235
266166
  if ((_c = func === null || func === void 0 ? void 0 : func.layers) === null || _c === void 0 ? void 0 : _c.length) {
265236
266167
  params.Layers = func.layers.map(item => ({
265237
266168
  LayerName: item.name,
265238
- LayerVersion: item.version
266169
+ // item.version 可能是字符串(如 JSON 配置文件中的 "1" 或模板变量替换结果),
266170
+ // SCF API 要求 LayerVersion 为 int64,需显式转为 number
266171
+ LayerVersion: Number(item.version)
265239
266172
  }));
265240
266173
  }
265241
266174
  // 特殊处理:HTTP 函数多并发配置(大小写不敏感)
@@ -265290,7 +266223,7 @@ class FunctionService {
265290
266223
  * @memberof FunctionService
265291
266224
  */
265292
266225
  async updateFunctionCode(funcParam) {
265293
- const { func, functionRootPath, base64Code, codeSecret, functionPath, deployMode } = funcParam;
266226
+ const { func, functionRootPath, base64Code, codeSecret, functionPath, deployMode, code } = funcParam;
265294
266227
  const funcName = func.name;
265295
266228
  const { namespace } = this.getFunctionConfig();
265296
266229
  // 镜像部署:使用镜像配置更新函数代码
@@ -265317,6 +266250,10 @@ class FunctionService {
265317
266250
  });
265318
266251
  }
265319
266252
  }
266253
+ const customCode = code;
266254
+ if (customCode && (base64Code || functionPath || functionRootPath)) {
266255
+ console.warn(`[${funcName}] 检测到同时传入 code 与本地打包参数,已优先使用 code 参数`);
266256
+ }
265320
266257
  // 代码部署:原有逻辑
265321
266258
  let installDependency;
265322
266259
  // Node 函数默认安装依赖
@@ -265325,7 +266262,7 @@ class FunctionService {
265325
266262
  if (func.installDependency !== undefined) {
265326
266263
  installDependency = toBooleanString(func.installDependency);
265327
266264
  }
265328
- const codeParams = await this.getCodeParams({
266265
+ const codeParams = customCode || await this.getCodeParams({
265329
266266
  func,
265330
266267
  functionPath,
265331
266268
  functionRootPath,
@@ -265919,7 +266856,7 @@ class FunctionService {
265919
266856
  const { func, functionPath, functionRootPath } = options;
265920
266857
  const { env, appId } = this.getFunctionConfig();
265921
266858
  if (!appId) {
265922
- throw new error_1.CloudBaseError('无法获取 AppId,请确认环境已开通存储服务');
266859
+ throw new error_1.CloudBaseError('无法获取 AppId,请确认环境初始化完成或已开通存储服务');
265923
266860
  }
265924
266861
  const objectPath = `${appId}/${env}/${func.name}.zip`;
265925
266862
  // 1. 生成存放函数包的临时 Cos 目录
@@ -266155,14 +267092,18 @@ class FunctionService {
266155
267092
  * @memberof FunctionService
266156
267093
  */
266157
267094
  getFunctionConfig() {
266158
- var _a, _b, _c, _d, _e;
267095
+ var _a, _b, _c, _d, _e, _f;
266159
267096
  const envConfig = this.environment.lazyEnvironmentConfig;
266160
267097
  // Functions 可能为空
266161
267098
  const namespace = ((_b = (_a = envConfig.Functions) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.Namespace) || '';
266162
- // AppId 可能为空字符串,从 Bucket 名称中提取
267099
+ // AppId 优先从 DescribeEnvInfo 的 UserInfo 获取(账号级标识,不依赖存储服务)
267100
+ // 兜底从 Storages[0] 的 AppId 或 Bucket 名提取
266163
267101
  // COS Bucket 格式: {BucketName}-{APPID},APPID 为纯数字
266164
267102
  const storage = (_c = envConfig.Storages) === null || _c === void 0 ? void 0 : _c[0];
266165
- const appId = (storage === null || storage === void 0 ? void 0 : storage.AppId) || ((_e = (_d = storage === null || storage === void 0 ? void 0 : storage.Bucket) === null || _d === void 0 ? void 0 : _d.match(/-(\d+)$/)) === null || _e === void 0 ? void 0 : _e[1]) || '';
267103
+ const appId = ((_d = envConfig.UserInfo) === null || _d === void 0 ? void 0 : _d.AppId)
267104
+ || (storage === null || storage === void 0 ? void 0 : storage.AppId)
267105
+ || ((_f = (_e = storage === null || storage === void 0 ? void 0 : storage.Bucket) === null || _e === void 0 ? void 0 : _e.match(/-(\d+)$/)) === null || _f === void 0 ? void 0 : _f[1])
267106
+ || '';
266166
267107
  const { proxy } = this.environment.cloudBaseContext;
266167
267108
  return {
266168
267109
  proxy,