@fastgpt-plugin/sdk-factory 0.0.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,270 @@
1
+ # FastGPT Plugin SDK Factory
2
+
3
+ 用于构建 FastGPT Tool Plugin 的 TypeScript SDK。它把插件声明、输入输出校验、密钥配置、运行时通信和流式响应封装成少量 API,让工具开发者专注于业务逻辑。
4
+
5
+ TypeScript SDK for building FastGPT Tool Plugins. It wraps plugin metadata, input/output validation, secret configuration, runtime communication, and streaming responses behind a small API surface so tool authors can focus on business logic.
6
+
7
+ ## 安装 / Installation
8
+
9
+ ```bash
10
+ pnpm add @fastgpt-plugin/sdk-factory zod
11
+ ```
12
+
13
+ 在 monorepo 内开发时可直接使用 workspace 依赖。
14
+
15
+ When developing inside this monorepo, use the workspace dependency directly.
16
+
17
+ ## 快速开始 / Quick Start
18
+
19
+ 插件入口文件需要默认导出 `defineTool()` 或 `defineToolSet()` 返回的实例。
20
+
21
+ The plugin entry file must default-export the instance returned by `defineTool()` or `defineToolSet()`.
22
+
23
+ ```ts
24
+ import { createToolHandler, defineTool } from '@fastgpt-plugin/sdk-factory';
25
+ import z from 'zod';
26
+
27
+ const handler = createToolHandler({
28
+ inputSchema: z.object({
29
+ text: z.string()
30
+ }),
31
+ outputSchema: z.object({
32
+ result: z.string()
33
+ }),
34
+ handler: async (input) => {
35
+ return {
36
+ result: input.text.toUpperCase()
37
+ };
38
+ }
39
+ });
40
+
41
+ export default defineTool({
42
+ manifest: {
43
+ pluginId: 'uppercase',
44
+ version: '1.0.0',
45
+ name: {
46
+ en: 'Uppercase',
47
+ 'zh-CN': '转大写'
48
+ },
49
+ description: {
50
+ en: 'Convert text to uppercase',
51
+ 'zh-CN': '将文本转换为大写'
52
+ },
53
+ versionDescription: {
54
+ en: 'Initial version',
55
+ 'zh-CN': '初始版本'
56
+ },
57
+ tags: ['tools']
58
+ },
59
+ handler
60
+ });
61
+ ```
62
+
63
+ ## API / API
64
+
65
+ ### `createToolHandler(definition)`
66
+
67
+ 定义工具处理器,并通过 Zod schema 推导 `input`、`output` 和 `secrets` 类型。
68
+
69
+ Defines a tool handler and infers `input`, `output`, and `secrets` types from Zod schemas.
70
+
71
+ ```ts
72
+ const handler = createToolHandler({
73
+ inputSchema: z.object({
74
+ query: z.string()
75
+ }),
76
+ outputSchema: z.object({
77
+ answer: z.string()
78
+ }),
79
+ secretSchema: z.object({
80
+ apiKey: z.string()
81
+ }),
82
+ handler: async (input, ctx) => {
83
+ ctx.streamResponse({
84
+ type: 'answer',
85
+ content: `Searching: ${input.query}`
86
+ });
87
+
88
+ return {
89
+ answer: `Result for ${input.query} with ${ctx.secrets?.apiKey}`
90
+ };
91
+ }
92
+ });
93
+ ```
94
+
95
+ 处理器上下文包含:
96
+
97
+ The handler context includes:
98
+
99
+ | 字段 / Field | 说明 / Description |
100
+ | --- | --- |
101
+ | `systemVar` | FastGPT 注入的系统变量。System variables injected by FastGPT. |
102
+ | `secrets` | 按 `secretSchema` 校验后的密钥配置。Secret values validated by `secretSchema`. |
103
+ | `invoke` | 反向调用宿主能力的客户端,例如上传文件。Client for invoking host capabilities, such as file upload. |
104
+ | `streamResponse` | 发送流式工具回答。Sends streaming tool answers. |
105
+
106
+ ### `defineTool(options)`
107
+
108
+ 定义单个工具插件。`manifest` 描述插件基础信息,`handler` 是工具执行逻辑。
109
+
110
+ Defines a single-tool plugin. `manifest` describes the plugin metadata, and `handler` contains the execution logic.
111
+
112
+ ```ts
113
+ export default defineTool({
114
+ manifest,
115
+ handler
116
+ });
117
+ ```
118
+
119
+ ### `defineToolSet(options)`
120
+
121
+ 定义一个包含多个子工具的工具集。所有子工具共用顶层 `manifest`,每个子工具拥有独立的 `id`、名称、描述和 handler。
122
+
123
+ Defines a tool set with multiple child tools. All child tools share the top-level `manifest`; each child tool has its own `id`, name, description, and handler.
124
+
125
+ ```ts
126
+ const searchHandler = createToolHandler({
127
+ inputSchema: z.object({ query: z.string() }),
128
+ outputSchema: z.object({ items: z.array(z.string()) }),
129
+ handler: async (input) => ({ items: [input.query] })
130
+ });
131
+
132
+ const summaryHandler = createToolHandler({
133
+ inputSchema: z.object({ content: z.string() }),
134
+ outputSchema: z.object({ summary: z.string() }),
135
+ handler: async (input) => ({ summary: input.content.slice(0, 100) })
136
+ });
137
+
138
+ export default defineToolSet({
139
+ manifest: {
140
+ pluginId: 'text-tools',
141
+ version: '1.0.0',
142
+ name: {
143
+ en: 'Text Tools',
144
+ 'zh-CN': '文本工具集'
145
+ },
146
+ description: {
147
+ en: 'Search and summarize text',
148
+ 'zh-CN': '搜索和总结文本'
149
+ }
150
+ },
151
+ children: [
152
+ {
153
+ id: 'search',
154
+ name: {
155
+ en: 'Search',
156
+ 'zh-CN': '搜索'
157
+ },
158
+ description: {
159
+ en: 'Search text',
160
+ 'zh-CN': '搜索文本'
161
+ },
162
+ toolDescription: 'Search text by query',
163
+ handler: searchHandler
164
+ },
165
+ {
166
+ id: 'summary',
167
+ name: {
168
+ en: 'Summary',
169
+ 'zh-CN': '总结'
170
+ },
171
+ description: {
172
+ en: 'Summarize text',
173
+ 'zh-CN': '总结文本'
174
+ },
175
+ toolDescription: 'Summarize text content',
176
+ handler: summaryHandler
177
+ }
178
+ ]
179
+ });
180
+ ```
181
+
182
+ ## Manifest / 插件声明
183
+
184
+ `manifest` 使用中英文国际化字段,常用字段如下:
185
+
186
+ `manifest` uses bilingual i18n fields. Common fields:
187
+
188
+ | 字段 / Field | 必填 / Required | 说明 / Description |
189
+ | --- | --- | --- |
190
+ | `pluginId` | 是 / Yes | 插件唯一 ID。Unique plugin ID. |
191
+ | `version` | 是 / Yes | 插件版本。Plugin version. |
192
+ | `name` | 是 / Yes | 插件名称,格式为 `{ en, 'zh-CN' }`。Plugin name in `{ en, 'zh-CN' }` format. |
193
+ | `description` | 是 / Yes | 插件描述,格式为 `{ en, 'zh-CN' }`。Plugin description in `{ en, 'zh-CN' }` format. |
194
+ | `versionDescription` | 否 / No | 版本说明。Version description. |
195
+ | `author` | 否 / No | 作者。Author. |
196
+ | `repoUrl` | 否 / No | 仓库地址。Repository URL. |
197
+ | `tutorialUrl` | 否 / No | 教程地址。Tutorial URL. |
198
+ | `tags` | 否 / No | 插件标签。Plugin tags. |
199
+ | `permission` | 否 / No | 插件权限声明。Plugin permission declarations. |
200
+ | `icon` | 否 / No | 插件图标;构建流程可自动补齐。Plugin icon; the build pipeline can fill it automatically. |
201
+ | `toolDescription` | 否 / No | 面向模型的工具说明;构建流程可自动补齐。Tool description for the model; the build pipeline can fill it automatically. |
202
+
203
+ ## Secret 配置 / Secret Configuration
204
+
205
+ 单工具可在 handler 内声明 `secretSchema`。工具集可在 `defineToolSet()` 顶层声明共用 `secretSchema`。
206
+
207
+ A single tool can declare `secretSchema` in its handler. A tool set can declare a shared `secretSchema` at the top level of `defineToolSet()`.
208
+
209
+ ```ts
210
+ const secretSchema = z.object({
211
+ apiKey: z.string()
212
+ });
213
+
214
+ const handler = createToolHandler({
215
+ inputSchema: z.object({ prompt: z.string() }),
216
+ outputSchema: z.object({ text: z.string() }),
217
+ secretSchema,
218
+ handler: async (input, ctx) => {
219
+ return {
220
+ text: `${input.prompt}:${ctx.secrets?.apiKey}`
221
+ };
222
+ }
223
+ });
224
+ ```
225
+
226
+ ## 反向调用 / Host Invocation
227
+
228
+ `ctx.invoke` 用于调用 FastGPT 宿主能力。目前 SDK 提供文件上传能力。
229
+
230
+ Use `ctx.invoke` to call FastGPT host capabilities. The SDK currently exposes file upload.
231
+
232
+ ```ts
233
+ const handler = createToolHandler({
234
+ inputSchema: z.object({
235
+ content: z.string()
236
+ }),
237
+ outputSchema: z.object({
238
+ accessURL: z.string(),
239
+ fileName: z.string(),
240
+ size: z.number()
241
+ }),
242
+ handler: async (input, { invoke }) => {
243
+ const [result, err] = await invoke.uploadFile({
244
+ fileName: 'result.txt',
245
+ contentType: 'text/plain',
246
+ file: Buffer.from(input.content, 'utf-8')
247
+ });
248
+
249
+ if (err || !result) {
250
+ throw new Error('Failed to upload file');
251
+ }
252
+
253
+ return {
254
+ accessURL: result.accessURL,
255
+ fileName: result.fileName,
256
+ size: result.size
257
+ };
258
+ }
259
+ });
260
+ ```
261
+
262
+ ## 构建 / Build
263
+
264
+ ```bash
265
+ pnpm --filter @fastgpt-plugin/sdk-factory build
266
+ ```
267
+
268
+ 构建后包入口为 `dist/index.js`,类型声明为 `dist/index.d.ts`。
269
+
270
+ After build, the package entry is `dist/index.js` and type declarations are emitted to `dist/index.d.ts`.
@@ -0,0 +1,240 @@
1
+ import z from "zod";
2
+ import * as node_stream0 from "node:stream";
3
+
4
+ //#region src/manifest.type.d.ts
5
+ declare const UserToolManifestSchema: z.ZodObject<{
6
+ toolDescription: z.ZodOptional<z.ZodString>;
7
+ icon: z.ZodOptional<z.ZodString>;
8
+ name: z.ZodObject<{
9
+ en: z.ZodString;
10
+ "zh-CN": z.ZodOptional<z.ZodString>;
11
+ "zh-Hant": z.ZodOptional<z.ZodString>;
12
+ }, z.core.$strip>;
13
+ description: z.ZodObject<{
14
+ en: z.ZodString;
15
+ "zh-CN": z.ZodOptional<z.ZodString>;
16
+ "zh-Hant": z.ZodOptional<z.ZodString>;
17
+ }, z.core.$strip>;
18
+ pluginId: z.ZodString;
19
+ version: z.ZodString;
20
+ author: z.ZodOptional<z.ZodString>;
21
+ repoUrl: z.ZodOptional<z.ZodString>;
22
+ tutorialUrl: z.ZodOptional<z.ZodURL>;
23
+ tags: z.ZodOptional<z.ZodArray<z.ZodEnum<{
24
+ tools: "tools";
25
+ search: "search";
26
+ multimodal: "multimodal";
27
+ communication: "communication";
28
+ finance: "finance";
29
+ design: "design";
30
+ productivity: "productivity";
31
+ news: "news";
32
+ entertainment: "entertainment";
33
+ social: "social";
34
+ scientific: "scientific";
35
+ other: "other";
36
+ }>>>;
37
+ versionDescription: z.ZodOptional<z.ZodObject<{
38
+ en: z.ZodString;
39
+ "zh-CN": z.ZodOptional<z.ZodString>;
40
+ "zh-Hant": z.ZodOptional<z.ZodString>;
41
+ }, z.core.$strip>>;
42
+ permission: z.ZodOptional<z.ZodArray<z.ZodEnum<{
43
+ "userInfo:read": "userInfo:read";
44
+ "teamInfo:read": "teamInfo:read";
45
+ "model:read": "model:read";
46
+ "dataset:read": "dataset:read";
47
+ "file-upload:allow": "file-upload:allow";
48
+ }>>>;
49
+ }, z.core.$strip>;
50
+ type UserToolManifestType = z.infer<typeof UserToolManifestSchema>;
51
+ //#endregion
52
+ //#region ../../packages/domain/src/value-objects/i18n-string.vo.d.ts
53
+ declare const I18nStringSchema: z.ZodObject<{
54
+ en: z.ZodString;
55
+ "zh-CN": z.ZodOptional<z.ZodString>;
56
+ "zh-Hant": z.ZodOptional<z.ZodString>;
57
+ }, z.core.$strip>;
58
+ type I18nStringType = z.infer<typeof I18nStringSchema>;
59
+ //#endregion
60
+ //#region ../../packages/domain/src/value-objects/result.vo.d.ts
61
+ /** usecase, interface-adapter 都要使用这个类型, 手动处理错误,并且能从内到外把错误透出来*/
62
+ type ResultFailure<E = unknown> = {
63
+ reason: I18nStringType;
64
+ error: E;
65
+ };
66
+ type Result<T = unknown, E = unknown> = [T, null] | [null, ResultFailure<E>];
67
+ //#endregion
68
+ //#region ../../packages/domain/src/ports/invoke.port.d.ts
69
+ declare const InvokeUploadFileInputSchema: z.ZodObject<{
70
+ file: z.ZodUnion<readonly [z.ZodCustom<node_stream0.Readable, node_stream0.Readable>, z.ZodPipe<z.ZodUnion<readonly [z.ZodCustom<Buffer<ArrayBufferLike>, Buffer<ArrayBufferLike>>, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>]>, z.ZodTransform<Buffer<any>, Uint8Array<ArrayBuffer> | Buffer<ArrayBufferLike>>>]>;
71
+ fileName: z.ZodOptional<z.ZodString>;
72
+ contentType: z.ZodOptional<z.ZodIntersection<z.ZodEnum<{
73
+ "application/javascript": "application/javascript";
74
+ "application/json": "application/json";
75
+ "application/yaml": "application/yaml";
76
+ "application/zip": "application/zip";
77
+ "image/jpeg": "image/jpeg";
78
+ "image/png": "image/png";
79
+ "image/gif": "image/gif";
80
+ "image/webp": "image/webp";
81
+ "image/svg+xml": "image/svg+xml";
82
+ "application/pdf": "application/pdf";
83
+ "text/plain": "text/plain";
84
+ "text/csv": "text/csv";
85
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
86
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
87
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation": "application/vnd.openxmlformats-officedocument.presentationml.presentation";
88
+ "application/msword": "application/msword";
89
+ "application/vnd.ms-excel": "application/vnd.ms-excel";
90
+ "application/vnd.ms-powerpoint": "application/vnd.ms-powerpoint";
91
+ "text/markdown": "text/markdown";
92
+ "audio/mpeg": "audio/mpeg";
93
+ "video/mp4": "video/mp4";
94
+ "audio/wav": "audio/wav";
95
+ "text/html": "text/html";
96
+ "application/xml": "application/xml";
97
+ "application/gzip": "application/gzip";
98
+ "application/octet-stream": "application/octet-stream";
99
+ "multipart/form-data": "multipart/form-data";
100
+ "text/event-stream": "text/event-stream";
101
+ }>, z.ZodString>>;
102
+ }, z.core.$strip>;
103
+ declare const InvokeUploadFileOutputSchema: z.ZodObject<{
104
+ accessURL: z.ZodString;
105
+ etag: z.ZodString;
106
+ fileName: z.ZodString;
107
+ contentType: z.ZodIntersection<z.ZodEnum<{
108
+ "application/javascript": "application/javascript";
109
+ "application/json": "application/json";
110
+ "application/yaml": "application/yaml";
111
+ "application/zip": "application/zip";
112
+ "image/jpeg": "image/jpeg";
113
+ "image/png": "image/png";
114
+ "image/gif": "image/gif";
115
+ "image/webp": "image/webp";
116
+ "image/svg+xml": "image/svg+xml";
117
+ "application/pdf": "application/pdf";
118
+ "text/plain": "text/plain";
119
+ "text/csv": "text/csv";
120
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
121
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
122
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation": "application/vnd.openxmlformats-officedocument.presentationml.presentation";
123
+ "application/msword": "application/msword";
124
+ "application/vnd.ms-excel": "application/vnd.ms-excel";
125
+ "application/vnd.ms-powerpoint": "application/vnd.ms-powerpoint";
126
+ "text/markdown": "text/markdown";
127
+ "audio/mpeg": "audio/mpeg";
128
+ "video/mp4": "video/mp4";
129
+ "audio/wav": "audio/wav";
130
+ "text/html": "text/html";
131
+ "application/xml": "application/xml";
132
+ "application/gzip": "application/gzip";
133
+ "application/octet-stream": "application/octet-stream";
134
+ "multipart/form-data": "multipart/form-data";
135
+ "text/event-stream": "text/event-stream";
136
+ }>, z.ZodString>;
137
+ size: z.ZodNumber;
138
+ createTime: z.ZodDate;
139
+ }, z.core.$strip>;
140
+ type InvokeUploadFileInputType = z.infer<typeof InvokeUploadFileInputSchema>;
141
+ type InvokeUploadFileOutputType = z.infer<typeof InvokeUploadFileOutputSchema>;
142
+ /**
143
+ * 反向调用 FastGPT 的能力
144
+ */
145
+ interface InvokePort {
146
+ /** 上传文件 */
147
+ uploadFile(input: InvokeUploadFileInputType): Promise<Result<InvokeUploadFileOutputType>>;
148
+ }
149
+ //#endregion
150
+ //#region ../../packages/domain/src/value-objects/system-var.vo.d.ts
151
+ declare const SystemVarSchema: z.ZodObject<{
152
+ user: z.ZodObject<{
153
+ id: z.ZodString;
154
+ username: z.ZodString;
155
+ contact: z.ZodString;
156
+ membername: z.ZodString;
157
+ teamName: z.ZodString;
158
+ teamId: z.ZodString;
159
+ name: z.ZodString;
160
+ }, z.core.$strip>;
161
+ app: z.ZodObject<{
162
+ id: z.ZodString;
163
+ name: z.ZodString;
164
+ }, z.core.$strip>;
165
+ tool: z.ZodObject<{
166
+ id: z.ZodString;
167
+ version: z.ZodString;
168
+ prefix: z.ZodOptional<z.ZodString>;
169
+ }, z.core.$loose>;
170
+ time: z.ZodString;
171
+ }, z.core.$strip>;
172
+ type SystemVarType = z.infer<typeof SystemVarSchema>;
173
+ //#endregion
174
+ //#region ../../packages/domain/src/value-objects/tool.vo.d.ts
175
+ declare const ToolAnswerSchema: z.ZodObject<{
176
+ type: z.ZodEnum<{
177
+ answer: "answer";
178
+ fastAnswer: "fastAnswer";
179
+ }>;
180
+ content: z.ZodString;
181
+ }, z.core.$strip>;
182
+ type ToolAnswerType = z.infer<typeof ToolAnswerSchema>;
183
+ //#endregion
184
+ //#region src/tool-factory.d.ts
185
+ type ToolChildManifestDefinition = {
186
+ id: string;
187
+ description: UserToolManifestType['description'];
188
+ name: UserToolManifestType['name'];
189
+ icon?: string;
190
+ toolDescription?: string;
191
+ };
192
+ type ToolInputSchema = z.ZodObject<any>;
193
+ type ToolOutputSchema = z.ZodObject<any>;
194
+ type ToolSecretSchema = z.ZodTypeAny | undefined;
195
+ type ToolSecretValue<TSecret extends ToolSecretSchema> = TSecret extends z.ZodTypeAny ? z.output<NoInfer<TSecret>> : undefined;
196
+ type ToolHandlerContext<TSecret extends ToolSecretSchema> = {
197
+ systemVar: SystemVarType;
198
+ secrets?: ToolSecretValue<TSecret>;
199
+ invoke: InvokePort;
200
+ streamResponse: (msg: ToolAnswerType) => void;
201
+ };
202
+ type ToolHandlerFn<TInput extends ToolInputSchema, TOutput extends ToolOutputSchema, TSecret extends ToolSecretSchema> = (input: z.output<NoInfer<TInput>>, ctx: ToolHandlerContext<TSecret>) => Promise<z.output<NoInfer<TOutput>>>;
203
+ type ToolHandlerDefinition<TInput extends ToolInputSchema = ToolInputSchema, TOutput extends ToolOutputSchema = ToolOutputSchema, TSecret extends ToolSecretSchema = undefined> = {
204
+ inputSchema: TInput;
205
+ outputSchema: TOutput;
206
+ secretSchema?: TSecret;
207
+ handler: ToolHandlerFn<TInput, TOutput, TSecret>;
208
+ };
209
+ declare function createToolHandler<TInput extends ToolInputSchema, TOutput extends ToolOutputSchema, TSecret extends ToolSecretSchema = undefined>(def: ToolHandlerDefinition<TInput, TOutput, TSecret>): ToolHandlerDefinition<TInput, TOutput, TSecret>;
210
+ //#endregion
211
+ //#region src/index.d.ts
212
+ interface DefinedToolFactory {
213
+ setSecretSchema<TSecret extends Record<string, unknown>>(schema: z.ZodType<TSecret>): void;
214
+ registerTool(definition: ToolHandlerDefinition, id?: string, childManifest?: ToolChildManifestDefinition): void;
215
+ getSecretSchema(): z.ZodType<any>;
216
+ getToolHandler(): ToolHandlerDefinition;
217
+ getToolHandler(childId: string): ToolHandlerDefinition | undefined;
218
+ getUserToolManifest(): UserToolManifestType;
219
+ getChildManifests(): ToolChildManifestDefinition[];
220
+ }
221
+ declare const defineTool: ({
222
+ manifest,
223
+ handler
224
+ }: {
225
+ manifest: UserToolManifestType;
226
+ handler: ToolHandlerDefinition<any, any, any>;
227
+ }) => DefinedToolFactory;
228
+ declare const defineToolSet: ({
229
+ manifest,
230
+ children,
231
+ secretSchema
232
+ }: {
233
+ manifest: UserToolManifestType;
234
+ secretSchema?: z.ZodObject<any>;
235
+ children: (ToolChildManifestDefinition & {
236
+ handler: ToolHandlerDefinition<any, any, any>;
237
+ })[];
238
+ }) => DefinedToolFactory;
239
+ //#endregion
240
+ export { DefinedToolFactory, createToolHandler, defineTool, defineToolSet };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import e from"zod";import{Readable as t}from"node:stream";import{randomUUID as n}from"node:crypto";import r,{Readable as i}from"stream";var a=class e{stream;closed=!1;constructor(e){this.stream=new t({objectMode:!0,read(){}}),e?.(this)}static create(t){return new e(t)}write(e){this.ensureWritable(),this.stream.push(e)}send(e){this.write(e)}close(){this.closed||(this.closed=!0,this.stream.push(null))}end(){this.close()}fail(e){this.closed=!0,this.stream.destroy(e)}toReadable(){return this.stream}onData(e){return this.stream.on(`data`,e),this}onEnd(e){return this.stream.on(`end`,e),this}onError(e){return this.stream.on(`error`,e),this}async consume(e){for await(let t of this.values())await e(t)}async*values(){for await(let e of this.stream)yield e}ensureWritable(){if(this.closed||this.stream.destroyed)throw Error(`StreamData is already closed`)}};const o=e=>(e=e.replace(/(?<=https?:\/\/)[^\s]+/g,`xxx`),e=e.replace(/ns-[\w-]+/g,`xxx`),e),s=(e,t=``)=>o(typeof e==`string`?e:e?.response?.data?.message||e?.response?.message||e?.message||e?.response?.data?.msg||e?.response?.msg||e?.msg||t),c=`application/javascript,application/json,application/yaml,application/zip,image/jpeg,image/png,image/gif,image/webp,image/svg+xml,application/pdf,text/plain,text/csv,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.presentationml.presentation,application/msword,application/vnd.ms-excel,application/vnd.ms-powerpoint,text/markdown,audio/mpeg,video/mp4,audio/wav,text/html,application/xml,application/gzip,application/octet-stream,multipart/form-data,text/event-stream`.split(`,`),l=e.enum(c).and(e.string().regex(/^[^/]+\/[^/]+$/)),u=e.object({fileKey:e.string(),fileName:e.string(),contentType:l,size:e.number(),etag:e.string(),createTime:e.date()}),d=e.object({fileKey:e.string().optional(),path:e.string().optional(),fileName:e.string().optional(),contentType:l.optional(),overwrite:e.boolean().optional(),file:e.union([e.instanceof(i,{error:`Stream cannot be empty`}),e.union([e.instanceof(Buffer,{error:`Buffer is required`}),e.instanceof(Uint8Array,{error:`Uint8Array is required`})]).transform(e=>e instanceof Uint8Array&&!(e instanceof Buffer)?Buffer.from(e):e)])});e.object({...d.omit({fileKey:!0,overwrite:!0,path:!0}).shape});const f=e.object({...u.omit({fileKey:!0}).shape,accessURL:e.string()}),p=e.enum([`uploadFile`]).enum,m=e=>[e,null];function h(e,t){return g(e)?[null,e]:[null,{reason:e,error:t}]}function g(e){return!!(e&&typeof e==`object`&&`reason`in e&&`error`in e)}const _=`__plugin_ipc_stream__`,v=`__pluginIpcDuplexReply__`;var y=class{pending=new Map;readyHandlers=new Set;errorHandlers=new Set;streamHandlers=new Map;incomingStreams=new Map;bufferedIncomingStreams=new Map;pendingStreamWaiters=new Map;requestHandler=null;eventHandler=null;endpoint;options;unsubscribeMessage;closed=!1;constructor(e,t={}){C(e)?(this.endpoint=e,this.options=t):(this.endpoint=process,this.options=e??{}),this.unsubscribeMessage=x(this.endpoint,e=>{this.dispatch(e).catch(e=>{this.emitError(e instanceof Error?e:Error(String(e)))})})}onReady(e){return this.readyHandlers.add(e),()=>{this.readyHandlers.delete(e)}}onError(e){return this.errorHandlers.add(e),()=>{this.errorHandlers.delete(e)}}setRequestHandler(e){this.requestHandler=e}setEventHandler(e){this.eventHandler=e}async requestDuplex(e,t,r){this.ensureOpen();let i=r?.requestId??n(),a=r?.timeoutMs??this.options.defaultTimeoutMs??3e4,o=Date.now(),s=r?.input===void 0?void 0:this.pipeStream(j(i),r.input,{traceId:r.traceId,streamId:r.inputStreamId,meta:r.inputMeta});s&&s.catch(e=>{this.emitError(e instanceof Error?e:Error(String(e)))});let c=await this.request(e,t,{timeoutMs:a,traceId:r?.traceId,requestId:i});if(!E(c))return{requestId:i,result:c,...s===void 0?{}:{inputDone:s}};let l;if(c.hasOutputStream){let e=Date.now()-o,t=Math.max(1,a-e);l=await this.waitForStream(M(i),{timeoutMs:t})}return{requestId:i,result:c.result,...l===void 0?{}:{output:l},...s===void 0?{}:{inputDone:s}}}replyDuplex(e,t,n){return{[v]:!0,...t===void 0?{}:{result:t},...n?.output===void 0?{}:{output:n.output},...n===void 0?{}:{options:{...n.traceId===void 0?{}:{traceId:n.traceId},...n.outputStreamId===void 0?{}:{outputStreamId:n.outputStreamId},...n.outputMeta===void 0?{}:{outputMeta:n.outputMeta}}}}}async waitForRequestInputStream(e,t){return this.waitForStream(j(e.id),t)}onStream(e,t){this.streamHandlers.has(e)||this.streamHandlers.set(e,new Set);let n=this.streamHandlers.get(e),r=t;return n.add(r),()=>{n.delete(r),n.size===0&&this.streamHandlers.delete(e)}}async waitForStream(e,t){this.ensureOpen();let n=this.bufferedIncomingStreams.get(e);if(n&&n.length>0){let t=n.shift();return n.length===0&&this.bufferedIncomingStreams.delete(e),t}return new Promise((n,r)=>{let i={resolve:n,reject:r};t?.timeoutMs!==void 0&&(i.timer=setTimeout(()=>{let t=this.pendingStreamWaiters.get(e);if(!t)return;let n=t.indexOf(i);n>=0&&t.splice(n,1),t.length===0&&this.pendingStreamWaiters.delete(e),r(Error(`Wait stream timeout: ${e}`))},t.timeoutMs)),this.pendingStreamWaiters.has(e)||this.pendingStreamWaiters.set(e,[]),this.pendingStreamWaiters.get(e).push(i)})}async createWritableStream(e,t){this.ensureOpen();let r=t?.streamId??n(),i=!1;return await this.sendStreamFrame({type:`start`,streamId:r,streamName:e,...t?.meta===void 0?{}:{meta:t.meta}},t?.traceId),{streamId:r,write:async e=>{if(i)throw Error(`Stream is already closed: ${r}`);await this.sendStreamFrame({type:`chunk`,streamId:r,chunk:e},t?.traceId)},end:async()=>{i||(i=!0,await this.sendStreamFrame({type:`end`,streamId:r},t?.traceId))},fail:async e=>{i||(i=!0,await this.sendStreamFrame({type:`error`,streamId:r,error:O(e)},t?.traceId))}}}async pipeStream(e,t,n){let r=await this.createWritableStream(e,n);try{for await(let e of A(t))await r.write(e);await r.end()}catch(e){throw await r.fail(e instanceof Error?e:Error(String(e))),e}}async request(e,t,r){this.ensureOpen();let i=r?.requestId??n(),a=r?.timeoutMs??this.options.defaultTimeoutMs??3e4;return new Promise((n,o)=>{let s=setTimeout(()=>{this.pending.delete(i),o(Object.assign(Error(`Request timeout: ${e}`),{code:`REQUEST_TIMEOUT`,requestId:i,method:e}))},a);this.pending.set(i,{resolve:n,reject:o,timer:s}),this.send({id:i,messageType:`request`,method:e,params:t,...r?.traceId===void 0?{}:{traceId:r.traceId},timestamp:Date.now()}).catch(e=>{clearTimeout(s),this.pending.delete(i),o(e)})})}async emit(e,t,r){this.ensureOpen(),await this.send({id:n(),messageType:`event`,method:e,params:t,...r===void 0?{}:{traceId:r},timestamp:Date.now()})}async sendReady(){this.ensureOpen(),await this.send({id:n(),messageType:`ready`,timestamp:Date.now()})}async reply(e,t){this.ensureOpen(),await this.send({id:e.id,messageType:`response`,...t===void 0?{}:{result:t},...e.traceId===void 0?{}:{traceId:e.traceId},timestamp:Date.now()})}async replyError(e,t){this.ensureOpen();let n=O(t);await this.send({id:e.id,messageType:`error`,error:n,...e.traceId===void 0?{}:{traceId:e.traceId},timestamp:Date.now()})}async close(e=Error(`IPC channel closed`)){this.closed||(this.closed=!0,this.unsubscribeMessage(),this.rejectAllPending(e),this.rejectAllPendingStreamWaiters(e),this.failAllIncomingStreams(e))}async dispatch(e){if(!this.closed){if(e.messageType===`response`||e.messageType===`error`){this.handleResponse(e);return}if(e.messageType===`ready`){this.readyHandlers.forEach(t=>t(e));return}if(e.messageType===`event`){if(T(e)){await this.handleStreamFrameMessage(e);return}await this.eventHandler?.(e);return}e.messageType===`request`&&await this.handleRequest(e)}}handleResponse(e){let t=this.pending.get(e.id);if(t){if(clearTimeout(t.timer),this.pending.delete(e.id),e.error){t.reject(k(e.error));return}t.resolve(e.result)}}async handleRequest(e){if(!e.method){await this.replyError(e,{code:`INVALID_REQUEST`,message:`Request method is required`});return}if(!this.requestHandler){await this.replyError(e,{code:`METHOD_NOT_FOUND`,message:`Method not found: ${e.method}`});return}try{let t=await this.requestHandler(e);if(D(t)){await this.handleDuplexReply(e,t);return}await this.reply(e,t)}catch(t){await this.replyError(e,t instanceof Error?t:Error(String(t)))}}rejectAllPending(e){this.pending.forEach((t,n)=>{clearTimeout(t.timer),t.reject(e),this.pending.delete(n)})}rejectAllPendingStreamWaiters(e){this.pendingStreamWaiters.forEach((t,n)=>{t.forEach(t=>{t.timer&&clearTimeout(t.timer),t.reject(e)}),this.pendingStreamWaiters.delete(n)})}failAllIncomingStreams(e){this.incomingStreams.forEach((t,n)=>{t.stream.fail(e),this.incomingStreams.delete(n)}),this.bufferedIncomingStreams.clear()}async sendStreamFrame(e,t){await this.emit(_,e,t)}async handleDuplexReply(e,t){let n=t.options?.traceId??e.traceId;t.output!==void 0&&this.pipeStream(M(e.id),t.output,{traceId:n,streamId:t.options?.outputStreamId,meta:t.options?.outputMeta}).catch(e=>{this.emitError(e instanceof Error?e:Error(String(e)))}),await this.reply(e,{[v]:!0,hasOutputStream:t.output!==void 0,...t.result===void 0?{}:{result:t.result}})}async handleStreamFrameMessage(e){let t=e.params;switch(t.type){case`start`:{let n={streamId:t.streamId,streamName:t.streamName,stream:a.create(),...e.traceId===void 0?{}:{traceId:e.traceId},...t.meta===void 0?{}:{meta:t.meta}};this.incomingStreams.set(t.streamId,n),this.dispatchIncomingStream(n);return}case`chunk`:{let e=this.incomingStreams.get(t.streamId);if(!e){this.emitError(Error(`Stream not found: ${t.streamId}`));return}e.stream.write(t.chunk);return}case`end`:{let e=this.incomingStreams.get(t.streamId);if(!e)return;e.stream.close(),this.incomingStreams.delete(t.streamId);return}case`error`:{let e=this.incomingStreams.get(t.streamId);if(!e)return;e.stream.fail(k(t.error)),this.incomingStreams.delete(t.streamId);return}}}dispatchIncomingStream(e){let t=!1,n=this.pendingStreamWaiters.get(e.streamName);if(n&&n.length>0){let r=n.shift();r.timer&&clearTimeout(r.timer),n.length===0&&this.pendingStreamWaiters.delete(e.streamName),r.resolve(e),t=!0}let r=this.streamHandlers.get(e.streamName);if(r&&r.size>0){r.forEach(t=>{Promise.resolve(t(e)).catch(e=>{this.emitError(e instanceof Error?e:Error(String(e)))})});return}t||(this.bufferedIncomingStreams.has(e.streamName)||this.bufferedIncomingStreams.set(e.streamName,[]),this.bufferedIncomingStreams.get(e.streamName).push(e))}emitError(e){this.errorHandlers.forEach(t=>t(e))}async send(e){await S(this.endpoint,e)}ensureOpen(){if(this.closed)throw Error(`IPC channel closed`)}};function b(e){return new y(process,e)}function x(e,t){let n=e,r=e=>{w(e)&&t(e)};return n.on(`message`,r),()=>n.off(`message`,r)}function S(e,t){let n=e;if(typeof n.send!=`function`)throw Error(`IPC channel not available`);return new Promise((e,r)=>{n.send(t,t=>{if(t){r(t);return}e()})})}function C(e){return!!(e&&typeof e==`object`&&`on`in e&&typeof e.on==`function`&&`off`in e&&typeof e.off==`function`)}function w(e){return!!(e&&typeof e==`object`&&`messageType`in e)}function T(e){return e.messageType===`event`&&e.method===_}function E(e){return!!(e&&typeof e==`object`&&v in e&&`hasOutputStream`in e)}function D(e){return!!(e&&typeof e==`object`&&v in e)}function O(e){return typeof e==`string`?{code:`HANDLER_ERROR`,message:e}:(e instanceof Error,{code:e.code??`HANDLER_ERROR`,message:e.message,...e.stack===void 0?{}:{stack:e.stack}})}function k(e){return Object.assign(Error(e.message),{code:e.code,stack:e.stack})}function A(e){return e instanceof a?e.values():e}function j(e){return`__plugin_ipc_request_input_stream__:${e}`}function M(e){return`__plugin_ipc_request_output_stream__:${e}`}var N=class{constructor(e,t={}){this.channel=e,this.options=t}async uploadFile(e){try{let t=await this.channel.requestDuplex(`__host_invoke__`,{method:p.uploadFile,args:{contentType:e.contentType,fileName:e.fileName}},{requestId:n(),traceId:this.options.invocationId,input:Buffer.isBuffer(e.file)?r.Readable.from(e.file):e.file});return m(f.parse(t.result))}catch(e){return h({en:`Failed to upload file`,"zh-CN":`上传文件失败`},e)}}};const P=e.enum([`en`,`zh-CN`,`zh-Hant`]).enum;e.object({[P.en]:e.string(),[P[`zh-CN`]]:e.string().optional(),[P[`zh-Hant`]]:e.string().optional()});const F=e.object({[P.en]:e.string(),[P[`zh-CN`]]:e.string(),[P[`zh-Hant`]]:e.string()});e.object({pluginId:e.string(),version:e.string(),etag:e.string()});const I=e.literal(`system`).or(e.string());e.array(e.record(e.string(),F));const L=e.enum([`localPool`,`serverless`]).enum;e.object({pluginId:e.string(),version:e.string().optional(),source:I.optional()});var R=class{channel;getChannel(){if(!this.channel)throw Error(`Channel is not initialized yet.`);return this.channel}mode;checkRuntimeMode(){process.env.RUNTIME_MODE==L.localPool&&(this.mode=L.localPool),process.env.RUNTIME_MODE===`dev`&&(this.mode=`dev`)}async init(){this.checkRuntimeMode(),this.mode===`localPool`&&(this.channel=b(),setImmediate(()=>this.getChannel().sendReady())),this.mode===`dev`&&(this.channel=z().pluginChannel,setImmediate(()=>this.getChannel().sendReady()))}constructor(){this.init()}};function z(){let e=globalThis.__FASTGPT_PLUGIN_LOCAL_DEBUG_RUNTIME__;if(!e)throw Error(`Local debug runtime is not initialized. Set it before importing the plugin.`);return e}function B(e){return e}var V=class t extends R{toolHandlers=new Map;childManifests=new Map;secretSchema=e.record(e.string(),e.unknown());constructor(e){super(),this.userToolManifest=e,this.mode&&this.getChannel().setRequestHandler(async e=>{if(e.method===`run`)try{let{input:t,systemVar:n,childId:r,secrets:i}=e.params,o=this.toolHandlers.get(r??`tool`);if(!o)throw Error(`No tool registered`);let s=a.create(),c=a.create();s.onData(e=>{c.send({type:`stream`,data:e})});let l=await o.handler(t,{systemVar:n,secrets:i,invoke:new N(this.getChannel(),{invocationId:e.traceId}),streamResponse:e=>{s.send(e)}});return c.send({type:`response`,data:l}),c.end(),this.getChannel().replyDuplex(e,void 0,{output:c})}catch(t){let n=a.create();return n.write({data:s(t),type:`error`}),n.end(),this.getChannel().replyDuplex(e,void 0,{output:n})}})}setSecretSchema(e){this.secretSchema=e}registerTool(e,t=`tool`,n){this.toolHandlers.set(t,e),n&&this.childManifests.set(t,n)}static getInstance(e){return new t(e)}getSecretSchema(){return this.secretSchema}getToolHandler(e=`tool`){return this.toolHandlers.get(e)}getUserToolManifest(){return this.userToolManifest}getChildManifests(){return[...this.childManifests.values()]}};const H=({manifest:t,handler:n})=>{let r=V.getInstance(t);return r.setSecretSchema(n.secretSchema??e.object()),r.registerTool(n),r},U=({manifest:t,children:n,secretSchema:r})=>{let i=V.getInstance(t);return n.forEach(e=>{i.registerTool(e.handler,e.id,{id:e.id,description:e.description,name:e.name,...e.icon===void 0?{}:{icon:e.icon},...e.toolDescription===void 0?{}:{toolDescription:e.toolDescription}})}),i.setSecretSchema(r??e.object()),i};export{B as createToolHandler,H as defineTool,U as defineToolSet};
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@fastgpt-plugin/sdk-factory",
3
+ "version": "0.0.1-alpha.1",
4
+ "dependencies": {
5
+ "@fastgpt-plugin/domain": "workspace:*",
6
+ "@fastgpt-plugin/infrastructure": "workspace:*"
7
+ },
8
+ "scripts": {
9
+ "build": "tsdown"
10
+ },
11
+ "devDependencies": {
12
+ "tsdown": "catalog:"
13
+ },
14
+ "type": "module",
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "exports": {
20
+ ".": {
21
+ "import": "./dist/index.js",
22
+ "types": "./dist/index.d.ts"
23
+ },
24
+ "./*": {
25
+ "import": "./dist/*.js",
26
+ "types": "./dist/*.d.ts"
27
+ }
28
+ },
29
+ "private": false,
30
+ "publishConfig": {
31
+ "access": "public"
32
+ }
33
+ }