@cloudbase/cloudbase-mcp 2.27.0 → 2.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/README.zh-CN.md +1 -0
- package/dist/cli.cjs +5 -5
- package/dist/index.cjs +1758 -125
- package/dist/index.js +1762 -130
- package/package.json +4 -2
- package/server.json +62 -0
package/dist/index.cjs
CHANGED
|
@@ -3310,8 +3310,17 @@ warn.forProperties(exports, 'deprecated', ['emitErrs', 'levelLength']);
|
|
|
3310
3310
|
"use strict";
|
|
3311
3311
|
|
|
3312
3312
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
3313
|
-
exports.DEFAULT_CREATE_ENV_RESOURCES = exports.CREATE_ENV_RESOURCE_VALUES = void 0;
|
|
3313
|
+
exports.GATEWAY_ENV_QPS_DEFAULT_RESOURCE_ID = exports.ENV_METRIC_PERIOD_VALUES = exports.ENV_METRIC_NAME_VALUES = exports.ENV_USAGE_MODULE_VALUES = exports.DEFAULT_CREATE_ENV_RESOURCES = exports.CREATE_ENV_RESOURCE_VALUES = void 0;
|
|
3314
3314
|
exports.resolveCreateEnvResources = resolveCreateEnvResources;
|
|
3315
|
+
exports.resolveEnvUsageModules = resolveEnvUsageModules;
|
|
3316
|
+
exports.extractAccountCircleDate = extractAccountCircleDate;
|
|
3317
|
+
exports.resolveEnvUsageDateRange = resolveEnvUsageDateRange;
|
|
3318
|
+
exports.formatEnvMetricTime = formatEnvMetricTime;
|
|
3319
|
+
exports.resolveEnvMetricName = resolveEnvMetricName;
|
|
3320
|
+
exports.resolveEnvMetricPeriod = resolveEnvMetricPeriod;
|
|
3321
|
+
exports.resolveEnvMetricTimeRange = resolveEnvMetricTimeRange;
|
|
3322
|
+
exports.resolveEnvMetricResourceId = resolveEnvMetricResourceId;
|
|
3323
|
+
exports.summarizeEnvMetricCurve = summarizeEnvMetricCurve;
|
|
3315
3324
|
exports.simplifyEnvList = simplifyEnvList;
|
|
3316
3325
|
exports.isUsableNoSqlDatabaseEntry = isUsableNoSqlDatabaseEntry;
|
|
3317
3326
|
exports.registerEnvTools = registerEnvTools;
|
|
@@ -3343,6 +3352,245 @@ function resolveCreateEnvResources(resources) {
|
|
|
3343
3352
|
}
|
|
3344
3353
|
return [...exports.DEFAULT_CREATE_ENV_RESOURCES];
|
|
3345
3354
|
}
|
|
3355
|
+
/**
|
|
3356
|
+
* Resource-usage modules aligned with tcb CLI `USAGE_MODULES`
|
|
3357
|
+
* (`tcb env usage` / `tcb env info --type`).
|
|
3358
|
+
*/
|
|
3359
|
+
exports.ENV_USAGE_MODULE_VALUES = [
|
|
3360
|
+
"FLEXDB",
|
|
3361
|
+
"TDSQL",
|
|
3362
|
+
"SCF",
|
|
3363
|
+
"EKS",
|
|
3364
|
+
"COS",
|
|
3365
|
+
"AI",
|
|
3366
|
+
"HOSTING",
|
|
3367
|
+
"Auth",
|
|
3368
|
+
"APIInvocation",
|
|
3369
|
+
"HTTPInvocation",
|
|
3370
|
+
"VM",
|
|
3371
|
+
"Workflow",
|
|
3372
|
+
"Other",
|
|
3373
|
+
];
|
|
3374
|
+
const ENV_USAGE_MODULE_SET = new Set(exports.ENV_USAGE_MODULE_VALUES);
|
|
3375
|
+
const ENV_USAGE_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
3376
|
+
function resolveEnvUsageModules(type) {
|
|
3377
|
+
if (!Array.isArray(type) || type.length === 0) {
|
|
3378
|
+
return [...exports.ENV_USAGE_MODULE_VALUES];
|
|
3379
|
+
}
|
|
3380
|
+
const normalized = type
|
|
3381
|
+
.map((item) => (typeof item === "string" ? item.trim() : ""))
|
|
3382
|
+
.filter(Boolean);
|
|
3383
|
+
const invalid = normalized.filter((item) => !ENV_USAGE_MODULE_SET.has(item));
|
|
3384
|
+
if (invalid.length > 0) {
|
|
3385
|
+
throw new Error(`无效的用量模块 type: ${invalid.join(", ")}。可选值:${exports.ENV_USAGE_MODULE_VALUES.join(", ")}`);
|
|
3386
|
+
}
|
|
3387
|
+
return normalized;
|
|
3388
|
+
}
|
|
3389
|
+
function extractAccountCircleDate(rawTime) {
|
|
3390
|
+
if (typeof rawTime !== "string" || rawTime.trim().length === 0) {
|
|
3391
|
+
return undefined;
|
|
3392
|
+
}
|
|
3393
|
+
const datePart = rawTime.trim().split(/\s+/)[0];
|
|
3394
|
+
return ENV_USAGE_DATE_RE.test(datePart) ? datePart : undefined;
|
|
3395
|
+
}
|
|
3396
|
+
function normalizeUsageDateInput(value) {
|
|
3397
|
+
return typeof value === "string" && value.trim().length > 0
|
|
3398
|
+
? value.trim()
|
|
3399
|
+
: undefined;
|
|
3400
|
+
}
|
|
3401
|
+
function resolveEnvUsageDateRange(options) {
|
|
3402
|
+
const startFromParams = normalizeUsageDateInput(options.startDate);
|
|
3403
|
+
const endFromParams = normalizeUsageDateInput(options.endDate);
|
|
3404
|
+
if (startFromParams || endFromParams) {
|
|
3405
|
+
if (!startFromParams || !endFromParams) {
|
|
3406
|
+
throw new Error("查询资源用量时 startDate 与 endDate 必须同时提供,格式为 YYYY-MM-DD。");
|
|
3407
|
+
}
|
|
3408
|
+
if (!ENV_USAGE_DATE_RE.test(startFromParams) || !ENV_USAGE_DATE_RE.test(endFromParams)) {
|
|
3409
|
+
throw new Error("startDate / endDate 格式必须为 YYYY-MM-DD。");
|
|
3410
|
+
}
|
|
3411
|
+
if (startFromParams > endFromParams) {
|
|
3412
|
+
throw new Error("startDate 不能晚于 endDate。");
|
|
3413
|
+
}
|
|
3414
|
+
return {
|
|
3415
|
+
startDate: startFromParams,
|
|
3416
|
+
endDate: endFromParams,
|
|
3417
|
+
dateSource: "params",
|
|
3418
|
+
};
|
|
3419
|
+
}
|
|
3420
|
+
const startDate = extractAccountCircleDate(options.accountCircle?.StartTime);
|
|
3421
|
+
const endDate = extractAccountCircleDate(options.accountCircle?.EndTime);
|
|
3422
|
+
if (!startDate || !endDate) {
|
|
3423
|
+
throw new Error("无法从计费周期推导用量日期范围。请显式传入 startDate/endDate(YYYY-MM-DD),或确认环境计费周期可用。");
|
|
3424
|
+
}
|
|
3425
|
+
return { startDate, endDate, dateSource: "accountCircle" };
|
|
3426
|
+
}
|
|
3427
|
+
/**
|
|
3428
|
+
* Monitor metric names for queryEnv(action=metrics).
|
|
3429
|
+
* Subset of TCB DescribeCurveData MetricName from the CloudBase monitor catalog
|
|
3430
|
+
* (gateway/env QPS, functions, NoSQL, MySQL, CloudRun).
|
|
3431
|
+
*/
|
|
3432
|
+
exports.ENV_METRIC_NAME_VALUES = [
|
|
3433
|
+
"GatewayTraceEnvQPS",
|
|
3434
|
+
"EnvQPSAll",
|
|
3435
|
+
"FunctionInvocation",
|
|
3436
|
+
"FunctionError",
|
|
3437
|
+
"FunctionTimeout",
|
|
3438
|
+
"FunctionThrottle",
|
|
3439
|
+
"FunctionDuration",
|
|
3440
|
+
"FunctionConcurrentExecutions",
|
|
3441
|
+
"DbRead",
|
|
3442
|
+
"DbWrite",
|
|
3443
|
+
"DbSizepkg",
|
|
3444
|
+
"MysqlCpuUsageRate",
|
|
3445
|
+
"MysqlMemoryUse",
|
|
3446
|
+
"MysqlStorageUsage",
|
|
3447
|
+
"MysqlQps",
|
|
3448
|
+
"MysqlSlowQueries",
|
|
3449
|
+
"MysqlDbConnections",
|
|
3450
|
+
"TkeCpuUsedService",
|
|
3451
|
+
"TkeMemUsedService",
|
|
3452
|
+
"TkeQPSService",
|
|
3453
|
+
"TkeHttpErrorService",
|
|
3454
|
+
"TkeInvokeNumService",
|
|
3455
|
+
];
|
|
3456
|
+
exports.ENV_METRIC_PERIOD_VALUES = [300, 3600, 86400];
|
|
3457
|
+
/** Console default ResourceID for environment-level gateway QPS. */
|
|
3458
|
+
exports.GATEWAY_ENV_QPS_DEFAULT_RESOURCE_ID = "all|:|all|:|all|:|all";
|
|
3459
|
+
const ENV_METRIC_NAME_SET = new Set(exports.ENV_METRIC_NAME_VALUES);
|
|
3460
|
+
const ENV_METRIC_PERIOD_SET = new Set(exports.ENV_METRIC_PERIOD_VALUES);
|
|
3461
|
+
const ENV_METRICS_REQUIRING_RESOURCE_ID = new Set([
|
|
3462
|
+
"TkeCpuUsedService",
|
|
3463
|
+
"TkeMemUsedService",
|
|
3464
|
+
"TkeQPSService",
|
|
3465
|
+
"TkeHttpErrorService",
|
|
3466
|
+
"TkeInvokeNumService",
|
|
3467
|
+
]);
|
|
3468
|
+
const ENV_METRIC_TIME_RE = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
|
|
3469
|
+
const ENV_METRIC_MIN_RANGE_MS = 5 * 60 * 1000;
|
|
3470
|
+
function padMetricTimeUnit(value) {
|
|
3471
|
+
return value.toString().padStart(2, "0");
|
|
3472
|
+
}
|
|
3473
|
+
function formatEnvMetricTime(date) {
|
|
3474
|
+
return `${date.getFullYear()}-${padMetricTimeUnit(date.getMonth() + 1)}-${padMetricTimeUnit(date.getDate())} ${padMetricTimeUnit(date.getHours())}:${padMetricTimeUnit(date.getMinutes())}:${padMetricTimeUnit(date.getSeconds())}`;
|
|
3475
|
+
}
|
|
3476
|
+
function resolveEnvMetricName(metricName) {
|
|
3477
|
+
const normalized = typeof metricName === "string" && metricName.trim().length > 0
|
|
3478
|
+
? metricName.trim()
|
|
3479
|
+
: undefined;
|
|
3480
|
+
if (!normalized) {
|
|
3481
|
+
throw new Error(`查询监控指标时 metricName 为必填参数。可选值:${exports.ENV_METRIC_NAME_VALUES.join(", ")}`);
|
|
3482
|
+
}
|
|
3483
|
+
if (!ENV_METRIC_NAME_SET.has(normalized)) {
|
|
3484
|
+
throw new Error(`无效的 metricName: ${normalized}。可选值:${exports.ENV_METRIC_NAME_VALUES.join(", ")}`);
|
|
3485
|
+
}
|
|
3486
|
+
return normalized;
|
|
3487
|
+
}
|
|
3488
|
+
function resolveEnvMetricPeriod(period) {
|
|
3489
|
+
if (period === undefined || period === null || period === "") {
|
|
3490
|
+
return undefined;
|
|
3491
|
+
}
|
|
3492
|
+
const numericPeriod = typeof period === "number"
|
|
3493
|
+
? period
|
|
3494
|
+
: typeof period === "string" && /^\d+$/.test(period.trim())
|
|
3495
|
+
? Number(period.trim())
|
|
3496
|
+
: Number.NaN;
|
|
3497
|
+
if (!ENV_METRIC_PERIOD_SET.has(numericPeriod)) {
|
|
3498
|
+
throw new Error(`period 仅支持 300、3600、86400(秒)。当前值:${String(period)}`);
|
|
3499
|
+
}
|
|
3500
|
+
return numericPeriod;
|
|
3501
|
+
}
|
|
3502
|
+
function resolveEnvMetricTimeRange(options) {
|
|
3503
|
+
const startFromParams = typeof options.startTime === "string" && options.startTime.trim().length > 0
|
|
3504
|
+
? options.startTime.trim()
|
|
3505
|
+
: undefined;
|
|
3506
|
+
const endFromParams = typeof options.endTime === "string" && options.endTime.trim().length > 0
|
|
3507
|
+
? options.endTime.trim()
|
|
3508
|
+
: undefined;
|
|
3509
|
+
if (startFromParams || endFromParams) {
|
|
3510
|
+
if (!startFromParams || !endFromParams) {
|
|
3511
|
+
throw new Error("查询监控指标时 startTime 与 endTime 必须同时提供,格式为 YYYY-MM-DD HH:mm:ss。");
|
|
3512
|
+
}
|
|
3513
|
+
if (!ENV_METRIC_TIME_RE.test(startFromParams) || !ENV_METRIC_TIME_RE.test(endFromParams)) {
|
|
3514
|
+
throw new Error("startTime / endTime 格式必须为 YYYY-MM-DD HH:mm:ss。");
|
|
3515
|
+
}
|
|
3516
|
+
const startMs = Date.parse(startFromParams.replace(" ", "T"));
|
|
3517
|
+
const endMs = Date.parse(endFromParams.replace(" ", "T"));
|
|
3518
|
+
if (Number.isNaN(startMs) || Number.isNaN(endMs)) {
|
|
3519
|
+
throw new Error("startTime / endTime 无法解析为有效时间。");
|
|
3520
|
+
}
|
|
3521
|
+
if (endMs - startMs < ENV_METRIC_MIN_RANGE_MS) {
|
|
3522
|
+
throw new Error("结束时间需要晚于开始时间至少五分钟(监控最小粒度为 5 分钟)。");
|
|
3523
|
+
}
|
|
3524
|
+
return {
|
|
3525
|
+
startTime: startFromParams,
|
|
3526
|
+
endTime: endFromParams,
|
|
3527
|
+
timeSource: "params",
|
|
3528
|
+
};
|
|
3529
|
+
}
|
|
3530
|
+
const now = options.now ?? new Date();
|
|
3531
|
+
const start = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
|
3532
|
+
return {
|
|
3533
|
+
startTime: formatEnvMetricTime(start),
|
|
3534
|
+
endTime: formatEnvMetricTime(now),
|
|
3535
|
+
timeSource: "defaultLast24h",
|
|
3536
|
+
};
|
|
3537
|
+
}
|
|
3538
|
+
function resolveEnvMetricResourceId(metricName, resourceID) {
|
|
3539
|
+
const normalized = typeof resourceID === "string" && resourceID.trim().length > 0
|
|
3540
|
+
? resourceID.trim()
|
|
3541
|
+
: undefined;
|
|
3542
|
+
if (normalized) {
|
|
3543
|
+
return normalized;
|
|
3544
|
+
}
|
|
3545
|
+
if (metricName === "GatewayTraceEnvQPS") {
|
|
3546
|
+
return exports.GATEWAY_ENV_QPS_DEFAULT_RESOURCE_ID;
|
|
3547
|
+
}
|
|
3548
|
+
if (ENV_METRICS_REQUIRING_RESOURCE_ID.has(metricName)) {
|
|
3549
|
+
throw new Error(`查询 ${metricName} 时 resourceID 为必填(云托管服务名)。请先 queryCloudRun(action="list") 获取服务名后再查询。`);
|
|
3550
|
+
}
|
|
3551
|
+
return undefined;
|
|
3552
|
+
}
|
|
3553
|
+
function summarizeEnvMetricCurve(curve) {
|
|
3554
|
+
const newValues = Array.isArray(curve.NewValues) ? curve.NewValues : [];
|
|
3555
|
+
const values = Array.isArray(curve.Values) ? curve.Values : [];
|
|
3556
|
+
const times = Array.isArray(curve.Time) ? curve.Time : [];
|
|
3557
|
+
const series = (newValues.length > 0 ? newValues : values).filter((item) => typeof item === "number" && Number.isFinite(item));
|
|
3558
|
+
if (series.length === 0) {
|
|
3559
|
+
return {
|
|
3560
|
+
sampleCount: 0,
|
|
3561
|
+
max: null,
|
|
3562
|
+
min: null,
|
|
3563
|
+
avg: null,
|
|
3564
|
+
latest: null,
|
|
3565
|
+
peakTimestamp: null,
|
|
3566
|
+
allZero: true,
|
|
3567
|
+
};
|
|
3568
|
+
}
|
|
3569
|
+
let max = series[0];
|
|
3570
|
+
let min = series[0];
|
|
3571
|
+
let sum = 0;
|
|
3572
|
+
let peakIndex = 0;
|
|
3573
|
+
for (let i = 0; i < series.length; i++) {
|
|
3574
|
+
const value = series[i];
|
|
3575
|
+
sum += value;
|
|
3576
|
+
if (value > max) {
|
|
3577
|
+
max = value;
|
|
3578
|
+
peakIndex = i;
|
|
3579
|
+
}
|
|
3580
|
+
if (value < min) {
|
|
3581
|
+
min = value;
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3584
|
+
return {
|
|
3585
|
+
sampleCount: series.length,
|
|
3586
|
+
max,
|
|
3587
|
+
min,
|
|
3588
|
+
avg: sum / series.length,
|
|
3589
|
+
latest: series[series.length - 1],
|
|
3590
|
+
peakTimestamp: typeof times[peakIndex] === "number" ? times[peakIndex] : null,
|
|
3591
|
+
allZero: series.every((value) => value === 0),
|
|
3592
|
+
};
|
|
3593
|
+
}
|
|
3346
3594
|
/**
|
|
3347
3595
|
* Simplify environment list data by keeping only essential fields for AI assistant
|
|
3348
3596
|
* This reduces token consumption when returning environment lists via MCP tools
|
|
@@ -4196,6 +4444,19 @@ function buildEnvQueryErrorMessage(error, action) {
|
|
|
4196
4444
|
if (hasNetworkError) {
|
|
4197
4445
|
suggestions.push("网络错误:请检查网络连接,稍后重试。");
|
|
4198
4446
|
}
|
|
4447
|
+
if (action === "usage" && suggestions.length === 0) {
|
|
4448
|
+
suggestions.push("查询环境资源用量失败,建议:");
|
|
4449
|
+
suggestions.push("1. 先调用 auth(action=\"status\") 确认登录状态;未登录则 auth(action=\"start_auth\")");
|
|
4450
|
+
suggestions.push("2. 使用 queryEnv(action=\"list\") 确认 envId 正确且可访问");
|
|
4451
|
+
suggestions.push(`3. 再调用 queryEnv(action=\"usage\", envId=\"<EnvId>\");可用 type 过滤模块(${exports.ENV_USAGE_MODULE_VALUES.join(", ")})`);
|
|
4452
|
+
}
|
|
4453
|
+
if (action === "metrics" && suggestions.length === 0) {
|
|
4454
|
+
suggestions.push("查询环境监控指标失败,建议:");
|
|
4455
|
+
suggestions.push("1. 先调用 auth(action=\"status\") 确认登录状态;未登录则 auth(action=\"start_auth\")");
|
|
4456
|
+
suggestions.push("2. 使用 queryEnv(action=\"list\") 确认 envId 正确且可访问");
|
|
4457
|
+
suggestions.push(`3. 再调用 queryEnv(action=\"metrics\", envId=\"<EnvId>\", metricName=\"GatewayTraceEnvQPS\");metricName 可选值:${exports.ENV_METRIC_NAME_VALUES.join(", ")}`);
|
|
4458
|
+
suggestions.push("4. startTime/endTime 格式为 YYYY-MM-DD HH:mm:ss,须成对传入;period 仅 300/3600/86400");
|
|
4459
|
+
}
|
|
4199
4460
|
// If no specific pattern matched, provide general guidance
|
|
4200
4461
|
if (suggestions.length === 0) {
|
|
4201
4462
|
suggestions.push("查询环境信息时出错,建议:");
|
|
@@ -5106,8 +5367,8 @@ function registerEnvTools(server) {
|
|
|
5106
5367
|
}
|
|
5107
5368
|
});
|
|
5108
5369
|
} // end: wxide guard for auth tool
|
|
5109
|
-
// queryEnv - 环境查询(合并 listEnvs + getEnvInfo + getEnvAuthDomains)
|
|
5110
|
-
const queryEnvHandler = async ({ action, alias, aliasExact, envId, limit, offset, fields, }) => {
|
|
5370
|
+
// queryEnv - 环境查询(合并 listEnvs + getEnvInfo + getEnvAuthDomains + usage + metrics)
|
|
5371
|
+
const queryEnvHandler = async ({ action, alias, aliasExact, envId, limit, offset, fields, type, startDate, endDate, needUsageDetails, metricName, startTime, endTime, period, resourceID, subresourceID, }) => {
|
|
5111
5372
|
try {
|
|
5112
5373
|
let result;
|
|
5113
5374
|
switch (action) {
|
|
@@ -5269,6 +5530,94 @@ function registerEnvTools(server) {
|
|
|
5269
5530
|
};
|
|
5270
5531
|
}
|
|
5271
5532
|
break;
|
|
5533
|
+
case "usage": {
|
|
5534
|
+
// Explicit EnvId is required (DescribeEnvPostpayPackage / credits APIs reject missing EnvId).
|
|
5535
|
+
const usageEnvId = normalizeOptionalToolString(envId);
|
|
5536
|
+
if (!usageEnvId) {
|
|
5537
|
+
throw new Error('查询资源用量时 envId 为必填参数。请先调用 queryEnv(action="list") 获取 EnvId,再调用 queryEnv(action="usage", envId="<EnvId>")。');
|
|
5538
|
+
}
|
|
5539
|
+
const modules = resolveEnvUsageModules(type);
|
|
5540
|
+
const includeDetails = typeof needUsageDetails === "boolean" ? needUsageDetails : true;
|
|
5541
|
+
const cloudbaseUsage = await getManagerForEnvQuery(usageEnvId);
|
|
5542
|
+
const accountCircle = await cloudbaseUsage.env.describeEnvAccountCircle({
|
|
5543
|
+
EnvId: usageEnvId,
|
|
5544
|
+
});
|
|
5545
|
+
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, accountCircle);
|
|
5546
|
+
const dateRange = resolveEnvUsageDateRange({
|
|
5547
|
+
startDate,
|
|
5548
|
+
endDate,
|
|
5549
|
+
accountCircle,
|
|
5550
|
+
});
|
|
5551
|
+
const usageDetail = await cloudbaseUsage.env.describeCreditsUsageDetail({
|
|
5552
|
+
EnvId: usageEnvId,
|
|
5553
|
+
Modules: modules,
|
|
5554
|
+
StartDate: dateRange.startDate,
|
|
5555
|
+
EndDate: dateRange.endDate,
|
|
5556
|
+
NeedUsageDetails: includeDetails,
|
|
5557
|
+
});
|
|
5558
|
+
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, usageDetail);
|
|
5559
|
+
result = {
|
|
5560
|
+
EnvId: usageEnvId,
|
|
5561
|
+
Modules: modules,
|
|
5562
|
+
StartDate: dateRange.startDate,
|
|
5563
|
+
EndDate: dateRange.endDate,
|
|
5564
|
+
DateSource: dateRange.dateSource,
|
|
5565
|
+
NeedUsageDetails: includeDetails,
|
|
5566
|
+
AccountCircle: {
|
|
5567
|
+
StartTime: accountCircle?.StartTime,
|
|
5568
|
+
EndTime: accountCircle?.EndTime,
|
|
5569
|
+
HistoryTime: accountCircle?.HistoryTime ?? [],
|
|
5570
|
+
RequestId: accountCircle?.RequestId,
|
|
5571
|
+
},
|
|
5572
|
+
Usages: usageDetail?.Usages ?? [],
|
|
5573
|
+
RequestId: usageDetail?.RequestId,
|
|
5574
|
+
};
|
|
5575
|
+
break;
|
|
5576
|
+
}
|
|
5577
|
+
case "metrics": {
|
|
5578
|
+
const metricsEnvId = normalizeOptionalToolString(envId);
|
|
5579
|
+
if (!metricsEnvId) {
|
|
5580
|
+
throw new Error('查询监控指标时 envId 为必填参数。请先调用 queryEnv(action="list") 获取 EnvId,再调用 queryEnv(action="metrics", envId="<EnvId>", metricName="GatewayTraceEnvQPS")。');
|
|
5581
|
+
}
|
|
5582
|
+
const resolvedMetricName = resolveEnvMetricName(metricName);
|
|
5583
|
+
const timeRange = resolveEnvMetricTimeRange({ startTime, endTime });
|
|
5584
|
+
const resolvedPeriod = resolveEnvMetricPeriod(period);
|
|
5585
|
+
const resolvedResourceId = resolveEnvMetricResourceId(resolvedMetricName, resourceID);
|
|
5586
|
+
const resolvedSubresourceId = normalizeOptionalToolString(subresourceID);
|
|
5587
|
+
const cloudbaseMetrics = await getManagerForEnvQuery(metricsEnvId);
|
|
5588
|
+
if (typeof cloudbaseMetrics?.monitor?.describeCurveData !== "function") {
|
|
5589
|
+
throw new Error("当前 CloudBase Manager 不支持 monitor.describeCurveData。请升级 @cloudbase/manager-node。");
|
|
5590
|
+
}
|
|
5591
|
+
const curveParams = {
|
|
5592
|
+
MetricName: resolvedMetricName,
|
|
5593
|
+
StartTime: timeRange.startTime,
|
|
5594
|
+
EndTime: timeRange.endTime,
|
|
5595
|
+
};
|
|
5596
|
+
if (resolvedPeriod !== undefined) {
|
|
5597
|
+
curveParams.Period = resolvedPeriod;
|
|
5598
|
+
}
|
|
5599
|
+
if (resolvedResourceId) {
|
|
5600
|
+
curveParams.ResourceID = resolvedResourceId;
|
|
5601
|
+
}
|
|
5602
|
+
if (resolvedSubresourceId) {
|
|
5603
|
+
curveParams.SubresourceID = resolvedSubresourceId;
|
|
5604
|
+
}
|
|
5605
|
+
const curve = await cloudbaseMetrics.monitor.describeCurveData(curveParams);
|
|
5606
|
+
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, curve);
|
|
5607
|
+
result = {
|
|
5608
|
+
EnvId: metricsEnvId,
|
|
5609
|
+
MetricName: resolvedMetricName,
|
|
5610
|
+
StartTime: timeRange.startTime,
|
|
5611
|
+
EndTime: timeRange.endTime,
|
|
5612
|
+
TimeSource: timeRange.timeSource,
|
|
5613
|
+
Period: resolvedPeriod ?? curve?.Period,
|
|
5614
|
+
ResourceID: resolvedResourceId,
|
|
5615
|
+
SubresourceID: resolvedSubresourceId,
|
|
5616
|
+
Curve: curve,
|
|
5617
|
+
Summary: summarizeEnvMetricCurve(curve ?? {}),
|
|
5618
|
+
};
|
|
5619
|
+
break;
|
|
5620
|
+
}
|
|
5272
5621
|
default:
|
|
5273
5622
|
throw new Error(`不支持的查询类型: ${action}`);
|
|
5274
5623
|
}
|
|
@@ -5301,20 +5650,63 @@ function registerEnvTools(server) {
|
|
|
5301
5650
|
// Register primary tool name (queryEnv)
|
|
5302
5651
|
const queryEnvToolSchema = {
|
|
5303
5652
|
title: "CloudBase 环境查询",
|
|
5304
|
-
description: "查询 CloudBase
|
|
5653
|
+
description: "查询 CloudBase 环境相关信息,支持查询环境列表、指定环境详情、安全域名、资源用量与监控指标。(曾用名:envQuery、listEnvs、getEnvInfo、getEnvAuthDomains)当 action=list 时,会按 DescribeEnvs 语义做列表/筛选,标准返回字段为 EnvId、Alias、Status、EnvType、Region、PackageId、PackageName、IsDefault,并支持通过 fields 白名单裁剪这些字段;aliasExact=true 时会按别名精确筛选,避免把前缀相近的环境误当作候选;即使传入 envId,action=list 也只返回摘要,不会返回完整资源明细或 expiry。如需查询某个已知 EnvId 对应环境的详细信息(包括资源字段和计费信息),必须使用 action=info 并传入目标环境的 envId 参数。action=info 会在可用时补充 BillingInfo(如 ExpireTime、PayMode、IsAutoRenew 等计费字段)。\n\n📊 action=usage 对齐 tcb env usage/info:透传 Manager SDK describeEnvAccountCircle + describeCreditsUsageDetail,返回计费周期与各模块资源点用量(FLEXDB/SCF/COS 等)。envId 必填;type 可选过滤模块;未传 startDate/endDate 时自动使用当前计费周期。\n\n📈 action=metrics 对齐 TCB DescribeCurveData(manager.monitor.describeCurveData,不是云监控 GetMonitorData):查询环境/网关 QPS、云函数调用与错误、数据库 CPU/内存/磁盘、云托管 CPU/QPS 等时序。envId 与 metricName 必填;startTime/endTime 格式 YYYY-MM-DD HH:mm:ss,须成对传入,不传则默认最近 24 小时;period 仅 300/3600/86400。GatewayTraceEnvQPS 未传 resourceID 时自动填环境级 all|:|all|:|all|:|all;云托管 Tke* 指标必须传服务名 resourceID。禁止用 callCloudApi 猜测监控 Action。\n\n🔍 action=info 还会派生三个用于后端选型的字段:\n- `EnvInfo.RuntimeMode`:'postgresql' 或 'nosql',表示新业务建议默认使用的后端(PG 已开通时为 postgresql,否则为 nosql)。\n- `EnvInfo.RuntimeBackends`:`{postgresql, nosql, mysql}` 三个布尔值,描述当前环境实际并存的后端。\n- `EnvInfo.RuntimeModeHints`:每个后端对应的 API/工具/skill 提示。\n\n🌐 action=info 还会在不改写 `StaticStorages[].StaticDomain`(云 API 名义域名)的前提下,投影网关路由 Enable 状态:`StaticStorages[].staticDomainRouteEnabled` 与 `EnvInfo.staticDomainRouteEnabled`(与 queryHosting websiteConfig 同源)。`false` 表示默认静态域名根路由已禁用(访问会返回 GATEWAY_ROUTE_DISABLED),勿把名义域名当成可达 URL。\n\nAI 在写业务/权限/存储代码前必须先看这三项:PG 模式下新业务推荐 `app.rdb()` + RLS(`managePgDatabase action=execute` 跑 `CREATE POLICY`)+ pgstore;已存在的 NoSQL 集合 / 旧 storage / `managePermissions(resourceType=\"noSqlDatabase\")` 在 PG 环境下仍然有效。真正不适用的是 MySQL:当 `RuntimeBackends.mysql === false` 时,`manageMysqlDatabase` / `queryMysqlDatabase` / `relational-database-mcp-cloudbase` skill 都不该使用。",
|
|
5305
5654
|
inputSchema: {
|
|
5306
5655
|
action: zod_1.z
|
|
5307
|
-
.enum(["list", "info", "domains"])
|
|
5308
|
-
.describe("查询类型:list=环境列表/摘要筛选(按 DescribeEnvs 语义筛选,支持通过 envId 筛选,返回 EnvId、Alias、Status、EnvType、Region、PackageId、PackageName、IsDefault,不支持 expiry),info=指定环境的详细信息(必须传入 envId,返回资源字段和计费信息),domains
|
|
5656
|
+
.enum(["list", "info", "domains", "usage", "metrics"])
|
|
5657
|
+
.describe("查询类型:list=环境列表/摘要筛选(按 DescribeEnvs 语义筛选,支持通过 envId 筛选,返回 EnvId、Alias、Status、EnvType、Region、PackageId、PackageName、IsDefault,不支持 expiry),info=指定环境的详细信息(必须传入 envId,返回资源字段和计费信息),domains=安全域名列表,usage=环境资源用量(必须传入 envId,对齐 tcb env usage/info),metrics=环境监控时序(必须传入 envId 与 metricName,对齐 TCB DescribeCurveData)"),
|
|
5309
5658
|
alias: zod_1.z.string().optional().describe("按环境别名筛选。action=list 时可选"),
|
|
5310
5659
|
aliasExact: zod_1.z.boolean().optional().describe("按环境别名精确筛选。action=list 时可选;与 alias 配合使用"),
|
|
5311
|
-
envId: zod_1.z
|
|
5660
|
+
envId: zod_1.z
|
|
5661
|
+
.string()
|
|
5662
|
+
.optional()
|
|
5663
|
+
.describe("环境 ID。action=list 时可选(仅按 DescribeEnvs 语义做筛选,仍返回摘要);action=info / action=usage / action=metrics 时必填。"),
|
|
5312
5664
|
limit: zod_1.z.number().int().positive().optional().describe("返回数量上限。action=list 时可选"),
|
|
5313
5665
|
offset: zod_1.z.number().int().min(0).optional().describe("分页偏移。action=list 时可选"),
|
|
5314
5666
|
fields: zod_1.z
|
|
5315
5667
|
.array(zod_1.z.enum(DEFAULT_ENV_FIELDS))
|
|
5316
5668
|
.optional()
|
|
5317
5669
|
.describe("返回字段白名单。仅支持 EnvId、Alias、Status、EnvType、Region、PackageId、PackageName、IsDefault。action=list 时可选"),
|
|
5670
|
+
type: zod_1.z
|
|
5671
|
+
.array(zod_1.z.enum(exports.ENV_USAGE_MODULE_VALUES))
|
|
5672
|
+
.optional()
|
|
5673
|
+
.describe("用量模块过滤。仅 action=usage 时有效;不传则查询全部模块。可选值对齐 tcb CLI:FLEXDB、TDSQL、SCF、EKS、COS、AI、HOSTING、Auth、APIInvocation、HTTPInvocation、VM、Workflow、Other。"),
|
|
5674
|
+
startDate: zod_1.z
|
|
5675
|
+
.string()
|
|
5676
|
+
.optional()
|
|
5677
|
+
.describe("用量开始日期(YYYY-MM-DD)。仅 action=usage 时有效;与 endDate 成对传入。不传则使用当前计费周期。"),
|
|
5678
|
+
endDate: zod_1.z
|
|
5679
|
+
.string()
|
|
5680
|
+
.optional()
|
|
5681
|
+
.describe("用量结束日期(YYYY-MM-DD)。仅 action=usage 时有效;与 startDate 成对传入。不传则使用当前计费周期。"),
|
|
5682
|
+
needUsageDetails: zod_1.z
|
|
5683
|
+
.boolean()
|
|
5684
|
+
.optional()
|
|
5685
|
+
.describe("是否返回每日用量明细。仅 action=usage 时有效;默认 true。"),
|
|
5686
|
+
metricName: zod_1.z
|
|
5687
|
+
.enum(exports.ENV_METRIC_NAME_VALUES)
|
|
5688
|
+
.optional()
|
|
5689
|
+
.describe("监控指标名。仅 action=metrics 时有效且必填。GatewayTraceEnvQPS/EnvQPSAll=环境与网关 QPS;FunctionInvocation/FunctionError/FunctionTimeout/FunctionThrottle=云函数调用、错误、超时、限流;DbRead/DbWrite/DbSizepkg=文档库读写与容量;MysqlCpuUsageRate/MysqlMemoryUse/MysqlStorageUsage=SQL 库 CPU/内存/磁盘;TkeCpuUsedService/TkeQPSService/TkeHttpErrorService=云托管 CPU/QPS/错误。"),
|
|
5690
|
+
startTime: zod_1.z
|
|
5691
|
+
.string()
|
|
5692
|
+
.optional()
|
|
5693
|
+
.describe("监控开始时间(YYYY-MM-DD HH:mm:ss)。仅 action=metrics 时有效;与 endTime 成对传入。不传则默认最近 24 小时。结束时间须晚于开始时间至少五分钟。"),
|
|
5694
|
+
endTime: zod_1.z
|
|
5695
|
+
.string()
|
|
5696
|
+
.optional()
|
|
5697
|
+
.describe("监控结束时间(YYYY-MM-DD HH:mm:ss)。仅 action=metrics 时有效;与 startTime 成对传入。不传则默认最近 24 小时。"),
|
|
5698
|
+
period: zod_1.z
|
|
5699
|
+
.union([zod_1.z.literal(300), zod_1.z.literal(3600), zod_1.z.literal(86400)])
|
|
5700
|
+
.optional()
|
|
5701
|
+
.describe("统计周期(秒)。仅 action=metrics 时有效;仅支持 300、3600、86400。不传则由后端按时间范围自动选择。时间范围 ≤1 天不可用 86400;>3 天不可用 300。"),
|
|
5702
|
+
resourceID: zod_1.z
|
|
5703
|
+
.string()
|
|
5704
|
+
.optional()
|
|
5705
|
+
.describe("资源 ID。仅 action=metrics 时有效。云函数传函数名,文档库传集合名,云托管必须传服务名;GatewayTraceEnvQPS 不传则使用环境级 all|:|all|:|all|:|all。"),
|
|
5706
|
+
subresourceID: zod_1.z
|
|
5707
|
+
.string()
|
|
5708
|
+
.optional()
|
|
5709
|
+
.describe("子资源 ID。仅 action=metrics 时有效;查询云托管某版本监控时传入版本名。"),
|
|
5318
5710
|
},
|
|
5319
5711
|
annotations: {
|
|
5320
5712
|
readOnlyHint: true,
|
|
@@ -86234,6 +86626,182 @@ class Profiler {
|
|
|
86234
86626
|
module.exports = Profiler;
|
|
86235
86627
|
|
|
86236
86628
|
|
|
86629
|
+
/***/ }),
|
|
86630
|
+
|
|
86631
|
+
/***/ 23656:
|
|
86632
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
86633
|
+
|
|
86634
|
+
"use strict";
|
|
86635
|
+
|
|
86636
|
+
/**
|
|
86637
|
+
* Guidance + bounded wait when a cloud function is not Active.
|
|
86638
|
+
* SCF rejects UpdateFunctionConfiguration / UpdateFunctionCode while Status=Updating.
|
|
86639
|
+
*/
|
|
86640
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
86641
|
+
exports.functionUpdatingRuntime = exports.FUNCTION_UPDATING_WAIT = exports.FUNCTION_BUSY_STATUSES = exports.FUNCTION_UPDATING_RETRY_AFTER_SECONDS = exports.FUNCTION_UPDATING_ERROR_CODE = void 0;
|
|
86642
|
+
exports.getErrorMessage = getErrorMessage;
|
|
86643
|
+
exports.isFunctionBusyStatus = isFunctionBusyStatus;
|
|
86644
|
+
exports.isFunctionUpdatingError = isFunctionUpdatingError;
|
|
86645
|
+
exports.buildFunctionUpdatingMessage = buildFunctionUpdatingMessage;
|
|
86646
|
+
exports.buildFunctionUpdatingNextActions = buildFunctionUpdatingNextActions;
|
|
86647
|
+
exports.buildFunctionUpdatingPayload = buildFunctionUpdatingPayload;
|
|
86648
|
+
exports.waitUntilFunctionActive = waitUntilFunctionActive;
|
|
86649
|
+
exports.FUNCTION_UPDATING_ERROR_CODE = "FUNCTION_UPDATING";
|
|
86650
|
+
/** Agents must wait at least this long before retrying the same write. */
|
|
86651
|
+
exports.FUNCTION_UPDATING_RETRY_AFTER_SECONDS = 10;
|
|
86652
|
+
exports.FUNCTION_BUSY_STATUSES = [
|
|
86653
|
+
"Updating",
|
|
86654
|
+
"Creating",
|
|
86655
|
+
"Publishing",
|
|
86656
|
+
"Deleting",
|
|
86657
|
+
];
|
|
86658
|
+
exports.FUNCTION_UPDATING_WAIT = {
|
|
86659
|
+
intervalMs: 2000,
|
|
86660
|
+
maxAttempts: 5,
|
|
86661
|
+
timeoutMs: 12_000,
|
|
86662
|
+
};
|
|
86663
|
+
/**
|
|
86664
|
+
* Injectable sleep for unit tests. Production uses setTimeout.
|
|
86665
|
+
* Tests must restore the original function in afterEach.
|
|
86666
|
+
*/
|
|
86667
|
+
exports.functionUpdatingRuntime = {
|
|
86668
|
+
sleep: (ms) => new Promise((resolve) => {
|
|
86669
|
+
setTimeout(resolve, ms);
|
|
86670
|
+
}),
|
|
86671
|
+
};
|
|
86672
|
+
function getErrorMessage(error) {
|
|
86673
|
+
if (error instanceof Error) {
|
|
86674
|
+
return error.message;
|
|
86675
|
+
}
|
|
86676
|
+
return String(error);
|
|
86677
|
+
}
|
|
86678
|
+
function isFunctionBusyStatus(status) {
|
|
86679
|
+
if (typeof status !== "string") {
|
|
86680
|
+
return false;
|
|
86681
|
+
}
|
|
86682
|
+
return exports.FUNCTION_BUSY_STATUSES.includes(status);
|
|
86683
|
+
}
|
|
86684
|
+
const UPDATING_ERROR_PATTERNS = [
|
|
86685
|
+
/当前函数处于\s*Updating/i,
|
|
86686
|
+
/函数处于\s*Updating/i,
|
|
86687
|
+
/currently\s+(?:in\s+)?updating/i,
|
|
86688
|
+
/function\s+is\s+(?:currently\s+)?updating/i,
|
|
86689
|
+
/Status\s*(?:is|=|:)?\s*Updating/i,
|
|
86690
|
+
/scf\/UpdateFunction(?:Configuration|Code).{0,80}Updating/i,
|
|
86691
|
+
/函数状态异常,检查超时/,
|
|
86692
|
+
];
|
|
86693
|
+
function isFunctionUpdatingError(error) {
|
|
86694
|
+
const message = getErrorMessage(error);
|
|
86695
|
+
return UPDATING_ERROR_PATTERNS.some((pattern) => pattern.test(message));
|
|
86696
|
+
}
|
|
86697
|
+
function buildFunctionUpdatingMessage(params) {
|
|
86698
|
+
const namePart = params.functionName
|
|
86699
|
+
? `函数 \`${params.functionName}\``
|
|
86700
|
+
: "目标函数";
|
|
86701
|
+
const statusPart = params.status ? `(当前 Status=${params.status})` : "";
|
|
86702
|
+
const lines = [
|
|
86703
|
+
`${namePart} 尚未就绪${statusPart},无法执行 manageFunctions(action="${params.action}")。`,
|
|
86704
|
+
`不要立即重试同一写操作(SCF 会在 Updating 期间连续拒绝 UpdateFunctionConfiguration / UpdateFunctionCode)。`,
|
|
86705
|
+
`请等待 ${exports.FUNCTION_UPDATING_RETRY_AFTER_SECONDS} 秒后重试,或先调用 queryFunctions(action="getFunctionDetail") 确认 Status 为 Active。`,
|
|
86706
|
+
];
|
|
86707
|
+
if (params.rawMessage) {
|
|
86708
|
+
lines.push(`原始错误: ${params.rawMessage}`);
|
|
86709
|
+
}
|
|
86710
|
+
return lines.join("\n");
|
|
86711
|
+
}
|
|
86712
|
+
function buildFunctionUpdatingNextActions(params) {
|
|
86713
|
+
const functionName = params.functionName;
|
|
86714
|
+
return [
|
|
86715
|
+
{
|
|
86716
|
+
tool: "queryFunctions",
|
|
86717
|
+
action: "getFunctionDetail",
|
|
86718
|
+
reason: "查看函数 Status,等到 Active 后再写配置/代码",
|
|
86719
|
+
suggested_args: {
|
|
86720
|
+
action: "getFunctionDetail",
|
|
86721
|
+
...(functionName ? { functionName } : {}),
|
|
86722
|
+
},
|
|
86723
|
+
},
|
|
86724
|
+
{
|
|
86725
|
+
tool: "manageFunctions",
|
|
86726
|
+
action: params.action,
|
|
86727
|
+
reason: `等待 ${exports.FUNCTION_UPDATING_RETRY_AFTER_SECONDS} 秒且 Status=Active 后,用相同参数重试;禁止立刻连打`,
|
|
86728
|
+
suggested_args: {
|
|
86729
|
+
action: params.action,
|
|
86730
|
+
...(functionName ? { functionName } : {}),
|
|
86731
|
+
},
|
|
86732
|
+
},
|
|
86733
|
+
];
|
|
86734
|
+
}
|
|
86735
|
+
function buildFunctionUpdatingPayload(params) {
|
|
86736
|
+
return {
|
|
86737
|
+
success: false,
|
|
86738
|
+
errorCode: exports.FUNCTION_UPDATING_ERROR_CODE,
|
|
86739
|
+
retryAfterSeconds: exports.FUNCTION_UPDATING_RETRY_AFTER_SECONDS,
|
|
86740
|
+
message: buildFunctionUpdatingMessage(params),
|
|
86741
|
+
data: {
|
|
86742
|
+
action: params.action,
|
|
86743
|
+
...(params.functionName ? { functionName: params.functionName } : {}),
|
|
86744
|
+
...(params.status ? { status: params.status } : {}),
|
|
86745
|
+
retryAfterSeconds: exports.FUNCTION_UPDATING_RETRY_AFTER_SECONDS,
|
|
86746
|
+
doNotRetryImmediately: true,
|
|
86747
|
+
...(typeof params.waitedAttempts === "number"
|
|
86748
|
+
? { waitedAttempts: params.waitedAttempts }
|
|
86749
|
+
: {}),
|
|
86750
|
+
...(params.timedOut ? { waitTimedOut: true } : {}),
|
|
86751
|
+
...(params.rawMessage ? { rawError: params.rawMessage.slice(0, 500) } : {}),
|
|
86752
|
+
},
|
|
86753
|
+
nextActions: buildFunctionUpdatingNextActions(params),
|
|
86754
|
+
};
|
|
86755
|
+
}
|
|
86756
|
+
/**
|
|
86757
|
+
* Poll function Status until it leaves a busy state.
|
|
86758
|
+
* Always bounded: maxAttempts AND timeoutMs; never spins without a sleep between polls.
|
|
86759
|
+
*/
|
|
86760
|
+
async function waitUntilFunctionActive(getStatus, options = {}) {
|
|
86761
|
+
const intervalMs = options.intervalMs ?? exports.FUNCTION_UPDATING_WAIT.intervalMs;
|
|
86762
|
+
const maxAttempts = options.maxAttempts ?? exports.FUNCTION_UPDATING_WAIT.maxAttempts;
|
|
86763
|
+
const timeoutMs = options.timeoutMs ?? exports.FUNCTION_UPDATING_WAIT.timeoutMs;
|
|
86764
|
+
const sleep = options.sleep ?? ((ms) => exports.functionUpdatingRuntime.sleep(ms));
|
|
86765
|
+
const now = options.now ?? Date.now;
|
|
86766
|
+
if (maxAttempts < 1) {
|
|
86767
|
+
throw new Error("waitUntilFunctionActive maxAttempts must be >= 1");
|
|
86768
|
+
}
|
|
86769
|
+
if (intervalMs < 0 || timeoutMs < 0) {
|
|
86770
|
+
throw new Error("waitUntilFunctionActive intervalMs/timeoutMs must be >= 0");
|
|
86771
|
+
}
|
|
86772
|
+
if (options.initialStatus !== undefined &&
|
|
86773
|
+
!isFunctionBusyStatus(options.initialStatus)) {
|
|
86774
|
+
return {
|
|
86775
|
+
ready: true,
|
|
86776
|
+
status: options.initialStatus,
|
|
86777
|
+
attempts: 0,
|
|
86778
|
+
timedOut: false,
|
|
86779
|
+
};
|
|
86780
|
+
}
|
|
86781
|
+
const startedAt = now();
|
|
86782
|
+
let attempts = 0;
|
|
86783
|
+
let status = options.initialStatus;
|
|
86784
|
+
while (attempts < maxAttempts) {
|
|
86785
|
+
if (now() - startedAt >= timeoutMs) {
|
|
86786
|
+
return { ready: false, status, attempts, timedOut: true };
|
|
86787
|
+
}
|
|
86788
|
+
attempts += 1;
|
|
86789
|
+
status = await getStatus();
|
|
86790
|
+
if (!isFunctionBusyStatus(status)) {
|
|
86791
|
+
return { ready: true, status, attempts, timedOut: false };
|
|
86792
|
+
}
|
|
86793
|
+
if (attempts >= maxAttempts) {
|
|
86794
|
+
break;
|
|
86795
|
+
}
|
|
86796
|
+
if (now() - startedAt + intervalMs >= timeoutMs) {
|
|
86797
|
+
return { ready: false, status, attempts, timedOut: true };
|
|
86798
|
+
}
|
|
86799
|
+
await sleep(intervalMs);
|
|
86800
|
+
}
|
|
86801
|
+
return { ready: false, status, attempts, timedOut: false };
|
|
86802
|
+
}
|
|
86803
|
+
|
|
86804
|
+
|
|
86237
86805
|
/***/ }),
|
|
86238
86806
|
|
|
86239
86807
|
/***/ 23670:
|
|
@@ -91041,6 +91609,8 @@ const cloud_mode_js_1 = __webpack_require__(89684);
|
|
|
91041
91609
|
const gateway_access_urls_js_1 = __webpack_require__(22435);
|
|
91042
91610
|
const json_content_js_1 = __webpack_require__(4365);
|
|
91043
91611
|
const logger_js_1 = __webpack_require__(13039);
|
|
91612
|
+
const tool_result_js_1 = __webpack_require__(9835);
|
|
91613
|
+
const function_updating_js_1 = __webpack_require__(23656);
|
|
91044
91614
|
const fs_1 = __webpack_require__(29021);
|
|
91045
91615
|
const path_1 = __importDefault(__webpack_require__(33873));
|
|
91046
91616
|
exports.SUPPORTED_RUNTIMES = {
|
|
@@ -91361,6 +91931,10 @@ function buildFunctionOperationErrorMessage(operation, functionName, functionRoo
|
|
|
91361
91931
|
suggestions.push("如果 HTTP 函数只使用原生 Node.js API 且没有第三方依赖,可以保留函数目录中的 index.js 和 scf_bootstrap,工具会跳过依赖安装。");
|
|
91362
91932
|
suggestions.push("如果你确实依赖 npm 包,请在函数目录下补充 package.json 后重试。");
|
|
91363
91933
|
}
|
|
91934
|
+
if ((0, function_updating_js_1.isFunctionUpdatingError)(error)) {
|
|
91935
|
+
suggestions.push(`函数当前处于 Updating/非 Active,不要立即重试 ${operation}。` +
|
|
91936
|
+
`请等待约 10 秒,或先 queryFunctions(action="getFunctionDetail") 确认 Status 为 Active 后再重试。`);
|
|
91937
|
+
}
|
|
91364
91938
|
// Handle invalid parameter value errors from CloudBase API
|
|
91365
91939
|
if (/invalid parameter value/i.test(baseMessage)) {
|
|
91366
91940
|
suggestions.push("检测到参数值格式错误。请重点检查以下配置项:");
|
|
@@ -91388,6 +91962,24 @@ function wrapFunctionOperationError(operation, functionName, functionRootPath, e
|
|
|
91388
91962
|
}
|
|
91389
91963
|
return wrappedError;
|
|
91390
91964
|
}
|
|
91965
|
+
async function waitForManageWriteOrGuide(action, functionName, getFunctionDetail, initialDetail) {
|
|
91966
|
+
const waitResult = await (0, function_updating_js_1.waitUntilFunctionActive)(async () => {
|
|
91967
|
+
const latest = await getFunctionDetail(functionName);
|
|
91968
|
+
return typeof latest?.Status === "string" ? latest.Status : undefined;
|
|
91969
|
+
}, {
|
|
91970
|
+
initialStatus: typeof initialDetail?.Status === "string" ? initialDetail.Status : undefined,
|
|
91971
|
+
});
|
|
91972
|
+
if (waitResult.ready) {
|
|
91973
|
+
return undefined;
|
|
91974
|
+
}
|
|
91975
|
+
return (0, function_updating_js_1.buildFunctionUpdatingPayload)({
|
|
91976
|
+
action,
|
|
91977
|
+
functionName,
|
|
91978
|
+
status: waitResult.status,
|
|
91979
|
+
waitedAttempts: waitResult.attempts,
|
|
91980
|
+
timedOut: waitResult.timedOut,
|
|
91981
|
+
});
|
|
91982
|
+
}
|
|
91391
91983
|
function registerFunctionTools(server) {
|
|
91392
91984
|
const cloudBaseOptions = server.cloudBaseOptions;
|
|
91393
91985
|
const deployOverrides = server.pluginOptions?.functions;
|
|
@@ -91410,6 +92002,15 @@ function registerFunctionTools(server) {
|
|
|
91410
92002
|
return (0, json_content_js_1.jsonContent)(await handler());
|
|
91411
92003
|
}
|
|
91412
92004
|
catch (error) {
|
|
92005
|
+
if ((0, tool_result_js_1.isToolPayloadError)(error)) {
|
|
92006
|
+
return (0, json_content_js_1.jsonContent)(error.payload);
|
|
92007
|
+
}
|
|
92008
|
+
if ((0, function_updating_js_1.isFunctionUpdatingError)(error)) {
|
|
92009
|
+
return (0, json_content_js_1.jsonContent)((0, function_updating_js_1.buildFunctionUpdatingPayload)({
|
|
92010
|
+
action: "manageFunctions",
|
|
92011
|
+
rawMessage: (0, function_updating_js_1.getErrorMessage)(error),
|
|
92012
|
+
}));
|
|
92013
|
+
}
|
|
91413
92014
|
return (0, json_content_js_1.jsonContent)(buildErrorEnvelope(error));
|
|
91414
92015
|
}
|
|
91415
92016
|
};
|
|
@@ -91964,6 +92565,24 @@ function registerFunctionTools(server) {
|
|
|
91964
92565
|
throw new Error("updateFunctionCode 操作时,functionName 参数是必需的");
|
|
91965
92566
|
}
|
|
91966
92567
|
const cloudbase = await getManager();
|
|
92568
|
+
if (typeof cloudbase.functions.getFunctionDetail === "function") {
|
|
92569
|
+
try {
|
|
92570
|
+
const currentDetail = await cloudbase.functions.getFunctionDetail(input.functionName);
|
|
92571
|
+
const waited = await waitForManageWriteOrGuide("updateFunctionCode", input.functionName, (name) => cloudbase.functions.getFunctionDetail(name), currentDetail);
|
|
92572
|
+
if (waited) {
|
|
92573
|
+
return waited;
|
|
92574
|
+
}
|
|
92575
|
+
}
|
|
92576
|
+
catch (error) {
|
|
92577
|
+
if ((0, function_updating_js_1.isFunctionUpdatingError)(error)) {
|
|
92578
|
+
return (0, function_updating_js_1.buildFunctionUpdatingPayload)({
|
|
92579
|
+
action: "updateFunctionCode",
|
|
92580
|
+
functionName: input.functionName,
|
|
92581
|
+
rawMessage: (0, function_updating_js_1.getErrorMessage)(error),
|
|
92582
|
+
});
|
|
92583
|
+
}
|
|
92584
|
+
}
|
|
92585
|
+
}
|
|
91967
92586
|
// 镜像更新分支:后续迭代只需用新镜像 tag 更新函数。
|
|
91968
92587
|
const updateImageConfig = input.imageConfig ??
|
|
91969
92588
|
input.func?.imageConfig;
|
|
@@ -91982,6 +92601,13 @@ function registerFunctionTools(server) {
|
|
|
91982
92601
|
});
|
|
91983
92602
|
}
|
|
91984
92603
|
catch (error) {
|
|
92604
|
+
if ((0, function_updating_js_1.isFunctionUpdatingError)(error)) {
|
|
92605
|
+
return (0, function_updating_js_1.buildFunctionUpdatingPayload)({
|
|
92606
|
+
action: "updateFunctionCode",
|
|
92607
|
+
functionName: input.functionName,
|
|
92608
|
+
rawMessage: (0, function_updating_js_1.getErrorMessage)(error),
|
|
92609
|
+
});
|
|
92610
|
+
}
|
|
91985
92611
|
throw wrapFunctionOperationError("updateFunctionCode", input.functionName, undefined, error);
|
|
91986
92612
|
}
|
|
91987
92613
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, imageResult);
|
|
@@ -92039,6 +92665,13 @@ function registerFunctionTools(server) {
|
|
|
92039
92665
|
result = await cloudbase.functions.updateFunctionCode(updateParams);
|
|
92040
92666
|
}
|
|
92041
92667
|
catch (error) {
|
|
92668
|
+
if ((0, function_updating_js_1.isFunctionUpdatingError)(error)) {
|
|
92669
|
+
return (0, function_updating_js_1.buildFunctionUpdatingPayload)({
|
|
92670
|
+
action: "updateFunctionCode",
|
|
92671
|
+
functionName: input.functionName,
|
|
92672
|
+
rawMessage: (0, function_updating_js_1.getErrorMessage)(error),
|
|
92673
|
+
});
|
|
92674
|
+
}
|
|
92042
92675
|
throw wrapFunctionOperationError("updateFunctionCode", input.functionName, processedRootPath, error);
|
|
92043
92676
|
}
|
|
92044
92677
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
@@ -92083,6 +92716,10 @@ function registerFunctionTools(server) {
|
|
|
92083
92716
|
if (!functionDetail) {
|
|
92084
92717
|
throw new Error(`函数 ${input.functionName} 不存在或无法获取详情`);
|
|
92085
92718
|
}
|
|
92719
|
+
const configBusy = await waitForManageWriteOrGuide("updateFunctionConfig", input.functionName, (name) => cloudbase.functions.getFunctionDetail(name), functionDetail);
|
|
92720
|
+
if (configBusy) {
|
|
92721
|
+
return configBusy;
|
|
92722
|
+
}
|
|
92086
92723
|
const currentVpc = typeof functionDetail.VpcConfig === "object" &&
|
|
92087
92724
|
functionDetail.VpcConfig !== null &&
|
|
92088
92725
|
functionDetail.VpcConfig.SubnetId &&
|
|
@@ -92092,27 +92729,39 @@ function registerFunctionTools(server) {
|
|
|
92092
92729
|
vpcId: functionDetail.VpcConfig.VpcId,
|
|
92093
92730
|
}
|
|
92094
92731
|
: undefined;
|
|
92095
|
-
|
|
92096
|
-
|
|
92097
|
-
|
|
92098
|
-
|
|
92099
|
-
|
|
92100
|
-
|
|
92101
|
-
|
|
92102
|
-
|
|
92103
|
-
|
|
92104
|
-
|
|
92105
|
-
|
|
92106
|
-
|
|
92107
|
-
|
|
92108
|
-
|
|
92109
|
-
|
|
92110
|
-
{
|
|
92111
|
-
|
|
92112
|
-
|
|
92113
|
-
|
|
92114
|
-
|
|
92115
|
-
|
|
92732
|
+
try {
|
|
92733
|
+
const result = await cloudbase.functions.updateFunctionConfig({
|
|
92734
|
+
name: input.functionName,
|
|
92735
|
+
envVariables: Object.assign({}, (functionDetail.Environment?.Variables || []).reduce((acc, curr) => {
|
|
92736
|
+
acc[curr.Key] = curr.Value;
|
|
92737
|
+
return acc;
|
|
92738
|
+
}, {}), input.envVariables ?? {}),
|
|
92739
|
+
timeout: input.timeout ?? functionDetail.Timeout,
|
|
92740
|
+
vpc: Object.assign({}, currentVpc, input.vpc ?? {}),
|
|
92741
|
+
});
|
|
92742
|
+
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
92743
|
+
return buildEnvelope({
|
|
92744
|
+
action: input.action,
|
|
92745
|
+
functionName: input.functionName,
|
|
92746
|
+
raw: result,
|
|
92747
|
+
}, `已更新函数 ${input.functionName} 的配置`, [
|
|
92748
|
+
{
|
|
92749
|
+
tool: "queryFunctions",
|
|
92750
|
+
action: "getFunctionDetail",
|
|
92751
|
+
reason: "确认配置变更结果",
|
|
92752
|
+
},
|
|
92753
|
+
]);
|
|
92754
|
+
}
|
|
92755
|
+
catch (error) {
|
|
92756
|
+
if ((0, function_updating_js_1.isFunctionUpdatingError)(error)) {
|
|
92757
|
+
return (0, function_updating_js_1.buildFunctionUpdatingPayload)({
|
|
92758
|
+
action: "updateFunctionConfig",
|
|
92759
|
+
functionName: input.functionName,
|
|
92760
|
+
rawMessage: (0, function_updating_js_1.getErrorMessage)(error),
|
|
92761
|
+
});
|
|
92762
|
+
}
|
|
92763
|
+
throw error;
|
|
92764
|
+
}
|
|
92116
92765
|
}
|
|
92117
92766
|
case "invokeFunction": {
|
|
92118
92767
|
if (!input.functionName) {
|
|
@@ -116574,9 +117223,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
116574
117223
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
116575
117224
|
};
|
|
116576
117225
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
116577
|
-
exports.CLOUDRUN_PACKAGE_TYPES = exports.CLOUDRUN_ACCESS_TYPES = exports.CLOUDRUN_SERVICE_TYPES = void 0;
|
|
117226
|
+
exports.CLOUDRUN_ENV_TYPE = exports.CLOUDRUN_DEPLOY_REGISTRATION_INTERVAL_MS = exports.CLOUDRUN_DEPLOY_REGISTRATION_MAX_WAIT_MS = exports.CLOUDRUN_PACKAGE_TYPES = exports.CLOUDRUN_ACCESS_TYPES = exports.CLOUDRUN_SERVICE_TYPES = void 0;
|
|
117227
|
+
exports.buildManageCloudRunErrorMessage = buildManageCloudRunErrorMessage;
|
|
117228
|
+
exports.isValidCloudRunBuildId = isValidCloudRunBuildId;
|
|
117229
|
+
exports.isValidCloudRunRunId = isValidCloudRunRunId;
|
|
117230
|
+
exports.waitForCloudRunDeployRegistration = waitForCloudRunDeployRegistration;
|
|
117231
|
+
exports.buildCloudRunDeployNextStep = buildCloudRunDeployNextStep;
|
|
117232
|
+
exports.buildCloudRunDeployProgressHint = buildCloudRunDeployProgressHint;
|
|
116578
117233
|
exports.queryCloudRunEnvStatus = queryCloudRunEnvStatus;
|
|
116579
117234
|
exports.describeCloudRunEnvStatus = describeCloudRunEnvStatus;
|
|
117235
|
+
exports.extractEnvBaseInfoVpc = extractEnvBaseInfoVpc;
|
|
117236
|
+
exports.resolveCloudRunDeployVpcInfo = resolveCloudRunDeployVpcInfo;
|
|
117237
|
+
exports.buildCreateCloudRunEnvParam = buildCreateCloudRunEnvParam;
|
|
116580
117238
|
exports.ensureCloudRunEnvInitialized = ensureCloudRunEnvInitialized;
|
|
116581
117239
|
exports.detectCloudRunDbNetworkRisk = detectCloudRunDbNetworkRisk;
|
|
116582
117240
|
exports.extractCloudRunImageInfo = extractCloudRunImageInfo;
|
|
@@ -116589,6 +117247,7 @@ const cloudbase_manager_js_1 = __webpack_require__(3431);
|
|
|
116589
117247
|
const logger_js_1 = __webpack_require__(13039);
|
|
116590
117248
|
const gateway_access_urls_js_1 = __webpack_require__(22435);
|
|
116591
117249
|
const notification_js_1 = __webpack_require__(32720);
|
|
117250
|
+
const capi_js_1 = __webpack_require__(40738);
|
|
116592
117251
|
const cloudrun_config_js_1 = __webpack_require__(48266);
|
|
116593
117252
|
// CloudRun service types
|
|
116594
117253
|
exports.CLOUDRUN_SERVICE_TYPES = ['function', 'container'];
|
|
@@ -116598,7 +117257,7 @@ exports.CLOUDRUN_ACCESS_TYPES = ['OA', 'PUBLIC', 'MINIAPP', 'VPC'];
|
|
|
116598
117257
|
exports.CLOUDRUN_PACKAGE_TYPES = ['Trial', 'Standard', 'Professional', 'Enterprise'];
|
|
116599
117258
|
// Input schema for queryCloudRun tool
|
|
116600
117259
|
const queryCloudRunInputSchema = {
|
|
116601
|
-
action: zod_1.z.enum(['list', 'detail', 'templates', 'getDeployLog', 'envStatus']).describe('查询操作类型:list=获取云托管服务列表(支持分页和筛选),detail=查询指定服务的详细信息(包含服务配置和最新部署状态),templates=获取可用的项目模板列表(用于初始化新项目),getDeployLog
|
|
117260
|
+
action: zod_1.z.enum(['list', 'detail', 'templates', 'getDeployLog', 'getProcessLog', 'getDeployRecords', 'envStatus']).describe('查询操作类型:list=获取云托管服务列表(支持分页和筛选),detail=查询指定服务的详细信息(包含服务配置和最新部署状态),templates=获取可用的项目模板列表(用于初始化新项目),getDeployLog=获取构建日志(仅云端源码构建有意义,走 CODING/DescribeCloudRunBuildLog;已有镜像部署无构建过程;未登录 CODING 的账号会报错),getProcessLog=获取运行日志(部署阶段步骤+容器启动/运行日志,走 tcbr/DescribeCloudRunProcessLog;镜像部署与源码构建均可用,不依赖 CODING;RunId 来自 detail/getDeployRecords 的 latestDeploy.RunId),getDeployRecords=获取指定服务的部署记录列表(按部署时间倒序,含 BuildId/RunId/FlowRatio/Status 等字段,用于查看历史发布与回滚上下文),envStatus=查询当前环境云托管是否已开通及开通状态(Status=creating开通中/normal已开通),用于initEnv之后轮询进度或deploy之前确认环境是否就绪'),
|
|
116602
117261
|
// List operation parameters
|
|
116603
117262
|
pageSize: zod_1.z.number().min(1).max(100).optional().default(10).describe('分页大小,控制每页返回的服务数量。取值范围:1-100,默认值:10。建议根据网络性能和显示需求调整'),
|
|
116604
117263
|
pageNum: zod_1.z.number().min(1).optional().default(1).describe('页码,用于分页查询。从1开始,默认值:1。配合pageSize使用可实现分页浏览'),
|
|
@@ -116606,19 +117265,26 @@ const queryCloudRunInputSchema = {
|
|
|
116606
117265
|
serverType: zod_1.z.enum(exports.CLOUDRUN_SERVICE_TYPES).optional().describe('服务类型筛选条件:function=函数型云托管(仅支持Node.js,有特殊的开发要求和限制,适合简单的API服务),container=容器型服务(推荐使用,支持任意语言和框架如Java/Go/Python/PHP/.NET等,适合大多数应用场景)'),
|
|
116607
117266
|
envId: zod_1.z.string().optional().describe('环境 ID(action=envStatus 时使用;不传则使用当前配置的环境)。格式如 env-xxxxxx'),
|
|
116608
117267
|
// Detail and log operation parameters
|
|
116609
|
-
detailServerName: zod_1.z.string().optional().describe('
|
|
116610
|
-
buildId: zod_1.z.number().optional().describe('构建ID,仅在action=getDeployLog
|
|
117268
|
+
detailServerName: zod_1.z.string().optional().describe('要查询详细信息、部署记录、构建日志或运行日志的服务名称。当action为detail、getDeployLog、getProcessLog或getDeployRecords时建议提供,必须是已存在的服务名称。可通过list操作获取可用的服务名称列表'),
|
|
117269
|
+
buildId: zod_1.z.number().optional().describe('构建ID,仅在action=getDeployLog时使用(构建日志,仅云端源码构建)。不传时默认返回最近一次部署的构建日志'),
|
|
117270
|
+
runId: zod_1.z.string().optional().describe('运行ID(RunId),仅在action=getProcessLog时使用。不传时默认取该服务最近一次部署记录的 RunId(与 detail/getDeployRecords 的 latestDeploy.RunId 同源)。镜像部署与源码构建均可查询运行日志'),
|
|
116611
117271
|
};
|
|
116612
117272
|
// Input schema for manageCloudRun tool
|
|
116613
117273
|
const ManageCloudRunInputSchema = {
|
|
116614
|
-
action: zod_1.z.enum(['init', 'download', 'run', 'deploy', 'delete', 'createAgent', 'updateConfig', 'initEnv']).describe('云托管服务管理操作类型:init=从模板初始化新的云托管项目代码(在targetPath目录下创建以serverName命名的子目录,支持多种语言和框架模板),download=从云端下载现有服务的代码到本地进行开发,run=在本地运行函数型云托管服务(用于开发和调试,仅支持函数型服务),deploy
|
|
117274
|
+
action: zod_1.z.enum(['init', 'download', 'run', 'deploy', 'delete', 'createAgent', 'updateConfig', 'initEnv', 'traffic']).describe('云托管服务管理操作类型:init=从模板初始化新的云托管项目代码(在targetPath目录下创建以serverName命名的子目录,支持多种语言和框架模板),download=从云端下载现有服务的代码到本地进行开发,run=在本地运行函数型云托管服务(用于开发和调试,仅支持函数型服务),deploy=触发部署并轻量等待任务注册(不会 hang 等完整构建)。源码构建(targetPath)返回 buildId,用 getDeployLog 轮询后再 getProcessLog;已有镜像部署(imageUrl,DeployType=image,BuildId 常为 0)跳过 getDeployLog,用 detail/getDeployRecords 取 RunId 后 getProcessLog。传 imageUrl 时 targetPath 可省略;已存在服务会 Read-Merge-Write 保留远程 VpcConf/EnvParams/OpenAccessTypes),updateConfig=仅更新服务配置不重新上传代码(对齐控制台服务设置,走 SubmitServerConfigChangeDiff;不需要 targetPath),delete=删除指定的云托管服务(不可恢复,需要确认),createAgent=创建函数型Agent(基于函数型云托管开发AI智能体),initEnv=开通当前环境的云托管(异步创建云托管环境,幂等:已开通直接返回;适合新环境首次部署前使用),traffic=流量管理与灰度发布(set=调整稳定版/灰度版流量比例,promote=将灰度版本升级为全量,rollback=回滚到上一个稳定版本;对应 tcb cloudrun traffic 命令)'),
|
|
116615
117275
|
serverName: zod_1.z.string().describe('云托管服务名称,用于标识和管理服务。命名规则:支持大小写字母、数字、连字符和下划线,必须以字母开头,长度3-45个字符。在init操作中会作为在targetPath下创建的子目录名,在其他操作中作为目标服务名。initEnv 操作不需要此参数'),
|
|
117276
|
+
// Traffic management operation parameters (action=traffic)
|
|
117277
|
+
trafficOp: zod_1.z.enum(['set', 'promote', 'rollback']).optional().describe('流量管理子操作(action=traffic 时使用):set=调整灰度流量比例(需先部署新版本至灰度,通过 stablePercent/canaryPercent 设置稳定版与灰度版流量比例,两者之和必须等于100);promote=将灰度版本全量发布(灰度版本流量置为100%并关闭灰度发布,等价于 tcb cloudrun traffic promote);rollback=回滚到上一个稳定版本(停止当前灰度/发布中的版本,回到稳定版本,等价于 tcb cloudrun traffic rollback)'),
|
|
117278
|
+
stablePercent: zod_1.z.number().min(0).max(100).optional().describe('稳定版本流量比例(trafficOp=set 时使用),取值范围0-100。与 canaryPercent 之和必须等于100。例如希望 90% 流量打到稳定版、10% 打到灰度版,则 stablePercent=90, canaryPercent=10'),
|
|
117279
|
+
canaryPercent: zod_1.z.number().min(0).max(100).optional().describe('灰度版本流量比例(trafficOp=set 时使用),取值范围0-100。与 stablePercent 之和必须等于100。例如希望 90% 流量打到稳定版、10% 打到灰度版,则 stablePercent=90, canaryPercent=10'),
|
|
116616
117280
|
// InitEnv operation parameters
|
|
116617
117281
|
envId: zod_1.z.string().optional().describe('环境 ID(action=initEnv 时使用;不传则使用当前配置的环境)。格式如 env-xxxxxx'),
|
|
116618
117282
|
packageType: zod_1.z.enum(exports.CLOUDRUN_PACKAGE_TYPES).optional().default('Trial').describe('云托管环境套餐类型(action=initEnv 时使用):Trial=试用,Standard=标准,Professional=专业,Enterprise=企业。默认 Trial'),
|
|
117283
|
+
vpcId: zod_1.z.string().optional().describe('VPC 网络 ID(action=initEnv 时可选)。当平台拒绝系统创建网络时必填,格式如 vpc-xxxxxxxx。与 subnetIds 一起透传给 CreateCloudRunEnv 的 VpcId/SubNetIds。多数场景可不传(由系统创建网络)'),
|
|
117284
|
+
subnetIds: zod_1.z.array(zod_1.z.string()).optional().describe('子网 ID 列表(action=initEnv 时可选)。当需指定自有 VPC 时必填,如 ["subnet-xxxxxxxx"]。与 vpcId 一起透传给 CreateCloudRunEnv 的 SubNetIds'),
|
|
116619
117285
|
// Deploy operation parameters
|
|
116620
|
-
targetPath: zod_1.z.string().optional().describe('本地代码路径,必须是绝对路径。在deploy操作中指定要部署的代码目录,在download操作中指定下载目标目录,在init操作中指定云托管服务的上级目录(会在该目录下创建以serverName命名的子目录)。updateConfig 不需要此参数。建议约定:项目根目录下的cloudrun/目录,例如:/Users/username/projects/my-project/cloudrun。使用 imageUrl
|
|
116621
|
-
imageUrl: zod_1.z.string().optional().describe('已有镜像部署(action=deploy 时使用):直接指定容器镜像地址,如 ccr.ccs.tencentyun.com/ns/img:v1 或公网 registry 地址。传入后走 DeployType="image"(容器型)部署,无需本地源码目录(targetPath 可省略)。支持:1) 公网匿名可拉取的镜像直填地址;2) 私有/需登录的镜像(如 ghcr.io)需先在本地 docker pull → docker tag/push 到腾讯云 CCR → 填入 CCR
|
|
117286
|
+
targetPath: zod_1.z.string().optional().describe('本地代码路径,必须是绝对路径。在deploy操作中指定要部署的代码目录,在download操作中指定下载目标目录,在init操作中指定云托管服务的上级目录(会在该目录下创建以serverName命名的子目录)。updateConfig 不需要此参数。建议约定:项目根目录下的cloudrun/目录,例如:/Users/username/projects/my-project/cloudrun。使用 imageUrl 部署已有镜像时此参数可省略。注意:本地有源码目录不等于必须走源码构建;若用户指定镜像请优先传 imageUrl,不要仅因存在 targetPath 就回退到源码构建'),
|
|
117287
|
+
imageUrl: zod_1.z.string().optional().describe('已有镜像部署(action=deploy 时使用):直接指定容器镜像地址,如 ccr.ccs.tencentyun.com/ns/img:v1 或公网 registry 地址。传入后走 DeployType="image"(容器型)部署,无需本地源码目录(targetPath 可省略)。支持:1) 公网匿名可拉取的镜像直填地址;2) 私有/需登录的镜像(如 ghcr.io)需先在本地 docker pull → docker tag/push 到腾讯云 CCR → 填入 CCR 地址。不传则维持源码构建(本地代码打包上传)。约束:若用户明确提到使用某个镜像、或无需重新构建代码,则必须传 imageUrl 走镜像部署,不要回退到源码构建。注意:无论哪种部署方式,环境都需先开通云托管(未开通时先调用 initEnv,Status=normal 后再部署)'),
|
|
116622
117288
|
envParamsReplaceAll: zod_1.z.boolean().optional().default(false).describe('EnvParams 合并策略(deploy / updateConfig):false(默认)= 与远程按 key 合并(输入覆盖同名 key,远程其余 key 保留);true= 用输入 EnvParams 整包替换远程。仅当显式传入 EnvParams 时生效'),
|
|
116623
117289
|
serverConfig: zod_1.z.object({
|
|
116624
117290
|
OpenAccessTypes: zod_1.z.array(zod_1.z.enum(exports.CLOUDRUN_ACCESS_TYPES)).optional().describe('公网访问类型配置,控制服务的访问权限:OA=办公网访问,PUBLIC=公网访问(默认,可通过HTTPS域名访问),MINIAPP=小程序访问,VPC=VPC访问(仅同VPC内可访问)。可配置多个类型'),
|
|
@@ -116639,7 +117305,7 @@ const ManageCloudRunInputSchema = {
|
|
|
116639
117305
|
InternalDomain: zod_1.z.string().optional().describe('内网域名配置,用于配置服务的内网访问域名。仅在启用内网访问时有效'),
|
|
116640
117306
|
EntryPoint: zod_1.z.array(zod_1.z.string()).optional().describe('Dockerfile EntryPoint参数配置,仅容器型服务需要。指定容器启动时的入口程序数组,如["node","app.js"]'),
|
|
116641
117307
|
Cmd: zod_1.z.array(zod_1.z.string()).optional().describe('Dockerfile Cmd参数配置,仅容器型服务需要。指定容器启动时的默认命令数组,如["npm","start"]'),
|
|
116642
|
-
InitialDelaySeconds: zod_1.z.number().min(0).optional().describe('
|
|
117308
|
+
InitialDelaySeconds: zod_1.z.number().min(0).optional().describe('端口健康检查初始延迟(秒)。部署完成后先等待 N 秒才开始端口探测,之后约每 5s 检查一次、连续约 30 次;30 次全失败才判定部署失败(约 150s 探测窗口),不是「N 秒后立即失败」。启动耗时长的应用建议调到 60–120'),
|
|
116643
117309
|
LogType: zod_1.z.string().optional().describe('日志类型配置,指定服务的日志收集类型。影响日志的采集方式和存储格式'),
|
|
116644
117310
|
LogSetId: zod_1.z.string().optional().describe('CLS日志集ID配置,指定日志服务(CLS)的日志集ID。需要先开通CLS日志服务'),
|
|
116645
117311
|
LogTopicId: zod_1.z.string().optional().describe('CLS日志主题ID配置,指定日志服务(CLS)的日志主题ID。需要先开通CLS日志服务'),
|
|
@@ -116753,15 +117419,228 @@ function validateAndNormalizePath(inputPath) {
|
|
|
116753
117419
|
function buildManageCloudRunErrorMessage(action, serverName, error) {
|
|
116754
117420
|
const baseMessage = error instanceof Error ? error.message : String(error);
|
|
116755
117421
|
const suggestions = [];
|
|
117422
|
+
if ((0, capi_js_1.isCamAuthError)(baseMessage)) {
|
|
117423
|
+
suggestions.push((0, capi_js_1.buildCamAuthGuidance)());
|
|
117424
|
+
}
|
|
116756
117425
|
if (/已有部署发布任务运行中|部署发布任务运行中/i.test(baseMessage)) {
|
|
116757
117426
|
suggestions.push(`服务 \`${serverName}\` 当前已有部署任务在执行,请等待现有任务完成后再重试。`);
|
|
116758
117427
|
suggestions.push("如果你确认要覆盖当前流程,可在合适时机使用 `force=true` 再次发起。");
|
|
116759
117428
|
}
|
|
117429
|
+
if (/云托管资源未开通|无法使用系统创建网络|VpcInfo/i.test(baseMessage)) {
|
|
117430
|
+
suggestions.push("CreateCloudRunServer 需要有效 VPC:请传 serverConfig.VpcConf(VpcId+SubnetId)," +
|
|
117431
|
+
"或在 initEnv 时传入 vpcId/subnetIds(环境开通后 deploy 会自动从 EnvBaseInfo 回填)。" +
|
|
117432
|
+
"若平台拒绝系统创建网络,必须指定上海地域 VPC。");
|
|
117433
|
+
}
|
|
116760
117434
|
if (suggestions.length === 0) {
|
|
116761
117435
|
suggestions.push("请检查服务状态、部署参数和目标目录后重试。");
|
|
116762
117436
|
}
|
|
116763
117437
|
return `[manageCloudRun/${action}] ${baseMessage}\n建议:${suggestions.join(" ")}`;
|
|
116764
117438
|
}
|
|
117439
|
+
/**
|
|
117440
|
+
* Lightweight wait after manageCloudRun deploy: poll until the platform has
|
|
117441
|
+
* registered the manage task / assigned identifiers for follow-up log queries.
|
|
117442
|
+
* - source builds: prefer BuildId > 0 (getDeployLog)
|
|
117443
|
+
* - image deploys: prefer RunId (getProcessLog; BuildId is often 0)
|
|
117444
|
+
* Does NOT wait for the full build/deploy (that can take 5–30 minutes).
|
|
117445
|
+
* Aligns with tcb CLI's DescribeServerManageTask polling entrypoint, but with
|
|
117446
|
+
* a short timeout suitable for MCP tool calls.
|
|
117447
|
+
*/
|
|
117448
|
+
exports.CLOUDRUN_DEPLOY_REGISTRATION_MAX_WAIT_MS = 45_000;
|
|
117449
|
+
exports.CLOUDRUN_DEPLOY_REGISTRATION_INTERVAL_MS = 3_000;
|
|
117450
|
+
function sleepMs(ms) {
|
|
117451
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
117452
|
+
}
|
|
117453
|
+
function isValidCloudRunBuildId(value) {
|
|
117454
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
117455
|
+
}
|
|
117456
|
+
function isValidCloudRunRunId(value) {
|
|
117457
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
117458
|
+
}
|
|
117459
|
+
function extractServerManageTaskInfo(resp) {
|
|
117460
|
+
const data = (resp ?? {});
|
|
117461
|
+
const task = (data.Task ?? data.task ?? {});
|
|
117462
|
+
const rawId = task.Id ?? task.id;
|
|
117463
|
+
const rawStatus = task.Status ?? task.status;
|
|
117464
|
+
const taskId = typeof rawId === "number" && rawId > 0
|
|
117465
|
+
? rawId
|
|
117466
|
+
: typeof rawId === "string" && /^\d+$/.test(rawId) && Number(rawId) > 0
|
|
117467
|
+
? Number(rawId)
|
|
117468
|
+
: undefined;
|
|
117469
|
+
const taskStatus = typeof rawStatus === "string" && rawStatus.trim()
|
|
117470
|
+
? rawStatus.trim()
|
|
117471
|
+
: undefined;
|
|
117472
|
+
return {
|
|
117473
|
+
...(taskId !== undefined ? { taskId } : {}),
|
|
117474
|
+
...(taskStatus ? { taskStatus } : {}),
|
|
117475
|
+
};
|
|
117476
|
+
}
|
|
117477
|
+
/**
|
|
117478
|
+
* Poll DescribeServerManageTask + getDeployRecords until the preferred
|
|
117479
|
+
* identifier appears:
|
|
117480
|
+
* - source (default): BuildId > 0 for getDeployLog
|
|
117481
|
+
* - image: RunId for getProcessLog (BuildId is often 0)
|
|
117482
|
+
* If only taskId is seen, keeps polling for the preferred id until maxWaitMs.
|
|
117483
|
+
*/
|
|
117484
|
+
async function waitForCloudRunDeployRegistration(options) {
|
|
117485
|
+
const mode = options.mode ?? "source";
|
|
117486
|
+
const maxWaitMs = options.maxWaitMs ?? exports.CLOUDRUN_DEPLOY_REGISTRATION_MAX_WAIT_MS;
|
|
117487
|
+
const intervalMs = options.intervalMs ?? exports.CLOUDRUN_DEPLOY_REGISTRATION_INTERVAL_MS;
|
|
117488
|
+
const sleepFn = options.sleepFn ?? sleepMs;
|
|
117489
|
+
const startAt = Date.now();
|
|
117490
|
+
let lastTaskId;
|
|
117491
|
+
let lastTaskStatus;
|
|
117492
|
+
let lastBuildId;
|
|
117493
|
+
let lastRunId;
|
|
117494
|
+
// Immediate first probe, then interval retries until timeout.
|
|
117495
|
+
for (;;) {
|
|
117496
|
+
if (options.manager.commonService) {
|
|
117497
|
+
try {
|
|
117498
|
+
const resp = await options.manager
|
|
117499
|
+
.commonService("tcbr", "2022-02-17")
|
|
117500
|
+
.call({
|
|
117501
|
+
Action: "DescribeServerManageTask",
|
|
117502
|
+
Param: {
|
|
117503
|
+
EnvId: options.envId,
|
|
117504
|
+
ServerName: options.serverName,
|
|
117505
|
+
TaskId: lastTaskId ?? 0,
|
|
117506
|
+
},
|
|
117507
|
+
});
|
|
117508
|
+
const info = extractServerManageTaskInfo(resp);
|
|
117509
|
+
if (info.taskId !== undefined) {
|
|
117510
|
+
lastTaskId = info.taskId;
|
|
117511
|
+
}
|
|
117512
|
+
if (info.taskStatus) {
|
|
117513
|
+
lastTaskStatus = info.taskStatus;
|
|
117514
|
+
}
|
|
117515
|
+
}
|
|
117516
|
+
catch {
|
|
117517
|
+
// Task may not be queryable yet; retry until timeout.
|
|
117518
|
+
}
|
|
117519
|
+
}
|
|
117520
|
+
if (typeof options.cloudrunService.getDeployRecords === "function") {
|
|
117521
|
+
try {
|
|
117522
|
+
const recordsResult = (await options.cloudrunService.getDeployRecords({
|
|
117523
|
+
serverName: options.serverName,
|
|
117524
|
+
}));
|
|
117525
|
+
const latest = recordsResult?.DeployRecords?.[0];
|
|
117526
|
+
if (isValidCloudRunBuildId(latest?.BuildId)) {
|
|
117527
|
+
lastBuildId = latest.BuildId;
|
|
117528
|
+
}
|
|
117529
|
+
if (isValidCloudRunRunId(latest?.RunId)) {
|
|
117530
|
+
lastRunId = latest.RunId.trim();
|
|
117531
|
+
}
|
|
117532
|
+
}
|
|
117533
|
+
catch {
|
|
117534
|
+
// Deploy record may lag behind task registration; retry.
|
|
117535
|
+
}
|
|
117536
|
+
}
|
|
117537
|
+
const preferredReady = mode === "image"
|
|
117538
|
+
? isValidCloudRunRunId(lastRunId)
|
|
117539
|
+
: isValidCloudRunBuildId(lastBuildId);
|
|
117540
|
+
if (preferredReady) {
|
|
117541
|
+
return {
|
|
117542
|
+
registered: true,
|
|
117543
|
+
timedOut: false,
|
|
117544
|
+
taskId: lastTaskId,
|
|
117545
|
+
...(isValidCloudRunBuildId(lastBuildId) ? { buildId: lastBuildId } : {}),
|
|
117546
|
+
...(isValidCloudRunRunId(lastRunId) ? { runId: lastRunId } : {}),
|
|
117547
|
+
taskStatus: lastTaskStatus,
|
|
117548
|
+
waitMs: Date.now() - startAt,
|
|
117549
|
+
};
|
|
117550
|
+
}
|
|
117551
|
+
if (Date.now() - startAt >= maxWaitMs) {
|
|
117552
|
+
break;
|
|
117553
|
+
}
|
|
117554
|
+
await sleepFn(intervalMs);
|
|
117555
|
+
}
|
|
117556
|
+
const registered = isValidCloudRunBuildId(lastBuildId) ||
|
|
117557
|
+
isValidCloudRunRunId(lastRunId) ||
|
|
117558
|
+
(typeof lastTaskId === "number" && lastTaskId > 0);
|
|
117559
|
+
return {
|
|
117560
|
+
registered,
|
|
117561
|
+
timedOut: true,
|
|
117562
|
+
taskId: lastTaskId,
|
|
117563
|
+
...(isValidCloudRunBuildId(lastBuildId) ? { buildId: lastBuildId } : {}),
|
|
117564
|
+
...(isValidCloudRunRunId(lastRunId) ? { runId: lastRunId } : {}),
|
|
117565
|
+
taskStatus: lastTaskStatus,
|
|
117566
|
+
waitMs: Date.now() - startAt,
|
|
117567
|
+
};
|
|
117568
|
+
}
|
|
117569
|
+
/**
|
|
117570
|
+
* Build manageCloudRun(deploy) next_step by DeployType:
|
|
117571
|
+
* - source: getDeployLog (+ follow-up getProcessLog)
|
|
117572
|
+
* - image: skip getDeployLog; getProcessLog via RunId (or getDeployRecords first)
|
|
117573
|
+
*/
|
|
117574
|
+
function buildCloudRunDeployNextStep(options) {
|
|
117575
|
+
const { deployType, serverName, registered } = options;
|
|
117576
|
+
if (deployType === "image") {
|
|
117577
|
+
if (isValidCloudRunRunId(options.runId)) {
|
|
117578
|
+
return {
|
|
117579
|
+
tool: "queryCloudRun",
|
|
117580
|
+
action: "getProcessLog",
|
|
117581
|
+
suggested_args: {
|
|
117582
|
+
action: "getProcessLog",
|
|
117583
|
+
detailServerName: serverName,
|
|
117584
|
+
runId: options.runId.trim(),
|
|
117585
|
+
},
|
|
117586
|
+
note: "Image deploy has no CODING build process; skip getDeployLog. Poll getProcessLog for deploy-step and container runtime logs.",
|
|
117587
|
+
};
|
|
117588
|
+
}
|
|
117589
|
+
return {
|
|
117590
|
+
tool: "queryCloudRun",
|
|
117591
|
+
action: "getDeployRecords",
|
|
117592
|
+
suggested_args: {
|
|
117593
|
+
action: "getDeployRecords",
|
|
117594
|
+
detailServerName: serverName,
|
|
117595
|
+
},
|
|
117596
|
+
note: registered
|
|
117597
|
+
? "Image deploy: read latestDeploy.RunId, then queryCloudRun(action=\"getProcessLog\", runId=...). Skip getDeployLog."
|
|
117598
|
+
: "Image deploy registration still queueing; retry getDeployRecords for RunId, then getProcessLog. Skip getDeployLog.",
|
|
117599
|
+
};
|
|
117600
|
+
}
|
|
117601
|
+
if (isValidCloudRunBuildId(options.buildId)) {
|
|
117602
|
+
return {
|
|
117603
|
+
tool: "queryCloudRun",
|
|
117604
|
+
action: "getDeployLog",
|
|
117605
|
+
suggested_args: {
|
|
117606
|
+
action: "getDeployLog",
|
|
117607
|
+
detailServerName: serverName,
|
|
117608
|
+
buildId: options.buildId,
|
|
117609
|
+
},
|
|
117610
|
+
note: "Source build: poll getDeployLog for build progress, then getProcessLog (RunId from detail/getDeployRecords) for runtime/deploy-step logs.",
|
|
117611
|
+
};
|
|
117612
|
+
}
|
|
117613
|
+
return {
|
|
117614
|
+
tool: "queryCloudRun",
|
|
117615
|
+
action: "getDeployLog",
|
|
117616
|
+
suggested_args: {
|
|
117617
|
+
action: "getDeployLog",
|
|
117618
|
+
detailServerName: serverName,
|
|
117619
|
+
},
|
|
117620
|
+
note: registered
|
|
117621
|
+
? "BuildId not yet available; omit buildId to use the latest deploy record. After build, use getProcessLog for runtime logs."
|
|
117622
|
+
: "Registration timed out; wait a moment then retry getDeployLog, or open consoleUrl. After build, use getProcessLog for runtime logs.",
|
|
117623
|
+
};
|
|
117624
|
+
}
|
|
117625
|
+
function buildCloudRunDeployProgressHint(options) {
|
|
117626
|
+
const { deployType, serverName, consoleUrl } = options;
|
|
117627
|
+
if (deployType === "image") {
|
|
117628
|
+
if (isValidCloudRunRunId(options.runId)) {
|
|
117629
|
+
return ` Image deploy: use queryCloudRun(action="getProcessLog", detailServerName="${serverName}", runId="${options.runId.trim()}") for runtime/deploy-step logs (skip getDeployLog).`;
|
|
117630
|
+
}
|
|
117631
|
+
if (options.registered) {
|
|
117632
|
+
return ` Image deploy registered${typeof options.taskId === "number" ? ` (taskId=${options.taskId})` : ""}; RunId not yet available — use queryCloudRun(action="getDeployRecords", detailServerName="${serverName}") then getProcessLog (skip getDeployLog), or check ${consoleUrl}.`;
|
|
117633
|
+
}
|
|
117634
|
+
return ` Image deploy registration timed out after ${Math.round(options.waitMs / 1000)}s — retry getDeployRecords for RunId then getProcessLog (skip getDeployLog), or check ${consoleUrl}.`;
|
|
117635
|
+
}
|
|
117636
|
+
if (isValidCloudRunBuildId(options.buildId)) {
|
|
117637
|
+
return ` Use queryCloudRun(action="getDeployLog", detailServerName="${serverName}", buildId=${options.buildId}) to poll build progress, then getProcessLog for runtime logs.`;
|
|
117638
|
+
}
|
|
117639
|
+
if (options.registered) {
|
|
117640
|
+
return ` Task registered (taskId=${options.taskId ?? "unknown"}); BuildId not yet available — retry queryCloudRun(action="getDeployLog", detailServerName="${serverName}") shortly, then getProcessLog, or check ${consoleUrl}.`;
|
|
117641
|
+
}
|
|
117642
|
+
return ` Build registration timed out after ${Math.round(options.waitMs / 1000)}s — deployment may still be queueing. Check ${consoleUrl} or retry queryCloudRun(action="getDeployLog", detailServerName="${serverName}") later.`;
|
|
117643
|
+
}
|
|
116765
117644
|
const CLOUDRUN_DB_ENV_KEY_PATTERN = /^(DATABASE_URL|DB_HOST|DB_PORT|DB_USER|DB_PASSWORD|DB_NAME|MYSQL_|POSTGRES_|PGHOST|PGPORT|PGUSER|PGPASSWORD|PGDATABASE|PG_|REDIS_|MONGO_|MONGODB_|SQLALCHEMY_DATABASE_URI|SPRING_DATASOURCE_)/i;
|
|
116766
117645
|
const CLOUDRUN_DB_URL_PATTERN = /(mysql|mariadb|postgres|postgresql|mongodb(\+srv)?|redis|rediss):\/\//i;
|
|
116767
117646
|
async function queryCloudRunEnvStatus(options) {
|
|
@@ -116788,11 +117667,80 @@ async function describeCloudRunEnvStatus(manager, envId) {
|
|
|
116788
117667
|
return { isExist: false, status: "unopened", baseInfo };
|
|
116789
117668
|
}
|
|
116790
117669
|
const rawStatus = typeof baseInfo.Status === "string" ? baseInfo.Status : "";
|
|
116791
|
-
|
|
116792
|
-
|
|
117670
|
+
// Platform may return NORMAL/CREATING (uppercase); normalize before matching.
|
|
117671
|
+
const normalizedStatus = rawStatus.toLowerCase();
|
|
117672
|
+
const status = normalizedStatus === "creating" || normalizedStatus === "normal"
|
|
117673
|
+
? normalizedStatus
|
|
116793
117674
|
: "unknown";
|
|
116794
117675
|
return { isExist: true, status, baseInfo };
|
|
116795
117676
|
}
|
|
117677
|
+
/** CloudRun EnvType for CreateCloudRunEnv (baas DescribeEnvBaseInfo enum). */
|
|
117678
|
+
exports.CLOUDRUN_ENV_TYPE = "baas";
|
|
117679
|
+
/**
|
|
117680
|
+
* Extract VPC binding from DescribeEnvBaseInfo.EnvBaseInfo when the env was
|
|
117681
|
+
* opened with an explicit VPC.
|
|
117682
|
+
*/
|
|
117683
|
+
function extractEnvBaseInfoVpc(baseInfo) {
|
|
117684
|
+
if (!baseInfo) {
|
|
117685
|
+
return undefined;
|
|
117686
|
+
}
|
|
117687
|
+
const vpcId = typeof baseInfo.VpcId === "string" ? baseInfo.VpcId.trim() : "";
|
|
117688
|
+
const rawSubnets = baseInfo.SubNetIds ?? baseInfo.SubnetIds;
|
|
117689
|
+
const subnetIds = Array.isArray(rawSubnets)
|
|
117690
|
+
? rawSubnets
|
|
117691
|
+
.filter((item) => typeof item === "string" && item.trim().length > 0)
|
|
117692
|
+
.map((item) => item.trim())
|
|
117693
|
+
: [];
|
|
117694
|
+
if (!vpcId || subnetIds.length === 0) {
|
|
117695
|
+
return undefined;
|
|
117696
|
+
}
|
|
117697
|
+
return { VpcId: vpcId, SubnetIds: subnetIds };
|
|
117698
|
+
}
|
|
117699
|
+
/**
|
|
117700
|
+
* Resolve CreateCloudRunServer.VpcInfo: prefer explicit serverConfig.VpcConf,
|
|
117701
|
+
* otherwise fall back to env-level VPC from DescribeEnvBaseInfo.
|
|
117702
|
+
*/
|
|
117703
|
+
function resolveCloudRunDeployVpcInfo(options) {
|
|
117704
|
+
const conf = options.vpcConf;
|
|
117705
|
+
if (conf?.VpcId?.trim() && conf?.SubnetId?.trim()) {
|
|
117706
|
+
return {
|
|
117707
|
+
VpcId: conf.VpcId.trim(),
|
|
117708
|
+
CreateType: 2,
|
|
117709
|
+
SubnetIds: [conf.SubnetId.trim()],
|
|
117710
|
+
};
|
|
117711
|
+
}
|
|
117712
|
+
const fromEnv = extractEnvBaseInfoVpc(options.envBaseInfo);
|
|
117713
|
+
if (!fromEnv) {
|
|
117714
|
+
return undefined;
|
|
117715
|
+
}
|
|
117716
|
+
return {
|
|
117717
|
+
VpcId: fromEnv.VpcId,
|
|
117718
|
+
CreateType: 2,
|
|
117719
|
+
SubnetIds: fromEnv.SubnetIds,
|
|
117720
|
+
};
|
|
117721
|
+
}
|
|
117722
|
+
/**
|
|
117723
|
+
* Build CreateCloudRunEnv Param, always including EnvType=baas.
|
|
117724
|
+
* Optional vpcId/subnetIds are used when the platform requires an explicit VPC.
|
|
117725
|
+
*/
|
|
117726
|
+
function buildCreateCloudRunEnvParam(options) {
|
|
117727
|
+
const param = {
|
|
117728
|
+
EnvId: options.envId,
|
|
117729
|
+
PackageType: options.packageType,
|
|
117730
|
+
EnvType: exports.CLOUDRUN_ENV_TYPE,
|
|
117731
|
+
};
|
|
117732
|
+
const vpcId = options.vpcId?.trim();
|
|
117733
|
+
const subnetIds = (options.subnetIds ?? [])
|
|
117734
|
+
.filter((id) => typeof id === "string" && id.trim().length > 0)
|
|
117735
|
+
.map((id) => id.trim());
|
|
117736
|
+
if (vpcId) {
|
|
117737
|
+
param.VpcId = vpcId;
|
|
117738
|
+
}
|
|
117739
|
+
if (subnetIds.length > 0) {
|
|
117740
|
+
param.SubNetIds = subnetIds;
|
|
117741
|
+
}
|
|
117742
|
+
return param;
|
|
117743
|
+
}
|
|
116796
117744
|
/**
|
|
116797
117745
|
* 探测当前环境云托管(大租户)是否已初始化。
|
|
116798
117746
|
*
|
|
@@ -117002,7 +117950,7 @@ function registerCloudRunTools(server) {
|
|
|
117002
117950
|
// Tool 1: Get CloudRun service information (read operations)
|
|
117003
117951
|
server.registerTool("queryCloudRun", {
|
|
117004
117952
|
title: "查询 CloudRun 服务信息",
|
|
117005
|
-
description: "
|
|
117953
|
+
description: "查询云托管服务信息,支持获取服务列表、查询服务详情、获取可用模板列表、获取构建日志(getDeployLog,仅云端源码构建/依赖 CODING)、获取运行日志(getProcessLog,镜像与源码部署均可/不依赖 CODING)、获取部署记录以及查询环境云托管开通状态(envStatus)。返回的服务信息包括服务名称、状态、访问类型、配置详情以及最近部署上下文。",
|
|
117006
117954
|
inputSchema: queryCloudRunInputSchema,
|
|
117007
117955
|
annotations: {
|
|
117008
117956
|
readOnlyHint: true,
|
|
@@ -117090,10 +118038,13 @@ function registerCloudRunTools(server) {
|
|
|
117090
118038
|
if (!latestDeploy) {
|
|
117091
118039
|
message = `Retrieved details for service '${serverName}'. No deploy records found yet.`;
|
|
117092
118040
|
}
|
|
117093
|
-
else if (typeof latestDeploy.Status ===
|
|
117094
|
-
|
|
118041
|
+
else if (typeof latestDeploy.Status === "string" &&
|
|
118042
|
+
// Platform may return FAILED/CREATING (uppercase); normalize before matching.
|
|
118043
|
+
latestDeploy.Status.toLowerCase().includes("failed")) {
|
|
118044
|
+
message = `Service '${serverName}' latest deploy failed. Use queryCloudRun(action="getProcessLog") for runtime/deploy-step logs (RunId from latestDeploy); use getDeployLog only for cloud source-build logs (CODING).`;
|
|
117095
118045
|
}
|
|
117096
|
-
else if (typeof latestDeploy.Status ===
|
|
118046
|
+
else if (typeof latestDeploy.Status === "string" &&
|
|
118047
|
+
latestDeploy.Status.toLowerCase().includes("creating")) {
|
|
117097
118048
|
message = `Service '${serverName}' latest deploy is still running. Please check again later or query the deploy log for progress.`;
|
|
117098
118049
|
}
|
|
117099
118050
|
else {
|
|
@@ -117176,6 +118127,9 @@ function registerCloudRunTools(server) {
|
|
|
117176
118127
|
};
|
|
117177
118128
|
}
|
|
117178
118129
|
const buildId = input.buildId ?? latestDeploy.BuildId;
|
|
118130
|
+
// Build log (CODING / DescribeCloudRunBuildLog). Meaningful only for
|
|
118131
|
+
// cloud source builds; image deploys have no build process. Accounts
|
|
118132
|
+
// without a CODING user may fail here — use getProcessLog for runtime logs.
|
|
117179
118133
|
const buildLogResult = await cloudrunService.getBuildLog({
|
|
117180
118134
|
serverName,
|
|
117181
118135
|
buildId,
|
|
@@ -117206,11 +118160,130 @@ function registerCloudRunTools(server) {
|
|
|
117206
118160
|
buildId,
|
|
117207
118161
|
deployRecord: latestDeploy,
|
|
117208
118162
|
buildLog: buildLogResult?.Log || null,
|
|
118163
|
+
// Optional best-effort attach; prefer dedicated getProcessLog for runtime diagnosis
|
|
117209
118164
|
processLogs,
|
|
117210
118165
|
combinedLogText,
|
|
117211
118166
|
...(processLogsWarning ? { processLogsWarning } : {})
|
|
117212
118167
|
},
|
|
117213
|
-
message: `Retrieved
|
|
118168
|
+
message: `Retrieved build log for service '${serverName}' (getDeployLog=构建日志; for 运行日志 use getProcessLog with RunId)`
|
|
118169
|
+
}, null, 2)
|
|
118170
|
+
}
|
|
118171
|
+
]
|
|
118172
|
+
};
|
|
118173
|
+
}
|
|
118174
|
+
case 'getProcessLog': {
|
|
118175
|
+
const serverName = getCloudRunQueryServerName(input);
|
|
118176
|
+
if (!serverName && !input.runId?.trim()) {
|
|
118177
|
+
return {
|
|
118178
|
+
content: [
|
|
118179
|
+
{
|
|
118180
|
+
type: "text",
|
|
118181
|
+
text: JSON.stringify({
|
|
118182
|
+
success: false,
|
|
118183
|
+
error: "runId is required, or provide detailServerName/serverName to resolve latest RunId",
|
|
118184
|
+
message: "Pass runId from detail/getDeployRecords latestDeploy.RunId, or provide a service name to use the latest deploy RunId."
|
|
118185
|
+
}, null, 2)
|
|
118186
|
+
}
|
|
118187
|
+
]
|
|
118188
|
+
};
|
|
118189
|
+
}
|
|
118190
|
+
let runId = input.runId?.trim() || "";
|
|
118191
|
+
let deployRecord = null;
|
|
118192
|
+
if (serverName) {
|
|
118193
|
+
const deployRecordsResult = await cloudrunService.getDeployRecords({ serverName });
|
|
118194
|
+
const deployRecords = Array.isArray(deployRecordsResult?.DeployRecords)
|
|
118195
|
+
? deployRecordsResult.DeployRecords
|
|
118196
|
+
: [];
|
|
118197
|
+
deployRecord = deployRecords[0] ?? null;
|
|
118198
|
+
if (!runId) {
|
|
118199
|
+
runId = typeof deployRecord?.RunId === "string" ? deployRecord.RunId.trim() : "";
|
|
118200
|
+
}
|
|
118201
|
+
else if (deployRecord?.RunId && deployRecord.RunId !== runId) {
|
|
118202
|
+
// Keep latest deploy as context only; explicit runId wins
|
|
118203
|
+
}
|
|
118204
|
+
}
|
|
118205
|
+
if (!runId) {
|
|
118206
|
+
return {
|
|
118207
|
+
content: [
|
|
118208
|
+
{
|
|
118209
|
+
type: "text",
|
|
118210
|
+
text: JSON.stringify({
|
|
118211
|
+
success: false,
|
|
118212
|
+
error: serverName
|
|
118213
|
+
? `Service '${serverName}' has no RunId on the latest deploy record.`
|
|
118214
|
+
: "runId is required for getProcessLog action",
|
|
118215
|
+
message: "Deploy the service first, then read RunId from queryCloudRun(action=\"detail\") or getDeployRecords (latestDeploy.RunId)."
|
|
118216
|
+
}, null, 2)
|
|
118217
|
+
}
|
|
118218
|
+
]
|
|
118219
|
+
};
|
|
118220
|
+
}
|
|
118221
|
+
if (typeof cloudrunService.getProcessLog !== "function") {
|
|
118222
|
+
throw new Error("Current CloudBase Manager SDK does not support getProcessLog; please upgrade @cloudbase/manager-node.");
|
|
118223
|
+
}
|
|
118224
|
+
const processLogResult = await cloudrunService.getProcessLog({
|
|
118225
|
+
RunId: runId,
|
|
118226
|
+
});
|
|
118227
|
+
const processLogs = Array.isArray(processLogResult?.Logs)
|
|
118228
|
+
? processLogResult.Logs
|
|
118229
|
+
: [];
|
|
118230
|
+
const processLogText = processLogs.length > 0
|
|
118231
|
+
? normalizeProcessLogText(processLogs)
|
|
118232
|
+
: "";
|
|
118233
|
+
return {
|
|
118234
|
+
content: [
|
|
118235
|
+
{
|
|
118236
|
+
type: "text",
|
|
118237
|
+
text: JSON.stringify({
|
|
118238
|
+
success: true,
|
|
118239
|
+
data: {
|
|
118240
|
+
serverName: serverName || undefined,
|
|
118241
|
+
runId,
|
|
118242
|
+
deployRecord,
|
|
118243
|
+
processLogs,
|
|
118244
|
+
processLogText,
|
|
118245
|
+
requestId: processLogResult?.RequestId,
|
|
118246
|
+
},
|
|
118247
|
+
message: `Retrieved process/runtime log for RunId='${runId}' (getProcessLog=运行日志; getDeployLog=构建日志且仅云端构建/依赖 CODING)`
|
|
118248
|
+
}, null, 2)
|
|
118249
|
+
}
|
|
118250
|
+
]
|
|
118251
|
+
};
|
|
118252
|
+
}
|
|
118253
|
+
case 'getDeployRecords': {
|
|
118254
|
+
const serverName = getCloudRunQueryServerName(input);
|
|
118255
|
+
if (!serverName) {
|
|
118256
|
+
return {
|
|
118257
|
+
content: [
|
|
118258
|
+
{
|
|
118259
|
+
type: "text",
|
|
118260
|
+
text: JSON.stringify({
|
|
118261
|
+
success: false,
|
|
118262
|
+
error: "detailServerName or serverName is required for getDeployRecords action",
|
|
118263
|
+
message: "Please provide detailServerName or serverName."
|
|
118264
|
+
}, null, 2)
|
|
118265
|
+
}
|
|
118266
|
+
]
|
|
118267
|
+
};
|
|
118268
|
+
}
|
|
118269
|
+
const deployRecordsResult = await cloudrunService.getDeployRecords({ serverName });
|
|
118270
|
+
const deployRecords = Array.isArray(deployRecordsResult?.DeployRecords)
|
|
118271
|
+
? deployRecordsResult.DeployRecords
|
|
118272
|
+
: [];
|
|
118273
|
+
return {
|
|
118274
|
+
content: [
|
|
118275
|
+
{
|
|
118276
|
+
type: "text",
|
|
118277
|
+
text: JSON.stringify({
|
|
118278
|
+
success: true,
|
|
118279
|
+
data: {
|
|
118280
|
+
serverName,
|
|
118281
|
+
deployRecords,
|
|
118282
|
+
// 部署记录按部署时间倒序(最新在前),首条为最近一次部署
|
|
118283
|
+
total: deployRecords.length,
|
|
118284
|
+
latestDeploy: deployRecords[0] ?? null
|
|
118285
|
+
},
|
|
118286
|
+
message: `Retrieved ${deployRecords.length} deploy records for service '${serverName}'`
|
|
117214
118287
|
}, null, 2)
|
|
117215
118288
|
}
|
|
117216
118289
|
]
|
|
@@ -117284,7 +118357,7 @@ function registerCloudRunTools(server) {
|
|
|
117284
118357
|
// Tool 2: Manage CloudRun services (write operations)
|
|
117285
118358
|
server.registerTool("manageCloudRun", {
|
|
117286
118359
|
title: "管理 CloudRun 服务",
|
|
117287
|
-
description: "管理云托管服务,按开发顺序支持:开通云托管环境(initEnv)、初始化项目(可从模板开始,模板列表可通过 queryCloudRun 查询)、下载服务代码、本地运行(仅函数型服务)、部署代码、仅更新配置(updateConfig,无需重新上传代码)、删除服务。deploy 支持两种方式:1) 源码构建(传入 targetPath,本地代码打包上传,默认路径);2) 已有镜像部署(传入 imageUrl,如 ccr.ccs.tencentyun.com/ns/img:v1,走 DeployType=image 容器型部署,targetPath 可省略)。deploy 对已存在服务会先读取远程配置再合并(保留 VpcConf/EnvParams/OpenAccessTypes)。updateConfig 对齐控制台服务设置页。删除操作需要确认,建议设置force=true。新环境首次部署前若提示未开通云托管,先调用 initEnv 开通(异步、幂等)。",
|
|
118360
|
+
description: "管理云托管服务,按开发顺序支持:开通云托管环境(initEnv)、初始化项目(可从模板开始,模板列表可通过 queryCloudRun 查询)、下载服务代码、本地运行(仅函数型服务)、部署代码、仅更新配置(updateConfig,无需重新上传代码)、删除服务。deploy 支持两种方式:1) 源码构建(传入 targetPath,本地代码打包上传,默认路径);2) 已有镜像部署(传入 imageUrl,如 ccr.ccs.tencentyun.com/ns/img:v1,走 DeployType=image 容器型部署,targetPath 可省略)。deploy 语义为「触发部署 + 轻量等待任务注册」(最多约 45s)。源码构建返回 buildId,用 getDeployLog 轮询构建进度后再 getProcessLog;镜像部署(imageUrl)BuildId 常为 0,跳过 getDeployLog,返回 runId/next_step 引导 getProcessLog(或先 getDeployRecords 取 RunId)。若用户明确指定镜像或无需重新构建,必须传 imageUrl,不要仅因本地有源码目录就回退到源码构建。deploy 对已存在服务会先读取远程配置再合并(保留 VpcConf/EnvParams/OpenAccessTypes)。updateConfig 对齐控制台服务设置页。删除操作需要确认,建议设置force=true。新环境首次部署前若提示未开通云托管,先调用 initEnv 开通(异步、幂等)。",
|
|
117288
118361
|
inputSchema: ManageCloudRunInputSchema,
|
|
117289
118362
|
annotations: {
|
|
117290
118363
|
readOnlyHint: false,
|
|
@@ -117364,13 +118437,19 @@ function registerCloudRunTools(server) {
|
|
|
117364
118437
|
};
|
|
117365
118438
|
}
|
|
117366
118439
|
// 未开通 → 发起异步开通(CreateCloudRunEnv 异步,不阻塞等待)。
|
|
118440
|
+
// Always pass EnvType=baas; optional vpcId/subnetIds when an explicit VPC is required.
|
|
117367
118441
|
let createResult;
|
|
117368
118442
|
try {
|
|
117369
118443
|
createResult = await manager
|
|
117370
118444
|
.commonService("tcbr", "2022-02-17")
|
|
117371
118445
|
.call({
|
|
117372
118446
|
Action: "CreateCloudRunEnv",
|
|
117373
|
-
Param: {
|
|
118447
|
+
Param: buildCreateCloudRunEnvParam({
|
|
118448
|
+
envId,
|
|
118449
|
+
packageType,
|
|
118450
|
+
vpcId: input.vpcId,
|
|
118451
|
+
subnetIds: input.subnetIds,
|
|
118452
|
+
}),
|
|
117374
118453
|
});
|
|
117375
118454
|
}
|
|
117376
118455
|
catch (error) {
|
|
@@ -117398,6 +118477,98 @@ function registerCloudRunTools(server) {
|
|
|
117398
118477
|
]
|
|
117399
118478
|
};
|
|
117400
118479
|
}
|
|
118480
|
+
case 'traffic': {
|
|
118481
|
+
const trafficOp = input.trafficOp;
|
|
118482
|
+
if (!trafficOp) {
|
|
118483
|
+
throw new Error("trafficOp is required for traffic operation (set | promote | rollback)");
|
|
118484
|
+
}
|
|
118485
|
+
if (trafficOp === 'set') {
|
|
118486
|
+
const stable = input.stablePercent;
|
|
118487
|
+
const canary = input.canaryPercent;
|
|
118488
|
+
if (typeof stable !== 'number' ||
|
|
118489
|
+
typeof canary !== 'number') {
|
|
118490
|
+
throw new Error("stablePercent and canaryPercent are required for trafficOp=set");
|
|
118491
|
+
}
|
|
118492
|
+
if (stable + canary !== 100) {
|
|
118493
|
+
throw new Error(`stablePercent + canaryPercent must equal 100 (got ${stable} + ${canary} = ${stable + canary}). ` +
|
|
118494
|
+
`Example: 90/10 means 90% to stable version, 10% to canary version.`);
|
|
118495
|
+
}
|
|
118496
|
+
let setResult;
|
|
118497
|
+
try {
|
|
118498
|
+
setResult = await cloudrunService.setTraffic(input.serverName, stable, canary);
|
|
118499
|
+
}
|
|
118500
|
+
catch (error) {
|
|
118501
|
+
throw new Error(buildManageCloudRunErrorMessage('traffic/set', input.serverName, error));
|
|
118502
|
+
}
|
|
118503
|
+
return {
|
|
118504
|
+
content: [
|
|
118505
|
+
{
|
|
118506
|
+
type: "text",
|
|
118507
|
+
text: JSON.stringify({
|
|
118508
|
+
success: true,
|
|
118509
|
+
data: {
|
|
118510
|
+
serverName: input.serverName,
|
|
118511
|
+
trafficOp: 'set',
|
|
118512
|
+
stablePercent: stable,
|
|
118513
|
+
canaryPercent: canary,
|
|
118514
|
+
result: setResult ?? null
|
|
118515
|
+
},
|
|
118516
|
+
message: `Set traffic for service '${input.serverName}': stable ${stable}% / canary ${canary}%`
|
|
118517
|
+
}, null, 2)
|
|
118518
|
+
}
|
|
118519
|
+
]
|
|
118520
|
+
};
|
|
118521
|
+
}
|
|
118522
|
+
if (trafficOp === 'promote') {
|
|
118523
|
+
let promoteResult;
|
|
118524
|
+
try {
|
|
118525
|
+
promoteResult = await cloudrunService.promote(input.serverName);
|
|
118526
|
+
}
|
|
118527
|
+
catch (error) {
|
|
118528
|
+
throw new Error(buildManageCloudRunErrorMessage('traffic/promote', input.serverName, error));
|
|
118529
|
+
}
|
|
118530
|
+
return {
|
|
118531
|
+
content: [
|
|
118532
|
+
{
|
|
118533
|
+
type: "text",
|
|
118534
|
+
text: JSON.stringify({
|
|
118535
|
+
success: true,
|
|
118536
|
+
data: {
|
|
118537
|
+
serverName: input.serverName,
|
|
118538
|
+
trafficOp: 'promote',
|
|
118539
|
+
result: promoteResult ?? null
|
|
118540
|
+
},
|
|
118541
|
+
message: `Promoted canary version to full release for service '${input.serverName}' (100% traffic). This is irreversible.`
|
|
118542
|
+
}, null, 2)
|
|
118543
|
+
}
|
|
118544
|
+
]
|
|
118545
|
+
};
|
|
118546
|
+
}
|
|
118547
|
+
// rollback
|
|
118548
|
+
let rollbackResult;
|
|
118549
|
+
try {
|
|
118550
|
+
rollbackResult = await cloudrunService.rollback(input.serverName);
|
|
118551
|
+
}
|
|
118552
|
+
catch (error) {
|
|
118553
|
+
throw new Error(buildManageCloudRunErrorMessage('traffic/rollback', input.serverName, error));
|
|
118554
|
+
}
|
|
118555
|
+
return {
|
|
118556
|
+
content: [
|
|
118557
|
+
{
|
|
118558
|
+
type: "text",
|
|
118559
|
+
text: JSON.stringify({
|
|
118560
|
+
success: true,
|
|
118561
|
+
data: {
|
|
118562
|
+
serverName: input.serverName,
|
|
118563
|
+
trafficOp: 'rollback',
|
|
118564
|
+
result: rollbackResult ?? null
|
|
118565
|
+
},
|
|
118566
|
+
message: `Rolled back service '${input.serverName}' to the previous stable version.`
|
|
118567
|
+
}, null, 2)
|
|
118568
|
+
}
|
|
118569
|
+
]
|
|
118570
|
+
};
|
|
118571
|
+
}
|
|
117401
118572
|
case 'createAgent': {
|
|
117402
118573
|
if (!targetPath) {
|
|
117403
118574
|
throw new Error("targetPath is required for createAgent operation");
|
|
@@ -117663,14 +118834,30 @@ for await (let x of res.textStream) {
|
|
|
117663
118834
|
deployParams.serverConfig = effectiveServerConfig;
|
|
117664
118835
|
}
|
|
117665
118836
|
// Manager SDK create path prefers top-level vpcInfo (CreateCloudRunServer.VpcInfo).
|
|
117666
|
-
//
|
|
118837
|
+
// Prefer serverConfig.VpcConf; if missing, auto-fill from env DescribeEnvBaseInfo
|
|
118838
|
+
// (envs opened with vpcId/subnetIds already carry VpcId/SubNetIds).
|
|
117667
118839
|
const vpcConf = effectiveServerConfig?.VpcConf;
|
|
117668
|
-
|
|
117669
|
-
|
|
117670
|
-
|
|
117671
|
-
|
|
117672
|
-
|
|
117673
|
-
|
|
118840
|
+
let envBaseInfoForVpc = null;
|
|
118841
|
+
const explicitVpcId = vpcConf?.VpcId?.trim() ?? "";
|
|
118842
|
+
const explicitSubnetId = vpcConf?.SubnetId?.trim() ?? "";
|
|
118843
|
+
if (!explicitVpcId || !explicitSubnetId) {
|
|
118844
|
+
try {
|
|
118845
|
+
const envIdForVpc = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
|
|
118846
|
+
const envStatusForVpc = await describeCloudRunEnvStatus(manager, envIdForVpc);
|
|
118847
|
+
if (envStatusForVpc.isExist) {
|
|
118848
|
+
envBaseInfoForVpc = envStatusForVpc.baseInfo;
|
|
118849
|
+
}
|
|
118850
|
+
}
|
|
118851
|
+
catch {
|
|
118852
|
+
// Best-effort: deploy may still succeed without vpcInfo when the platform creates the network.
|
|
118853
|
+
}
|
|
118854
|
+
}
|
|
118855
|
+
const resolvedVpcInfo = resolveCloudRunDeployVpcInfo({
|
|
118856
|
+
vpcConf,
|
|
118857
|
+
envBaseInfo: envBaseInfoForVpc,
|
|
118858
|
+
});
|
|
118859
|
+
if (resolvedVpcInfo) {
|
|
118860
|
+
deployParams.vpcInfo = resolvedVpcInfo;
|
|
117674
118861
|
}
|
|
117675
118862
|
let result;
|
|
117676
118863
|
try {
|
|
@@ -117681,6 +118868,18 @@ for await (let x of res.textStream) {
|
|
|
117681
118868
|
}
|
|
117682
118869
|
// Generate cloudbaserc.json configuration file (source-build only; image deploy has no local project dir)
|
|
117683
118870
|
const currentEnvId = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
|
|
118871
|
+
// Lightweight wait for task / BuildId (source) or RunId (image) registration.
|
|
118872
|
+
// Does not wait for full build/deploy completion.
|
|
118873
|
+
const deployType = input.imageUrl
|
|
118874
|
+
? "image"
|
|
118875
|
+
: "source";
|
|
118876
|
+
const registration = await waitForCloudRunDeployRegistration({
|
|
118877
|
+
manager,
|
|
118878
|
+
cloudrunService,
|
|
118879
|
+
envId: currentEnvId,
|
|
118880
|
+
serverName: input.serverName,
|
|
118881
|
+
mode: deployType,
|
|
118882
|
+
});
|
|
117684
118883
|
let cloudbasercGenerated = false;
|
|
117685
118884
|
if (targetPath) {
|
|
117686
118885
|
const cloudbasercPath = path_1.default.join(targetPath, 'cloudbaserc.json');
|
|
@@ -117756,6 +118955,24 @@ for await (let x of res.textStream) {
|
|
|
117756
118955
|
const warningSuffix = dbNetworkRisk
|
|
117757
118956
|
? ` Warning: ${dbNetworkRisk.message} Set serverConfig.VpcConf before relying on DB connectivity.`
|
|
117758
118957
|
: "";
|
|
118958
|
+
const progressHint = buildCloudRunDeployProgressHint({
|
|
118959
|
+
deployType,
|
|
118960
|
+
serverName: input.serverName,
|
|
118961
|
+
buildId: registration.buildId,
|
|
118962
|
+
runId: registration.runId,
|
|
118963
|
+
registered: registration.registered,
|
|
118964
|
+
timedOut: registration.timedOut,
|
|
118965
|
+
waitMs: registration.waitMs,
|
|
118966
|
+
consoleUrl,
|
|
118967
|
+
taskId: registration.taskId,
|
|
118968
|
+
});
|
|
118969
|
+
const nextStep = buildCloudRunDeployNextStep({
|
|
118970
|
+
deployType,
|
|
118971
|
+
serverName: input.serverName,
|
|
118972
|
+
buildId: registration.buildId,
|
|
118973
|
+
runId: registration.runId,
|
|
118974
|
+
registered: registration.registered,
|
|
118975
|
+
});
|
|
117759
118976
|
return {
|
|
117760
118977
|
content: [
|
|
117761
118978
|
{
|
|
@@ -117765,12 +118982,30 @@ for await (let x of res.textStream) {
|
|
|
117765
118982
|
data: {
|
|
117766
118983
|
serviceName: input.serverName,
|
|
117767
118984
|
status: 'deploying',
|
|
117768
|
-
deployType
|
|
118985
|
+
deployType,
|
|
117769
118986
|
...(targetPath ? { deployPath: targetPath } : {}),
|
|
117770
118987
|
...(input.imageUrl ? { imageUrl: input.imageUrl } : {}),
|
|
117771
118988
|
serverType: serverType,
|
|
117772
118989
|
cloudbasercGenerated,
|
|
117773
118990
|
consoleUrl,
|
|
118991
|
+
...(isValidCloudRunBuildId(registration.buildId)
|
|
118992
|
+
? { buildId: registration.buildId }
|
|
118993
|
+
: {}),
|
|
118994
|
+
...(isValidCloudRunRunId(registration.runId)
|
|
118995
|
+
? { runId: registration.runId }
|
|
118996
|
+
: {}),
|
|
118997
|
+
...(typeof registration.taskId === "number"
|
|
118998
|
+
? { taskId: registration.taskId }
|
|
118999
|
+
: {}),
|
|
119000
|
+
registration: {
|
|
119001
|
+
registered: registration.registered,
|
|
119002
|
+
timedOut: registration.timedOut,
|
|
119003
|
+
waitMs: registration.waitMs,
|
|
119004
|
+
...(registration.taskStatus
|
|
119005
|
+
? { taskStatus: registration.taskStatus }
|
|
119006
|
+
: {}),
|
|
119007
|
+
},
|
|
119008
|
+
next_step: nextStep,
|
|
117774
119009
|
...(existingService
|
|
117775
119010
|
? {
|
|
117776
119011
|
configMerge: {
|
|
@@ -117791,8 +119026,10 @@ for await (let x of res.textStream) {
|
|
|
117791
119026
|
? { accessUrlSource: preferredAccessSource }
|
|
117792
119027
|
: {}),
|
|
117793
119028
|
...(warnings.length > 0 ? { warnings } : {}),
|
|
119029
|
+
// Keep raw SDK result for debugging without implying build finished.
|
|
119030
|
+
...(result != null ? { deployAccepted: true } : {}),
|
|
117794
119031
|
},
|
|
117795
|
-
message: `Triggered deployment for ${serverType} service '${input.serverName}' ${input.imageUrl ? `from image ${input.imageUrl}` : `from ${targetPath}`}.
|
|
119032
|
+
message: `Triggered deployment for ${serverType} service '${input.serverName}' ${input.imageUrl ? `from image ${input.imageUrl}` : `from ${targetPath}`}. Deployment is registering/${deployType === "image" ? "starting" : "building"} (status=deploying); this call does not wait for full completion.${progressHint}${warningSuffix}`
|
|
117796
119033
|
}, null, 2)
|
|
117797
119034
|
}
|
|
117798
119035
|
]
|
|
@@ -121928,7 +123165,9 @@ function registerAppTools(server) {
|
|
|
121928
123165
|
buildId,
|
|
121929
123166
|
});
|
|
121930
123167
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
121931
|
-
|
|
123168
|
+
// Platform may return Failed/failed; normalize before matching.
|
|
123169
|
+
const isFailed = typeof result.Status === "string" &&
|
|
123170
|
+
result.Status.toLowerCase() === "failed";
|
|
121932
123171
|
const payload = {
|
|
121933
123172
|
action,
|
|
121934
123173
|
serviceName,
|
|
@@ -131459,8 +132698,11 @@ module.exports = setup;
|
|
|
131459
132698
|
"use strict";
|
|
131460
132699
|
|
|
131461
132700
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
132701
|
+
exports.CAM_AUTH_ERROR_PATTERN = void 0;
|
|
131462
132702
|
exports.assertTcbCloudRunActionAllowed = assertTcbCloudRunActionAllowed;
|
|
131463
132703
|
exports.removeEmptyStringParams = removeEmptyStringParams;
|
|
132704
|
+
exports.buildCamAuthGuidance = buildCamAuthGuidance;
|
|
132705
|
+
exports.isCamAuthError = isCamAuthError;
|
|
131464
132706
|
exports.buildCapiErrorMessage = buildCapiErrorMessage;
|
|
131465
132707
|
exports.registerCapiTools = registerCapiTools;
|
|
131466
132708
|
const zod_1 = __webpack_require__(7552);
|
|
@@ -131480,27 +132722,30 @@ const ALLOWED_SERVICES = [
|
|
|
131480
132722
|
"vpc",
|
|
131481
132723
|
];
|
|
131482
132724
|
/**
|
|
131483
|
-
* tcb
|
|
132725
|
+
* Legacy tcb small-tenant CloudRun (CloudBase Run) API family — blocked.
|
|
131484
132726
|
*
|
|
131485
|
-
*
|
|
131486
|
-
* CreateCloudBaseRunResource
|
|
131487
|
-
*
|
|
131488
|
-
*
|
|
132727
|
+
* CloudRun must use tcbr (CreateCloudRunEnv / CreateCloudRunServer). tcb
|
|
132728
|
+
* CreateCloudBaseRunResource is the 2018 small-tenant open API and is no longer
|
|
132729
|
+
* publicly listed; calling it on an env without a large-tenant record creates
|
|
132730
|
+
* wrong small-tenant services/versions (user incident 2026-08-13; ATO research
|
|
132731
|
+
* efec7cc5). Describe/Delete of the same family are blocked too. Matching is
|
|
132732
|
+
* case-insensitive so Action casing variants cannot bypass the check.
|
|
131489
132733
|
*/
|
|
131490
132734
|
const TCB_CLOUDRUN_FORBIDDEN_ACTIONS = [
|
|
131491
132735
|
"CreateCloudBaseRunResource",
|
|
131492
132736
|
"DescribeCloudBaseRunResource",
|
|
131493
132737
|
"DeleteCloudBaseRunResource",
|
|
131494
132738
|
];
|
|
132739
|
+
const TCB_CLOUDRUN_FORBIDDEN_ACTIONS_LOWER = new Set(TCB_CLOUDRUN_FORBIDDEN_ACTIONS.map((name) => name.toLowerCase()));
|
|
131495
132740
|
const TCB_CLOUDRUN_FORBIDDEN_HINT = `云托管(CloudBase Run)统一走 tcbr 新逻辑(CreateCloudRunEnv / CreateCloudRunServer),` +
|
|
131496
132741
|
`不要使用 tcb 旧小租户接口(${TCB_CLOUDRUN_FORBIDDEN_ACTIONS.join(" / ")})。` +
|
|
131497
132742
|
`新环境请先初始化云托管:callCloudApi(service="tcbr", version="2022-02-17", action="CreateCloudRunEnv", params={EnvId:"..."}),` +
|
|
131498
132743
|
`再通过 manageCloudRun(action="deploy") 创建服务;查询单个环境基础信息/是否已开通云托管用 callCloudApi(service="tcbr", version="2022-02-17", action="DescribeEnvBaseInfo", params={EnvId:"..."}),查询环境列表/资源信息用 DescribeCloudRunEnvs。`;
|
|
131499
132744
|
/**
|
|
131500
|
-
*
|
|
132745
|
+
* Reject legacy tcb small-tenant CloudRun APIs; throw with tcbr guidance when blocked.
|
|
131501
132746
|
*/
|
|
131502
132747
|
function assertTcbCloudRunActionAllowed(service, action) {
|
|
131503
|
-
if (service === "tcb" &&
|
|
132748
|
+
if (service === "tcb" && TCB_CLOUDRUN_FORBIDDEN_ACTIONS_LOWER.has(action.toLowerCase())) {
|
|
131504
132749
|
throw new Error(`[${service}/${action}] 已禁用:${TCB_CLOUDRUN_FORBIDDEN_HINT}`);
|
|
131505
132750
|
}
|
|
131506
132751
|
}
|
|
@@ -131584,6 +132829,21 @@ function buildCapiDocGuidance(service) {
|
|
|
131584
132829
|
}
|
|
131585
132830
|
return `请优先核对对应官方云 API 文档;若你的场景其实是通过 HTTP 协议直接集成 auth/functions/cloudrun/storage/mysqldb 等 CloudBase 业务 API,请优先使用 OpenAPI / Swagger 或 searchKnowledgeBase(mode="openapi"),不要继续猜测管控面 Action。`;
|
|
131586
132831
|
}
|
|
132832
|
+
/** Match CAM / authorization failures from Tencent Cloud control-plane APIs. */
|
|
132833
|
+
exports.CAM_AUTH_ERROR_PATTERN = /UnauthorizedOperation|AuthFailure|not authorized|cam.*denied|Forbidden/i;
|
|
132834
|
+
/**
|
|
132835
|
+
* Guidance when control-plane calls fail due to CAM / API Key scope limits.
|
|
132836
|
+
* Shared by callCloudApi and manageCloudRun error builders.
|
|
132837
|
+
*/
|
|
132838
|
+
function buildCamAuthGuidance() {
|
|
132839
|
+
return ("这通常是 CAM 权限不足(常见于 API Key 登录仅授权数据面:DB/函数/存储)。请任选其一:" +
|
|
132840
|
+
"1) 改用 device code 登录管控面:auth(action=\"start_auth\", authMode=\"device\");" +
|
|
132841
|
+
"2) 或使用腾讯云 SecretId/SecretKey(确认子账号已授 CAM 策略,如 QcloudTCBFullAccess、QcloudVPCReadOnlyAccess);" +
|
|
132842
|
+
"3) 确认目标资源属于当前登录账号。");
|
|
132843
|
+
}
|
|
132844
|
+
function isCamAuthError(message) {
|
|
132845
|
+
return exports.CAM_AUTH_ERROR_PATTERN.test(message);
|
|
132846
|
+
}
|
|
131587
132847
|
function buildCapiErrorMessage(service, action, error) {
|
|
131588
132848
|
const baseMessage = error instanceof Error ? error.message : String(error);
|
|
131589
132849
|
const suggestions = [];
|
|
@@ -131591,6 +132851,10 @@ function buildCapiErrorMessage(service, action, error) {
|
|
|
131591
132851
|
const hasInvalidActionError = /invalid or not found|does not exist|not recognized/i.test(baseMessage);
|
|
131592
132852
|
const hasParameterError = /parameter\s+`?.+?`?\s+is not recognized|MissingParameter|missing parameter|missing required/i.test(baseMessage);
|
|
131593
132853
|
const hasInvalidParameterValueError = /invalid parameter value/i.test(baseMessage);
|
|
132854
|
+
const hasCamAuthError = isCamAuthError(baseMessage);
|
|
132855
|
+
if (hasCamAuthError) {
|
|
132856
|
+
suggestions.push(buildCamAuthGuidance());
|
|
132857
|
+
}
|
|
131594
132858
|
if (hasInvalidActionError) {
|
|
131595
132859
|
suggestions.push(`Action \`${action}\` 可能不存在或不对外开放。请不要继续猜测 Action 名称,先确认 service=\`${service}\` 下该 Action 在当前 API 版本是否真实存在。`);
|
|
131596
132860
|
if (service === "tcb") {
|
|
@@ -131708,9 +132972,13 @@ function registerCapiTools(server) {
|
|
|
131708
132972
|
}
|
|
131709
132973
|
if (service === 'tcb') {
|
|
131710
132974
|
const tcbCapiForbidList = [
|
|
131711
|
-
//
|
|
132975
|
+
// Cloud APIs not clearly public
|
|
131712
132976
|
'DescribeStorageACL', 'ModifyStorageACL', 'DescribeSecurityRule',
|
|
131713
|
-
//
|
|
132977
|
+
// Legacy small-tenant CloudRun open/query/delete (no longer public; also blocked by assertTcbCloudRunActionAllowed)
|
|
132978
|
+
"CreateCloudBaseRunResource",
|
|
132979
|
+
"DescribeCloudBaseRunResource",
|
|
132980
|
+
"DeleteCloudBaseRunResource",
|
|
132981
|
+
// Cloud APIs scheduled for retirement
|
|
131714
132982
|
"ListTables",
|
|
131715
132983
|
"DescribeCloudBaseGWAPI",
|
|
131716
132984
|
"DescribeCloudBaseGWService",
|
|
@@ -142355,7 +143623,7 @@ class TelemetryReporter {
|
|
|
142355
143623
|
const arch = os_1.default.arch(); // 系统架构
|
|
142356
143624
|
// Build-time injected version; guard for vitest / non-webpack runtimes.
|
|
142357
143625
|
const mcpVersion = process.env.npm_package_version ||
|
|
142358
|
-
( true ? "2.
|
|
143626
|
+
( true ? "2.28.1" : 0);
|
|
142359
143627
|
return {
|
|
142360
143628
|
userAgent: `${osType} ${osRelease} ${arch} ${nodeVersion} CloudBase-MCP/${mcpVersion}`,
|
|
142361
143629
|
deviceId: this.deviceId,
|
|
@@ -169298,6 +170566,98 @@ async function resolvePgDbContext(cloudBaseOptions, args) {
|
|
|
169298
170566
|
role: args?.role ?? "cloudbase_admin",
|
|
169299
170567
|
};
|
|
169300
170568
|
}
|
|
170569
|
+
/** Default / recommended roles for CloudBase PG ExecutePGSql.Role */
|
|
170570
|
+
const PG_DEFAULT_ROLE = "cloudbase_admin";
|
|
170571
|
+
const PG_RECOMMENDED_ROLES = [
|
|
170572
|
+
"cloudbase_admin",
|
|
170573
|
+
"anon",
|
|
170574
|
+
"authenticated",
|
|
170575
|
+
"service_role",
|
|
170576
|
+
];
|
|
170577
|
+
/**
|
|
170578
|
+
* Roles that look like login/instance principals and routinely fail SET ROLE
|
|
170579
|
+
* in beacon data (e.g. postgres, postgres_pgdb_<id>). Reject before the API call.
|
|
170580
|
+
*/
|
|
170581
|
+
function isLikelyInvalidPgExecuteRole(role) {
|
|
170582
|
+
const normalized = role.trim();
|
|
170583
|
+
if (!normalized) {
|
|
170584
|
+
return true;
|
|
170585
|
+
}
|
|
170586
|
+
if (normalized === "postgres") {
|
|
170587
|
+
return true;
|
|
170588
|
+
}
|
|
170589
|
+
// Instance-derived login principals, not valid ExecutePGSql Role targets
|
|
170590
|
+
if (/^postgres_pgdb_[a-z0-9]+$/i.test(normalized)) {
|
|
170591
|
+
return true;
|
|
170592
|
+
}
|
|
170593
|
+
// AI-invented concatenations such as cloudbase_admin_pgdb_xxx_pgdb_xxx
|
|
170594
|
+
if (/_pgdb_[a-z0-9]+(_pgdb_[a-z0-9]+)?$/i.test(normalized) &&
|
|
170595
|
+
!PG_RECOMMENDED_ROLES.includes(normalized)) {
|
|
170596
|
+
return true;
|
|
170597
|
+
}
|
|
170598
|
+
return false;
|
|
170599
|
+
}
|
|
170600
|
+
function extractPgRoleNameFromError(message) {
|
|
170601
|
+
const missing = message.match(/role\s+"([^"]+)"\s+does\s+not\s+exist/i);
|
|
170602
|
+
if (missing?.[1]) {
|
|
170603
|
+
return missing[1];
|
|
170604
|
+
}
|
|
170605
|
+
const setRole = message.match(/set\s+role\s+([^\s:]+)/i);
|
|
170606
|
+
if (setRole?.[1]) {
|
|
170607
|
+
return setRole[1].replace(/^"|"$/g, "");
|
|
170608
|
+
}
|
|
170609
|
+
const denied = message.match(/permission\s+denied\s+to\s+set\s+role\s+"([^"]+)"/i);
|
|
170610
|
+
if (denied?.[1]) {
|
|
170611
|
+
return denied[1];
|
|
170612
|
+
}
|
|
170613
|
+
return null;
|
|
170614
|
+
}
|
|
170615
|
+
function isPgRoleExecutionError(message) {
|
|
170616
|
+
const lower = message.toLowerCase();
|
|
170617
|
+
return ((lower.includes("set role") &&
|
|
170618
|
+
(lower.includes("does not exist") ||
|
|
170619
|
+
lower.includes("permission denied"))) ||
|
|
170620
|
+
/role\s+"[^"]+"\s+does\s+not\s+exist/i.test(message) ||
|
|
170621
|
+
/permission\s+denied\s+to\s+set\s+role/i.test(message));
|
|
170622
|
+
}
|
|
170623
|
+
function buildPgRoleGuidanceMessage(attemptedRole) {
|
|
170624
|
+
const recommended = PG_RECOMMENDED_ROLES.join(", ");
|
|
170625
|
+
return (`PostgreSQL role "${attemptedRole}" cannot be used with ExecutePGSql (SET ROLE failed or role does not exist). ` +
|
|
170626
|
+
`Do not invent roles like postgres / postgres_pgdb_* from env or instance names. ` +
|
|
170627
|
+
`Omit role (defaults to ${PG_DEFAULT_ROLE}) or pass one of: ${recommended}. ` +
|
|
170628
|
+
`To list roles that exist in this database, retry with role=${PG_DEFAULT_ROLE} and SQL: SELECT rolname FROM pg_roles ORDER BY rolname;`);
|
|
170629
|
+
}
|
|
170630
|
+
function buildPgRoleErrorPayload(attemptedRole, rawMessage) {
|
|
170631
|
+
return {
|
|
170632
|
+
success: false,
|
|
170633
|
+
errorCode: "PG_ROLE_NOT_AVAILABLE",
|
|
170634
|
+
message: buildPgRoleGuidanceMessage(attemptedRole),
|
|
170635
|
+
data: {
|
|
170636
|
+
attemptedRole,
|
|
170637
|
+
defaultRole: PG_DEFAULT_ROLE,
|
|
170638
|
+
recommendedRoles: [...PG_RECOMMENDED_ROLES],
|
|
170639
|
+
listRolesSql: "SELECT rolname FROM pg_roles ORDER BY rolname;",
|
|
170640
|
+
...(rawMessage ? { rawError: rawMessage.slice(0, 500) } : {}),
|
|
170641
|
+
},
|
|
170642
|
+
nextActions: [
|
|
170643
|
+
buildNextAction(MANAGE_PG_DATABASE, "execute", `Retry without a custom role (defaults to ${PG_DEFAULT_ROLE}), or pass role=${PG_DEFAULT_ROLE} explicitly.`, {
|
|
170644
|
+
action: "execute",
|
|
170645
|
+
role: PG_DEFAULT_ROLE,
|
|
170646
|
+
sql: "SELECT rolname FROM pg_roles ORDER BY rolname;",
|
|
170647
|
+
confirm: true,
|
|
170648
|
+
}),
|
|
170649
|
+
buildNextAction(QUERY_PG_DATABASE, "context", "Inspect the auto-derived PG context (includes default role) before retrying.", { action: "context" }),
|
|
170650
|
+
],
|
|
170651
|
+
};
|
|
170652
|
+
}
|
|
170653
|
+
function tryBuildPgRoleErrorPayload(error, fallbackRole) {
|
|
170654
|
+
const rawMessage = error instanceof Error ? error.message : String(error);
|
|
170655
|
+
if (!isPgRoleExecutionError(rawMessage)) {
|
|
170656
|
+
return null;
|
|
170657
|
+
}
|
|
170658
|
+
const attemptedRole = extractPgRoleNameFromError(rawMessage) ?? fallbackRole;
|
|
170659
|
+
return buildPgRoleErrorPayload(attemptedRole, rawMessage);
|
|
170660
|
+
}
|
|
169301
170661
|
function normalizeLimit(limit, fallback = 20, max = 200) {
|
|
169302
170662
|
if (!Number.isFinite(limit)) {
|
|
169303
170663
|
return fallback;
|
|
@@ -170522,7 +171882,26 @@ async function handleReadOnlySql(args, context, deps) {
|
|
|
170522
171882
|
}
|
|
170523
171883
|
const limit = normalizeLimit(args.limit);
|
|
170524
171884
|
const limitedSql = buildLimitedReadOnlySql(args.sql, limit);
|
|
170525
|
-
|
|
171885
|
+
let result;
|
|
171886
|
+
try {
|
|
171887
|
+
result = await withPgClient(context, deps, (client) => client.query(limitedSql));
|
|
171888
|
+
}
|
|
171889
|
+
catch (error) {
|
|
171890
|
+
const rolePayload = tryBuildPgRoleErrorPayload(error, context.role);
|
|
171891
|
+
if (rolePayload) {
|
|
171892
|
+
return buildPgToolResult(rolePayload);
|
|
171893
|
+
}
|
|
171894
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
171895
|
+
return buildPgToolResult({
|
|
171896
|
+
success: false,
|
|
171897
|
+
errorCode: "PG_SQL_EXEC_FAILED",
|
|
171898
|
+
message: `PostgreSQL read-only SQL execution failed: ${reason}`,
|
|
171899
|
+
data: {
|
|
171900
|
+
role: context.role,
|
|
171901
|
+
sqlPreview: limitedSql.trim().slice(0, 500),
|
|
171902
|
+
},
|
|
171903
|
+
});
|
|
171904
|
+
}
|
|
170526
171905
|
const summary = summarizeQueryResult(result, limit);
|
|
170527
171906
|
return buildPgToolResult({
|
|
170528
171907
|
success: true,
|
|
@@ -170577,6 +171956,9 @@ async function handleExecuteSql(args, context, deps) {
|
|
|
170577
171956
|
],
|
|
170578
171957
|
});
|
|
170579
171958
|
}
|
|
171959
|
+
if (args.role !== undefined && isLikelyInvalidPgExecuteRole(args.role)) {
|
|
171960
|
+
return buildPgToolResult(buildPgRoleErrorPayload(args.role.trim() || "(empty)"));
|
|
171961
|
+
}
|
|
170580
171962
|
if (!classification.readOnly && !args.confirm) {
|
|
170581
171963
|
return buildPgToolResult({
|
|
170582
171964
|
success: false,
|
|
@@ -170596,7 +171978,26 @@ async function handleExecuteSql(args, context, deps) {
|
|
|
170596
171978
|
],
|
|
170597
171979
|
});
|
|
170598
171980
|
}
|
|
170599
|
-
|
|
171981
|
+
let result;
|
|
171982
|
+
try {
|
|
171983
|
+
result = await withPgClient(context, deps, (client) => client.query(args.sql));
|
|
171984
|
+
}
|
|
171985
|
+
catch (error) {
|
|
171986
|
+
const rolePayload = tryBuildPgRoleErrorPayload(error, context.role);
|
|
171987
|
+
if (rolePayload) {
|
|
171988
|
+
return buildPgToolResult(rolePayload);
|
|
171989
|
+
}
|
|
171990
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
171991
|
+
return buildPgToolResult({
|
|
171992
|
+
success: false,
|
|
171993
|
+
errorCode: "PG_SQL_EXEC_FAILED",
|
|
171994
|
+
message: `PostgreSQL SQL execution failed: ${reason}`,
|
|
171995
|
+
data: {
|
|
171996
|
+
role: context.role,
|
|
171997
|
+
sqlPreview: args.sql.trim().slice(0, 500),
|
|
171998
|
+
},
|
|
171999
|
+
});
|
|
172000
|
+
}
|
|
170600
172001
|
const targetTable = parseTargetTableFromSql(args.sql, context.defaultSchema);
|
|
170601
172002
|
return buildPgToolResult({
|
|
170602
172003
|
success: true,
|
|
@@ -171489,7 +172890,9 @@ function registerPGDatabaseTools(server, providedDeps) {
|
|
|
171489
172890
|
role: zod_1.z
|
|
171490
172891
|
.string()
|
|
171491
172892
|
.optional()
|
|
171492
|
-
.describe(
|
|
172893
|
+
.describe(`可选的 PostgreSQL role,传给 Manager SDK executePGSql 的 Role(平台会 SET ROLE)。默认 ${PG_DEFAULT_ROLE}。` +
|
|
172894
|
+
`推荐取值:${PG_RECOMMENDED_ROLES.join(" / ")}。` +
|
|
172895
|
+
`不要传 postgres、postgres_pgdb_* 或从环境名臆造的角色;不确定时省略本字段,或先用 ${PG_DEFAULT_ROLE} 执行 SELECT rolname FROM pg_roles。`),
|
|
171493
172896
|
objectName: zod_1.z
|
|
171494
172897
|
.string()
|
|
171495
172898
|
.optional()
|
|
@@ -171593,6 +172996,10 @@ function registerPGDatabaseTools(server, providedDeps) {
|
|
|
171593
172996
|
return handleExecuteSql(args, context, deps);
|
|
171594
172997
|
}
|
|
171595
172998
|
}
|
|
172999
|
+
// Reject known-bad Role values before readiness probe / ExecutePGSql SET ROLE.
|
|
173000
|
+
if (args.role !== undefined && isLikelyInvalidPgExecuteRole(args.role)) {
|
|
173001
|
+
return buildPgToolResult(buildPgRoleErrorPayload(args.role.trim() || "(empty)"));
|
|
173002
|
+
}
|
|
171596
173003
|
try {
|
|
171597
173004
|
await ensurePgReadyOnce(cbOpts, deps);
|
|
171598
173005
|
}
|
|
@@ -223930,7 +225337,7 @@ ${envIdSection}
|
|
|
223930
225337
|
## 环境信息
|
|
223931
225338
|
- 操作系统: ${os_1.default.type()} ${os_1.default.release()}
|
|
223932
225339
|
- Node.js版本: ${process.version}
|
|
223933
|
-
- MCP 版本:${process.env.npm_package_version || ( true ? "2.
|
|
225340
|
+
- MCP 版本:${process.env.npm_package_version || ( true ? "2.28.1" : 0)}
|
|
223934
225341
|
- 系统架构: ${os_1.default.arch()}
|
|
223935
225342
|
- 时间: ${new Date().toISOString()}
|
|
223936
225343
|
- 请求ID: ${requestId}
|
|
@@ -224011,7 +225418,7 @@ function createWrappedHandler(name, handler, server) {
|
|
|
224011
225418
|
requestId: (typeof error === 'object' && error && 'requestId' in error) ? error.requestId : '',
|
|
224012
225419
|
ide: server.ide || process.env.INTEGRATION_IDE || ''
|
|
224013
225420
|
});
|
|
224014
|
-
const mcpVersion = true ? "2.
|
|
225421
|
+
const mcpVersion = true ? "2.28.1" : 0;
|
|
224015
225422
|
const requestIdSuffix = requestId ? `\n🆔 RequestId: ${requestId}` : '';
|
|
224016
225423
|
const enhancedErrorMessage = `${errorMessage}${requestIdSuffix}\n\n📦 CloudBase MCP v${mcpVersion}\n🔗 遇到问题?请复制以下链接到浏览器打开\n即可自动携带错误详情快速创建 GitHub Issue:\n${issueLink}`;
|
|
224017
225424
|
// 创建新的错误对象,保持原有的错误类型但更新消息
|
|
@@ -233968,6 +235375,15 @@ const logger_js_1 = __webpack_require__(13039);
|
|
|
233968
235375
|
const CATEGORY = "NoSQL database";
|
|
233969
235376
|
const COLLECTION_READY_TIMEOUT_MS = 10000;
|
|
233970
235377
|
const COLLECTION_READY_POLL_INTERVAL_MS = 500;
|
|
235378
|
+
/** Default page size for QueryRecords (unchanged; overrun tuning is a separate track). */
|
|
235379
|
+
const QUERY_RECORDS_DEFAULT_LIMIT = 100;
|
|
235380
|
+
/** Cloud API MgoLimit `lte` upper bound (aligned with ListTables / SDK limit docs). */
|
|
235381
|
+
const QUERY_RECORDS_MAX_LIMIT = 1000;
|
|
235382
|
+
const PROJECTION_EXAMPLE = '{"_id":1,"name":1,"createdAt":1}';
|
|
235383
|
+
const PROJECTION_EXCLUDE_EXAMPLE = '{"password":0}';
|
|
235384
|
+
const PROJECTION_GUIDANCE = `projection 必须是字段投影对象(或对应 JSON 字符串),值只能是 1/0/true/false。` +
|
|
235385
|
+
`合法示例:${PROJECTION_EXAMPLE}(只返回这些字段),或 ${PROJECTION_EXCLUDE_EXAMPLE}(排除字段)。` +
|
|
235386
|
+
`不要传字段名数组、逗号分隔字符串、查询条件,也不要在同一投影里混用包含(1/true)与排除(0/false)(_id 除外)。`;
|
|
233971
235387
|
/** Convert object values to JSON strings for API calls */
|
|
233972
235388
|
const toJSONString = (v) => typeof v === "object" && v !== null ? JSON.stringify(v) : v;
|
|
233973
235389
|
function delay(ms) {
|
|
@@ -234038,6 +235454,95 @@ function normalizeSortInput(sort) {
|
|
|
234038
235454
|
}
|
|
234039
235455
|
return JSON.stringify(parsed.map((item) => normalizeSortItem(item)));
|
|
234040
235456
|
}
|
|
235457
|
+
function isProjectionFlag(value) {
|
|
235458
|
+
return value === 0 || value === 1 || value === true || value === false;
|
|
235459
|
+
}
|
|
235460
|
+
function normalizeProjectionInput(projection) {
|
|
235461
|
+
if (projection === undefined || projection === null) {
|
|
235462
|
+
return undefined;
|
|
235463
|
+
}
|
|
235464
|
+
let parsed = projection;
|
|
235465
|
+
if (typeof parsed === "string") {
|
|
235466
|
+
const trimmed = parsed.trim();
|
|
235467
|
+
if (!trimmed) {
|
|
235468
|
+
return undefined;
|
|
235469
|
+
}
|
|
235470
|
+
try {
|
|
235471
|
+
parsed = JSON.parse(trimmed);
|
|
235472
|
+
}
|
|
235473
|
+
catch {
|
|
235474
|
+
throw new Error(`projection 必须是合法 JSON 对象字符串。${PROJECTION_GUIDANCE}`);
|
|
235475
|
+
}
|
|
235476
|
+
}
|
|
235477
|
+
if (Array.isArray(parsed)) {
|
|
235478
|
+
throw new Error(`projection 不支持字段名数组。请改为对象,例如 ${PROJECTION_EXAMPLE}。${PROJECTION_GUIDANCE}`);
|
|
235479
|
+
}
|
|
235480
|
+
if (!parsed || typeof parsed !== "object") {
|
|
235481
|
+
throw new Error(`projection 必须是对象。请改为例如 ${PROJECTION_EXAMPLE}。${PROJECTION_GUIDANCE}`);
|
|
235482
|
+
}
|
|
235483
|
+
const entries = Object.entries(parsed);
|
|
235484
|
+
if (entries.length === 0) {
|
|
235485
|
+
return undefined;
|
|
235486
|
+
}
|
|
235487
|
+
let hasInclude = false;
|
|
235488
|
+
let hasExclude = false;
|
|
235489
|
+
const normalized = {};
|
|
235490
|
+
for (const [key, value] of entries) {
|
|
235491
|
+
if (typeof key !== "string" || key.trim() === "") {
|
|
235492
|
+
throw new Error(`projection 字段名必须是非空字符串。${PROJECTION_GUIDANCE}`);
|
|
235493
|
+
}
|
|
235494
|
+
if (!isProjectionFlag(value)) {
|
|
235495
|
+
throw new Error(`projection["${key}"] 的值非法(当前为 ${JSON.stringify(value)}),仅支持 1/0/true/false。` +
|
|
235496
|
+
`合法示例:${PROJECTION_EXAMPLE}。${PROJECTION_GUIDANCE}`);
|
|
235497
|
+
}
|
|
235498
|
+
const flag = value === true || value === 1 ? 1 : 0;
|
|
235499
|
+
normalized[key] = flag;
|
|
235500
|
+
if (key === "_id") {
|
|
235501
|
+
continue;
|
|
235502
|
+
}
|
|
235503
|
+
if (flag === 1) {
|
|
235504
|
+
hasInclude = true;
|
|
235505
|
+
}
|
|
235506
|
+
else {
|
|
235507
|
+
hasExclude = true;
|
|
235508
|
+
}
|
|
235509
|
+
}
|
|
235510
|
+
if (hasInclude && hasExclude) {
|
|
235511
|
+
throw new Error(`projection 不能同时混用包含(1/true)与排除(0/false)(_id 除外)。` +
|
|
235512
|
+
`请只保留一种模式,例如 ${PROJECTION_EXAMPLE} 或 ${PROJECTION_EXCLUDE_EXAMPLE}。`);
|
|
235513
|
+
}
|
|
235514
|
+
return JSON.stringify(normalized);
|
|
235515
|
+
}
|
|
235516
|
+
function normalizeQueryLimit(limit) {
|
|
235517
|
+
if (limit === undefined || limit === null) {
|
|
235518
|
+
return QUERY_RECORDS_DEFAULT_LIMIT;
|
|
235519
|
+
}
|
|
235520
|
+
if (typeof limit !== "number" || !Number.isFinite(limit)) {
|
|
235521
|
+
throw new Error(`limit 必须是数字,取值范围 1-${QUERY_RECORDS_MAX_LIMIT}(默认 ${QUERY_RECORDS_DEFAULT_LIMIT})。` +
|
|
235522
|
+
`超出上限时请用 offset 分页,例如 limit=${QUERY_RECORDS_MAX_LIMIT}, offset=0,再 offset+=limit。`);
|
|
235523
|
+
}
|
|
235524
|
+
if (!Number.isInteger(limit)) {
|
|
235525
|
+
throw new Error(`limit 必须是整数,取值范围 1-${QUERY_RECORDS_MAX_LIMIT}。当前值:${limit}`);
|
|
235526
|
+
}
|
|
235527
|
+
if (limit < 1 || limit > QUERY_RECORDS_MAX_LIMIT) {
|
|
235528
|
+
throw new Error(`limit 超出上限:当前 ${limit},允许范围 1-${QUERY_RECORDS_MAX_LIMIT}(Cloud API MgoLimit lte)。` +
|
|
235529
|
+
`请将 limit 调整为 ≤${QUERY_RECORDS_MAX_LIMIT},并用 offset 分页拉取更多数据` +
|
|
235530
|
+
`(例如 limit=${QUERY_RECORDS_MAX_LIMIT}, offset=0,下一页 offset=${QUERY_RECORDS_MAX_LIMIT})。`);
|
|
235531
|
+
}
|
|
235532
|
+
return limit;
|
|
235533
|
+
}
|
|
235534
|
+
function enhanceQueryRecordsError(error) {
|
|
235535
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
235536
|
+
if (/Query projection.*illegal|projection entered in the request is illegal/i.test(message)) {
|
|
235537
|
+
throw new Error(`QueryRecords 投影非法:${message}。修正建议:${PROJECTION_GUIDANCE}`);
|
|
235538
|
+
}
|
|
235539
|
+
if (/MgoLimit.*lte|Field validation for 'MgoLimit' failed on the 'lte'/i.test(message)) {
|
|
235540
|
+
throw new Error(`QueryRecords MgoLimit 超限:${message}。` +
|
|
235541
|
+
`limit 最大为 ${QUERY_RECORDS_MAX_LIMIT},请缩小 limit 并用 offset 分页` +
|
|
235542
|
+
`(例如 limit=${QUERY_RECORDS_MAX_LIMIT}, offset=0)。`);
|
|
235543
|
+
}
|
|
235544
|
+
throw error instanceof Error ? error : new Error(message);
|
|
235545
|
+
}
|
|
234041
235546
|
function withCollectionName(collectionName, payload) {
|
|
234042
235547
|
return {
|
|
234043
235548
|
...payload,
|
|
@@ -234572,7 +236077,9 @@ deleteCollection: 删除集合`),
|
|
|
234572
236077
|
// readNoSqlDatabaseContent
|
|
234573
236078
|
server.registerTool?.("readNoSqlDatabaseContent", {
|
|
234574
236079
|
title: "查询并获取 CloudBase NoSQL 数据库数据记录",
|
|
234575
|
-
description: "查询 CloudBase NoSQL 数据库中的数据记录。支持按条件筛选、分页、排序,适用于管理端数据查询与运维。"
|
|
236080
|
+
description: "查询 CloudBase NoSQL 数据库中的数据记录。支持按条件筛选、分页、排序,适用于管理端数据查询与运维。" +
|
|
236081
|
+
`limit 默认 ${QUERY_RECORDS_DEFAULT_LIMIT}、最大 ${QUERY_RECORDS_MAX_LIMIT};超出请用 offset 分页。` +
|
|
236082
|
+
`projection 仅支持 { field: 1|0 } 对象(示例 ${PROJECTION_EXAMPLE}),不要传字段数组。`,
|
|
234576
236083
|
inputSchema: {
|
|
234577
236084
|
collectionName: zod_1.z.string().describe("集合名称"),
|
|
234578
236085
|
instanceId: zod_1.z
|
|
@@ -234586,7 +236093,9 @@ deleteCollection: 删除集合`),
|
|
|
234586
236093
|
projection: zod_1.z
|
|
234587
236094
|
.union([zod_1.z.object({}).passthrough(), zod_1.z.string()])
|
|
234588
236095
|
.optional()
|
|
234589
|
-
.describe(
|
|
236096
|
+
.describe(`返回字段投影,仅支持对象或对应 JSON 字符串,值只能是 1/0/true/false。` +
|
|
236097
|
+
`合法示例:${PROJECTION_EXAMPLE}(包含)或 ${PROJECTION_EXCLUDE_EXAMPLE}(排除)。` +
|
|
236098
|
+
`不要传 ["name","age"] 这类字段数组,也不要混用包含与排除(_id 除外)。`),
|
|
234590
236099
|
sort: zod_1.z
|
|
234591
236100
|
.union([
|
|
234592
236101
|
zod_1.z.array(zod_1.z
|
|
@@ -234599,7 +236108,11 @@ deleteCollection: 删除集合`),
|
|
|
234599
236108
|
])
|
|
234600
236109
|
.optional()
|
|
234601
236110
|
.describe('排序条件,仅支持数组 [{"key":"createdAt","direction":-1}] 或对应 JSON 字符串。'),
|
|
234602
|
-
limit: zod_1.z
|
|
236111
|
+
limit: zod_1.z
|
|
236112
|
+
.number()
|
|
236113
|
+
.optional()
|
|
236114
|
+
.describe(`返回数量限制,整数,范围 1-${QUERY_RECORDS_MAX_LIMIT},默认 ${QUERY_RECORDS_DEFAULT_LIMIT}。` +
|
|
236115
|
+
`超过 ${QUERY_RECORDS_MAX_LIMIT} 会被 Cloud API MgoLimit lte 校验拒绝;请用 offset 分页。`),
|
|
234603
236116
|
offset: zod_1.z.number().optional().describe("跳过的记录数"),
|
|
234604
236117
|
},
|
|
234605
236118
|
annotations: {
|
|
@@ -234615,46 +236128,53 @@ deleteCollection: 删除集合`),
|
|
|
234615
236128
|
durationMs: Date.now() - managerStartedAt,
|
|
234616
236129
|
});
|
|
234617
236130
|
const normalizedSort = normalizeSortInput(sort);
|
|
234618
|
-
const
|
|
234619
|
-
|
|
234620
|
-
|
|
234621
|
-
|
|
234622
|
-
|
|
234623
|
-
|
|
234624
|
-
|
|
234625
|
-
|
|
234626
|
-
|
|
234627
|
-
|
|
234628
|
-
|
|
234629
|
-
|
|
234630
|
-
|
|
234631
|
-
|
|
234632
|
-
|
|
234633
|
-
|
|
234634
|
-
const parseStartedAt = Date.now();
|
|
234635
|
-
const documents = normalizeNoSqlDocuments(result.Data);
|
|
234636
|
-
logNoSqlLatency("readNoSqlDatabaseContent", "parseResult", {
|
|
234637
|
-
collectionName,
|
|
234638
|
-
durationMs: Date.now() - parseStartedAt,
|
|
234639
|
-
documentCount: documents.length,
|
|
234640
|
-
});
|
|
234641
|
-
return {
|
|
234642
|
-
content: [
|
|
234643
|
-
{
|
|
234644
|
-
type: "text",
|
|
234645
|
-
text: JSON.stringify(withCollectionName(collectionName, {
|
|
234646
|
-
success: true,
|
|
234647
|
-
requestId: result.RequestId,
|
|
234648
|
-
data: documents,
|
|
234649
|
-
total: typeof result.Pager?.Total === "number"
|
|
234650
|
-
? result.Pager.Total
|
|
234651
|
-
: documents.length,
|
|
234652
|
-
pager: result.Pager,
|
|
234653
|
-
message: "文档查询成功",
|
|
234654
|
-
}), null, 2),
|
|
236131
|
+
const normalizedProjection = normalizeProjectionInput(projection);
|
|
236132
|
+
const normalizedLimit = normalizeQueryLimit(limit);
|
|
236133
|
+
try {
|
|
236134
|
+
const result = await callNoSqlContentApi({
|
|
236135
|
+
toolName: "readNoSqlDatabaseContent",
|
|
236136
|
+
action: "QueryRecords",
|
|
236137
|
+
collectionName,
|
|
236138
|
+
cloudbase,
|
|
236139
|
+
cloudBaseOptions,
|
|
236140
|
+
instanceIdOverride: instanceId,
|
|
236141
|
+
param: {
|
|
236142
|
+
MgoQuery: toJSONString(query),
|
|
236143
|
+
MgoProjection: normalizedProjection,
|
|
236144
|
+
MgoSort: normalizedSort,
|
|
236145
|
+
MgoLimit: normalizedLimit,
|
|
236146
|
+
MgoOffset: offset,
|
|
234655
236147
|
},
|
|
234656
|
-
|
|
234657
|
-
|
|
236148
|
+
});
|
|
236149
|
+
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
236150
|
+
const parseStartedAt = Date.now();
|
|
236151
|
+
const documents = normalizeNoSqlDocuments(result.Data);
|
|
236152
|
+
logNoSqlLatency("readNoSqlDatabaseContent", "parseResult", {
|
|
236153
|
+
collectionName,
|
|
236154
|
+
durationMs: Date.now() - parseStartedAt,
|
|
236155
|
+
documentCount: documents.length,
|
|
236156
|
+
});
|
|
236157
|
+
return {
|
|
236158
|
+
content: [
|
|
236159
|
+
{
|
|
236160
|
+
type: "text",
|
|
236161
|
+
text: JSON.stringify(withCollectionName(collectionName, {
|
|
236162
|
+
success: true,
|
|
236163
|
+
requestId: result.RequestId,
|
|
236164
|
+
data: documents,
|
|
236165
|
+
total: typeof result.Pager?.Total === "number"
|
|
236166
|
+
? result.Pager.Total
|
|
236167
|
+
: documents.length,
|
|
236168
|
+
pager: result.Pager,
|
|
236169
|
+
message: "文档查询成功",
|
|
236170
|
+
}), null, 2),
|
|
236171
|
+
},
|
|
236172
|
+
],
|
|
236173
|
+
};
|
|
236174
|
+
}
|
|
236175
|
+
catch (error) {
|
|
236176
|
+
enhanceQueryRecordsError(error);
|
|
236177
|
+
}
|
|
234658
236178
|
});
|
|
234659
236179
|
// writeNoSqlDatabaseContent
|
|
234660
236180
|
server.registerTool?.("writeNoSqlDatabaseContent", {
|
|
@@ -239838,6 +241358,7 @@ exports.CloudBaseError = CloudBaseError;
|
|
|
239838
241358
|
"use strict";
|
|
239839
241359
|
|
|
239840
241360
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
241361
|
+
exports.validateUserRegoContent = validateUserRegoContent;
|
|
239841
241362
|
exports.registerPermissionTools = registerPermissionTools;
|
|
239842
241363
|
const zod_1 = __webpack_require__(7552);
|
|
239843
241364
|
const cloudbase_manager_js_1 = __webpack_require__(3431);
|
|
@@ -239849,6 +241370,9 @@ const QUERY_PERMISSION_ACTIONS = [
|
|
|
239849
241370
|
"getRole",
|
|
239850
241371
|
"listUsers",
|
|
239851
241372
|
"getUser",
|
|
241373
|
+
// Align with CLI `tcb policy list/get` (OPA gateway authz)
|
|
241374
|
+
"listPolicy",
|
|
241375
|
+
"getPolicy",
|
|
239852
241376
|
];
|
|
239853
241377
|
const MANAGE_PERMISSION_ACTIONS = [
|
|
239854
241378
|
"updateResourcePermission",
|
|
@@ -239862,7 +241386,14 @@ const MANAGE_PERMISSION_ACTIONS = [
|
|
|
239862
241386
|
"createUser",
|
|
239863
241387
|
"updateUser",
|
|
239864
241388
|
"deleteUsers",
|
|
241389
|
+
// Align with CLI `tcb policy set` (OPA Rego; disables legacy gateway auth)
|
|
241390
|
+
"setPolicy",
|
|
239865
241391
|
];
|
|
241392
|
+
/** Keys accepted by Manager SDK describeEnvAuthzConfig / modifyEnvAuthzConfig. */
|
|
241393
|
+
const AUTHZ_USER_REGO_KEY = "authz.user.rego";
|
|
241394
|
+
const AUTHZ_PLATFORM_EXTENSION_REGO_KEY = "authz.platform.extension.rego";
|
|
241395
|
+
/** CLI `tcb policy list --resource-type` only documents `policy`. */
|
|
241396
|
+
const POLICY_LIST_RESOURCE_TYPES = ["policy"];
|
|
239866
241397
|
function buildWriteVerificationHint(resourceId) {
|
|
239867
241398
|
return `对于 ${resourceId} 这类有后端权限控制的集合,前端调用 .doc(id).update() / .doc(id).remove() 后,不能只看是否没有抛异常。请显式检查返回结果中的 updated / deleted 是否大于 0;如果 result.code、result.message 存在,或 updated / deleted 为 0,要把它当作真实失败并向上抛错。`;
|
|
239868
241399
|
}
|
|
@@ -239901,10 +241432,32 @@ function isPostgresqlPermissionApiUnsupported(error) {
|
|
|
239901
241432
|
const message = error instanceof Error ? error.message : String(error);
|
|
239902
241433
|
return /does not support PostgreSQL type environments/i.test(message);
|
|
239903
241434
|
}
|
|
239904
|
-
const AUTHZ_USER_REGO_KEY = "authz.user.rego";
|
|
239905
241435
|
function looksLikeUserRego(value) {
|
|
239906
241436
|
return /^\s*package\s+authz\.user\b/m.test(value);
|
|
239907
241437
|
}
|
|
241438
|
+
/**
|
|
241439
|
+
* Validate user Rego before setPolicy / modifyEnvAuthzConfig.
|
|
241440
|
+
* Aligns with CLI docs (must start with `package authz.user`) and CLI non-empty check.
|
|
241441
|
+
* Does not run a full OPA compiler; backend still owns deep syntax validation.
|
|
241442
|
+
*/
|
|
241443
|
+
function validateUserRegoContent(value) {
|
|
241444
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
241445
|
+
throw new Error("action=setPolicy 需要非空 regoContent(对齐 CLI `tcb policy set <regoContent>`)。");
|
|
241446
|
+
}
|
|
241447
|
+
const trimmed = value.trim();
|
|
241448
|
+
if (!looksLikeUserRego(trimmed)) {
|
|
241449
|
+
throw new Error("Rego 策略必须以 `package authz.user` 开头(对齐 https://docs.cloudbase.net/cli-v1/policy/management )。");
|
|
241450
|
+
}
|
|
241451
|
+
const open = (trimmed.match(/\{/g) || []).length;
|
|
241452
|
+
const close = (trimmed.match(/\}/g) || []).length;
|
|
241453
|
+
if (open !== close) {
|
|
241454
|
+
throw new Error(`Rego 策略花括号不匹配({=${open}, }=${close})。请检查语法后再调用 setPolicy。`);
|
|
241455
|
+
}
|
|
241456
|
+
return trimmed;
|
|
241457
|
+
}
|
|
241458
|
+
function resolveAuthzConfigKey(extension) {
|
|
241459
|
+
return extension ? AUTHZ_PLATFORM_EXTENSION_REGO_KEY : AUTHZ_USER_REGO_KEY;
|
|
241460
|
+
}
|
|
239908
241461
|
/** Detect legacy function securityRule shapes that mean "public invoke". */
|
|
239909
241462
|
function isPublicFunctionInvokeRule(securityRule) {
|
|
239910
241463
|
if (!securityRule || securityRule.trim() === "") {
|
|
@@ -239976,29 +241529,38 @@ function resolveFunctionAuthzRegoInput(securityRule, resourceId) {
|
|
|
239976
241529
|
`2) permission="CUSTOM" with securityRule='{"invoke":true}' to generate a public-functions allow policy.\n` +
|
|
239977
241530
|
`See https://docs.cloudbase.net/cli-v1/policy/management`);
|
|
239978
241531
|
}
|
|
239979
|
-
async function
|
|
241532
|
+
async function describeEnvAuthzConfigByKey(cloudbase, key = AUTHZ_USER_REGO_KEY) {
|
|
239980
241533
|
if (!cloudbase?.permission?.describeEnvAuthzConfig) {
|
|
239981
241534
|
throw new Error("Current @cloudbase/manager-node does not expose permission.describeEnvAuthzConfig. Upgrade manager-node (>= 5.5.5) to align with CLI tcb policy get.");
|
|
239982
241535
|
}
|
|
239983
|
-
const result = await cloudbase.permission.describeEnvAuthzConfig({
|
|
239984
|
-
key: AUTHZ_USER_REGO_KEY,
|
|
239985
|
-
});
|
|
241536
|
+
const result = await cloudbase.permission.describeEnvAuthzConfig({ key });
|
|
239986
241537
|
const value = typeof result?.Item?.Value === "string"
|
|
239987
241538
|
? result.Item.Value
|
|
239988
241539
|
: typeof result?.Value === "string"
|
|
239989
241540
|
? result.Value
|
|
239990
241541
|
: "";
|
|
239991
|
-
return { value, raw: result };
|
|
241542
|
+
return { key, value, raw: result };
|
|
241543
|
+
}
|
|
241544
|
+
async function describeEnvAuthzUserRego(cloudbase) {
|
|
241545
|
+
const result = await describeEnvAuthzConfigByKey(cloudbase, AUTHZ_USER_REGO_KEY);
|
|
241546
|
+
return { value: result.value, raw: result.raw };
|
|
239992
241547
|
}
|
|
239993
241548
|
async function modifyEnvAuthzUserRego(cloudbase, value) {
|
|
239994
241549
|
if (!cloudbase?.permission?.modifyEnvAuthzConfig) {
|
|
239995
241550
|
throw new Error("Current @cloudbase/manager-node does not expose permission.modifyEnvAuthzConfig. Upgrade manager-node (>= 5.5.5) to align with CLI tcb policy set.");
|
|
239996
241551
|
}
|
|
241552
|
+
const validated = validateUserRegoContent(value);
|
|
239997
241553
|
return cloudbase.permission.modifyEnvAuthzConfig({
|
|
239998
241554
|
key: AUTHZ_USER_REGO_KEY,
|
|
239999
|
-
value,
|
|
241555
|
+
value: validated,
|
|
240000
241556
|
});
|
|
240001
241557
|
}
|
|
241558
|
+
async function describeResourcePolicyListAligned(cloudbase, policyResourceType) {
|
|
241559
|
+
if (!cloudbase?.permission?.describeResourcePolicyList) {
|
|
241560
|
+
throw new Error("Current @cloudbase/manager-node does not expose permission.describeResourcePolicyList. Upgrade manager-node (>= 5.5.5) to align with CLI tcb policy list.");
|
|
241561
|
+
}
|
|
241562
|
+
return cloudbase.permission.describeResourcePolicyList(policyResourceType ? { resourceType: policyResourceType } : undefined);
|
|
241563
|
+
}
|
|
240002
241564
|
async function describeResourcePermissionWithFunctionPgFallback(options) {
|
|
240003
241565
|
const { cloudbase, envId, resourceType, resources } = options;
|
|
240004
241566
|
try {
|
|
@@ -240232,7 +241794,7 @@ function registerPermissionTools(server) {
|
|
|
240232
241794
|
};
|
|
240233
241795
|
server.registerTool?.("queryPermissions", {
|
|
240234
241796
|
title: "查询 CloudBase 权限与用户配置",
|
|
240235
|
-
description: "查询 CloudBase
|
|
241797
|
+
description: "查询 CloudBase 权限与用户配置,支持查询资源权限(数据库/云函数/存储桶等)、角色列表/详情、应用用户列表/详情,以及网关 OPA 授权策略(对齐 CLI `tcb policy list/get`)。\n\n示例:\n- 查询存储桶权限:`action=\"getResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\"`\n- 列出旧网关策略:`action=\"listPolicy\"`(PG / OPA 引擎环境返回空列表,与 CLI 一致)\n- 读取用户 Rego:`action=\"getPolicy\"`;平台扩展策略:`action=\"getPolicy\", extension=true`\n\n📌 跨后端边界提示:调用前先用 `envQuery(action=\"info\", envId=...)` 看 `EnvInfo.RuntimeBackends`。`resourceType=\"noSqlDatabase\"` 查询的是 CloudBase NoSQL 集合规则,与 CloudBase PostgreSQL(PG)表的行级安全(RLS)是两套独立机制——同一个 PG 环境里 NoSQL 集合若仍在使用,对那些集合查询本工具结果**仍然有效**。要查 PG 表 RLS,请改用 `queryPgDatabase(action=\"sql\", sql=\"SELECT * FROM pg_policies WHERE tablename=...\")`。本工具不涉及 MySQL 权限。\n\n⚠️ PostgreSQL 环境:平台 `DescribeResourcePermission` 对 PG 环境会直接拒绝。当 `resourceType=\"function\"` 时,本工具会自动回退到 Manager SDK `describeEnvAuthzConfig`(与 CLI `tcb policy get` 一致,读取 `authz.user.rego`)。显式 OPA 策略请用 `listPolicy` / `getPolicy`。",
|
|
240236
241798
|
inputSchema: {
|
|
240237
241799
|
action: zod_1.z.enum(QUERY_PERMISSION_ACTIONS),
|
|
240238
241800
|
resourceType: zod_1.z
|
|
@@ -240247,13 +241809,21 @@ function registerPermissionTools(server) {
|
|
|
240247
241809
|
username: zod_1.z.string().optional(),
|
|
240248
241810
|
pageNo: zod_1.z.number().optional(),
|
|
240249
241811
|
pageSize: zod_1.z.number().optional(),
|
|
241812
|
+
extension: zod_1.z
|
|
241813
|
+
.boolean()
|
|
241814
|
+
.optional()
|
|
241815
|
+
.describe("仅 action=getPolicy。true=读取平台为该环境单独配置的策略(authz.platform.extension.rego),默认 false=用户策略(authz.user.rego),对齐 CLI `tcb policy get --extension`。"),
|
|
241816
|
+
policyResourceType: zod_1.z
|
|
241817
|
+
.enum(POLICY_LIST_RESOURCE_TYPES)
|
|
241818
|
+
.optional()
|
|
241819
|
+
.describe("仅 action=listPolicy。按资源类型过滤,当前仅支持 `policy`,对齐 CLI `tcb policy list --resource-type policy`。"),
|
|
240250
241820
|
},
|
|
240251
241821
|
annotations: {
|
|
240252
241822
|
readOnlyHint: true,
|
|
240253
241823
|
openWorldHint: true,
|
|
240254
241824
|
category: "permissions",
|
|
240255
241825
|
},
|
|
240256
|
-
}, async ({ action, resourceType, resourceId, resourceIds, roleId, roleIdentity, roleName, uid, username, pageNo, pageSize, }) => withEnvelope(async () => {
|
|
241826
|
+
}, async ({ action, resourceType, resourceId, resourceIds, roleId, roleIdentity, roleName, uid, username, pageNo, pageSize, extension, policyResourceType, }) => withEnvelope(async () => {
|
|
240257
241827
|
const envId = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
|
|
240258
241828
|
const cloudbase = await getManager();
|
|
240259
241829
|
switch (action) {
|
|
@@ -240399,11 +241969,43 @@ function registerPermissionTools(server) {
|
|
|
240399
241969
|
raw: result,
|
|
240400
241970
|
}, "应用用户详情查询成功");
|
|
240401
241971
|
}
|
|
241972
|
+
case "listPolicy": {
|
|
241973
|
+
const result = await describeResourcePolicyListAligned(cloudbase, policyResourceType);
|
|
241974
|
+
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
241975
|
+
const data = result
|
|
241976
|
+
?.Data;
|
|
241977
|
+
const policyList = data?.PolicyList ?? [];
|
|
241978
|
+
const total = data?.Total ?? policyList.length;
|
|
241979
|
+
return buildEnvelope({
|
|
241980
|
+
action,
|
|
241981
|
+
envId,
|
|
241982
|
+
policyResourceType: policyResourceType ?? null,
|
|
241983
|
+
policies: policyList,
|
|
241984
|
+
total,
|
|
241985
|
+
note: "PG 环境与 authz_engine=opa 的环境会返回空列表(与 CLI `tcb policy list` / SDK describeResourcePolicyList 一致)。读取用户 Rego 请用 action=getPolicy。",
|
|
241986
|
+
raw: result,
|
|
241987
|
+
}, "网关授权策略列表查询成功");
|
|
241988
|
+
}
|
|
241989
|
+
case "getPolicy": {
|
|
241990
|
+
const key = resolveAuthzConfigKey(extension);
|
|
241991
|
+
const result = await describeEnvAuthzConfigByKey(cloudbase, key);
|
|
241992
|
+
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result.raw);
|
|
241993
|
+
return buildEnvelope({
|
|
241994
|
+
action,
|
|
241995
|
+
envId,
|
|
241996
|
+
key: result.key,
|
|
241997
|
+
extension: Boolean(extension),
|
|
241998
|
+
rego: result.value,
|
|
241999
|
+
raw: result.raw,
|
|
242000
|
+
}, extension
|
|
242001
|
+
? "平台扩展 OPA 策略查询成功(authz.platform.extension.rego)"
|
|
242002
|
+
: "用户 OPA 策略查询成功(authz.user.rego)");
|
|
242003
|
+
}
|
|
240402
242004
|
}
|
|
240403
242005
|
}));
|
|
240404
242006
|
server.registerTool?.("managePermissions", {
|
|
240405
242007
|
title: "管理 CloudBase 权限与用户配置",
|
|
240406
|
-
description: "管理 CloudBase 权限与用户配置,支持修改资源权限(数据库/云函数/存储桶等)、角色管理、成员与策略增删、应用用户 CRUD
|
|
242008
|
+
description: "管理 CloudBase 权限与用户配置,支持修改资源权限(数据库/云函数/存储桶等)、角色管理、成员与策略增删、应用用户 CRUD,以及设置网关 OPA Rego 策略(对齐 CLI `tcb policy set`)。\n\n示例:\n- 设置存储桶为私有:`action=\"updateResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\", permission=\"PRIVATE\"`\n- 创建角色:`action=\"createRole\", roleName=\"admin\", roleIdentity=\"admin\"`\n- 放开云函数匿名/未登录访问(PG 会走 OPA,对齐 CLI `tcb policy set`):`action=\"updateResourcePermission\", resourceType=\"function\", resourceId=\"myFn\", permission=\"CUSTOM\", securityRule='{\"invoke\":true}'`\n- 直接设置用户 Rego:`action=\"setPolicy\", regoContent=\"package authz.user\\n\\ndefault allow := false\\n\", confirm=true`(⚠️ 立即禁用旧网关鉴权)\n\n注意:`createUser` / `updateUser` 是环境侧应用用户管理能力,适合测试账号、管理员或预置用户,不应替代浏览器里的 Web SDK 注册表单;前端用户名密码注册应使用 `auth.signUp({ username, password })`,登录应使用 `auth.signInWithPassword({ username, password })`。直接在浏览器里用 `auth.signUp` 创建用户名密码用户取决于 SDK/provider 支持,使用前必须验证;不支持时应走后端或管理端边界,不能在浏览器暴露密钥。`securityRule` 的详细语义取决于 `resourceType`:`doc._openid`、`auth.openid`、查询条件子集校验,以及 `create` / `update` / `delete` JSON 模板仅适用于 `resourceType=\"noSqlDatabase\"` 的文档数据库安全规则;配置 `function` 或 `storage` 时,请参考各自官方安全规则文档,而不是复用 NoSQL 模板。\n\n📌 跨后端边界提示:调用前先用 `envQuery(action=\"info\", envId=...)` 看 `EnvInfo.RuntimeBackends`:\n- `resourceType=\"noSqlDatabase\"` 仅作用于 CloudBase NoSQL 文档数据库的集合;CloudBase PostgreSQL(PG)表的行级权限**不**受它控制——PG 表请改用 RLS:`managePgDatabase(action=\"execute\", confirm=true)` 跑 `ALTER TABLE ... ENABLE ROW LEVEL SECURITY` 与 `CREATE POLICY ...`。同一个 PG 环境里如果还有 NoSQL 集合在用,对那些**集合**继续使用 `noSqlDatabase` 规则是正确的——不是\"PG 环境就禁用本工具\"。\n- `resourceType=\"storage\"` 控制的是 NoSQL/COS 存储桶 ACL;PG 的 `pgstore` bucket 不在此 `resourceType` 覆盖范围内。\n- 本工具不涉及 MySQL;MySQL 数据库权限请走 MySQL 自身的 GRANT/REVOKE 语句(通过 `manageMysqlDatabase`)。\n\n⚠️ PostgreSQL 环境:平台 `ModifyResourcePermission` 对 PG 环境会直接拒绝。当 `resourceType=\"function\"` 时,本工具会自动回退到 Manager SDK `modifyEnvAuthzConfig`(与 CLI `tcb policy set` 一致,写入 `authz.user.rego`)。`securityRule` 可传完整 Rego(`package authz.user`)或 `'{\"invoke\":true}'`(自动生成放通 anonymous/unauthenticated 调 functions 的策略)。设置 Rego 后旧网关鉴权会失效,行为与 CLI 相同。显式 OPA 策略请优先用 `action=\"setPolicy\"`。",
|
|
240407
242009
|
inputSchema: {
|
|
240408
242010
|
action: zod_1.z.enum(MANAGE_PERMISSION_ACTIONS),
|
|
240409
242011
|
resourceType: zod_1.z
|
|
@@ -240434,6 +242036,14 @@ function registerPermissionTools(server) {
|
|
|
240434
242036
|
username: zod_1.z.string().optional(),
|
|
240435
242037
|
password: zod_1.z.string().optional(),
|
|
240436
242038
|
userStatus: zod_1.z.enum(["ACTIVE", "BLOCKED"]).optional(),
|
|
242039
|
+
regoContent: zod_1.z
|
|
242040
|
+
.string()
|
|
242041
|
+
.optional()
|
|
242042
|
+
.describe("仅 action=setPolicy。用户 OPA Rego 全文,必须以 `package authz.user` 开头,对齐 CLI `tcb policy set <regoContent>`。"),
|
|
242043
|
+
confirm: zod_1.z
|
|
242044
|
+
.boolean()
|
|
242045
|
+
.optional()
|
|
242046
|
+
.describe("仅 action=setPolicy。设置 Rego 后会立即禁用旧网关鉴权,必须显式传 confirm=true(对齐 CLI 确认提示)。"),
|
|
240437
242047
|
},
|
|
240438
242048
|
annotations: {
|
|
240439
242049
|
readOnlyHint: false,
|
|
@@ -240442,7 +242052,7 @@ function registerPermissionTools(server) {
|
|
|
240442
242052
|
openWorldHint: true,
|
|
240443
242053
|
category: "permissions",
|
|
240444
242054
|
},
|
|
240445
|
-
}, async ({ action, resourceType, resourceId, permission, securityRule, roleId, roleIds, roleName, roleIdentity, description, memberUids, policies, policyIds, uid, uids, username, password, userStatus, }) => withEnvelope(async () => {
|
|
242055
|
+
}, async ({ action, resourceType, resourceId, permission, securityRule, roleId, roleIds, roleName, roleIdentity, description, memberUids, policies, policyIds, uid, uids, username, password, userStatus, regoContent, confirm, }) => withEnvelope(async () => {
|
|
240446
242056
|
const envId = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
|
|
240447
242057
|
const cloudbase = await getManager();
|
|
240448
242058
|
const normalizedPolicies = normalizeRecordArray(policies, "policies");
|
|
@@ -240623,6 +242233,26 @@ function registerPermissionTools(server) {
|
|
|
240623
242233
|
raw: result,
|
|
240624
242234
|
}, "应用用户删除成功");
|
|
240625
242235
|
}
|
|
242236
|
+
case "setPolicy": {
|
|
242237
|
+
if (confirm !== true) {
|
|
242238
|
+
throw new Error("action=setPolicy 会立即禁用旧网关鉴权(对齐 CLI `tcb policy set`),必须显式传 confirm=true。");
|
|
242239
|
+
}
|
|
242240
|
+
const validated = validateUserRegoContent(regoContent ?? "");
|
|
242241
|
+
const result = await modifyEnvAuthzUserRego(cloudbase, validated);
|
|
242242
|
+
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
242243
|
+
return buildEnvelope({
|
|
242244
|
+
action,
|
|
242245
|
+
envId,
|
|
242246
|
+
key: AUTHZ_USER_REGO_KEY,
|
|
242247
|
+
rego: validated,
|
|
242248
|
+
sideEffect: "Setting authz.user.rego immediately disables legacy gateway authorization.",
|
|
242249
|
+
nextSteps: [
|
|
242250
|
+
'queryPermissions(action="getPolicy")',
|
|
242251
|
+
'queryPermissions(action="listPolicy")',
|
|
242252
|
+
],
|
|
242253
|
+
raw: result,
|
|
242254
|
+
}, "用户 OPA Rego 策略设置成功(旧网关鉴权已失效)");
|
|
242255
|
+
}
|
|
240626
242256
|
}
|
|
240627
242257
|
}));
|
|
240628
242258
|
}
|
|
@@ -241231,7 +242861,7 @@ function registerSetupTools(server) {
|
|
|
241231
242861
|
server.registerTool("downloadTemplate", {
|
|
241232
242862
|
title: "下载项目模板",
|
|
241233
242863
|
description: `自动下载并部署CloudBase项目模板。
|
|
241234
|
-
**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.
|
|
242864
|
+
**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.28.1" : 0}),便于后续维护和版本追踪\n- 下载 rules 模板时,如果项目中已存在 README.md 文件,系统会自动保护该文件不被覆盖(除非设置 overwrite=true)`,
|
|
241235
242865
|
inputSchema: {
|
|
241236
242866
|
template: zod_1.z
|
|
241237
242867
|
.enum(["react", "vue", "miniprogram", "uniapp", "rules"])
|
|
@@ -264455,7 +266085,10 @@ function registerGatewayTools(server) {
|
|
|
264455
266085
|
};
|
|
264456
266086
|
const pickUsableDefaultDomain = (domains) => {
|
|
264457
266087
|
const candidates = domains.filter((item) => item.IsDefault === true && item.Domain);
|
|
264458
|
-
return (candidates.find((item) => item.Enable !== false &&
|
|
266088
|
+
return (candidates.find((item) => item.Enable !== false &&
|
|
266089
|
+
// Platform may return Success/success; normalize before matching.
|
|
266090
|
+
typeof item.Status === "string" &&
|
|
266091
|
+
item.Status.toLowerCase() === "success") ??
|
|
264459
266092
|
candidates.find((item) => item.Enable !== false) ??
|
|
264460
266093
|
candidates[0]);
|
|
264461
266094
|
};
|