@fastgpt-plugin/sdk-factory 0.0.1-alpha.1 → 0.0.1-alpha.10
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 +174 -0
- package/README.md +82 -18
- package/dist/index.d.ts +43 -25
- package/dist/index.js +1 -1
- package/package.json +8 -11
- package/skills/fastgpt-plugin-development/SKILL.md +31 -0
- package/skills/fastgpt-sdk-factory/SKILL.md +259 -0
- package/skills/fastgpt-system-tool-development/SKILL.md +267 -0
package/README.en.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# FastGPT Plugin SDK Factory
|
|
2
|
+
|
|
3
|
+
Language: [简体中文](./README.md) | [English](./README.en.md)
|
|
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
|
+
When developing inside this monorepo, use the workspace dependency directly.
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
The plugin entry file must default-export the instance returned by `defineTool()` or `defineToolSet()`.
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import {
|
|
21
|
+
createToolHandler,
|
|
22
|
+
defineTool,
|
|
23
|
+
type InputSchemaMetaType,
|
|
24
|
+
type OutputSchemaMetaType
|
|
25
|
+
} from '@fastgpt-plugin/sdk-factory';
|
|
26
|
+
import z from 'zod';
|
|
27
|
+
|
|
28
|
+
const handler = createToolHandler({
|
|
29
|
+
inputSchema: z.object({
|
|
30
|
+
text: z.string().meta({
|
|
31
|
+
title: 'Text'
|
|
32
|
+
} satisfies InputSchemaMetaType)
|
|
33
|
+
}),
|
|
34
|
+
outputSchema: z.object({
|
|
35
|
+
result: z.string().meta({
|
|
36
|
+
title: 'Result'
|
|
37
|
+
} satisfies OutputSchemaMetaType)
|
|
38
|
+
}),
|
|
39
|
+
handler: async (input) => {
|
|
40
|
+
return {
|
|
41
|
+
result: input.text.toUpperCase()
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export default defineTool({
|
|
47
|
+
manifest: {
|
|
48
|
+
pluginId: 'uppercase',
|
|
49
|
+
version: '1.0.0',
|
|
50
|
+
name: {
|
|
51
|
+
en: 'Uppercase',
|
|
52
|
+
'zh-CN': '转大写'
|
|
53
|
+
},
|
|
54
|
+
description: {
|
|
55
|
+
en: 'Convert text to uppercase',
|
|
56
|
+
'zh-CN': '将文本转换为大写'
|
|
57
|
+
},
|
|
58
|
+
versionDescription: {
|
|
59
|
+
en: 'Initial version',
|
|
60
|
+
'zh-CN': '初始版本'
|
|
61
|
+
},
|
|
62
|
+
tags: ['tools']
|
|
63
|
+
},
|
|
64
|
+
handler
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## API
|
|
69
|
+
|
|
70
|
+
### `createToolHandler(definition)`
|
|
71
|
+
|
|
72
|
+
Defines a tool handler and infers `input`, `output`, and `secrets` types from Zod schemas.
|
|
73
|
+
|
|
74
|
+
The handler context includes:
|
|
75
|
+
|
|
76
|
+
| Field | Description |
|
|
77
|
+
| --- | --- |
|
|
78
|
+
| `systemVar` | System variables injected by FastGPT. |
|
|
79
|
+
| `secrets` | Secret values validated by `secretSchema`. |
|
|
80
|
+
| `invoke` | Client for invoking host capabilities, such as file upload. |
|
|
81
|
+
| `streamResponse` | Sends streaming tool answers. |
|
|
82
|
+
|
|
83
|
+
### `defineTool(options)`
|
|
84
|
+
|
|
85
|
+
Defines a single-tool plugin. `manifest` describes the plugin metadata, and `handler` contains the execution logic.
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
export default defineTool({
|
|
89
|
+
manifest,
|
|
90
|
+
handler
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### `defineToolSet(options)`
|
|
95
|
+
|
|
96
|
+
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.
|
|
97
|
+
|
|
98
|
+
## Manifest
|
|
99
|
+
|
|
100
|
+
`manifest` uses bilingual i18n fields. Common fields:
|
|
101
|
+
|
|
102
|
+
| Field | Required | Description |
|
|
103
|
+
| --- | --- | --- |
|
|
104
|
+
| `pluginId` | Yes | Unique plugin ID. |
|
|
105
|
+
| `version` | Yes | Plugin version. |
|
|
106
|
+
| `name` | Yes | Plugin name in `{ en, 'zh-CN' }` format. |
|
|
107
|
+
| `description` | Yes | Plugin description in `{ en, 'zh-CN' }` format. |
|
|
108
|
+
| `versionDescription` | No | Version description. |
|
|
109
|
+
| `author` | No | Author. |
|
|
110
|
+
| `repoUrl` | No | Repository URL. |
|
|
111
|
+
| `tutorialUrl` | No | Tutorial URL. |
|
|
112
|
+
| `tags` | No | Plugin tags. |
|
|
113
|
+
| `permission` | No | Plugin permission declarations. |
|
|
114
|
+
| `icon` | No | Plugin icon; the build pipeline can fill it automatically. |
|
|
115
|
+
| `toolDescription` | No | Tool description for the model; the build pipeline can fill it automatically. |
|
|
116
|
+
|
|
117
|
+
## Secret Configuration
|
|
118
|
+
|
|
119
|
+
A single tool can declare `secretSchema` in its handler. A tool set can declare a shared `secretSchema` at the top level of `defineToolSet()`.
|
|
120
|
+
|
|
121
|
+
Schema field metadata is written into the built JSON Schema through Zod `.meta()`. Use `InputSchemaMetaType` for input fields, `OutputSchemaMetaType` for output fields, and `SecretSchemaMetaType` for secret fields. Every `secretSchema` field must include `isSecret`; set it to `true` for values that need encrypted storage.
|
|
122
|
+
|
|
123
|
+
## Host Invocation
|
|
124
|
+
|
|
125
|
+
Use `ctx.invoke` to call FastGPT host capabilities. The SDK currently exposes file upload.
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
const handler = createToolHandler({
|
|
129
|
+
inputSchema: z.object({
|
|
130
|
+
content: z.string().meta({
|
|
131
|
+
title: 'Content'
|
|
132
|
+
} satisfies InputSchemaMetaType)
|
|
133
|
+
}),
|
|
134
|
+
outputSchema: z.object({
|
|
135
|
+
accessURL: z.string().meta({
|
|
136
|
+
title: 'Access URL'
|
|
137
|
+
} satisfies OutputSchemaMetaType),
|
|
138
|
+
fileName: z.string().meta({
|
|
139
|
+
title: 'File Name'
|
|
140
|
+
} satisfies OutputSchemaMetaType),
|
|
141
|
+
size: z.number().meta({
|
|
142
|
+
title: 'Size'
|
|
143
|
+
} satisfies OutputSchemaMetaType)
|
|
144
|
+
}),
|
|
145
|
+
handler: async (input, { invoke }) => {
|
|
146
|
+
const [result, err] = await invoke.uploadFile({
|
|
147
|
+
fileName: 'result.txt',
|
|
148
|
+
contentType: 'text/plain',
|
|
149
|
+
file: Buffer.from(input.content, 'utf-8')
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
if (err) {
|
|
153
|
+
throw err;
|
|
154
|
+
}
|
|
155
|
+
if (!result) {
|
|
156
|
+
throw new Error('Failed to upload file');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
accessURL: result.accessURL,
|
|
161
|
+
fileName: result.fileName,
|
|
162
|
+
size: result.size
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Build
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
pnpm --filter @fastgpt-plugin/sdk-factory build
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
After build, the package entry is `dist/index.js` and type declarations are emitted to `dist/index.d.ts`.
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# FastGPT Plugin SDK Factory
|
|
2
2
|
|
|
3
|
+
语言:[简体中文](./README.md) | [English](./README.en.md)
|
|
4
|
+
|
|
3
5
|
用于构建 FastGPT Tool Plugin 的 TypeScript SDK。它把插件声明、输入输出校验、密钥配置、运行时通信和流式响应封装成少量 API,让工具开发者专注于业务逻辑。
|
|
4
6
|
|
|
5
7
|
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.
|
|
@@ -21,15 +23,24 @@ When developing inside this monorepo, use the workspace dependency directly.
|
|
|
21
23
|
The plugin entry file must default-export the instance returned by `defineTool()` or `defineToolSet()`.
|
|
22
24
|
|
|
23
25
|
```ts
|
|
24
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
createToolHandler,
|
|
28
|
+
defineTool,
|
|
29
|
+
type InputSchemaMetaType,
|
|
30
|
+
type OutputSchemaMetaType
|
|
31
|
+
} from '@fastgpt-plugin/sdk-factory';
|
|
25
32
|
import z from 'zod';
|
|
26
33
|
|
|
27
34
|
const handler = createToolHandler({
|
|
28
35
|
inputSchema: z.object({
|
|
29
|
-
text: z.string()
|
|
36
|
+
text: z.string().meta({
|
|
37
|
+
title: 'Text'
|
|
38
|
+
} satisfies InputSchemaMetaType)
|
|
30
39
|
}),
|
|
31
40
|
outputSchema: z.object({
|
|
32
|
-
result: z.string()
|
|
41
|
+
result: z.string().meta({
|
|
42
|
+
title: 'Result'
|
|
43
|
+
} satisfies OutputSchemaMetaType)
|
|
33
44
|
}),
|
|
34
45
|
handler: async (input) => {
|
|
35
46
|
return {
|
|
@@ -71,13 +82,20 @@ Defines a tool handler and infers `input`, `output`, and `secrets` types from Zo
|
|
|
71
82
|
```ts
|
|
72
83
|
const handler = createToolHandler({
|
|
73
84
|
inputSchema: z.object({
|
|
74
|
-
query: z.string()
|
|
85
|
+
query: z.string().meta({
|
|
86
|
+
title: 'Query'
|
|
87
|
+
} satisfies InputSchemaMetaType)
|
|
75
88
|
}),
|
|
76
89
|
outputSchema: z.object({
|
|
77
|
-
answer: z.string()
|
|
90
|
+
answer: z.string().meta({
|
|
91
|
+
title: 'Answer'
|
|
92
|
+
} satisfies OutputSchemaMetaType)
|
|
78
93
|
}),
|
|
79
94
|
secretSchema: z.object({
|
|
80
|
-
apiKey: z.string()
|
|
95
|
+
apiKey: z.string().meta({
|
|
96
|
+
title: 'API Key',
|
|
97
|
+
isSecret: true
|
|
98
|
+
} satisfies SecretSchemaMetaType)
|
|
81
99
|
}),
|
|
82
100
|
handler: async (input, ctx) => {
|
|
83
101
|
ctx.streamResponse({
|
|
@@ -124,14 +142,30 @@ Defines a tool set with multiple child tools. All child tools share the top-leve
|
|
|
124
142
|
|
|
125
143
|
```ts
|
|
126
144
|
const searchHandler = createToolHandler({
|
|
127
|
-
inputSchema: z.object({
|
|
128
|
-
|
|
145
|
+
inputSchema: z.object({
|
|
146
|
+
query: z.string().meta({
|
|
147
|
+
title: 'Query'
|
|
148
|
+
} satisfies InputSchemaMetaType)
|
|
149
|
+
}),
|
|
150
|
+
outputSchema: z.object({
|
|
151
|
+
items: z.array(z.string()).meta({
|
|
152
|
+
title: 'Items'
|
|
153
|
+
} satisfies OutputSchemaMetaType)
|
|
154
|
+
}),
|
|
129
155
|
handler: async (input) => ({ items: [input.query] })
|
|
130
156
|
});
|
|
131
157
|
|
|
132
158
|
const summaryHandler = createToolHandler({
|
|
133
|
-
inputSchema: z.object({
|
|
134
|
-
|
|
159
|
+
inputSchema: z.object({
|
|
160
|
+
content: z.string().meta({
|
|
161
|
+
title: 'Content'
|
|
162
|
+
} satisfies InputSchemaMetaType)
|
|
163
|
+
}),
|
|
164
|
+
outputSchema: z.object({
|
|
165
|
+
summary: z.string().meta({
|
|
166
|
+
title: 'Summary'
|
|
167
|
+
} satisfies OutputSchemaMetaType)
|
|
168
|
+
}),
|
|
135
169
|
handler: async (input) => ({ summary: input.content.slice(0, 100) })
|
|
136
170
|
});
|
|
137
171
|
|
|
@@ -206,14 +240,33 @@ export default defineToolSet({
|
|
|
206
240
|
|
|
207
241
|
A single tool can declare `secretSchema` in its handler. A tool set can declare a shared `secretSchema` at the top level of `defineToolSet()`.
|
|
208
242
|
|
|
243
|
+
Schema 字段元数据通过 Zod `.meta()` 写入构建后的 JSON Schema。输入字段使用 `InputSchemaMetaType`,输出字段使用 `OutputSchemaMetaType`,密钥字段使用 `SecretSchemaMetaType`。`secretSchema` 的每个字段都要包含 `isSecret`,需要加密存储时设为 `true`。
|
|
244
|
+
|
|
245
|
+
Schema field metadata is written into the built JSON Schema through Zod `.meta()`. Use `InputSchemaMetaType` for input fields, `OutputSchemaMetaType` for output fields, and `SecretSchemaMetaType` for secret fields. Every `secretSchema` field must include `isSecret`; set it to `true` for values that need encrypted storage.
|
|
246
|
+
|
|
209
247
|
```ts
|
|
210
248
|
const secretSchema = z.object({
|
|
211
|
-
apiKey: z.string()
|
|
249
|
+
apiKey: z.string().meta({
|
|
250
|
+
title: 'API Key',
|
|
251
|
+
isSecret: true
|
|
252
|
+
} satisfies SecretSchemaMetaType),
|
|
253
|
+
baseURL: z.url().meta({
|
|
254
|
+
title: 'Base URL',
|
|
255
|
+
isSecret: false
|
|
256
|
+
} satisfies SecretSchemaMetaType)
|
|
212
257
|
});
|
|
213
258
|
|
|
214
259
|
const handler = createToolHandler({
|
|
215
|
-
inputSchema: z.object({
|
|
216
|
-
|
|
260
|
+
inputSchema: z.object({
|
|
261
|
+
prompt: z.string().meta({
|
|
262
|
+
title: 'Prompt'
|
|
263
|
+
} satisfies InputSchemaMetaType)
|
|
264
|
+
}),
|
|
265
|
+
outputSchema: z.object({
|
|
266
|
+
text: z.string().meta({
|
|
267
|
+
title: 'Text'
|
|
268
|
+
} satisfies OutputSchemaMetaType)
|
|
269
|
+
}),
|
|
217
270
|
secretSchema,
|
|
218
271
|
handler: async (input, ctx) => {
|
|
219
272
|
return {
|
|
@@ -232,12 +285,20 @@ Use `ctx.invoke` to call FastGPT host capabilities. The SDK currently exposes fi
|
|
|
232
285
|
```ts
|
|
233
286
|
const handler = createToolHandler({
|
|
234
287
|
inputSchema: z.object({
|
|
235
|
-
content: z.string()
|
|
288
|
+
content: z.string().meta({
|
|
289
|
+
title: 'Content'
|
|
290
|
+
} satisfies InputSchemaMetaType)
|
|
236
291
|
}),
|
|
237
292
|
outputSchema: z.object({
|
|
238
|
-
accessURL: z.string()
|
|
239
|
-
|
|
240
|
-
|
|
293
|
+
accessURL: z.string().meta({
|
|
294
|
+
title: 'Access URL'
|
|
295
|
+
} satisfies OutputSchemaMetaType),
|
|
296
|
+
fileName: z.string().meta({
|
|
297
|
+
title: 'File Name'
|
|
298
|
+
} satisfies OutputSchemaMetaType),
|
|
299
|
+
size: z.number().meta({
|
|
300
|
+
title: 'Size'
|
|
301
|
+
} satisfies OutputSchemaMetaType)
|
|
241
302
|
}),
|
|
242
303
|
handler: async (input, { invoke }) => {
|
|
243
304
|
const [result, err] = await invoke.uploadFile({
|
|
@@ -246,7 +307,10 @@ const handler = createToolHandler({
|
|
|
246
307
|
file: Buffer.from(input.content, 'utf-8')
|
|
247
308
|
});
|
|
248
309
|
|
|
249
|
-
if (err
|
|
310
|
+
if (err) {
|
|
311
|
+
throw err;
|
|
312
|
+
}
|
|
313
|
+
if (!result) {
|
|
250
314
|
throw new Error('Failed to upload file');
|
|
251
315
|
}
|
|
252
316
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import z from "zod";
|
|
2
|
-
import * as node_stream0 from "node:stream";
|
|
2
|
+
import * as _$node_stream0 from "node:stream";
|
|
3
3
|
|
|
4
4
|
//#region src/manifest.type.d.ts
|
|
5
5
|
declare const UserToolManifestSchema: z.ZodObject<{
|
|
@@ -59,15 +59,18 @@ type I18nStringType = z.infer<typeof I18nStringSchema>;
|
|
|
59
59
|
//#endregion
|
|
60
60
|
//#region ../../packages/domain/src/value-objects/result.vo.d.ts
|
|
61
61
|
/** usecase, interface-adapter 都要使用这个类型, 手动处理错误,并且能从内到外把错误透出来*/
|
|
62
|
-
type ResultFailure<E =
|
|
62
|
+
type ResultFailure<E extends Error = Error> = {
|
|
63
63
|
reason: I18nStringType;
|
|
64
64
|
error: E;
|
|
65
|
+
code?: string;
|
|
66
|
+
message?: string;
|
|
67
|
+
data?: unknown;
|
|
65
68
|
};
|
|
66
|
-
type Result<T = unknown, E =
|
|
69
|
+
type Result<T = unknown, E extends Error = Error> = [T, null] | [null, ResultFailure<E>];
|
|
67
70
|
//#endregion
|
|
68
71
|
//#region ../../packages/domain/src/ports/invoke.port.d.ts
|
|
69
72
|
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>>>]>;
|
|
73
|
+
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
74
|
fileName: z.ZodOptional<z.ZodString>;
|
|
72
75
|
contentType: z.ZodOptional<z.ZodIntersection<z.ZodEnum<{
|
|
73
76
|
"application/javascript": "application/javascript";
|
|
@@ -102,9 +105,9 @@ declare const InvokeUploadFileInputSchema: z.ZodObject<{
|
|
|
102
105
|
}, z.core.$strip>;
|
|
103
106
|
declare const InvokeUploadFileOutputSchema: z.ZodObject<{
|
|
104
107
|
accessURL: z.ZodString;
|
|
105
|
-
etag: z.ZodString
|
|
106
|
-
fileName: z.ZodString
|
|
107
|
-
contentType: z.ZodIntersection<z.ZodEnum<{
|
|
108
|
+
etag: z.ZodOptional<z.ZodString>;
|
|
109
|
+
fileName: z.ZodOptional<z.ZodString>;
|
|
110
|
+
contentType: z.ZodOptional<z.ZodIntersection<z.ZodEnum<{
|
|
108
111
|
"application/javascript": "application/javascript";
|
|
109
112
|
"application/json": "application/json";
|
|
110
113
|
"application/yaml": "application/yaml";
|
|
@@ -133,40 +136,50 @@ declare const InvokeUploadFileOutputSchema: z.ZodObject<{
|
|
|
133
136
|
"application/octet-stream": "application/octet-stream";
|
|
134
137
|
"multipart/form-data": "multipart/form-data";
|
|
135
138
|
"text/event-stream": "text/event-stream";
|
|
136
|
-
}>, z.ZodString
|
|
137
|
-
size: z.ZodNumber
|
|
138
|
-
createTime: z.ZodDate
|
|
139
|
+
}>, z.ZodString>>;
|
|
140
|
+
size: z.ZodOptional<z.ZodNumber>;
|
|
141
|
+
createTime: z.ZodOptional<z.ZodDate>;
|
|
139
142
|
}, z.core.$strip>;
|
|
140
143
|
type InvokeUploadFileInputType = z.infer<typeof InvokeUploadFileInputSchema>;
|
|
141
144
|
type InvokeUploadFileOutputType = z.infer<typeof InvokeUploadFileOutputSchema>;
|
|
145
|
+
declare const InvokeUserInfoOutputSchema: z.ZodObject<{
|
|
146
|
+
username: z.ZodString;
|
|
147
|
+
contact: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
148
|
+
memberName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
149
|
+
orgs: z.ZodArray<z.ZodObject<{
|
|
150
|
+
pathId: z.ZodString;
|
|
151
|
+
name: z.ZodString;
|
|
152
|
+
}, z.core.$strip>>;
|
|
153
|
+
groups: z.ZodArray<z.ZodObject<{
|
|
154
|
+
name: z.ZodString;
|
|
155
|
+
}, z.core.$strip>>;
|
|
156
|
+
}, z.core.$strip>;
|
|
157
|
+
type InvokeUserInfoOutputType = z.infer<typeof InvokeUserInfoOutputSchema>;
|
|
158
|
+
type InvokeWecomCorpTokenOutputType = {
|
|
159
|
+
access_token: string;
|
|
160
|
+
expires_in: number;
|
|
161
|
+
};
|
|
142
162
|
/**
|
|
143
163
|
* 反向调用 FastGPT 的能力
|
|
144
164
|
*/
|
|
145
165
|
interface InvokePort {
|
|
146
166
|
/** 上传文件 */
|
|
147
167
|
uploadFile(input: InvokeUploadFileInputType): Promise<Result<InvokeUploadFileOutputType>>;
|
|
168
|
+
userInfo(): Promise<Result<InvokeUserInfoOutputType>>;
|
|
169
|
+
getWecomCorpToken(): Promise<Result<InvokeWecomCorpTokenOutputType>>;
|
|
148
170
|
}
|
|
149
171
|
//#endregion
|
|
150
172
|
//#region ../../packages/domain/src/value-objects/system-var.vo.d.ts
|
|
151
173
|
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
174
|
app: z.ZodObject<{
|
|
162
175
|
id: z.ZodString;
|
|
163
176
|
name: z.ZodString;
|
|
164
177
|
}, z.core.$strip>;
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
178
|
+
chat: z.ZodObject<{
|
|
179
|
+
chatId: z.ZodString;
|
|
180
|
+
uid: z.ZodOptional<z.ZodString>;
|
|
181
|
+
}, z.core.$strip>;
|
|
182
|
+
invokeToken: z.ZodString;
|
|
170
183
|
time: z.ZodString;
|
|
171
184
|
}, z.core.$strip>;
|
|
172
185
|
type SystemVarType = z.infer<typeof SystemVarSchema>;
|
|
@@ -209,6 +222,11 @@ type ToolHandlerDefinition<TInput extends ToolInputSchema = ToolInputSchema, TOu
|
|
|
209
222
|
declare function createToolHandler<TInput extends ToolInputSchema, TOutput extends ToolOutputSchema, TSecret extends ToolSecretSchema = undefined>(def: ToolHandlerDefinition<TInput, TOutput, TSecret>): ToolHandlerDefinition<TInput, TOutput, TSecret>;
|
|
210
223
|
//#endregion
|
|
211
224
|
//#region src/index.d.ts
|
|
225
|
+
type InputSchemaMetaType = z.GlobalMeta;
|
|
226
|
+
type OutputSchemaMetaType = z.GlobalMeta;
|
|
227
|
+
type SecretSchemaMetaType = z.GlobalMeta & {
|
|
228
|
+
/** 标注该字段是否需要加密存储 */isSecret: boolean;
|
|
229
|
+
};
|
|
212
230
|
interface DefinedToolFactory {
|
|
213
231
|
setSecretSchema<TSecret extends Record<string, unknown>>(schema: z.ZodType<TSecret>): void;
|
|
214
232
|
registerTool(definition: ToolHandlerDefinition, id?: string, childManifest?: ToolChildManifestDefinition): void;
|
|
@@ -237,4 +255,4 @@ declare const defineToolSet: ({
|
|
|
237
255
|
})[];
|
|
238
256
|
}) => DefinedToolFactory;
|
|
239
257
|
//#endregion
|
|
240
|
-
export { DefinedToolFactory, createToolHandler, defineTool, defineToolSet };
|
|
258
|
+
export { DefinedToolFactory, InputSchemaMetaType, OutputSchemaMetaType, SecretSchemaMetaType, type ToolHandlerContext, createToolHandler, defineTool, defineToolSet };
|
package/dist/index.js
CHANGED
|
@@ -1 +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};
|
|
1
|
+
import e from"zod";import{Readable as t}from"node:stream";import n,{Readable as r}from"stream";import{randomUUID as i}from"node:crypto";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={ready:`client.ready`,stdio:`client.stdio`,fail:`client.fail`,request:`client.request`},s=e.enum([o.ready,o.stdio,o.fail,o.request]);e.object({pid:e.number().optional(),version:e.string().optional(),runtimeMode:e.string().optional(),capabilities:e.array(e.string()).optional(),startedAt:e.number().optional(),meta:e.unknown().optional()}),e.object({stream:e.enum([`stdout`,`stderr`]),chunk:e.string(),timestamp:e.number().optional()}),e.object({reason:e.enum([`startup`,`runtime`,`crash`,`shutdown`,`unknown`]),error:e.object({code:e.string(),message:e.string(),data:e.unknown().optional()}).optional(),exitCode:e.number().nullable().optional(),signal:e.string().nullable().optional(),timestamp:e.number().optional()});const c={streamFrame:`channel.stream`},l=e.enum([c.streamFrame]),u={request:`host.request`,ping:`host.ping`,shutdown:`host.shutdown`},ee=e.enum([u.request,u.ping,u.shutdown]);e.object({timestamp:e.number()}),e.object({timestamp:e.number(),receivedAt:e.number().optional()}),e.object({reason:e.string().optional(),timeoutMs:e.number().nonnegative().optional()}),e.object({accepted:e.boolean(),message:e.string().optional()}),e.union([s,l,ee]);const d=e.enum([`en`,`zh-CN`,`zh-Hant`]).enum,te=e.object({[d.en]:e.string(),[d[`zh-CN`]]:e.string().optional(),[d[`zh-Hant`]]:e.string().optional()}),ne=e.object({[d.en]:e.string(),[d[`zh-CN`]]:e.string(),[d[`zh-Hant`]]:e.string()}),f=e.literal(`1.0`),p=e.union([e.string(),e.number()]),m=e.object({code:e.string(),message:e.string(),reason:te.optional(),data:e.unknown().optional(),cause:e.lazy(()=>m).optional()}),h={parseError:`PARSE_ERROR`,invalidMessage:`INVALID_MESSAGE`,methodNotFound:`METHOD_NOT_FOUND`,invalidParams:`INVALID_PARAMS`,internalError:`INTERNAL_ERROR`,requestTimeout:`REQUEST_TIMEOUT`,channelClosed:`CHANNEL_CLOSED`,streamError:`STREAM_ERROR`},g=e.object({protocol:f,id:p,method:e.string(),params:e.unknown().optional(),traceId:e.string().optional(),timestamp:e.number().optional()}),re=g.omit({id:!0}),ie=e.object({protocol:f,id:p,result:e.unknown().optional(),traceId:e.string().optional(),timestamp:e.number().optional()}),ae=e.object({protocol:f,id:p,error:m,traceId:e.string().optional(),timestamp:e.number().optional()});e.union([g,re,ie,ae]),e.enum([`ipc`,`tcp`,`http`]),e.enum([`host`,`client`]),e.object({streamId:e.string(),streamName:e.string(),meta:e.unknown().optional()}),e.discriminatedUnion(`type`,[e.object({type:e.literal(`start`),streamId:e.string(),streamName:e.string(),meta:e.unknown().optional()}),e.object({type:e.literal(`chunk`),streamId:e.string(),chunk:e.unknown()}),e.object({type:e.literal(`end`),streamId:e.string()}),e.object({type:e.literal(`error`),streamId:e.string(),error:m})]);const oe=e=>(e=e.replace(/(?<=https?:\/\/)[^\s]+/g,`xxx`),e=e.replace(/ns-[\w-]+/g,`xxx`),e),_=(e,t=``)=>oe(v(e)||t),v=(e,t=0)=>t>5?void 0:typeof e==`string`?e:!e||typeof e!=`object`?void 0:ce(e,t)||se(e,t)||le(y(e?.response?.data,t),y(e?.response?.body,t),y(e?.body,t),x(e?.response?.data?.error?.reason),x(e?.response?.error?.reason),x(e?.error?.reason),x(e?.response?.data?.reason),x(e?.response?.reason),e?.response?.data?.message||e?.response?.data?.error?.message||e?.response?.error?.message||e?.error?.message||e?.response?.message||e?.message||e?.response?.data?.msg||e?.response?.msg||e?.msg,x(e?.response?.data?.error),x(e?.response?.error),x(e?.error),x(e?.reason),v(e?.error,t+1),v(e?.cause,t+1),x(e)),se=(e,t)=>{if(!(!(`reason`in e)||!(`cause`in e)))return S(x(e.reason),b(e.cause,t+1))},ce=(e,t)=>{if(`error`in e)return S(x(e.reason),v(e.error,t+1))},y=(e,t)=>{if(e){if(typeof e!=`string`)return v(e,t+1);try{let n=JSON.parse(e);return v(n?.error,t+1)||v(n,t+1)}catch{return e||void 0}}},b=(e,t)=>!e||typeof e!=`object`?v(e,t):S(x(e.reason),`cause`in e?b(e.cause,t+1):void 0)??v(e,t),x=e=>{if(!e||typeof e!=`object`)return;let t=e,n=t[`zh-CN`]??t.en??t[`zh-Hant`];return typeof n==`string`?n:void 0},le=(...e)=>e.find(e=>typeof e==`string`&&e.length>0),S=(...e)=>{let t=e.filter((t,n)=>!!t&&e.indexOf(t)===n).filter((e,t,n)=>!n.some((n,r)=>r>t&&n.startsWith(`${e}:`)));return t.length>0?t.join(`: `):void 0},ue=`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(`,`),C=e.enum(ue).and(e.string().regex(/^[^/]+\/[^/]+$/)),de=e.object({fileKey:e.string(),fileName:e.string(),contentType:C,size:e.number(),etag:e.string(),createTime:e.date()}),fe=e.object({fileKey:e.string().optional(),path:e.string().optional(),fileName:e.string().optional(),contentType:C.optional(),overwrite:e.boolean().optional(),file:e.union([e.instanceof(r,{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({...fe.omit({fileKey:!0,overwrite:!0,path:!0}).shape});const pe=e.object({...de.omit({fileKey:!0}).partial().shape,accessURL:e.string()});e.object({username:e.string(),contact:e.string().nullish(),memberName:e.string().nullish(),orgs:e.array(e.object({pathId:e.string(),name:e.string()})),groups:e.array(e.object({name:e.string()}))});const w=e.enum([`uploadFile`,`userInfo`,`wecomCorpToken`]).enum,me={en:`Internal Server Error`,"zh-CN":`服务器内部错误`},T=new Map;var E=class extends Error{code;reason;httpStatus;telemetryKind;visibility;severity;data;constructor(e,t={}){super(t.message??e.message,{cause:M(t.cause)}),this.name=`RegisteredError`,this.code=e.code,this.reason=t.reason??e.reason,this.httpStatus=e.httpStatus??500,this.telemetryKind=e.telemetryKind,this.visibility=e.visibility??`public`,this.severity=e.severity??`expected`,this.data=t.data}};function he(e){for(let t of e){if(T.get(t.code))throw Error(`Error definition already registered: ${t.code}`);T.set(t.code,t)}}function ge(e){return T.get(e)}function D(e,t={}){let n=M(t.cause),r=n===void 0?Error(e.en):Error(e.en,{cause:n});return Object.assign(r,{reason:e})}function O(e,t=`Unknown error`){if(e instanceof Error)return e;if(typeof e==`string`)return Error(e);if(I(e))return D(e);if(R(e)){let n=I(e.reason)?e.reason:void 0,r=O(e.error,n?.en??t);return n&&be(n,k(r))?r:n?D(n,{cause:r}):Error(t,{cause:r})}return Error(F(e,t),{cause:e})}function k(e,t){if(e instanceof E)return e.visibility===`internal`?me:e.reason;if(I(e))return e;let n=L(e)?e:void 0;return n&&I(n.reason)?n.reason:n&&I(n.error)?n.error:t||(e instanceof Error?{en:e.message,"zh-CN":e.message}:typeof e==`string`?{en:e,"zh-CN":e}:me)}function A(e,t){return N(e,{includeStack:t?.includeStack??!1,depth:0,seen:new WeakSet})}function j(e){let t=e.cause?j(e.cause):void 0,n=e.code?ge(e.code):void 0;if(n){let r=new E(n,{message:e.message,reason:e.reason,cause:t,data:L(e.data)?e.data:void 0});return e.stack&&(r.stack=e.stack),r}let r=Error(e.message,{cause:t});return r.name=e.name||`Error`,e.stack&&(r.stack=e.stack),Object.assign(r,{...e.code===void 0?{}:{code:e.code},...e.reason===void 0?{}:{reason:e.reason},...e.data===void 0?{}:{data:e.data}})}function M(e){return e===void 0||e instanceof Error?e:typeof e==`string`?Error(e):I(e)?D(e):R(e)?O(e):e}function N(e,t){if(t.depth>5)return{name:`Error`,message:`Error cause depth exceeded`};if(e instanceof E)return{name:e.name,code:e.code,message:e.message,reason:e.reason,httpStatus:e.httpStatus,telemetryKind:e.telemetryKind,visibility:e.visibility,data:e.data,...t.includeStack&&e.stack!==void 0?{stack:e.stack}:{},...e.cause===void 0?{}:{cause:N(e.cause,P(t,e))}};if(e instanceof Error){let n=ve(e,`reason`);return{name:e.name,code:_e(e,`code`),message:e.message,...n===void 0?{}:{reason:n},data:ye(e,`data`),...t.includeStack&&e.stack!==void 0?{stack:e.stack}:{},...e.cause===void 0?{}:{cause:N(e.cause,P(t,e))}}}return typeof e==`string`?{name:`Error`,message:e}:{name:`Error`,message:F(e,`Unknown error`)}}function P(e,t){return e.seen.has(t)?{...e,depth:6}:(e.seen.add(t),{...e,depth:e.depth+1})}function F(e,t){if(e==null)return t;if(L(e)){let t=e.message??e.msg;if(typeof t==`string`&&t.length>0)return t}return String(e)}function _e(e,t){let n=e[t];return typeof n==`string`?n:void 0}function ve(e,t){let n=e[t];return I(n)?n:void 0}function ye(e,t){return e[t]}function I(e){return L(e)&&typeof e.en==`string`&&(e[`zh-CN`]===void 0||typeof e[`zh-CN`]==`string`)&&(e[`zh-Hant`]===void 0||typeof e[`zh-Hant`]==`string`)}function be(e,t){return e.en===t.en&&e[`zh-CN`]===t[`zh-CN`]&&e[`zh-Hant`]===t[`zh-Hant`]}function L(e){return!!(e&&typeof e==`object`)}function R(e){return L(e)&&`error`in e&&(`reason`in e||L(e.error))}const z=e=>[e,null];function B(e,t){return xe(e)?[null,V(e.reason,e.error,e)]:e instanceof Error?[null,V(k(e),e)]:[null,V(e,t===void 0?D(e):Se(e,t))]}function xe(e){return!!(e&&typeof e==`object`&&`reason`in e&&`error`in e)}function Se(e,t){return D(e,{cause:O(t,e.en)})}function V(e,t,n){let r=A(t),i=n?.code??r.code,a=n?.message??r.message,o=n&&`data`in n?n.data:r.data;return{reason:e,error:t,...i===void 0?{}:{code:i},...a===void 0?{}:{message:a},...o===void 0?{}:{data:o}}}var Ce=class{constructor(e,t={}){this.channel=e,this.options=t}async getWecomCorpToken(){try{let[e,t]=(await this.channel.request(o.request,{method:w.wecomCorpToken,args:{}},{traceId:this.options.invocationId})).result;return t?B({en:`Failed to get wecom corp token`,"zh-CN":`获取企业微信企业令牌失败`},t):z(e)}catch(e){return B({en:`Failed to get wecom corp token`,"zh-CN":`获取企业微信企业令牌失败`},e)}}async userInfo(){try{let[e,t]=(await this.channel.request(o.request,{method:w.userInfo,args:{}},{traceId:this.options.invocationId})).result;return t?B({en:`Failed to get user info`,"zh-CN":`获取用户信息失败`},t):z(e)}catch(e){return B({en:`Failed to get user info`,"zh-CN":`获取用户信息失败`},e)}}async uploadFile(e){try{let[t,n]=(await this.channel.request(o.request,{method:w.uploadFile,args:{contentType:e.contentType,fileName:e.fileName}},{traceId:this.options.invocationId,input:this.toUploadInputStream(e.file)})).result;return n?B({en:`Failed to upload file`,"zh-CN":`上传文件失败`},n):z(pe.parse(t))}catch(e){return B({en:`Failed to upload file`,"zh-CN":`上传文件失败`},e)}}toUploadInputStream(e){return e instanceof n.Readable?e:n.Readable.from(Buffer.from(e))}};e.object({pluginId:e.string(),version:e.string(),etag:e.string()});const we=e.literal(`system`).or(e.string()),Te=e.object({id:e.string(),name:ne});e.array(Te);const H=e.enum([`localPool`,`serverless`]).enum;e.object({pluginId:e.string(),version:e.string().optional(),source:we.optional()});const U={unknown:`common.unknown`,operationFailed:`common.operation_failed`,badRequest:`http.bad_request`,unauthorized:`http.unauthorized`,notFound:`http.not_found`,validationFailed:`http.validation_failed`,internalServerError:`http.internal_server_error`,pluginRuntimeManagerDestroyed:`plugin.runtime.manager_destroyed`,pluginRuntimeAlreadyRegistered:`plugin.runtime.already_registered`,pluginRuntimeReplacementPluginNotFound:`plugin.runtime.replacement_plugin_not_found`,pluginRuntimeConfigLoadFailed:`plugin.runtime.config_load_failed`,pluginRuntimeConfigUpdateFailed:`plugin.runtime.config_update_failed`,pluginRuntimeConfigResetFailed:`plugin.runtime.config_reset_failed`,pluginRuntimeConfigInvalid:`plugin.runtime.config_invalid`,pluginRuntimePodQuotaExceeded:`plugin.runtime.pod_quota_exceeded`,pluginRuntimePluginInfoLoadFailed:`plugin.runtime.plugin_info_load_failed`,pluginRuntimeInitializeFailed:`plugin.runtime.initialize_failed`,pluginRuntimePluginNotFound:`plugin.runtime.plugin_not_found`,pluginRuntimeEventNotSupported:`plugin.runtime.event_not_supported`,pluginRuntimeShutdownFailed:`plugin.runtime.shutdown_failed`,pluginInvokeFailed:`plugin.invoke.failed`,pluginInvokeTimeout:`plugin.invoke.timeout`,pluginInvokeQueueTimeout:`plugin.invoke.queue_timeout`};he([{code:U.unknown,message:`Unknown error`,reason:{en:`Unknown error`,"zh-CN":`未知错误`},visibility:`internal`,severity:`unexpected`},{code:U.operationFailed,message:`Operation failed`,reason:{en:`Operation failed`,"zh-CN":`操作失败`}},{code:U.badRequest,message:`Bad Request`,reason:{en:`Bad Request`,"zh-CN":`请求参数错误`},httpStatus:400},{code:U.unauthorized,message:`Unauthorized`,reason:{en:`Unauthorized`,"zh-CN":`未授权`},httpStatus:401},{code:U.notFound,message:`Not Found`,reason:{en:`Not Found`,"zh-CN":`资源未找到`},httpStatus:404},{code:U.validationFailed,message:`Validation failed`,reason:{en:`Validation failed`,"zh-CN":`数据校验失败`},httpStatus:400},{code:U.internalServerError,message:`Internal Server Error`,reason:{en:`Internal Server Error`,"zh-CN":`服务器内部错误`},httpStatus:500,visibility:`internal`,severity:`unexpected`},{code:U.pluginRuntimeManagerDestroyed,message:`Plugin manager already destroyed`,reason:{en:`Plugin manager already destroyed`,"zh-CN":`插件管理器已销毁`}},{code:U.pluginRuntimeAlreadyRegistered,message:`Plugin already registered`,reason:{en:`Plugin already registered`,"zh-CN":`插件已注册`},httpStatus:409,telemetryKind:`plugin_already_registered`},{code:U.pluginRuntimeReplacementPluginNotFound,message:`Replacement plugin not found`,reason:{en:`Replacement plugin not found`,"zh-CN":`替换插件未找到`},httpStatus:404},{code:U.pluginRuntimeConfigLoadFailed,message:`Failed to get plugin runtime config`,reason:{en:`Failed to get plugin runtime config`,"zh-CN":`获取插件运行时配置失败`},telemetryKind:`runtime_config_load_failed`},{code:U.pluginRuntimeConfigUpdateFailed,message:`Failed to update plugin runtime config`,reason:{en:`Failed to update plugin runtime config`,"zh-CN":`更新插件运行时配置失败`},telemetryKind:`runtime_config_update_failed`},{code:U.pluginRuntimeConfigResetFailed,message:`Failed to reset plugin runtime config`,reason:{en:`Failed to reset plugin runtime config`,"zh-CN":`重置插件运行时配置失败`},telemetryKind:`runtime_config_reset_failed`},{code:U.pluginRuntimeConfigInvalid,message:`Invalid plugin runtime config`,reason:{en:`Invalid plugin runtime config`,"zh-CN":`插件运行时配置无效`},httpStatus:400,telemetryKind:`runtime_config_invalid`},{code:U.pluginRuntimePodQuotaExceeded,message:`Pod quota exceeded`,reason:{en:`Pod quota exceeded`,"zh-CN":`Pod 配额超出`},httpStatus:429,telemetryKind:`pod_quota_exceeded`},{code:U.pluginRuntimePluginInfoLoadFailed,message:`Register plugin error, can not get plugin info`,reason:{en:`Register plugin error, can not get plugin info`,"zh-CN":`注册插件失败,无法获取插件信息`}},{code:U.pluginRuntimeInitializeFailed,message:`Failed to initialize plugin runtime`,reason:{en:`Failed to initialize plugin runtime`,"zh-CN":`初始化插件运行时失败`},telemetryKind:`plugin_runtime_initialize_failed`},{code:U.pluginRuntimePluginNotFound,message:`Plugin not found`,reason:{en:`Plugin not found`,"zh-CN":`插件未找到`},httpStatus:404},{code:U.pluginRuntimeEventNotSupported,message:`Event not supported`,reason:{en:`Event not supported`,"zh-CN":`不支持的事件`},httpStatus:400},{code:U.pluginRuntimeShutdownFailed,message:`Error during shutdown`,reason:{en:`Error during shutdown`,"zh-CN":`关闭过程中发生错误`},telemetryKind:`runtime_shutdown_failed`},{code:U.pluginInvokeFailed,message:`Invoke failed`,reason:{en:`Invoke failed`,"zh-CN":`调用失败`},telemetryKind:`invoke_failed`},{code:U.pluginInvokeTimeout,message:`Plugin invocation timed out`,reason:{en:`Plugin invocation timed out`,"zh-CN":`插件调用超时`},httpStatus:504,telemetryKind:`invoke_timeout`},{code:U.pluginInvokeQueueTimeout,message:`Plugin invocation waited too long for an available local-pool pod`,reason:{en:`Plugin invocation waited too long for an available local-pool pod`,"zh-CN":`插件调用等待空闲本地运行实例超时`},httpStatus:503,telemetryKind:`queue_timeout`}]);const W=`__fastgptChannelReply__`,G=`__fastgptChannelError__`;var Ee=class{transport=`ipc`;pending=new Map;errorHandlers=new Set;closeHandlers=new Set;incomingStreams=new Map;bufferedIncomingStreams=new Map;pendingStreamWaiters=new Map;requestHandler=null;notificationHandler=null;closed=!1;unsubscribeMessage;constructor(e,t,n={}){this.side=e,this.endpoint=t,this.options=n,this.unsubscribeMessage=Oe(t,e=>{this.dispatch(e).catch(e=>{this.emitError(e instanceof Error?e:Error(String(e)))})})}async request(e,t,n){this.ensureOpen();let r=n?.id??i(),a=n?.timeoutMs??this.options.defaultTimeoutMs??3e4,o=n?.input===void 0?void 0:this.pipeStream(Re(r),n.input,{traceId:n.traceId,streamId:n.inputStream?.streamId,streamName:n.inputStream?.streamName,meta:n.inputStream?.meta}),s=Date.now();o&&o.catch(e=>{this.emitError(e instanceof Error?e:Error(String(e)))});let c=await new Promise((i,o)=>{let s=setTimeout(()=>{this.pending.delete(r),o(Object.assign(Error(`Request timeout: ${String(e)}`),{code:h.requestTimeout,requestId:r,method:e}))},a);this.pending.set(r,{method:String(e),resolve:i,reject:o,timer:s}),this.send({protocol:`1.0`,id:r,method:String(e),params:t,...n?.traceId===void 0?{}:{traceId:n.traceId},timestamp:Date.now()}).catch(e=>{clearTimeout(s),this.pending.delete(r),o(e)})}),l=c,u;if(Pe(c)&&(l=c.result,c.hasOutputStream)){let e=Date.now()-s,t=Math.max(1,a-e);u=await this.waitForStream(Q(r),{timeoutMs:t})}return{requestId:r,result:l,...u===void 0?{}:{output:u},...o===void 0?{}:{inputDone:o}}}async notify(e,t,n){this.ensureOpen(),await this.send({protocol:`1.0`,method:String(e),params:t,...n?.traceId===void 0?{}:{traceId:n.traceId},timestamp:Date.now()})}setRequestHandler(e){this.requestHandler=e}setNotificationHandler(e){this.notificationHandler=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 n=t?.streamId??i(),r=!1,a=t?.streamName??e;return await this.sendStreamFrame({type:`start`,streamId:n,streamName:a,...t?.meta===void 0?{}:{meta:t.meta}},t?.traceId),{streamId:n,streamName:a,...t?.meta===void 0?{}:{meta:t.meta},write:async e=>{if(r)throw Error(`Stream is already closed: ${n}`);await this.sendStreamFrame({type:`chunk`,streamId:n,chunk:e},t?.traceId)},end:async()=>{r||(r=!0,await this.sendStreamFrame({type:`end`,streamId:n},t?.traceId))},fail:async e=>{r||(r=!0,await this.sendStreamFrame({type:`error`,streamId:n,error:K(e)},t?.traceId))}}}async pipeStream(e,t,n){let r=await this.createWritableStream(e,n);try{for await(let e of Le(t))await r.write(e);await r.end()}catch(e){throw await r.fail(e instanceof Error?e:Error(String(e))),e}}onError(e){return this.errorHandlers.add(e),()=>{this.errorHandlers.delete(e)}}onClose(e){return this.closeHandlers.add(e),()=>{this.closeHandlers.delete(e)}}async close(e=Error(`Channel closed`)){this.closed||(this.closed=!0,this.unsubscribeMessage(),this.rejectAllPending(e),this.rejectAllPendingStreamWaiters(e),this.failAllIncomingStreams(e),this.closeHandlers.forEach(t=>t(e)))}createReply(e,t){return{[W]:!0,...e===void 0?{}:{result:e},...t?.output===void 0?{}:{output:t.output},...t?.outputStream===void 0?{}:{outputStream:t.outputStream}}}async dispatch(e){if(!this.closed){if(Me(e)){this.handleResponse(e);return}if(je(e)){await this.handleRequest(e);return}await this.handleNotification(e)}}handleResponse(e){let t=this.pending.get(e.id);if(t){if(clearTimeout(t.timer),this.pending.delete(e.id),`error`in e){t.reject(q(e.error));return}t.resolve(X(e.result))}}async handleRequest(e){if(!this.requestHandler){await this.replyError(e,{code:h.methodNotFound,message:`Method not found: ${e.method}`});return}try{let t=await this.requestHandler({id:e.id,method:e.method,params:e.params,...e.traceId===void 0?{}:{traceId:e.traceId},raw:e,waitForInputStream:t=>this.waitForStream(Re(e.id),t)});if(Ne(t)){await this.handleReplyDescriptor(e,t);return}await this.reply(e,t)}catch(t){await this.replyError(e,t instanceof Error?t:Error(String(t)))}}async handleNotification(e){if(e.method===c.streamFrame){await this.handleStreamFrame(e.params,e.traceId);return}await this.notificationHandler?.({method:e.method,params:e.params,...e.traceId===void 0?{}:{traceId:e.traceId},raw:e})}async handleReplyDescriptor(e,t){t.output!==void 0&&this.pipeStream(Q(e.id),t.output,{traceId:t.outputStream?.traceId??e.traceId,streamId:t.outputStream?.streamId,streamName:t.outputStream?.streamName,meta:t.outputStream?.meta}).catch(e=>{this.emitError(e instanceof Error?e:Error(String(e)))}),await this.reply(e,{[W]:!0,hasOutputStream:t.output!==void 0,...t.result===void 0?{}:{result:t.result}})}async reply(e,t){await this.send({protocol:`1.0`,id:e.id,...t===void 0?{}:{result:Y(t)},...e.traceId===void 0?{}:{traceId:e.traceId},timestamp:Date.now()})}async replyError(e,t){await this.send({protocol:`1.0`,id:e.id,error:K(t),...e.traceId===void 0?{}:{traceId:e.traceId},timestamp:Date.now()})}async sendStreamFrame(e,t){await this.notify(c.streamFrame,e,{traceId:t})}async handleStreamFrame(e,t){switch(e.type){case`start`:{let n={streamId:e.streamId,streamName:e.streamName,stream:a.create(),...t===void 0?{}:{traceId:t},...e.meta===void 0?{}:{meta:e.meta}};this.incomingStreams.set(e.streamId,n),this.dispatchIncomingStream(n);return}case`chunk`:{let t=this.incomingStreams.get(e.streamId);if(!t){this.emitError(Error(`Stream not found: ${e.streamId}`));return}t.stream.write(e.chunk);return}case`end`:{let t=this.incomingStreams.get(e.streamId);if(!t)return;t.stream.end(),this.incomingStreams.delete(e.streamId);return}case`error`:{let t=this.incomingStreams.get(e.streamId);if(!t)return;t.stream.fail(q(e.error)),this.incomingStreams.delete(e.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}t||(this.bufferedIncomingStreams.has(e.streamName)||this.bufferedIncomingStreams.set(e.streamName,[]),this.bufferedIncomingStreams.get(e.streamName).push(e))}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()}emitError(e){this.errorHandlers.forEach(t=>t(e))}async send(e){await ke(this.endpoint,e)}ensureOpen(){if(this.closed)throw Error(`Channel closed`)}};function De(e){return new Ee(`client`,process,e)}function Oe(e,t){let n=e,r=e=>{Ae(e)&&t(e)};return n.on(`message`,r),()=>n.off(`message`,r)}function ke(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 Ae(e){return!!(e&&typeof e==`object`&&`protocol`in e)}function je(e){return`id`in e&&`method`in e}function Me(e){return`id`in e&&!(`method`in e)&&(`result`in e||`error`in e)}function Ne(e){return!!(e&&typeof e==`object`&&W in e&&e[W]===!0)}function Pe(e){return!!(e&&typeof e==`object`&&W in e&&`hasOutputStream`in e)}function K(e){return typeof e==`string`?{code:h.internalError,message:e}:e instanceof Error?J(A(e)):e}function q(e){return j(Fe(e))}function J(e){return{code:e.code??h.internalError,message:e.message,...e.reason===void 0?{}:{reason:e.reason},...e.data===void 0?{}:{data:e.data},...e.cause===void 0?{}:{cause:J(e.cause)}}}function Fe(e){return{name:`Error`,code:e.code,message:e.message,reason:e.reason,data:e.data,...e.cause===void 0?{}:{cause:Fe(e.cause)}}}function Y(e,t=new WeakSet){return e instanceof Error?{[G]:!0,error:A(e)}:!e||typeof e!=`object`||t.has(e)?e:(t.add(e),Array.isArray(e)?e.map(e=>Y(e,t)):Z(e)?Object.fromEntries(Object.entries(e).map(([e,n])=>[e,Y(n,t)])):e)}function X(e,t=new WeakMap){if(Ie(e))return j(e.error);if(!e||typeof e!=`object`)return e;let n=t.get(e);if(n)return n;if(Array.isArray(e)){let n=[];return t.set(e,n),n.push(...e.map(e=>X(e,t))),n}if(!Z(e))return e;let r={};t.set(e,r);for(let[n,i]of Object.entries(e))r[n]=X(i,t);return r}function Ie(e){return Z(e)&&e[G]===!0&&Z(e.error)&&typeof e.error.name==`string`&&typeof e.error.message==`string`}function Z(e){if(!e||typeof e!=`object`)return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null}function Le(e){return e instanceof a?e.values():e}function Re(e){return`request.input:${String(e)}`}function Q(e){return`request.output:${String(e)}`}var ze=class{channel;getChannel(){if(!this.channel)throw Error(`Channel is not initialized yet.`);return this.channel}mode;checkRuntimeMode(){process.env.RUNTIME_MODE==H.localPool&&(this.mode=H.localPool),process.env.RUNTIME_MODE===`dev`&&(this.mode=`dev`)}async init(){this.checkRuntimeMode(),this.mode===`localPool`&&(this.channel=De(),setImmediate(()=>{this.getChannel().notify(o.ready,{pid:process.pid,runtimeMode:H.localPool,startedAt:Date.now()})})),this.mode===`dev`&&(this.channel=Be().pluginChannel,setImmediate(()=>{this.getChannel().notify(o.ready,{pid:process.pid,runtimeMode:`dev`,startedAt:Date.now()})}))}constructor(){this.init()}};function Be(){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 Ve(e){return e}var $=class t extends ze{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===u.request&&e.params.eventName===`run`)try{let{input:t,systemVar:n,childId:r,secrets:i}=e.params.payload,o=this.toolHandlers.get(r??`tool`);if(!o)throw Error(`No tool registered`);let s=a.create();return(async()=>{try{let r=await o.handler(t,{systemVar:n,secrets:i,invoke:new Ce(this.getChannel(),{invocationId:e.traceId}),streamResponse:e=>{s.send({type:`stream`,data:e})}});s.send({type:`response`,data:r})}catch(e){s.send({data:_(e,`Unknown error during tool execution`),type:`error`})}finally{s.end()}})(),this.getChannel().createReply(void 0,{output:s})}catch(e){let t=a.create();return t.write({data:_(e,`Unknown error during tool execution`),type:`error`}),t.end(),this.getChannel().createReply(void 0,{output:t})}})}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 He=({manifest:t,handler:n})=>{let r=$.getInstance(t);return r.setSecretSchema(n.secretSchema??e.object()),r.registerTool(n),r},Ue=({manifest:t,children:n,secretSchema:r})=>{let i=$.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{Ve as createToolHandler,He as defineTool,Ue as defineToolSet};
|
package/package.json
CHANGED
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fastgpt-plugin/sdk-factory",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
4
|
-
"dependencies": {
|
|
5
|
-
"@fastgpt-plugin/domain": "workspace:*",
|
|
6
|
-
"@fastgpt-plugin/infrastructure": "workspace:*"
|
|
7
|
-
},
|
|
8
|
-
"scripts": {
|
|
9
|
-
"build": "tsdown"
|
|
10
|
-
},
|
|
3
|
+
"version": "0.0.1-alpha.10",
|
|
11
4
|
"devDependencies": {
|
|
12
|
-
"tsdown": "
|
|
5
|
+
"tsdown": "^0.21.10"
|
|
13
6
|
},
|
|
14
7
|
"type": "module",
|
|
15
8
|
"files": [
|
|
16
9
|
"dist",
|
|
17
|
-
"README.md"
|
|
10
|
+
"README.md",
|
|
11
|
+
"skills"
|
|
18
12
|
],
|
|
19
13
|
"exports": {
|
|
20
14
|
".": {
|
|
@@ -29,5 +23,8 @@
|
|
|
29
23
|
"private": false,
|
|
30
24
|
"publishConfig": {
|
|
31
25
|
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsdown"
|
|
32
29
|
}
|
|
33
|
-
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fastgpt-plugin-development
|
|
3
|
+
description: Use as the entry skill when a user wants to develop a FastGPT plugin. Identify the plugin category, then route to the matching specialized skill such as fastgpt-system-tool-development.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# FastGPT 插件开发入口
|
|
7
|
+
|
|
8
|
+
在用户要开发 FastGPT 插件时先使用这个 skill。它只负责判断插件类型和选择后续规范,具体实现规则放在对应专项 skill 中。
|
|
9
|
+
|
|
10
|
+
## 类型判断
|
|
11
|
+
|
|
12
|
+
先确认用户要开发的插件类型:
|
|
13
|
+
|
|
14
|
+
- 系统工具:通过 `@fastgpt-plugin/sdk-factory` 暴露 `defineTool()` 或 `defineToolSet()`,由 FastGPT 作为工具调用。
|
|
15
|
+
- 其他插件类型:按后续新增的专项 skill 处理。
|
|
16
|
+
|
|
17
|
+
当前已支持:
|
|
18
|
+
|
|
19
|
+
- `fastgpt-system-tool-development`: 系统工具插件开发规范,文件位于 `../fastgpt-system-tool-development/SKILL.md`。
|
|
20
|
+
|
|
21
|
+
## 路由规则
|
|
22
|
+
|
|
23
|
+
- 用户提到系统工具、tool、tool-suite、工具调用、`defineTool()`、`defineToolSet()`、`createToolHandler()` 时,继续读取 `../fastgpt-system-tool-development/SKILL.md`。
|
|
24
|
+
- 用户只说“开发插件”且没有说明类型时,先根据需求判断是否属于系统工具;能判断时直接进入对应专项 skill。
|
|
25
|
+
- 无法判断插件类型时,先询问用户插件要暴露成哪类能力,再进入对应专项 skill。
|
|
26
|
+
|
|
27
|
+
## 通用原则
|
|
28
|
+
|
|
29
|
+
- 入口 skill 不承载具体插件代码模板。
|
|
30
|
+
- 专项 skill 负责说明插件怎么写、目录包含哪些内容、通过什么工具写和验证。
|
|
31
|
+
- 新增插件类型时,在这里补一条类型说明和对应 skill 名称。
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fastgpt-sdk-factory
|
|
3
|
+
description: Use when creating, reviewing, or modifying FastGPT Tool Plugins with the @fastgpt-plugin/sdk-factory TypeScript SDK, including defineTool, defineToolSet, createToolHandler, Zod input/output schemas, secretSchema, ctx.invoke host calls, and streaming tool responses.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# FastGPT SDK Factory
|
|
7
|
+
|
|
8
|
+
Use this skill when working on a FastGPT Tool Plugin that imports `@fastgpt-plugin/sdk-factory` or the local `sdk/factory` package.
|
|
9
|
+
|
|
10
|
+
## Core Pattern
|
|
11
|
+
|
|
12
|
+
Plugin entry files default-export the instance returned by `defineTool()` or `defineToolSet()`.
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import {
|
|
16
|
+
createToolHandler,
|
|
17
|
+
defineTool,
|
|
18
|
+
type InputSchemaMetaType,
|
|
19
|
+
type OutputSchemaMetaType
|
|
20
|
+
} from '@fastgpt-plugin/sdk-factory';
|
|
21
|
+
import z from 'zod';
|
|
22
|
+
|
|
23
|
+
const handler = createToolHandler({
|
|
24
|
+
inputSchema: z.object({
|
|
25
|
+
text: z.string().meta({
|
|
26
|
+
title: 'Text'
|
|
27
|
+
} satisfies InputSchemaMetaType)
|
|
28
|
+
}),
|
|
29
|
+
outputSchema: z.object({
|
|
30
|
+
result: z.string().meta({
|
|
31
|
+
title: 'Result'
|
|
32
|
+
} satisfies OutputSchemaMetaType)
|
|
33
|
+
}),
|
|
34
|
+
handler: async (input) => ({
|
|
35
|
+
result: input.text.toUpperCase()
|
|
36
|
+
})
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export default defineTool({
|
|
40
|
+
manifest: {
|
|
41
|
+
pluginId: 'uppercase',
|
|
42
|
+
version: '1.0.0',
|
|
43
|
+
name: {
|
|
44
|
+
en: 'Uppercase',
|
|
45
|
+
'zh-CN': '转大写'
|
|
46
|
+
},
|
|
47
|
+
description: {
|
|
48
|
+
en: 'Convert text to uppercase',
|
|
49
|
+
'zh-CN': '将文本转换为大写'
|
|
50
|
+
},
|
|
51
|
+
versionDescription: {
|
|
52
|
+
en: 'Initial version',
|
|
53
|
+
'zh-CN': '初始版本'
|
|
54
|
+
},
|
|
55
|
+
tags: ['tools']
|
|
56
|
+
},
|
|
57
|
+
handler
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Handler Rules
|
|
62
|
+
|
|
63
|
+
- Define handlers with `createToolHandler({ inputSchema, outputSchema, secretSchema?, handler })`.
|
|
64
|
+
- Use `z.object(...)` for `inputSchema` and `outputSchema` so TypeScript can infer `input` and return types.
|
|
65
|
+
- Import `InputSchemaMetaType`, `OutputSchemaMetaType`, and `SecretSchemaMetaType` from `@fastgpt-plugin/sdk-factory` when schema metadata is used.
|
|
66
|
+
- Add `.meta({ ... } satisfies InputSchemaMetaType)` to input fields and `.meta({ ... } satisfies OutputSchemaMetaType)` to output fields that need UI/manifest metadata.
|
|
67
|
+
- Set `toolDescription` in an input field's `.meta()` when that field should be available as an automatically filled tool-call parameter; without `toolDescription`, users must specify the field manually.
|
|
68
|
+
- Add `.meta({ isSecret: true | false, ... } satisfies SecretSchemaMetaType)` to every `secretSchema` field; set `isSecret: true` for values that must be encrypted at rest.
|
|
69
|
+
- Return an object that matches `outputSchema`; throw errors for failed operations.
|
|
70
|
+
- Add `secretSchema` when plugin configuration needs secrets such as API keys.
|
|
71
|
+
- Read secrets from `ctx.secrets`; the value is typed from `secretSchema`.
|
|
72
|
+
- Use `ctx.streamResponse({ type: 'answer', content })` to emit incremental visible output before the final response.
|
|
73
|
+
- Use `ctx.systemVar` only for FastGPT-provided runtime variables.
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const secretSchema = z.object({
|
|
77
|
+
apiKey: z.string().meta({
|
|
78
|
+
title: 'API Key',
|
|
79
|
+
isSecret: true
|
|
80
|
+
} satisfies SecretSchemaMetaType)
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const handler = createToolHandler({
|
|
84
|
+
inputSchema: z.object({
|
|
85
|
+
query: z.string().meta({
|
|
86
|
+
title: 'Query',
|
|
87
|
+
toolDescription: 'Search query'
|
|
88
|
+
} satisfies InputSchemaMetaType)
|
|
89
|
+
}),
|
|
90
|
+
outputSchema: z.object({
|
|
91
|
+
answer: z.string().meta({
|
|
92
|
+
title: 'Answer'
|
|
93
|
+
} satisfies OutputSchemaMetaType)
|
|
94
|
+
}),
|
|
95
|
+
secretSchema,
|
|
96
|
+
handler: async (input, ctx) => {
|
|
97
|
+
ctx.streamResponse({
|
|
98
|
+
type: 'answer',
|
|
99
|
+
content: `Searching: ${input.query}`
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
answer: `Result for ${input.query}`
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Manifest Rules
|
|
110
|
+
|
|
111
|
+
`manifest` must include these fields unless the surrounding package already supplies them:
|
|
112
|
+
|
|
113
|
+
- `pluginId`: stable unique plugin id.
|
|
114
|
+
- `version`: plugin version, usually semver.
|
|
115
|
+
- `name`: i18n object shaped as `{ en, 'zh-CN' }`.
|
|
116
|
+
- `description`: i18n object shaped as `{ en, 'zh-CN' }`.
|
|
117
|
+
|
|
118
|
+
Common optional fields:
|
|
119
|
+
|
|
120
|
+
- `versionDescription`
|
|
121
|
+
- `author`
|
|
122
|
+
- `repoUrl`
|
|
123
|
+
- `tutorialUrl`
|
|
124
|
+
- `tags`
|
|
125
|
+
- `permission`
|
|
126
|
+
- `icon`
|
|
127
|
+
- `toolDescription`
|
|
128
|
+
|
|
129
|
+
Use `permission` to declare host capabilities required by the plugin. It is an array of permission strings. Declare only the minimum permissions that the plugin actually uses. When using `ctx.invoke` to call host capabilities, declare the matching permission.
|
|
130
|
+
|
|
131
|
+
Supported permissions:
|
|
132
|
+
|
|
133
|
+
- `userInfo:read`: read user information, for example through `ctx.invoke.userInfo()`.
|
|
134
|
+
- `teamInfo:read`: read team information.
|
|
135
|
+
- `model:read`: read model information.
|
|
136
|
+
- `dataset:read`: read dataset information.
|
|
137
|
+
- `file-upload:allow`: upload files, for example through `ctx.invoke.uploadFile()`.
|
|
138
|
+
|
|
139
|
+
Keep `pluginId`, child tool `id`, input field names, and output field names stable for compatibility.
|
|
140
|
+
|
|
141
|
+
## Tool Sets
|
|
142
|
+
|
|
143
|
+
Use `defineToolSet()` for one plugin with multiple child tools. Put shared metadata in the top-level `manifest`; put each child tool's `id`, `name`, `description`, optional `icon`, optional `toolDescription`, and `handler` in `children`.
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
import {
|
|
147
|
+
createToolHandler,
|
|
148
|
+
defineToolSet,
|
|
149
|
+
type InputSchemaMetaType,
|
|
150
|
+
type OutputSchemaMetaType,
|
|
151
|
+
type SecretSchemaMetaType
|
|
152
|
+
} from '@fastgpt-plugin/sdk-factory';
|
|
153
|
+
import z from 'zod';
|
|
154
|
+
|
|
155
|
+
const secretSchema = z.object({
|
|
156
|
+
apiKey: z.string().meta({
|
|
157
|
+
title: 'API Key',
|
|
158
|
+
isSecret: true
|
|
159
|
+
} satisfies SecretSchemaMetaType)
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const searchHandler = createToolHandler({
|
|
163
|
+
inputSchema: z.object({
|
|
164
|
+
query: z.string().meta({
|
|
165
|
+
title: 'Query'
|
|
166
|
+
} satisfies InputSchemaMetaType)
|
|
167
|
+
}),
|
|
168
|
+
outputSchema: z.object({
|
|
169
|
+
items: z.array(z.string()).meta({
|
|
170
|
+
title: 'Items'
|
|
171
|
+
} satisfies OutputSchemaMetaType)
|
|
172
|
+
}),
|
|
173
|
+
secretSchema,
|
|
174
|
+
handler: async (input) => ({ items: [input.query] })
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
export default defineToolSet({
|
|
178
|
+
secretSchema,
|
|
179
|
+
manifest: {
|
|
180
|
+
pluginId: 'text-tools',
|
|
181
|
+
version: '1.0.0',
|
|
182
|
+
name: {
|
|
183
|
+
en: 'Text Tools',
|
|
184
|
+
'zh-CN': '文本工具集'
|
|
185
|
+
},
|
|
186
|
+
description: {
|
|
187
|
+
en: 'Search and summarize text',
|
|
188
|
+
'zh-CN': '搜索和总结文本'
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
children: [
|
|
192
|
+
{
|
|
193
|
+
id: 'search',
|
|
194
|
+
name: {
|
|
195
|
+
en: 'Search',
|
|
196
|
+
'zh-CN': '搜索'
|
|
197
|
+
},
|
|
198
|
+
description: {
|
|
199
|
+
en: 'Search text',
|
|
200
|
+
'zh-CN': '搜索文本'
|
|
201
|
+
},
|
|
202
|
+
toolDescription: 'Search text by query',
|
|
203
|
+
handler: searchHandler
|
|
204
|
+
}
|
|
205
|
+
]
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Host Invocation
|
|
210
|
+
|
|
211
|
+
Use `ctx.invoke` for host capabilities. The SDK exposes `userInfo()` and `uploadFile()`. Declare the corresponding `manifest.permission` item before using a host capability; for example, `ctx.invoke.uploadFile()` requires `file-upload:allow`. These methods return a `Result` tuple shaped as `[result, err]`; check `err` before using `result`. When `err` is present, throw or return that original `err` so host-side error details are preserved.
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
const uploadHandler = createToolHandler({
|
|
215
|
+
inputSchema: z.object({
|
|
216
|
+
content: z.string().meta({
|
|
217
|
+
title: 'Content'
|
|
218
|
+
} satisfies InputSchemaMetaType)
|
|
219
|
+
}),
|
|
220
|
+
outputSchema: z.object({
|
|
221
|
+
accessURL: z.string().meta({
|
|
222
|
+
title: 'Access URL'
|
|
223
|
+
} satisfies OutputSchemaMetaType),
|
|
224
|
+
fileName: z.string().meta({
|
|
225
|
+
title: 'File Name'
|
|
226
|
+
} satisfies OutputSchemaMetaType),
|
|
227
|
+
size: z.number().meta({
|
|
228
|
+
title: 'Size'
|
|
229
|
+
} satisfies OutputSchemaMetaType)
|
|
230
|
+
}),
|
|
231
|
+
handler: async (input, { invoke }) => {
|
|
232
|
+
const [result, err] = await invoke.uploadFile({
|
|
233
|
+
fileName: 'result.txt',
|
|
234
|
+
contentType: 'text/plain',
|
|
235
|
+
file: Buffer.from(input.content, 'utf-8')
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
if (err) {
|
|
239
|
+
throw err;
|
|
240
|
+
}
|
|
241
|
+
if (!result) {
|
|
242
|
+
throw new Error('Failed to upload file');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
accessURL: result.accessURL,
|
|
247
|
+
fileName: result.fileName,
|
|
248
|
+
size: result.size
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Local Development
|
|
255
|
+
|
|
256
|
+
- In external plugins, import from `@fastgpt-plugin/sdk-factory`.
|
|
257
|
+
- Inside this monorepo package tests or fixtures, imports may point at `sdk/factory/src` or `../../src/index` when matching existing local patterns.
|
|
258
|
+
- Build the SDK with `pnpm --filter @fastgpt-plugin/sdk-factory build`.
|
|
259
|
+
- When changing SDK behavior, check `sdk/factory/src/index.ts`, `sdk/factory/src/tool-factory.ts`, `sdk/factory/src/invoke.client.ts`, and fixtures under `sdk/factory/test/fixtures/`.
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fastgpt-system-tool-development
|
|
3
|
+
description: Use when writing a FastGPT system tool plugin with @fastgpt-plugin/sdk-factory and @fastgpt-plugin/cli, including tool structure, SDK writing rules, required files, avatar/logo naming, and build/check/pack tooling.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# FastGPT 系统工具开发规范
|
|
7
|
+
|
|
8
|
+
在编写 FastGPT 系统工具插件时使用这个 skill。目标是指导代理用 `@fastgpt-plugin/sdk-factory` 写出符合 FastGPT 约定的系统工具,并用 `@fastgpt-plugin/cli` 创建、构建、检查和打包。
|
|
9
|
+
|
|
10
|
+
## 使用工具
|
|
11
|
+
|
|
12
|
+
同时参考这些能力:
|
|
13
|
+
|
|
14
|
+
- `fastgpt-sdk-factory`: 处理 `defineTool()`、`defineToolSet()`、`createToolHandler()`、Zod schema、`secretSchema`、`ctx.invoke`、`ctx.streamResponse()` 等 SDK 写法。
|
|
15
|
+
- `cli-usage`: 使用 `@fastgpt-plugin/cli` 的 `create`、`build`、`check`、`pack` 命令。
|
|
16
|
+
|
|
17
|
+
常用命令:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx @fastgpt-plugin/cli create <plugin-name> --type tool --cwd <target-directory>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx @fastgpt-plugin/cli create <plugin-name> --type tool-suite --cwd <target-directory>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx @fastgpt-plugin/cli build --entry <plugin-directory> --output <plugin-directory>/dist
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx @fastgpt-plugin/cli check --entry <plugin-directory> --output <plugin-directory>/dist
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx @fastgpt-plugin/cli pack --entry <plugin-directory> --dist <plugin-directory>/dist --output <plugin-directory>/out
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 系统工具包含内容
|
|
40
|
+
|
|
41
|
+
一个系统工具目录通常包含:
|
|
42
|
+
|
|
43
|
+
- `index.ts`: 系统工具入口,默认导出 `defineTool()` 或 `defineToolSet()` 的返回值。
|
|
44
|
+
- `package.json`: 声明依赖和 `build`、`build:dev`、`pack`、`test` 脚本。
|
|
45
|
+
- `logo.<ext>`: 主工具头像,必须放在工具根目录。
|
|
46
|
+
- `README.md`: 工具用途、输入、输出、密钥配置和使用说明。
|
|
47
|
+
- `assets/`: 可选资源目录,例如图标或示例资源。
|
|
48
|
+
- `dist/`: CLI 构建输出目录,包含 `index.js`、`manifest.json` 和必要资源。
|
|
49
|
+
- `out/`: CLI 打包输出目录,通常包含 `.pkg` 文件。
|
|
50
|
+
|
|
51
|
+
依赖建议:
|
|
52
|
+
|
|
53
|
+
- 运行依赖:`@fastgpt-plugin/sdk-factory`、`zod`。
|
|
54
|
+
- 开发依赖:`@fastgpt-plugin/cli`、`typescript`、`vitest`。
|
|
55
|
+
|
|
56
|
+
## 系统工具写法
|
|
57
|
+
|
|
58
|
+
入口文件必须默认导出 SDK factory 实例:
|
|
59
|
+
|
|
60
|
+
- 单工具使用 `defineTool()`。
|
|
61
|
+
- 多个相关工具组成一个插件时使用 `defineToolSet()`。
|
|
62
|
+
- 每个 handler 用 `createToolHandler()` 定义。
|
|
63
|
+
- `inputSchema` 和 `outputSchema` 使用 `z.object(...)`。
|
|
64
|
+
- `inputSchema` 字段使用 `.meta({ ... } satisfies InputSchemaMetaType)`。
|
|
65
|
+
- `inputSchema` 字段需要被工具调用自动填参时,必须在 `.meta()` 中设置 `toolDescription`;未设置时该字段只能由用户手动指定。
|
|
66
|
+
- `outputSchema` 字段使用 `.meta({ ... } satisfies OutputSchemaMetaType)`。
|
|
67
|
+
- `secretSchema` 字段使用 `.meta({ isSecret: true | false, ... } satisfies SecretSchemaMetaType)`;需要加密存储的字段标记为 `isSecret: true`。
|
|
68
|
+
- handler 返回值必须匹配 `outputSchema`。
|
|
69
|
+
|
|
70
|
+
manifest 至少包含:
|
|
71
|
+
|
|
72
|
+
- `pluginId`: 稳定唯一工具 ID。
|
|
73
|
+
- `version`: 工具版本。
|
|
74
|
+
- `name`: `{ en, 'zh-CN' }` 格式。
|
|
75
|
+
- `description`: `{ en, 'zh-CN' }` 格式。
|
|
76
|
+
|
|
77
|
+
常用可选字段:
|
|
78
|
+
|
|
79
|
+
- `versionDescription`
|
|
80
|
+
- `author`
|
|
81
|
+
- `repoUrl`
|
|
82
|
+
- `tutorialUrl`
|
|
83
|
+
- `tags`
|
|
84
|
+
- `permission`
|
|
85
|
+
- `icon`
|
|
86
|
+
- `toolDescription`
|
|
87
|
+
|
|
88
|
+
`permission` 用于声明插件运行时需要的宿主能力,类型为权限字符串数组。只声明当前插件实际使用的最小权限;没有使用对应能力时不要添加。使用 `ctx.invoke` 反向调用宿主能力时,必须声明对应权限。
|
|
89
|
+
|
|
90
|
+
当前可用权限:
|
|
91
|
+
|
|
92
|
+
- `userInfo:read`: 读取用户信息,例如调用 `ctx.invoke.userInfo()`。
|
|
93
|
+
- `teamInfo:read`: 读取团队信息。
|
|
94
|
+
- `model:read`: 读取模型信息。
|
|
95
|
+
- `dataset:read`: 读取知识库信息。
|
|
96
|
+
- `file-upload:allow`: 上传文件,例如调用 `ctx.invoke.uploadFile()`。
|
|
97
|
+
|
|
98
|
+
兼容性要求:
|
|
99
|
+
|
|
100
|
+
- `pluginId`、工具 `id`、输入字段名、输出字段名对外使用后保持稳定。
|
|
101
|
+
- 用户配置的密钥、API Key、Base URL 等使用 `secretSchema` 描述,通过 `ctx.secrets` 读取。
|
|
102
|
+
- 错误信息应能帮助定位问题,同时避免暴露密钥、令牌和完整上游敏感响应。
|
|
103
|
+
- 需要向用户展示执行进度时使用 `ctx.streamResponse()`。
|
|
104
|
+
- 需要生成文件时使用 `ctx.invoke.uploadFile()`,并在 `manifest.permission` 中声明 `file-upload:allow`;收到 `[result, err]` 的 `err` 时保留原始 `err`,避免覆盖反向调用的宿主错误信息。
|
|
105
|
+
|
|
106
|
+
## 头像规范
|
|
107
|
+
|
|
108
|
+
CLI 构建时会自动扫描系统工具根目录中的头像文件,并写入 `manifest.icon` 或子工具 `icon` 字段。工具代码里通常不用手写 `icon`。
|
|
109
|
+
|
|
110
|
+
命名方式:
|
|
111
|
+
|
|
112
|
+
- 主工具头像:`logo.<ext>`,例如 `logo.svg`、`logo.png`。
|
|
113
|
+
- 工具集子工具头像:`<childId>.logo.<ext>`,例如子工具 `id` 为 `search` 时使用 `search.logo.svg`。
|
|
114
|
+
- 子工具没有独立头像时,CLI 会复用主工具头像。
|
|
115
|
+
|
|
116
|
+
格式限制:
|
|
117
|
+
|
|
118
|
+
- 支持扩展名:`.svg`、`.png`、`.jpg`、`.jpeg`、`.webp`、`.gif`。
|
|
119
|
+
- 头像文件必须放在系统工具根目录,不能放在 `assets/` 后再通过 manifest 引用。
|
|
120
|
+
- `childId` 必须和 `defineToolSet({ children })` 中的子工具 `id` 完全一致。
|
|
121
|
+
- 避免使用空格、中文或特殊符号作为头像文件名的一部分。
|
|
122
|
+
- `logo.*` 和 `<childId>.logo.*` 只保留一个匹配文件,避免不同扩展名同时存在导致扫描结果不明确。
|
|
123
|
+
- `build` 后检查 `dist/manifest.json` 中的 `icon` 字段是否指向已复制到 `dist/` 的头像文件。
|
|
124
|
+
|
|
125
|
+
## 单工具模板
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import {
|
|
129
|
+
createToolHandler,
|
|
130
|
+
defineTool,
|
|
131
|
+
type InputSchemaMetaType,
|
|
132
|
+
type OutputSchemaMetaType
|
|
133
|
+
} from '@fastgpt-plugin/sdk-factory';
|
|
134
|
+
import z from 'zod';
|
|
135
|
+
|
|
136
|
+
const handler = createToolHandler({
|
|
137
|
+
inputSchema: z.object({
|
|
138
|
+
text: z.string().min(1).meta({
|
|
139
|
+
title: 'Text',
|
|
140
|
+
toolDescription: 'Text to normalize'
|
|
141
|
+
} satisfies InputSchemaMetaType)
|
|
142
|
+
}),
|
|
143
|
+
outputSchema: z.object({
|
|
144
|
+
result: z.string().meta({
|
|
145
|
+
title: 'Result'
|
|
146
|
+
} satisfies OutputSchemaMetaType)
|
|
147
|
+
}),
|
|
148
|
+
handler: async (input) => {
|
|
149
|
+
return {
|
|
150
|
+
result: input.text.trim()
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
export default defineTool({
|
|
156
|
+
manifest: {
|
|
157
|
+
pluginId: 'text-normalizer',
|
|
158
|
+
version: '1.0.0',
|
|
159
|
+
name: {
|
|
160
|
+
en: 'Text Normalizer',
|
|
161
|
+
'zh-CN': '文本规范化'
|
|
162
|
+
},
|
|
163
|
+
description: {
|
|
164
|
+
en: 'Normalize input text',
|
|
165
|
+
'zh-CN': '规范化输入文本'
|
|
166
|
+
},
|
|
167
|
+
versionDescription: {
|
|
168
|
+
en: 'Initial version',
|
|
169
|
+
'zh-CN': '初始版本'
|
|
170
|
+
},
|
|
171
|
+
permission: []
|
|
172
|
+
},
|
|
173
|
+
handler
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## 工具集模板
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
import {
|
|
181
|
+
createToolHandler,
|
|
182
|
+
defineToolSet,
|
|
183
|
+
type InputSchemaMetaType,
|
|
184
|
+
type OutputSchemaMetaType,
|
|
185
|
+
type SecretSchemaMetaType
|
|
186
|
+
} from '@fastgpt-plugin/sdk-factory';
|
|
187
|
+
import z from 'zod';
|
|
188
|
+
|
|
189
|
+
const secretSchema = z.object({
|
|
190
|
+
apiKey: z.string().min(1).meta({
|
|
191
|
+
title: 'API Key',
|
|
192
|
+
isSecret: true
|
|
193
|
+
} satisfies SecretSchemaMetaType)
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const searchHandler = createToolHandler({
|
|
197
|
+
inputSchema: z.object({
|
|
198
|
+
query: z.string().min(1).meta({
|
|
199
|
+
title: 'Query',
|
|
200
|
+
toolDescription: 'Search query'
|
|
201
|
+
} satisfies InputSchemaMetaType)
|
|
202
|
+
}),
|
|
203
|
+
outputSchema: z.object({
|
|
204
|
+
items: z.array(z.string()).meta({
|
|
205
|
+
title: 'Items'
|
|
206
|
+
} satisfies OutputSchemaMetaType)
|
|
207
|
+
}),
|
|
208
|
+
secretSchema,
|
|
209
|
+
handler: async (input, ctx) => {
|
|
210
|
+
ctx.streamResponse({
|
|
211
|
+
type: 'answer',
|
|
212
|
+
content: `Searching: ${input.query}`
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
items: []
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
export default defineToolSet({
|
|
222
|
+
secretSchema,
|
|
223
|
+
manifest: {
|
|
224
|
+
pluginId: 'example-search-tools',
|
|
225
|
+
version: '1.0.0',
|
|
226
|
+
name: {
|
|
227
|
+
en: 'Example Search Tools',
|
|
228
|
+
'zh-CN': '示例搜索工具集'
|
|
229
|
+
},
|
|
230
|
+
description: {
|
|
231
|
+
en: 'Search public example data',
|
|
232
|
+
'zh-CN': '搜索公开示例数据'
|
|
233
|
+
},
|
|
234
|
+
permission: []
|
|
235
|
+
},
|
|
236
|
+
children: [
|
|
237
|
+
{
|
|
238
|
+
id: 'search',
|
|
239
|
+
name: {
|
|
240
|
+
en: 'Search',
|
|
241
|
+
'zh-CN': '搜索'
|
|
242
|
+
},
|
|
243
|
+
description: {
|
|
244
|
+
en: 'Search by query',
|
|
245
|
+
'zh-CN': '按查询搜索'
|
|
246
|
+
},
|
|
247
|
+
toolDescription: 'Search public example data by query',
|
|
248
|
+
handler: searchHandler
|
|
249
|
+
}
|
|
250
|
+
]
|
|
251
|
+
});
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## 验证重点
|
|
255
|
+
|
|
256
|
+
- `index.ts` 默认导出正确。
|
|
257
|
+
- `manifest` 字段完整,国际化字段包含 `en` 和 `zh-CN`。
|
|
258
|
+
- `permission` 只包含插件实际使用的权限,且权限值来自当前支持的枚举。
|
|
259
|
+
- 使用 `ctx.invoke` 时已在 `manifest.permission` 声明对应权限,例如上传文件声明 `file-upload:allow`。
|
|
260
|
+
- Zod schema 覆盖全部用户可见输入和输出;需要工具调用自动填参的输入字段包含 `toolDescription`。
|
|
261
|
+
- handler 成功路径返回值与 `outputSchema` 一致。
|
|
262
|
+
- 外部调用失败、空响应、超时、鉴权失败都有明确错误。
|
|
263
|
+
- 密钥配置只通过 `secretSchema` 和 `ctx.secrets` 处理。
|
|
264
|
+
- 系统工具根目录存在 `logo.<ext>`。
|
|
265
|
+
- 工具集子工具头像按 `<childId>.logo.<ext>` 命名,或确认复用主头像。
|
|
266
|
+
- `build` 后存在 `dist/index.js` 和 `dist/manifest.json`。
|
|
267
|
+
- `check` 通过后再执行 `pack`。
|