@fastgpt-plugin/cli 0.2.0-alpha.1 → 0.2.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +2 -0
- package/README.md +2 -0
- package/dist/index.js +811 -76
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -46,6 +46,8 @@ fastgpt-plugin dev --no-interactive \
|
|
|
46
46
|
--connect "fpg_dbg_..."
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
After `--connect` starts and connects successfully, it overwrites the persisted connection key in local `config.json`, so later `fastgpt-plugin dev` runs can reuse it. In the TUI, press `c` to enter and save a new connection key. Stop the session with `Ctrl+C`; press `Ctrl+C` again to force exit.
|
|
50
|
+
|
|
49
51
|
When passing a raw connection key, set `FASTGPT_PLUGIN_DEBUG_CONNECT_URL`, or set `FASTGPT_PLUGIN_SERVER_URL` so the CLI can request `/api/plugin/debug-sessions/connection-key:exchange`. Full connect links remain supported for compatibility. The exchange result returns the gateway WSS endpoint, `debug:tmbId:{tmbId}` source, and scoped connect token. The CLI does not need `CONNECTION_GATEWAY_AUTH_TOKEN` or `JWT_SECRET`.
|
|
50
52
|
|
|
51
53
|
When no plugin directories are passed, `dev` auto-discovers plugins from the current directory. If the current directory has `index.ts`, it is used as the plugin entry; otherwise, the CLI scans one level of child directories for `index.ts`. You can still pass multiple plugin directories explicitly.
|
package/README.md
CHANGED
|
@@ -46,6 +46,8 @@ fastgpt-plugin dev --no-interactive \
|
|
|
46
46
|
--connect "fpg_dbg_..."
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
`--connect` 启动并成功连接后会覆盖本地持久配置 `config.json` 里的 connection key,后续 `fastgpt-plugin dev` 可直接复用该配置。TUI 运行中按 `c` 可以重新输入并保存 connection key;停止会话使用 `Ctrl+C`,再次按 `Ctrl+C` 强制退出。
|
|
50
|
+
|
|
49
51
|
使用裸 connection key 时,需要设置 `FASTGPT_PLUGIN_DEBUG_CONNECT_URL`,或设置 `FASTGPT_PLUGIN_SERVER_URL` 让 CLI 默认请求 `/api/plugin/debug-sessions/connection-key:exchange`。兼容场景仍可传入完整 connect link。exchange 结果会返回 gateway WSS 地址、`debug:tmbId:{tmbId}` source 和 scoped connect token。CLI 不需要 `CONNECTION_GATEWAY_AUTH_TOKEN` 或 `JWT_SECRET`。
|
|
50
52
|
|
|
51
53
|
`dev` 未传插件目录时会自动探测当前目录:如果当前目录有 `index.ts`,则把当前目录作为插件;否则扫描当前目录下一层子目录中的 `index.ts`。也可以手动传入多个插件目录。
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { registerHooks, stripTypeScriptTypes } from "node:module";
|
|
2
|
+
import { createRequire, registerHooks, stripTypeScriptTypes } from "node:module";
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -526,7 +526,7 @@ var CheckCommand = class extends BaseCommand {
|
|
|
526
526
|
//#endregion
|
|
527
527
|
//#region package.json
|
|
528
528
|
var name = "@fastgpt-plugin/cli";
|
|
529
|
-
var version = "0.2.0-alpha.
|
|
529
|
+
var version = "0.2.0-alpha.3";
|
|
530
530
|
|
|
531
531
|
//#endregion
|
|
532
532
|
//#region src/constants.ts
|
|
@@ -686,6 +686,40 @@ var CreateCommand = class extends BaseCommand {
|
|
|
686
686
|
}
|
|
687
687
|
};
|
|
688
688
|
|
|
689
|
+
//#endregion
|
|
690
|
+
//#region ../../packages/domain/src/value-objects/plugin-debug-session.vo.ts
|
|
691
|
+
const PLUGIN_DEBUG_SOURCE_KIND = "debug";
|
|
692
|
+
const PLUGIN_DEBUG_SESSION_SOURCE_TMB_ID_KEY = "tmbId";
|
|
693
|
+
const PLUGIN_DEBUG_SOURCE_PREFIX = `${PLUGIN_DEBUG_SOURCE_KIND}:`;
|
|
694
|
+
const PLUGIN_DEBUG_SESSION_SOURCE_FORMAT = `${PLUGIN_DEBUG_SOURCE_KIND}:${PLUGIN_DEBUG_SESSION_SOURCE_TMB_ID_KEY}:{tmbId}`;
|
|
695
|
+
const PluginDebugSessionIdSchema = z.string().min(1);
|
|
696
|
+
const PluginDebugSessionTmbIdSchema = z.string().min(1);
|
|
697
|
+
const PluginDebugSessionSourceSchema = z.string().min(1).refine((source) => parsePluginDebugSessionSource(source) !== null, { message: "Invalid plugin debug session source" });
|
|
698
|
+
const PluginDebugSessionStatusSchema = z.enum([
|
|
699
|
+
"enabled",
|
|
700
|
+
"connected",
|
|
701
|
+
"disconnected",
|
|
702
|
+
"revoked"
|
|
703
|
+
]);
|
|
704
|
+
const PluginDebugSessionSchema = z.object({
|
|
705
|
+
tmbId: PluginDebugSessionTmbIdSchema,
|
|
706
|
+
source: PluginDebugSessionSourceSchema,
|
|
707
|
+
status: PluginDebugSessionStatusSchema,
|
|
708
|
+
enabled: z.boolean(),
|
|
709
|
+
keyId: z.string().min(1),
|
|
710
|
+
connectionKeyHash: z.string().min(1),
|
|
711
|
+
connectionKey: z.string().min(1).optional(),
|
|
712
|
+
createdAt: z.number().int().positive(),
|
|
713
|
+
updatedAt: z.number().int().positive(),
|
|
714
|
+
refreshedAt: z.number().int().positive().optional(),
|
|
715
|
+
revokedAt: z.number().int().positive().optional()
|
|
716
|
+
});
|
|
717
|
+
function parsePluginDebugSessionSource(source) {
|
|
718
|
+
const parts = source.split(":");
|
|
719
|
+
if (parts.length !== 3 || parts[0] !== PLUGIN_DEBUG_SOURCE_KIND || parts[1] !== PLUGIN_DEBUG_SESSION_SOURCE_TMB_ID_KEY || !parts[2]) return null;
|
|
720
|
+
return { tmbId: parts[2] };
|
|
721
|
+
}
|
|
722
|
+
|
|
689
723
|
//#endregion
|
|
690
724
|
//#region src/debug/discover.ts
|
|
691
725
|
const IGNORED_DIRS = new Set([
|
|
@@ -777,6 +811,12 @@ const ConnectionGatewayWsHeartbeatMessageSchema = z.object({
|
|
|
777
811
|
type: z.literal("heartbeat"),
|
|
778
812
|
ts: z.number().int().positive()
|
|
779
813
|
});
|
|
814
|
+
const ConnectionGatewayWsMetadataMessageSchema = z.object({
|
|
815
|
+
protocol: ConnectionGatewayWsProtocolSchema,
|
|
816
|
+
type: z.literal("metadata"),
|
|
817
|
+
requestId: z.string().min(1).optional(),
|
|
818
|
+
metadata: z.record(z.string(), z.unknown())
|
|
819
|
+
});
|
|
780
820
|
const ConnectionGatewayWsErrorMessageSchema = z.object({
|
|
781
821
|
protocol: ConnectionGatewayWsProtocolSchema,
|
|
782
822
|
type: z.literal("error"),
|
|
@@ -793,7 +833,8 @@ const ConnectionGatewayWsBoundMessageSchema = z.object({
|
|
|
793
833
|
const ConnectionGatewayWsClientMessageSchema = z.discriminatedUnion("type", [
|
|
794
834
|
ConnectionGatewayWsBindMessageSchema,
|
|
795
835
|
ConnectionGatewayWsEnvelopeMessageSchema,
|
|
796
|
-
ConnectionGatewayWsHeartbeatMessageSchema
|
|
836
|
+
ConnectionGatewayWsHeartbeatMessageSchema,
|
|
837
|
+
ConnectionGatewayWsMetadataMessageSchema
|
|
797
838
|
]);
|
|
798
839
|
const ConnectionGatewayWsServerMessageSchema = z.discriminatedUnion("type", [
|
|
799
840
|
ConnectionGatewayWsBoundMessageSchema,
|
|
@@ -997,7 +1038,11 @@ const RESOLVE_EXTENSIONS = [
|
|
|
997
1038
|
".cjs",
|
|
998
1039
|
".json"
|
|
999
1040
|
];
|
|
1041
|
+
const TRANSFORM_PROBE_SOURCE = "const __fastgptPluginTransformProbe__: number = 1;";
|
|
1000
1042
|
let hooksRegistered = false;
|
|
1043
|
+
let transformModeSupported;
|
|
1044
|
+
let cachedTypeScriptCompiler;
|
|
1045
|
+
const localRequire = createRequire(import.meta.url);
|
|
1001
1046
|
function ensureDebugImportHooks() {
|
|
1002
1047
|
if (hooksRegistered) return;
|
|
1003
1048
|
hooksRegistered = true;
|
|
@@ -1021,15 +1066,90 @@ function ensureDebugImportHooks() {
|
|
|
1021
1066
|
if (SOURCE_EXTENSIONS.includes(ext)) return {
|
|
1022
1067
|
format: "module",
|
|
1023
1068
|
shortCircuit: true,
|
|
1024
|
-
source:
|
|
1025
|
-
|
|
1026
|
-
|
|
1069
|
+
source: compileTypeScriptModuleSource(fs$1.readFileSync(fileURLToPath(parsed), "utf-8"), {
|
|
1070
|
+
filePath: fileURLToPath(parsed),
|
|
1071
|
+
workspaceRoot
|
|
1027
1072
|
})
|
|
1028
1073
|
};
|
|
1029
1074
|
return nextLoad(url, context);
|
|
1030
1075
|
}
|
|
1031
1076
|
});
|
|
1032
1077
|
}
|
|
1078
|
+
function compileTypeScriptModuleSource(sourceText, { filePath, workspaceRoot, stripTypeScriptTypesFn = stripTypeScriptTypes, typeScriptCompiler }) {
|
|
1079
|
+
if (supportsTransformMode(stripTypeScriptTypesFn)) try {
|
|
1080
|
+
return stripTypeScriptTypesFn(sourceText, {
|
|
1081
|
+
mode: "transform",
|
|
1082
|
+
sourceMap: false
|
|
1083
|
+
});
|
|
1084
|
+
} catch {}
|
|
1085
|
+
return transpileTypeScriptModuleSource(sourceText, {
|
|
1086
|
+
filePath,
|
|
1087
|
+
workspaceRoot,
|
|
1088
|
+
typeScriptCompiler
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
function supportsTransformMode(stripTypeScriptTypesFn) {
|
|
1092
|
+
if (stripTypeScriptTypesFn === stripTypeScriptTypes) {
|
|
1093
|
+
if (transformModeSupported !== void 0) return transformModeSupported;
|
|
1094
|
+
transformModeSupported = canTransformTypeScript(stripTypeScriptTypesFn);
|
|
1095
|
+
return transformModeSupported;
|
|
1096
|
+
}
|
|
1097
|
+
return canTransformTypeScript(stripTypeScriptTypesFn);
|
|
1098
|
+
}
|
|
1099
|
+
function canTransformTypeScript(stripTypeScriptTypesFn) {
|
|
1100
|
+
try {
|
|
1101
|
+
stripTypeScriptTypesFn(TRANSFORM_PROBE_SOURCE, {
|
|
1102
|
+
mode: "transform",
|
|
1103
|
+
sourceMap: false
|
|
1104
|
+
});
|
|
1105
|
+
return true;
|
|
1106
|
+
} catch {
|
|
1107
|
+
return false;
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
function transpileTypeScriptModuleSource(sourceText, { filePath, workspaceRoot, typeScriptCompiler }) {
|
|
1111
|
+
const compiler = typeScriptCompiler ?? loadTypeScriptCompiler(filePath, workspaceRoot);
|
|
1112
|
+
const transpiled = compiler.transpileModule(sourceText, {
|
|
1113
|
+
fileName: filePath,
|
|
1114
|
+
reportDiagnostics: true,
|
|
1115
|
+
compilerOptions: {
|
|
1116
|
+
module: compiler.ModuleKind.ESNext,
|
|
1117
|
+
target: compiler.ScriptTarget.ES2022,
|
|
1118
|
+
moduleResolution: compiler.ModuleResolutionKind.Bundler,
|
|
1119
|
+
jsx: compiler.JsxEmit.ReactJSX,
|
|
1120
|
+
sourceMap: false,
|
|
1121
|
+
inlineSourceMap: false,
|
|
1122
|
+
inlineSources: false,
|
|
1123
|
+
isolatedModules: true,
|
|
1124
|
+
esModuleInterop: true,
|
|
1125
|
+
allowImportingTsExtensions: true,
|
|
1126
|
+
verbatimModuleSyntax: true
|
|
1127
|
+
}
|
|
1128
|
+
});
|
|
1129
|
+
const errors = transpiled.diagnostics?.filter((diagnostic) => diagnostic.category === compiler.DiagnosticCategory.Error) ?? [];
|
|
1130
|
+
if (errors.length > 0) {
|
|
1131
|
+
const formatted = errors.map((diagnostic) => formatDiagnosticMessage(compiler, diagnostic)).join("\n");
|
|
1132
|
+
throw new Error(`Failed to transpile TypeScript debug module: ${filePath}\n${formatted}`);
|
|
1133
|
+
}
|
|
1134
|
+
return transpiled.outputText;
|
|
1135
|
+
}
|
|
1136
|
+
function loadTypeScriptCompiler(filePath, workspaceRoot) {
|
|
1137
|
+
if (cachedTypeScriptCompiler) return cachedTypeScriptCompiler;
|
|
1138
|
+
const resolvePaths = [
|
|
1139
|
+
path.dirname(filePath),
|
|
1140
|
+
process.cwd(),
|
|
1141
|
+
workspaceRoot,
|
|
1142
|
+
path.dirname(fileURLToPath(import.meta.url))
|
|
1143
|
+
].filter((value) => Boolean(value));
|
|
1144
|
+
cachedTypeScriptCompiler = localRequire(localRequire.resolve("typescript", { paths: resolvePaths }));
|
|
1145
|
+
return cachedTypeScriptCompiler;
|
|
1146
|
+
}
|
|
1147
|
+
function formatDiagnosticMessage(compiler, diagnostic) {
|
|
1148
|
+
const message = compiler.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
|
1149
|
+
if (!diagnostic.file || diagnostic.start === void 0) return message;
|
|
1150
|
+
const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
|
|
1151
|
+
return `${line + 1}:${character + 1} ${message}`;
|
|
1152
|
+
}
|
|
1033
1153
|
function resolveSpecifier({ specifier, parentURL, workspaceRoot }) {
|
|
1034
1154
|
if (specifier.startsWith("node:")) return null;
|
|
1035
1155
|
if (specifier.startsWith("file:")) return toFileURL(resolveExistingPath(fileURLToPath(new URL(specifier))));
|
|
@@ -1191,9 +1311,188 @@ const InvokeMethodEnumSchema = z.enum([
|
|
|
1191
1311
|
]);
|
|
1192
1312
|
const InvokeMethodEnum = InvokeMethodEnumSchema.enum;
|
|
1193
1313
|
|
|
1314
|
+
//#endregion
|
|
1315
|
+
//#region ../../packages/domain/src/value-objects/error.vo.ts
|
|
1316
|
+
const DEFAULT_INTERNAL_REASON = {
|
|
1317
|
+
en: "Internal Server Error",
|
|
1318
|
+
"zh-CN": "服务器内部错误"
|
|
1319
|
+
};
|
|
1320
|
+
var RegisteredError = class extends Error {
|
|
1321
|
+
code;
|
|
1322
|
+
reason;
|
|
1323
|
+
httpStatus;
|
|
1324
|
+
telemetryKind;
|
|
1325
|
+
visibility;
|
|
1326
|
+
severity;
|
|
1327
|
+
data;
|
|
1328
|
+
constructor(definition, options = {}) {
|
|
1329
|
+
super(options.message ?? definition.message, { cause: normalizeErrorCause(options.cause) });
|
|
1330
|
+
this.name = "RegisteredError";
|
|
1331
|
+
this.code = definition.code;
|
|
1332
|
+
this.reason = options.reason ?? definition.reason;
|
|
1333
|
+
this.httpStatus = definition.httpStatus ?? 500;
|
|
1334
|
+
this.telemetryKind = definition.telemetryKind;
|
|
1335
|
+
this.visibility = definition.visibility ?? "public";
|
|
1336
|
+
this.severity = definition.severity ?? "expected";
|
|
1337
|
+
this.data = options.data;
|
|
1338
|
+
}
|
|
1339
|
+
};
|
|
1340
|
+
function createReasonError(reason, options = {}) {
|
|
1341
|
+
const cause = normalizeErrorCause(options.cause);
|
|
1342
|
+
const error = cause === void 0 ? new Error(reason.en) : new Error(reason.en, { cause });
|
|
1343
|
+
return Object.assign(error, { reason });
|
|
1344
|
+
}
|
|
1345
|
+
function normalizeToError(error, fallbackMessage = "Unknown error") {
|
|
1346
|
+
if (error instanceof Error) return error;
|
|
1347
|
+
if (typeof error === "string") return new Error(error);
|
|
1348
|
+
if (isI18nString(error)) return createReasonError(error);
|
|
1349
|
+
if (isResultFailureLike(error)) {
|
|
1350
|
+
const reason = isI18nString(error.reason) ? error.reason : void 0;
|
|
1351
|
+
const cause = normalizeToError(error.error, reason?.en ?? fallbackMessage);
|
|
1352
|
+
if (reason && isSameI18nString(reason, getErrorReason(cause))) return cause;
|
|
1353
|
+
return reason ? createReasonError(reason, { cause }) : new Error(fallbackMessage, { cause });
|
|
1354
|
+
}
|
|
1355
|
+
return new Error(getUnknownErrorMessage(error, fallbackMessage), { cause: error });
|
|
1356
|
+
}
|
|
1357
|
+
function getErrorReason(error, fallback) {
|
|
1358
|
+
if (error instanceof RegisteredError) return error.visibility === "internal" ? DEFAULT_INTERNAL_REASON : error.reason;
|
|
1359
|
+
if (isI18nString(error)) return error;
|
|
1360
|
+
const record = isRecord(error) ? error : void 0;
|
|
1361
|
+
if (record && isI18nString(record.reason)) return record.reason;
|
|
1362
|
+
if (record && isI18nString(record.error)) return record.error;
|
|
1363
|
+
if (fallback) return fallback;
|
|
1364
|
+
if (error instanceof Error) return {
|
|
1365
|
+
en: error.message,
|
|
1366
|
+
"zh-CN": error.message
|
|
1367
|
+
};
|
|
1368
|
+
if (typeof error === "string") return {
|
|
1369
|
+
en: error,
|
|
1370
|
+
"zh-CN": error
|
|
1371
|
+
};
|
|
1372
|
+
return DEFAULT_INTERNAL_REASON;
|
|
1373
|
+
}
|
|
1374
|
+
function serializeError(error, options) {
|
|
1375
|
+
return serializeErrorInner(error, {
|
|
1376
|
+
includeStack: options?.includeStack ?? false,
|
|
1377
|
+
depth: 0,
|
|
1378
|
+
seen: /* @__PURE__ */ new WeakSet()
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
function normalizeErrorCause(cause) {
|
|
1382
|
+
if (cause === void 0 || cause instanceof Error) return cause;
|
|
1383
|
+
if (typeof cause === "string") return new Error(cause);
|
|
1384
|
+
if (isI18nString(cause)) return createReasonError(cause);
|
|
1385
|
+
if (isResultFailureLike(cause)) return normalizeToError(cause);
|
|
1386
|
+
return cause;
|
|
1387
|
+
}
|
|
1388
|
+
function serializeErrorInner(error, state) {
|
|
1389
|
+
if (state.depth > 5) return {
|
|
1390
|
+
name: "Error",
|
|
1391
|
+
message: "Error cause depth exceeded"
|
|
1392
|
+
};
|
|
1393
|
+
if (error instanceof RegisteredError) return {
|
|
1394
|
+
name: error.name,
|
|
1395
|
+
code: error.code,
|
|
1396
|
+
message: error.message,
|
|
1397
|
+
reason: error.reason,
|
|
1398
|
+
httpStatus: error.httpStatus,
|
|
1399
|
+
telemetryKind: error.telemetryKind,
|
|
1400
|
+
visibility: error.visibility,
|
|
1401
|
+
data: error.data,
|
|
1402
|
+
...state.includeStack && error.stack !== void 0 ? { stack: error.stack } : {},
|
|
1403
|
+
...error.cause !== void 0 ? { cause: serializeErrorInner(error.cause, nextSerializeState(state, error)) } : {}
|
|
1404
|
+
};
|
|
1405
|
+
if (error instanceof Error) {
|
|
1406
|
+
const reason = getI18nStringField(error, "reason");
|
|
1407
|
+
return {
|
|
1408
|
+
name: error.name,
|
|
1409
|
+
code: getStringField(error, "code"),
|
|
1410
|
+
message: error.message,
|
|
1411
|
+
...reason !== void 0 ? { reason } : {},
|
|
1412
|
+
data: getUnknownField(error, "data"),
|
|
1413
|
+
...state.includeStack && error.stack !== void 0 ? { stack: error.stack } : {},
|
|
1414
|
+
...error.cause !== void 0 ? { cause: serializeErrorInner(error.cause, nextSerializeState(state, error)) } : {}
|
|
1415
|
+
};
|
|
1416
|
+
}
|
|
1417
|
+
if (typeof error === "string") return {
|
|
1418
|
+
name: "Error",
|
|
1419
|
+
message: error
|
|
1420
|
+
};
|
|
1421
|
+
return {
|
|
1422
|
+
name: "Error",
|
|
1423
|
+
message: getUnknownErrorMessage(error, "Unknown error")
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
function nextSerializeState(state, error) {
|
|
1427
|
+
if (state.seen.has(error)) return {
|
|
1428
|
+
...state,
|
|
1429
|
+
depth: 6
|
|
1430
|
+
};
|
|
1431
|
+
state.seen.add(error);
|
|
1432
|
+
return {
|
|
1433
|
+
...state,
|
|
1434
|
+
depth: state.depth + 1
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
function getUnknownErrorMessage(error, fallback) {
|
|
1438
|
+
if (error === null || error === void 0) return fallback;
|
|
1439
|
+
if (isRecord(error)) {
|
|
1440
|
+
const message = error.message ?? error.msg;
|
|
1441
|
+
if (typeof message === "string" && message.length > 0) return message;
|
|
1442
|
+
}
|
|
1443
|
+
return String(error);
|
|
1444
|
+
}
|
|
1445
|
+
function getStringField(error, field) {
|
|
1446
|
+
const value = error[field];
|
|
1447
|
+
return typeof value === "string" ? value : void 0;
|
|
1448
|
+
}
|
|
1449
|
+
function getI18nStringField(error, field) {
|
|
1450
|
+
const value = error[field];
|
|
1451
|
+
return isI18nString(value) ? value : void 0;
|
|
1452
|
+
}
|
|
1453
|
+
function getUnknownField(error, field) {
|
|
1454
|
+
return error[field];
|
|
1455
|
+
}
|
|
1456
|
+
function isI18nString(value) {
|
|
1457
|
+
return isRecord(value) && typeof value.en === "string" && (value["zh-CN"] === void 0 || typeof value["zh-CN"] === "string") && (value["zh-Hant"] === void 0 || typeof value["zh-Hant"] === "string");
|
|
1458
|
+
}
|
|
1459
|
+
function isSameI18nString(left, right) {
|
|
1460
|
+
return left.en === right.en && left["zh-CN"] === right["zh-CN"] && left["zh-Hant"] === right["zh-Hant"];
|
|
1461
|
+
}
|
|
1462
|
+
function isRecord(value) {
|
|
1463
|
+
return Boolean(value && typeof value === "object");
|
|
1464
|
+
}
|
|
1465
|
+
function isResultFailureLike(value) {
|
|
1466
|
+
return isRecord(value) && "error" in value && ("reason" in value || isRecord(value.error));
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1194
1469
|
//#endregion
|
|
1195
1470
|
//#region ../../packages/domain/src/value-objects/result.vo.ts
|
|
1196
1471
|
const successResult = (data) => [data, null];
|
|
1472
|
+
function failureResult(reasonOrFailure, error) {
|
|
1473
|
+
if (isResultFailure(reasonOrFailure)) return [null, toResultFailure(reasonOrFailure.reason, reasonOrFailure.error, reasonOrFailure)];
|
|
1474
|
+
if (reasonOrFailure instanceof Error) return [null, toResultFailure(getErrorReason(reasonOrFailure), reasonOrFailure)];
|
|
1475
|
+
return [null, toResultFailure(reasonOrFailure, error === void 0 ? createReasonError(reasonOrFailure) : createLegacyFailureError(reasonOrFailure, error))];
|
|
1476
|
+
}
|
|
1477
|
+
function isResultFailure(value) {
|
|
1478
|
+
return Boolean(value && typeof value === "object" && "reason" in value && "error" in value);
|
|
1479
|
+
}
|
|
1480
|
+
function createLegacyFailureError(reason, cause) {
|
|
1481
|
+
return createReasonError(reason, { cause: normalizeToError(cause, reason.en) });
|
|
1482
|
+
}
|
|
1483
|
+
function toResultFailure(reason, error, existing) {
|
|
1484
|
+
const serialized = serializeError(error);
|
|
1485
|
+
const code = existing?.code ?? serialized.code;
|
|
1486
|
+
const message = existing?.message ?? serialized.message;
|
|
1487
|
+
const data = existing && "data" in existing ? existing.data : serialized.data;
|
|
1488
|
+
return {
|
|
1489
|
+
reason,
|
|
1490
|
+
error,
|
|
1491
|
+
...code !== void 0 ? { code } : {},
|
|
1492
|
+
...message !== void 0 ? { message } : {},
|
|
1493
|
+
...data !== void 0 ? { data } : {}
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1197
1496
|
|
|
1198
1497
|
//#endregion
|
|
1199
1498
|
//#region ../../packages/domain/src/value-objects/stream.vo.ts
|
|
@@ -1690,7 +1989,7 @@ function createIncomingStream({ source, streamName, streamId, traceId, meta }) {
|
|
|
1690
1989
|
};
|
|
1691
1990
|
}
|
|
1692
1991
|
function toStreamData(source) {
|
|
1693
|
-
if (isStreamDataLike$
|
|
1992
|
+
if (isStreamDataLike$2(source)) return source;
|
|
1694
1993
|
const output = StreamData.create();
|
|
1695
1994
|
(async () => {
|
|
1696
1995
|
try {
|
|
@@ -1702,7 +2001,7 @@ function toStreamData(source) {
|
|
|
1702
2001
|
})();
|
|
1703
2002
|
return output;
|
|
1704
2003
|
}
|
|
1705
|
-
function isStreamDataLike$
|
|
2004
|
+
function isStreamDataLike$2(source) {
|
|
1706
2005
|
return source instanceof StreamData || typeof source === "object" && source !== null && typeof source.values === "function" && typeof source.consume === "function";
|
|
1707
2006
|
}
|
|
1708
2007
|
|
|
@@ -1737,7 +2036,7 @@ async function loadDebugSession({ entryDir, uploadDir }) {
|
|
|
1737
2036
|
else process.env.RUNTIME_MODE = previousRuntimeMode;
|
|
1738
2037
|
}
|
|
1739
2038
|
}
|
|
1740
|
-
async function runDebugTool({ runtime, snapshot, toolId, input, secrets, systemVar }) {
|
|
2039
|
+
async function runDebugTool({ runtime, snapshot, toolId, input, secrets, systemVar, traceId }) {
|
|
1741
2040
|
const targetTool = pickTargetTool(snapshot, toolId);
|
|
1742
2041
|
const mergedSystemVar = createDebugSystemVar(snapshot, targetTool.id, systemVar);
|
|
1743
2042
|
const response = await runtime.invokePlugin("run", {
|
|
@@ -1745,7 +2044,7 @@ async function runDebugTool({ runtime, snapshot, toolId, input, secrets, systemV
|
|
|
1745
2044
|
systemVar: mergedSystemVar,
|
|
1746
2045
|
...snapshot.isToolSet ? { childId: targetTool.id } : {},
|
|
1747
2046
|
...secrets ? { secrets } : {}
|
|
1748
|
-
}, { traceId: randomUUID() });
|
|
2047
|
+
}, { traceId: traceId ?? randomUUID() });
|
|
1749
2048
|
if (!response.output) throw new Error("调试运行未返回输出流。");
|
|
1750
2049
|
const streamMessages = [];
|
|
1751
2050
|
let finalResponse;
|
|
@@ -1805,7 +2104,7 @@ function createDebugSnapshot(tool, location) {
|
|
|
1805
2104
|
author: getOptionalString(userManifest.author),
|
|
1806
2105
|
tags: toStringArray(userManifest.tags),
|
|
1807
2106
|
permissions: toStringArray(userManifest.permission),
|
|
1808
|
-
secretSchema: ensurePlainObject(secretSchema),
|
|
2107
|
+
secretSchema: ensurePlainObject$1(secretSchema),
|
|
1809
2108
|
isToolSet: children.length > 0,
|
|
1810
2109
|
tools
|
|
1811
2110
|
};
|
|
@@ -1818,8 +2117,8 @@ function createSingleToolSnapshot(tool, userManifest) {
|
|
|
1818
2117
|
name: pickLocalizedText(userManifest.name),
|
|
1819
2118
|
description: pickLocalizedText(userManifest.description),
|
|
1820
2119
|
toolDescription: pickToolDescription(getOptionalString(userManifest.toolDescription), userManifest.description),
|
|
1821
|
-
inputSchema: ensurePlainObject(z.toJSONSchema(handler.inputSchema)),
|
|
1822
|
-
outputSchema: ensurePlainObject(z.toJSONSchema(handler.outputSchema))
|
|
2120
|
+
inputSchema: ensurePlainObject$1(z.toJSONSchema(handler.inputSchema)),
|
|
2121
|
+
outputSchema: ensurePlainObject$1(z.toJSONSchema(handler.outputSchema))
|
|
1823
2122
|
};
|
|
1824
2123
|
}
|
|
1825
2124
|
function createChildToolSnapshot(tool, child) {
|
|
@@ -1830,8 +2129,8 @@ function createChildToolSnapshot(tool, child) {
|
|
|
1830
2129
|
name: pickLocalizedText(child.name),
|
|
1831
2130
|
description: pickLocalizedText(child.description),
|
|
1832
2131
|
toolDescription: pickToolDescription(child.toolDescription, child.description),
|
|
1833
|
-
inputSchema: ensurePlainObject(z.toJSONSchema(handler.inputSchema)),
|
|
1834
|
-
outputSchema: ensurePlainObject(z.toJSONSchema(handler.outputSchema))
|
|
2132
|
+
inputSchema: ensurePlainObject$1(z.toJSONSchema(handler.inputSchema)),
|
|
2133
|
+
outputSchema: ensurePlainObject$1(z.toJSONSchema(handler.outputSchema))
|
|
1835
2134
|
};
|
|
1836
2135
|
}
|
|
1837
2136
|
function pickTargetTool(snapshot, toolId) {
|
|
@@ -1843,10 +2142,10 @@ function pickTargetTool(snapshot, toolId) {
|
|
|
1843
2142
|
}
|
|
1844
2143
|
function createVirtualHostHandler(uploadDir) {
|
|
1845
2144
|
return async ({ method, args, input }) => {
|
|
1846
|
-
const invokeArgs = ensurePlainObject(args);
|
|
2145
|
+
const invokeArgs = ensurePlainObject$1(args);
|
|
1847
2146
|
switch (method) {
|
|
1848
2147
|
case InvokeMethodEnum.uploadFile: {
|
|
1849
|
-
const buffer = await readSourceToBuffer(input);
|
|
2148
|
+
const buffer = await readSourceToBuffer$1(input);
|
|
1850
2149
|
const fileName = sanitizeFileName(typeof invokeArgs.fileName === "string" && invokeArgs.fileName.trim().length > 0 ? invokeArgs.fileName : "debug-upload.bin");
|
|
1851
2150
|
const contentType = typeof invokeArgs.contentType === "string" && invokeArgs.contentType.trim().length > 0 ? invokeArgs.contentType : "application/octet-stream";
|
|
1852
2151
|
const saveDir = path.resolve(uploadDir);
|
|
@@ -1866,17 +2165,17 @@ function createVirtualHostHandler(uploadDir) {
|
|
|
1866
2165
|
}
|
|
1867
2166
|
};
|
|
1868
2167
|
}
|
|
1869
|
-
async function readSourceToBuffer(source) {
|
|
2168
|
+
async function readSourceToBuffer$1(source) {
|
|
1870
2169
|
if (!source) return Buffer.alloc(0);
|
|
1871
2170
|
const chunks = [];
|
|
1872
|
-
const iterable = isStreamDataLike(source) ? source.values() : source;
|
|
1873
|
-
for await (const chunk of iterable) chunks.push(toBuffer(chunk));
|
|
2171
|
+
const iterable = isStreamDataLike$1(source) ? source.values() : source;
|
|
2172
|
+
for await (const chunk of iterable) chunks.push(toBuffer$1(chunk));
|
|
1874
2173
|
return Buffer.concat(chunks);
|
|
1875
2174
|
}
|
|
1876
|
-
function isStreamDataLike(source) {
|
|
2175
|
+
function isStreamDataLike$1(source) {
|
|
1877
2176
|
return source instanceof StreamData || typeof source === "object" && source !== null && typeof source.values === "function" && typeof source.consume === "function";
|
|
1878
2177
|
}
|
|
1879
|
-
function toBuffer(value) {
|
|
2178
|
+
function toBuffer$1(value) {
|
|
1880
2179
|
if (Buffer.isBuffer(value)) return value;
|
|
1881
2180
|
if (value instanceof Uint8Array) return Buffer.from(value);
|
|
1882
2181
|
if (typeof value === "string") return Buffer.from(value);
|
|
@@ -1914,7 +2213,7 @@ function toStringArray(value) {
|
|
|
1914
2213
|
const items = value.filter((item) => typeof item === "string");
|
|
1915
2214
|
return items.length > 0 ? items : void 0;
|
|
1916
2215
|
}
|
|
1917
|
-
function ensurePlainObject(value) {
|
|
2216
|
+
function ensurePlainObject$1(value) {
|
|
1918
2217
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1919
2218
|
}
|
|
1920
2219
|
function sanitizeFileName(fileName) {
|
|
@@ -1945,6 +2244,8 @@ async function assertPathExists(targetPath, message) {
|
|
|
1945
2244
|
|
|
1946
2245
|
//#endregion
|
|
1947
2246
|
//#region src/debug/gateway.ts
|
|
2247
|
+
const GATEWAY_DUPLICATE_CONNECTION_CODE = "connection_gateway.session_already_bound";
|
|
2248
|
+
const GATEWAY_HEARTBEAT_INTERVAL_MS = 5e3;
|
|
1948
2249
|
async function connectDebugGateway({ targets, options, onLog }) {
|
|
1949
2250
|
if (targets.length === 0) throw new Error("Gateway debug targets cannot be empty");
|
|
1950
2251
|
if (options.reconnect) return connectReconnectingDebugGateway({
|
|
@@ -1963,19 +2264,32 @@ async function connectReconnectingDebugGateway({ targets, options, onLog }) {
|
|
|
1963
2264
|
let current = null;
|
|
1964
2265
|
let currentSession = null;
|
|
1965
2266
|
let latestTargets = targets;
|
|
2267
|
+
let hasAttemptedConnection = false;
|
|
2268
|
+
let currentOptions = options;
|
|
2269
|
+
const resolveReconnectOptions = options.resolveReconnectOptions;
|
|
1966
2270
|
const intervalMs = Math.max(100, options.reconnectIntervalMs ?? 2e3);
|
|
1967
2271
|
const closedPromise = (async () => {
|
|
1968
2272
|
while (!closed) {
|
|
1969
2273
|
try {
|
|
2274
|
+
const attemptOptions = hasAttemptedConnection && resolveReconnectOptions ? await resolveReconnectOptions() : currentOptions;
|
|
2275
|
+
hasAttemptedConnection = true;
|
|
2276
|
+
currentOptions = {
|
|
2277
|
+
...attemptOptions,
|
|
2278
|
+
resolveReconnectOptions
|
|
2279
|
+
};
|
|
1970
2280
|
current = await connectSingleDebugGateway({
|
|
1971
2281
|
targets: latestTargets,
|
|
1972
|
-
options,
|
|
2282
|
+
options: currentOptions,
|
|
1973
2283
|
onLog
|
|
1974
2284
|
});
|
|
1975
2285
|
currentSession = current.session;
|
|
1976
2286
|
await current.closed;
|
|
1977
2287
|
} catch (error) {
|
|
1978
2288
|
if (closed) return;
|
|
2289
|
+
if (isDuplicateConnectionError(error)) {
|
|
2290
|
+
closed = true;
|
|
2291
|
+
throw error;
|
|
2292
|
+
}
|
|
1979
2293
|
onLog?.(`Connection Gateway 调试通道断开: ${formatErrorMessage(error)}`);
|
|
1980
2294
|
} finally {
|
|
1981
2295
|
current = null;
|
|
@@ -1994,6 +2308,9 @@ async function connectReconnectingDebugGateway({ targets, options, onLog }) {
|
|
|
1994
2308
|
latestTargets = targets;
|
|
1995
2309
|
current?.updateTargets(targets);
|
|
1996
2310
|
},
|
|
2311
|
+
reconnect() {
|
|
2312
|
+
current?.close();
|
|
2313
|
+
},
|
|
1997
2314
|
close() {
|
|
1998
2315
|
closed = true;
|
|
1999
2316
|
current?.close();
|
|
@@ -2006,6 +2323,7 @@ async function connectSingleDebugGateway({ targets, options, onLog }) {
|
|
|
2006
2323
|
let targetsByPluginId = createTargetsByPluginId(targets);
|
|
2007
2324
|
let closed = false;
|
|
2008
2325
|
let session = null;
|
|
2326
|
+
let stopHeartbeat = () => void 0;
|
|
2009
2327
|
let boundResolve;
|
|
2010
2328
|
let boundReject;
|
|
2011
2329
|
const bound = new Promise((resolve, reject) => {
|
|
@@ -2015,6 +2333,7 @@ async function connectSingleDebugGateway({ targets, options, onLog }) {
|
|
|
2015
2333
|
const closedPromise = new Promise((resolve) => {
|
|
2016
2334
|
socket.addEventListener("close", () => {
|
|
2017
2335
|
closed = true;
|
|
2336
|
+
stopHeartbeat();
|
|
2018
2337
|
resolve();
|
|
2019
2338
|
});
|
|
2020
2339
|
socket.addEventListener("error", () => {
|
|
@@ -2030,7 +2349,7 @@ async function connectSingleDebugGateway({ targets, options, onLog }) {
|
|
|
2030
2349
|
}
|
|
2031
2350
|
if (message.type === "heartbeat") return;
|
|
2032
2351
|
if (message.type === "error") {
|
|
2033
|
-
const error =
|
|
2352
|
+
const error = createGatewayError(message.code, message.message);
|
|
2034
2353
|
if (!session) boundReject(error);
|
|
2035
2354
|
onLog?.(`Connection Gateway 错误: ${message.code} ${message.message}`);
|
|
2036
2355
|
return;
|
|
@@ -2063,19 +2382,56 @@ async function connectSingleDebugGateway({ targets, options, onLog }) {
|
|
|
2063
2382
|
throw error;
|
|
2064
2383
|
});
|
|
2065
2384
|
const boundSession = await bound;
|
|
2385
|
+
stopHeartbeat = startGatewayHeartbeat(socket);
|
|
2066
2386
|
onLog?.(`已连接 Connection Gateway session: ${boundSession.id}`);
|
|
2067
2387
|
return {
|
|
2068
2388
|
session: boundSession,
|
|
2069
2389
|
updateTargets(targets) {
|
|
2070
2390
|
targetsByPluginId = createTargetsByPluginId(targets);
|
|
2391
|
+
updateGatewayMetadata({
|
|
2392
|
+
socket,
|
|
2393
|
+
targets,
|
|
2394
|
+
options,
|
|
2395
|
+
onLog
|
|
2396
|
+
});
|
|
2071
2397
|
onLog?.(`已热更新本地调试插件: ${targets.map((target) => target.snapshot.pluginId).join(", ")}`);
|
|
2072
2398
|
},
|
|
2073
2399
|
close() {
|
|
2074
|
-
if (!closed)
|
|
2400
|
+
if (!closed) {
|
|
2401
|
+
stopHeartbeat();
|
|
2402
|
+
socket.close();
|
|
2403
|
+
}
|
|
2075
2404
|
},
|
|
2076
2405
|
closed: closedPromise
|
|
2077
2406
|
};
|
|
2078
2407
|
}
|
|
2408
|
+
async function updateGatewayMetadata({ socket, targets, options, onLog }) {
|
|
2409
|
+
try {
|
|
2410
|
+
await sendMessage(socket, {
|
|
2411
|
+
protocol: "connection-gateway.ws.v1",
|
|
2412
|
+
type: "metadata",
|
|
2413
|
+
requestId: randomUUID(),
|
|
2414
|
+
metadata: makePluginDebugMetadata(targets, options.source)
|
|
2415
|
+
});
|
|
2416
|
+
} catch (error) {
|
|
2417
|
+
onLog?.(`Connection Gateway metadata 更新失败: ${formatErrorMessage(error)}`);
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
function startGatewayHeartbeat(socket) {
|
|
2421
|
+
const timer = setInterval(() => {
|
|
2422
|
+
sendMessage(socket, {
|
|
2423
|
+
protocol: "connection-gateway.ws.v1",
|
|
2424
|
+
type: "heartbeat",
|
|
2425
|
+
ts: Date.now()
|
|
2426
|
+
}).catch(() => {
|
|
2427
|
+
socket.close();
|
|
2428
|
+
});
|
|
2429
|
+
}, GATEWAY_HEARTBEAT_INTERVAL_MS);
|
|
2430
|
+
if (typeof timer === "object" && timer && "unref" in timer) timer.unref();
|
|
2431
|
+
return () => {
|
|
2432
|
+
clearInterval(timer);
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2079
2435
|
function createTargetsByPluginId(targets) {
|
|
2080
2436
|
return new Map(targets.map((target) => [target.snapshot.pluginId, target]));
|
|
2081
2437
|
}
|
|
@@ -2088,6 +2444,7 @@ async function handleGatewayEnvelope({ socket, session, targetsByPluginId, envel
|
|
|
2088
2444
|
if (!pluginId) throw new Error("Gateway debug request missing pluginId");
|
|
2089
2445
|
const target = targetsByPluginId.get(pluginId);
|
|
2090
2446
|
if (!target) throw new Error(`Gateway debug target not found: ${source} ${pluginId}`);
|
|
2447
|
+
const traceId = envelope.traceId ?? requiredRequestId(envelope);
|
|
2091
2448
|
onLog?.(`收到远程调试请求: ${source} ${pluginId} ${envelope.requestId ?? "-"}`);
|
|
2092
2449
|
await sendEnvelope(socket, {
|
|
2093
2450
|
protocol: "connection-gateway.v1",
|
|
@@ -2101,15 +2458,21 @@ async function handleGatewayEnvelope({ socket, session, targetsByPluginId, envel
|
|
|
2101
2458
|
createdAt: Date.now(),
|
|
2102
2459
|
payload: { kind: "plugin-debug.accepted" }
|
|
2103
2460
|
});
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2461
|
+
target.invokeBridge?.bind(traceId, { invokeToken: request.payload.systemVar.invokeToken });
|
|
2462
|
+
try {
|
|
2463
|
+
const result = await runDebugTool({
|
|
2464
|
+
runtime: target.runtime,
|
|
2465
|
+
snapshot: target.snapshot,
|
|
2466
|
+
toolId: request.payload.childId,
|
|
2467
|
+
input: request.payload.input,
|
|
2468
|
+
secrets: request.payload.secrets,
|
|
2469
|
+
systemVar: request.payload.systemVar,
|
|
2470
|
+
traceId
|
|
2471
|
+
});
|
|
2472
|
+
for (const message of result.streamMessages) await sendStreamChunk(socket, session, envelope, message);
|
|
2473
|
+
} finally {
|
|
2474
|
+
target.invokeBridge?.release(traceId);
|
|
2475
|
+
}
|
|
2113
2476
|
await sendEnvelope(socket, {
|
|
2114
2477
|
protocol: "connection-gateway.v1",
|
|
2115
2478
|
sessionId: session.id,
|
|
@@ -2212,11 +2575,245 @@ function delay(ms) {
|
|
|
2212
2575
|
function formatErrorMessage(error) {
|
|
2213
2576
|
return error instanceof Error ? error.message : String(error);
|
|
2214
2577
|
}
|
|
2578
|
+
function createGatewayError(code, message) {
|
|
2579
|
+
const error = new Error(message);
|
|
2580
|
+
Object.assign(error, { code });
|
|
2581
|
+
return error;
|
|
2582
|
+
}
|
|
2583
|
+
function isDuplicateConnectionError(error) {
|
|
2584
|
+
return error instanceof Error && ("code" in error ? error.code === GATEWAY_DUPLICATE_CONNECTION_CODE : false);
|
|
2585
|
+
}
|
|
2215
2586
|
function parseWsServerMessage(data) {
|
|
2216
2587
|
const text = typeof data === "string" ? data : data instanceof ArrayBuffer ? Buffer.from(data).toString("utf8") : ArrayBuffer.isView(data) ? Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString("utf8") : String(data);
|
|
2217
2588
|
return ConnectionGatewayWsServerMessageSchema.parse(JSON.parse(text));
|
|
2218
2589
|
}
|
|
2219
2590
|
|
|
2591
|
+
//#endregion
|
|
2592
|
+
//#region ../../packages/infrastructure/src/plugin/invoke/invoke.impl.ts
|
|
2593
|
+
var InvokeManager = class {
|
|
2594
|
+
constructor(deps) {
|
|
2595
|
+
this.deps = deps;
|
|
2596
|
+
}
|
|
2597
|
+
async getWecomCorpToken() {
|
|
2598
|
+
const [result, responseErr] = await this.requestFastGPT({
|
|
2599
|
+
path: "/api/proApi/support/wecom/getCorpToken",
|
|
2600
|
+
init: { method: "GET" },
|
|
2601
|
+
failureReason: {
|
|
2602
|
+
en: "Get wecom corp token failed",
|
|
2603
|
+
"zh-CN": "获取企业微信企业令牌失败"
|
|
2604
|
+
},
|
|
2605
|
+
invalidJsonReason: {
|
|
2606
|
+
en: "Invalid wecom corp token output",
|
|
2607
|
+
"zh-CN": "获取企业微信企业令牌返回数据格式错误"
|
|
2608
|
+
}
|
|
2609
|
+
});
|
|
2610
|
+
if (responseErr) return failureResult(responseErr);
|
|
2611
|
+
return successResult(result);
|
|
2612
|
+
}
|
|
2613
|
+
async userInfo() {
|
|
2614
|
+
const [result, responseErr] = await this.requestFastGPT({
|
|
2615
|
+
path: "/api/invoke/userInfo",
|
|
2616
|
+
init: { method: "GET" },
|
|
2617
|
+
failureReason: {
|
|
2618
|
+
en: "Invoke user info failed",
|
|
2619
|
+
"zh-CN": "反向调用用户信息失败"
|
|
2620
|
+
},
|
|
2621
|
+
invalidJsonReason: {
|
|
2622
|
+
en: "Invalid invoke user info output",
|
|
2623
|
+
"zh-CN": "反向调用用户信息返回数据格式错误"
|
|
2624
|
+
}
|
|
2625
|
+
});
|
|
2626
|
+
if (responseErr) return failureResult(responseErr);
|
|
2627
|
+
try {
|
|
2628
|
+
return successResult(InvokeUserInfoOutputSchema.parse(result));
|
|
2629
|
+
} catch (error) {
|
|
2630
|
+
return failureResult({
|
|
2631
|
+
en: "Invalid invoke user info output",
|
|
2632
|
+
"zh-CN": "反向调用用户信息返回数据格式错误"
|
|
2633
|
+
}, error);
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
async uploadFile(input) {
|
|
2637
|
+
let request;
|
|
2638
|
+
try {
|
|
2639
|
+
request = await this.buildUploadFileRequest(input);
|
|
2640
|
+
} catch (error) {
|
|
2641
|
+
return failureResult({
|
|
2642
|
+
en: "Invoke upload file failed",
|
|
2643
|
+
"zh-CN": "反向调用上传文件失败"
|
|
2644
|
+
}, error);
|
|
2645
|
+
}
|
|
2646
|
+
const [result, responseErr] = await this.requestFastGPT({
|
|
2647
|
+
path: "/api/invoke/fileUpload",
|
|
2648
|
+
init: {
|
|
2649
|
+
method: "POST",
|
|
2650
|
+
body: request.formData
|
|
2651
|
+
},
|
|
2652
|
+
failureReason: {
|
|
2653
|
+
en: "Invoke upload file failed",
|
|
2654
|
+
"zh-CN": "反向调用上传文件失败"
|
|
2655
|
+
},
|
|
2656
|
+
invalidJsonReason: {
|
|
2657
|
+
en: "Invalid invoke upload file output",
|
|
2658
|
+
"zh-CN": "反向调用上传文件返回数据格式错误"
|
|
2659
|
+
}
|
|
2660
|
+
});
|
|
2661
|
+
if (responseErr) return failureResult(responseErr);
|
|
2662
|
+
try {
|
|
2663
|
+
return successResult(InvokeUploadFileOutputSchema.parse({ accessURL: result.url }));
|
|
2664
|
+
} catch (error) {
|
|
2665
|
+
return failureResult({
|
|
2666
|
+
en: "Invalid invoke upload file output",
|
|
2667
|
+
"zh-CN": "反向调用上传文件返回数据格式错误"
|
|
2668
|
+
}, error);
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
buildUrl(path) {
|
|
2672
|
+
return new URL(path, this.deps.fastgptBaseUrl).toString();
|
|
2673
|
+
}
|
|
2674
|
+
async buildUploadFileRequest(input) {
|
|
2675
|
+
const formData = new FormData();
|
|
2676
|
+
const fileBuffer = await this.toBuffer(input.file);
|
|
2677
|
+
const fileName = input.fileName ?? randomUUID();
|
|
2678
|
+
const contentType = input.contentType ?? "application/octet-stream";
|
|
2679
|
+
const blobPart = new Uint8Array(fileBuffer.byteLength);
|
|
2680
|
+
blobPart.set(fileBuffer);
|
|
2681
|
+
formData.append("file", new Blob([blobPart], { type: contentType }), fileName);
|
|
2682
|
+
if (input.fileName) formData.append("fileName", input.fileName);
|
|
2683
|
+
if (input.contentType) formData.append("contentType", input.contentType);
|
|
2684
|
+
return {
|
|
2685
|
+
formData,
|
|
2686
|
+
meta: {
|
|
2687
|
+
fileName,
|
|
2688
|
+
contentType
|
|
2689
|
+
}
|
|
2690
|
+
};
|
|
2691
|
+
}
|
|
2692
|
+
async toBuffer(file) {
|
|
2693
|
+
if (Buffer.isBuffer(file)) return file;
|
|
2694
|
+
if (file instanceof Readable$1) {
|
|
2695
|
+
const chunks = [];
|
|
2696
|
+
for await (const chunk of file) chunks.push(this.toBufferChunk(chunk));
|
|
2697
|
+
return Buffer.concat(chunks);
|
|
2698
|
+
}
|
|
2699
|
+
return Buffer.from(file);
|
|
2700
|
+
}
|
|
2701
|
+
toBufferChunk(chunk) {
|
|
2702
|
+
if (Buffer.isBuffer(chunk)) return chunk;
|
|
2703
|
+
if (chunk instanceof Uint8Array) return Buffer.from(chunk);
|
|
2704
|
+
if (chunk instanceof ArrayBuffer) return Buffer.from(chunk);
|
|
2705
|
+
if (ArrayBuffer.isView(chunk)) return Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
2706
|
+
if (typeof chunk === "string") return Buffer.from(chunk);
|
|
2707
|
+
if (typeof chunk === "number") return Buffer.from([chunk]);
|
|
2708
|
+
if (this.isSerializedBufferChunk(chunk)) return Buffer.from(chunk.data);
|
|
2709
|
+
throw new TypeError(`Unsupported upload file stream chunk type: ${Object.prototype.toString.call(chunk)}`);
|
|
2710
|
+
}
|
|
2711
|
+
isSerializedBufferChunk(value) {
|
|
2712
|
+
return this.isRecord(value) && value.type === "Buffer" && Array.isArray(value.data) && value.data.every((item) => Number.isInteger(item) && Number.isSafeInteger(item) && item >= 0 && item <= 255);
|
|
2713
|
+
}
|
|
2714
|
+
async requestFastGPT({ path, init, failureReason, invalidJsonReason }) {
|
|
2715
|
+
const headers = new Headers(init?.headers);
|
|
2716
|
+
headers.set("Authorization", `Bearer ${this.deps.token}`);
|
|
2717
|
+
let response;
|
|
2718
|
+
try {
|
|
2719
|
+
response = await fetch(this.buildUrl(path), {
|
|
2720
|
+
...init,
|
|
2721
|
+
headers
|
|
2722
|
+
});
|
|
2723
|
+
} catch (error) {
|
|
2724
|
+
return failureResult(failureReason, error);
|
|
2725
|
+
}
|
|
2726
|
+
if (!response.ok) return failureResult(failureReason, await this.readErrorResponse(response));
|
|
2727
|
+
let payload;
|
|
2728
|
+
try {
|
|
2729
|
+
payload = await response.json();
|
|
2730
|
+
} catch (error) {
|
|
2731
|
+
return failureResult(invalidJsonReason, error);
|
|
2732
|
+
}
|
|
2733
|
+
return this.parseResponse(payload, failureReason);
|
|
2734
|
+
}
|
|
2735
|
+
parseResponse(payload, fallbackReason) {
|
|
2736
|
+
if (!this.isRecord(payload)) return successResult(payload);
|
|
2737
|
+
if (payload.success === false) return failureResult({
|
|
2738
|
+
en: this.getResponseMessage(payload) ?? fallbackReason.en,
|
|
2739
|
+
"zh-CN": this.getResponseMessage(payload) ?? fallbackReason["zh-CN"]
|
|
2740
|
+
}, payload);
|
|
2741
|
+
if (typeof payload.code === "number" && payload.code >= 400) return failureResult({
|
|
2742
|
+
en: this.getResponseMessage(payload) ?? fallbackReason.en,
|
|
2743
|
+
"zh-CN": this.getResponseMessage(payload) ?? fallbackReason["zh-CN"]
|
|
2744
|
+
}, payload);
|
|
2745
|
+
return successResult("data" in payload ? payload.data : payload);
|
|
2746
|
+
}
|
|
2747
|
+
async readErrorResponse(response) {
|
|
2748
|
+
return {
|
|
2749
|
+
status: response.status,
|
|
2750
|
+
statusText: response.statusText,
|
|
2751
|
+
body: (await response.text().catch(() => "")).slice(0, 1e3)
|
|
2752
|
+
};
|
|
2753
|
+
}
|
|
2754
|
+
getResponseMessage(payload) {
|
|
2755
|
+
return typeof payload.message === "string" ? payload.message : void 0;
|
|
2756
|
+
}
|
|
2757
|
+
isRecord(value) {
|
|
2758
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
2759
|
+
}
|
|
2760
|
+
};
|
|
2761
|
+
|
|
2762
|
+
//#endregion
|
|
2763
|
+
//#region src/debug/remote-invoke.ts
|
|
2764
|
+
var RemoteDebugInvokeBridge = class {
|
|
2765
|
+
sessions = /* @__PURE__ */ new Map();
|
|
2766
|
+
getFastgptBaseUrl;
|
|
2767
|
+
constructor(fastgptBaseUrl) {
|
|
2768
|
+
this.getFastgptBaseUrl = typeof fastgptBaseUrl === "function" ? fastgptBaseUrl : () => fastgptBaseUrl;
|
|
2769
|
+
}
|
|
2770
|
+
attach(runtime) {
|
|
2771
|
+
runtime.setHostRequestHandler((request) => this.handleHostRequest(request));
|
|
2772
|
+
}
|
|
2773
|
+
bind(traceId, input) {
|
|
2774
|
+
this.sessions.set(traceId, { invokeToken: input.invokeToken });
|
|
2775
|
+
}
|
|
2776
|
+
release(traceId) {
|
|
2777
|
+
this.sessions.delete(traceId);
|
|
2778
|
+
}
|
|
2779
|
+
async handleHostRequest({ method, args, input, traceId }) {
|
|
2780
|
+
if (!traceId) throw new Error("远程调试反向调用缺少 traceId。");
|
|
2781
|
+
const session = this.sessions.get(traceId);
|
|
2782
|
+
if (!session) throw new Error("远程调试反向调用会话不存在或已结束。");
|
|
2783
|
+
const invoke = new InvokeManager({
|
|
2784
|
+
token: session.invokeToken,
|
|
2785
|
+
fastgptBaseUrl: this.getFastgptBaseUrl()
|
|
2786
|
+
});
|
|
2787
|
+
switch (method) {
|
|
2788
|
+
case InvokeMethodEnum.uploadFile: return invoke.uploadFile({
|
|
2789
|
+
...ensurePlainObject(args),
|
|
2790
|
+
file: input ? await readSourceToBuffer(input) : Buffer.alloc(0)
|
|
2791
|
+
});
|
|
2792
|
+
case InvokeMethodEnum.userInfo: return invoke.userInfo();
|
|
2793
|
+
case InvokeMethodEnum.wecomCorpToken: return invoke.getWecomCorpToken();
|
|
2794
|
+
default: throw new Error(`远程调试宿主暂不支持反向调用: ${String(method)}`);
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
};
|
|
2798
|
+
function ensurePlainObject(value) {
|
|
2799
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2800
|
+
}
|
|
2801
|
+
async function readSourceToBuffer(source) {
|
|
2802
|
+
const chunks = [];
|
|
2803
|
+
const iterable = isStreamDataLike(source) ? source.values() : source;
|
|
2804
|
+
for await (const chunk of iterable) chunks.push(toBuffer(chunk));
|
|
2805
|
+
return Buffer.concat(chunks);
|
|
2806
|
+
}
|
|
2807
|
+
function isStreamDataLike(source) {
|
|
2808
|
+
return source instanceof StreamData || typeof source === "object" && source !== null && typeof source.values === "function" && typeof source.consume === "function";
|
|
2809
|
+
}
|
|
2810
|
+
function toBuffer(value) {
|
|
2811
|
+
if (Buffer.isBuffer(value)) return value;
|
|
2812
|
+
if (value instanceof Uint8Array) return Buffer.from(value);
|
|
2813
|
+
if (typeof value === "string") return Buffer.from(value);
|
|
2814
|
+
return Buffer.from(JSON.stringify(value));
|
|
2815
|
+
}
|
|
2816
|
+
|
|
2220
2817
|
//#endregion
|
|
2221
2818
|
//#region src/debug/remote-session.ts
|
|
2222
2819
|
async function runRemoteDebugSession({ entriesInput, options, commandName, migrationHint }) {
|
|
@@ -2247,6 +2844,11 @@ async function runRemoteDebugSessionOnce({ entriesInput, options, commandName })
|
|
|
2247
2844
|
commandName,
|
|
2248
2845
|
targets
|
|
2249
2846
|
});
|
|
2847
|
+
let currentGatewayOptions = gatewayOptions;
|
|
2848
|
+
gatewayOptions.onResolvedReconnectOptions = (nextOptions) => {
|
|
2849
|
+
currentGatewayOptions = nextOptions;
|
|
2850
|
+
};
|
|
2851
|
+
attachRemoteInvokeBridges(targets, () => currentGatewayOptions.fastgptBaseUrl);
|
|
2250
2852
|
const reporter = createRemoteDebugReporter({
|
|
2251
2853
|
commandName,
|
|
2252
2854
|
options,
|
|
@@ -2264,6 +2866,20 @@ async function runRemoteDebugSessionOnce({ entriesInput, options, commandName })
|
|
|
2264
2866
|
gateway.close();
|
|
2265
2867
|
if (force) process.exit(130);
|
|
2266
2868
|
});
|
|
2869
|
+
reporter.attachConfigureConnectionKey(async () => {
|
|
2870
|
+
await configureConnectionKey({
|
|
2871
|
+
options,
|
|
2872
|
+
commandName,
|
|
2873
|
+
targets,
|
|
2874
|
+
reconnect: () => {
|
|
2875
|
+
if (gateway.reconnect) {
|
|
2876
|
+
gateway.reconnect();
|
|
2877
|
+
return;
|
|
2878
|
+
}
|
|
2879
|
+
gateway.close();
|
|
2880
|
+
}
|
|
2881
|
+
});
|
|
2882
|
+
});
|
|
2267
2883
|
const source = gateway.session.sessionScope.source ?? gatewayOptions.source ?? "-";
|
|
2268
2884
|
reporter.ready(source);
|
|
2269
2885
|
await gateway.closed;
|
|
@@ -2284,6 +2900,11 @@ async function runWatchRemoteDebugSession({ entriesInput, options, commandName }
|
|
|
2284
2900
|
commandName,
|
|
2285
2901
|
targets: initialTargets
|
|
2286
2902
|
});
|
|
2903
|
+
let currentGatewayOptions = gatewayOptions;
|
|
2904
|
+
gatewayOptions.onResolvedReconnectOptions = (nextOptions) => {
|
|
2905
|
+
currentGatewayOptions = nextOptions;
|
|
2906
|
+
};
|
|
2907
|
+
attachRemoteInvokeBridges(initialTargets, () => currentGatewayOptions.fastgptBaseUrl);
|
|
2287
2908
|
const reporter = createRemoteDebugReporter({
|
|
2288
2909
|
commandName,
|
|
2289
2910
|
options,
|
|
@@ -2294,6 +2915,7 @@ async function runWatchRemoteDebugSession({ entriesInput, options, commandName }
|
|
|
2294
2915
|
let reloadPending = false;
|
|
2295
2916
|
let closed = false;
|
|
2296
2917
|
let gateway;
|
|
2918
|
+
let activeTargets = initialTargets;
|
|
2297
2919
|
const reloadTargets = async () => {
|
|
2298
2920
|
if (reloadRunning) {
|
|
2299
2921
|
reloadPending = true;
|
|
@@ -2305,6 +2927,8 @@ async function runWatchRemoteDebugSession({ entriesInput, options, commandName }
|
|
|
2305
2927
|
reloadPending = false;
|
|
2306
2928
|
try {
|
|
2307
2929
|
const nextTargets = await loadTargets(entries, options);
|
|
2930
|
+
attachRemoteInvokeBridges(nextTargets, () => currentGatewayOptions.fastgptBaseUrl);
|
|
2931
|
+
activeTargets = nextTargets;
|
|
2308
2932
|
gateway?.updateTargets(nextTargets);
|
|
2309
2933
|
reporter.log(`检测到插件文件变化,已热更新 ${nextTargets.length} 个本地插件。`);
|
|
2310
2934
|
} catch (error) {
|
|
@@ -2332,6 +2956,20 @@ async function runWatchRemoteDebugSession({ entriesInput, options, commandName }
|
|
|
2332
2956
|
connectedGateway.close();
|
|
2333
2957
|
if (force) process.exit(130);
|
|
2334
2958
|
});
|
|
2959
|
+
reporter.attachConfigureConnectionKey(async () => {
|
|
2960
|
+
await configureConnectionKey({
|
|
2961
|
+
options,
|
|
2962
|
+
commandName,
|
|
2963
|
+
targets: activeTargets,
|
|
2964
|
+
reconnect: () => {
|
|
2965
|
+
if (connectedGateway.reconnect) {
|
|
2966
|
+
connectedGateway.reconnect();
|
|
2967
|
+
return;
|
|
2968
|
+
}
|
|
2969
|
+
connectedGateway.close();
|
|
2970
|
+
}
|
|
2971
|
+
});
|
|
2972
|
+
});
|
|
2335
2973
|
const source = connectedGateway.session.sessionScope.source ?? gatewayOptions.source ?? "-";
|
|
2336
2974
|
reporter.ready(source);
|
|
2337
2975
|
await connectedGateway.closed;
|
|
@@ -2369,6 +3007,13 @@ async function loadTargets(entries, options) {
|
|
|
2369
3007
|
if (duplicatePluginIds.length > 0) throw new Error(`gateway pluginId 重复: ${duplicatePluginIds.join(", ")}`);
|
|
2370
3008
|
return targets;
|
|
2371
3009
|
}
|
|
3010
|
+
function attachRemoteInvokeBridges(targets, getFastgptBaseUrl) {
|
|
3011
|
+
targets.forEach((target) => {
|
|
3012
|
+
const invokeBridge = new RemoteDebugInvokeBridge(getFastgptBaseUrl);
|
|
3013
|
+
invokeBridge.attach(target.runtime);
|
|
3014
|
+
target.invokeBridge = invokeBridge;
|
|
3015
|
+
});
|
|
3016
|
+
}
|
|
2372
3017
|
function watchDebugEntries(entries, onChange) {
|
|
2373
3018
|
const watchers = entries.map((entry) => fsWatch(entry, {
|
|
2374
3019
|
recursive: true,
|
|
@@ -2400,7 +3045,10 @@ function createRemoteDebugReporter({ commandName, options, gatewayOptions, targe
|
|
|
2400
3045
|
return options.interactive !== false && Boolean(process.stdout.isTTY) && Boolean(process.stdin.isTTY) ? new TuiRemoteDebugReporter(summary) : new PlainRemoteDebugReporter(summary);
|
|
2401
3046
|
}
|
|
2402
3047
|
async function ensureConnectLink({ options, commandName, targets }) {
|
|
2403
|
-
if (options.connect)
|
|
3048
|
+
if (options.connect) {
|
|
3049
|
+
options.connectPersistOnSuccess = true;
|
|
3050
|
+
return;
|
|
3051
|
+
}
|
|
2404
3052
|
const savedConnect = await readSavedConnectionKey();
|
|
2405
3053
|
if (savedConnect) {
|
|
2406
3054
|
options.connect = savedConnect;
|
|
@@ -2428,12 +3076,12 @@ async function promptConnectLink({ commandName, targets, defaultValue, errorMess
|
|
|
2428
3076
|
"",
|
|
2429
3077
|
...errorMessage ? [`Last error ${errorMessage}`, ""] : [],
|
|
2430
3078
|
"Paste the FastGPT connection key or connect link to start the WSS debug session.",
|
|
3079
|
+
...defaultValue ? ["Press Enter to reuse the current value, or type a new one to replace it.", ""] : [],
|
|
2431
3080
|
""
|
|
2432
3081
|
].join("\n"));
|
|
2433
3082
|
return (await input({
|
|
2434
3083
|
message: "FastGPT connection key",
|
|
2435
3084
|
default: defaultValue,
|
|
2436
|
-
prefill: defaultValue ? "editable" : void 0,
|
|
2437
3085
|
validate(value) {
|
|
2438
3086
|
return value.trim().length > 0 || "请输入 FastGPT connection key";
|
|
2439
3087
|
}
|
|
@@ -2463,6 +3111,16 @@ async function saveConnectionKey(connectionKey) {
|
|
|
2463
3111
|
mode: 384
|
|
2464
3112
|
});
|
|
2465
3113
|
}
|
|
3114
|
+
async function configureConnectionKey({ options, commandName, targets, reconnect }) {
|
|
3115
|
+
options.connect = await promptConnectLink({
|
|
3116
|
+
commandName,
|
|
3117
|
+
targets,
|
|
3118
|
+
defaultValue: options.connect
|
|
3119
|
+
});
|
|
3120
|
+
options.connectPersistOnSuccess = true;
|
|
3121
|
+
logger.info("已更新 FastGPT connection key,重连成功后会保存到本地配置。");
|
|
3122
|
+
reconnect();
|
|
3123
|
+
}
|
|
2466
3124
|
async function readCliConfig() {
|
|
2467
3125
|
const configPath = getCliConfigPath();
|
|
2468
3126
|
let raw;
|
|
@@ -2494,10 +3152,20 @@ const CLI_CONFIG_FILE_NAME = "config.json";
|
|
|
2494
3152
|
const CLI_CONFIG_DIR_NAME = "fastgpt-plugin";
|
|
2495
3153
|
const CliConfigSchema = z.object({ debug: z.object({ connectionKey: z.string().min(1).optional() }).optional() }).passthrough();
|
|
2496
3154
|
var PlainRemoteDebugReporter = class {
|
|
3155
|
+
closeSession;
|
|
3156
|
+
interruptCount = 0;
|
|
3157
|
+
onSigint = () => {
|
|
3158
|
+
this.requestClose("SIGINT");
|
|
3159
|
+
};
|
|
3160
|
+
onSigterm = () => {
|
|
3161
|
+
this.requestClose("SIGTERM");
|
|
3162
|
+
};
|
|
2497
3163
|
constructor(summary) {
|
|
2498
3164
|
this.summary = summary;
|
|
2499
3165
|
}
|
|
2500
3166
|
start() {
|
|
3167
|
+
process.on("SIGINT", this.onSigint);
|
|
3168
|
+
process.on("SIGTERM", this.onSigterm);
|
|
2501
3169
|
logger.info([
|
|
2502
3170
|
"FastGPT 插件开发会话",
|
|
2503
3171
|
` command: fastgpt-plugin ${this.summary.commandName}`,
|
|
@@ -2509,6 +3177,13 @@ var PlainRemoteDebugReporter = class {
|
|
|
2509
3177
|
...this.summary.targets.map(formatTargetLine)
|
|
2510
3178
|
].join("\n"));
|
|
2511
3179
|
}
|
|
3180
|
+
requestClose(signal) {
|
|
3181
|
+
this.interruptCount += 1;
|
|
3182
|
+
const force = this.interruptCount >= 2;
|
|
3183
|
+
this.log(force ? `再次收到 ${signal},正在强制退出。` : `收到 ${signal},正在关闭远程调试会话。再次发送 ${signal} 强制退出。`);
|
|
3184
|
+
this.closeSession?.({ force });
|
|
3185
|
+
if (force && !this.closeSession) process.exit(130);
|
|
3186
|
+
}
|
|
2512
3187
|
log(message) {
|
|
2513
3188
|
logger.info(message);
|
|
2514
3189
|
}
|
|
@@ -2518,8 +3193,13 @@ var PlainRemoteDebugReporter = class {
|
|
|
2518
3193
|
});
|
|
2519
3194
|
logger.info(`已建立 1 个远程调试通道,挂载 ${this.summary.targets.length} 个插件,按 Ctrl+C 停止。`);
|
|
2520
3195
|
}
|
|
2521
|
-
attachClose() {
|
|
3196
|
+
attachClose(close) {
|
|
3197
|
+
this.closeSession = close;
|
|
3198
|
+
}
|
|
3199
|
+
attachConfigureConnectionKey() {}
|
|
2522
3200
|
end() {
|
|
3201
|
+
process.off("SIGINT", this.onSigint);
|
|
3202
|
+
process.off("SIGTERM", this.onSigterm);
|
|
2523
3203
|
logger.info("远程调试会话已结束。");
|
|
2524
3204
|
}
|
|
2525
3205
|
};
|
|
@@ -2529,36 +3209,34 @@ var TuiRemoteDebugReporter = class {
|
|
|
2529
3209
|
logs = [];
|
|
2530
3210
|
instance;
|
|
2531
3211
|
closeSession;
|
|
3212
|
+
configureConnectionKey;
|
|
2532
3213
|
interruptCount = 0;
|
|
3214
|
+
configuring = false;
|
|
2533
3215
|
onSigint = () => {
|
|
2534
|
-
this.requestClose("
|
|
3216
|
+
this.requestClose("SIGINT");
|
|
2535
3217
|
};
|
|
2536
|
-
requestClose = (
|
|
2537
|
-
if (reason === "q") {
|
|
2538
|
-
this.log("收到退出指令,正在关闭远程调试会话。");
|
|
2539
|
-
this.closeSession?.({ force: false });
|
|
2540
|
-
return;
|
|
2541
|
-
}
|
|
3218
|
+
requestClose = (signal) => {
|
|
2542
3219
|
this.interruptCount += 1;
|
|
2543
3220
|
const force = this.interruptCount >= 2;
|
|
2544
3221
|
this.status = force ? "closed" : "closing";
|
|
2545
|
-
this.log(force ?
|
|
3222
|
+
this.log(force ? `再次收到 ${signal},正在强制退出。` : `收到 ${signal},正在关闭远程调试会话。再次按 Ctrl+C 强制退出。`);
|
|
2546
3223
|
this.closeSession?.({ force });
|
|
2547
3224
|
if (force && !this.closeSession) process.exit(130);
|
|
2548
3225
|
};
|
|
3226
|
+
requestConfigureConnectionKey = () => {
|
|
3227
|
+
if (this.configuring) return;
|
|
3228
|
+
if (!this.configureConnectionKey) {
|
|
3229
|
+
this.log("connection key 配置入口尚未就绪。");
|
|
3230
|
+
return;
|
|
3231
|
+
}
|
|
3232
|
+
this.runConfigureConnectionKey();
|
|
3233
|
+
};
|
|
2549
3234
|
constructor(summary) {
|
|
2550
3235
|
this.summary = summary;
|
|
2551
3236
|
}
|
|
2552
3237
|
start() {
|
|
2553
3238
|
process.on("SIGINT", this.onSigint);
|
|
2554
|
-
this.
|
|
2555
|
-
stdin: process.stdin,
|
|
2556
|
-
stdout: process.stdout,
|
|
2557
|
-
stderr: process.stderr,
|
|
2558
|
-
exitOnCtrlC: false,
|
|
2559
|
-
interactive: true,
|
|
2560
|
-
patchConsole: false
|
|
2561
|
-
});
|
|
3239
|
+
this.mount();
|
|
2562
3240
|
}
|
|
2563
3241
|
log(message) {
|
|
2564
3242
|
this.logs.push({
|
|
@@ -2576,39 +3254,78 @@ var TuiRemoteDebugReporter = class {
|
|
|
2576
3254
|
attachClose(close) {
|
|
2577
3255
|
this.closeSession = close;
|
|
2578
3256
|
}
|
|
3257
|
+
attachConfigureConnectionKey(handler) {
|
|
3258
|
+
this.configureConnectionKey = handler;
|
|
3259
|
+
}
|
|
2579
3260
|
async end() {
|
|
2580
3261
|
process.off("SIGINT", this.onSigint);
|
|
2581
3262
|
this.status = "closed";
|
|
2582
3263
|
this.rerender();
|
|
2583
|
-
|
|
2584
|
-
this.instance = void 0;
|
|
2585
|
-
if (instance) {
|
|
2586
|
-
await Promise.race([instance.waitUntilRenderFlush(), setTimeout$1(50)]).catch(() => void 0);
|
|
2587
|
-
instance.unmount();
|
|
2588
|
-
await Promise.race([instance.waitUntilExit(), setTimeout$1(50)]).catch(() => void 0);
|
|
2589
|
-
}
|
|
3264
|
+
await this.unmount();
|
|
2590
3265
|
logger.info("远程调试会话已结束。");
|
|
2591
3266
|
}
|
|
2592
3267
|
rerender() {
|
|
2593
3268
|
this.instance?.rerender(this.createApp());
|
|
2594
3269
|
}
|
|
3270
|
+
mount() {
|
|
3271
|
+
if (this.instance) return;
|
|
3272
|
+
this.instance = render(this.createApp(), {
|
|
3273
|
+
stdin: process.stdin,
|
|
3274
|
+
stdout: process.stdout,
|
|
3275
|
+
stderr: process.stderr,
|
|
3276
|
+
exitOnCtrlC: false,
|
|
3277
|
+
interactive: true,
|
|
3278
|
+
patchConsole: false
|
|
3279
|
+
});
|
|
3280
|
+
}
|
|
3281
|
+
async unmount() {
|
|
3282
|
+
const instance = this.instance;
|
|
3283
|
+
this.instance = void 0;
|
|
3284
|
+
if (!instance) return;
|
|
3285
|
+
await Promise.race([instance.waitUntilRenderFlush(), setTimeout$1(50)]).catch(() => void 0);
|
|
3286
|
+
instance.unmount();
|
|
3287
|
+
await Promise.race([instance.waitUntilExit(), setTimeout$1(50)]).catch(() => void 0);
|
|
3288
|
+
}
|
|
3289
|
+
async runConfigureConnectionKey() {
|
|
3290
|
+
if (!this.configureConnectionKey) return;
|
|
3291
|
+
const previousStatus = this.status;
|
|
3292
|
+
this.configuring = true;
|
|
3293
|
+
this.status = "configuring";
|
|
3294
|
+
this.rerender();
|
|
3295
|
+
try {
|
|
3296
|
+
await this.unmount();
|
|
3297
|
+
await this.configureConnectionKey();
|
|
3298
|
+
this.status = "connecting";
|
|
3299
|
+
this.log("已更新 connection key,正在重新连接 Connection Gateway。");
|
|
3300
|
+
} catch (error) {
|
|
3301
|
+
this.status = previousStatus;
|
|
3302
|
+
this.log(`connection key 配置失败: ${formatConnectionKeyExchangeError(error)}`);
|
|
3303
|
+
} finally {
|
|
3304
|
+
this.configuring = false;
|
|
3305
|
+
if (this.status !== "closed") {
|
|
3306
|
+
this.mount();
|
|
3307
|
+
this.rerender();
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
2595
3311
|
createApp() {
|
|
2596
3312
|
return React.createElement(RemoteDebugInkApp, {
|
|
2597
3313
|
summary: this.summary,
|
|
2598
3314
|
status: this.status,
|
|
2599
3315
|
source: this.source,
|
|
2600
3316
|
logs: this.logs,
|
|
2601
|
-
|
|
3317
|
+
onClose: () => this.requestClose("SIGINT"),
|
|
3318
|
+
onConfigureConnectionKey: this.requestConfigureConnectionKey
|
|
2602
3319
|
});
|
|
2603
3320
|
}
|
|
2604
3321
|
};
|
|
2605
|
-
function RemoteDebugInkApp({ summary, status, source, logs,
|
|
3322
|
+
function RemoteDebugInkApp({ summary, status, source, logs, onClose, onConfigureConnectionKey }) {
|
|
2606
3323
|
useInput((inputValue, key) => {
|
|
2607
3324
|
if (key.ctrl && inputValue === "c") {
|
|
2608
|
-
|
|
3325
|
+
onClose();
|
|
2609
3326
|
return;
|
|
2610
3327
|
}
|
|
2611
|
-
if (inputValue === "
|
|
3328
|
+
if (inputValue === "c") onConfigureConnectionKey();
|
|
2612
3329
|
});
|
|
2613
3330
|
const statusMeta = getTuiStatusMeta(status);
|
|
2614
3331
|
const sourceLabel = source === "-" ? "waiting for bind" : source;
|
|
@@ -2700,7 +3417,7 @@ function RemoteDebugInkApp({ summary, status, source, logs, onQuit }) {
|
|
|
2700
3417
|
}, ...activityItems)), React.createElement(Box, {
|
|
2701
3418
|
marginTop: 1,
|
|
2702
3419
|
justifyContent: "space-between"
|
|
2703
|
-
}, React.createElement(Text, { dimColor: true }, "
|
|
3420
|
+
}, React.createElement(Text, { dimColor: true }, "c configure connection key"), React.createElement(Text, { dimColor: true }, "Ctrl+C stop · second Ctrl+C force exit")));
|
|
2704
3421
|
}
|
|
2705
3422
|
function getTuiStatusMeta(status) {
|
|
2706
3423
|
if (status === "connected") return {
|
|
@@ -2717,6 +3434,13 @@ function getTuiStatusMeta(status) {
|
|
|
2717
3434
|
color: "gray",
|
|
2718
3435
|
borderColor: "gray"
|
|
2719
3436
|
};
|
|
3437
|
+
if (status === "configuring") return {
|
|
3438
|
+
label: "CONFIGURE",
|
|
3439
|
+
marker: "◆",
|
|
3440
|
+
description: "Waiting for a new FastGPT connection key.",
|
|
3441
|
+
color: "cyanBright",
|
|
3442
|
+
borderColor: "cyan"
|
|
3443
|
+
};
|
|
2720
3444
|
if (status === "closing") return {
|
|
2721
3445
|
label: "CLOSING",
|
|
2722
3446
|
marker: "◆",
|
|
@@ -2773,15 +3497,28 @@ function formatConnectionKeyExchangeError(error) {
|
|
|
2773
3497
|
}
|
|
2774
3498
|
async function resolveConnectGatewayOptions(options) {
|
|
2775
3499
|
const info = await exchangeConnectLink(options.connect);
|
|
2776
|
-
|
|
3500
|
+
const resolvedOptions = {
|
|
2777
3501
|
gatewayUrl: normalizeGatewayWsUrl(info.gatewayUrl),
|
|
2778
3502
|
connectToken: info.connectToken,
|
|
2779
3503
|
userId: info.tmbId,
|
|
2780
3504
|
source: info.source,
|
|
3505
|
+
fastgptBaseUrl: info.fastgptBaseUrl,
|
|
2781
3506
|
tokenTtlMs: Math.max(1, info.expiresAt - Date.now()),
|
|
2782
3507
|
reconnect: options.noReconnect ? false : options.reconnect ?? true,
|
|
2783
3508
|
reconnectIntervalMs: toPositiveInt(options.reconnectIntervalMs ?? process.env.CONNECTION_GATEWAY_RECONNECT_INTERVAL_MS ?? "2000", "reconnect-interval-ms")
|
|
2784
3509
|
};
|
|
3510
|
+
resolvedOptions.resolveReconnectOptions = async () => {
|
|
3511
|
+
const nextOptions = await resolveConnectGatewayOptions(options);
|
|
3512
|
+
if (options.connectPersistOnSuccess === true) {
|
|
3513
|
+
await saveConnectionKey(options.connect);
|
|
3514
|
+
logger.info(`已保存 FastGPT connection key 到本地配置: ${getCliConfigPath()}`);
|
|
3515
|
+
options.connectPersistOnSuccess = false;
|
|
3516
|
+
}
|
|
3517
|
+
resolvedOptions.onResolvedReconnectOptions?.(nextOptions);
|
|
3518
|
+
nextOptions.onResolvedReconnectOptions = resolvedOptions.onResolvedReconnectOptions;
|
|
3519
|
+
return nextOptions;
|
|
3520
|
+
};
|
|
3521
|
+
return resolvedOptions;
|
|
2785
3522
|
}
|
|
2786
3523
|
function resolveUploadDir({ entryDir, index, total, uploadDir }) {
|
|
2787
3524
|
if (!uploadDir) return path.resolve(path.join(entryDir, ".fastgpt-plugin-debug/uploads"));
|
|
@@ -2793,10 +3530,12 @@ const ConnectInfoSchema = z.object({
|
|
|
2793
3530
|
transport: z.literal("websocket"),
|
|
2794
3531
|
source: z.string().min(1),
|
|
2795
3532
|
connectToken: z.string().min(1),
|
|
3533
|
+
fastgptBaseUrl: z.string().min(1).optional(),
|
|
2796
3534
|
expiresAt: z.number().int().positive()
|
|
2797
3535
|
});
|
|
2798
3536
|
async function exchangeConnectLink(connectInput) {
|
|
2799
|
-
const
|
|
3537
|
+
const exchangeUrl = isHttpUrl(connectInput) ? connectInput : resolveConnectionKeyExchangeUrl();
|
|
3538
|
+
const response = isHttpUrl(connectInput) ? await fetch(exchangeUrl) : await fetch(exchangeUrl, {
|
|
2800
3539
|
method: "POST",
|
|
2801
3540
|
headers: { "Content-Type": "application/json" },
|
|
2802
3541
|
body: JSON.stringify({ connectionKey: connectInput })
|
|
@@ -2807,6 +3546,7 @@ async function exchangeConnectLink(connectInput) {
|
|
|
2807
3546
|
const info = ConnectInfoSchema.parse(payload.data ?? payload);
|
|
2808
3547
|
return {
|
|
2809
3548
|
...info,
|
|
3549
|
+
fastgptBaseUrl: info.fastgptBaseUrl ?? new URL(exchangeUrl).origin,
|
|
2810
3550
|
tmbId: parseTmbIdFromDebugSource(info.source)
|
|
2811
3551
|
};
|
|
2812
3552
|
}
|
|
@@ -2826,11 +3566,9 @@ function resolveConnectionKeyExchangeUrl() {
|
|
|
2826
3566
|
return new URL("/api/plugin/debug-sessions/connection-key:exchange", baseUrl).toString();
|
|
2827
3567
|
}
|
|
2828
3568
|
function parseTmbIdFromDebugSource(source) {
|
|
2829
|
-
const
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
if (!tmbId) throw new Error(`debug source 缺少 tmbId: ${source}`);
|
|
2833
|
-
return tmbId;
|
|
3569
|
+
const parsed = parsePluginDebugSessionSource(source);
|
|
3570
|
+
if (!parsed) throw new Error(`debug source 缺少 tmbId: ${source}`);
|
|
3571
|
+
return parsed.tmbId;
|
|
2834
3572
|
}
|
|
2835
3573
|
function normalizeGatewayWsUrl(gatewayUrl) {
|
|
2836
3574
|
const parsed = new URL(gatewayUrl);
|
|
@@ -3160,9 +3898,6 @@ async function run(argv) {
|
|
|
3160
3898
|
|
|
3161
3899
|
//#endregion
|
|
3162
3900
|
//#region src/index.ts
|
|
3163
|
-
process.on("SIGINT", () => {
|
|
3164
|
-
process.exit(130);
|
|
3165
|
-
});
|
|
3166
3901
|
const main = async () => {
|
|
3167
3902
|
try {
|
|
3168
3903
|
await run();
|