@fastgpt-plugin/cli 0.1.0-beta.9 → 0.2.0-alpha.2
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 +71 -0
- package/README.md +41 -4
- package/dist/index.js +1872 -108
- package/package.json +6 -3
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";
|
|
@@ -8,8 +8,12 @@ import { build } from "tsdown";
|
|
|
8
8
|
import z from "zod";
|
|
9
9
|
import { input, select } from "@inquirer/prompts";
|
|
10
10
|
import { kebabCase } from "es-toolkit";
|
|
11
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
12
11
|
import fs$1, { createWriteStream } from "node:fs";
|
|
12
|
+
import os from "node:os";
|
|
13
|
+
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
14
|
+
import { Box, Text, render, useInput } from "ink";
|
|
15
|
+
import React from "react";
|
|
16
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
13
17
|
import { Readable } from "stream";
|
|
14
18
|
import { Readable as Readable$1 } from "node:stream";
|
|
15
19
|
import { ZipFile } from "yazl";
|
|
@@ -48,27 +52,6 @@ const PluginPermissionEnumSchema = z.enum([
|
|
|
48
52
|
]);
|
|
49
53
|
const PluginPermissionEnum = PluginPermissionEnumSchema.enum;
|
|
50
54
|
|
|
51
|
-
//#endregion
|
|
52
|
-
//#region ../../packages/domain/src/value-objects/plugin.vo.ts
|
|
53
|
-
const PluginUniqueIdSchema = z.object({
|
|
54
|
-
pluginId: z.string(),
|
|
55
|
-
version: z.string(),
|
|
56
|
-
etag: z.string()
|
|
57
|
-
});
|
|
58
|
-
const PluginSourceSchema = z.literal("system").or(z.string());
|
|
59
|
-
const PluginTagListItemSchema = z.object({
|
|
60
|
-
id: z.string(),
|
|
61
|
-
name: I18nStringStrictSchema
|
|
62
|
-
});
|
|
63
|
-
const PluginTagListSchema = z.array(PluginTagListItemSchema);
|
|
64
|
-
const PluginRuntimeModeSchema = z.enum(["localPool", "serverless"]);
|
|
65
|
-
const PluginRuntimeModeEnum = PluginRuntimeModeSchema.enum;
|
|
66
|
-
const UserPluginIdSchema = z.object({
|
|
67
|
-
pluginId: z.string(),
|
|
68
|
-
version: z.string().optional(),
|
|
69
|
-
source: PluginSourceSchema.optional()
|
|
70
|
-
});
|
|
71
|
-
|
|
72
55
|
//#endregion
|
|
73
56
|
//#region ../../packages/domain/src/entities/plugin-base.entity.ts
|
|
74
57
|
const PluginTagSchema = z.enum([
|
|
@@ -543,7 +526,7 @@ var CheckCommand = class extends BaseCommand {
|
|
|
543
526
|
//#endregion
|
|
544
527
|
//#region package.json
|
|
545
528
|
var name = "@fastgpt-plugin/cli";
|
|
546
|
-
var version = "0.
|
|
529
|
+
var version = "0.2.0-alpha.2";
|
|
547
530
|
|
|
548
531
|
//#endregion
|
|
549
532
|
//#region src/constants.ts
|
|
@@ -703,6 +686,335 @@ var CreateCommand = class extends BaseCommand {
|
|
|
703
686
|
}
|
|
704
687
|
};
|
|
705
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
|
+
|
|
723
|
+
//#endregion
|
|
724
|
+
//#region src/debug/discover.ts
|
|
725
|
+
const IGNORED_DIRS = new Set([
|
|
726
|
+
".build",
|
|
727
|
+
".fastgpt-plugin-debug",
|
|
728
|
+
".git",
|
|
729
|
+
".turbo",
|
|
730
|
+
".vite",
|
|
731
|
+
"coverage",
|
|
732
|
+
"dist",
|
|
733
|
+
"node_modules",
|
|
734
|
+
"__pack_output__"
|
|
735
|
+
]);
|
|
736
|
+
async function discoverDebugEntries(cwd = process.cwd()) {
|
|
737
|
+
const root = path.resolve(cwd);
|
|
738
|
+
if (await hasIndexFile(root)) return [root];
|
|
739
|
+
const children = await fs.readdir(root, { withFileTypes: true });
|
|
740
|
+
const entries = [];
|
|
741
|
+
for (const child of children) {
|
|
742
|
+
if (!child.isDirectory() || IGNORED_DIRS.has(child.name)) continue;
|
|
743
|
+
const candidate = path.join(root, child.name);
|
|
744
|
+
if (await hasIndexFile(candidate)) entries.push(candidate);
|
|
745
|
+
}
|
|
746
|
+
entries.sort();
|
|
747
|
+
if (entries.length === 0) throw new Error(`当前目录未发现可调试插件,请传入插件目录或在当前目录下放置 index.ts。`);
|
|
748
|
+
return entries;
|
|
749
|
+
}
|
|
750
|
+
async function hasIndexFile(dir) {
|
|
751
|
+
try {
|
|
752
|
+
return (await fs.stat(path.join(dir, "index.ts"))).isFile();
|
|
753
|
+
} catch {
|
|
754
|
+
return false;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
//#endregion
|
|
759
|
+
//#region ../../packages/domain/src/value-objects/connection-gateway.vo.ts
|
|
760
|
+
const ConnectionGatewayConsumerTypeSchema = z.string().min(1).max(64);
|
|
761
|
+
const ConnectionGatewayTransportSchema = z.literal("websocket");
|
|
762
|
+
const ConnectionGatewayCapabilitySchema = z.string().min(1).max(64);
|
|
763
|
+
const ConnectionGatewaySessionScopeSchema = z.object({
|
|
764
|
+
userId: z.string().min(1),
|
|
765
|
+
teamId: z.string().min(1).optional(),
|
|
766
|
+
source: z.string().min(1).optional(),
|
|
767
|
+
sources: z.array(z.string().min(1)).optional()
|
|
768
|
+
});
|
|
769
|
+
const ConnectionGatewayTokenClaimsSchema = z.object({
|
|
770
|
+
consumerType: ConnectionGatewayConsumerTypeSchema,
|
|
771
|
+
subject: z.string().min(1),
|
|
772
|
+
sessionScope: ConnectionGatewaySessionScopeSchema,
|
|
773
|
+
transport: ConnectionGatewayTransportSchema,
|
|
774
|
+
capabilities: z.array(ConnectionGatewayCapabilitySchema).default([]),
|
|
775
|
+
expiresAt: z.number().int().positive(),
|
|
776
|
+
issuedAt: z.number().int().positive().optional(),
|
|
777
|
+
nonce: z.string().min(1).optional()
|
|
778
|
+
});
|
|
779
|
+
const ConnectionGatewayEnvelopeSchema = z.object({
|
|
780
|
+
protocol: z.literal("connection-gateway.v1"),
|
|
781
|
+
sessionId: z.string().min(1),
|
|
782
|
+
generation: z.number().int().nonnegative(),
|
|
783
|
+
requestId: z.string().min(1).optional(),
|
|
784
|
+
type: z.enum([
|
|
785
|
+
"request",
|
|
786
|
+
"response",
|
|
787
|
+
"event",
|
|
788
|
+
"stream"
|
|
789
|
+
]),
|
|
790
|
+
consumerType: ConnectionGatewayConsumerTypeSchema,
|
|
791
|
+
capability: ConnectionGatewayCapabilitySchema.optional(),
|
|
792
|
+
traceId: z.string().optional(),
|
|
793
|
+
payload: z.unknown().optional(),
|
|
794
|
+
createdAt: z.number().int().positive()
|
|
795
|
+
});
|
|
796
|
+
const ConnectionGatewayWsProtocolSchema = z.literal("connection-gateway.ws.v1");
|
|
797
|
+
const ConnectionGatewayWsBindMessageSchema = z.object({
|
|
798
|
+
protocol: ConnectionGatewayWsProtocolSchema,
|
|
799
|
+
type: z.literal("bind"),
|
|
800
|
+
requestId: z.string().min(1),
|
|
801
|
+
token: z.string().min(1),
|
|
802
|
+
metadata: z.record(z.string(), z.unknown()).optional()
|
|
803
|
+
});
|
|
804
|
+
const ConnectionGatewayWsEnvelopeMessageSchema = z.object({
|
|
805
|
+
protocol: ConnectionGatewayWsProtocolSchema,
|
|
806
|
+
type: z.literal("envelope"),
|
|
807
|
+
envelope: ConnectionGatewayEnvelopeSchema
|
|
808
|
+
});
|
|
809
|
+
const ConnectionGatewayWsHeartbeatMessageSchema = z.object({
|
|
810
|
+
protocol: ConnectionGatewayWsProtocolSchema,
|
|
811
|
+
type: z.literal("heartbeat"),
|
|
812
|
+
ts: z.number().int().positive()
|
|
813
|
+
});
|
|
814
|
+
const ConnectionGatewayWsErrorMessageSchema = z.object({
|
|
815
|
+
protocol: ConnectionGatewayWsProtocolSchema,
|
|
816
|
+
type: z.literal("error"),
|
|
817
|
+
requestId: z.string().min(1).optional(),
|
|
818
|
+
code: z.string().min(1),
|
|
819
|
+
message: z.string().min(1)
|
|
820
|
+
});
|
|
821
|
+
const ConnectionGatewayWsBoundMessageSchema = z.object({
|
|
822
|
+
protocol: ConnectionGatewayWsProtocolSchema,
|
|
823
|
+
type: z.literal("bound"),
|
|
824
|
+
requestId: z.string().min(1),
|
|
825
|
+
session: z.lazy(() => ConnectionGatewaySessionSchema)
|
|
826
|
+
});
|
|
827
|
+
const ConnectionGatewayWsClientMessageSchema = z.discriminatedUnion("type", [
|
|
828
|
+
ConnectionGatewayWsBindMessageSchema,
|
|
829
|
+
ConnectionGatewayWsEnvelopeMessageSchema,
|
|
830
|
+
ConnectionGatewayWsHeartbeatMessageSchema
|
|
831
|
+
]);
|
|
832
|
+
const ConnectionGatewayWsServerMessageSchema = z.discriminatedUnion("type", [
|
|
833
|
+
ConnectionGatewayWsBoundMessageSchema,
|
|
834
|
+
ConnectionGatewayWsEnvelopeMessageSchema,
|
|
835
|
+
ConnectionGatewayWsHeartbeatMessageSchema,
|
|
836
|
+
ConnectionGatewayWsErrorMessageSchema
|
|
837
|
+
]);
|
|
838
|
+
const ConnectionGatewaySessionStatusSchema = z.enum([
|
|
839
|
+
"connecting",
|
|
840
|
+
"connected",
|
|
841
|
+
"draining",
|
|
842
|
+
"closed"
|
|
843
|
+
]);
|
|
844
|
+
const ConnectionGatewaySessionSchema = z.object({
|
|
845
|
+
id: z.string().min(1),
|
|
846
|
+
consumerType: ConnectionGatewayConsumerTypeSchema,
|
|
847
|
+
subject: z.string().min(1),
|
|
848
|
+
sessionScope: ConnectionGatewaySessionScopeSchema,
|
|
849
|
+
transport: ConnectionGatewayTransportSchema,
|
|
850
|
+
capabilities: z.array(ConnectionGatewayCapabilitySchema),
|
|
851
|
+
generation: z.number().int().nonnegative(),
|
|
852
|
+
ownerNodeId: z.string().min(1),
|
|
853
|
+
status: ConnectionGatewaySessionStatusSchema,
|
|
854
|
+
connectedAt: z.number().int().positive(),
|
|
855
|
+
lastSeenAt: z.number().int().positive(),
|
|
856
|
+
expiresAt: z.number().int().positive(),
|
|
857
|
+
metadata: z.record(z.string(), z.unknown()).optional()
|
|
858
|
+
});
|
|
859
|
+
const ConnectionGatewaySessionStatusViewSchema = z.object({
|
|
860
|
+
session: ConnectionGatewaySessionSchema.nullable(),
|
|
861
|
+
ownerAlive: z.boolean(),
|
|
862
|
+
mailboxLag: z.number().int().nonnegative(),
|
|
863
|
+
logs: z.array(z.object({
|
|
864
|
+
ts: z.number().int().positive(),
|
|
865
|
+
level: z.enum([
|
|
866
|
+
"debug",
|
|
867
|
+
"info",
|
|
868
|
+
"warn",
|
|
869
|
+
"error"
|
|
870
|
+
]),
|
|
871
|
+
message: z.string(),
|
|
872
|
+
data: z.record(z.string(), z.unknown()).optional()
|
|
873
|
+
}))
|
|
874
|
+
});
|
|
875
|
+
const ConnectionGatewayMetricsSchema = z.object({
|
|
876
|
+
nodeId: z.string(),
|
|
877
|
+
activeConnections: z.number().int().nonnegative(),
|
|
878
|
+
activeSessions: z.number().int().nonnegative(),
|
|
879
|
+
inFlightRequests: z.number().int().nonnegative(),
|
|
880
|
+
streamBufferBytes: z.number().int().nonnegative(),
|
|
881
|
+
slowConsumers: z.number().int().nonnegative(),
|
|
882
|
+
ownerLeaseExpiries: z.number().int().nonnegative(),
|
|
883
|
+
mailbox: z.object({
|
|
884
|
+
lag: z.number().int().nonnegative(),
|
|
885
|
+
redisRoundTripMs: z.number().nonnegative()
|
|
886
|
+
}),
|
|
887
|
+
limits: z.object({
|
|
888
|
+
maxConnections: z.number().int().positive(),
|
|
889
|
+
maxSessionsPerSubject: z.number().int().positive(),
|
|
890
|
+
maxInFlightPerSession: z.number().int().positive(),
|
|
891
|
+
maxEnvelopeBytes: z.number().int().positive()
|
|
892
|
+
})
|
|
893
|
+
});
|
|
894
|
+
const ConnectionGatewayResourceLimitsSchema = z.object({
|
|
895
|
+
maxConnections: z.number().int().positive(),
|
|
896
|
+
maxSessionsPerSubject: z.number().int().positive(),
|
|
897
|
+
maxInFlightPerSession: z.number().int().positive(),
|
|
898
|
+
maxEnvelopeBytes: z.number().int().positive(),
|
|
899
|
+
slowConsumerBufferBytes: z.number().int().positive()
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
//#endregion
|
|
903
|
+
//#region ../../packages/domain/src/value-objects/system-var.vo.ts
|
|
904
|
+
const SystemVarSchema = z.object({
|
|
905
|
+
app: z.object({
|
|
906
|
+
id: z.string(),
|
|
907
|
+
name: z.string()
|
|
908
|
+
}),
|
|
909
|
+
chat: z.object({
|
|
910
|
+
chatId: z.string(),
|
|
911
|
+
uid: z.string().optional()
|
|
912
|
+
}),
|
|
913
|
+
invokeToken: z.string(),
|
|
914
|
+
time: z.string()
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
//#endregion
|
|
918
|
+
//#region ../../packages/domain/src/value-objects/plugin.vo.ts
|
|
919
|
+
const PluginUniqueIdSchema = z.object({
|
|
920
|
+
pluginId: z.string(),
|
|
921
|
+
version: z.string(),
|
|
922
|
+
etag: z.string()
|
|
923
|
+
});
|
|
924
|
+
const PluginSourceSchema = z.literal("system").or(z.string());
|
|
925
|
+
const PluginTagListItemSchema = z.object({
|
|
926
|
+
id: z.string(),
|
|
927
|
+
name: I18nStringStrictSchema
|
|
928
|
+
});
|
|
929
|
+
const PluginTagListSchema = z.array(PluginTagListItemSchema);
|
|
930
|
+
const PluginRuntimeModeSchema = z.enum(["localPool", "serverless"]);
|
|
931
|
+
const PluginRuntimeModeEnum = PluginRuntimeModeSchema.enum;
|
|
932
|
+
const UserPluginIdSchema = z.object({
|
|
933
|
+
pluginId: z.string(),
|
|
934
|
+
version: z.string().optional(),
|
|
935
|
+
source: PluginSourceSchema.optional()
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
//#endregion
|
|
939
|
+
//#region ../../packages/domain/src/value-objects/tool.vo.ts
|
|
940
|
+
const ToolHandlerReturnSchema = z.record(z.string(), z.unknown());
|
|
941
|
+
const StreamMessageTypeSchema = z.enum([
|
|
942
|
+
"response",
|
|
943
|
+
"error",
|
|
944
|
+
"stream"
|
|
945
|
+
]);
|
|
946
|
+
const StreamMessageTypeEnum = StreamMessageTypeSchema.enum;
|
|
947
|
+
const StreamDataAnswerTypeSchema = z.enum(["answer", "fastAnswer"]);
|
|
948
|
+
const StreamDataAnswerTypeEnum = StreamDataAnswerTypeSchema.enum;
|
|
949
|
+
const ToolAnswerSchema = z.object({
|
|
950
|
+
type: StreamDataAnswerTypeSchema,
|
|
951
|
+
content: z.string()
|
|
952
|
+
});
|
|
953
|
+
const ToolStreamMessageSchema = z.discriminatedUnion("type", [
|
|
954
|
+
z.object({
|
|
955
|
+
type: z.literal(StreamMessageTypeEnum.response),
|
|
956
|
+
data: ToolHandlerReturnSchema
|
|
957
|
+
}),
|
|
958
|
+
z.object({
|
|
959
|
+
type: z.literal(StreamMessageTypeEnum.stream),
|
|
960
|
+
data: ToolAnswerSchema
|
|
961
|
+
}),
|
|
962
|
+
z.object({
|
|
963
|
+
type: z.literal(StreamMessageTypeEnum.error),
|
|
964
|
+
data: z.string()
|
|
965
|
+
})
|
|
966
|
+
]);
|
|
967
|
+
const ToolRunInputSchema = z.object({
|
|
968
|
+
pluginId: z.string(),
|
|
969
|
+
version: z.preprocess((value) => {
|
|
970
|
+
if (value === "") return void 0;
|
|
971
|
+
return value;
|
|
972
|
+
}, z.string().optional()),
|
|
973
|
+
source: PluginSourceSchema.optional(),
|
|
974
|
+
childId: z.string().optional(),
|
|
975
|
+
input: z.record(z.string(), z.unknown()),
|
|
976
|
+
secrets: z.record(z.string(), z.unknown()).optional(),
|
|
977
|
+
systemVar: SystemVarSchema
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
//#endregion
|
|
981
|
+
//#region ../../packages/domain/src/value-objects/connection-gateway-debug.vo.ts
|
|
982
|
+
const CONNECTION_GATEWAY_PLUGIN_DEBUG_INVOKE_CAPABILITY = "invoke";
|
|
983
|
+
const ConnectionGatewayPluginDebugRequestPayloadSchema = z.object({
|
|
984
|
+
kind: z.literal("plugin-debug.run"),
|
|
985
|
+
eventName: z.literal("run"),
|
|
986
|
+
payload: z.object({
|
|
987
|
+
pluginId: z.string().min(1).optional(),
|
|
988
|
+
input: z.record(z.string(), z.unknown()),
|
|
989
|
+
secrets: z.record(z.string(), z.unknown()).optional(),
|
|
990
|
+
systemVar: SystemVarSchema,
|
|
991
|
+
source: z.string().optional(),
|
|
992
|
+
childId: z.string().optional()
|
|
993
|
+
})
|
|
994
|
+
});
|
|
995
|
+
const ConnectionGatewayPluginDebugResponsePayloadSchema = z.discriminatedUnion("kind", [z.object({ kind: z.literal("plugin-debug.accepted") }), z.object({
|
|
996
|
+
kind: z.literal("plugin-debug.error"),
|
|
997
|
+
message: z.string(),
|
|
998
|
+
code: z.string().optional(),
|
|
999
|
+
data: z.unknown().optional()
|
|
1000
|
+
})]);
|
|
1001
|
+
const ConnectionGatewayPluginDebugStreamPayloadSchema = z.discriminatedUnion("event", [
|
|
1002
|
+
z.object({
|
|
1003
|
+
kind: z.literal("plugin-debug.stream"),
|
|
1004
|
+
event: z.literal("chunk"),
|
|
1005
|
+
data: ToolStreamMessageSchema
|
|
1006
|
+
}),
|
|
1007
|
+
z.object({
|
|
1008
|
+
kind: z.literal("plugin-debug.stream"),
|
|
1009
|
+
event: z.literal("end")
|
|
1010
|
+
}),
|
|
1011
|
+
z.object({
|
|
1012
|
+
kind: z.literal("plugin-debug.stream"),
|
|
1013
|
+
event: z.literal("error"),
|
|
1014
|
+
message: z.string()
|
|
1015
|
+
})
|
|
1016
|
+
]);
|
|
1017
|
+
|
|
706
1018
|
//#endregion
|
|
707
1019
|
//#region src/debug/import-hooks.ts
|
|
708
1020
|
const SOURCE_EXTENSIONS = [
|
|
@@ -719,7 +1031,11 @@ const RESOLVE_EXTENSIONS = [
|
|
|
719
1031
|
".cjs",
|
|
720
1032
|
".json"
|
|
721
1033
|
];
|
|
1034
|
+
const TRANSFORM_PROBE_SOURCE = "const __fastgptPluginTransformProbe__: number = 1;";
|
|
722
1035
|
let hooksRegistered = false;
|
|
1036
|
+
let transformModeSupported;
|
|
1037
|
+
let cachedTypeScriptCompiler;
|
|
1038
|
+
const localRequire = createRequire(import.meta.url);
|
|
723
1039
|
function ensureDebugImportHooks() {
|
|
724
1040
|
if (hooksRegistered) return;
|
|
725
1041
|
hooksRegistered = true;
|
|
@@ -743,15 +1059,90 @@ function ensureDebugImportHooks() {
|
|
|
743
1059
|
if (SOURCE_EXTENSIONS.includes(ext)) return {
|
|
744
1060
|
format: "module",
|
|
745
1061
|
shortCircuit: true,
|
|
746
|
-
source:
|
|
747
|
-
|
|
748
|
-
|
|
1062
|
+
source: compileTypeScriptModuleSource(fs$1.readFileSync(fileURLToPath(parsed), "utf-8"), {
|
|
1063
|
+
filePath: fileURLToPath(parsed),
|
|
1064
|
+
workspaceRoot
|
|
749
1065
|
})
|
|
750
1066
|
};
|
|
751
1067
|
return nextLoad(url, context);
|
|
752
1068
|
}
|
|
753
1069
|
});
|
|
754
1070
|
}
|
|
1071
|
+
function compileTypeScriptModuleSource(sourceText, { filePath, workspaceRoot, stripTypeScriptTypesFn = stripTypeScriptTypes, typeScriptCompiler }) {
|
|
1072
|
+
if (supportsTransformMode(stripTypeScriptTypesFn)) try {
|
|
1073
|
+
return stripTypeScriptTypesFn(sourceText, {
|
|
1074
|
+
mode: "transform",
|
|
1075
|
+
sourceMap: false
|
|
1076
|
+
});
|
|
1077
|
+
} catch {}
|
|
1078
|
+
return transpileTypeScriptModuleSource(sourceText, {
|
|
1079
|
+
filePath,
|
|
1080
|
+
workspaceRoot,
|
|
1081
|
+
typeScriptCompiler
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
function supportsTransformMode(stripTypeScriptTypesFn) {
|
|
1085
|
+
if (stripTypeScriptTypesFn === stripTypeScriptTypes) {
|
|
1086
|
+
if (transformModeSupported !== void 0) return transformModeSupported;
|
|
1087
|
+
transformModeSupported = canTransformTypeScript(stripTypeScriptTypesFn);
|
|
1088
|
+
return transformModeSupported;
|
|
1089
|
+
}
|
|
1090
|
+
return canTransformTypeScript(stripTypeScriptTypesFn);
|
|
1091
|
+
}
|
|
1092
|
+
function canTransformTypeScript(stripTypeScriptTypesFn) {
|
|
1093
|
+
try {
|
|
1094
|
+
stripTypeScriptTypesFn(TRANSFORM_PROBE_SOURCE, {
|
|
1095
|
+
mode: "transform",
|
|
1096
|
+
sourceMap: false
|
|
1097
|
+
});
|
|
1098
|
+
return true;
|
|
1099
|
+
} catch {
|
|
1100
|
+
return false;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
function transpileTypeScriptModuleSource(sourceText, { filePath, workspaceRoot, typeScriptCompiler }) {
|
|
1104
|
+
const compiler = typeScriptCompiler ?? loadTypeScriptCompiler(filePath, workspaceRoot);
|
|
1105
|
+
const transpiled = compiler.transpileModule(sourceText, {
|
|
1106
|
+
fileName: filePath,
|
|
1107
|
+
reportDiagnostics: true,
|
|
1108
|
+
compilerOptions: {
|
|
1109
|
+
module: compiler.ModuleKind.ESNext,
|
|
1110
|
+
target: compiler.ScriptTarget.ES2022,
|
|
1111
|
+
moduleResolution: compiler.ModuleResolutionKind.Bundler,
|
|
1112
|
+
jsx: compiler.JsxEmit.ReactJSX,
|
|
1113
|
+
sourceMap: false,
|
|
1114
|
+
inlineSourceMap: false,
|
|
1115
|
+
inlineSources: false,
|
|
1116
|
+
isolatedModules: true,
|
|
1117
|
+
esModuleInterop: true,
|
|
1118
|
+
allowImportingTsExtensions: true,
|
|
1119
|
+
verbatimModuleSyntax: true
|
|
1120
|
+
}
|
|
1121
|
+
});
|
|
1122
|
+
const errors = transpiled.diagnostics?.filter((diagnostic) => diagnostic.category === compiler.DiagnosticCategory.Error) ?? [];
|
|
1123
|
+
if (errors.length > 0) {
|
|
1124
|
+
const formatted = errors.map((diagnostic) => formatDiagnosticMessage(compiler, diagnostic)).join("\n");
|
|
1125
|
+
throw new Error(`Failed to transpile TypeScript debug module: ${filePath}\n${formatted}`);
|
|
1126
|
+
}
|
|
1127
|
+
return transpiled.outputText;
|
|
1128
|
+
}
|
|
1129
|
+
function loadTypeScriptCompiler(filePath, workspaceRoot) {
|
|
1130
|
+
if (cachedTypeScriptCompiler) return cachedTypeScriptCompiler;
|
|
1131
|
+
const resolvePaths = [
|
|
1132
|
+
path.dirname(filePath),
|
|
1133
|
+
process.cwd(),
|
|
1134
|
+
workspaceRoot,
|
|
1135
|
+
path.dirname(fileURLToPath(import.meta.url))
|
|
1136
|
+
].filter((value) => Boolean(value));
|
|
1137
|
+
cachedTypeScriptCompiler = localRequire(localRequire.resolve("typescript", { paths: resolvePaths }));
|
|
1138
|
+
return cachedTypeScriptCompiler;
|
|
1139
|
+
}
|
|
1140
|
+
function formatDiagnosticMessage(compiler, diagnostic) {
|
|
1141
|
+
const message = compiler.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
|
1142
|
+
if (!diagnostic.file || diagnostic.start === void 0) return message;
|
|
1143
|
+
const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
|
|
1144
|
+
return `${line + 1}:${character + 1} ${message}`;
|
|
1145
|
+
}
|
|
755
1146
|
function resolveSpecifier({ specifier, parentURL, workspaceRoot }) {
|
|
756
1147
|
if (specifier.startsWith("node:")) return null;
|
|
757
1148
|
if (specifier.startsWith("file:")) return toFileURL(resolveExistingPath(fileURLToPath(new URL(specifier))));
|
|
@@ -913,9 +1304,188 @@ const InvokeMethodEnumSchema = z.enum([
|
|
|
913
1304
|
]);
|
|
914
1305
|
const InvokeMethodEnum = InvokeMethodEnumSchema.enum;
|
|
915
1306
|
|
|
1307
|
+
//#endregion
|
|
1308
|
+
//#region ../../packages/domain/src/value-objects/error.vo.ts
|
|
1309
|
+
const DEFAULT_INTERNAL_REASON = {
|
|
1310
|
+
en: "Internal Server Error",
|
|
1311
|
+
"zh-CN": "服务器内部错误"
|
|
1312
|
+
};
|
|
1313
|
+
var RegisteredError = class extends Error {
|
|
1314
|
+
code;
|
|
1315
|
+
reason;
|
|
1316
|
+
httpStatus;
|
|
1317
|
+
telemetryKind;
|
|
1318
|
+
visibility;
|
|
1319
|
+
severity;
|
|
1320
|
+
data;
|
|
1321
|
+
constructor(definition, options = {}) {
|
|
1322
|
+
super(options.message ?? definition.message, { cause: normalizeErrorCause(options.cause) });
|
|
1323
|
+
this.name = "RegisteredError";
|
|
1324
|
+
this.code = definition.code;
|
|
1325
|
+
this.reason = options.reason ?? definition.reason;
|
|
1326
|
+
this.httpStatus = definition.httpStatus ?? 500;
|
|
1327
|
+
this.telemetryKind = definition.telemetryKind;
|
|
1328
|
+
this.visibility = definition.visibility ?? "public";
|
|
1329
|
+
this.severity = definition.severity ?? "expected";
|
|
1330
|
+
this.data = options.data;
|
|
1331
|
+
}
|
|
1332
|
+
};
|
|
1333
|
+
function createReasonError(reason, options = {}) {
|
|
1334
|
+
const cause = normalizeErrorCause(options.cause);
|
|
1335
|
+
const error = cause === void 0 ? new Error(reason.en) : new Error(reason.en, { cause });
|
|
1336
|
+
return Object.assign(error, { reason });
|
|
1337
|
+
}
|
|
1338
|
+
function normalizeToError(error, fallbackMessage = "Unknown error") {
|
|
1339
|
+
if (error instanceof Error) return error;
|
|
1340
|
+
if (typeof error === "string") return new Error(error);
|
|
1341
|
+
if (isI18nString(error)) return createReasonError(error);
|
|
1342
|
+
if (isResultFailureLike(error)) {
|
|
1343
|
+
const reason = isI18nString(error.reason) ? error.reason : void 0;
|
|
1344
|
+
const cause = normalizeToError(error.error, reason?.en ?? fallbackMessage);
|
|
1345
|
+
if (reason && isSameI18nString(reason, getErrorReason(cause))) return cause;
|
|
1346
|
+
return reason ? createReasonError(reason, { cause }) : new Error(fallbackMessage, { cause });
|
|
1347
|
+
}
|
|
1348
|
+
return new Error(getUnknownErrorMessage(error, fallbackMessage), { cause: error });
|
|
1349
|
+
}
|
|
1350
|
+
function getErrorReason(error, fallback) {
|
|
1351
|
+
if (error instanceof RegisteredError) return error.visibility === "internal" ? DEFAULT_INTERNAL_REASON : error.reason;
|
|
1352
|
+
if (isI18nString(error)) return error;
|
|
1353
|
+
const record = isRecord(error) ? error : void 0;
|
|
1354
|
+
if (record && isI18nString(record.reason)) return record.reason;
|
|
1355
|
+
if (record && isI18nString(record.error)) return record.error;
|
|
1356
|
+
if (fallback) return fallback;
|
|
1357
|
+
if (error instanceof Error) return {
|
|
1358
|
+
en: error.message,
|
|
1359
|
+
"zh-CN": error.message
|
|
1360
|
+
};
|
|
1361
|
+
if (typeof error === "string") return {
|
|
1362
|
+
en: error,
|
|
1363
|
+
"zh-CN": error
|
|
1364
|
+
};
|
|
1365
|
+
return DEFAULT_INTERNAL_REASON;
|
|
1366
|
+
}
|
|
1367
|
+
function serializeError(error, options) {
|
|
1368
|
+
return serializeErrorInner(error, {
|
|
1369
|
+
includeStack: options?.includeStack ?? false,
|
|
1370
|
+
depth: 0,
|
|
1371
|
+
seen: /* @__PURE__ */ new WeakSet()
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
function normalizeErrorCause(cause) {
|
|
1375
|
+
if (cause === void 0 || cause instanceof Error) return cause;
|
|
1376
|
+
if (typeof cause === "string") return new Error(cause);
|
|
1377
|
+
if (isI18nString(cause)) return createReasonError(cause);
|
|
1378
|
+
if (isResultFailureLike(cause)) return normalizeToError(cause);
|
|
1379
|
+
return cause;
|
|
1380
|
+
}
|
|
1381
|
+
function serializeErrorInner(error, state) {
|
|
1382
|
+
if (state.depth > 5) return {
|
|
1383
|
+
name: "Error",
|
|
1384
|
+
message: "Error cause depth exceeded"
|
|
1385
|
+
};
|
|
1386
|
+
if (error instanceof RegisteredError) return {
|
|
1387
|
+
name: error.name,
|
|
1388
|
+
code: error.code,
|
|
1389
|
+
message: error.message,
|
|
1390
|
+
reason: error.reason,
|
|
1391
|
+
httpStatus: error.httpStatus,
|
|
1392
|
+
telemetryKind: error.telemetryKind,
|
|
1393
|
+
visibility: error.visibility,
|
|
1394
|
+
data: error.data,
|
|
1395
|
+
...state.includeStack && error.stack !== void 0 ? { stack: error.stack } : {},
|
|
1396
|
+
...error.cause !== void 0 ? { cause: serializeErrorInner(error.cause, nextSerializeState(state, error)) } : {}
|
|
1397
|
+
};
|
|
1398
|
+
if (error instanceof Error) {
|
|
1399
|
+
const reason = getI18nStringField(error, "reason");
|
|
1400
|
+
return {
|
|
1401
|
+
name: error.name,
|
|
1402
|
+
code: getStringField(error, "code"),
|
|
1403
|
+
message: error.message,
|
|
1404
|
+
...reason !== void 0 ? { reason } : {},
|
|
1405
|
+
data: getUnknownField(error, "data"),
|
|
1406
|
+
...state.includeStack && error.stack !== void 0 ? { stack: error.stack } : {},
|
|
1407
|
+
...error.cause !== void 0 ? { cause: serializeErrorInner(error.cause, nextSerializeState(state, error)) } : {}
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
if (typeof error === "string") return {
|
|
1411
|
+
name: "Error",
|
|
1412
|
+
message: error
|
|
1413
|
+
};
|
|
1414
|
+
return {
|
|
1415
|
+
name: "Error",
|
|
1416
|
+
message: getUnknownErrorMessage(error, "Unknown error")
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
function nextSerializeState(state, error) {
|
|
1420
|
+
if (state.seen.has(error)) return {
|
|
1421
|
+
...state,
|
|
1422
|
+
depth: 6
|
|
1423
|
+
};
|
|
1424
|
+
state.seen.add(error);
|
|
1425
|
+
return {
|
|
1426
|
+
...state,
|
|
1427
|
+
depth: state.depth + 1
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
1430
|
+
function getUnknownErrorMessage(error, fallback) {
|
|
1431
|
+
if (error === null || error === void 0) return fallback;
|
|
1432
|
+
if (isRecord(error)) {
|
|
1433
|
+
const message = error.message ?? error.msg;
|
|
1434
|
+
if (typeof message === "string" && message.length > 0) return message;
|
|
1435
|
+
}
|
|
1436
|
+
return String(error);
|
|
1437
|
+
}
|
|
1438
|
+
function getStringField(error, field) {
|
|
1439
|
+
const value = error[field];
|
|
1440
|
+
return typeof value === "string" ? value : void 0;
|
|
1441
|
+
}
|
|
1442
|
+
function getI18nStringField(error, field) {
|
|
1443
|
+
const value = error[field];
|
|
1444
|
+
return isI18nString(value) ? value : void 0;
|
|
1445
|
+
}
|
|
1446
|
+
function getUnknownField(error, field) {
|
|
1447
|
+
return error[field];
|
|
1448
|
+
}
|
|
1449
|
+
function isI18nString(value) {
|
|
1450
|
+
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");
|
|
1451
|
+
}
|
|
1452
|
+
function isSameI18nString(left, right) {
|
|
1453
|
+
return left.en === right.en && left["zh-CN"] === right["zh-CN"] && left["zh-Hant"] === right["zh-Hant"];
|
|
1454
|
+
}
|
|
1455
|
+
function isRecord(value) {
|
|
1456
|
+
return Boolean(value && typeof value === "object");
|
|
1457
|
+
}
|
|
1458
|
+
function isResultFailureLike(value) {
|
|
1459
|
+
return isRecord(value) && "error" in value && ("reason" in value || isRecord(value.error));
|
|
1460
|
+
}
|
|
1461
|
+
|
|
916
1462
|
//#endregion
|
|
917
1463
|
//#region ../../packages/domain/src/value-objects/result.vo.ts
|
|
918
1464
|
const successResult = (data) => [data, null];
|
|
1465
|
+
function failureResult(reasonOrFailure, error) {
|
|
1466
|
+
if (isResultFailure(reasonOrFailure)) return [null, toResultFailure(reasonOrFailure.reason, reasonOrFailure.error, reasonOrFailure)];
|
|
1467
|
+
if (reasonOrFailure instanceof Error) return [null, toResultFailure(getErrorReason(reasonOrFailure), reasonOrFailure)];
|
|
1468
|
+
return [null, toResultFailure(reasonOrFailure, error === void 0 ? createReasonError(reasonOrFailure) : createLegacyFailureError(reasonOrFailure, error))];
|
|
1469
|
+
}
|
|
1470
|
+
function isResultFailure(value) {
|
|
1471
|
+
return Boolean(value && typeof value === "object" && "reason" in value && "error" in value);
|
|
1472
|
+
}
|
|
1473
|
+
function createLegacyFailureError(reason, cause) {
|
|
1474
|
+
return createReasonError(reason, { cause: normalizeToError(cause, reason.en) });
|
|
1475
|
+
}
|
|
1476
|
+
function toResultFailure(reason, error, existing) {
|
|
1477
|
+
const serialized = serializeError(error);
|
|
1478
|
+
const code = existing?.code ?? serialized.code;
|
|
1479
|
+
const message = existing?.message ?? serialized.message;
|
|
1480
|
+
const data = existing && "data" in existing ? existing.data : serialized.data;
|
|
1481
|
+
return {
|
|
1482
|
+
reason,
|
|
1483
|
+
error,
|
|
1484
|
+
...code !== void 0 ? { code } : {},
|
|
1485
|
+
...message !== void 0 ? { message } : {},
|
|
1486
|
+
...data !== void 0 ? { data } : {}
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
919
1489
|
|
|
920
1490
|
//#endregion
|
|
921
1491
|
//#region ../../packages/domain/src/value-objects/stream.vo.ts
|
|
@@ -977,63 +1547,6 @@ var StreamData = class StreamData {
|
|
|
977
1547
|
}
|
|
978
1548
|
};
|
|
979
1549
|
|
|
980
|
-
//#endregion
|
|
981
|
-
//#region ../../packages/domain/src/value-objects/system-var.vo.ts
|
|
982
|
-
const SystemVarSchema = z.object({
|
|
983
|
-
app: z.object({
|
|
984
|
-
id: z.string(),
|
|
985
|
-
name: z.string()
|
|
986
|
-
}),
|
|
987
|
-
chat: z.object({
|
|
988
|
-
chatId: z.string(),
|
|
989
|
-
uid: z.string().optional()
|
|
990
|
-
}),
|
|
991
|
-
invokeToken: z.string(),
|
|
992
|
-
time: z.string()
|
|
993
|
-
});
|
|
994
|
-
|
|
995
|
-
//#endregion
|
|
996
|
-
//#region ../../packages/domain/src/value-objects/tool.vo.ts
|
|
997
|
-
const ToolHandlerReturnSchema = z.record(z.string(), z.unknown());
|
|
998
|
-
const StreamMessageTypeSchema = z.enum([
|
|
999
|
-
"response",
|
|
1000
|
-
"error",
|
|
1001
|
-
"stream"
|
|
1002
|
-
]);
|
|
1003
|
-
const StreamMessageTypeEnum = StreamMessageTypeSchema.enum;
|
|
1004
|
-
const StreamDataAnswerTypeSchema = z.enum(["answer", "fastAnswer"]);
|
|
1005
|
-
const StreamDataAnswerTypeEnum = StreamDataAnswerTypeSchema.enum;
|
|
1006
|
-
const ToolAnswerSchema = z.object({
|
|
1007
|
-
type: StreamDataAnswerTypeSchema,
|
|
1008
|
-
content: z.string()
|
|
1009
|
-
});
|
|
1010
|
-
const ToolStreamMessageSchema = z.discriminatedUnion("type", [
|
|
1011
|
-
z.object({
|
|
1012
|
-
type: z.literal(StreamMessageTypeEnum.response),
|
|
1013
|
-
data: ToolHandlerReturnSchema
|
|
1014
|
-
}),
|
|
1015
|
-
z.object({
|
|
1016
|
-
type: z.literal(StreamMessageTypeEnum.stream),
|
|
1017
|
-
data: ToolAnswerSchema
|
|
1018
|
-
}),
|
|
1019
|
-
z.object({
|
|
1020
|
-
type: z.literal(StreamMessageTypeEnum.error),
|
|
1021
|
-
data: z.string()
|
|
1022
|
-
})
|
|
1023
|
-
]);
|
|
1024
|
-
const ToolRunInputSchema = z.object({
|
|
1025
|
-
pluginId: z.string(),
|
|
1026
|
-
version: z.preprocess((value) => {
|
|
1027
|
-
if (value === "") return void 0;
|
|
1028
|
-
return value;
|
|
1029
|
-
}, z.string().optional()),
|
|
1030
|
-
source: PluginSourceSchema.optional(),
|
|
1031
|
-
childId: z.string().optional(),
|
|
1032
|
-
input: z.record(z.string(), z.unknown()),
|
|
1033
|
-
secrets: z.record(z.string(), z.unknown()).optional(),
|
|
1034
|
-
systemVar: SystemVarSchema
|
|
1035
|
-
});
|
|
1036
|
-
|
|
1037
1550
|
//#endregion
|
|
1038
1551
|
//#region ../../packages/infrastructure/src/plugin/plugin-runtime/ports/channel/event/client.ts
|
|
1039
1552
|
/**
|
|
@@ -1171,7 +1684,9 @@ const PluginChannelMessageIdSchema = z.union([z.string(), z.number()]);
|
|
|
1171
1684
|
const PluginChannelErrorSchema = z.object({
|
|
1172
1685
|
code: z.string(),
|
|
1173
1686
|
message: z.string(),
|
|
1174
|
-
|
|
1687
|
+
reason: I18nStringSchema.optional(),
|
|
1688
|
+
data: z.unknown().optional(),
|
|
1689
|
+
cause: z.lazy(() => PluginChannelErrorSchema).optional()
|
|
1175
1690
|
});
|
|
1176
1691
|
/**
|
|
1177
1692
|
* request message:需要对端返回 success/error。
|
|
@@ -1467,7 +1982,7 @@ function createIncomingStream({ source, streamName, streamId, traceId, meta }) {
|
|
|
1467
1982
|
};
|
|
1468
1983
|
}
|
|
1469
1984
|
function toStreamData(source) {
|
|
1470
|
-
if (source
|
|
1985
|
+
if (isStreamDataLike$2(source)) return source;
|
|
1471
1986
|
const output = StreamData.create();
|
|
1472
1987
|
(async () => {
|
|
1473
1988
|
try {
|
|
@@ -1479,6 +1994,9 @@ function toStreamData(source) {
|
|
|
1479
1994
|
})();
|
|
1480
1995
|
return output;
|
|
1481
1996
|
}
|
|
1997
|
+
function isStreamDataLike$2(source) {
|
|
1998
|
+
return source instanceof StreamData || typeof source === "object" && source !== null && typeof source.values === "function" && typeof source.consume === "function";
|
|
1999
|
+
}
|
|
1482
2000
|
|
|
1483
2001
|
//#endregion
|
|
1484
2002
|
//#region src/debug/session.ts
|
|
@@ -1511,7 +2029,7 @@ async function loadDebugSession({ entryDir, uploadDir }) {
|
|
|
1511
2029
|
else process.env.RUNTIME_MODE = previousRuntimeMode;
|
|
1512
2030
|
}
|
|
1513
2031
|
}
|
|
1514
|
-
async function runDebugTool({ runtime, snapshot, toolId, input, secrets, systemVar }) {
|
|
2032
|
+
async function runDebugTool({ runtime, snapshot, toolId, input, secrets, systemVar, traceId }) {
|
|
1515
2033
|
const targetTool = pickTargetTool(snapshot, toolId);
|
|
1516
2034
|
const mergedSystemVar = createDebugSystemVar(snapshot, targetTool.id, systemVar);
|
|
1517
2035
|
const response = await runtime.invokePlugin("run", {
|
|
@@ -1519,7 +2037,7 @@ async function runDebugTool({ runtime, snapshot, toolId, input, secrets, systemV
|
|
|
1519
2037
|
systemVar: mergedSystemVar,
|
|
1520
2038
|
...snapshot.isToolSet ? { childId: targetTool.id } : {},
|
|
1521
2039
|
...secrets ? { secrets } : {}
|
|
1522
|
-
}, { traceId: randomUUID() });
|
|
2040
|
+
}, { traceId: traceId ?? randomUUID() });
|
|
1523
2041
|
if (!response.output) throw new Error("调试运行未返回输出流。");
|
|
1524
2042
|
const streamMessages = [];
|
|
1525
2043
|
let finalResponse;
|
|
@@ -1579,7 +2097,7 @@ function createDebugSnapshot(tool, location) {
|
|
|
1579
2097
|
author: getOptionalString(userManifest.author),
|
|
1580
2098
|
tags: toStringArray(userManifest.tags),
|
|
1581
2099
|
permissions: toStringArray(userManifest.permission),
|
|
1582
|
-
secretSchema: ensurePlainObject(secretSchema),
|
|
2100
|
+
secretSchema: ensurePlainObject$1(secretSchema),
|
|
1583
2101
|
isToolSet: children.length > 0,
|
|
1584
2102
|
tools
|
|
1585
2103
|
};
|
|
@@ -1592,8 +2110,8 @@ function createSingleToolSnapshot(tool, userManifest) {
|
|
|
1592
2110
|
name: pickLocalizedText(userManifest.name),
|
|
1593
2111
|
description: pickLocalizedText(userManifest.description),
|
|
1594
2112
|
toolDescription: pickToolDescription(getOptionalString(userManifest.toolDescription), userManifest.description),
|
|
1595
|
-
inputSchema: ensurePlainObject(z.toJSONSchema(handler.inputSchema)),
|
|
1596
|
-
outputSchema: ensurePlainObject(z.toJSONSchema(handler.outputSchema))
|
|
2113
|
+
inputSchema: ensurePlainObject$1(z.toJSONSchema(handler.inputSchema)),
|
|
2114
|
+
outputSchema: ensurePlainObject$1(z.toJSONSchema(handler.outputSchema))
|
|
1597
2115
|
};
|
|
1598
2116
|
}
|
|
1599
2117
|
function createChildToolSnapshot(tool, child) {
|
|
@@ -1604,8 +2122,8 @@ function createChildToolSnapshot(tool, child) {
|
|
|
1604
2122
|
name: pickLocalizedText(child.name),
|
|
1605
2123
|
description: pickLocalizedText(child.description),
|
|
1606
2124
|
toolDescription: pickToolDescription(child.toolDescription, child.description),
|
|
1607
|
-
inputSchema: ensurePlainObject(z.toJSONSchema(handler.inputSchema)),
|
|
1608
|
-
outputSchema: ensurePlainObject(z.toJSONSchema(handler.outputSchema))
|
|
2125
|
+
inputSchema: ensurePlainObject$1(z.toJSONSchema(handler.inputSchema)),
|
|
2126
|
+
outputSchema: ensurePlainObject$1(z.toJSONSchema(handler.outputSchema))
|
|
1609
2127
|
};
|
|
1610
2128
|
}
|
|
1611
2129
|
function pickTargetTool(snapshot, toolId) {
|
|
@@ -1617,10 +2135,10 @@ function pickTargetTool(snapshot, toolId) {
|
|
|
1617
2135
|
}
|
|
1618
2136
|
function createVirtualHostHandler(uploadDir) {
|
|
1619
2137
|
return async ({ method, args, input }) => {
|
|
1620
|
-
const invokeArgs = ensurePlainObject(args);
|
|
2138
|
+
const invokeArgs = ensurePlainObject$1(args);
|
|
1621
2139
|
switch (method) {
|
|
1622
2140
|
case InvokeMethodEnum.uploadFile: {
|
|
1623
|
-
const buffer = await readSourceToBuffer(input);
|
|
2141
|
+
const buffer = await readSourceToBuffer$1(input);
|
|
1624
2142
|
const fileName = sanitizeFileName(typeof invokeArgs.fileName === "string" && invokeArgs.fileName.trim().length > 0 ? invokeArgs.fileName : "debug-upload.bin");
|
|
1625
2143
|
const contentType = typeof invokeArgs.contentType === "string" && invokeArgs.contentType.trim().length > 0 ? invokeArgs.contentType : "application/octet-stream";
|
|
1626
2144
|
const saveDir = path.resolve(uploadDir);
|
|
@@ -1640,14 +2158,17 @@ function createVirtualHostHandler(uploadDir) {
|
|
|
1640
2158
|
}
|
|
1641
2159
|
};
|
|
1642
2160
|
}
|
|
1643
|
-
async function readSourceToBuffer(source) {
|
|
2161
|
+
async function readSourceToBuffer$1(source) {
|
|
1644
2162
|
if (!source) return Buffer.alloc(0);
|
|
1645
2163
|
const chunks = [];
|
|
1646
|
-
const iterable = source
|
|
1647
|
-
for await (const chunk of iterable) chunks.push(toBuffer(chunk));
|
|
2164
|
+
const iterable = isStreamDataLike$1(source) ? source.values() : source;
|
|
2165
|
+
for await (const chunk of iterable) chunks.push(toBuffer$1(chunk));
|
|
1648
2166
|
return Buffer.concat(chunks);
|
|
1649
2167
|
}
|
|
1650
|
-
function
|
|
2168
|
+
function isStreamDataLike$1(source) {
|
|
2169
|
+
return source instanceof StreamData || typeof source === "object" && source !== null && typeof source.values === "function" && typeof source.consume === "function";
|
|
2170
|
+
}
|
|
2171
|
+
function toBuffer$1(value) {
|
|
1651
2172
|
if (Buffer.isBuffer(value)) return value;
|
|
1652
2173
|
if (value instanceof Uint8Array) return Buffer.from(value);
|
|
1653
2174
|
if (typeof value === "string") return Buffer.from(value);
|
|
@@ -1685,7 +2206,7 @@ function toStringArray(value) {
|
|
|
1685
2206
|
const items = value.filter((item) => typeof item === "string");
|
|
1686
2207
|
return items.length > 0 ? items : void 0;
|
|
1687
2208
|
}
|
|
1688
|
-
function ensurePlainObject(value) {
|
|
2209
|
+
function ensurePlainObject$1(value) {
|
|
1689
2210
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1690
2211
|
}
|
|
1691
2212
|
function sanitizeFileName(fileName) {
|
|
@@ -1714,22 +2235,1227 @@ async function assertPathExists(targetPath, message) {
|
|
|
1714
2235
|
}
|
|
1715
2236
|
}
|
|
1716
2237
|
|
|
2238
|
+
//#endregion
|
|
2239
|
+
//#region src/debug/gateway.ts
|
|
2240
|
+
const GATEWAY_DUPLICATE_CONNECTION_CODE = "connection_gateway.session_already_bound";
|
|
2241
|
+
async function connectDebugGateway({ targets, options, onLog }) {
|
|
2242
|
+
if (targets.length === 0) throw new Error("Gateway debug targets cannot be empty");
|
|
2243
|
+
if (options.reconnect) return connectReconnectingDebugGateway({
|
|
2244
|
+
targets,
|
|
2245
|
+
options,
|
|
2246
|
+
onLog
|
|
2247
|
+
});
|
|
2248
|
+
return connectSingleDebugGateway({
|
|
2249
|
+
targets,
|
|
2250
|
+
options,
|
|
2251
|
+
onLog
|
|
2252
|
+
});
|
|
2253
|
+
}
|
|
2254
|
+
async function connectReconnectingDebugGateway({ targets, options, onLog }) {
|
|
2255
|
+
let closed = false;
|
|
2256
|
+
let current = null;
|
|
2257
|
+
let currentSession = null;
|
|
2258
|
+
let latestTargets = targets;
|
|
2259
|
+
let hasAttemptedConnection = false;
|
|
2260
|
+
let currentOptions = options;
|
|
2261
|
+
const resolveReconnectOptions = options.resolveReconnectOptions;
|
|
2262
|
+
const intervalMs = Math.max(100, options.reconnectIntervalMs ?? 2e3);
|
|
2263
|
+
const closedPromise = (async () => {
|
|
2264
|
+
while (!closed) {
|
|
2265
|
+
try {
|
|
2266
|
+
const attemptOptions = hasAttemptedConnection && resolveReconnectOptions ? await resolveReconnectOptions() : currentOptions;
|
|
2267
|
+
hasAttemptedConnection = true;
|
|
2268
|
+
currentOptions = {
|
|
2269
|
+
...attemptOptions,
|
|
2270
|
+
resolveReconnectOptions
|
|
2271
|
+
};
|
|
2272
|
+
current = await connectSingleDebugGateway({
|
|
2273
|
+
targets: latestTargets,
|
|
2274
|
+
options: currentOptions,
|
|
2275
|
+
onLog
|
|
2276
|
+
});
|
|
2277
|
+
currentSession = current.session;
|
|
2278
|
+
await current.closed;
|
|
2279
|
+
} catch (error) {
|
|
2280
|
+
if (closed) return;
|
|
2281
|
+
if (isDuplicateConnectionError(error)) {
|
|
2282
|
+
closed = true;
|
|
2283
|
+
throw error;
|
|
2284
|
+
}
|
|
2285
|
+
onLog?.(`Connection Gateway 调试通道断开: ${formatErrorMessage(error)}`);
|
|
2286
|
+
} finally {
|
|
2287
|
+
current = null;
|
|
2288
|
+
}
|
|
2289
|
+
if (!closed) {
|
|
2290
|
+
onLog?.(`将在 ${intervalMs}ms 后重连 Connection Gateway。`);
|
|
2291
|
+
await delay(intervalMs);
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
})();
|
|
2295
|
+
while (!currentSession && !closed) await delay(10);
|
|
2296
|
+
if (!currentSession) throw new Error("Connection Gateway debug session was closed before connecting");
|
|
2297
|
+
return {
|
|
2298
|
+
session: currentSession,
|
|
2299
|
+
updateTargets(targets) {
|
|
2300
|
+
latestTargets = targets;
|
|
2301
|
+
current?.updateTargets(targets);
|
|
2302
|
+
},
|
|
2303
|
+
close() {
|
|
2304
|
+
closed = true;
|
|
2305
|
+
current?.close();
|
|
2306
|
+
},
|
|
2307
|
+
closed: closedPromise
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2310
|
+
async function connectSingleDebugGateway({ targets, options, onLog }) {
|
|
2311
|
+
const socket = await openWebSocket(options.gatewayUrl);
|
|
2312
|
+
let targetsByPluginId = createTargetsByPluginId(targets);
|
|
2313
|
+
let closed = false;
|
|
2314
|
+
let session = null;
|
|
2315
|
+
let boundResolve;
|
|
2316
|
+
let boundReject;
|
|
2317
|
+
const bound = new Promise((resolve, reject) => {
|
|
2318
|
+
boundResolve = resolve;
|
|
2319
|
+
boundReject = reject;
|
|
2320
|
+
});
|
|
2321
|
+
const closedPromise = new Promise((resolve) => {
|
|
2322
|
+
socket.addEventListener("close", () => {
|
|
2323
|
+
closed = true;
|
|
2324
|
+
resolve();
|
|
2325
|
+
});
|
|
2326
|
+
socket.addEventListener("error", () => {
|
|
2327
|
+
onLog?.("Connection Gateway WebSocket 错误");
|
|
2328
|
+
});
|
|
2329
|
+
});
|
|
2330
|
+
socket.addEventListener("message", (event) => {
|
|
2331
|
+
const message = parseWsServerMessage(event.data);
|
|
2332
|
+
if (message.type === "bound") {
|
|
2333
|
+
session = message.session;
|
|
2334
|
+
boundResolve(message.session);
|
|
2335
|
+
return;
|
|
2336
|
+
}
|
|
2337
|
+
if (message.type === "heartbeat") return;
|
|
2338
|
+
if (message.type === "error") {
|
|
2339
|
+
const error = createGatewayError(message.code, message.message);
|
|
2340
|
+
if (!session) boundReject(error);
|
|
2341
|
+
onLog?.(`Connection Gateway 错误: ${message.code} ${message.message}`);
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2344
|
+
if (message.type === "envelope") {
|
|
2345
|
+
const envelope = message.envelope;
|
|
2346
|
+
if (!session) {
|
|
2347
|
+
boundReject(/* @__PURE__ */ new Error("Gateway envelope received before session bound"));
|
|
2348
|
+
return;
|
|
2349
|
+
}
|
|
2350
|
+
handleGatewayEnvelope({
|
|
2351
|
+
socket,
|
|
2352
|
+
session,
|
|
2353
|
+
targetsByPluginId,
|
|
2354
|
+
envelope,
|
|
2355
|
+
onLog
|
|
2356
|
+
}).catch(async (error) => {
|
|
2357
|
+
if (session) await sendEnvelope(socket, makeErrorEnvelope(session, envelope, error));
|
|
2358
|
+
});
|
|
2359
|
+
}
|
|
2360
|
+
});
|
|
2361
|
+
await sendMessage(socket, {
|
|
2362
|
+
protocol: "connection-gateway.ws.v1",
|
|
2363
|
+
type: "bind",
|
|
2364
|
+
requestId: randomUUID(),
|
|
2365
|
+
token: options.connectToken,
|
|
2366
|
+
metadata: makePluginDebugMetadata(targets, options.source)
|
|
2367
|
+
}).catch(async (error) => {
|
|
2368
|
+
socket.close();
|
|
2369
|
+
throw error;
|
|
2370
|
+
});
|
|
2371
|
+
const boundSession = await bound;
|
|
2372
|
+
onLog?.(`已连接 Connection Gateway session: ${boundSession.id}`);
|
|
2373
|
+
return {
|
|
2374
|
+
session: boundSession,
|
|
2375
|
+
updateTargets(targets) {
|
|
2376
|
+
targetsByPluginId = createTargetsByPluginId(targets);
|
|
2377
|
+
onLog?.(`已热更新本地调试插件: ${targets.map((target) => target.snapshot.pluginId).join(", ")}`);
|
|
2378
|
+
},
|
|
2379
|
+
close() {
|
|
2380
|
+
if (!closed) socket.close();
|
|
2381
|
+
},
|
|
2382
|
+
closed: closedPromise
|
|
2383
|
+
};
|
|
2384
|
+
}
|
|
2385
|
+
function createTargetsByPluginId(targets) {
|
|
2386
|
+
return new Map(targets.map((target) => [target.snapshot.pluginId, target]));
|
|
2387
|
+
}
|
|
2388
|
+
async function handleGatewayEnvelope({ socket, session, targetsByPluginId, envelope, onLog }) {
|
|
2389
|
+
if (envelope.type !== "request") return;
|
|
2390
|
+
const request = ConnectionGatewayPluginDebugRequestPayloadSchema.parse(envelope.payload);
|
|
2391
|
+
const source = request.payload.source ?? session.sessionScope.source;
|
|
2392
|
+
if (!source) throw new Error("Gateway debug request missing source");
|
|
2393
|
+
const pluginId = request.payload.pluginId;
|
|
2394
|
+
if (!pluginId) throw new Error("Gateway debug request missing pluginId");
|
|
2395
|
+
const target = targetsByPluginId.get(pluginId);
|
|
2396
|
+
if (!target) throw new Error(`Gateway debug target not found: ${source} ${pluginId}`);
|
|
2397
|
+
const traceId = envelope.traceId ?? requiredRequestId(envelope);
|
|
2398
|
+
onLog?.(`收到远程调试请求: ${source} ${pluginId} ${envelope.requestId ?? "-"}`);
|
|
2399
|
+
await sendEnvelope(socket, {
|
|
2400
|
+
protocol: "connection-gateway.v1",
|
|
2401
|
+
sessionId: session.id,
|
|
2402
|
+
generation: session.generation,
|
|
2403
|
+
requestId: requiredRequestId(envelope),
|
|
2404
|
+
type: "response",
|
|
2405
|
+
consumerType: session.consumerType,
|
|
2406
|
+
capability: CONNECTION_GATEWAY_PLUGIN_DEBUG_INVOKE_CAPABILITY,
|
|
2407
|
+
traceId: envelope.traceId,
|
|
2408
|
+
createdAt: Date.now(),
|
|
2409
|
+
payload: { kind: "plugin-debug.accepted" }
|
|
2410
|
+
});
|
|
2411
|
+
target.invokeBridge?.bind(traceId, { invokeToken: request.payload.systemVar.invokeToken });
|
|
2412
|
+
try {
|
|
2413
|
+
const result = await runDebugTool({
|
|
2414
|
+
runtime: target.runtime,
|
|
2415
|
+
snapshot: target.snapshot,
|
|
2416
|
+
toolId: request.payload.childId,
|
|
2417
|
+
input: request.payload.input,
|
|
2418
|
+
secrets: request.payload.secrets,
|
|
2419
|
+
systemVar: request.payload.systemVar,
|
|
2420
|
+
traceId
|
|
2421
|
+
});
|
|
2422
|
+
for (const message of result.streamMessages) await sendStreamChunk(socket, session, envelope, message);
|
|
2423
|
+
} finally {
|
|
2424
|
+
target.invokeBridge?.release(traceId);
|
|
2425
|
+
}
|
|
2426
|
+
await sendEnvelope(socket, {
|
|
2427
|
+
protocol: "connection-gateway.v1",
|
|
2428
|
+
sessionId: session.id,
|
|
2429
|
+
generation: session.generation,
|
|
2430
|
+
requestId: requiredRequestId(envelope),
|
|
2431
|
+
type: "stream",
|
|
2432
|
+
consumerType: session.consumerType,
|
|
2433
|
+
capability: CONNECTION_GATEWAY_PLUGIN_DEBUG_INVOKE_CAPABILITY,
|
|
2434
|
+
traceId: envelope.traceId,
|
|
2435
|
+
createdAt: Date.now(),
|
|
2436
|
+
payload: {
|
|
2437
|
+
kind: "plugin-debug.stream",
|
|
2438
|
+
event: "end"
|
|
2439
|
+
}
|
|
2440
|
+
});
|
|
2441
|
+
}
|
|
2442
|
+
async function sendStreamChunk(socket, session, request, message) {
|
|
2443
|
+
await sendEnvelope(socket, {
|
|
2444
|
+
protocol: "connection-gateway.v1",
|
|
2445
|
+
sessionId: session.id,
|
|
2446
|
+
generation: session.generation,
|
|
2447
|
+
requestId: requiredRequestId(request),
|
|
2448
|
+
type: "stream",
|
|
2449
|
+
consumerType: session.consumerType,
|
|
2450
|
+
capability: CONNECTION_GATEWAY_PLUGIN_DEBUG_INVOKE_CAPABILITY,
|
|
2451
|
+
traceId: request.traceId,
|
|
2452
|
+
createdAt: Date.now(),
|
|
2453
|
+
payload: {
|
|
2454
|
+
kind: "plugin-debug.stream",
|
|
2455
|
+
event: "chunk",
|
|
2456
|
+
data: ToolStreamMessageSchema.parse(message)
|
|
2457
|
+
}
|
|
2458
|
+
});
|
|
2459
|
+
}
|
|
2460
|
+
function makeErrorEnvelope(session, request, error) {
|
|
2461
|
+
return {
|
|
2462
|
+
protocol: "connection-gateway.v1",
|
|
2463
|
+
sessionId: session.id,
|
|
2464
|
+
generation: session.generation,
|
|
2465
|
+
requestId: request.requestId,
|
|
2466
|
+
type: "stream",
|
|
2467
|
+
consumerType: session.consumerType,
|
|
2468
|
+
capability: CONNECTION_GATEWAY_PLUGIN_DEBUG_INVOKE_CAPABILITY,
|
|
2469
|
+
traceId: request.traceId,
|
|
2470
|
+
createdAt: Date.now(),
|
|
2471
|
+
payload: {
|
|
2472
|
+
kind: "plugin-debug.stream",
|
|
2473
|
+
event: "error",
|
|
2474
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2475
|
+
}
|
|
2476
|
+
};
|
|
2477
|
+
}
|
|
2478
|
+
function makePluginDebugMetadata(targets, source) {
|
|
2479
|
+
return { pluginDebug: { targets: targets.map((target) => ({
|
|
2480
|
+
source,
|
|
2481
|
+
pluginId: target.snapshot.pluginId,
|
|
2482
|
+
version: target.snapshot.version,
|
|
2483
|
+
name: target.snapshot.name,
|
|
2484
|
+
description: target.snapshot.description,
|
|
2485
|
+
toolDescription: target.snapshot.toolDescription,
|
|
2486
|
+
author: target.snapshot.author,
|
|
2487
|
+
tags: target.snapshot.tags,
|
|
2488
|
+
permissions: target.snapshot.permissions,
|
|
2489
|
+
secretSchema: target.snapshot.secretSchema,
|
|
2490
|
+
isToolSet: target.snapshot.isToolSet,
|
|
2491
|
+
tools: target.snapshot.tools
|
|
2492
|
+
})) } };
|
|
2493
|
+
}
|
|
2494
|
+
async function openWebSocket(url) {
|
|
2495
|
+
return new Promise((resolve, reject) => {
|
|
2496
|
+
const socket = new WebSocket(url);
|
|
2497
|
+
socket.addEventListener("open", () => {
|
|
2498
|
+
resolve(socket);
|
|
2499
|
+
}, { once: true });
|
|
2500
|
+
socket.addEventListener("error", () => {
|
|
2501
|
+
reject(/* @__PURE__ */ new Error(`Connection Gateway WebSocket connect failed: ${url}`));
|
|
2502
|
+
}, { once: true });
|
|
2503
|
+
});
|
|
2504
|
+
}
|
|
2505
|
+
async function sendEnvelope(socket, envelope) {
|
|
2506
|
+
await sendMessage(socket, {
|
|
2507
|
+
protocol: "connection-gateway.ws.v1",
|
|
2508
|
+
type: "envelope",
|
|
2509
|
+
envelope
|
|
2510
|
+
});
|
|
2511
|
+
}
|
|
2512
|
+
async function sendMessage(socket, message) {
|
|
2513
|
+
if (socket.readyState !== WebSocket.OPEN) throw new Error("Connection Gateway WebSocket is not open");
|
|
2514
|
+
socket.send(JSON.stringify(message));
|
|
2515
|
+
}
|
|
2516
|
+
function requiredRequestId(envelope) {
|
|
2517
|
+
if (!envelope.requestId) throw new Error("Gateway request envelope missing requestId");
|
|
2518
|
+
return envelope.requestId;
|
|
2519
|
+
}
|
|
2520
|
+
function delay(ms) {
|
|
2521
|
+
return new Promise((resolve) => {
|
|
2522
|
+
setTimeout(resolve, ms);
|
|
2523
|
+
});
|
|
2524
|
+
}
|
|
2525
|
+
function formatErrorMessage(error) {
|
|
2526
|
+
return error instanceof Error ? error.message : String(error);
|
|
2527
|
+
}
|
|
2528
|
+
function createGatewayError(code, message) {
|
|
2529
|
+
const error = new Error(message);
|
|
2530
|
+
Object.assign(error, { code });
|
|
2531
|
+
return error;
|
|
2532
|
+
}
|
|
2533
|
+
function isDuplicateConnectionError(error) {
|
|
2534
|
+
return error instanceof Error && ("code" in error ? error.code === GATEWAY_DUPLICATE_CONNECTION_CODE : false);
|
|
2535
|
+
}
|
|
2536
|
+
function parseWsServerMessage(data) {
|
|
2537
|
+
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);
|
|
2538
|
+
return ConnectionGatewayWsServerMessageSchema.parse(JSON.parse(text));
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2541
|
+
//#endregion
|
|
2542
|
+
//#region ../../packages/infrastructure/src/plugin/invoke/invoke.impl.ts
|
|
2543
|
+
var InvokeManager = class {
|
|
2544
|
+
constructor(deps) {
|
|
2545
|
+
this.deps = deps;
|
|
2546
|
+
}
|
|
2547
|
+
async getWecomCorpToken() {
|
|
2548
|
+
const [result, responseErr] = await this.requestFastGPT({
|
|
2549
|
+
path: "/api/proApi/support/wecom/getCorpToken",
|
|
2550
|
+
init: { method: "GET" },
|
|
2551
|
+
failureReason: {
|
|
2552
|
+
en: "Get wecom corp token failed",
|
|
2553
|
+
"zh-CN": "获取企业微信企业令牌失败"
|
|
2554
|
+
},
|
|
2555
|
+
invalidJsonReason: {
|
|
2556
|
+
en: "Invalid wecom corp token output",
|
|
2557
|
+
"zh-CN": "获取企业微信企业令牌返回数据格式错误"
|
|
2558
|
+
}
|
|
2559
|
+
});
|
|
2560
|
+
if (responseErr) return failureResult(responseErr);
|
|
2561
|
+
return successResult(result);
|
|
2562
|
+
}
|
|
2563
|
+
async userInfo() {
|
|
2564
|
+
const [result, responseErr] = await this.requestFastGPT({
|
|
2565
|
+
path: "/api/invoke/userInfo",
|
|
2566
|
+
init: { method: "GET" },
|
|
2567
|
+
failureReason: {
|
|
2568
|
+
en: "Invoke user info failed",
|
|
2569
|
+
"zh-CN": "反向调用用户信息失败"
|
|
2570
|
+
},
|
|
2571
|
+
invalidJsonReason: {
|
|
2572
|
+
en: "Invalid invoke user info output",
|
|
2573
|
+
"zh-CN": "反向调用用户信息返回数据格式错误"
|
|
2574
|
+
}
|
|
2575
|
+
});
|
|
2576
|
+
if (responseErr) return failureResult(responseErr);
|
|
2577
|
+
try {
|
|
2578
|
+
return successResult(InvokeUserInfoOutputSchema.parse(result));
|
|
2579
|
+
} catch (error) {
|
|
2580
|
+
return failureResult({
|
|
2581
|
+
en: "Invalid invoke user info output",
|
|
2582
|
+
"zh-CN": "反向调用用户信息返回数据格式错误"
|
|
2583
|
+
}, error);
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
async uploadFile(input) {
|
|
2587
|
+
let request;
|
|
2588
|
+
try {
|
|
2589
|
+
request = await this.buildUploadFileRequest(input);
|
|
2590
|
+
} catch (error) {
|
|
2591
|
+
return failureResult({
|
|
2592
|
+
en: "Invoke upload file failed",
|
|
2593
|
+
"zh-CN": "反向调用上传文件失败"
|
|
2594
|
+
}, error);
|
|
2595
|
+
}
|
|
2596
|
+
const [result, responseErr] = await this.requestFastGPT({
|
|
2597
|
+
path: "/api/invoke/fileUpload",
|
|
2598
|
+
init: {
|
|
2599
|
+
method: "POST",
|
|
2600
|
+
body: request.formData
|
|
2601
|
+
},
|
|
2602
|
+
failureReason: {
|
|
2603
|
+
en: "Invoke upload file failed",
|
|
2604
|
+
"zh-CN": "反向调用上传文件失败"
|
|
2605
|
+
},
|
|
2606
|
+
invalidJsonReason: {
|
|
2607
|
+
en: "Invalid invoke upload file output",
|
|
2608
|
+
"zh-CN": "反向调用上传文件返回数据格式错误"
|
|
2609
|
+
}
|
|
2610
|
+
});
|
|
2611
|
+
if (responseErr) return failureResult(responseErr);
|
|
2612
|
+
try {
|
|
2613
|
+
return successResult(InvokeUploadFileOutputSchema.parse({ accessURL: result.url }));
|
|
2614
|
+
} catch (error) {
|
|
2615
|
+
return failureResult({
|
|
2616
|
+
en: "Invalid invoke upload file output",
|
|
2617
|
+
"zh-CN": "反向调用上传文件返回数据格式错误"
|
|
2618
|
+
}, error);
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
buildUrl(path) {
|
|
2622
|
+
return new URL(path, this.deps.fastgptBaseUrl).toString();
|
|
2623
|
+
}
|
|
2624
|
+
async buildUploadFileRequest(input) {
|
|
2625
|
+
const formData = new FormData();
|
|
2626
|
+
const fileBuffer = await this.toBuffer(input.file);
|
|
2627
|
+
const fileName = input.fileName ?? randomUUID();
|
|
2628
|
+
const contentType = input.contentType ?? "application/octet-stream";
|
|
2629
|
+
const blobPart = new Uint8Array(fileBuffer.byteLength);
|
|
2630
|
+
blobPart.set(fileBuffer);
|
|
2631
|
+
formData.append("file", new Blob([blobPart], { type: contentType }), fileName);
|
|
2632
|
+
if (input.fileName) formData.append("fileName", input.fileName);
|
|
2633
|
+
if (input.contentType) formData.append("contentType", input.contentType);
|
|
2634
|
+
return {
|
|
2635
|
+
formData,
|
|
2636
|
+
meta: {
|
|
2637
|
+
fileName,
|
|
2638
|
+
contentType
|
|
2639
|
+
}
|
|
2640
|
+
};
|
|
2641
|
+
}
|
|
2642
|
+
async toBuffer(file) {
|
|
2643
|
+
if (Buffer.isBuffer(file)) return file;
|
|
2644
|
+
if (file instanceof Readable$1) {
|
|
2645
|
+
const chunks = [];
|
|
2646
|
+
for await (const chunk of file) chunks.push(this.toBufferChunk(chunk));
|
|
2647
|
+
return Buffer.concat(chunks);
|
|
2648
|
+
}
|
|
2649
|
+
return Buffer.from(file);
|
|
2650
|
+
}
|
|
2651
|
+
toBufferChunk(chunk) {
|
|
2652
|
+
if (Buffer.isBuffer(chunk)) return chunk;
|
|
2653
|
+
if (chunk instanceof Uint8Array) return Buffer.from(chunk);
|
|
2654
|
+
if (chunk instanceof ArrayBuffer) return Buffer.from(chunk);
|
|
2655
|
+
if (ArrayBuffer.isView(chunk)) return Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
2656
|
+
if (typeof chunk === "string") return Buffer.from(chunk);
|
|
2657
|
+
if (typeof chunk === "number") return Buffer.from([chunk]);
|
|
2658
|
+
if (this.isSerializedBufferChunk(chunk)) return Buffer.from(chunk.data);
|
|
2659
|
+
throw new TypeError(`Unsupported upload file stream chunk type: ${Object.prototype.toString.call(chunk)}`);
|
|
2660
|
+
}
|
|
2661
|
+
isSerializedBufferChunk(value) {
|
|
2662
|
+
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);
|
|
2663
|
+
}
|
|
2664
|
+
async requestFastGPT({ path, init, failureReason, invalidJsonReason }) {
|
|
2665
|
+
const headers = new Headers(init?.headers);
|
|
2666
|
+
headers.set("Authorization", `Bearer ${this.deps.token}`);
|
|
2667
|
+
let response;
|
|
2668
|
+
try {
|
|
2669
|
+
response = await fetch(this.buildUrl(path), {
|
|
2670
|
+
...init,
|
|
2671
|
+
headers
|
|
2672
|
+
});
|
|
2673
|
+
} catch (error) {
|
|
2674
|
+
return failureResult(failureReason, error);
|
|
2675
|
+
}
|
|
2676
|
+
if (!response.ok) return failureResult(failureReason, await this.readErrorResponse(response));
|
|
2677
|
+
let payload;
|
|
2678
|
+
try {
|
|
2679
|
+
payload = await response.json();
|
|
2680
|
+
} catch (error) {
|
|
2681
|
+
return failureResult(invalidJsonReason, error);
|
|
2682
|
+
}
|
|
2683
|
+
return this.parseResponse(payload, failureReason);
|
|
2684
|
+
}
|
|
2685
|
+
parseResponse(payload, fallbackReason) {
|
|
2686
|
+
if (!this.isRecord(payload)) return successResult(payload);
|
|
2687
|
+
if (payload.success === false) return failureResult({
|
|
2688
|
+
en: this.getResponseMessage(payload) ?? fallbackReason.en,
|
|
2689
|
+
"zh-CN": this.getResponseMessage(payload) ?? fallbackReason["zh-CN"]
|
|
2690
|
+
}, payload);
|
|
2691
|
+
if (typeof payload.code === "number" && payload.code >= 400) return failureResult({
|
|
2692
|
+
en: this.getResponseMessage(payload) ?? fallbackReason.en,
|
|
2693
|
+
"zh-CN": this.getResponseMessage(payload) ?? fallbackReason["zh-CN"]
|
|
2694
|
+
}, payload);
|
|
2695
|
+
return successResult("data" in payload ? payload.data : payload);
|
|
2696
|
+
}
|
|
2697
|
+
async readErrorResponse(response) {
|
|
2698
|
+
return {
|
|
2699
|
+
status: response.status,
|
|
2700
|
+
statusText: response.statusText,
|
|
2701
|
+
body: (await response.text().catch(() => "")).slice(0, 1e3)
|
|
2702
|
+
};
|
|
2703
|
+
}
|
|
2704
|
+
getResponseMessage(payload) {
|
|
2705
|
+
return typeof payload.message === "string" ? payload.message : void 0;
|
|
2706
|
+
}
|
|
2707
|
+
isRecord(value) {
|
|
2708
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
2709
|
+
}
|
|
2710
|
+
};
|
|
2711
|
+
|
|
2712
|
+
//#endregion
|
|
2713
|
+
//#region src/debug/remote-invoke.ts
|
|
2714
|
+
var RemoteDebugInvokeBridge = class {
|
|
2715
|
+
sessions = /* @__PURE__ */ new Map();
|
|
2716
|
+
constructor(fastgptBaseUrl) {
|
|
2717
|
+
this.fastgptBaseUrl = fastgptBaseUrl;
|
|
2718
|
+
}
|
|
2719
|
+
attach(runtime) {
|
|
2720
|
+
runtime.setHostRequestHandler((request) => this.handleHostRequest(request));
|
|
2721
|
+
}
|
|
2722
|
+
bind(traceId, input) {
|
|
2723
|
+
this.sessions.set(traceId, { invokeToken: input.invokeToken });
|
|
2724
|
+
}
|
|
2725
|
+
release(traceId) {
|
|
2726
|
+
this.sessions.delete(traceId);
|
|
2727
|
+
}
|
|
2728
|
+
async handleHostRequest({ method, args, input, traceId }) {
|
|
2729
|
+
if (!traceId) throw new Error("远程调试反向调用缺少 traceId。");
|
|
2730
|
+
const session = this.sessions.get(traceId);
|
|
2731
|
+
if (!session) throw new Error("远程调试反向调用会话不存在或已结束。");
|
|
2732
|
+
const invoke = new InvokeManager({
|
|
2733
|
+
token: session.invokeToken,
|
|
2734
|
+
fastgptBaseUrl: this.fastgptBaseUrl
|
|
2735
|
+
});
|
|
2736
|
+
switch (method) {
|
|
2737
|
+
case InvokeMethodEnum.uploadFile: return invoke.uploadFile({
|
|
2738
|
+
...ensurePlainObject(args),
|
|
2739
|
+
file: input ? await readSourceToBuffer(input) : Buffer.alloc(0)
|
|
2740
|
+
});
|
|
2741
|
+
case InvokeMethodEnum.userInfo: return invoke.userInfo();
|
|
2742
|
+
case InvokeMethodEnum.wecomCorpToken: return invoke.getWecomCorpToken();
|
|
2743
|
+
default: throw new Error(`远程调试宿主暂不支持反向调用: ${String(method)}`);
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
};
|
|
2747
|
+
function ensurePlainObject(value) {
|
|
2748
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2749
|
+
}
|
|
2750
|
+
async function readSourceToBuffer(source) {
|
|
2751
|
+
const chunks = [];
|
|
2752
|
+
const iterable = isStreamDataLike(source) ? source.values() : source;
|
|
2753
|
+
for await (const chunk of iterable) chunks.push(toBuffer(chunk));
|
|
2754
|
+
return Buffer.concat(chunks);
|
|
2755
|
+
}
|
|
2756
|
+
function isStreamDataLike(source) {
|
|
2757
|
+
return source instanceof StreamData || typeof source === "object" && source !== null && typeof source.values === "function" && typeof source.consume === "function";
|
|
2758
|
+
}
|
|
2759
|
+
function toBuffer(value) {
|
|
2760
|
+
if (Buffer.isBuffer(value)) return value;
|
|
2761
|
+
if (value instanceof Uint8Array) return Buffer.from(value);
|
|
2762
|
+
if (typeof value === "string") return Buffer.from(value);
|
|
2763
|
+
return Buffer.from(JSON.stringify(value));
|
|
2764
|
+
}
|
|
2765
|
+
|
|
2766
|
+
//#endregion
|
|
2767
|
+
//#region src/debug/remote-session.ts
|
|
2768
|
+
async function runRemoteDebugSession({ entriesInput, options, commandName, migrationHint }) {
|
|
2769
|
+
if (migrationHint) logger.warn(migrationHint);
|
|
2770
|
+
if (options.watch) {
|
|
2771
|
+
await runWatchRemoteDebugSession({
|
|
2772
|
+
entriesInput,
|
|
2773
|
+
options,
|
|
2774
|
+
commandName
|
|
2775
|
+
});
|
|
2776
|
+
return;
|
|
2777
|
+
}
|
|
2778
|
+
await runRemoteDebugSessionOnce({
|
|
2779
|
+
entriesInput,
|
|
2780
|
+
options,
|
|
2781
|
+
commandName
|
|
2782
|
+
});
|
|
2783
|
+
}
|
|
2784
|
+
async function runRemoteDebugSessionOnce({ entriesInput, options, commandName }) {
|
|
2785
|
+
const targets = await loadTargets(await resolveDebugEntries(entriesInput), options);
|
|
2786
|
+
await ensureConnectLink({
|
|
2787
|
+
options,
|
|
2788
|
+
commandName,
|
|
2789
|
+
targets
|
|
2790
|
+
});
|
|
2791
|
+
const gatewayOptions = await resolveGatewayOptions({
|
|
2792
|
+
options,
|
|
2793
|
+
commandName,
|
|
2794
|
+
targets
|
|
2795
|
+
});
|
|
2796
|
+
attachRemoteInvokeBridges(targets, gatewayOptions.fastgptBaseUrl);
|
|
2797
|
+
const reporter = createRemoteDebugReporter({
|
|
2798
|
+
commandName,
|
|
2799
|
+
options,
|
|
2800
|
+
gatewayOptions,
|
|
2801
|
+
targets
|
|
2802
|
+
});
|
|
2803
|
+
reporter.start();
|
|
2804
|
+
try {
|
|
2805
|
+
const gateway = await connectDebugGateway({
|
|
2806
|
+
targets,
|
|
2807
|
+
options: gatewayOptions,
|
|
2808
|
+
onLog: (message) => reporter.log(`[gateway] ${message}`)
|
|
2809
|
+
});
|
|
2810
|
+
reporter.attachClose(({ force }) => {
|
|
2811
|
+
gateway.close();
|
|
2812
|
+
if (force) process.exit(130);
|
|
2813
|
+
});
|
|
2814
|
+
const source = gateway.session.sessionScope.source ?? gatewayOptions.source ?? "-";
|
|
2815
|
+
reporter.ready(source);
|
|
2816
|
+
await gateway.closed;
|
|
2817
|
+
} finally {
|
|
2818
|
+
await reporter.end();
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
async function runWatchRemoteDebugSession({ entriesInput, options, commandName }) {
|
|
2822
|
+
const entries = await resolveDebugEntries(entriesInput);
|
|
2823
|
+
const initialTargets = await loadTargets(entries, options);
|
|
2824
|
+
await ensureConnectLink({
|
|
2825
|
+
options,
|
|
2826
|
+
commandName,
|
|
2827
|
+
targets: initialTargets
|
|
2828
|
+
});
|
|
2829
|
+
const gatewayOptions = await resolveGatewayOptions({
|
|
2830
|
+
options,
|
|
2831
|
+
commandName,
|
|
2832
|
+
targets: initialTargets
|
|
2833
|
+
});
|
|
2834
|
+
attachRemoteInvokeBridges(initialTargets, gatewayOptions.fastgptBaseUrl);
|
|
2835
|
+
const reporter = createRemoteDebugReporter({
|
|
2836
|
+
commandName,
|
|
2837
|
+
options,
|
|
2838
|
+
gatewayOptions,
|
|
2839
|
+
targets: initialTargets
|
|
2840
|
+
});
|
|
2841
|
+
let reloadRunning = false;
|
|
2842
|
+
let reloadPending = false;
|
|
2843
|
+
let closed = false;
|
|
2844
|
+
let gateway;
|
|
2845
|
+
const reloadTargets = async () => {
|
|
2846
|
+
if (reloadRunning) {
|
|
2847
|
+
reloadPending = true;
|
|
2848
|
+
return;
|
|
2849
|
+
}
|
|
2850
|
+
reloadRunning = true;
|
|
2851
|
+
try {
|
|
2852
|
+
do {
|
|
2853
|
+
reloadPending = false;
|
|
2854
|
+
try {
|
|
2855
|
+
const nextTargets = await loadTargets(entries, options);
|
|
2856
|
+
attachRemoteInvokeBridges(nextTargets, gatewayOptions.fastgptBaseUrl);
|
|
2857
|
+
gateway?.updateTargets(nextTargets);
|
|
2858
|
+
reporter.log(`检测到插件文件变化,已热更新 ${nextTargets.length} 个本地插件。`);
|
|
2859
|
+
} catch (error) {
|
|
2860
|
+
reporter.log(`插件热更新失败: ${formatConnectionKeyExchangeError(error)}`);
|
|
2861
|
+
}
|
|
2862
|
+
} while (reloadPending && !closed);
|
|
2863
|
+
} finally {
|
|
2864
|
+
reloadRunning = false;
|
|
2865
|
+
}
|
|
2866
|
+
};
|
|
2867
|
+
const watcher = watchDebugEntries(entries, () => {
|
|
2868
|
+
reloadTargets();
|
|
2869
|
+
});
|
|
2870
|
+
reporter.start();
|
|
2871
|
+
try {
|
|
2872
|
+
const connectedGateway = await connectDebugGateway({
|
|
2873
|
+
targets: initialTargets,
|
|
2874
|
+
options: gatewayOptions,
|
|
2875
|
+
onLog: (message) => reporter.log(`[gateway] ${message}`)
|
|
2876
|
+
});
|
|
2877
|
+
gateway = connectedGateway;
|
|
2878
|
+
reporter.attachClose(({ force }) => {
|
|
2879
|
+
closed = true;
|
|
2880
|
+
watcher.close();
|
|
2881
|
+
connectedGateway.close();
|
|
2882
|
+
if (force) process.exit(130);
|
|
2883
|
+
});
|
|
2884
|
+
const source = connectedGateway.session.sessionScope.source ?? gatewayOptions.source ?? "-";
|
|
2885
|
+
reporter.ready(source);
|
|
2886
|
+
await connectedGateway.closed;
|
|
2887
|
+
} finally {
|
|
2888
|
+
closed = true;
|
|
2889
|
+
watcher.close();
|
|
2890
|
+
await reporter.end();
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
async function resolveDebugEntries(entriesInput) {
|
|
2894
|
+
const entries = (Array.isArray(entriesInput) ? entriesInput : [entriesInput]).filter((entry) => entry.trim().length > 0);
|
|
2895
|
+
if (entries.length > 0) return entries.map((entry) => path.resolve(entry));
|
|
2896
|
+
const discovered = await discoverDebugEntries();
|
|
2897
|
+
logger.info([`自动发现 ${discovered.length} 个可调试插件:`, ...discovered.map((entry) => ` - ${entry}`)].join("\n"));
|
|
2898
|
+
return discovered;
|
|
2899
|
+
}
|
|
2900
|
+
async function loadTargets(entries, options) {
|
|
2901
|
+
const targets = [];
|
|
2902
|
+
for (const [index, entryDir] of entries.entries()) {
|
|
2903
|
+
const session = await loadDebugSession({
|
|
2904
|
+
entryDir,
|
|
2905
|
+
uploadDir: resolveUploadDir({
|
|
2906
|
+
entryDir,
|
|
2907
|
+
index,
|
|
2908
|
+
total: entries.length,
|
|
2909
|
+
uploadDir: options.uploadDir
|
|
2910
|
+
})
|
|
2911
|
+
});
|
|
2912
|
+
targets.push({
|
|
2913
|
+
runtime: session.runtime,
|
|
2914
|
+
snapshot: session.snapshot
|
|
2915
|
+
});
|
|
2916
|
+
}
|
|
2917
|
+
const duplicatePluginIds = findDuplicateValues$1(targets.map((target) => target.snapshot.pluginId));
|
|
2918
|
+
if (duplicatePluginIds.length > 0) throw new Error(`gateway pluginId 重复: ${duplicatePluginIds.join(", ")}`);
|
|
2919
|
+
return targets;
|
|
2920
|
+
}
|
|
2921
|
+
function attachRemoteInvokeBridges(targets, fastgptBaseUrl) {
|
|
2922
|
+
targets.forEach((target) => {
|
|
2923
|
+
const invokeBridge = new RemoteDebugInvokeBridge(fastgptBaseUrl);
|
|
2924
|
+
invokeBridge.attach(target.runtime);
|
|
2925
|
+
target.invokeBridge = invokeBridge;
|
|
2926
|
+
});
|
|
2927
|
+
}
|
|
2928
|
+
function watchDebugEntries(entries, onChange) {
|
|
2929
|
+
const watchers = entries.map((entry) => fsWatch(entry, {
|
|
2930
|
+
recursive: true,
|
|
2931
|
+
onChange
|
|
2932
|
+
}));
|
|
2933
|
+
return { close() {
|
|
2934
|
+
watchers.forEach((watcher) => watcher.close());
|
|
2935
|
+
} };
|
|
2936
|
+
}
|
|
2937
|
+
function fsWatch(dir, { recursive, onChange }) {
|
|
2938
|
+
let timer;
|
|
2939
|
+
const watcher = fs$1.watch(dir, { recursive }, () => {
|
|
2940
|
+
clearTimeout(timer);
|
|
2941
|
+
timer = setTimeout(onChange, 150);
|
|
2942
|
+
});
|
|
2943
|
+
return { close() {
|
|
2944
|
+
clearTimeout(timer);
|
|
2945
|
+
watcher.close();
|
|
2946
|
+
} };
|
|
2947
|
+
}
|
|
2948
|
+
function createRemoteDebugReporter({ commandName, options, gatewayOptions, targets }) {
|
|
2949
|
+
const summary = {
|
|
2950
|
+
commandName,
|
|
2951
|
+
mode: "FastGPT connection key",
|
|
2952
|
+
gateway: gatewayOptions.gatewayUrl,
|
|
2953
|
+
reconnect: gatewayOptions.reconnect === false ? "off" : "on",
|
|
2954
|
+
targets: targets.map((target) => target.snapshot)
|
|
2955
|
+
};
|
|
2956
|
+
return options.interactive !== false && Boolean(process.stdout.isTTY) && Boolean(process.stdin.isTTY) ? new TuiRemoteDebugReporter(summary) : new PlainRemoteDebugReporter(summary);
|
|
2957
|
+
}
|
|
2958
|
+
async function ensureConnectLink({ options, commandName, targets }) {
|
|
2959
|
+
if (options.connect) return;
|
|
2960
|
+
const savedConnect = await readSavedConnectionKey();
|
|
2961
|
+
if (savedConnect) {
|
|
2962
|
+
options.connect = savedConnect;
|
|
2963
|
+
options.connectPersistOnSuccess = true;
|
|
2964
|
+
logger.info(`已读取本地 FastGPT connection key 配置: ${getCliConfigPath()}`);
|
|
2965
|
+
return;
|
|
2966
|
+
}
|
|
2967
|
+
if (options.interactive === false || !process.stdin.isTTY || !process.stdout.isTTY) throw new Error("dev 需要 --connect,或在交互式终端中输入 FastGPT connection key。");
|
|
2968
|
+
options.connect = await promptConnectLink({
|
|
2969
|
+
commandName,
|
|
2970
|
+
targets
|
|
2971
|
+
});
|
|
2972
|
+
options.connectPersistOnSuccess = true;
|
|
2973
|
+
}
|
|
2974
|
+
async function promptConnectLink({ commandName, targets, defaultValue, errorMessage }) {
|
|
2975
|
+
process.stdout.write([
|
|
2976
|
+
"\x1B[?25h\x1B[2J\x1B[HFastGPT Plugin Dev",
|
|
2977
|
+
"",
|
|
2978
|
+
`Command fastgpt-plugin ${commandName}`,
|
|
2979
|
+
"Mode waiting for FastGPT connection key",
|
|
2980
|
+
"Status pending",
|
|
2981
|
+
"",
|
|
2982
|
+
"Plugins",
|
|
2983
|
+
...targets.map((target) => ` ${target.snapshot.pluginId}@${target.snapshot.version}`),
|
|
2984
|
+
"",
|
|
2985
|
+
...errorMessage ? [`Last error ${errorMessage}`, ""] : [],
|
|
2986
|
+
"Paste the FastGPT connection key or connect link to start the WSS debug session.",
|
|
2987
|
+
...defaultValue ? ["Press Enter to reuse the current value, or type a new one to replace it.", ""] : [],
|
|
2988
|
+
""
|
|
2989
|
+
].join("\n"));
|
|
2990
|
+
return (await input({
|
|
2991
|
+
message: "FastGPT connection key",
|
|
2992
|
+
default: defaultValue,
|
|
2993
|
+
validate(value) {
|
|
2994
|
+
return value.trim().length > 0 || "请输入 FastGPT connection key";
|
|
2995
|
+
}
|
|
2996
|
+
})).trim();
|
|
2997
|
+
}
|
|
2998
|
+
async function readSavedConnectionKey() {
|
|
2999
|
+
return (await readCliConfig()).debug?.connectionKey?.trim() || void 0;
|
|
3000
|
+
}
|
|
3001
|
+
async function saveConnectionKey(connectionKey) {
|
|
3002
|
+
const trimmed = connectionKey.trim();
|
|
3003
|
+
if (!trimmed) return;
|
|
3004
|
+
const configPath = getCliConfigPath();
|
|
3005
|
+
const config = await readCliConfig();
|
|
3006
|
+
const nextConfig = {
|
|
3007
|
+
...config,
|
|
3008
|
+
debug: {
|
|
3009
|
+
...config.debug,
|
|
3010
|
+
connectionKey: trimmed
|
|
3011
|
+
}
|
|
3012
|
+
};
|
|
3013
|
+
await fs.mkdir(path.dirname(configPath), {
|
|
3014
|
+
recursive: true,
|
|
3015
|
+
mode: 448
|
|
3016
|
+
});
|
|
3017
|
+
await fs.writeFile(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, {
|
|
3018
|
+
encoding: "utf-8",
|
|
3019
|
+
mode: 384
|
|
3020
|
+
});
|
|
3021
|
+
}
|
|
3022
|
+
async function readCliConfig() {
|
|
3023
|
+
const configPath = getCliConfigPath();
|
|
3024
|
+
let raw;
|
|
3025
|
+
try {
|
|
3026
|
+
raw = await fs.readFile(configPath, "utf-8");
|
|
3027
|
+
} catch (error) {
|
|
3028
|
+
if (isNodeError(error) && error.code === "ENOENT") return {};
|
|
3029
|
+
throw error;
|
|
3030
|
+
}
|
|
3031
|
+
if (!raw.trim()) return {};
|
|
3032
|
+
return CliConfigSchema.parse(JSON.parse(raw));
|
|
3033
|
+
}
|
|
3034
|
+
function getCliConfigPath() {
|
|
3035
|
+
return path.join(getCliConfigDir(), CLI_CONFIG_FILE_NAME);
|
|
3036
|
+
}
|
|
3037
|
+
function getCliConfigDir() {
|
|
3038
|
+
const xdgConfigHome = process.env.XDG_CONFIG_HOME?.trim();
|
|
3039
|
+
if (xdgConfigHome) return path.join(xdgConfigHome, CLI_CONFIG_DIR_NAME);
|
|
3040
|
+
return path.join(os.homedir(), ".config", CLI_CONFIG_DIR_NAME);
|
|
3041
|
+
}
|
|
3042
|
+
function isNodeError(error) {
|
|
3043
|
+
return error instanceof Error && "code" in error;
|
|
3044
|
+
}
|
|
3045
|
+
function formatTargetLine(snapshot) {
|
|
3046
|
+
const tools = snapshot.tools.map((tool) => tool.id).join(", ") || "-";
|
|
3047
|
+
return ` - ${snapshot.pluginId}@${snapshot.version} tools=[${tools}] entry=${snapshot.entryDir}`;
|
|
3048
|
+
}
|
|
3049
|
+
const CLI_CONFIG_FILE_NAME = "config.json";
|
|
3050
|
+
const CLI_CONFIG_DIR_NAME = "fastgpt-plugin";
|
|
3051
|
+
const CliConfigSchema = z.object({ debug: z.object({ connectionKey: z.string().min(1).optional() }).optional() }).passthrough();
|
|
3052
|
+
var PlainRemoteDebugReporter = class {
|
|
3053
|
+
constructor(summary) {
|
|
3054
|
+
this.summary = summary;
|
|
3055
|
+
}
|
|
3056
|
+
start() {
|
|
3057
|
+
logger.info([
|
|
3058
|
+
"FastGPT 插件开发会话",
|
|
3059
|
+
` command: fastgpt-plugin ${this.summary.commandName}`,
|
|
3060
|
+
` mode: ${this.summary.mode}`,
|
|
3061
|
+
" ui: plain",
|
|
3062
|
+
` gateway: ${this.summary.gateway}`,
|
|
3063
|
+
` reconnect: ${this.summary.reconnect}`,
|
|
3064
|
+
" plugins:",
|
|
3065
|
+
...this.summary.targets.map(formatTargetLine)
|
|
3066
|
+
].join("\n"));
|
|
3067
|
+
}
|
|
3068
|
+
log(message) {
|
|
3069
|
+
logger.info(message);
|
|
3070
|
+
}
|
|
3071
|
+
ready(source) {
|
|
3072
|
+
this.summary.targets.forEach((target) => {
|
|
3073
|
+
logger.success(`远程调试已就绪: ${source} ${target.pluginId}`);
|
|
3074
|
+
});
|
|
3075
|
+
logger.info(`已建立 1 个远程调试通道,挂载 ${this.summary.targets.length} 个插件,按 Ctrl+C 停止。`);
|
|
3076
|
+
}
|
|
3077
|
+
attachClose() {}
|
|
3078
|
+
end() {
|
|
3079
|
+
logger.info("远程调试会话已结束。");
|
|
3080
|
+
}
|
|
3081
|
+
};
|
|
3082
|
+
var TuiRemoteDebugReporter = class {
|
|
3083
|
+
status = "connecting";
|
|
3084
|
+
source = "-";
|
|
3085
|
+
logs = [];
|
|
3086
|
+
instance;
|
|
3087
|
+
closeSession;
|
|
3088
|
+
interruptCount = 0;
|
|
3089
|
+
onSigint = () => {
|
|
3090
|
+
this.requestClose("ctrl-c");
|
|
3091
|
+
};
|
|
3092
|
+
requestClose = (reason) => {
|
|
3093
|
+
if (reason === "q") {
|
|
3094
|
+
this.log("收到退出指令,正在关闭远程调试会话。");
|
|
3095
|
+
this.closeSession?.({ force: false });
|
|
3096
|
+
return;
|
|
3097
|
+
}
|
|
3098
|
+
this.interruptCount += 1;
|
|
3099
|
+
const force = this.interruptCount >= 2;
|
|
3100
|
+
this.status = force ? "closed" : "closing";
|
|
3101
|
+
this.log(force ? "再次收到 Ctrl+C,正在强制退出。" : "收到 Ctrl+C,正在关闭远程调试会话。再次按 Ctrl+C 强制退出。");
|
|
3102
|
+
this.closeSession?.({ force });
|
|
3103
|
+
if (force && !this.closeSession) process.exit(130);
|
|
3104
|
+
};
|
|
3105
|
+
constructor(summary) {
|
|
3106
|
+
this.summary = summary;
|
|
3107
|
+
}
|
|
3108
|
+
start() {
|
|
3109
|
+
process.on("SIGINT", this.onSigint);
|
|
3110
|
+
this.instance = render(this.createApp(), {
|
|
3111
|
+
stdin: process.stdin,
|
|
3112
|
+
stdout: process.stdout,
|
|
3113
|
+
stderr: process.stderr,
|
|
3114
|
+
exitOnCtrlC: false,
|
|
3115
|
+
interactive: true,
|
|
3116
|
+
patchConsole: false
|
|
3117
|
+
});
|
|
3118
|
+
}
|
|
3119
|
+
log(message) {
|
|
3120
|
+
this.logs.push({
|
|
3121
|
+
at: /* @__PURE__ */ new Date(),
|
|
3122
|
+
message
|
|
3123
|
+
});
|
|
3124
|
+
this.logs = this.logs.slice(-6);
|
|
3125
|
+
this.rerender();
|
|
3126
|
+
}
|
|
3127
|
+
ready(source) {
|
|
3128
|
+
this.status = "connected";
|
|
3129
|
+
this.source = source;
|
|
3130
|
+
this.log(`远程调试已就绪,挂载 ${this.summary.targets.length} 个插件。`);
|
|
3131
|
+
}
|
|
3132
|
+
attachClose(close) {
|
|
3133
|
+
this.closeSession = close;
|
|
3134
|
+
}
|
|
3135
|
+
async end() {
|
|
3136
|
+
process.off("SIGINT", this.onSigint);
|
|
3137
|
+
this.status = "closed";
|
|
3138
|
+
this.rerender();
|
|
3139
|
+
const instance = this.instance;
|
|
3140
|
+
this.instance = void 0;
|
|
3141
|
+
if (instance) {
|
|
3142
|
+
await Promise.race([instance.waitUntilRenderFlush(), setTimeout$1(50)]).catch(() => void 0);
|
|
3143
|
+
instance.unmount();
|
|
3144
|
+
await Promise.race([instance.waitUntilExit(), setTimeout$1(50)]).catch(() => void 0);
|
|
3145
|
+
}
|
|
3146
|
+
logger.info("远程调试会话已结束。");
|
|
3147
|
+
}
|
|
3148
|
+
rerender() {
|
|
3149
|
+
this.instance?.rerender(this.createApp());
|
|
3150
|
+
}
|
|
3151
|
+
createApp() {
|
|
3152
|
+
return React.createElement(RemoteDebugInkApp, {
|
|
3153
|
+
summary: this.summary,
|
|
3154
|
+
status: this.status,
|
|
3155
|
+
source: this.source,
|
|
3156
|
+
logs: this.logs,
|
|
3157
|
+
onQuit: this.requestClose
|
|
3158
|
+
});
|
|
3159
|
+
}
|
|
3160
|
+
};
|
|
3161
|
+
function RemoteDebugInkApp({ summary, status, source, logs, onQuit }) {
|
|
3162
|
+
useInput((inputValue, key) => {
|
|
3163
|
+
if (key.ctrl && inputValue === "c") {
|
|
3164
|
+
onQuit("ctrl-c");
|
|
3165
|
+
return;
|
|
3166
|
+
}
|
|
3167
|
+
if (inputValue === "q") onQuit("q");
|
|
3168
|
+
});
|
|
3169
|
+
const statusMeta = getTuiStatusMeta(status);
|
|
3170
|
+
const sourceLabel = source === "-" ? "waiting for bind" : source;
|
|
3171
|
+
const rows = [
|
|
3172
|
+
["command", `fastgpt-plugin ${summary.commandName}`],
|
|
3173
|
+
["gateway", summary.gateway],
|
|
3174
|
+
["source", sourceLabel],
|
|
3175
|
+
["reconnect", summary.reconnect === "on" ? "enabled" : "disabled"]
|
|
3176
|
+
];
|
|
3177
|
+
const plugins = summary.targets.map((target) => {
|
|
3178
|
+
const toolCount = target.tools.length;
|
|
3179
|
+
return {
|
|
3180
|
+
id: `${target.pluginId}:${target.version}`,
|
|
3181
|
+
name: `${target.pluginId}@${target.version}`,
|
|
3182
|
+
tools: toolCount === 1 ? "1 tool" : `${toolCount} tools`,
|
|
3183
|
+
entry: target.entryDir
|
|
3184
|
+
};
|
|
3185
|
+
});
|
|
3186
|
+
const activityItems = logs.length > 0 ? logs.map((entry, index) => React.createElement(Box, {
|
|
3187
|
+
key: `${entry.at.getTime()}:${index}`,
|
|
3188
|
+
flexDirection: "row"
|
|
3189
|
+
}, React.createElement(Text, { color: "gray" }, formatTuiTime(entry.at)), React.createElement(Text, { dimColor: true }, " │ "), React.createElement(Text, { wrap: "truncate-end" }, entry.message))) : [React.createElement(Text, {
|
|
3190
|
+
key: "empty-log",
|
|
3191
|
+
dimColor: true
|
|
3192
|
+
}, "waiting for gateway events")];
|
|
3193
|
+
return React.createElement(Box, {
|
|
3194
|
+
flexDirection: "column",
|
|
3195
|
+
paddingX: 1,
|
|
3196
|
+
paddingY: 1
|
|
3197
|
+
}, React.createElement(Box, {
|
|
3198
|
+
borderStyle: "round",
|
|
3199
|
+
borderColor: statusMeta.borderColor,
|
|
3200
|
+
paddingX: 2,
|
|
3201
|
+
paddingY: 1,
|
|
3202
|
+
flexDirection: "column"
|
|
3203
|
+
}, React.createElement(Box, { justifyContent: "space-between" }, React.createElement(Box, null, React.createElement(Text, {
|
|
3204
|
+
bold: true,
|
|
3205
|
+
color: "cyanBright"
|
|
3206
|
+
}, "FastGPT Plugin Dev"), React.createElement(Text, { dimColor: true }, " remote debug")), React.createElement(Box, null, React.createElement(Text, {
|
|
3207
|
+
color: statusMeta.color,
|
|
3208
|
+
bold: true
|
|
3209
|
+
}, statusMeta.label), React.createElement(Text, { dimColor: true }, ` ${plugins.length} plugins`))), React.createElement(Box, { marginTop: 1 }, React.createElement(Text, { color: statusMeta.color }, statusMeta.marker), React.createElement(Text, { dimColor: true }, " "), React.createElement(Text, { wrap: "truncate-end" }, statusMeta.description))), React.createElement(Box, {
|
|
3210
|
+
marginTop: 1,
|
|
3211
|
+
columnGap: 1
|
|
3212
|
+
}, React.createElement(Box, {
|
|
3213
|
+
borderStyle: "round",
|
|
3214
|
+
borderColor: "gray",
|
|
3215
|
+
paddingX: 1,
|
|
3216
|
+
paddingY: 1,
|
|
3217
|
+
flexDirection: "column",
|
|
3218
|
+
width: "50%"
|
|
3219
|
+
}, React.createElement(Text, { bold: true }, "Session"), React.createElement(Box, {
|
|
3220
|
+
flexDirection: "column",
|
|
3221
|
+
marginTop: 1
|
|
3222
|
+
}, ...rows.map(([label, value]) => React.createElement(SessionRow, {
|
|
3223
|
+
key: label,
|
|
3224
|
+
label,
|
|
3225
|
+
value
|
|
3226
|
+
})))), React.createElement(Box, {
|
|
3227
|
+
borderStyle: "round",
|
|
3228
|
+
borderColor: "gray",
|
|
3229
|
+
paddingX: 1,
|
|
3230
|
+
paddingY: 1,
|
|
3231
|
+
flexDirection: "column",
|
|
3232
|
+
width: "50%"
|
|
3233
|
+
}, React.createElement(Box, { justifyContent: "space-between" }, React.createElement(Text, { bold: true }, "Plugins"), React.createElement(Text, { dimColor: true }, `${plugins.length} mounted`)), React.createElement(Box, {
|
|
3234
|
+
flexDirection: "column",
|
|
3235
|
+
marginTop: 1
|
|
3236
|
+
}, ...plugins.map((plugin, index) => React.createElement(Box, {
|
|
3237
|
+
key: plugin.id,
|
|
3238
|
+
flexDirection: "column",
|
|
3239
|
+
marginTop: index === 0 ? 0 : 1
|
|
3240
|
+
}, React.createElement(Box, null, React.createElement(Text, { color: "greenBright" }, "● "), React.createElement(Text, {
|
|
3241
|
+
bold: true,
|
|
3242
|
+
wrap: "truncate-end"
|
|
3243
|
+
}, plugin.name), React.createElement(Text, { dimColor: true }, ` ${plugin.tools}`)), React.createElement(Text, {
|
|
3244
|
+
dimColor: true,
|
|
3245
|
+
wrap: "truncate-end"
|
|
3246
|
+
}, ` ${plugin.entry}`)))))), React.createElement(Box, {
|
|
3247
|
+
borderStyle: "round",
|
|
3248
|
+
borderColor: "gray",
|
|
3249
|
+
paddingX: 1,
|
|
3250
|
+
paddingY: 1,
|
|
3251
|
+
flexDirection: "column",
|
|
3252
|
+
marginTop: 1
|
|
3253
|
+
}, React.createElement(Box, { justifyContent: "space-between" }, React.createElement(Text, { bold: true }, "Activity"), React.createElement(Text, { dimColor: true }, "latest 6 events")), React.createElement(Box, {
|
|
3254
|
+
flexDirection: "column",
|
|
3255
|
+
marginTop: 1
|
|
3256
|
+
}, ...activityItems)), React.createElement(Box, {
|
|
3257
|
+
marginTop: 1,
|
|
3258
|
+
justifyContent: "space-between"
|
|
3259
|
+
}, React.createElement(Text, { dimColor: true }, "q stop"), React.createElement(Text, { dimColor: true }, "Ctrl+C stop · second Ctrl+C force exit")));
|
|
3260
|
+
}
|
|
3261
|
+
function getTuiStatusMeta(status) {
|
|
3262
|
+
if (status === "connected") return {
|
|
3263
|
+
label: "CONNECTED",
|
|
3264
|
+
marker: "●",
|
|
3265
|
+
description: "Gateway channel is live and ready to receive FastGPT invocations.",
|
|
3266
|
+
color: "greenBright",
|
|
3267
|
+
borderColor: "green"
|
|
3268
|
+
};
|
|
3269
|
+
if (status === "closed") return {
|
|
3270
|
+
label: "CLOSED",
|
|
3271
|
+
marker: "■",
|
|
3272
|
+
description: "Remote debug session has been closed.",
|
|
3273
|
+
color: "gray",
|
|
3274
|
+
borderColor: "gray"
|
|
3275
|
+
};
|
|
3276
|
+
if (status === "closing") return {
|
|
3277
|
+
label: "CLOSING",
|
|
3278
|
+
marker: "◆",
|
|
3279
|
+
description: "Closing gateway channel. Press Ctrl+C again to force exit.",
|
|
3280
|
+
color: "yellowBright",
|
|
3281
|
+
borderColor: "yellow"
|
|
3282
|
+
};
|
|
3283
|
+
return {
|
|
3284
|
+
label: "CONNECTING",
|
|
3285
|
+
marker: "◆",
|
|
3286
|
+
description: "Binding local plugins to the FastGPT connection gateway.",
|
|
3287
|
+
color: "yellowBright",
|
|
3288
|
+
borderColor: "yellow"
|
|
3289
|
+
};
|
|
3290
|
+
}
|
|
3291
|
+
function SessionRow({ label, value }) {
|
|
3292
|
+
return React.createElement(Box, null, React.createElement(Text, { color: "gray" }, label.padEnd(10)), React.createElement(Text, { wrap: "truncate-end" }, value));
|
|
3293
|
+
}
|
|
3294
|
+
function formatTuiTime(value) {
|
|
3295
|
+
return [
|
|
3296
|
+
value.getHours().toString().padStart(2, "0"),
|
|
3297
|
+
value.getMinutes().toString().padStart(2, "0"),
|
|
3298
|
+
value.getSeconds().toString().padStart(2, "0")
|
|
3299
|
+
].join(":");
|
|
3300
|
+
}
|
|
3301
|
+
async function resolveGatewayOptions({ options, commandName, targets }) {
|
|
3302
|
+
if (!options.connect) throw new Error("dev 需要 --connect 来建立远程调试通道。");
|
|
3303
|
+
while (true) try {
|
|
3304
|
+
const gatewayOptions = await resolveConnectGatewayOptions(options);
|
|
3305
|
+
if (options.connectPersistOnSuccess === true) {
|
|
3306
|
+
await saveConnectionKey(options.connect);
|
|
3307
|
+
logger.info(`已保存 FastGPT connection key 到本地配置: ${getCliConfigPath()}`);
|
|
3308
|
+
options.connectPersistOnSuccess = false;
|
|
3309
|
+
}
|
|
3310
|
+
return gatewayOptions;
|
|
3311
|
+
} catch (error) {
|
|
3312
|
+
const errorMessage = formatConnectionKeyExchangeError(error);
|
|
3313
|
+
if (!canPromptForConnectInput(options)) throw error;
|
|
3314
|
+
logger.error(errorMessage);
|
|
3315
|
+
options.connect = await promptConnectLink({
|
|
3316
|
+
commandName,
|
|
3317
|
+
targets,
|
|
3318
|
+
defaultValue: options.connect,
|
|
3319
|
+
errorMessage
|
|
3320
|
+
});
|
|
3321
|
+
options.connectPersistOnSuccess = true;
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3324
|
+
function canPromptForConnectInput(options) {
|
|
3325
|
+
return options.interactive !== false && Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY);
|
|
3326
|
+
}
|
|
3327
|
+
function formatConnectionKeyExchangeError(error) {
|
|
3328
|
+
return error instanceof Error ? error.message : String(error);
|
|
3329
|
+
}
|
|
3330
|
+
async function resolveConnectGatewayOptions(options) {
|
|
3331
|
+
const info = await exchangeConnectLink(options.connect);
|
|
3332
|
+
return {
|
|
3333
|
+
gatewayUrl: normalizeGatewayWsUrl(info.gatewayUrl),
|
|
3334
|
+
connectToken: info.connectToken,
|
|
3335
|
+
userId: info.tmbId,
|
|
3336
|
+
source: info.source,
|
|
3337
|
+
fastgptBaseUrl: info.fastgptBaseUrl,
|
|
3338
|
+
tokenTtlMs: Math.max(1, info.expiresAt - Date.now()),
|
|
3339
|
+
reconnect: options.noReconnect ? false : options.reconnect ?? true,
|
|
3340
|
+
reconnectIntervalMs: toPositiveInt(options.reconnectIntervalMs ?? process.env.CONNECTION_GATEWAY_RECONNECT_INTERVAL_MS ?? "2000", "reconnect-interval-ms"),
|
|
3341
|
+
resolveReconnectOptions: () => resolveConnectGatewayOptions(options)
|
|
3342
|
+
};
|
|
3343
|
+
}
|
|
3344
|
+
function resolveUploadDir({ entryDir, index, total, uploadDir }) {
|
|
3345
|
+
if (!uploadDir) return path.resolve(path.join(entryDir, ".fastgpt-plugin-debug/uploads"));
|
|
3346
|
+
if (total === 1) return path.resolve(uploadDir);
|
|
3347
|
+
return path.resolve(uploadDir, `${index + 1}-${sanitizePathSegment$1(path.basename(entryDir))}`);
|
|
3348
|
+
}
|
|
3349
|
+
const ConnectInfoSchema = z.object({
|
|
3350
|
+
gatewayUrl: z.string().min(1),
|
|
3351
|
+
transport: z.literal("websocket"),
|
|
3352
|
+
source: z.string().min(1),
|
|
3353
|
+
connectToken: z.string().min(1),
|
|
3354
|
+
fastgptBaseUrl: z.string().min(1).optional(),
|
|
3355
|
+
expiresAt: z.number().int().positive()
|
|
3356
|
+
});
|
|
3357
|
+
async function exchangeConnectLink(connectInput) {
|
|
3358
|
+
const exchangeUrl = isHttpUrl(connectInput) ? connectInput : resolveConnectionKeyExchangeUrl();
|
|
3359
|
+
const response = isHttpUrl(connectInput) ? await fetch(exchangeUrl) : await fetch(exchangeUrl, {
|
|
3360
|
+
method: "POST",
|
|
3361
|
+
headers: { "Content-Type": "application/json" },
|
|
3362
|
+
body: JSON.stringify({ connectionKey: connectInput })
|
|
3363
|
+
});
|
|
3364
|
+
const text = await response.text();
|
|
3365
|
+
if (!response.ok) throw new Error(`connection key 请求失败: ${response.status} ${text}`);
|
|
3366
|
+
const payload = text ? JSON.parse(text) : {};
|
|
3367
|
+
const info = ConnectInfoSchema.parse(payload.data ?? payload);
|
|
3368
|
+
return {
|
|
3369
|
+
...info,
|
|
3370
|
+
fastgptBaseUrl: info.fastgptBaseUrl ?? new URL(exchangeUrl).origin,
|
|
3371
|
+
tmbId: parseTmbIdFromDebugSource(info.source)
|
|
3372
|
+
};
|
|
3373
|
+
}
|
|
3374
|
+
function isHttpUrl(value) {
|
|
3375
|
+
try {
|
|
3376
|
+
const parsed = new URL(value);
|
|
3377
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
3378
|
+
} catch {
|
|
3379
|
+
return false;
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
function resolveConnectionKeyExchangeUrl() {
|
|
3383
|
+
const explicit = process.env.FASTGPT_PLUGIN_DEBUG_CONNECT_URL;
|
|
3384
|
+
if (explicit) return explicit;
|
|
3385
|
+
const baseUrl = process.env.FASTGPT_PLUGIN_SERVER_URL ?? process.env.PLUGIN_SERVER_URL;
|
|
3386
|
+
if (!baseUrl) throw new Error("使用裸 connection key 时需要设置 FASTGPT_PLUGIN_DEBUG_CONNECT_URL 或 FASTGPT_PLUGIN_SERVER_URL。");
|
|
3387
|
+
return new URL("/api/plugin/debug-sessions/connection-key:exchange", baseUrl).toString();
|
|
3388
|
+
}
|
|
3389
|
+
function parseTmbIdFromDebugSource(source) {
|
|
3390
|
+
const parsed = parsePluginDebugSessionSource(source);
|
|
3391
|
+
if (!parsed) throw new Error(`debug source 缺少 tmbId: ${source}`);
|
|
3392
|
+
return parsed.tmbId;
|
|
3393
|
+
}
|
|
3394
|
+
function normalizeGatewayWsUrl(gatewayUrl) {
|
|
3395
|
+
const parsed = new URL(gatewayUrl);
|
|
3396
|
+
if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") throw new Error("gatewayUrl 必须使用 ws:// 或 wss:// 协议。");
|
|
3397
|
+
if (!parsed.hostname) throw new Error("gatewayUrl 必须包含 host。");
|
|
3398
|
+
return parsed.toString();
|
|
3399
|
+
}
|
|
3400
|
+
function toPositiveInt(value, label) {
|
|
3401
|
+
const parsed = Number(value);
|
|
3402
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0) throw new Error(`${label} 必须是正整数。`);
|
|
3403
|
+
return parsed;
|
|
3404
|
+
}
|
|
3405
|
+
function findDuplicateValues$1(values) {
|
|
3406
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3407
|
+
const duplicates = /* @__PURE__ */ new Set();
|
|
3408
|
+
values.forEach((value) => {
|
|
3409
|
+
if (seen.has(value)) {
|
|
3410
|
+
duplicates.add(value);
|
|
3411
|
+
return;
|
|
3412
|
+
}
|
|
3413
|
+
seen.add(value);
|
|
3414
|
+
});
|
|
3415
|
+
return [...duplicates];
|
|
3416
|
+
}
|
|
3417
|
+
function sanitizePathSegment$1(value) {
|
|
3418
|
+
return value.replace(/[^a-zA-Z0-9._-]/g, "_") || "plugin";
|
|
3419
|
+
}
|
|
3420
|
+
|
|
1717
3421
|
//#endregion
|
|
1718
3422
|
//#region src/commands/debug.ts
|
|
1719
3423
|
var DebugCommand = class extends BaseCommand {
|
|
1720
3424
|
register(parent) {
|
|
1721
|
-
parent.command("debug <
|
|
1722
|
-
await this.run(
|
|
3425
|
+
parent.command("debug <entries...>").description("直接加载本地插件目录并调试工具").option("-t, --tool <childId>", "工具集子工具 ID").option("--run", "立即执行一次工具调试", false).option("-i, --input <json>", "工具输入 JSON 字符串").option("--input-file <path>", "工具输入 JSON 文件路径").option("--secrets <json>", "secrets JSON 字符串").option("--secrets-file <path>", "secrets JSON 文件路径").option("--system-var <json>", "systemVar JSON 字符串").option("--system-var-file <path>", "systemVar JSON 文件路径").option("--upload-dir <path>", "虚拟 uploadFile 的输出目录").option("--connect <keyOrUrl>", "兼容入口:FastGPT debug connection key 或 connect link,建议改用 dev 命令").option("--reconnect", "兼容入口:断线后自动重连", true).option("--no-reconnect", "兼容入口:关闭自动重连").option("--reconnect-interval-ms <ms>", "兼容入口:重连间隔").action(async (entries, opts) => {
|
|
3426
|
+
await this.run(entries, opts);
|
|
1723
3427
|
});
|
|
1724
3428
|
}
|
|
1725
|
-
async run(
|
|
1726
|
-
const
|
|
1727
|
-
const
|
|
1728
|
-
|
|
3429
|
+
async run(entriesInput, options) {
|
|
3430
|
+
const entries = (Array.isArray(entriesInput) ? entriesInput : [entriesInput]).map((entry) => path.resolve(entry));
|
|
3431
|
+
const isMultiEntry = entries.length > 1;
|
|
3432
|
+
if (options.connect) {
|
|
3433
|
+
await runRemoteDebugSession({
|
|
3434
|
+
entriesInput,
|
|
3435
|
+
options,
|
|
3436
|
+
commandName: "dev",
|
|
3437
|
+
migrationHint: "debug --connect 是兼容入口,远程集成调试建议改用 fastgpt-plugin dev。"
|
|
3438
|
+
});
|
|
3439
|
+
return;
|
|
3440
|
+
}
|
|
3441
|
+
if (isMultiEntry && !options.connect) throw new Error("debug 是轻量本地调试入口,一次只支持一个插件;多插件远程调试请使用 dev。");
|
|
3442
|
+
if (isMultiEntry && options.run) throw new Error("--run 只支持单个插件入口。");
|
|
3443
|
+
const sessions = [];
|
|
3444
|
+
for (const [index, entryDir] of entries.entries()) sessions.push(await loadDebugSession({
|
|
1729
3445
|
entryDir,
|
|
1730
|
-
uploadDir
|
|
3446
|
+
uploadDir: this.resolveUploadDir({
|
|
3447
|
+
entryDir,
|
|
3448
|
+
index,
|
|
3449
|
+
total: entries.length,
|
|
3450
|
+
uploadDir: options.uploadDir
|
|
3451
|
+
})
|
|
3452
|
+
}));
|
|
3453
|
+
sessions.forEach((session) => {
|
|
3454
|
+
this.printSnapshot(session.snapshot, session.uploadDir);
|
|
1731
3455
|
});
|
|
1732
|
-
|
|
3456
|
+
const duplicatePluginIds = findDuplicateValues(sessions.map((session) => session.snapshot.pluginId));
|
|
3457
|
+
if (duplicatePluginIds.length > 0) throw new Error(`gateway pluginId 重复: ${duplicatePluginIds.join(", ")}`);
|
|
3458
|
+
const [session] = sessions;
|
|
1733
3459
|
if (!options.run) return;
|
|
1734
3460
|
const input = await this.readJsonOption(options.input, options.inputFile, "input");
|
|
1735
3461
|
const secrets = await this.readJsonOption(options.secrets, options.secretsFile, "secrets");
|
|
@@ -1810,6 +3536,11 @@ var DebugCommand = class extends BaseCommand {
|
|
|
1810
3536
|
JSON.stringify(this.createInputExample(tool.inputSchema))
|
|
1811
3537
|
].map(quoteShellArg).join(" ");
|
|
1812
3538
|
}
|
|
3539
|
+
resolveUploadDir({ entryDir, index, total, uploadDir }) {
|
|
3540
|
+
if (!uploadDir) return path.resolve(path.join(entryDir, ".fastgpt-plugin-debug/uploads"));
|
|
3541
|
+
if (total === 1) return path.resolve(uploadDir);
|
|
3542
|
+
return path.resolve(uploadDir, `${index + 1}-${sanitizePathSegment(path.basename(entryDir))}`);
|
|
3543
|
+
}
|
|
1813
3544
|
createInputExample(schema) {
|
|
1814
3545
|
if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
|
|
1815
3546
|
if ("const" in schema) return schema.const;
|
|
@@ -1853,6 +3584,38 @@ function quoteShellArg(value) {
|
|
|
1853
3584
|
if (/^[a-zA-Z0-9_./:@-]+$/.test(value)) return value;
|
|
1854
3585
|
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
1855
3586
|
}
|
|
3587
|
+
function findDuplicateValues(values) {
|
|
3588
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3589
|
+
const duplicates = /* @__PURE__ */ new Set();
|
|
3590
|
+
values.forEach((value) => {
|
|
3591
|
+
if (seen.has(value)) {
|
|
3592
|
+
duplicates.add(value);
|
|
3593
|
+
return;
|
|
3594
|
+
}
|
|
3595
|
+
seen.add(value);
|
|
3596
|
+
});
|
|
3597
|
+
return [...duplicates];
|
|
3598
|
+
}
|
|
3599
|
+
function sanitizePathSegment(value) {
|
|
3600
|
+
return value.replace(/[^a-zA-Z0-9._-]/g, "_") || "plugin";
|
|
3601
|
+
}
|
|
3602
|
+
|
|
3603
|
+
//#endregion
|
|
3604
|
+
//#region src/commands/dev.ts
|
|
3605
|
+
var DevCommand = class extends BaseCommand {
|
|
3606
|
+
register(parent) {
|
|
3607
|
+
parent.command("dev [entries...]").description("启动 FastGPT 插件集成开发会话").option("--connect <keyOrUrl>", "可选:FastGPT debug connection key 或 connect link,适合脚本和 Agent").option("--reconnect", "断线后自动重连", true).option("--no-reconnect", "关闭自动重连").option("--reconnect-interval-ms <ms>", "重连间隔").option("--upload-dir <path>", "虚拟 uploadFile 的输出目录").option("--watch", "监听插件文件变化并自动重载远程调试会话", false).option("--no-interactive", "使用纯文本状态输出,适合脚本和 Agent").action(async (entries, opts) => {
|
|
3608
|
+
await this.run(entries, opts);
|
|
3609
|
+
});
|
|
3610
|
+
}
|
|
3611
|
+
async run(entriesInput, options) {
|
|
3612
|
+
await runRemoteDebugSession({
|
|
3613
|
+
entriesInput,
|
|
3614
|
+
options,
|
|
3615
|
+
commandName: "dev"
|
|
3616
|
+
});
|
|
3617
|
+
}
|
|
3618
|
+
};
|
|
1856
3619
|
|
|
1857
3620
|
//#endregion
|
|
1858
3621
|
//#region src/commands/pack.ts
|
|
@@ -1942,6 +3705,7 @@ function createProgram() {
|
|
|
1942
3705
|
new CheckCommand().register(program);
|
|
1943
3706
|
new CreateCommand().register(program);
|
|
1944
3707
|
new DebugCommand().register(program);
|
|
3708
|
+
new DevCommand().register(program);
|
|
1945
3709
|
new PackCommand().register(program);
|
|
1946
3710
|
return program;
|
|
1947
3711
|
}
|