@cloudbase/cloudbase-mcp 2.25.1 → 2.25.3
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/cli.cjs +3 -3
- package/dist/index.cjs +1625 -118
- package/dist/index.js +1623 -118
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -461,15 +461,20 @@ async function prepareAuthEnvironment(params) {
|
|
|
461
461
|
if (setupContext.createFreeEnvAttempted) {
|
|
462
462
|
envSetupActions.push("create_free_env");
|
|
463
463
|
}
|
|
464
|
+
// Surface the user-facing notice emitted at checkAndCreateFreeEnv entry.
|
|
465
|
+
// Informational only — no confirm required. Prepend to the final message so
|
|
466
|
+
// the user knows an automatic free-env creation was attempted.
|
|
467
|
+
const userNotice = createResult.userNotice?.trim() || "";
|
|
464
468
|
if (createResult.success && createResult.envId) {
|
|
465
469
|
await cloudbase_manager_js_1.envManager.setEnvId(createResult.envId);
|
|
470
|
+
const successMessage = `当前已登录,已自动创建并绑定环境: ${createResult.envId}`;
|
|
466
471
|
return {
|
|
467
472
|
currentEnvId: createResult.envId,
|
|
468
473
|
envStatus: "READY",
|
|
469
474
|
envCandidates: [],
|
|
470
475
|
envSetupStatus: "AUTO_CREATED",
|
|
471
476
|
envSetupActions: dedupeActions(envSetupActions),
|
|
472
|
-
message:
|
|
477
|
+
message: userNotice ? `${userNotice}\n${successMessage}` : successMessage,
|
|
473
478
|
nextStep: (0, tool_result_js_1.buildAuthNextStep)("status", {
|
|
474
479
|
suggestedArgs: { action: "status" },
|
|
475
480
|
}),
|
|
@@ -488,6 +493,9 @@ async function prepareAuthEnvironment(params) {
|
|
|
488
493
|
message: "环境创建失败,请稍后重试或手动创建环境。",
|
|
489
494
|
helpUrl: "https://buy.cloud.tencent.com/lowcode?buyType=tcb&channel=mcp",
|
|
490
495
|
});
|
|
496
|
+
const failureMessage = userNotice
|
|
497
|
+
? `${userNotice}\n${createFailure.message}`
|
|
498
|
+
: createFailure.message;
|
|
491
499
|
return {
|
|
492
500
|
currentEnvId: null,
|
|
493
501
|
envStatus: "NONE",
|
|
@@ -495,7 +503,7 @@ async function prepareAuthEnvironment(params) {
|
|
|
495
503
|
envSetupStatus: "ACTION_REQUIRED",
|
|
496
504
|
envSetupActions: dedupeActions(envSetupActions),
|
|
497
505
|
envSetupFailure: createFailure,
|
|
498
|
-
message:
|
|
506
|
+
message: failureMessage,
|
|
499
507
|
nextStep: (0, tool_result_js_1.buildAuthNextStep)("status", {
|
|
500
508
|
suggestedArgs: { action: "status" },
|
|
501
509
|
}),
|
|
@@ -817,6 +825,297 @@ function buildEnvQueryErrorMessage(error, action) {
|
|
|
817
825
|
function normalizeOptionalToolBoolean(value) {
|
|
818
826
|
return typeof value === "boolean" ? value : undefined;
|
|
819
827
|
}
|
|
828
|
+
/**
|
|
829
|
+
* 解析用于询价的 region。与 cloudbase-manager.ts 一致:
|
|
830
|
+
* cloudBaseOptions.region → TCB_REGION → 'ap-shanghai'。
|
|
831
|
+
*/
|
|
832
|
+
function resolvePricingRegion(cloudBaseOptions) {
|
|
833
|
+
return ((typeof cloudBaseOptions?.region === "string" && cloudBaseOptions.region) ||
|
|
834
|
+
process.env.TCB_REGION ||
|
|
835
|
+
"ap-shanghai");
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* 从 describeBaasPackageList 查询某个 packageId 的人类可读套餐名。
|
|
839
|
+
* 失败时返回 undefined,调用方降级为只显示 packageId。
|
|
840
|
+
*/
|
|
841
|
+
async function fetchPackageTitle(manager, packageId) {
|
|
842
|
+
try {
|
|
843
|
+
const result = await manager.env.describeBaasPackageList({
|
|
844
|
+
TargetAction: "new",
|
|
845
|
+
Source: "qcloud",
|
|
846
|
+
});
|
|
847
|
+
const packageList = result?.PackageList || [];
|
|
848
|
+
const matched = packageList.find((item) => item?.BillTags === packageId || item?.PackageName === packageId);
|
|
849
|
+
if (matched) {
|
|
850
|
+
return (matched.PackageTitle || matched.PackageName || matched.BillTags);
|
|
851
|
+
}
|
|
852
|
+
return undefined;
|
|
853
|
+
}
|
|
854
|
+
catch (error) {
|
|
855
|
+
(0, logger_js_1.debug)("fetchPackageTitle failed", { packageId, error });
|
|
856
|
+
return undefined;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* 查询环境的当前计费/套餐信息(PackageName、PackageId、Region、到期时间、PayMode)。
|
|
861
|
+
* 失败时返回 undefined。
|
|
862
|
+
*/
|
|
863
|
+
async function fetchEnvBillingSummary(manager, envId) {
|
|
864
|
+
try {
|
|
865
|
+
// describeBillingInfo 返回 EnvBillingInfoList;从中挑出 envId 对应项
|
|
866
|
+
const billingResult = await manager.env.describeBillingInfo({ EnvId: envId });
|
|
867
|
+
const billingList = billingResult?.EnvBillingInfoList ||
|
|
868
|
+
billingResult?.Response?.EnvBillingInfoList ||
|
|
869
|
+
billingResult?.Data?.EnvBillingInfoList ||
|
|
870
|
+
[];
|
|
871
|
+
const matched = Array.isArray(billingList)
|
|
872
|
+
? billingList.find((item) => item?.EnvId === envId) ?? billingList[0]
|
|
873
|
+
: undefined;
|
|
874
|
+
if (!matched) {
|
|
875
|
+
return undefined;
|
|
876
|
+
}
|
|
877
|
+
return {
|
|
878
|
+
packageName: matched.PackageName,
|
|
879
|
+
packageId: matched.PackageId,
|
|
880
|
+
region: matched.Region,
|
|
881
|
+
expireTime: matched.ExpireTime,
|
|
882
|
+
payMode: matched.PayMode,
|
|
883
|
+
isAutoRenew: matched.IsAutoRenew,
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
catch (error) {
|
|
887
|
+
(0, logger_js_1.debug)("fetchEnvBillingSummary failed", { envId, error });
|
|
888
|
+
return undefined;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* 询价新购价格。失败时返回 { error }。
|
|
893
|
+
*/
|
|
894
|
+
async function calculateCreatePrice(manager, params) {
|
|
895
|
+
try {
|
|
896
|
+
const priceResult = await manager.env.calculatePackageCreatePrice({
|
|
897
|
+
packageId: params.packageId,
|
|
898
|
+
region: params.region,
|
|
899
|
+
period: params.period,
|
|
900
|
+
});
|
|
901
|
+
return { priceResult };
|
|
902
|
+
}
|
|
903
|
+
catch (error) {
|
|
904
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
905
|
+
(0, logger_js_1.debug)("calculatePackageCreatePrice failed", { params, error });
|
|
906
|
+
return { error: message };
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* 询价续费价格。失败时返回 { error }。
|
|
911
|
+
*/
|
|
912
|
+
async function calculateRenewPrice(manager, envId, period) {
|
|
913
|
+
try {
|
|
914
|
+
const priceResult = await manager.env.calculatePackageRenewPrice({
|
|
915
|
+
envId,
|
|
916
|
+
period,
|
|
917
|
+
});
|
|
918
|
+
return { priceResult };
|
|
919
|
+
}
|
|
920
|
+
catch (error) {
|
|
921
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
922
|
+
(0, logger_js_1.debug)("calculatePackageRenewPrice failed", { envId, period, error });
|
|
923
|
+
return { error: message };
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* 询价变配价格(含退款)。失败时返回 { error }。
|
|
928
|
+
*/
|
|
929
|
+
async function calculateModifyPrice(manager, envId, newPackageId) {
|
|
930
|
+
try {
|
|
931
|
+
const priceResult = await manager.env.calculatePackageModifyPrice({
|
|
932
|
+
envId,
|
|
933
|
+
newPackageId,
|
|
934
|
+
});
|
|
935
|
+
return { priceResult };
|
|
936
|
+
}
|
|
937
|
+
catch (error) {
|
|
938
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
939
|
+
(0, logger_js_1.debug)("calculatePackageModifyPrice failed", { envId, newPackageId, error });
|
|
940
|
+
return { error: message };
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* 把 PriceResult 格式化为人类可读的价格摘要。
|
|
945
|
+
* 返回 { summary, detail }:summary 是一行短语,detail 是多行明细。
|
|
946
|
+
*/
|
|
947
|
+
function formatPriceSection(priceResult) {
|
|
948
|
+
if (!priceResult || typeof priceResult !== "object") {
|
|
949
|
+
return { summary: "询价返回为空", detail: "" };
|
|
950
|
+
}
|
|
951
|
+
const currency = priceResult.Currency === "USD" ? "$" : "¥";
|
|
952
|
+
const realTotalCost = Number(priceResult.RealTotalCost);
|
|
953
|
+
const totalCost = Number(priceResult.TotalCost);
|
|
954
|
+
const unitPrice = Number(priceResult.Price);
|
|
955
|
+
const timeSpan = priceResult.TimeSpan;
|
|
956
|
+
const TimeUnit = priceResult.TimeUnit || "";
|
|
957
|
+
const refund = priceResult.Refund ? Number(priceResult.Refund) : undefined;
|
|
958
|
+
const lines = [];
|
|
959
|
+
let summary = "";
|
|
960
|
+
if (Number.isFinite(realTotalCost) && realTotalCost > 0) {
|
|
961
|
+
summary = `预计实付 ${currency}${realTotalCost}`;
|
|
962
|
+
lines.push(`- 实付总价: ${currency}${realTotalCost}`);
|
|
963
|
+
}
|
|
964
|
+
else if (Number.isFinite(totalCost) && totalCost > 0) {
|
|
965
|
+
summary = `预计费用 ${currency}${totalCost}`;
|
|
966
|
+
lines.push(`- 总价: ${currency}${totalCost}`);
|
|
967
|
+
}
|
|
968
|
+
if (Number.isFinite(totalCost) && totalCost > 0 && totalCost !== realTotalCost) {
|
|
969
|
+
lines.push(`- 原价: ${currency}${totalCost}`);
|
|
970
|
+
}
|
|
971
|
+
if (Number.isFinite(unitPrice) && unitPrice > 0) {
|
|
972
|
+
lines.push(`- 单价: ${currency}${unitPrice}`);
|
|
973
|
+
}
|
|
974
|
+
if (timeSpan !== undefined && TimeUnit) {
|
|
975
|
+
lines.push(`- 时长: ${timeSpan} ${TimeUnit}`);
|
|
976
|
+
}
|
|
977
|
+
if (refund !== undefined && Number.isFinite(refund) && refund > 0) {
|
|
978
|
+
lines.push(`- 退款: ${currency}${refund}(变配降级时退回差价)`);
|
|
979
|
+
if (summary) {
|
|
980
|
+
summary += `,退款 ${currency}${refund}`;
|
|
981
|
+
}
|
|
982
|
+
else {
|
|
983
|
+
summary = `退款 ${currency}${refund}`;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
if (priceResult.Formula && typeof priceResult.Formula === "string") {
|
|
987
|
+
lines.push(`- 计价公式: ${priceResult.Formula}`);
|
|
988
|
+
}
|
|
989
|
+
return {
|
|
990
|
+
summary: summary || "询价返回缺少总价字段",
|
|
991
|
+
detail: lines.join("\n"),
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* 构建释放方式说明。manageEnv 当前未提供 destroy action,
|
|
996
|
+
* 指向控制台让用户手动销毁。
|
|
997
|
+
*/
|
|
998
|
+
function buildReleaseMethodHint() {
|
|
999
|
+
return {
|
|
1000
|
+
method: "手动销毁",
|
|
1001
|
+
consoleUrl: "https://console.cloud.tencent.com/tcb",
|
|
1002
|
+
note: '环境创建后可随时销毁以停止计费。当前 manageEnv 未提供 destroy action,请前往控制台手动销毁,或调用 manageEnv(action="listPackages") 查看其他套餐。',
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* manageEnv 涉及计费的官方文档链接。来源:腾讯云开发 CloudBase 公开文档。
|
|
1007
|
+
* 任何 confirm 消息展示这些链接前不需要再次校验 URL 真实性。
|
|
1008
|
+
*/
|
|
1009
|
+
const MANAGE_ENV_DOC_LINKS = {
|
|
1010
|
+
// 包年包月套餐说明(个人版 / 标准版 / 企业版 / 企业高级版)
|
|
1011
|
+
package: "https://cloud.tencent.com/document/product/876/39093",
|
|
1012
|
+
// 计费能力项说明
|
|
1013
|
+
billingItems: "https://cloud.tencent.com/document/product/876/120713",
|
|
1014
|
+
// 资源点价格文档
|
|
1015
|
+
resourcePointPrice: "https://cloud.tencent.com/document/product/876/127357",
|
|
1016
|
+
// 预付费计费与到期释放(费用中心通用)
|
|
1017
|
+
prepayExpiry: "https://cloud.tencent.com/document/product/555/9618",
|
|
1018
|
+
};
|
|
1019
|
+
/**
|
|
1020
|
+
* 把文档链接渲染为多行 markdown 列表文本。
|
|
1021
|
+
* filter 不传时全部展示。
|
|
1022
|
+
*/
|
|
1023
|
+
function renderDocLinksBlock(filter) {
|
|
1024
|
+
const labels = {
|
|
1025
|
+
package: "包年包月套餐说明",
|
|
1026
|
+
billingItems: "计费能力项说明",
|
|
1027
|
+
resourcePointPrice: "资源点价格文档",
|
|
1028
|
+
prepayExpiry: "预付费计费与到期释放(费用中心通用)",
|
|
1029
|
+
};
|
|
1030
|
+
const entries = (filter ?? Object.keys(MANAGE_ENV_DOC_LINKS))
|
|
1031
|
+
.map((k) => `- [${labels[k]}](${MANAGE_ENV_DOC_LINKS[k]})`)
|
|
1032
|
+
.join("\n");
|
|
1033
|
+
return `参考文档:\n${entries}`;
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* 资源清单详细描述(对应控制台购买页"资源清单"段)。
|
|
1037
|
+
*/
|
|
1038
|
+
function buildResourceListText() {
|
|
1039
|
+
return "资源清单:\n- 云开发环境 ×1(含 云数据库 / 云函数 / 云存储 / 静态托管 / 身份认证 等基础资源)";
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* 计费项披露(对应控制台购买页"计费项"段)。
|
|
1043
|
+
* 此处为静态披露,调用方在 confirm 消息中拼接即可。
|
|
1044
|
+
*/
|
|
1045
|
+
function buildBillingItemsText() {
|
|
1046
|
+
return "计费项:\n- 数据库容量/调用 · 云函数调用/资源/流量 · 存储读写/CDN · 网关/认证/API 调用 · QPS 超限按量 · 日志";
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* 计费方式披露(对应控制台购买页"计费方式"段)。
|
|
1050
|
+
* 针对用户传入的 packageId 判断套餐类型:
|
|
1051
|
+
* - 含 free/activity/trial/试用 等关键字视为免费体验版
|
|
1052
|
+
* - 其余视为付费套餐
|
|
1053
|
+
*/
|
|
1054
|
+
function buildBillingModeText(packageId) {
|
|
1055
|
+
const id = (packageId || "").toLowerCase();
|
|
1056
|
+
const isFree = id.includes("free") ||
|
|
1057
|
+
id.includes("activity") ||
|
|
1058
|
+
id.includes("trial") ||
|
|
1059
|
+
id.includes("试用") ||
|
|
1060
|
+
id.includes("体验");
|
|
1061
|
+
if (isFree) {
|
|
1062
|
+
return ("计费方式:\n- 免费体验版(每月赠送约 3000 资源点 ≈ 3 元,0 元开通)\n" +
|
|
1063
|
+
"- 付费套餐:个人版 / 标准版 / 企业版 / 企业高级版");
|
|
1064
|
+
}
|
|
1065
|
+
return ("计费方式:\n- 付费套餐:个人版 / 标准版 / 企业版 / 企业高级版\n" +
|
|
1066
|
+
"- 免费体验版通过 auth 工具自动创建;本次 manageEnv(create) 不会创建免费版");
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* 资源释放方式详细说明(对应控制台购买页"资源释放方式"段)。
|
|
1070
|
+
* - 免费版:1 个月有效期 + 免费续期 + 停服(保留数据) + 1~7天回收站 + 释放(数据不可恢复)
|
|
1071
|
+
* - 付费版:到期未续费 → 停服(保留数据) + 1~7天可回收站找回 + 释放(数据不可恢复)
|
|
1072
|
+
* 任何时候都可在控制台主动销毁、关闭按量、退订加购资源。
|
|
1073
|
+
*/
|
|
1074
|
+
function buildReleaseMethodDetailText(packageId) {
|
|
1075
|
+
const id = (packageId || "").toLowerCase();
|
|
1076
|
+
const isFree = id.includes("free") ||
|
|
1077
|
+
id.includes("activity") ||
|
|
1078
|
+
id.includes("trial") ||
|
|
1079
|
+
id.includes("试用") ||
|
|
1080
|
+
id.includes("体验");
|
|
1081
|
+
const lines = ["资源释放方式:\n- 可随时在控制台销毁环境、关闭按量、退订加购资源"];
|
|
1082
|
+
if (isFree) {
|
|
1083
|
+
lines.push("- 免费体验版:有效期 1 个月,到期可免费续期 1 个月;未续期则停服(保留数据)→1~7天回收站→释放(数据不可恢复)");
|
|
1084
|
+
}
|
|
1085
|
+
else {
|
|
1086
|
+
lines.push("- 付费套餐到期未续费:停服(保留数据)→1~7天可回收站找回→释放(数据不可恢复)");
|
|
1087
|
+
lines.push("- 主动销毁:随时生效;销毁前请确保已迁移或备份数据");
|
|
1088
|
+
}
|
|
1089
|
+
return lines.join("\n");
|
|
1090
|
+
}
|
|
1091
|
+
/**
|
|
1092
|
+
* 预计费用披露段:把询价结果 + 周期说明 + 超额按量说明组合成人类可读文本。
|
|
1093
|
+
*/
|
|
1094
|
+
function buildPricingDisclosureText(packageId, priceSection, priceError, period) {
|
|
1095
|
+
const id = (packageId || "").toLowerCase();
|
|
1096
|
+
const isFree = id.includes("free") ||
|
|
1097
|
+
id.includes("activity") ||
|
|
1098
|
+
id.includes("trial") ||
|
|
1099
|
+
id.includes("试用") ||
|
|
1100
|
+
id.includes("体验");
|
|
1101
|
+
const lines = ["预计费用:"];
|
|
1102
|
+
if (isFree) {
|
|
1103
|
+
lines.push(`- 免费体验版:本次开通 0 元;超免费额度后需升级为付费套餐(免费版不支持开按量)`);
|
|
1104
|
+
lines.push("- 实际单价以资源点价格文档为准(3000 资源点 ≈ 3 元)");
|
|
1105
|
+
}
|
|
1106
|
+
else {
|
|
1107
|
+
lines.push(`- 付费套餐:${priceSection?.summary ?? (priceError ? `⚠️ 询价失败(${priceError}),请前往控制台确认` : "询价返回为空")}`);
|
|
1108
|
+
if (priceSection?.detail) {
|
|
1109
|
+
lines.push(priceSection.detail);
|
|
1110
|
+
}
|
|
1111
|
+
if (priceError && priceSection) {
|
|
1112
|
+
lines.push(`- ⚠️ 部分明细缺失:${priceError}`);
|
|
1113
|
+
}
|
|
1114
|
+
lines.push(`- 计费周期:约 ${period} 个月(包年包月),到期可续费或变配`);
|
|
1115
|
+
lines.push("- 超额可另开按量(次日结算),详细计费项以官方文档为准");
|
|
1116
|
+
}
|
|
1117
|
+
return lines.join("\n");
|
|
1118
|
+
}
|
|
820
1119
|
function resolveToolAuthOptions(server, overrides) {
|
|
821
1120
|
return (0, auth_js_1.resolveAuthOptions)({
|
|
822
1121
|
...overrides,
|
|
@@ -1645,7 +1944,7 @@ function registerEnvTools(server) {
|
|
|
1645
1944
|
if (server.ide !== 'wxide') {
|
|
1646
1945
|
server.registerTool?.("envDomainManagement", {
|
|
1647
1946
|
title: "CloudBase 环境域名管理(安全域名 / CORS 白名单)",
|
|
1648
|
-
description: "管理 CloudBase 环境的安全域名(安全域名 / CORS 白名单),支持添加和删除操作。(原工具名:createEnvDomain/deleteEnvDomain,为兼容旧AI规则可继续使用这些名称)当浏览器 Web 应用需要从本地 Vite / dev server 直接访问 CloudBase 资源时,应先用 queryEnv(action=domains) 检查当前实际浏览器 origin 对应的 host:port 是否已在白名单中,再按该实际值添加。新增或删除后通常需要继续轮询 queryEnv(action=domains) 确认状态收敛;安全域名一般约 10 分钟生效。⚠️ 重要:此工具仅用于 CORS/请求来源验证,不涉及 SSL
|
|
1947
|
+
description: "管理 CloudBase 环境的安全域名(安全域名 / CORS 白名单),支持添加和删除操作。(原工具名:createEnvDomain/deleteEnvDomain,为兼容旧AI规则可继续使用这些名称)当浏览器 Web 应用需要从本地 Vite / dev server 直接访问 CloudBase 资源时,应先用 queryEnv(action=domains) 检查当前实际浏览器 origin 对应的 host:port 是否已在白名单中,再按该实际值添加。新增或删除后通常需要继续轮询 queryEnv(action=domains) 确认状态收敛;安全域名一般约 10 分钟生效。⚠️ 重要:此工具仅用于 CORS/请求来源验证,不涉及 SSL 证书。自定义域名公网 HTTPS:先 queryGateway(listCustomDomains);已有域名则 manageGateway(createRoute) 显式传 domain(无需证书);仅首次绑定新域名才用 bindCustomDomain(需 certificateId)。",
|
|
1649
1948
|
inputSchema: {
|
|
1650
1949
|
action: zod_1.z
|
|
1651
1950
|
.enum(["create", "delete"])
|
|
@@ -1769,10 +2068,92 @@ function registerEnvTools(server) {
|
|
|
1769
2068
|
// CreateEnv (Manager SDK / Cloud API) accepts Alias, PackageId, Resources, Period, etc.
|
|
1770
2069
|
// It does NOT accept Region — region is determined by account/package, not this call.
|
|
1771
2070
|
if (!confirmed) {
|
|
2071
|
+
// 查询套餐名和预计费用(失败降级,不阻塞 confirm 流程)
|
|
2072
|
+
const packageTitle = packageId
|
|
2073
|
+
? await fetchPackageTitle(cloudbase, packageId)
|
|
2074
|
+
: undefined;
|
|
2075
|
+
const pricingRegion = resolvePricingRegion(cloudBaseOptions);
|
|
2076
|
+
const priceProbe = packageId
|
|
2077
|
+
? await calculateCreatePrice(cloudbase, {
|
|
2078
|
+
packageId,
|
|
2079
|
+
region: pricingRegion,
|
|
2080
|
+
period: duration,
|
|
2081
|
+
})
|
|
2082
|
+
: { error: "缺少 packageId,无法询价" };
|
|
2083
|
+
const priceSection = priceProbe.priceResult
|
|
2084
|
+
? formatPriceSection(priceProbe.priceResult)
|
|
2085
|
+
: null;
|
|
2086
|
+
const releaseMethod = buildReleaseMethodHint();
|
|
2087
|
+
// 组合多段披露文案(对照控制台购买页确认对话框)
|
|
2088
|
+
const messageLines = [];
|
|
2089
|
+
messageLines.push("即将为你开通云开发环境");
|
|
2090
|
+
messageLines.push("本操作会创建腾讯云开发环境资源。");
|
|
2091
|
+
messageLines.push("为付费套餐后才会产生费用;免费体验版不会立即扣费(每月赠送约 3000 资源点 ≈ 3 元)。");
|
|
2092
|
+
messageLines.push("请核对以下配置信息后传入 confirm=\"yes\":");
|
|
2093
|
+
messageLines.push(`- 别名: ${alias ?? "(未提供)"}`);
|
|
2094
|
+
messageLines.push(`- 套餐: ${packageId ?? "(未提供)"}` +
|
|
2095
|
+
(packageTitle ? `(${packageTitle})` : ""));
|
|
2096
|
+
messageLines.push(`- 资源类型: ${resolvedResources.join(", ")}`);
|
|
2097
|
+
messageLines.push(`- 时长: ${duration} 个月`);
|
|
2098
|
+
messageLines.push("- 说明: CreateEnv 不接受 Region 入参,环境地域由账号与套餐侧决定。");
|
|
2099
|
+
messageLines.push("");
|
|
2100
|
+
// 资源清单 / 计费项 / 计费方式
|
|
2101
|
+
messageLines.push(buildResourceListText());
|
|
2102
|
+
messageLines.push(buildBillingItemsText());
|
|
2103
|
+
messageLines.push(buildBillingModeText(packageId));
|
|
2104
|
+
messageLines.push("");
|
|
2105
|
+
// 预计费用
|
|
2106
|
+
messageLines.push(buildPricingDisclosureText(packageId, priceSection, priceProbe.error, duration));
|
|
2107
|
+
messageLines.push("");
|
|
2108
|
+
// 资源释放方式
|
|
2109
|
+
messageLines.push(buildReleaseMethodDetailText(packageId));
|
|
2110
|
+
messageLines.push("");
|
|
2111
|
+
// 文档链接
|
|
2112
|
+
messageLines.push(renderDocLinksBlock([
|
|
2113
|
+
"package",
|
|
2114
|
+
"billingItems",
|
|
2115
|
+
"resourcePointPrice",
|
|
2116
|
+
"prepayExpiry",
|
|
2117
|
+
]));
|
|
2118
|
+
messageLines.push("");
|
|
2119
|
+
// 已知晓声明
|
|
2120
|
+
messageLines.push("☐ 我已知晓将创建付费资源及计费规则,确认按上述配置开通。");
|
|
2121
|
+
messageLines.push(`(如需取消或修改,请勿传 confirm="yes",改传其他参数重试)`);
|
|
1772
2122
|
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1773
2123
|
ok: false,
|
|
1774
2124
|
code: "CONFIRM_REQUIRED",
|
|
1775
|
-
message:
|
|
2125
|
+
message: messageLines.join("\n"),
|
|
2126
|
+
package_info: packageTitle
|
|
2127
|
+
? { packageId, packageTitle }
|
|
2128
|
+
: packageId
|
|
2129
|
+
? { packageId, packageTitle: null }
|
|
2130
|
+
: undefined,
|
|
2131
|
+
pricing: priceSection
|
|
2132
|
+
? {
|
|
2133
|
+
summary: priceSection.summary,
|
|
2134
|
+
currency: priceProbe.priceResult?.Currency,
|
|
2135
|
+
realTotalCost: priceProbe.priceResult?.RealTotalCost,
|
|
2136
|
+
totalCost: priceProbe.priceResult?.TotalCost,
|
|
2137
|
+
timeSpan: priceProbe.priceResult?.TimeSpan,
|
|
2138
|
+
timeUnit: priceProbe.priceResult?.TimeUnit,
|
|
2139
|
+
inquiryRegion: pricingRegion,
|
|
2140
|
+
}
|
|
2141
|
+
: priceProbe.error
|
|
2142
|
+
? { inquiryFailed: true, error: priceProbe.error }
|
|
2143
|
+
: undefined,
|
|
2144
|
+
release_method: {
|
|
2145
|
+
...releaseMethod,
|
|
2146
|
+
detail: buildReleaseMethodDetailText(packageId),
|
|
2147
|
+
docLinks: [
|
|
2148
|
+
MANAGE_ENV_DOC_LINKS.package,
|
|
2149
|
+
MANAGE_ENV_DOC_LINKS.prepayExpiry,
|
|
2150
|
+
],
|
|
2151
|
+
},
|
|
2152
|
+
doc_links: MANAGE_ENV_DOC_LINKS,
|
|
2153
|
+
confirmation_acknowledgement: {
|
|
2154
|
+
text: "我已知晓将创建付费资源及计费规则",
|
|
2155
|
+
required: true,
|
|
2156
|
+
},
|
|
1776
2157
|
next_step: {
|
|
1777
2158
|
tool: "manageEnv",
|
|
1778
2159
|
action: "create",
|
|
@@ -1808,10 +2189,91 @@ function registerEnvTools(server) {
|
|
|
1808
2189
|
if (!envId || !packageId) {
|
|
1809
2190
|
throw new Error("变更套餐时 envId 和 packageId 为必填参数");
|
|
1810
2191
|
}
|
|
2192
|
+
// 查询当前套餐名 + 新套餐名 + 询价(失败降级)
|
|
2193
|
+
const [currentBilling, newPackageTitle] = await Promise.all([
|
|
2194
|
+
fetchEnvBillingSummary(cloudbase, envId),
|
|
2195
|
+
fetchPackageTitle(cloudbase, packageId),
|
|
2196
|
+
]);
|
|
2197
|
+
const priceProbe = await calculateModifyPrice(cloudbase, envId, packageId);
|
|
2198
|
+
const priceSection = priceProbe.priceResult
|
|
2199
|
+
? formatPriceSection(priceProbe.priceResult)
|
|
2200
|
+
: null;
|
|
2201
|
+
const releaseMethod = buildReleaseMethodHint();
|
|
2202
|
+
const messageLines = [];
|
|
2203
|
+
messageLines.push(`变更环境 ${envId} 的套餐需要您确认。变配后不会立即重新计费,但会触发差价结算或退款。`);
|
|
2204
|
+
messageLines.push("请核对后传入 confirm=\"yes\":");
|
|
2205
|
+
messageLines.push(`- 当前套餐: ${currentBilling?.packageName ?? currentBilling?.packageId ?? "(未知)"}` +
|
|
2206
|
+
(currentBilling?.packageId && currentBilling.packageName
|
|
2207
|
+
? `(${currentBilling.packageId})`
|
|
2208
|
+
: ""));
|
|
2209
|
+
if (currentBilling?.expireTime) {
|
|
2210
|
+
messageLines.push(`- 当前到期时间: ${currentBilling.expireTime}`);
|
|
2211
|
+
}
|
|
2212
|
+
messageLines.push(`- 新套餐: ${packageId}` +
|
|
2213
|
+
(newPackageTitle ? `(${newPackageTitle})` : ""));
|
|
2214
|
+
messageLines.push("");
|
|
2215
|
+
if (priceSection) {
|
|
2216
|
+
messageLines.push(`预计费用变化:${priceSection.summary}(差价/退款以账单为准)`);
|
|
2217
|
+
if (priceSection.detail) {
|
|
2218
|
+
messageLines.push(priceSection.detail);
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
else if (priceProbe.error) {
|
|
2222
|
+
messageLines.push(`预计费用变化:⚠️ 询价失败(${priceProbe.error}),请前往控制台确认价格`);
|
|
2223
|
+
}
|
|
2224
|
+
else {
|
|
2225
|
+
messageLines.push("预计费用变化:询价返回为空,请前往控制台确认价格");
|
|
2226
|
+
}
|
|
2227
|
+
messageLines.push("");
|
|
2228
|
+
// 资源释放方式(变配不影响存续,但与到期/销毁规则相同)
|
|
2229
|
+
messageLines.push(buildReleaseMethodDetailText(packageId));
|
|
2230
|
+
messageLines.push("");
|
|
2231
|
+
// 文档链接(变配与套餐/计费/到期都相关)
|
|
2232
|
+
messageLines.push(renderDocLinksBlock(["package", "prepayExpiry"]));
|
|
2233
|
+
messageLines.push("");
|
|
2234
|
+
messageLines.push("☐ 我已知晓变配将触发差价结算或退款,确认按上述配置变更。");
|
|
2235
|
+
messageLines.push(`(如需取消或修改,请勿传 confirm="yes")`);
|
|
1811
2236
|
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1812
2237
|
ok: false,
|
|
1813
2238
|
code: "CONFIRM_REQUIRED",
|
|
1814
|
-
message:
|
|
2239
|
+
message: messageLines.join("\n"),
|
|
2240
|
+
env_id: envId,
|
|
2241
|
+
current_package: currentBilling
|
|
2242
|
+
? {
|
|
2243
|
+
packageId: currentBilling.packageId,
|
|
2244
|
+
packageName: currentBilling.packageName,
|
|
2245
|
+
expireTime: currentBilling.expireTime,
|
|
2246
|
+
payMode: currentBilling.payMode,
|
|
2247
|
+
}
|
|
2248
|
+
: undefined,
|
|
2249
|
+
new_package: {
|
|
2250
|
+
packageId,
|
|
2251
|
+
packageTitle: newPackageTitle ?? null,
|
|
2252
|
+
},
|
|
2253
|
+
pricing: priceSection
|
|
2254
|
+
? {
|
|
2255
|
+
summary: priceSection.summary,
|
|
2256
|
+
currency: priceProbe.priceResult?.Currency,
|
|
2257
|
+
realTotalCost: priceProbe.priceResult?.RealTotalCost,
|
|
2258
|
+
totalCost: priceProbe.priceResult?.TotalCost,
|
|
2259
|
+
refund: priceProbe.priceResult?.Refund,
|
|
2260
|
+
}
|
|
2261
|
+
: priceProbe.error
|
|
2262
|
+
? { inquiryFailed: true, error: priceProbe.error }
|
|
2263
|
+
: undefined,
|
|
2264
|
+
release_method: {
|
|
2265
|
+
...releaseMethod,
|
|
2266
|
+
detail: buildReleaseMethodDetailText(packageId),
|
|
2267
|
+
docLinks: [MANAGE_ENV_DOC_LINKS.prepayExpiry],
|
|
2268
|
+
},
|
|
2269
|
+
doc_links: {
|
|
2270
|
+
package: MANAGE_ENV_DOC_LINKS.package,
|
|
2271
|
+
prepayExpiry: MANAGE_ENV_DOC_LINKS.prepayExpiry,
|
|
2272
|
+
},
|
|
2273
|
+
confirmation_acknowledgement: {
|
|
2274
|
+
text: "我已知晓变配将触发差价结算或退款",
|
|
2275
|
+
required: true,
|
|
2276
|
+
},
|
|
1815
2277
|
next_step: {
|
|
1816
2278
|
tool: "manageEnv",
|
|
1817
2279
|
action: "modifyPlan",
|
|
@@ -1841,10 +2303,87 @@ function registerEnvTools(server) {
|
|
|
1841
2303
|
if (!envId) {
|
|
1842
2304
|
throw new Error("续费环境时 envId 为必填参数");
|
|
1843
2305
|
}
|
|
2306
|
+
// 查询当前套餐名 + 到期时间 + 询价(失败降级)
|
|
2307
|
+
const [currentBilling, priceProbe] = await Promise.all([
|
|
2308
|
+
fetchEnvBillingSummary(cloudbase, envId),
|
|
2309
|
+
calculateRenewPrice(cloudbase, envId, duration),
|
|
2310
|
+
]);
|
|
2311
|
+
const priceSection = priceProbe.priceResult
|
|
2312
|
+
? formatPriceSection(priceProbe.priceResult)
|
|
2313
|
+
: null;
|
|
2314
|
+
const releaseMethod = buildReleaseMethodHint();
|
|
2315
|
+
const currentPackageId = currentBilling?.packageId;
|
|
2316
|
+
const messageLines = [];
|
|
2317
|
+
messageLines.push(`续费环境 ${envId} 需要您确认。续费按当前套餐类型计算,延长到期时间。`);
|
|
2318
|
+
messageLines.push("请核对后传入 confirm=\"yes\":");
|
|
2319
|
+
messageLines.push(`- 当前套餐: ${currentBilling?.packageName ?? currentBilling?.packageId ?? "(未知)"}` +
|
|
2320
|
+
(currentBilling?.packageId && currentBilling.packageName
|
|
2321
|
+
? `(${currentBilling.packageId})`
|
|
2322
|
+
: ""));
|
|
2323
|
+
if (currentBilling?.expireTime) {
|
|
2324
|
+
messageLines.push(`- 当前到期时间: ${currentBilling.expireTime}`);
|
|
2325
|
+
}
|
|
2326
|
+
messageLines.push(`- 续费时长: ${duration} 个月`);
|
|
2327
|
+
messageLines.push("");
|
|
2328
|
+
if (priceSection) {
|
|
2329
|
+
messageLines.push(`预计费用:${priceSection.summary}`);
|
|
2330
|
+
if (priceSection.detail) {
|
|
2331
|
+
messageLines.push(priceSection.detail);
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
else if (priceProbe.error) {
|
|
2335
|
+
messageLines.push(`预计费用:⚠️ 询价失败(${priceProbe.error}),请前往控制台确认价格`);
|
|
2336
|
+
}
|
|
2337
|
+
else {
|
|
2338
|
+
messageLines.push("预计费用:询价返回为空,请前往控制台确认价格");
|
|
2339
|
+
}
|
|
2340
|
+
messageLines.push("");
|
|
2341
|
+
// 资源释放方式
|
|
2342
|
+
messageLines.push(buildReleaseMethodDetailText(currentPackageId));
|
|
2343
|
+
messageLines.push("");
|
|
2344
|
+
// 文档链接(续费主要看预付费与到期释放)
|
|
2345
|
+
messageLines.push(renderDocLinksBlock(["prepayExpiry"]));
|
|
2346
|
+
messageLines.push("");
|
|
2347
|
+
messageLines.push("☐ 我已知晓续费将延长当前套餐的到期时间,确认按上述配置续费。");
|
|
2348
|
+
messageLines.push(`(如需取消或修改,请勿传 confirm="yes")`);
|
|
1844
2349
|
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1845
2350
|
ok: false,
|
|
1846
2351
|
code: "CONFIRM_REQUIRED",
|
|
1847
|
-
message:
|
|
2352
|
+
message: messageLines.join("\n"),
|
|
2353
|
+
env_id: envId,
|
|
2354
|
+
current_package: currentBilling
|
|
2355
|
+
? {
|
|
2356
|
+
packageId: currentBilling.packageId,
|
|
2357
|
+
packageName: currentBilling.packageName,
|
|
2358
|
+
expireTime: currentBilling.expireTime,
|
|
2359
|
+
payMode: currentBilling.payMode,
|
|
2360
|
+
isAutoRenew: currentBilling.isAutoRenew,
|
|
2361
|
+
}
|
|
2362
|
+
: undefined,
|
|
2363
|
+
pricing: priceSection
|
|
2364
|
+
? {
|
|
2365
|
+
summary: priceSection.summary,
|
|
2366
|
+
currency: priceProbe.priceResult?.Currency,
|
|
2367
|
+
realTotalCost: priceProbe.priceResult?.RealTotalCost,
|
|
2368
|
+
totalCost: priceProbe.priceResult?.TotalCost,
|
|
2369
|
+
timeSpan: priceProbe.priceResult?.TimeSpan,
|
|
2370
|
+
timeUnit: priceProbe.priceResult?.TimeUnit,
|
|
2371
|
+
}
|
|
2372
|
+
: priceProbe.error
|
|
2373
|
+
? { inquiryFailed: true, error: priceProbe.error }
|
|
2374
|
+
: undefined,
|
|
2375
|
+
release_method: {
|
|
2376
|
+
...releaseMethod,
|
|
2377
|
+
detail: buildReleaseMethodDetailText(currentPackageId),
|
|
2378
|
+
docLinks: [MANAGE_ENV_DOC_LINKS.prepayExpiry],
|
|
2379
|
+
},
|
|
2380
|
+
doc_links: {
|
|
2381
|
+
prepayExpiry: MANAGE_ENV_DOC_LINKS.prepayExpiry,
|
|
2382
|
+
},
|
|
2383
|
+
confirmation_acknowledgement: {
|
|
2384
|
+
text: "我已知晓续费将延长当前套餐的到期时间",
|
|
2385
|
+
required: true,
|
|
2386
|
+
},
|
|
1848
2387
|
next_step: {
|
|
1849
2388
|
tool: "manageEnv",
|
|
1850
2389
|
action: "renew",
|
|
@@ -2233,7 +2772,7 @@ ${envIdSection}
|
|
|
2233
2772
|
## 环境信息
|
|
2234
2773
|
- 操作系统: ${os_1.default.type()} ${os_1.default.release()}
|
|
2235
2774
|
- Node.js版本: ${process.version}
|
|
2236
|
-
- MCP 版本:${process.env.npm_package_version || "2.25.
|
|
2775
|
+
- MCP 版本:${process.env.npm_package_version || "2.25.3" || 0}
|
|
2237
2776
|
- 系统架构: ${os_1.default.arch()}
|
|
2238
2777
|
- 时间: ${new Date().toISOString()}
|
|
2239
2778
|
- 请求ID: ${requestId}
|
|
@@ -2314,7 +2853,7 @@ function createWrappedHandler(name, handler, server) {
|
|
|
2314
2853
|
requestId: (typeof error === 'object' && error && 'requestId' in error) ? error.requestId : '',
|
|
2315
2854
|
ide: server.ide || process.env.INTEGRATION_IDE || ''
|
|
2316
2855
|
});
|
|
2317
|
-
const mcpVersion = true ? "2.25.
|
|
2856
|
+
const mcpVersion = true ? "2.25.3" : 0;
|
|
2318
2857
|
const requestIdSuffix = requestId ? `\n🆔 RequestId: ${requestId}` : '';
|
|
2319
2858
|
const enhancedErrorMessage = `${errorMessage}${requestIdSuffix}\n\n📦 CloudBase MCP v${mcpVersion}\n🔗 遇到问题?请复制以下链接到浏览器打开\n即可自动携带错误详情快速创建 GitHub Issue:\n${issueLink}`;
|
|
2320
2859
|
// 创建新的错误对象,保持原有的错误类型但更新消息
|
|
@@ -2908,6 +3447,153 @@ function registerLogTools(server) {
|
|
|
2908
3447
|
|
|
2909
3448
|
module.exports = __WEBPACK_EXTERNAL_MODULE__modelcontextprotocol_sdk_types_js_70eb1363__;
|
|
2910
3449
|
|
|
3450
|
+
/***/ }),
|
|
3451
|
+
|
|
3452
|
+
/***/ 2435:
|
|
3453
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
3454
|
+
|
|
3455
|
+
|
|
3456
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
3457
|
+
exports.rankGatewayAccessUrls = rankGatewayAccessUrls;
|
|
3458
|
+
exports.flattenHttpServiceRoutes = flattenHttpServiceRoutes;
|
|
3459
|
+
exports.filterRoutesByUpstream = filterRoutesByUpstream;
|
|
3460
|
+
exports.toAccessUrlEnvelope = toAccessUrlEnvelope;
|
|
3461
|
+
exports.resolveGatewayAccessUrls = resolveGatewayAccessUrls;
|
|
3462
|
+
exports.preferGatewayOrFallback = preferGatewayOrFallback;
|
|
3463
|
+
function normalizeAccessPath(path) {
|
|
3464
|
+
if (!path) {
|
|
3465
|
+
return "/";
|
|
3466
|
+
}
|
|
3467
|
+
return path.startsWith("/") ? path : `/${path}`;
|
|
3468
|
+
}
|
|
3469
|
+
function buildHttpsUrl(domain, path) {
|
|
3470
|
+
return `https://${domain}${normalizeAccessPath(path)}`;
|
|
3471
|
+
}
|
|
3472
|
+
/**
|
|
3473
|
+
* Rank gateway route URLs: custom domains (IsDefault !== true) before default domain.
|
|
3474
|
+
* Stable within each group; dedupe by full URL.
|
|
3475
|
+
*/
|
|
3476
|
+
function rankGatewayAccessUrls(routes) {
|
|
3477
|
+
const mapped = routes
|
|
3478
|
+
.filter((route) => Boolean(route.Domain))
|
|
3479
|
+
.map((route) => {
|
|
3480
|
+
const path = normalizeAccessPath(route.Path);
|
|
3481
|
+
const isDefault = route.IsDefault === true;
|
|
3482
|
+
return {
|
|
3483
|
+
url: buildHttpsUrl(route.Domain, path),
|
|
3484
|
+
domain: route.Domain,
|
|
3485
|
+
path,
|
|
3486
|
+
isDefault,
|
|
3487
|
+
source: (isDefault
|
|
3488
|
+
? "gateway.default"
|
|
3489
|
+
: "gateway.custom"),
|
|
3490
|
+
};
|
|
3491
|
+
});
|
|
3492
|
+
mapped.sort((a, b) => {
|
|
3493
|
+
if (a.isDefault === b.isDefault) {
|
|
3494
|
+
return 0;
|
|
3495
|
+
}
|
|
3496
|
+
return a.isDefault ? 1 : -1;
|
|
3497
|
+
});
|
|
3498
|
+
const seen = new Set();
|
|
3499
|
+
const deduped = [];
|
|
3500
|
+
for (const item of mapped) {
|
|
3501
|
+
if (seen.has(item.url)) {
|
|
3502
|
+
continue;
|
|
3503
|
+
}
|
|
3504
|
+
seen.add(item.url);
|
|
3505
|
+
deduped.push(item);
|
|
3506
|
+
}
|
|
3507
|
+
return deduped;
|
|
3508
|
+
}
|
|
3509
|
+
function flattenHttpServiceRoutes(result) {
|
|
3510
|
+
return (result.Domains ?? []).flatMap((domainItem) => (domainItem.Routes ?? []).map((route) => ({
|
|
3511
|
+
Domain: domainItem.Domain ?? "",
|
|
3512
|
+
IsDefault: domainItem.IsDefault,
|
|
3513
|
+
Path: typeof route.Path === "string" ? route.Path : undefined,
|
|
3514
|
+
UpstreamResourceType: typeof route.UpstreamResourceType === "string"
|
|
3515
|
+
? route.UpstreamResourceType
|
|
3516
|
+
: undefined,
|
|
3517
|
+
UpstreamResourceName: typeof route.UpstreamResourceName === "string"
|
|
3518
|
+
? route.UpstreamResourceName
|
|
3519
|
+
: undefined,
|
|
3520
|
+
})));
|
|
3521
|
+
}
|
|
3522
|
+
function filterRoutesByUpstream(routes, input) {
|
|
3523
|
+
const name = input.upstreamResourceName;
|
|
3524
|
+
const types = input.upstreamResourceTypes?.map((t) => String(t));
|
|
3525
|
+
return routes.filter((route) => {
|
|
3526
|
+
if (route.UpstreamResourceName !== name) {
|
|
3527
|
+
return false;
|
|
3528
|
+
}
|
|
3529
|
+
if (!types || types.length === 0) {
|
|
3530
|
+
return true;
|
|
3531
|
+
}
|
|
3532
|
+
return Boolean(route.UpstreamResourceType &&
|
|
3533
|
+
types.includes(String(route.UpstreamResourceType)));
|
|
3534
|
+
});
|
|
3535
|
+
}
|
|
3536
|
+
function toAccessUrlEnvelope(ranked) {
|
|
3537
|
+
if (ranked.length === 0) {
|
|
3538
|
+
return { accessUrls: [], routes: [] };
|
|
3539
|
+
}
|
|
3540
|
+
return {
|
|
3541
|
+
accessUrl: ranked[0].url,
|
|
3542
|
+
accessUrls: ranked.map((item) => item.url),
|
|
3543
|
+
accessUrlSource: ranked[0].source,
|
|
3544
|
+
routes: ranked,
|
|
3545
|
+
};
|
|
3546
|
+
}
|
|
3547
|
+
/**
|
|
3548
|
+
* Best-effort lookup of gateway access URLs for an upstream resource.
|
|
3549
|
+
* Never throws — returns empty lists on failure.
|
|
3550
|
+
*/
|
|
3551
|
+
async function resolveGatewayAccessUrls(input) {
|
|
3552
|
+
try {
|
|
3553
|
+
const manager = await input.getManager();
|
|
3554
|
+
const result = await manager.env.describeHttpServiceRoute({
|
|
3555
|
+
EnvId: input.envId,
|
|
3556
|
+
Limit: 1000,
|
|
3557
|
+
});
|
|
3558
|
+
const flattened = flattenHttpServiceRoutes(result);
|
|
3559
|
+
const matched = filterRoutesByUpstream(flattened, {
|
|
3560
|
+
upstreamResourceName: input.upstreamResourceName,
|
|
3561
|
+
upstreamResourceTypes: input.upstreamResourceTypes,
|
|
3562
|
+
});
|
|
3563
|
+
return toAccessUrlEnvelope(rankGatewayAccessUrls(matched));
|
|
3564
|
+
}
|
|
3565
|
+
catch {
|
|
3566
|
+
return { accessUrls: [], routes: [] };
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
/**
|
|
3570
|
+
* Merge ranked gateway URLs with a resource-native fallback URL.
|
|
3571
|
+
* Gateway custom/default always win over the fallback when present.
|
|
3572
|
+
*/
|
|
3573
|
+
function preferGatewayOrFallback(input) {
|
|
3574
|
+
if (input.gateway.accessUrl) {
|
|
3575
|
+
const urls = [...input.gateway.accessUrls];
|
|
3576
|
+
if (input.fallbackUrl &&
|
|
3577
|
+
!urls.includes(input.fallbackUrl)) {
|
|
3578
|
+
urls.push(input.fallbackUrl);
|
|
3579
|
+
}
|
|
3580
|
+
return {
|
|
3581
|
+
accessUrl: input.gateway.accessUrl,
|
|
3582
|
+
accessUrls: urls,
|
|
3583
|
+
accessUrlSource: input.gateway.accessUrlSource,
|
|
3584
|
+
};
|
|
3585
|
+
}
|
|
3586
|
+
if (input.fallbackUrl) {
|
|
3587
|
+
return {
|
|
3588
|
+
accessUrl: input.fallbackUrl,
|
|
3589
|
+
accessUrls: [input.fallbackUrl],
|
|
3590
|
+
accessUrlSource: input.fallbackSource,
|
|
3591
|
+
};
|
|
3592
|
+
}
|
|
3593
|
+
return { accessUrls: [] };
|
|
3594
|
+
}
|
|
3595
|
+
|
|
3596
|
+
|
|
2911
3597
|
/***/ }),
|
|
2912
3598
|
|
|
2913
3599
|
/***/ 2448:
|
|
@@ -7148,6 +7834,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
|
7148
7834
|
exports.registerGatewayTools = registerGatewayTools;
|
|
7149
7835
|
const zod_1 = __webpack_require__(2971);
|
|
7150
7836
|
const cloudbase_manager_js_1 = __webpack_require__(3431);
|
|
7837
|
+
const gateway_access_urls_js_1 = __webpack_require__(2435);
|
|
7151
7838
|
const json_content_js_1 = __webpack_require__(4365);
|
|
7152
7839
|
const QUERY_GATEWAY_ACTIONS = [
|
|
7153
7840
|
"listRoutes",
|
|
@@ -7161,6 +7848,8 @@ const MANAGE_GATEWAY_ACTIONS = [
|
|
|
7161
7848
|
"bindCustomDomain",
|
|
7162
7849
|
"deleteCustomDomain",
|
|
7163
7850
|
];
|
|
7851
|
+
/** DomainType of the environment default HTTP service (gateway) domain. */
|
|
7852
|
+
const HTTP_SERVICE_DOMAIN_TYPE = "HTTPSERVICE";
|
|
7164
7853
|
const UPSTREAM_RESOURCE_TYPES = [
|
|
7165
7854
|
"SCF",
|
|
7166
7855
|
"WEB_SCF",
|
|
@@ -7197,21 +7886,19 @@ function registerGatewayTools(server) {
|
|
|
7197
7886
|
return (0, json_content_js_1.jsonContent)(buildErrorEnvelope(error));
|
|
7198
7887
|
}
|
|
7199
7888
|
};
|
|
7200
|
-
const listHttpServiceRoutes = async (
|
|
7889
|
+
const listHttpServiceRoutes = async (options) => {
|
|
7890
|
+
const filters = [];
|
|
7891
|
+
if (options?.domain) {
|
|
7892
|
+
filters.push({ Name: "Domain", Values: [options.domain] });
|
|
7893
|
+
}
|
|
7894
|
+
if (options?.domainType) {
|
|
7895
|
+
filters.push({ Name: "DomainType", Values: [options.domainType] });
|
|
7896
|
+
}
|
|
7201
7897
|
const cloudbase = await getManager();
|
|
7202
7898
|
const result = await cloudbase.env.describeHttpServiceRoute({
|
|
7203
7899
|
EnvId: await resolveEnvId(),
|
|
7204
7900
|
Limit: 1000,
|
|
7205
|
-
...(
|
|
7206
|
-
? {
|
|
7207
|
-
Filters: [
|
|
7208
|
-
{
|
|
7209
|
-
Name: "Domain",
|
|
7210
|
-
Values: [domain],
|
|
7211
|
-
},
|
|
7212
|
-
],
|
|
7213
|
-
}
|
|
7214
|
-
: {}),
|
|
7901
|
+
...(filters.length ? { Filters: filters } : {}),
|
|
7215
7902
|
});
|
|
7216
7903
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
7217
7904
|
return result;
|
|
@@ -7223,18 +7910,54 @@ function registerGatewayTools(server) {
|
|
|
7223
7910
|
IsDefault: domainItem.IsDefault,
|
|
7224
7911
|
...route,
|
|
7225
7912
|
})));
|
|
7226
|
-
const buildRouteUrls = (routes) =>
|
|
7227
|
-
.
|
|
7228
|
-
|
|
7913
|
+
const buildRouteUrls = (routes) => {
|
|
7914
|
+
const candidates = routes.map((route) => ({
|
|
7915
|
+
Domain: route.Domain,
|
|
7916
|
+
Path: route.Path,
|
|
7917
|
+
IsDefault: route.IsDefault,
|
|
7918
|
+
UpstreamResourceType: route.UpstreamResourceType,
|
|
7919
|
+
UpstreamResourceName: route.UpstreamResourceName,
|
|
7920
|
+
}));
|
|
7921
|
+
return (0, gateway_access_urls_js_1.rankGatewayAccessUrls)(candidates).map((item) => item.url);
|
|
7922
|
+
};
|
|
7923
|
+
const buildAccessUrlFields = (routes) => {
|
|
7924
|
+
const candidates = routes.map((route) => ({
|
|
7925
|
+
Domain: route.Domain,
|
|
7926
|
+
Path: route.Path,
|
|
7927
|
+
IsDefault: route.IsDefault,
|
|
7928
|
+
UpstreamResourceType: route.UpstreamResourceType,
|
|
7929
|
+
UpstreamResourceName: route.UpstreamResourceName,
|
|
7930
|
+
}));
|
|
7931
|
+
const envelope = (0, gateway_access_urls_js_1.toAccessUrlEnvelope)((0, gateway_access_urls_js_1.rankGatewayAccessUrls)(candidates));
|
|
7932
|
+
return {
|
|
7933
|
+
...(envelope.accessUrl ? { accessUrl: envelope.accessUrl } : {}),
|
|
7934
|
+
accessUrls: envelope.accessUrls,
|
|
7935
|
+
...(envelope.accessUrlSource
|
|
7936
|
+
? { accessUrlSource: envelope.accessUrlSource }
|
|
7937
|
+
: {}),
|
|
7938
|
+
};
|
|
7939
|
+
};
|
|
7940
|
+
const pickUsableDefaultDomain = (domains) => {
|
|
7941
|
+
const candidates = domains.filter((item) => item.IsDefault === true && item.Domain);
|
|
7942
|
+
return (candidates.find((item) => item.Enable !== false && item.Status === "SUCCESS") ??
|
|
7943
|
+
candidates.find((item) => item.Enable !== false) ??
|
|
7944
|
+
candidates[0]);
|
|
7945
|
+
};
|
|
7229
7946
|
const resolveDefaultHttpDomain = async () => {
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
const
|
|
7234
|
-
|
|
7235
|
-
|
|
7947
|
+
// Match console DescribeHTTPServiceRoute usage: filter DomainType=HTTPSERVICE.
|
|
7948
|
+
// Envs often also expose an IsDefault STATIC_STORE domain (*.tcloudbaseapp.com);
|
|
7949
|
+
// that is NOT the default HTTP access entry (*.{region}.app.tcloudbase.com).
|
|
7950
|
+
const httpServiceRoutes = await listHttpServiceRoutes({
|
|
7951
|
+
domainType: HTTP_SERVICE_DOMAIN_TYPE,
|
|
7952
|
+
});
|
|
7953
|
+
// Always re-filter client-side: some SDKs/mocks may ignore Filters.
|
|
7954
|
+
let preferred = pickUsableDefaultDomain((httpServiceRoutes.Domains ?? []).filter((item) => item.DomainType === HTTP_SERVICE_DOMAIN_TYPE));
|
|
7955
|
+
if (!preferred?.Domain) {
|
|
7956
|
+
const allRoutes = await listHttpServiceRoutes();
|
|
7957
|
+
preferred = pickUsableDefaultDomain((allRoutes.Domains ?? []).filter((item) => item.DomainType === HTTP_SERVICE_DOMAIN_TYPE));
|
|
7958
|
+
}
|
|
7236
7959
|
if (!preferred?.Domain) {
|
|
7237
|
-
throw new Error("环境默认 HTTP 访问域名未就绪或未开通。请先在控制台开通 HTTP 访问服务,或用 queryGateway(action=\"listRoutes\") 确认 Domains 中是否存在 IsDefault=true
|
|
7960
|
+
throw new Error("环境默认 HTTP 访问域名未就绪或未开通。请先在控制台开通 HTTP 访问服务,或用 queryGateway(action=\"listRoutes\") 确认 Domains 中是否存在 DomainType=HTTPSERVICE 且 IsDefault=true 的域名(形如 *.{region}.app.tcloudbase.com);不要使用静态托管域名(*.tcloudbaseapp.com)。也可以显式传入 domain 后重试 createRoute/updateRoute/deleteRoute。");
|
|
7238
7961
|
}
|
|
7239
7962
|
return preferred.Domain;
|
|
7240
7963
|
};
|
|
@@ -7330,12 +8053,13 @@ function registerGatewayTools(server) {
|
|
|
7330
8053
|
}, `已获取 ${customDomains.length} 个自定义域名`);
|
|
7331
8054
|
}
|
|
7332
8055
|
case "listRoutes": {
|
|
7333
|
-
const result = await listHttpServiceRoutes(input.domain);
|
|
8056
|
+
const result = await listHttpServiceRoutes({ domain: input.domain });
|
|
7334
8057
|
const routes = flattenRoutes(result);
|
|
7335
8058
|
return buildEnvelope({
|
|
7336
8059
|
action: input.action,
|
|
7337
8060
|
routes,
|
|
7338
8061
|
urls: buildRouteUrls(routes),
|
|
8062
|
+
...buildAccessUrlFields(routes),
|
|
7339
8063
|
total: result.TotalCount ?? routes.length,
|
|
7340
8064
|
raw: result,
|
|
7341
8065
|
}, `已获取 ${result.TotalCount ?? routes.length} 条 HTTP 路由`);
|
|
@@ -7344,7 +8068,7 @@ function registerGatewayTools(server) {
|
|
|
7344
8068
|
if (!input.routeId && !input.targetName && !input.path) {
|
|
7345
8069
|
throw new Error("action=getRoute 时至少需要提供 routeId、targetName 或 path");
|
|
7346
8070
|
}
|
|
7347
|
-
const result = await listHttpServiceRoutes(input.domain);
|
|
8071
|
+
const result = await listHttpServiceRoutes({ domain: input.domain });
|
|
7348
8072
|
const normalizedPath = input.path
|
|
7349
8073
|
? normalizeAccessPath(input.path)
|
|
7350
8074
|
: undefined;
|
|
@@ -7377,6 +8101,7 @@ function registerGatewayTools(server) {
|
|
|
7377
8101
|
routes: matches,
|
|
7378
8102
|
total: matches.length,
|
|
7379
8103
|
urls,
|
|
8104
|
+
...buildAccessUrlFields(matches),
|
|
7380
8105
|
raw: result,
|
|
7381
8106
|
}, matches.length === 0
|
|
7382
8107
|
? "未找到对应路由"
|
|
@@ -7430,6 +8155,11 @@ function registerGatewayTools(server) {
|
|
|
7430
8155
|
upstreamResourceName: payload.resolved.upstreamResourceName,
|
|
7431
8156
|
auth: payload.resolved.enableAuth ?? null,
|
|
7432
8157
|
enablePathTransmission: payload.resolved.enablePathTransmission ?? null,
|
|
8158
|
+
accessUrl: `https://${payload.resolved.domain}${payload.resolved.path}`,
|
|
8159
|
+
accessUrls: [`https://${payload.resolved.domain}${payload.resolved.path}`],
|
|
8160
|
+
accessUrlSource: input.domain && input.domain === payload.resolved.domain
|
|
8161
|
+
? "gateway.custom"
|
|
8162
|
+
: "gateway.default",
|
|
7433
8163
|
raw: result,
|
|
7434
8164
|
}, `已为目标 ${payload.resolved.upstreamResourceName} 在域名 ${payload.resolved.domain} 创建路由 ${payload.resolved.path}(${payload.resolved.upstreamResourceType})` +
|
|
7435
8165
|
(payload.resolved.enablePathTransmission === true
|
|
@@ -7456,6 +8186,11 @@ function registerGatewayTools(server) {
|
|
|
7456
8186
|
upstreamResourceName: payload.resolved.upstreamResourceName,
|
|
7457
8187
|
auth: payload.resolved.enableAuth ?? null,
|
|
7458
8188
|
enablePathTransmission: payload.resolved.enablePathTransmission ?? null,
|
|
8189
|
+
accessUrl: `https://${payload.resolved.domain}${payload.resolved.path}`,
|
|
8190
|
+
accessUrls: [`https://${payload.resolved.domain}${payload.resolved.path}`],
|
|
8191
|
+
accessUrlSource: input.domain && input.domain === payload.resolved.domain
|
|
8192
|
+
? "gateway.custom"
|
|
8193
|
+
: "gateway.default",
|
|
7459
8194
|
raw: result,
|
|
7460
8195
|
}, `HTTP 路由更新成功(${payload.resolved.domain}${payload.resolved.path}` +
|
|
7461
8196
|
(payload.resolved.enablePathTransmission === true
|
|
@@ -7531,11 +8266,13 @@ function registerGatewayTools(server) {
|
|
|
7531
8266
|
title: "查询 CloudBase 网关",
|
|
7532
8267
|
description: "CloudBase HTTP 网关统一只读入口(Domain/Route)。查询域名下路径路由及其上游:" +
|
|
7533
8268
|
"WEB_SCF/SCF=云函数,CBR=云托管,STATIC_STORE=静态托管,LH=轻量应用服务器。" +
|
|
7534
|
-
"主键为 Domain + Path;listRoutes / getRoute / listCustomDomains。"
|
|
8269
|
+
"主键为 Domain + Path;listRoutes / getRoute / listCustomDomains。" +
|
|
8270
|
+
"实现自定义域名访问前,先 listCustomDomains:若已有自定义域名,优先 createRoute 挂路由(无需证书 ID);仅在没有可用自定义域名时才 bindCustomDomain。",
|
|
7535
8271
|
inputSchema: {
|
|
7536
8272
|
action: zod_1.z
|
|
7537
8273
|
.enum(QUERY_GATEWAY_ACTIONS)
|
|
7538
|
-
.describe("只读操作类型:listRoutes、getRoute、listCustomDomains"
|
|
8274
|
+
.describe("只读操作类型:listRoutes、getRoute、listCustomDomains。" +
|
|
8275
|
+
"自定义域名访问场景先 listCustomDomains 确认是否已有域名可复用。"),
|
|
7539
8276
|
targetName: zod_1.z
|
|
7540
8277
|
.string()
|
|
7541
8278
|
.optional()
|
|
@@ -7558,18 +8295,23 @@ function registerGatewayTools(server) {
|
|
|
7558
8295
|
}, async (input) => withEnvelope(() => handleQueryGateway(input)));
|
|
7559
8296
|
server.registerTool?.("manageGateway", {
|
|
7560
8297
|
title: "管理 CloudBase 网关",
|
|
7561
|
-
description: "CloudBase HTTP 网关统一写入口(Domain/Route)。createRoute/updateRoute/deleteRoute 把域名下的 path 转到上游;未传 domain 时用 IsDefault
|
|
8298
|
+
description: "CloudBase HTTP 网关统一写入口(Domain/Route)。createRoute/updateRoute/deleteRoute 把域名下的 path 转到上游;未传 domain 时用 DomainType=HTTPSERVICE 的 IsDefault 默认 HTTP 域名(形如 *.{region}.app.tcloudbase.com),不会使用静态托管 CDN 域名(*.tcloudbaseapp.com,DomainType=STATIC_STORE)。" +
|
|
8299
|
+
"这是网关默认域上的路径路由,不是 STATIC_STORE 上游绑定;STATIC_STORE 上游必须显式传 upstreamResourceType=STATIC_STORE。" +
|
|
8300
|
+
"创建后可用 queryGateway(action=\"listRoutes\") 核对 Domain / DomainType / Path / UpstreamResourceType。" +
|
|
7562
8301
|
"上游类型只用一个参数 upstreamResourceType(也可写在 route.upstreamResourceType,route 优先):" +
|
|
7563
8302
|
"WEB_SCF=HTTP云函数,SCF=Event云函数,CBR=云托管,STATIC_STORE=静态托管,LH=轻量应用服务器;" +
|
|
7564
8303
|
"配合 targetName 或 route.serviceName(云函数名/云托管服务名/静态托管实例名,常见 staticstore)。" +
|
|
7565
8304
|
"createRoute 只建网关入口,不改上游权限。" +
|
|
7566
8305
|
"enablePathTransmission:默认 false 剥触发路径前缀;true 透传完整路径(CBR 多路由、WEB_SCF 自管子路径常需 true;STATIC_STORE 自定义触发路径映射站点根通常 false)。" +
|
|
7567
|
-
"⚠️
|
|
8306
|
+
"⚠️ 自定义域名访问:若环境已有自定义域名(先 queryGateway listCustomDomains),优先 createRoute 并显式传入该 domain,无需 certificateId;" +
|
|
8307
|
+
"仅首次绑定全新自定义域名时用 bindCustomDomain(需 certificateId)。CORS/安全域名用 envDomainManagement。",
|
|
7568
8308
|
inputSchema: {
|
|
7569
8309
|
action: zod_1.z
|
|
7570
8310
|
.enum(MANAGE_GATEWAY_ACTIONS)
|
|
7571
8311
|
.describe("写操作:createRoute/updateRoute/deleteRoute 管理路由;bindCustomDomain/deleteCustomDomain 管理自定义域名。" +
|
|
7572
|
-
"createRoute/updateRoute 必须提供 upstreamResourceType。"
|
|
8312
|
+
"createRoute/updateRoute 必须提供 upstreamResourceType。" +
|
|
8313
|
+
"已有自定义域名时优先 createRoute(domain=已有域名) 实现访问,不必再次 bindCustomDomain / 传入 certificateId;" +
|
|
8314
|
+
"bindCustomDomain 仅用于首次绑定新域名。"),
|
|
7573
8315
|
targetName: zod_1.z
|
|
7574
8316
|
.string()
|
|
7575
8317
|
.optional()
|
|
@@ -7623,11 +8365,15 @@ function registerGatewayTools(server) {
|
|
|
7623
8365
|
domain: zod_1.z
|
|
7624
8366
|
.string()
|
|
7625
8367
|
.optional()
|
|
7626
|
-
.describe("域名。省略时自动使用环境 IsDefault 默认 HTTP
|
|
8368
|
+
.describe("域名。省略时自动使用环境 DomainType=HTTPSERVICE 的 IsDefault 默认 HTTP 域名(*.{region}.app.tcloudbase.com),不会回退到静态托管 CDN 域名(*.tcloudbaseapp.com,DomainType=STATIC_STORE);也不是 STATIC_STORE 上游绑定。" +
|
|
8369
|
+
"可用 queryGateway(action=\"listRoutes\") 核对实际 Domain / DomainType。" +
|
|
8370
|
+
"已有自定义域名时请显式传入该域名并 createRoute/updateRoute/deleteRoute,即可实现自定义域名访问且无需证书 ID;" +
|
|
8371
|
+
"仅 bindCustomDomain 时表示要新绑定的域名。"),
|
|
7627
8372
|
certificateId: zod_1.z
|
|
7628
8373
|
.string()
|
|
7629
8374
|
.optional()
|
|
7630
|
-
.describe("证书 ID
|
|
8375
|
+
.describe("证书 ID。仅首次 bindCustomDomain 必填。" +
|
|
8376
|
+
"在已有自定义域名上 createRoute / updateRoute / deleteRoute 不需要 certificateId。"),
|
|
7631
8377
|
},
|
|
7632
8378
|
annotations: {
|
|
7633
8379
|
readOnlyHint: false,
|
|
@@ -8218,7 +8964,9 @@ const path_1 = __importDefault(__webpack_require__(2521));
|
|
|
8218
8964
|
const zod_1 = __webpack_require__(2971);
|
|
8219
8965
|
const cloudbase_manager_js_1 = __webpack_require__(3431);
|
|
8220
8966
|
const logger_js_1 = __webpack_require__(3039);
|
|
8967
|
+
const gateway_access_urls_js_1 = __webpack_require__(2435);
|
|
8221
8968
|
const notification_js_1 = __webpack_require__(2720);
|
|
8969
|
+
const cloudrun_config_js_1 = __webpack_require__(8266);
|
|
8222
8970
|
// CloudRun service types
|
|
8223
8971
|
exports.CLOUDRUN_SERVICE_TYPES = ['function', 'container'];
|
|
8224
8972
|
// CloudRun access types
|
|
@@ -8237,10 +8985,11 @@ const queryCloudRunInputSchema = {
|
|
|
8237
8985
|
};
|
|
8238
8986
|
// Input schema for manageCloudRun tool
|
|
8239
8987
|
const ManageCloudRunInputSchema = {
|
|
8240
|
-
action: zod_1.z.enum(['init', 'download', 'run', 'deploy', 'delete', 'createAgent']).describe('云托管服务管理操作类型:init=从模板初始化新的云托管项目代码(在targetPath目录下创建以serverName命名的子目录,支持多种语言和框架模板),download=从云端下载现有服务的代码到本地进行开发,run=在本地运行函数型云托管服务(用于开发和调试,仅支持函数型服务),deploy
|
|
8988
|
+
action: zod_1.z.enum(['init', 'download', 'run', 'deploy', 'delete', 'createAgent', 'updateConfig']).describe('云托管服务管理操作类型:init=从模板初始化新的云托管项目代码(在targetPath目录下创建以serverName命名的子目录,支持多种语言和框架模板),download=从云端下载现有服务的代码到本地进行开发,run=在本地运行函数型云托管服务(用于开发和调试,仅支持函数型服务),deploy=将本地代码部署到云端云托管服务(支持函数型和容器型;已存在服务会 Read-Merge-Write 保留远程 VpcConf/EnvParams/OpenAccessTypes),updateConfig=仅更新服务配置不重新上传代码(对齐控制台服务设置,走 SubmitServerConfigChangeDiff;不需要 targetPath),delete=删除指定的云托管服务(不可恢复,需要确认),createAgent=创建函数型Agent(基于函数型云托管开发AI智能体)'),
|
|
8241
8989
|
serverName: zod_1.z.string().describe('云托管服务名称,用于标识和管理服务。命名规则:支持大小写字母、数字、连字符和下划线,必须以字母开头,长度3-45个字符。在init操作中会作为在targetPath下创建的子目录名,在其他操作中作为目标服务名'),
|
|
8242
8990
|
// Deploy operation parameters
|
|
8243
|
-
targetPath: zod_1.z.string().optional().describe('本地代码路径,必须是绝对路径。在deploy操作中指定要部署的代码目录,在download操作中指定下载目标目录,在init操作中指定云托管服务的上级目录(会在该目录下创建以serverName
|
|
8991
|
+
targetPath: zod_1.z.string().optional().describe('本地代码路径,必须是绝对路径。在deploy操作中指定要部署的代码目录,在download操作中指定下载目标目录,在init操作中指定云托管服务的上级目录(会在该目录下创建以serverName命名的子目录)。updateConfig 不需要此参数。建议约定:项目根目录下的cloudrun/目录,例如:/Users/username/projects/my-project/cloudrun'),
|
|
8992
|
+
envParamsReplaceAll: zod_1.z.boolean().optional().default(false).describe('EnvParams 合并策略(deploy / updateConfig):false(默认)= 与远程按 key 合并(输入覆盖同名 key,远程其余 key 保留);true= 用输入 EnvParams 整包替换远程。仅当显式传入 EnvParams 时生效'),
|
|
8244
8993
|
serverConfig: zod_1.z.object({
|
|
8245
8994
|
OpenAccessTypes: zod_1.z.array(zod_1.z.enum(exports.CLOUDRUN_ACCESS_TYPES)).optional().describe('公网访问类型配置,控制服务的访问权限:OA=办公网访问,PUBLIC=公网访问(默认,可通过HTTPS域名访问),MINIAPP=小程序访问,VPC=VPC访问(仅同VPC内可访问)。可配置多个类型'),
|
|
8246
8995
|
Cpu: zod_1.z.number().positive().optional().describe('CPU规格配置,单位为核。可选值:0.25、0.5、1、2、4、8等。注意:内存规格必须是CPU规格的2倍(如CPU=0.25时内存=0.5,CPU=1时内存=2)。影响服务性能和计费'),
|
|
@@ -8279,7 +9028,7 @@ const ManageCloudRunInputSchema = {
|
|
|
8279
9028
|
VpcConf: zod_1.z.object({
|
|
8280
9029
|
VpcId: zod_1.z.string().describe('VPC网络ID,格式如 vpc-xxxxxxxx。必须与目标数据库/Redis 处于同一地域,并优先选择同一 VPC。禁止猜测或使用占位符;须来自数据库控制台、已有资源详情、callCloudApi 或用户确认。建议首次创建即配置;已存在服务也可在 deploy 时传入,部署后必须用 queryCloudRun detail 复核是否生效'),
|
|
8281
9030
|
SubnetId: zod_1.z.string().describe('子网ID,格式如 subnet-xxxxxxxx。云托管实例将占用该子网 IP,需确保有足够可用 IP'),
|
|
8282
|
-
}).optional().describe('VPC网络配置(实例出网/私有网络)。用于让云托管实例接入指定 VPC,从而内网访问 MySQL/PostgreSQL/Redis/CVM 等资源。与 OpenAccessTypes(外部如何访问本服务)是不同概念。TCP 连库场景必须配置。禁止猜测 VpcId/SubnetId,须来自数据库控制台、已有资源详情、callCloudApi
|
|
9031
|
+
}).optional().describe('VPC网络配置(实例出网/私有网络)。用于让云托管实例接入指定 VPC,从而内网访问 MySQL/PostgreSQL/Redis/CVM 等资源。与 OpenAccessTypes(外部如何访问本服务)是不同概念。TCP 连库场景必须配置。禁止猜测 VpcId/SubnetId,须来自数据库控制台、已有资源详情、callCloudApi 或用户确认。创建时映射为 SDK vpcInfo(CreateType=2);已存在服务可用 updateConfig 或 deploy(RMW 会保留未传入的远程 VpcConf)。部署/更新后必须用 queryCloudRun detail 复核 ServerConfig.VpcConf'),
|
|
8283
9032
|
VolumesConf: zod_1.z.array(zod_1.z.object({
|
|
8284
9033
|
VolumeName: zod_1.z.string().describe('存储卷名称'),
|
|
8285
9034
|
VolumeType: zod_1.z.string().describe('存储卷类型,如CFS表示云文件存储'),
|
|
@@ -8289,7 +9038,7 @@ const ManageCloudRunInputSchema = {
|
|
|
8289
9038
|
PublicAccess: zod_1.z.boolean().optional().describe('是否开启公网访问,true=开启公网访问,false=关闭公网访问'),
|
|
8290
9039
|
PublicAccessPath: zod_1.z.string().optional().describe('公网访问路径配置')
|
|
8291
9040
|
}).optional().describe('公网访问配置,用于控制服务的公网访问策略。可配置是否开启公网访问及访问路径'),
|
|
8292
|
-
}).optional().describe('
|
|
9041
|
+
}).optional().describe('服务配置项,用于 deploy / updateConfig。包括资源规格、访问权限、环境变量、日志、网络等。deploy 未提供时对已存在服务仍会从远程合并保留 VpcConf/EnvParams/OpenAccessTypes;updateConfig 至少需要一个配置字段'),
|
|
8293
9042
|
// Init operation parameters
|
|
8294
9043
|
template: zod_1.z.string().optional().default('helloworld').describe('项目模板标识符,用于指定初始化项目时使用的模板。可通过queryCloudRun的templates操作获取可用模板列表。常用模板:helloworld=Hello World示例,nodejs=Node.js项目模板,python=Python项目模板等'),
|
|
8295
9044
|
// Run operation parameters (function services only)
|
|
@@ -8473,6 +9222,30 @@ function normalizeProcessLogText(logs) {
|
|
|
8473
9222
|
})
|
|
8474
9223
|
.join("\n");
|
|
8475
9224
|
}
|
|
9225
|
+
function normalizeCloudRunDomainUrl(input) {
|
|
9226
|
+
if (typeof input !== "string" || !input.trim())
|
|
9227
|
+
return undefined;
|
|
9228
|
+
const raw = input.trim();
|
|
9229
|
+
return raw.startsWith("http://") || raw.startsWith("https://")
|
|
9230
|
+
? raw
|
|
9231
|
+
: `https://${raw}`;
|
|
9232
|
+
}
|
|
9233
|
+
function resolveCloudRunFallbackAccess(details) {
|
|
9234
|
+
const custom = normalizeCloudRunDomainUrl(details?.BaseInfo?.CustomDomainName);
|
|
9235
|
+
if (custom)
|
|
9236
|
+
return { url: custom, source: "cloudrun.customDomain" };
|
|
9237
|
+
const defaultDomain = normalizeCloudRunDomainUrl(details?.BaseInfo?.DefaultDomainName);
|
|
9238
|
+
if (defaultDomain)
|
|
9239
|
+
return { url: defaultDomain, source: "cloudrun.defaultDomain" };
|
|
9240
|
+
const publicDomain = normalizeCloudRunDomainUrl(details?.BaseInfo?.PublicDomain) ??
|
|
9241
|
+
normalizeCloudRunDomainUrl(details?.AccessInfo?.PublicDomain);
|
|
9242
|
+
if (publicDomain)
|
|
9243
|
+
return { url: publicDomain, source: "cloudrun.publicDomain" };
|
|
9244
|
+
const internal = normalizeCloudRunDomainUrl(details?.BaseInfo?.InternalDomain);
|
|
9245
|
+
if (internal)
|
|
9246
|
+
return { url: internal, source: "cloudrun.internalDomain" };
|
|
9247
|
+
return {};
|
|
9248
|
+
}
|
|
8476
9249
|
/**
|
|
8477
9250
|
* Format CloudRun service info for display
|
|
8478
9251
|
*/
|
|
@@ -8706,7 +9479,7 @@ function registerCloudRunTools(server) {
|
|
|
8706
9479
|
// Tool 2: Manage CloudRun services (write operations)
|
|
8707
9480
|
server.registerTool("manageCloudRun", {
|
|
8708
9481
|
title: "管理 CloudRun 服务",
|
|
8709
|
-
description: "管理云托管服务,按开发顺序支持:初始化项目(可从模板开始,模板列表可通过 queryCloudRun
|
|
9482
|
+
description: "管理云托管服务,按开发顺序支持:初始化项目(可从模板开始,模板列表可通过 queryCloudRun 查询)、下载服务代码、本地运行(仅函数型服务)、部署代码、仅更新配置(updateConfig,无需重新上传代码)、删除服务。deploy 对已存在服务会先读取远程配置再合并(保留 VpcConf/EnvParams/OpenAccessTypes)。updateConfig 对齐控制台服务设置页。删除操作需要确认,建议设置force=true。",
|
|
8710
9483
|
inputSchema: ManageCloudRunInputSchema,
|
|
8711
9484
|
annotations: {
|
|
8712
9485
|
readOnlyHint: false,
|
|
@@ -8897,6 +9670,8 @@ for await (let x of res.textStream) {
|
|
|
8897
9670
|
}
|
|
8898
9671
|
// Determine service type - use input.serverType if provided, otherwise auto-detect
|
|
8899
9672
|
let serverType;
|
|
9673
|
+
let remoteServerConfig = null;
|
|
9674
|
+
let existingService = false;
|
|
8900
9675
|
if (input.serverType) {
|
|
8901
9676
|
serverType = input.serverType;
|
|
8902
9677
|
}
|
|
@@ -8905,6 +9680,8 @@ for await (let x of res.textStream) {
|
|
|
8905
9680
|
// First try to get existing service details
|
|
8906
9681
|
const details = await cloudrunService.detail({ serverName: input.serverName });
|
|
8907
9682
|
serverType = details.BaseInfo?.ServerType || 'container';
|
|
9683
|
+
remoteServerConfig = (details.ServerConfig || null);
|
|
9684
|
+
existingService = true;
|
|
8908
9685
|
}
|
|
8909
9686
|
catch (e) {
|
|
8910
9687
|
// If service doesn't exist, determine by project structure
|
|
@@ -8938,20 +9715,40 @@ for await (let x of res.textStream) {
|
|
|
8938
9715
|
}
|
|
8939
9716
|
}
|
|
8940
9717
|
}
|
|
9718
|
+
// When serverType was provided explicitly, still try to load remote config for RMW.
|
|
9719
|
+
if (!existingService) {
|
|
9720
|
+
try {
|
|
9721
|
+
const details = await cloudrunService.detail({ serverName: input.serverName });
|
|
9722
|
+
remoteServerConfig = (details.ServerConfig || null);
|
|
9723
|
+
existingService = true;
|
|
9724
|
+
}
|
|
9725
|
+
catch {
|
|
9726
|
+
// New service create path
|
|
9727
|
+
}
|
|
9728
|
+
}
|
|
9729
|
+
let mergedFromRemote = [];
|
|
9730
|
+
let effectiveServerConfig = input.serverConfig;
|
|
9731
|
+
if (existingService) {
|
|
9732
|
+
const mergedResult = (0, cloudrun_config_js_1.mergeCloudRunServerConfig)({
|
|
9733
|
+
remote: remoteServerConfig,
|
|
9734
|
+
input: input.serverConfig || {},
|
|
9735
|
+
envParamsReplaceAll: Boolean(input.envParamsReplaceAll),
|
|
9736
|
+
});
|
|
9737
|
+
effectiveServerConfig = mergedResult.merged;
|
|
9738
|
+
mergedFromRemote = mergedResult.mergedFromRemote;
|
|
9739
|
+
}
|
|
8941
9740
|
const deployParams = {
|
|
8942
9741
|
serverName: input.serverName,
|
|
8943
9742
|
targetPath: targetPath,
|
|
8944
9743
|
force: input.force,
|
|
8945
9744
|
serverType: serverType,
|
|
8946
9745
|
};
|
|
8947
|
-
|
|
8948
|
-
|
|
8949
|
-
deployParams.serverConfig = input.serverConfig;
|
|
9746
|
+
if (effectiveServerConfig && Object.keys(effectiveServerConfig).length > 0) {
|
|
9747
|
+
deployParams.serverConfig = effectiveServerConfig;
|
|
8950
9748
|
}
|
|
8951
9749
|
// Manager SDK create path prefers top-level vpcInfo (CreateCloudRunServer.VpcInfo).
|
|
8952
9750
|
// Map serverConfig.VpcConf → vpcInfo so first-time create actually binds VPC.
|
|
8953
|
-
|
|
8954
|
-
const vpcConf = input.serverConfig?.VpcConf;
|
|
9751
|
+
const vpcConf = effectiveServerConfig?.VpcConf;
|
|
8955
9752
|
if (vpcConf?.VpcId?.trim() && vpcConf?.SubnetId?.trim()) {
|
|
8956
9753
|
deployParams.vpcInfo = {
|
|
8957
9754
|
VpcId: vpcConf.VpcId.trim(),
|
|
@@ -8982,47 +9779,46 @@ for await (let x of res.textStream) {
|
|
|
8982
9779
|
catch (error) {
|
|
8983
9780
|
(0, logger_js_1.debug)('cloudbaserc.json creation skipped:', error instanceof Error ? error : new Error(String(error)));
|
|
8984
9781
|
}
|
|
9782
|
+
let preferredAccessUrl;
|
|
9783
|
+
let preferredAccessUrls = [];
|
|
9784
|
+
let preferredAccessSource;
|
|
9785
|
+
let verifiedConfigSnapshot;
|
|
9786
|
+
try {
|
|
9787
|
+
const serviceDetails = await cloudrunService.detail({
|
|
9788
|
+
serverName: input.serverName,
|
|
9789
|
+
});
|
|
9790
|
+
verifiedConfigSnapshot = (0, cloudrun_config_js_1.summarizeConfigSnapshot)(serviceDetails?.ServerConfig);
|
|
9791
|
+
const fallback = resolveCloudRunFallbackAccess(serviceDetails);
|
|
9792
|
+
const gateway = await (0, gateway_access_urls_js_1.resolveGatewayAccessUrls)({
|
|
9793
|
+
envId: currentEnvId,
|
|
9794
|
+
upstreamResourceName: input.serverName,
|
|
9795
|
+
upstreamResourceTypes: ["CBR"],
|
|
9796
|
+
getManager: async () => {
|
|
9797
|
+
const manager = await getManager();
|
|
9798
|
+
if (!manager) {
|
|
9799
|
+
throw new Error("cloudbase manager unavailable");
|
|
9800
|
+
}
|
|
9801
|
+
return manager;
|
|
9802
|
+
},
|
|
9803
|
+
});
|
|
9804
|
+
const preferred = (0, gateway_access_urls_js_1.preferGatewayOrFallback)({
|
|
9805
|
+
gateway,
|
|
9806
|
+
fallbackUrl: fallback.url,
|
|
9807
|
+
fallbackSource: fallback.source,
|
|
9808
|
+
});
|
|
9809
|
+
preferredAccessUrl = preferred.accessUrl;
|
|
9810
|
+
preferredAccessUrls = preferred.accessUrls;
|
|
9811
|
+
preferredAccessSource = preferred.accessUrlSource;
|
|
9812
|
+
}
|
|
9813
|
+
catch {
|
|
9814
|
+
// best-effort URL enrichment only
|
|
9815
|
+
}
|
|
8985
9816
|
// Send deployment notification to CodeBuddy IDE
|
|
8986
9817
|
try {
|
|
8987
|
-
// Query service details to get access URL
|
|
8988
|
-
let serviceUrl = "";
|
|
8989
|
-
try {
|
|
8990
|
-
const serviceDetails = await cloudrunService.detail({ serverName: input.serverName });
|
|
8991
|
-
// Extract access URL from service details
|
|
8992
|
-
// Priority: DefaultDomainName > CustomDomainName > PublicDomain > InternalDomain
|
|
8993
|
-
const details = serviceDetails; // Use any to access dynamic properties
|
|
8994
|
-
if (details?.BaseInfo?.DefaultDomainName) {
|
|
8995
|
-
// DefaultDomainName is already a complete URL (e.g., https://...)
|
|
8996
|
-
serviceUrl = details.BaseInfo.DefaultDomainName;
|
|
8997
|
-
}
|
|
8998
|
-
else if (details?.BaseInfo?.CustomDomainName) {
|
|
8999
|
-
// CustomDomainName might be a domain without protocol
|
|
9000
|
-
const customDomain = details.BaseInfo.CustomDomainName;
|
|
9001
|
-
serviceUrl = customDomain.startsWith('http') ? customDomain : `https://${customDomain}`;
|
|
9002
|
-
}
|
|
9003
|
-
else if (details?.BaseInfo?.PublicDomain) {
|
|
9004
|
-
serviceUrl = `https://${details.BaseInfo.PublicDomain}`;
|
|
9005
|
-
}
|
|
9006
|
-
else if (details?.BaseInfo?.InternalDomain) {
|
|
9007
|
-
serviceUrl = `https://${details.BaseInfo.InternalDomain}`;
|
|
9008
|
-
}
|
|
9009
|
-
else if (details?.AccessInfo?.PublicDomain) {
|
|
9010
|
-
serviceUrl = `https://${details.AccessInfo.PublicDomain}`;
|
|
9011
|
-
}
|
|
9012
|
-
else {
|
|
9013
|
-
serviceUrl = ""; // URL not available
|
|
9014
|
-
}
|
|
9015
|
-
}
|
|
9016
|
-
catch (detailErr) {
|
|
9017
|
-
// If query fails, continue with empty URL
|
|
9018
|
-
serviceUrl = "";
|
|
9019
|
-
}
|
|
9020
|
-
// Extract project name from targetPath
|
|
9021
9818
|
const projectName = path_1.default.basename(targetPath);
|
|
9022
|
-
// Send notification
|
|
9023
9819
|
await (0, notification_js_1.sendDeployNotification)(server, {
|
|
9024
9820
|
deployType: 'cloudrun',
|
|
9025
|
-
url:
|
|
9821
|
+
url: preferredAccessUrl ?? "",
|
|
9026
9822
|
projectId: currentEnvId,
|
|
9027
9823
|
projectName: projectName,
|
|
9028
9824
|
consoleUrl: consoleUrl
|
|
@@ -9033,8 +9829,8 @@ for await (let x of res.textStream) {
|
|
|
9033
9829
|
// Error is already logged in sendDeployNotification
|
|
9034
9830
|
}
|
|
9035
9831
|
const dbNetworkRisk = detectCloudRunDbNetworkRisk({
|
|
9036
|
-
envParams:
|
|
9037
|
-
vpcConf:
|
|
9832
|
+
envParams: effectiveServerConfig?.EnvParams,
|
|
9833
|
+
vpcConf: effectiveServerConfig?.VpcConf,
|
|
9038
9834
|
});
|
|
9039
9835
|
const warnings = dbNetworkRisk ? [dbNetworkRisk] : [];
|
|
9040
9836
|
const warningSuffix = dbNetworkRisk
|
|
@@ -9053,6 +9849,25 @@ for await (let x of res.textStream) {
|
|
|
9053
9849
|
serverType: serverType,
|
|
9054
9850
|
cloudbasercGenerated: true,
|
|
9055
9851
|
consoleUrl,
|
|
9852
|
+
...(existingService
|
|
9853
|
+
? {
|
|
9854
|
+
configMerge: {
|
|
9855
|
+
existingService: true,
|
|
9856
|
+
mergedFromRemote,
|
|
9857
|
+
appliedConfig: (0, cloudrun_config_js_1.summarizeConfigSnapshot)(effectiveServerConfig),
|
|
9858
|
+
...(verifiedConfigSnapshot
|
|
9859
|
+
? { verifiedAfterDeploy: verifiedConfigSnapshot }
|
|
9860
|
+
: {}),
|
|
9861
|
+
},
|
|
9862
|
+
}
|
|
9863
|
+
: {}),
|
|
9864
|
+
...(preferredAccessUrl ? { accessUrl: preferredAccessUrl } : {}),
|
|
9865
|
+
...(preferredAccessUrls.length > 0
|
|
9866
|
+
? { accessUrls: preferredAccessUrls }
|
|
9867
|
+
: {}),
|
|
9868
|
+
...(preferredAccessSource
|
|
9869
|
+
? { accessUrlSource: preferredAccessSource }
|
|
9870
|
+
: {}),
|
|
9056
9871
|
...(warnings.length > 0 ? { warnings } : {}),
|
|
9057
9872
|
},
|
|
9058
9873
|
message: `Triggered deployment for ${serverType} service '${input.serverName}' from ${targetPath}. You can follow the progress in ${consoleUrl}.${warningSuffix}`
|
|
@@ -9061,6 +9876,125 @@ for await (let x of res.textStream) {
|
|
|
9061
9876
|
]
|
|
9062
9877
|
};
|
|
9063
9878
|
}
|
|
9879
|
+
case 'updateConfig': {
|
|
9880
|
+
if (!input.serverConfig || Object.keys(input.serverConfig).length === 0) {
|
|
9881
|
+
throw new Error("serverConfig with at least one field is required for updateConfig");
|
|
9882
|
+
}
|
|
9883
|
+
let remoteServerConfig = {};
|
|
9884
|
+
try {
|
|
9885
|
+
const details = await cloudrunService.detail({
|
|
9886
|
+
serverName: input.serverName,
|
|
9887
|
+
});
|
|
9888
|
+
remoteServerConfig = (details.ServerConfig ||
|
|
9889
|
+
{});
|
|
9890
|
+
}
|
|
9891
|
+
catch (error) {
|
|
9892
|
+
throw new Error(buildManageCloudRunErrorMessage("updateConfig", input.serverName, error));
|
|
9893
|
+
}
|
|
9894
|
+
// EnvParams on Diff replaces the whole blob — merge keys unless replaceAll.
|
|
9895
|
+
const dirty = { ...input.serverConfig };
|
|
9896
|
+
if (Object.prototype.hasOwnProperty.call(input.serverConfig, "EnvParams")) {
|
|
9897
|
+
const { merged } = (0, cloudrun_config_js_1.mergeCloudRunServerConfig)({
|
|
9898
|
+
remote: { EnvParams: remoteServerConfig.EnvParams },
|
|
9899
|
+
input: { EnvParams: input.serverConfig.EnvParams },
|
|
9900
|
+
envParamsReplaceAll: Boolean(input.envParamsReplaceAll),
|
|
9901
|
+
});
|
|
9902
|
+
if (merged.EnvParams !== undefined) {
|
|
9903
|
+
dirty.EnvParams = merged.EnvParams;
|
|
9904
|
+
}
|
|
9905
|
+
}
|
|
9906
|
+
let Items;
|
|
9907
|
+
try {
|
|
9908
|
+
Items = (0, cloudrun_config_js_1.parseServerConfigToDiffItems)(dirty);
|
|
9909
|
+
}
|
|
9910
|
+
catch (error) {
|
|
9911
|
+
throw new Error(buildManageCloudRunErrorMessage("updateConfig", input.serverName, error));
|
|
9912
|
+
}
|
|
9913
|
+
if (Items.length === 0) {
|
|
9914
|
+
return {
|
|
9915
|
+
content: [
|
|
9916
|
+
{
|
|
9917
|
+
type: "text",
|
|
9918
|
+
text: JSON.stringify({
|
|
9919
|
+
success: true,
|
|
9920
|
+
data: {
|
|
9921
|
+
serviceName: input.serverName,
|
|
9922
|
+
status: "noop",
|
|
9923
|
+
itemsCount: 0,
|
|
9924
|
+
appliedConfig: (0, cloudrun_config_js_1.summarizeConfigSnapshot)(dirty),
|
|
9925
|
+
verifiedConfig: (0, cloudrun_config_js_1.summarizeConfigSnapshot)(remoteServerConfig),
|
|
9926
|
+
},
|
|
9927
|
+
message: `No effective config changes for '${input.serverName}'.`,
|
|
9928
|
+
}, null, 2),
|
|
9929
|
+
},
|
|
9930
|
+
],
|
|
9931
|
+
};
|
|
9932
|
+
}
|
|
9933
|
+
const currentEnvId = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
|
|
9934
|
+
if (!manager.commonService) {
|
|
9935
|
+
throw new Error("Current CloudBase Manager does not support commonService; cannot call SubmitServerConfigChangeDiff.");
|
|
9936
|
+
}
|
|
9937
|
+
let diffResult;
|
|
9938
|
+
try {
|
|
9939
|
+
diffResult = await manager
|
|
9940
|
+
.commonService("tcbr", "2022-02-17")
|
|
9941
|
+
.call({
|
|
9942
|
+
Action: "SubmitServerConfigChangeDiff",
|
|
9943
|
+
Param: {
|
|
9944
|
+
EnvId: currentEnvId,
|
|
9945
|
+
ServerName: input.serverName,
|
|
9946
|
+
Items,
|
|
9947
|
+
},
|
|
9948
|
+
});
|
|
9949
|
+
}
|
|
9950
|
+
catch (error) {
|
|
9951
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
9952
|
+
if (/ResourceInUse|task|running|进行中/i.test(msg)) {
|
|
9953
|
+
throw new Error(buildManageCloudRunErrorMessage("updateConfig", input.serverName, new Error(`${msg} A deploy or config task may still be running. Wait, then retry; or use queryCloudRun(action="getDeployLog").`)));
|
|
9954
|
+
}
|
|
9955
|
+
throw new Error(buildManageCloudRunErrorMessage("updateConfig", input.serverName, error));
|
|
9956
|
+
}
|
|
9957
|
+
let verifiedConfig = (0, cloudrun_config_js_1.summarizeConfigSnapshot)(remoteServerConfig);
|
|
9958
|
+
try {
|
|
9959
|
+
const after = await cloudrunService.detail({
|
|
9960
|
+
serverName: input.serverName,
|
|
9961
|
+
});
|
|
9962
|
+
verifiedConfig = (0, cloudrun_config_js_1.summarizeConfigSnapshot)(after?.ServerConfig);
|
|
9963
|
+
}
|
|
9964
|
+
catch {
|
|
9965
|
+
// best-effort verify
|
|
9966
|
+
}
|
|
9967
|
+
const likelyRedeploy = (0, cloudrun_config_js_1.listLikelyRedeployFields)(dirty);
|
|
9968
|
+
const consoleUrl = `https://tcb.cloud.tencent.com/dev?envId=${currentEnvId}#/platform-run/service/detail?serverName=${input.serverName}&tabId=overview&envId=${currentEnvId}`;
|
|
9969
|
+
const taskId = diffResult?.TaskId ??
|
|
9970
|
+
diffResult?.Response?.TaskId ??
|
|
9971
|
+
diffResult?.taskId;
|
|
9972
|
+
const redeployHint = likelyRedeploy.length > 0
|
|
9973
|
+
? ` Fields that often trigger redeploy-with-online-image: ${likelyRedeploy.join(", ")}.`
|
|
9974
|
+
: " Change may apply as a hot update (e.g. MinNum/MaxNum/AccessTypes).";
|
|
9975
|
+
return {
|
|
9976
|
+
content: [
|
|
9977
|
+
{
|
|
9978
|
+
type: "text",
|
|
9979
|
+
text: JSON.stringify({
|
|
9980
|
+
success: true,
|
|
9981
|
+
data: {
|
|
9982
|
+
serviceName: input.serverName,
|
|
9983
|
+
status: "configUpdating",
|
|
9984
|
+
itemsCount: Items.length,
|
|
9985
|
+
itemsKeys: Items.map((i) => i.Key),
|
|
9986
|
+
...(taskId ? { taskId } : {}),
|
|
9987
|
+
appliedConfig: (0, cloudrun_config_js_1.summarizeConfigSnapshot)(dirty),
|
|
9988
|
+
verifiedConfig,
|
|
9989
|
+
likelyRedeployFields: likelyRedeploy,
|
|
9990
|
+
consoleUrl,
|
|
9991
|
+
},
|
|
9992
|
+
message: `Submitted config change for '${input.serverName}'.${redeployHint} Verify with queryCloudRun(action="detail"). Console: ${consoleUrl}`,
|
|
9993
|
+
}, null, 2),
|
|
9994
|
+
},
|
|
9995
|
+
],
|
|
9996
|
+
};
|
|
9997
|
+
}
|
|
9064
9998
|
case 'run': {
|
|
9065
9999
|
if (!targetPath) {
|
|
9066
10000
|
throw new Error("targetPath is required for run operation");
|
|
@@ -9592,6 +10526,31 @@ function registerAppAuthTools(server) {
|
|
|
9592
10526
|
return (0, json_content_js_1.jsonContent)(buildErrorEnvelope(error));
|
|
9593
10527
|
}
|
|
9594
10528
|
};
|
|
10529
|
+
// Best-effort inventory read used to verify whether CreateApiKey really produced a new key.
|
|
10530
|
+
// A failure here must not block key creation, so it returns null to mark "unknown"
|
|
10531
|
+
// rather than an empty list, which would be indistinguishable from "no keys exist".
|
|
10532
|
+
const listApiKeyRecords = async (cloudbase, keyType) => {
|
|
10533
|
+
try {
|
|
10534
|
+
const result = await cloudbase.env.describeApiKeyList({
|
|
10535
|
+
KeyType: keyType,
|
|
10536
|
+
PageNumber: 1,
|
|
10537
|
+
PageSize: 50,
|
|
10538
|
+
});
|
|
10539
|
+
return extractApiKeyList(result);
|
|
10540
|
+
}
|
|
10541
|
+
catch {
|
|
10542
|
+
return null;
|
|
10543
|
+
}
|
|
10544
|
+
};
|
|
10545
|
+
const listApiKeyIds = async (cloudbase, keyType) => {
|
|
10546
|
+
const records = await listApiKeyRecords(cloudbase, keyType);
|
|
10547
|
+
if (records === null) {
|
|
10548
|
+
return null;
|
|
10549
|
+
}
|
|
10550
|
+
return new Set(records
|
|
10551
|
+
.map((item) => (typeof item.KeyId === "string" ? item.KeyId : null))
|
|
10552
|
+
.filter((item) => item !== null));
|
|
10553
|
+
};
|
|
9595
10554
|
const describePublishableKey = async (cloudbase, envId) => {
|
|
9596
10555
|
const result = await cloudbase.env.describeApiKeyList({ KeyType: "publish_key", PageNumber: 1, PageSize: 10 });
|
|
9597
10556
|
return {
|
|
@@ -9712,7 +10671,7 @@ function registerAppAuthTools(server) {
|
|
|
9712
10671
|
}));
|
|
9713
10672
|
server.registerTool?.("manageAppAuth", {
|
|
9714
10673
|
title: "管理 CloudBase 应用认证配置",
|
|
9715
|
-
description: "CloudBase 应用侧认证配置写入口。用于修改登录方式、provider、client 配置,确保 publishable key,以及创建或删除 API key、自定义登录密钥。⚠️ 本工具为管理端配置工具,不执行用户登录。当任务要求编写客户端登录代码时(例如「用 JS SDK 登录」),应先通过本工具完成配置(如启用 usernamePassword、获取 publishable key),再在项目代码中编写 @cloudbase/js-sdk 客户端登录代码(如 auth.signInWithPassword()),而非使用本工具完成登录。若前端要接受普通用户名样式标识符,应先执行 action=patchLoginStrategy 并传入 patch={ usernamePassword: true }
|
|
10674
|
+
description: "CloudBase 应用侧认证配置写入口。用于修改登录方式、provider、client 配置,确保 publishable key,以及创建或删除 API key、自定义登录密钥。⚠️ 本工具为管理端配置工具,不执行用户登录。当任务要求编写客户端登录代码时(例如「用 JS SDK 登录」),应先通过本工具完成配置(如启用 usernamePassword、获取 publishable key),再在项目代码中编写 @cloudbase/js-sdk 客户端登录代码(如 auth.signInWithPassword()),而非使用本工具完成登录。若前端要接受普通用户名样式标识符,应先执行 action=patchLoginStrategy 并传入 patch={ usernamePassword: true },再实现对应前端登录逻辑。⚠️ action=createApiKey 返回体中的 created 字段表示是否真正新建:created=false 说明复用了环境中已存在的 key,此时 keyName/expireIn 入参不会生效,返回的 keyName/expireAt 均为服务端真实值,并会附带 warnings,切勿把它当作临时凭证分发。",
|
|
9716
10675
|
inputSchema: {
|
|
9717
10676
|
action: zod_1.z.enum(MANAGE_APP_AUTH_ACTIONS),
|
|
9718
10677
|
patch: zod_1.z
|
|
@@ -9737,8 +10696,16 @@ function registerAppAuthTools(server) {
|
|
|
9737
10696
|
.enum(APP_AUTH_KEY_TYPES)
|
|
9738
10697
|
.optional()
|
|
9739
10698
|
.describe("createApiKey 时的 API key 类型,默认 publish_key"),
|
|
9740
|
-
keyName: zod_1.z
|
|
9741
|
-
|
|
10699
|
+
keyName: zod_1.z
|
|
10700
|
+
.string()
|
|
10701
|
+
.optional()
|
|
10702
|
+
.describe("createApiKey 时的 API key 名称;服务端可能忽略该值(如 publish_key 每环境唯一时),返回体中的 keyName 一律为服务端实际存储的名称"),
|
|
10703
|
+
expireIn: zod_1.z
|
|
10704
|
+
.number()
|
|
10705
|
+
.int()
|
|
10706
|
+
.min(0)
|
|
10707
|
+
.optional()
|
|
10708
|
+
.describe("createApiKey 时的有效期,单位秒;0 表示不过期。复用已有 key 时该值不生效,此时返回体会带 created=false 与 warnings"),
|
|
9742
10709
|
keyId: zod_1.z.string().optional().describe("deleteApiKey 时的 API key 唯一标识"),
|
|
9743
10710
|
},
|
|
9744
10711
|
annotations: {
|
|
@@ -9863,6 +10830,7 @@ function registerAppAuthTools(server) {
|
|
|
9863
10830
|
}
|
|
9864
10831
|
case "createApiKey": {
|
|
9865
10832
|
const effectiveKeyType = keyType ?? "publish_key";
|
|
10833
|
+
const before = await listApiKeyIds(cloudbase, effectiveKeyType);
|
|
9866
10834
|
const result = await cloudbase.env.createApiKey({
|
|
9867
10835
|
KeyType: effectiveKeyType,
|
|
9868
10836
|
...(keyName ? { KeyName: keyName } : {}),
|
|
@@ -9870,19 +10838,44 @@ function registerAppAuthTools(server) {
|
|
|
9870
10838
|
});
|
|
9871
10839
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
9872
10840
|
const createApiKeyResult = normalizePlainObject(result, "createApiKeyResult") ?? {};
|
|
10841
|
+
const resultKeyId = typeof createApiKeyResult.KeyId === "string" ? createApiKeyResult.KeyId : null;
|
|
10842
|
+
const created = before === null || resultKeyId === null ? null : !before.has(resultKeyId);
|
|
10843
|
+
// The backend is the only source of truth for the stored name / expiry:
|
|
10844
|
+
// re-read the key record instead of echoing back the request parameters.
|
|
10845
|
+
const serverRecord = resultKeyId
|
|
10846
|
+
? (await listApiKeyRecords(cloudbase, effectiveKeyType))?.find((item) => item.KeyId === resultKeyId) ?? null
|
|
10847
|
+
: null;
|
|
10848
|
+
const keyNameSource = serverRecord ?? createApiKeyResult;
|
|
10849
|
+
const resolvedKeyName = getNonEmptyString(keyNameSource.Name) ??
|
|
10850
|
+
getNonEmptyString(keyNameSource.KeyName) ??
|
|
10851
|
+
null;
|
|
10852
|
+
const resolvedExpireAt = getNonEmptyString(serverRecord?.ExpireAt) ??
|
|
10853
|
+
getNonEmptyString(createApiKeyResult.ExpireAt) ??
|
|
10854
|
+
null;
|
|
10855
|
+
const resolvedCreatedAt = getNonEmptyString(serverRecord?.CreateAt) ??
|
|
10856
|
+
getNonEmptyString(createApiKeyResult.CreateAt) ??
|
|
10857
|
+
null;
|
|
10858
|
+
const warnings = [];
|
|
10859
|
+
if (created === false) {
|
|
10860
|
+
warnings.push(`keyType=${effectiveKeyType} 未创建新的 key,返回的是环境中已存在的 key(keyId=${resultKeyId})。若该类型每个环境唯一,请改用 action=ensurePublishableKey 语义理解此结果。`);
|
|
10861
|
+
}
|
|
10862
|
+
if (keyName && resolvedKeyName !== null && resolvedKeyName !== keyName) {
|
|
10863
|
+
warnings.push(`请求的 keyName="${keyName}" 未生效,服务端实际存储的名称为 "${resolvedKeyName}"。`);
|
|
10864
|
+
}
|
|
10865
|
+
if (typeof expireIn === "number" && created === false) {
|
|
10866
|
+
warnings.push(`请求的 expireIn=${expireIn} 未生效,返回的 key 使用其原有过期时间 ${resolvedExpireAt ?? "unknown"}。请勿将其视为临时凭证。`);
|
|
10867
|
+
}
|
|
9873
10868
|
return {
|
|
9874
10869
|
success: true,
|
|
9875
10870
|
envId,
|
|
9876
10871
|
keyType: effectiveKeyType,
|
|
9877
|
-
keyId:
|
|
9878
|
-
keyName:
|
|
9879
|
-
|
|
9880
|
-
|
|
9881
|
-
|
|
9882
|
-
|
|
9883
|
-
|
|
9884
|
-
expireAt: typeof result.ExpireAt === "string" ? result.ExpireAt : null,
|
|
9885
|
-
createdAt: typeof result.CreateAt === "string" ? result.CreateAt : null,
|
|
10872
|
+
keyId: resultKeyId,
|
|
10873
|
+
keyName: resolvedKeyName,
|
|
10874
|
+
apiKey: typeof createApiKeyResult.ApiKey === "string" ? createApiKeyResult.ApiKey : null,
|
|
10875
|
+
expireAt: resolvedExpireAt,
|
|
10876
|
+
createdAt: resolvedCreatedAt,
|
|
10877
|
+
...(typeof created === "boolean" ? { created } : {}),
|
|
10878
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
9886
10879
|
};
|
|
9887
10880
|
}
|
|
9888
10881
|
case "deleteApiKey": {
|
|
@@ -9984,7 +10977,7 @@ class TelemetryReporter {
|
|
|
9984
10977
|
const nodeVersion = process.version; // Node.js版本
|
|
9985
10978
|
const arch = os_1.default.arch(); // 系统架构
|
|
9986
10979
|
// 从构建时注入的版本号获取MCP版本信息
|
|
9987
|
-
const mcpVersion = process.env.npm_package_version || "2.25.
|
|
10980
|
+
const mcpVersion = process.env.npm_package_version || "2.25.3" || 0;
|
|
9988
10981
|
return {
|
|
9989
10982
|
userAgent: `${osType} ${osRelease} ${arch} ${nodeVersion} CloudBase-MCP/${mcpVersion}`,
|
|
9990
10983
|
deviceId: this.deviceId,
|
|
@@ -10328,6 +11321,7 @@ exports.registerFunctionTools = registerFunctionTools;
|
|
|
10328
11321
|
const zod_1 = __webpack_require__(2971);
|
|
10329
11322
|
const cloudbase_manager_js_1 = __webpack_require__(3431);
|
|
10330
11323
|
const cloud_mode_js_1 = __webpack_require__(9684);
|
|
11324
|
+
const gateway_access_urls_js_1 = __webpack_require__(2435);
|
|
10331
11325
|
const json_content_js_1 = __webpack_require__(4365);
|
|
10332
11326
|
const logger_js_1 = __webpack_require__(3039);
|
|
10333
11327
|
const fs_1 = __webpack_require__(4421);
|
|
@@ -11172,9 +12166,33 @@ function registerFunctionTools(server) {
|
|
|
11172
12166
|
const message = func.type === "HTTP"
|
|
11173
12167
|
? `已创建 HTTP 函数 ${functionName}。如果后续需要通过 URL 访问,请显式调用 manageGateway(action="createRoute"),并把 upstreamResourceType="WEB_SCF" 一起传入,再按实际路径和鉴权需求创建访问入口。评测或其他外部调用方可能会以匿名身份访问,而且失败后不一定会把 EXCEED_AUTHORITY 再反馈给 AI;交付前请主动确认访问路径和函数安全规则,若已出现 EXCEED_AUTHORITY,请先调用 queryPermissions(action="getResourcePermission", resourceType="function", resourceId="${functionName}") 查看当前规则,再按需要使用 managePermissions(action="updateResourcePermission") 调整权限。`
|
|
11174
12168
|
: `已创建函数 ${functionName}`;
|
|
12169
|
+
let accessUrl;
|
|
12170
|
+
let accessUrls = [];
|
|
12171
|
+
let accessUrlSource;
|
|
12172
|
+
if (func.type === "HTTP") {
|
|
12173
|
+
const envId = cloudBaseOptions?.envId ?? await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
|
|
12174
|
+
const gateway = await (0, gateway_access_urls_js_1.resolveGatewayAccessUrls)({
|
|
12175
|
+
envId,
|
|
12176
|
+
upstreamResourceName: functionName,
|
|
12177
|
+
upstreamResourceTypes: ["WEB_SCF", "SCF"],
|
|
12178
|
+
getManager: async () => {
|
|
12179
|
+
const manager = await getManager();
|
|
12180
|
+
if (!manager) {
|
|
12181
|
+
throw new Error("cloudbase manager unavailable");
|
|
12182
|
+
}
|
|
12183
|
+
return manager;
|
|
12184
|
+
},
|
|
12185
|
+
});
|
|
12186
|
+
accessUrl = gateway.accessUrl;
|
|
12187
|
+
accessUrls = gateway.accessUrls;
|
|
12188
|
+
accessUrlSource = gateway.accessUrlSource;
|
|
12189
|
+
}
|
|
11175
12190
|
return buildEnvelope({
|
|
11176
12191
|
action: input.action,
|
|
11177
12192
|
functionName,
|
|
12193
|
+
...(accessUrl ? { accessUrl } : {}),
|
|
12194
|
+
...(accessUrls.length > 0 ? { accessUrls } : {}),
|
|
12195
|
+
...(accessUrlSource ? { accessUrlSource } : {}),
|
|
11178
12196
|
raw: result,
|
|
11179
12197
|
}, message, nextActions);
|
|
11180
12198
|
}
|
|
@@ -11213,11 +12231,33 @@ function registerFunctionTools(server) {
|
|
|
11213
12231
|
throw wrapFunctionOperationError("updateFunctionCode", input.functionName, undefined, error);
|
|
11214
12232
|
}
|
|
11215
12233
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, imageResult);
|
|
12234
|
+
const envId = cloudBaseOptions?.envId ?? await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
|
|
12235
|
+
const imageGatewayAccess = await (0, gateway_access_urls_js_1.resolveGatewayAccessUrls)({
|
|
12236
|
+
envId,
|
|
12237
|
+
upstreamResourceName: input.functionName,
|
|
12238
|
+
upstreamResourceTypes: ["WEB_SCF", "SCF"],
|
|
12239
|
+
getManager: async () => {
|
|
12240
|
+
const manager = await getManager();
|
|
12241
|
+
if (!manager) {
|
|
12242
|
+
throw new Error("cloudbase manager unavailable");
|
|
12243
|
+
}
|
|
12244
|
+
return manager;
|
|
12245
|
+
},
|
|
12246
|
+
});
|
|
11216
12247
|
return buildEnvelope({
|
|
11217
12248
|
action: input.action,
|
|
11218
12249
|
functionName: input.functionName,
|
|
11219
12250
|
deployMode: "image",
|
|
11220
12251
|
imageUri: updateImageConfig.imageUri,
|
|
12252
|
+
...(imageGatewayAccess.accessUrl
|
|
12253
|
+
? { accessUrl: imageGatewayAccess.accessUrl }
|
|
12254
|
+
: {}),
|
|
12255
|
+
...(imageGatewayAccess.accessUrls.length > 0
|
|
12256
|
+
? { accessUrls: imageGatewayAccess.accessUrls }
|
|
12257
|
+
: {}),
|
|
12258
|
+
...(imageGatewayAccess.accessUrlSource
|
|
12259
|
+
? { accessUrlSource: imageGatewayAccess.accessUrlSource }
|
|
12260
|
+
: {}),
|
|
11221
12261
|
raw: imageResult,
|
|
11222
12262
|
}, `已将函数 ${input.functionName} 的镜像更新为 ${updateImageConfig.imageUri}。` +
|
|
11223
12263
|
`部署后可用 queryFunctions(action="getFunctionDetail") 确认函数已就绪(Active)。`, [
|
|
@@ -11248,9 +12288,29 @@ function registerFunctionTools(server) {
|
|
|
11248
12288
|
throw wrapFunctionOperationError("updateFunctionCode", input.functionName, processedRootPath, error);
|
|
11249
12289
|
}
|
|
11250
12290
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
12291
|
+
const envId = cloudBaseOptions?.envId ?? await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
|
|
12292
|
+
const gatewayAccess = await (0, gateway_access_urls_js_1.resolveGatewayAccessUrls)({
|
|
12293
|
+
envId,
|
|
12294
|
+
upstreamResourceName: input.functionName,
|
|
12295
|
+
upstreamResourceTypes: ["WEB_SCF", "SCF"],
|
|
12296
|
+
getManager: async () => {
|
|
12297
|
+
const manager = await getManager();
|
|
12298
|
+
if (!manager) {
|
|
12299
|
+
throw new Error("cloudbase manager unavailable");
|
|
12300
|
+
}
|
|
12301
|
+
return manager;
|
|
12302
|
+
},
|
|
12303
|
+
});
|
|
11251
12304
|
return buildEnvelope({
|
|
11252
12305
|
action: input.action,
|
|
11253
12306
|
functionName: input.functionName,
|
|
12307
|
+
...(gatewayAccess.accessUrl ? { accessUrl: gatewayAccess.accessUrl } : {}),
|
|
12308
|
+
...(gatewayAccess.accessUrls.length > 0
|
|
12309
|
+
? { accessUrls: gatewayAccess.accessUrls }
|
|
12310
|
+
: {}),
|
|
12311
|
+
...(gatewayAccess.accessUrlSource
|
|
12312
|
+
? { accessUrlSource: gatewayAccess.accessUrlSource }
|
|
12313
|
+
: {}),
|
|
11254
12314
|
raw: result,
|
|
11255
12315
|
}, `已更新函数 ${input.functionName} 的代码`, [
|
|
11256
12316
|
{
|
|
@@ -11898,6 +12958,68 @@ function isSchemaDdlRisk(risk, sql) {
|
|
|
11898
12958
|
function buildLocalMigrationFileHint(version, name) {
|
|
11899
12959
|
return `migrations/${version}_${name}.sql`;
|
|
11900
12960
|
}
|
|
12961
|
+
/** How many recent migration records to scan when verifying a freshly pushed version. */
|
|
12962
|
+
const MIGRATION_VERIFY_LIMIT = 100;
|
|
12963
|
+
/**
|
|
12964
|
+
* Collect every migration version mentioned anywhere in a migration-list API response.
|
|
12965
|
+
* The response shape is not strongly typed, so walk it generically and pick up any
|
|
12966
|
+
* `Version` / `MigrationVersion` string field instead of guessing a single container key.
|
|
12967
|
+
*/
|
|
12968
|
+
function collectMigrationVersions(value, found = new Set()) {
|
|
12969
|
+
if (Array.isArray(value)) {
|
|
12970
|
+
for (const item of value) {
|
|
12971
|
+
collectMigrationVersions(item, found);
|
|
12972
|
+
}
|
|
12973
|
+
return found;
|
|
12974
|
+
}
|
|
12975
|
+
if (value && typeof value === "object") {
|
|
12976
|
+
for (const [key, child] of Object.entries(value)) {
|
|
12977
|
+
if ((key === "Version" || key === "MigrationVersion") && typeof child === "string" && child.trim()) {
|
|
12978
|
+
found.add(child.trim());
|
|
12979
|
+
}
|
|
12980
|
+
else {
|
|
12981
|
+
collectMigrationVersions(child, found);
|
|
12982
|
+
}
|
|
12983
|
+
}
|
|
12984
|
+
}
|
|
12985
|
+
return found;
|
|
12986
|
+
}
|
|
12987
|
+
/** Brief retries before concluding a pushed migration never landed (Push can be async). */
|
|
12988
|
+
const MIGRATION_VERIFY_ATTEMPTS = 4;
|
|
12989
|
+
const MIGRATION_VERIFY_DELAY_MS = 1500;
|
|
12990
|
+
/**
|
|
12991
|
+
* Confirm a pushed migration version is present in the remote migration history.
|
|
12992
|
+
*
|
|
12993
|
+
* `PushPGUserMigrations` can return a TaskId without the migration actually being
|
|
12994
|
+
* applied, so the raw API response is not sufficient evidence of success. Retries
|
|
12995
|
+
* briefly to tolerate async application, then returns `true`/`false` when the
|
|
12996
|
+
* history could be read, or `null` when verification itself failed (in which case
|
|
12997
|
+
* the caller must not claim the migration is applied).
|
|
12998
|
+
*/
|
|
12999
|
+
async function verifyMigrationApplied(context, version, cloudBaseOptions) {
|
|
13000
|
+
let lastError;
|
|
13001
|
+
let sawSuccessfulList = false;
|
|
13002
|
+
for (let attempt = 1; attempt <= MIGRATION_VERIFY_ATTEMPTS; attempt++) {
|
|
13003
|
+
try {
|
|
13004
|
+
const history = await callPgMigrationApi(context, "ListPGUserMigrations", { Limit: MIGRATION_VERIFY_LIMIT }, cloudBaseOptions);
|
|
13005
|
+
sawSuccessfulList = true;
|
|
13006
|
+
if (collectMigrationVersions(history).has(version)) {
|
|
13007
|
+
return { applied: true };
|
|
13008
|
+
}
|
|
13009
|
+
}
|
|
13010
|
+
catch (error) {
|
|
13011
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
13012
|
+
// Keep retrying: a transient list failure should not immediately fail verification.
|
|
13013
|
+
}
|
|
13014
|
+
if (attempt < MIGRATION_VERIFY_ATTEMPTS) {
|
|
13015
|
+
await sleep(MIGRATION_VERIFY_DELAY_MS);
|
|
13016
|
+
}
|
|
13017
|
+
}
|
|
13018
|
+
if (sawSuccessfulList) {
|
|
13019
|
+
return { applied: false };
|
|
13020
|
+
}
|
|
13021
|
+
return { applied: null, error: lastError };
|
|
13022
|
+
}
|
|
11901
13023
|
const MIGRATION_VERSION_REQUIRED_MESSAGE = "migrationVersion is required (14-digit UTC timestamp YYYYMMDDHHMMSS). " +
|
|
11902
13024
|
"Decide the version first, write local file migrations/<version>_<migrationName>.sql, " +
|
|
11903
13025
|
"then call planMigration/applyMigration with the same migrationVersion and migrationName.";
|
|
@@ -12770,6 +13892,49 @@ async function handleApplyMigration(args, context, deps, cloudBaseOptions) {
|
|
|
12770
13892
|
}
|
|
12771
13893
|
try {
|
|
12772
13894
|
const result = await callPgMigrationApi(context, "PushPGUserMigrations", params, cloudBaseOptions);
|
|
13895
|
+
// PushPGUserMigrations may return a TaskId without the migration having taken
|
|
13896
|
+
// effect. Confirm against the remote history before reporting success, so callers
|
|
13897
|
+
// never get a false-positive success for a silently dropped migration.
|
|
13898
|
+
const verification = await verifyMigrationApplied(context, version, cloudBaseOptions);
|
|
13899
|
+
if (verification.applied === false) {
|
|
13900
|
+
return buildPgToolResult({
|
|
13901
|
+
success: false,
|
|
13902
|
+
errorCode: "MIGRATION_NOT_APPLIED",
|
|
13903
|
+
data: {
|
|
13904
|
+
migrationVersion: version,
|
|
13905
|
+
migrationName: args.migrationName,
|
|
13906
|
+
localFileHint,
|
|
13907
|
+
apiResult: result,
|
|
13908
|
+
verified: false,
|
|
13909
|
+
},
|
|
13910
|
+
message: `PushPGUserMigrations returned without error, but migrationVersion=${version} is not present in the remote migration history, ` +
|
|
13911
|
+
"so the migration did NOT take effect. Do not assume the schema change is applied. " +
|
|
13912
|
+
"Re-check the SQL and migrationVersion, inspect the migration with action=migrationDetail, and retry action=applyMigration. " +
|
|
13913
|
+
"If the schema change is urgent you may fall back to action=execute with allowDdlViaExecute=true, but that bypasses migration history.",
|
|
13914
|
+
nextActions: [
|
|
13915
|
+
buildNextAction(MANAGE_PG_DATABASE, "listMigrations", "Inspect the remote migration history to confirm the migration is missing.", { action: "listMigrations", limit: 20 }),
|
|
13916
|
+
buildNextAction(MANAGE_PG_DATABASE, "migrationDetail", "Inspect the backend record for this migrationVersion to find why it was not applied.", { action: "migrationDetail", migrationVersion: version }),
|
|
13917
|
+
],
|
|
13918
|
+
});
|
|
13919
|
+
}
|
|
13920
|
+
if (verification.applied === null) {
|
|
13921
|
+
return buildPgToolResult({
|
|
13922
|
+
success: false,
|
|
13923
|
+
errorCode: "MIGRATION_VERIFICATION_FAILED",
|
|
13924
|
+
data: {
|
|
13925
|
+
migrationVersion: version,
|
|
13926
|
+
migrationName: args.migrationName,
|
|
13927
|
+
localFileHint,
|
|
13928
|
+
apiResult: result,
|
|
13929
|
+
verified: null,
|
|
13930
|
+
},
|
|
13931
|
+
message: `PushPGUserMigrations was submitted, but verifying migrationVersion=${version} against the remote history failed: ${verification.error}. ` +
|
|
13932
|
+
"The migration may or may not have been applied — verify with action=listMigrations before running any dependent SQL.",
|
|
13933
|
+
nextActions: [
|
|
13934
|
+
buildNextAction(MANAGE_PG_DATABASE, "listMigrations", "Manually verify whether the remote migration history records this migrationVersion.", { action: "listMigrations", limit: 20 }),
|
|
13935
|
+
],
|
|
13936
|
+
});
|
|
13937
|
+
}
|
|
12773
13938
|
return buildPgToolResult({
|
|
12774
13939
|
success: true,
|
|
12775
13940
|
data: {
|
|
@@ -12777,11 +13942,9 @@ async function handleApplyMigration(args, context, deps, cloudBaseOptions) {
|
|
|
12777
13942
|
migrationName: args.migrationName,
|
|
12778
13943
|
localFileHint,
|
|
12779
13944
|
apiResult: result,
|
|
13945
|
+
verified: true,
|
|
12780
13946
|
},
|
|
12781
|
-
message: `Migrations applied via PushPGUserMigrations. Ensure local file ${localFileHint} exists and matches.`,
|
|
12782
|
-
nextActions: [
|
|
12783
|
-
buildNextAction(MANAGE_PG_DATABASE, "listMigrations", "Verify the remote migration history records the same migrationVersion.", { action: "listMigrations", limit: 20 }),
|
|
12784
|
-
],
|
|
13947
|
+
message: `Migrations applied via PushPGUserMigrations and verified present in the remote migration history. Ensure local file ${localFileHint} exists and matches.`,
|
|
12785
13948
|
});
|
|
12786
13949
|
}
|
|
12787
13950
|
catch (error) {
|
|
@@ -13060,7 +14223,7 @@ function registerPGDatabaseTools(server, providedDeps) {
|
|
|
13060
14223
|
inputSchema: {
|
|
13061
14224
|
action: zod_1.z
|
|
13062
14225
|
.enum(MANAGE_ACTIONS)
|
|
13063
|
-
.describe("操作类型:execute=执行已确认的写入 SQL(DML/GRANT/RLS;schema DDL 默认拒绝,需 allowDdlViaExecute=true);dryRun=只分析 SQL 风险不执行;planMigration=预览迁移计划(需 migrationName + migrationVersion + sql);applyMigration=应用迁移,建表/改 schema 首选(需 migrationName + migrationVersion + sql + confirm=true);listMigrations=查询已应用的 Migration 列表(可传 limit/offset 分页);migrationDetail=查看单条 Migration 详情(需 migrationVersion);rollbackMigration=回滚最近 N 条 Migration(需 lastN + confirm=true);repairMigration=修复 Migration 历史记录(需 migrationVersion + migrationName + repairStatus + repairReason)"),
|
|
14226
|
+
.describe("操作类型:execute=执行已确认的写入 SQL(DML/GRANT/RLS;schema DDL 默认拒绝,需 allowDdlViaExecute=true);dryRun=只分析 SQL 风险不执行;planMigration=预览迁移计划(需 migrationName + migrationVersion + sql);applyMigration=应用迁移,建表/改 schema 首选(需 migrationName + migrationVersion + sql + confirm=true;成功返回前会自动校验该 migrationVersion 已落入远端迁移历史,未落库时返回 success=false 且 errorCode=MIGRATION_NOT_APPLIED);listMigrations=查询已应用的 Migration 列表(可传 limit/offset 分页);migrationDetail=查看单条 Migration 详情(需 migrationVersion);rollbackMigration=回滚最近 N 条 Migration(需 lastN + confirm=true);repairMigration=修复 Migration 历史记录(需 migrationVersion + migrationName + repairStatus + repairReason)"),
|
|
13064
14227
|
sql: zod_1.z
|
|
13065
14228
|
.string()
|
|
13066
14229
|
.optional()
|
|
@@ -14296,7 +15459,7 @@ function registerSetupTools(server) {
|
|
|
14296
15459
|
server.registerTool("downloadTemplate", {
|
|
14297
15460
|
title: "下载项目模板",
|
|
14298
15461
|
description: `自动下载并部署CloudBase项目模板。
|
|
14299
|
-
**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.
|
|
15462
|
+
**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.3" : 0}),便于后续维护和版本追踪\n- 下载 rules 模板时,如果项目中已存在 README.md 文件,系统会自动保护该文件不被覆盖(除非设置 overwrite=true)`,
|
|
14300
15463
|
inputSchema: {
|
|
14301
15464
|
template: zod_1.z
|
|
14302
15465
|
.enum(["react", "vue", "miniprogram", "uniapp", "rules"])
|
|
@@ -14492,6 +15655,7 @@ const zod_1 = __webpack_require__(2971);
|
|
|
14492
15655
|
const cloudbase_manager_js_1 = __webpack_require__(3431);
|
|
14493
15656
|
const json_content_js_1 = __webpack_require__(4365);
|
|
14494
15657
|
const cloud_mode_js_1 = __webpack_require__(9684);
|
|
15658
|
+
const gateway_access_urls_js_1 = __webpack_require__(2435);
|
|
14495
15659
|
const QUERY_APP_ACTIONS = ["listApps", "getApp", "listAppVersions", "getAppVersion", "getBuildLog"];
|
|
14496
15660
|
const MANAGE_APP_ACTIONS = ["deployApp", "getUploadUrl", "deleteApp", "deleteAppVersion"];
|
|
14497
15661
|
const APP_FRAMEWORKS = ["vue", "react", "next", "nuxt", "vite", "angular", "static"];
|
|
@@ -14868,6 +16032,8 @@ function registerAppTools(server) {
|
|
|
14868
16032
|
let appInfo;
|
|
14869
16033
|
let domain;
|
|
14870
16034
|
let accessUrl;
|
|
16035
|
+
let accessUrls = [];
|
|
16036
|
+
let accessUrlSource;
|
|
14871
16037
|
let accessUrlLookupWarning;
|
|
14872
16038
|
try {
|
|
14873
16039
|
appInfo = await appService.describeAppInfo({
|
|
@@ -14876,9 +16042,34 @@ function registerAppTools(server) {
|
|
|
14876
16042
|
});
|
|
14877
16043
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, appInfo);
|
|
14878
16044
|
({ domain, accessUrl } = normalizeAccessUrlFromDomain(appInfo?.Domain));
|
|
16045
|
+
const envId = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
|
|
16046
|
+
const gateway = await (0, gateway_access_urls_js_1.resolveGatewayAccessUrls)({
|
|
16047
|
+
envId,
|
|
16048
|
+
upstreamResourceName: serviceName,
|
|
16049
|
+
upstreamResourceTypes: ["STATIC_STORE"],
|
|
16050
|
+
getManager: async () => {
|
|
16051
|
+
const manager = await getManager();
|
|
16052
|
+
if (!manager) {
|
|
16053
|
+
throw new Error("cloudbase manager unavailable");
|
|
16054
|
+
}
|
|
16055
|
+
return manager;
|
|
16056
|
+
},
|
|
16057
|
+
});
|
|
16058
|
+
const preferred = (0, gateway_access_urls_js_1.preferGatewayOrFallback)({
|
|
16059
|
+
gateway,
|
|
16060
|
+
fallbackUrl: accessUrl,
|
|
16061
|
+
fallbackSource: "describeAppInfo.Domain",
|
|
16062
|
+
});
|
|
16063
|
+
accessUrl = preferred.accessUrl;
|
|
16064
|
+
accessUrls = preferred.accessUrls;
|
|
16065
|
+
accessUrlSource = preferred.accessUrlSource;
|
|
14879
16066
|
}
|
|
14880
16067
|
catch (error) {
|
|
14881
16068
|
accessUrlLookupWarning = error instanceof Error ? error.message : String(error);
|
|
16069
|
+
if (accessUrl) {
|
|
16070
|
+
accessUrls = [accessUrl];
|
|
16071
|
+
accessUrlSource = "describeAppInfo.Domain";
|
|
16072
|
+
}
|
|
14882
16073
|
}
|
|
14883
16074
|
return (0, json_content_js_1.jsonContent)(buildEnvelope({
|
|
14884
16075
|
action,
|
|
@@ -14887,7 +16078,8 @@ function registerAppTools(server) {
|
|
|
14887
16078
|
buildId: BuildId,
|
|
14888
16079
|
domain,
|
|
14889
16080
|
accessUrl,
|
|
14890
|
-
|
|
16081
|
+
accessUrls: accessUrls.length > 0 ? accessUrls : undefined,
|
|
16082
|
+
accessUrlSource,
|
|
14891
16083
|
accessUrlLookupWarning,
|
|
14892
16084
|
app: appInfo,
|
|
14893
16085
|
upload: { cosTimestamp: cosTs },
|
|
@@ -15194,6 +16386,17 @@ async function checkAndCreateFreeEnv(cloudbase, context) {
|
|
|
15194
16386
|
hasInitTcbError: !!newContext.initTcbError,
|
|
15195
16387
|
initTcbError: newContext.initTcbError
|
|
15196
16388
|
});
|
|
16389
|
+
// User-facing notice emitted at the entry: inform the user that an automatic
|
|
16390
|
+
// free-env creation is being attempted. Informational only — no confirm.
|
|
16391
|
+
// Built once up-front so every return path can surface the same notice.
|
|
16392
|
+
const userNotice = [
|
|
16393
|
+
"系统检测到您当前没有可用的 CloudBase 环境,将自动尝试为您创建一个免费环境。",
|
|
16394
|
+
"创建依据:通过 NewUser / ReturningUser / BaasFree 等优惠活动资格检查;",
|
|
16395
|
+
"环境别名:ai-native(默认);",
|
|
16396
|
+
"费用说明:该环境为免费活动赠送,创建过程不会产生费用;如不符合免费条件,将引导您前往购买页;",
|
|
16397
|
+
"流程说明:此为自动流程,无需您确认;创建成功后将自动绑定到当前会话。"
|
|
16398
|
+
].join("\n");
|
|
16399
|
+
(0, logger_js_1.debug)('[env-setup] User notice prepared:', { userNotice });
|
|
15197
16400
|
try {
|
|
15198
16401
|
// Step 1: Query promotional activities
|
|
15199
16402
|
(0, logger_js_1.debug)('[env-setup] Checking promotional activity eligibility...');
|
|
@@ -15233,7 +16436,8 @@ async function checkAndCreateFreeEnv(cloudbase, context) {
|
|
|
15233
16436
|
};
|
|
15234
16437
|
return {
|
|
15235
16438
|
success: false,
|
|
15236
|
-
context: newContext
|
|
16439
|
+
context: newContext,
|
|
16440
|
+
userNotice
|
|
15237
16441
|
};
|
|
15238
16442
|
}
|
|
15239
16443
|
(0, logger_js_1.debug)('[env-setup] User is eligible for free environment, attempting to create...');
|
|
@@ -15298,7 +16502,8 @@ async function checkAndCreateFreeEnv(cloudbase, context) {
|
|
|
15298
16502
|
};
|
|
15299
16503
|
return {
|
|
15300
16504
|
success: false,
|
|
15301
|
-
context: newContext
|
|
16505
|
+
context: newContext,
|
|
16506
|
+
userNotice
|
|
15302
16507
|
};
|
|
15303
16508
|
}
|
|
15304
16509
|
// Report create success
|
|
@@ -15312,7 +16517,8 @@ async function checkAndCreateFreeEnv(cloudbase, context) {
|
|
|
15312
16517
|
return {
|
|
15313
16518
|
success: true,
|
|
15314
16519
|
envId: envId.trim(), // Ensure no whitespace
|
|
15315
|
-
context: newContext
|
|
16520
|
+
context: newContext,
|
|
16521
|
+
userNotice
|
|
15316
16522
|
};
|
|
15317
16523
|
}
|
|
15318
16524
|
catch (createErr) {
|
|
@@ -15371,7 +16577,8 @@ async function checkAndCreateFreeEnv(cloudbase, context) {
|
|
|
15371
16577
|
});
|
|
15372
16578
|
return {
|
|
15373
16579
|
success: false,
|
|
15374
|
-
context: newContext
|
|
16580
|
+
context: newContext,
|
|
16581
|
+
userNotice
|
|
15375
16582
|
};
|
|
15376
16583
|
}
|
|
15377
16584
|
}
|
|
@@ -15392,7 +16599,8 @@ async function checkAndCreateFreeEnv(cloudbase, context) {
|
|
|
15392
16599
|
});
|
|
15393
16600
|
return {
|
|
15394
16601
|
success: false,
|
|
15395
|
-
context: newContext
|
|
16602
|
+
context: newContext,
|
|
16603
|
+
userNotice
|
|
15396
16604
|
};
|
|
15397
16605
|
}
|
|
15398
16606
|
}
|
|
@@ -15444,6 +16652,7 @@ const path_1 = __importDefault(__webpack_require__(2521));
|
|
|
15444
16652
|
const zod_1 = __webpack_require__(2971);
|
|
15445
16653
|
const cloudbase_manager_js_1 = __webpack_require__(3431);
|
|
15446
16654
|
const cloud_mode_js_1 = __webpack_require__(9684);
|
|
16655
|
+
const gateway_access_urls_js_1 = __webpack_require__(2435);
|
|
15447
16656
|
const notification_js_1 = __webpack_require__(2720);
|
|
15448
16657
|
const tool_result_js_1 = __webpack_require__(9835);
|
|
15449
16658
|
const HOSTING_CDN_SWITCH_VALUES = ['on', 'off'];
|
|
@@ -15979,9 +17188,48 @@ function registerHostingTools(server) {
|
|
|
15979
17188
|
// StaticDomain there but not in DescribeStaticStore.
|
|
15980
17189
|
const cdnFromStore = (store.CdnDomain ?? store.StaticDomain);
|
|
15981
17190
|
const staticDomain = cdnFromStore || await resolveHostingStaticDomain(cloudbase, server.logger);
|
|
15982
|
-
const
|
|
17191
|
+
const fallbackAccessUrl = buildHostingAccessUrl(staticDomain, input.cloudPath, input.localPath);
|
|
17192
|
+
const envId = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
|
|
17193
|
+
const gatewayCandidates = Array.from(new Set([
|
|
17194
|
+
store.StaticStoreName,
|
|
17195
|
+
store.Name,
|
|
17196
|
+
store.StoreName,
|
|
17197
|
+
store.Bucket,
|
|
17198
|
+
"staticstore",
|
|
17199
|
+
]
|
|
17200
|
+
.map((item) => (typeof item === "string" ? item.trim() : ""))
|
|
17201
|
+
.filter(Boolean)));
|
|
17202
|
+
let accessUrl = fallbackAccessUrl;
|
|
17203
|
+
let accessUrls = accessUrl ? [accessUrl] : [];
|
|
17204
|
+
let accessUrlSource = accessUrl
|
|
17205
|
+
? "hosting.staticDomain"
|
|
17206
|
+
: undefined;
|
|
17207
|
+
for (const upstreamName of gatewayCandidates) {
|
|
17208
|
+
const gateway = await (0, gateway_access_urls_js_1.resolveGatewayAccessUrls)({
|
|
17209
|
+
envId,
|
|
17210
|
+
upstreamResourceName: upstreamName,
|
|
17211
|
+
upstreamResourceTypes: ["STATIC_STORE"],
|
|
17212
|
+
getManager: async () => {
|
|
17213
|
+
const manager = await getManager();
|
|
17214
|
+
if (!manager) {
|
|
17215
|
+
throw new Error("cloudbase manager unavailable");
|
|
17216
|
+
}
|
|
17217
|
+
return manager;
|
|
17218
|
+
},
|
|
17219
|
+
});
|
|
17220
|
+
const preferred = (0, gateway_access_urls_js_1.preferGatewayOrFallback)({
|
|
17221
|
+
gateway,
|
|
17222
|
+
fallbackUrl: fallbackAccessUrl || undefined,
|
|
17223
|
+
fallbackSource: "hosting.staticDomain",
|
|
17224
|
+
});
|
|
17225
|
+
if (preferred.accessUrl) {
|
|
17226
|
+
accessUrl = preferred.accessUrl;
|
|
17227
|
+
accessUrls = preferred.accessUrls;
|
|
17228
|
+
accessUrlSource = preferred.accessUrlSource;
|
|
17229
|
+
break;
|
|
17230
|
+
}
|
|
17231
|
+
}
|
|
15983
17232
|
try {
|
|
15984
|
-
const envId = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
|
|
15985
17233
|
let projectName = 'unknown';
|
|
15986
17234
|
if (input.localPath) {
|
|
15987
17235
|
try {
|
|
@@ -16016,6 +17264,8 @@ function registerHostingTools(server) {
|
|
|
16016
17264
|
ignore: input.ignore,
|
|
16017
17265
|
staticDomain,
|
|
16018
17266
|
accessUrl,
|
|
17267
|
+
accessUrls,
|
|
17268
|
+
accessUrlSource,
|
|
16019
17269
|
result,
|
|
16020
17270
|
nextActions: uploadPrefix ? [buildFindFilesNextStep(uploadPrefix.replace(/^\/+/, ''))] : undefined,
|
|
16021
17271
|
},
|
|
@@ -16615,6 +17865,219 @@ module.exports = __WEBPACK_EXTERNAL_MODULE_fs_promises_f8dae9d1__;
|
|
|
16615
17865
|
|
|
16616
17866
|
module.exports = __WEBPACK_EXTERNAL_MODULE_os__;
|
|
16617
17867
|
|
|
17868
|
+
/***/ }),
|
|
17869
|
+
|
|
17870
|
+
/***/ 8266:
|
|
17871
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
17872
|
+
|
|
17873
|
+
|
|
17874
|
+
/**
|
|
17875
|
+
* CloudRun serverConfig merge (RMW) and DiffConfigItem helpers.
|
|
17876
|
+
* Aligns with console SubmitServerConfigChangeDiff field mapping.
|
|
17877
|
+
*/
|
|
17878
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
17879
|
+
exports.CONFIG_FIELDS_LIKELY_REDEPLOY = void 0;
|
|
17880
|
+
exports.mergeCloudRunServerConfig = mergeCloudRunServerConfig;
|
|
17881
|
+
exports.assertCpuMemPair = assertCpuMemPair;
|
|
17882
|
+
exports.parseServerConfigToDiffItems = parseServerConfigToDiffItems;
|
|
17883
|
+
exports.summarizeConfigSnapshot = summarizeConfigSnapshot;
|
|
17884
|
+
exports.listLikelyRedeployFields = listLikelyRedeployFields;
|
|
17885
|
+
const SUBMIT_DIFF_MAP = {
|
|
17886
|
+
Cpu: "CpuSpecs",
|
|
17887
|
+
Mem: "MemSpecs",
|
|
17888
|
+
OpenAccessTypes: "AccessTypes",
|
|
17889
|
+
EnvParams: "EnvParam",
|
|
17890
|
+
CustomLogs: "LogPath",
|
|
17891
|
+
};
|
|
17892
|
+
const STRING_KEYS = new Set([
|
|
17893
|
+
"CustomLogs",
|
|
17894
|
+
"EnvParams",
|
|
17895
|
+
"CreateTime",
|
|
17896
|
+
"Dockerfile",
|
|
17897
|
+
"BuildDir",
|
|
17898
|
+
"LogType",
|
|
17899
|
+
"LogSetId",
|
|
17900
|
+
"LogTopicId",
|
|
17901
|
+
"LogParseType",
|
|
17902
|
+
"Tag",
|
|
17903
|
+
"InternalAccess",
|
|
17904
|
+
"InternalDomain",
|
|
17905
|
+
"OperationMode",
|
|
17906
|
+
"SessionAffinity",
|
|
17907
|
+
]);
|
|
17908
|
+
const INT_KEYS = new Set(["MinNum", "MaxNum", "InitialDelaySeconds", "Port"]);
|
|
17909
|
+
const BOOL_KEYS = new Set(["HasDockerfile"]);
|
|
17910
|
+
const FLOAT_KEYS = new Set(["Cpu", "Mem"]);
|
|
17911
|
+
const ARRAY_KEYS = new Set(["OpenAccessTypes", "EntryPoint", "Cmd"]);
|
|
17912
|
+
function parseEnvParamsObject(raw) {
|
|
17913
|
+
if (!raw || !String(raw).trim()) {
|
|
17914
|
+
return {};
|
|
17915
|
+
}
|
|
17916
|
+
try {
|
|
17917
|
+
const value = JSON.parse(String(raw));
|
|
17918
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
17919
|
+
return {};
|
|
17920
|
+
}
|
|
17921
|
+
const out = {};
|
|
17922
|
+
for (const [k, v] of Object.entries(value)) {
|
|
17923
|
+
if (v === undefined || v === null)
|
|
17924
|
+
continue;
|
|
17925
|
+
out[k] = typeof v === "string" ? v : String(v);
|
|
17926
|
+
}
|
|
17927
|
+
return out;
|
|
17928
|
+
}
|
|
17929
|
+
catch {
|
|
17930
|
+
return {};
|
|
17931
|
+
}
|
|
17932
|
+
}
|
|
17933
|
+
function stringifyEnvParams(obj) {
|
|
17934
|
+
return JSON.stringify(obj);
|
|
17935
|
+
}
|
|
17936
|
+
function isCompleteVpcConf(vpc) {
|
|
17937
|
+
return Boolean(vpc?.VpcId?.trim() && vpc?.SubnetId?.trim());
|
|
17938
|
+
}
|
|
17939
|
+
/**
|
|
17940
|
+
* Merge remote ServerConfig with deploy/update input.
|
|
17941
|
+
* Explicit input wins for provided fields; EnvParams merges by key unless replaceAll.
|
|
17942
|
+
*/
|
|
17943
|
+
function mergeCloudRunServerConfig(options) {
|
|
17944
|
+
const remote = options.remote || {};
|
|
17945
|
+
const input = options.input || {};
|
|
17946
|
+
const mergedFromRemote = [];
|
|
17947
|
+
const merged = { ...input };
|
|
17948
|
+
if (isCompleteVpcConf(input.VpcConf)) {
|
|
17949
|
+
merged.VpcConf = {
|
|
17950
|
+
VpcId: input.VpcConf.VpcId.trim(),
|
|
17951
|
+
SubnetId: input.VpcConf.SubnetId.trim(),
|
|
17952
|
+
...(input.VpcConf.VpcCIDR ? { VpcCIDR: input.VpcConf.VpcCIDR } : {}),
|
|
17953
|
+
...(input.VpcConf.SubnetCIDR ? { SubnetCIDR: input.VpcConf.SubnetCIDR } : {}),
|
|
17954
|
+
};
|
|
17955
|
+
}
|
|
17956
|
+
else if (isCompleteVpcConf(remote.VpcConf)) {
|
|
17957
|
+
merged.VpcConf = {
|
|
17958
|
+
VpcId: remote.VpcConf.VpcId.trim(),
|
|
17959
|
+
SubnetId: remote.VpcConf.SubnetId.trim(),
|
|
17960
|
+
...(remote.VpcConf.VpcCIDR ? { VpcCIDR: remote.VpcConf.VpcCIDR } : {}),
|
|
17961
|
+
...(remote.VpcConf.SubnetCIDR ? { SubnetCIDR: remote.VpcConf.SubnetCIDR } : {}),
|
|
17962
|
+
};
|
|
17963
|
+
mergedFromRemote.push("VpcConf");
|
|
17964
|
+
}
|
|
17965
|
+
else {
|
|
17966
|
+
delete merged.VpcConf;
|
|
17967
|
+
}
|
|
17968
|
+
const inputHasEnv = Object.prototype.hasOwnProperty.call(input, "EnvParams");
|
|
17969
|
+
const remoteEnv = parseEnvParamsObject(remote.EnvParams);
|
|
17970
|
+
const inputEnv = parseEnvParamsObject(input.EnvParams);
|
|
17971
|
+
if (options.envParamsReplaceAll && inputHasEnv) {
|
|
17972
|
+
merged.EnvParams = stringifyEnvParams(inputEnv);
|
|
17973
|
+
}
|
|
17974
|
+
else if (inputHasEnv || Object.keys(remoteEnv).length > 0) {
|
|
17975
|
+
const combined = options.envParamsReplaceAll
|
|
17976
|
+
? { ...inputEnv }
|
|
17977
|
+
: { ...remoteEnv, ...inputEnv };
|
|
17978
|
+
if (Object.keys(remoteEnv).length > 0 && !options.envParamsReplaceAll) {
|
|
17979
|
+
const preserved = Object.keys(remoteEnv).filter((k) => !(k in inputEnv));
|
|
17980
|
+
if (preserved.length > 0) {
|
|
17981
|
+
mergedFromRemote.push("EnvParams");
|
|
17982
|
+
}
|
|
17983
|
+
}
|
|
17984
|
+
if (Object.keys(combined).length > 0 || inputHasEnv) {
|
|
17985
|
+
merged.EnvParams = stringifyEnvParams(combined);
|
|
17986
|
+
}
|
|
17987
|
+
}
|
|
17988
|
+
// Must be explicit so Manager SDK update path cannot wipe with OA/PUBLIC/MINIAPP defaults.
|
|
17989
|
+
if (Array.isArray(input.OpenAccessTypes) && input.OpenAccessTypes.length > 0) {
|
|
17990
|
+
merged.OpenAccessTypes = [...input.OpenAccessTypes];
|
|
17991
|
+
}
|
|
17992
|
+
else if (Array.isArray(remote.OpenAccessTypes) && remote.OpenAccessTypes.length > 0) {
|
|
17993
|
+
merged.OpenAccessTypes = [...remote.OpenAccessTypes];
|
|
17994
|
+
mergedFromRemote.push("OpenAccessTypes");
|
|
17995
|
+
}
|
|
17996
|
+
return { merged, mergedFromRemote };
|
|
17997
|
+
}
|
|
17998
|
+
function assertCpuMemPair(config) {
|
|
17999
|
+
const hasCpu = config.Cpu !== undefined && config.Cpu !== null;
|
|
18000
|
+
const hasMem = config.Mem !== undefined && config.Mem !== null;
|
|
18001
|
+
if (hasCpu !== hasMem) {
|
|
18002
|
+
throw new Error("Cpu and Mem must be provided together when updating CloudRun config (platform requires the pair).");
|
|
18003
|
+
}
|
|
18004
|
+
}
|
|
18005
|
+
/**
|
|
18006
|
+
* Convert serverConfig object to DiffConfigItem[] for SubmitServerConfigChangeDiff.
|
|
18007
|
+
*/
|
|
18008
|
+
function parseServerConfigToDiffItems(data) {
|
|
18009
|
+
assertCpuMemPair(data);
|
|
18010
|
+
const Items = [];
|
|
18011
|
+
for (const [k, v] of Object.entries(data)) {
|
|
18012
|
+
if (v === undefined || v === null)
|
|
18013
|
+
continue;
|
|
18014
|
+
const Key = SUBMIT_DIFF_MAP[k] || k;
|
|
18015
|
+
if (STRING_KEYS.has(k)) {
|
|
18016
|
+
if (v !== "") {
|
|
18017
|
+
Items.push({ Key, Value: String(v) });
|
|
18018
|
+
}
|
|
18019
|
+
}
|
|
18020
|
+
else if (INT_KEYS.has(k)) {
|
|
18021
|
+
Items.push({ Key, IntValue: Number(v) });
|
|
18022
|
+
}
|
|
18023
|
+
else if (BOOL_KEYS.has(k)) {
|
|
18024
|
+
Items.push({ Key, BoolValue: Boolean(v) });
|
|
18025
|
+
}
|
|
18026
|
+
else if (FLOAT_KEYS.has(k)) {
|
|
18027
|
+
Items.push({ Key, FloatValue: Number(v) });
|
|
18028
|
+
}
|
|
18029
|
+
else if (ARRAY_KEYS.has(k)) {
|
|
18030
|
+
Items.push({ Key, ArrayValue: v });
|
|
18031
|
+
}
|
|
18032
|
+
else if (k === "PolicyDetails") {
|
|
18033
|
+
Items.push({ Key, PolicyDetails: v });
|
|
18034
|
+
}
|
|
18035
|
+
else if (k === "TimerScale") {
|
|
18036
|
+
Items.push({ Key, TimerScale: v });
|
|
18037
|
+
}
|
|
18038
|
+
else if (k === "VpcConf") {
|
|
18039
|
+
Items.push({ Key, VpcConf: v });
|
|
18040
|
+
}
|
|
18041
|
+
else if (k === "VolumesConf") {
|
|
18042
|
+
Items.push({ Key, VolumesConf: v });
|
|
18043
|
+
}
|
|
18044
|
+
else if (k === "PublicNetConf") {
|
|
18045
|
+
Items.push({ Key, PublicNetConf: v });
|
|
18046
|
+
}
|
|
18047
|
+
}
|
|
18048
|
+
return Items;
|
|
18049
|
+
}
|
|
18050
|
+
function summarizeConfigSnapshot(config) {
|
|
18051
|
+
const vpc = config?.VpcConf;
|
|
18052
|
+
const envKeys = Object.keys(parseEnvParamsObject(config?.EnvParams));
|
|
18053
|
+
return {
|
|
18054
|
+
hasVpcConf: isCompleteVpcConf(vpc),
|
|
18055
|
+
...(vpc?.VpcId ? { vpcId: vpc.VpcId } : {}),
|
|
18056
|
+
...(vpc?.SubnetId ? { subnetId: vpc.SubnetId } : {}),
|
|
18057
|
+
...(Array.isArray(config?.OpenAccessTypes)
|
|
18058
|
+
? { openAccessTypes: config.OpenAccessTypes }
|
|
18059
|
+
: {}),
|
|
18060
|
+
envParamKeys: envKeys,
|
|
18061
|
+
};
|
|
18062
|
+
}
|
|
18063
|
+
exports.CONFIG_FIELDS_LIKELY_REDEPLOY = [
|
|
18064
|
+
"VpcConf",
|
|
18065
|
+
"EnvParams",
|
|
18066
|
+
"Cpu",
|
|
18067
|
+
"Mem",
|
|
18068
|
+
"Port",
|
|
18069
|
+
"Dockerfile",
|
|
18070
|
+
"BuildDir",
|
|
18071
|
+
"VolumesConf",
|
|
18072
|
+
"LogType",
|
|
18073
|
+
"LogSetId",
|
|
18074
|
+
"LogTopicId",
|
|
18075
|
+
];
|
|
18076
|
+
function listLikelyRedeployFields(config) {
|
|
18077
|
+
return exports.CONFIG_FIELDS_LIKELY_REDEPLOY.filter((k) => Object.prototype.hasOwnProperty.call(config, k));
|
|
18078
|
+
}
|
|
18079
|
+
|
|
18080
|
+
|
|
16618
18081
|
/***/ }),
|
|
16619
18082
|
|
|
16620
18083
|
/***/ 9097:
|
|
@@ -17878,6 +19341,7 @@ const QUERY_ACTIONS = [
|
|
|
17878
19341
|
"describeTaskStatus",
|
|
17879
19342
|
"getInstanceInfo",
|
|
17880
19343
|
"describeInstance",
|
|
19344
|
+
"getConnectionInfo",
|
|
17881
19345
|
];
|
|
17882
19346
|
const MANAGE_ACTIONS = [
|
|
17883
19347
|
"provisionMySQL",
|
|
@@ -17885,6 +19349,17 @@ const MANAGE_ACTIONS = [
|
|
|
17885
19349
|
"runStatement",
|
|
17886
19350
|
"initializeSchema",
|
|
17887
19351
|
];
|
|
19352
|
+
function sanitizeInstanceInfo(instanceInfo) {
|
|
19353
|
+
return {
|
|
19354
|
+
exists: instanceInfo.exists,
|
|
19355
|
+
envId: instanceInfo.envId,
|
|
19356
|
+
instanceId: instanceInfo.instanceId,
|
|
19357
|
+
schema: instanceInfo.schema,
|
|
19358
|
+
rawStatus: instanceInfo.rawStatus,
|
|
19359
|
+
status: instanceInfo.status,
|
|
19360
|
+
...(instanceInfo.clusterId ? { clusterId: instanceInfo.clusterId } : {}),
|
|
19361
|
+
};
|
|
19362
|
+
}
|
|
17888
19363
|
function buildNextAction(tool, action, reason, suggestedArgs) {
|
|
17889
19364
|
return {
|
|
17890
19365
|
tool,
|
|
@@ -18333,11 +19808,12 @@ async function handleDescribeTaskStatus(args, context) {
|
|
|
18333
19808
|
}
|
|
18334
19809
|
async function handleGetInstanceInfo(context) {
|
|
18335
19810
|
const instanceInfo = await getSqlInstanceInfo(context);
|
|
19811
|
+
const sanitized = sanitizeInstanceInfo(instanceInfo);
|
|
18336
19812
|
return buildSqlToolResult({
|
|
18337
19813
|
success: true,
|
|
18338
|
-
data:
|
|
19814
|
+
data: sanitized,
|
|
18339
19815
|
message: instanceInfo.exists
|
|
18340
|
-
? "Resolved current SQL database instance context."
|
|
19816
|
+
? "Resolved current SQL database instance context (lifecycle only; connection credentials are omitted). Prefer SDK or runQuery/runStatement for app data access. Use getConnectionInfo only for explicit TCP migration."
|
|
18341
19817
|
: "No SQL database instance is currently available for this environment.",
|
|
18342
19818
|
nextActions: instanceInfo.exists
|
|
18343
19819
|
? undefined
|
|
@@ -18346,6 +19822,33 @@ async function handleGetInstanceInfo(context) {
|
|
|
18346
19822
|
],
|
|
18347
19823
|
});
|
|
18348
19824
|
}
|
|
19825
|
+
async function handleGetConnectionInfo(context) {
|
|
19826
|
+
const instanceInfo = await getSqlInstanceInfo(context);
|
|
19827
|
+
if (!instanceInfo.exists) {
|
|
19828
|
+
return buildSqlToolResult({
|
|
19829
|
+
success: false,
|
|
19830
|
+
errorCode: "MYSQL_NOT_CREATED",
|
|
19831
|
+
data: sanitizeInstanceInfo(instanceInfo),
|
|
19832
|
+
message: "No MySQL instance exists for the current environment, so connection details cannot be returned.",
|
|
19833
|
+
nextActions: [
|
|
19834
|
+
buildNextAction(MANAGE_MYSQL_DATABASE, "provisionMySQL", "Provision MySQL before requesting connection details.", { action: "provisionMySQL", confirm: true }),
|
|
19835
|
+
],
|
|
19836
|
+
});
|
|
19837
|
+
}
|
|
19838
|
+
return buildSqlToolResult({
|
|
19839
|
+
success: true,
|
|
19840
|
+
data: {
|
|
19841
|
+
...sanitizeInstanceInfo(instanceInfo),
|
|
19842
|
+
// Full backend passthrough for explicit TCP migration only (may include credentials).
|
|
19843
|
+
clusterDetail: instanceInfo.clusterDetail,
|
|
19844
|
+
createResult: instanceInfo.createResult,
|
|
19845
|
+
},
|
|
19846
|
+
message: "Returned raw MySQL connection/cluster payload for explicit TCP migration only. Prefer CloudBase SDK or queryMysqlDatabase(runQuery)/manageMysqlDatabase(runStatement) for normal app CRUD — do not treat this as the default data path.",
|
|
19847
|
+
nextActions: [
|
|
19848
|
+
buildNextAction(QUERY_MYSQL_DATABASE, "runQuery", "Prefer platform-delegated read-only SQL instead of embedding TCP credentials in app code.", { action: "runQuery", sql: "SELECT 1" }),
|
|
19849
|
+
],
|
|
19850
|
+
});
|
|
19851
|
+
}
|
|
18349
19852
|
async function handleProvisionMySQL(args, context) {
|
|
18350
19853
|
if (args.confirm !== true) {
|
|
18351
19854
|
return buildSqlToolResult({
|
|
@@ -18363,7 +19866,7 @@ async function handleProvisionMySQL(args, context) {
|
|
|
18363
19866
|
existing.status === "RUNNING") {
|
|
18364
19867
|
return buildSqlToolResult({
|
|
18365
19868
|
success: true,
|
|
18366
|
-
data: existing,
|
|
19869
|
+
data: sanitizeInstanceInfo(existing),
|
|
18367
19870
|
message: "A SQL database instance already exists for the current environment.",
|
|
18368
19871
|
nextActions: existing.status === "READY"
|
|
18369
19872
|
? [
|
|
@@ -18696,11 +20199,11 @@ function registerSQLDatabaseTools(server) {
|
|
|
18696
20199
|
};
|
|
18697
20200
|
server.registerTool?.(QUERY_MYSQL_DATABASE, {
|
|
18698
20201
|
title: "查询 CloudBase MySQL 数据库状态或执行只读 SQL",
|
|
18699
|
-
description: "查询 CloudBase MySQL 数据库信息。支持执行只读 SQL、查询 MySQL 开通结果、查询 MySQL
|
|
20202
|
+
description: "查询 CloudBase MySQL 数据库信息。支持执行只读 SQL、查询 MySQL 开通结果、查询 MySQL 任务状态,以及获取当前实例生命周期上下文。标准 getInstanceInfo/describeInstance 不返回连接凭据;仅 getConnectionInfo 透传原始连接/集群载荷(含可能的凭据),且仅用于显式 TCP 迁移。业务 CRUD 优先使用 SDK 或 runQuery/runStatement。",
|
|
18700
20203
|
inputSchema: {
|
|
18701
20204
|
action: zod_1.z
|
|
18702
20205
|
.enum(QUERY_ACTIONS)
|
|
18703
|
-
.describe("runQuery=execute read-only SQL; describeCreateResult=query CreateMySQL result; describeTaskStatus=query MySQL task status; getInstanceInfo=get
|
|
20206
|
+
.describe("runQuery=execute read-only SQL; describeCreateResult=query CreateMySQL result; describeTaskStatus=query MySQL task status; getInstanceInfo=get lifecycle context without connection credentials; describeInstance=alias of getInstanceInfo; getConnectionInfo=passthrough raw connection/cluster payload including possible credentials (TCP migration exception only)"),
|
|
18704
20207
|
sql: zod_1.z
|
|
18705
20208
|
.string()
|
|
18706
20209
|
.optional()
|
|
@@ -18736,6 +20239,8 @@ function registerSQLDatabaseTools(server) {
|
|
|
18736
20239
|
case "getInstanceInfo":
|
|
18737
20240
|
case "describeInstance":
|
|
18738
20241
|
return handleGetInstanceInfo(context);
|
|
20242
|
+
case "getConnectionInfo":
|
|
20243
|
+
return handleGetConnectionInfo(context);
|
|
18739
20244
|
default:
|
|
18740
20245
|
throw new Error(`Unsupported SQL query action: ${args.action}`);
|
|
18741
20246
|
}
|