@fastgpt-plugin/sdk-factory 0.0.1-alpha.5 → 0.0.1-alpha.7
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 +80 -18
- package/dist/index.d.ts +45 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/skills/fastgpt-sdk-factory/SKILL.md +63 -16
- package/skills/fastgpt-system-tool-development/SKILL.md +33 -8
package/README.md
CHANGED
|
@@ -21,15 +21,24 @@ When developing inside this monorepo, use the workspace dependency directly.
|
|
|
21
21
|
The plugin entry file must default-export the instance returned by `defineTool()` or `defineToolSet()`.
|
|
22
22
|
|
|
23
23
|
```ts
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
createToolHandler,
|
|
26
|
+
defineTool,
|
|
27
|
+
type InputSchemaMetaType,
|
|
28
|
+
type OutputSchemaMetaType
|
|
29
|
+
} from '@fastgpt-plugin/sdk-factory';
|
|
25
30
|
import z from 'zod';
|
|
26
31
|
|
|
27
32
|
const handler = createToolHandler({
|
|
28
33
|
inputSchema: z.object({
|
|
29
|
-
text: z.string()
|
|
34
|
+
text: z.string().meta({
|
|
35
|
+
title: 'Text'
|
|
36
|
+
} satisfies InputSchemaMetaType)
|
|
30
37
|
}),
|
|
31
38
|
outputSchema: z.object({
|
|
32
|
-
result: z.string()
|
|
39
|
+
result: z.string().meta({
|
|
40
|
+
title: 'Result'
|
|
41
|
+
} satisfies OutputSchemaMetaType)
|
|
33
42
|
}),
|
|
34
43
|
handler: async (input) => {
|
|
35
44
|
return {
|
|
@@ -71,13 +80,20 @@ Defines a tool handler and infers `input`, `output`, and `secrets` types from Zo
|
|
|
71
80
|
```ts
|
|
72
81
|
const handler = createToolHandler({
|
|
73
82
|
inputSchema: z.object({
|
|
74
|
-
query: z.string()
|
|
83
|
+
query: z.string().meta({
|
|
84
|
+
title: 'Query'
|
|
85
|
+
} satisfies InputSchemaMetaType)
|
|
75
86
|
}),
|
|
76
87
|
outputSchema: z.object({
|
|
77
|
-
answer: z.string()
|
|
88
|
+
answer: z.string().meta({
|
|
89
|
+
title: 'Answer'
|
|
90
|
+
} satisfies OutputSchemaMetaType)
|
|
78
91
|
}),
|
|
79
92
|
secretSchema: z.object({
|
|
80
|
-
apiKey: z.string()
|
|
93
|
+
apiKey: z.string().meta({
|
|
94
|
+
title: 'API Key',
|
|
95
|
+
isSecret: true
|
|
96
|
+
} satisfies SecretSchemaMetaType)
|
|
81
97
|
}),
|
|
82
98
|
handler: async (input, ctx) => {
|
|
83
99
|
ctx.streamResponse({
|
|
@@ -124,14 +140,30 @@ Defines a tool set with multiple child tools. All child tools share the top-leve
|
|
|
124
140
|
|
|
125
141
|
```ts
|
|
126
142
|
const searchHandler = createToolHandler({
|
|
127
|
-
inputSchema: z.object({
|
|
128
|
-
|
|
143
|
+
inputSchema: z.object({
|
|
144
|
+
query: z.string().meta({
|
|
145
|
+
title: 'Query'
|
|
146
|
+
} satisfies InputSchemaMetaType)
|
|
147
|
+
}),
|
|
148
|
+
outputSchema: z.object({
|
|
149
|
+
items: z.array(z.string()).meta({
|
|
150
|
+
title: 'Items'
|
|
151
|
+
} satisfies OutputSchemaMetaType)
|
|
152
|
+
}),
|
|
129
153
|
handler: async (input) => ({ items: [input.query] })
|
|
130
154
|
});
|
|
131
155
|
|
|
132
156
|
const summaryHandler = createToolHandler({
|
|
133
|
-
inputSchema: z.object({
|
|
134
|
-
|
|
157
|
+
inputSchema: z.object({
|
|
158
|
+
content: z.string().meta({
|
|
159
|
+
title: 'Content'
|
|
160
|
+
} satisfies InputSchemaMetaType)
|
|
161
|
+
}),
|
|
162
|
+
outputSchema: z.object({
|
|
163
|
+
summary: z.string().meta({
|
|
164
|
+
title: 'Summary'
|
|
165
|
+
} satisfies OutputSchemaMetaType)
|
|
166
|
+
}),
|
|
135
167
|
handler: async (input) => ({ summary: input.content.slice(0, 100) })
|
|
136
168
|
});
|
|
137
169
|
|
|
@@ -206,14 +238,33 @@ export default defineToolSet({
|
|
|
206
238
|
|
|
207
239
|
A single tool can declare `secretSchema` in its handler. A tool set can declare a shared `secretSchema` at the top level of `defineToolSet()`.
|
|
208
240
|
|
|
241
|
+
Schema 字段元数据通过 Zod `.meta()` 写入构建后的 JSON Schema。输入字段使用 `InputSchemaMetaType`,输出字段使用 `OutputSchemaMetaType`,密钥字段使用 `SecretSchemaMetaType`。`secretSchema` 的每个字段都要包含 `isSecret`,需要加密存储时设为 `true`。
|
|
242
|
+
|
|
243
|
+
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.
|
|
244
|
+
|
|
209
245
|
```ts
|
|
210
246
|
const secretSchema = z.object({
|
|
211
|
-
apiKey: z.string()
|
|
247
|
+
apiKey: z.string().meta({
|
|
248
|
+
title: 'API Key',
|
|
249
|
+
isSecret: true
|
|
250
|
+
} satisfies SecretSchemaMetaType),
|
|
251
|
+
baseURL: z.url().meta({
|
|
252
|
+
title: 'Base URL',
|
|
253
|
+
isSecret: false
|
|
254
|
+
} satisfies SecretSchemaMetaType)
|
|
212
255
|
});
|
|
213
256
|
|
|
214
257
|
const handler = createToolHandler({
|
|
215
|
-
inputSchema: z.object({
|
|
216
|
-
|
|
258
|
+
inputSchema: z.object({
|
|
259
|
+
prompt: z.string().meta({
|
|
260
|
+
title: 'Prompt'
|
|
261
|
+
} satisfies InputSchemaMetaType)
|
|
262
|
+
}),
|
|
263
|
+
outputSchema: z.object({
|
|
264
|
+
text: z.string().meta({
|
|
265
|
+
title: 'Text'
|
|
266
|
+
} satisfies OutputSchemaMetaType)
|
|
267
|
+
}),
|
|
217
268
|
secretSchema,
|
|
218
269
|
handler: async (input, ctx) => {
|
|
219
270
|
return {
|
|
@@ -232,12 +283,20 @@ Use `ctx.invoke` to call FastGPT host capabilities. The SDK currently exposes fi
|
|
|
232
283
|
```ts
|
|
233
284
|
const handler = createToolHandler({
|
|
234
285
|
inputSchema: z.object({
|
|
235
|
-
content: z.string()
|
|
286
|
+
content: z.string().meta({
|
|
287
|
+
title: 'Content'
|
|
288
|
+
} satisfies InputSchemaMetaType)
|
|
236
289
|
}),
|
|
237
290
|
outputSchema: z.object({
|
|
238
|
-
accessURL: z.string()
|
|
239
|
-
|
|
240
|
-
|
|
291
|
+
accessURL: z.string().meta({
|
|
292
|
+
title: 'Access URL'
|
|
293
|
+
} satisfies OutputSchemaMetaType),
|
|
294
|
+
fileName: z.string().meta({
|
|
295
|
+
title: 'File Name'
|
|
296
|
+
} satisfies OutputSchemaMetaType),
|
|
297
|
+
size: z.number().meta({
|
|
298
|
+
title: 'Size'
|
|
299
|
+
} satisfies OutputSchemaMetaType)
|
|
241
300
|
}),
|
|
242
301
|
handler: async (input, { invoke }) => {
|
|
243
302
|
const [result, err] = await invoke.uploadFile({
|
|
@@ -246,7 +305,10 @@ const handler = createToolHandler({
|
|
|
246
305
|
file: Buffer.from(input.content, 'utf-8')
|
|
247
306
|
});
|
|
248
307
|
|
|
249
|
-
if (err
|
|
308
|
+
if (err) {
|
|
309
|
+
throw err;
|
|
310
|
+
}
|
|
311
|
+
if (!result) {
|
|
250
312
|
throw new Error('Failed to upload file');
|
|
251
313
|
}
|
|
252
314
|
|
package/dist/index.d.ts
CHANGED
|
@@ -102,6 +102,40 @@ declare const InvokeUploadFileInputSchema: z.ZodObject<{
|
|
|
102
102
|
}, z.core.$strip>;
|
|
103
103
|
declare const InvokeUploadFileOutputSchema: z.ZodObject<{
|
|
104
104
|
accessURL: z.ZodString;
|
|
105
|
+
etag: z.ZodOptional<z.ZodString>;
|
|
106
|
+
fileName: z.ZodOptional<z.ZodString>;
|
|
107
|
+
contentType: z.ZodOptional<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.ZodOptional<z.ZodNumber>;
|
|
138
|
+
createTime: z.ZodOptional<z.ZodDate>;
|
|
105
139
|
}, z.core.$strip>;
|
|
106
140
|
type InvokeUploadFileInputType = z.infer<typeof InvokeUploadFileInputSchema>;
|
|
107
141
|
type InvokeUploadFileOutputType = z.infer<typeof InvokeUploadFileOutputSchema>;
|
|
@@ -118,6 +152,10 @@ declare const InvokeUserInfoOutputSchema: z.ZodObject<{
|
|
|
118
152
|
}, z.core.$strip>>;
|
|
119
153
|
}, z.core.$strip>;
|
|
120
154
|
type InvokeUserInfoOutputType = z.infer<typeof InvokeUserInfoOutputSchema>;
|
|
155
|
+
type InvokeWecomCorpTokenOutputType = {
|
|
156
|
+
access_token: string;
|
|
157
|
+
expires_in: number;
|
|
158
|
+
};
|
|
121
159
|
/**
|
|
122
160
|
* 反向调用 FastGPT 的能力
|
|
123
161
|
*/
|
|
@@ -125,6 +163,7 @@ interface InvokePort {
|
|
|
125
163
|
/** 上传文件 */
|
|
126
164
|
uploadFile(input: InvokeUploadFileInputType): Promise<Result<InvokeUploadFileOutputType>>;
|
|
127
165
|
userInfo(): Promise<Result<InvokeUserInfoOutputType>>;
|
|
166
|
+
getWecomCorpToken(): Promise<Result<InvokeWecomCorpTokenOutputType>>;
|
|
128
167
|
}
|
|
129
168
|
//#endregion
|
|
130
169
|
//#region ../../packages/domain/src/value-objects/system-var.vo.d.ts
|
|
@@ -180,6 +219,11 @@ type ToolHandlerDefinition<TInput extends ToolInputSchema = ToolInputSchema, TOu
|
|
|
180
219
|
declare function createToolHandler<TInput extends ToolInputSchema, TOutput extends ToolOutputSchema, TSecret extends ToolSecretSchema = undefined>(def: ToolHandlerDefinition<TInput, TOutput, TSecret>): ToolHandlerDefinition<TInput, TOutput, TSecret>;
|
|
181
220
|
//#endregion
|
|
182
221
|
//#region src/index.d.ts
|
|
222
|
+
type InputSchemaMetaType = z.GlobalMeta;
|
|
223
|
+
type OutputSchemaMetaType = z.GlobalMeta;
|
|
224
|
+
type SecretSchemaMetaType = z.GlobalMeta & {
|
|
225
|
+
/** 标注该字段是否需要加密存储 */isSecret: boolean;
|
|
226
|
+
};
|
|
183
227
|
interface DefinedToolFactory {
|
|
184
228
|
setSecretSchema<TSecret extends Record<string, unknown>>(schema: z.ZodType<TSecret>): void;
|
|
185
229
|
registerTool(definition: ToolHandlerDefinition, id?: string, childManifest?: ToolChildManifestDefinition): void;
|
|
@@ -208,4 +252,4 @@ declare const defineToolSet: ({
|
|
|
208
252
|
})[];
|
|
209
253
|
}) => DefinedToolFactory;
|
|
210
254
|
//#endregion
|
|
211
|
-
export { DefinedToolFactory, type ToolHandlerContext, createToolHandler, defineTool, defineToolSet };
|
|
255
|
+
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(/^[^/]+\/[^/]+$/));e.object({fileKey:e.string(),fileName:e.string(),contentType:l,size:e.number(),etag:e.string(),createTime:e.date()});const u=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({...u.omit({fileKey:!0,overwrite:!0,path:!0}).shape});const d=e.object({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 f=e.enum([`uploadFile`,`userInfo`]).enum,p=e=>[e,null];function m(e,t){return h(e)?[null,e]:[null,{reason:e,error:t}]}function h(e){return!!(e&&typeof e==`object`&&`reason`in e&&`error`in e)}const g=`__host_invoke__`,_=`__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 userInfo(){try{let[e,t]=(await this.channel.requestDuplex(g,{method:f.userInfo,args:{}},{requestId:n(),traceId:this.options.invocationId})).result;return t?m({en:`Failed to get user info`,"zh-CN":`获取用户信息失败`},t):p(e)}catch(e){return m({en:`Failed to get user info`,"zh-CN":`获取用户信息失败`},e)}}async uploadFile(e){try{let[t,r]=(await this.channel.requestDuplex(g,{method:f.uploadFile,args:{contentType:e.contentType,fileName:e.fileName}},{requestId:n(),traceId:this.options.invocationId,input:this.toUploadInputStream(e.file)})).result;return r?m({en:`Failed to upload file`,"zh-CN":`上传文件失败`},r):p(d.parse(t))}catch(e){return m({en:`Failed to upload file`,"zh-CN":`上传文件失败`},e)}}toUploadInputStream(e){return e instanceof r.Readable?e:r.Readable.from(Buffer.from(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()),L=e.object({id:e.string(),name:F});e.array(L);const R=e.enum([`localPool`,`serverless`]).enum;e.object({pluginId:e.string(),version:e.string().optional(),source:I.optional()});var z=class{channel;getChannel(){if(!this.channel)throw Error(`Channel is not initialized yet.`);return this.channel}mode;checkRuntimeMode(){process.env.RUNTIME_MODE==R.localPool&&(this.mode=R.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=B().pluginChannel,setImmediate(()=>this.getChannel().sendReady()))}constructor(){this.init()}};function B(){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 V(e){return e}var H=class t extends z{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 c=a.create();return(async()=>{try{let r=await o.handler(t,{systemVar:n,secrets:i,invoke:new N(this.getChannel(),{invocationId:e.traceId}),streamResponse:e=>{c.send({type:`stream`,data:e})}});c.send({type:`response`,data:r})}catch(e){c.send({data:s(e,`Unknown error during tool execution`),type:`error`})}finally{c.end()}})(),this.getChannel().replyDuplex(e,void 0,{output:c})}catch(t){let n=a.create();return n.write({data:s(t,`Unknown error during tool execution`),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 U=({manifest:t,handler:n})=>{let r=H.getInstance(t);return r.setSecretSchema(n.secretSchema??e.object()),r.registerTool(n),r},W=({manifest:t,children:n,secretSchema:r})=>{let i=H.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{V as createToolHandler,U as defineTool,W 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`},d=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,d]);const f=e.literal(`1.0`),p=e.union([e.string(),e.number()]),m=e.object({code:e.string(),message:e.string(),data:e.unknown().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()}),_=g.omit({id:!0}),v=e.object({protocol:f,id:p,result:e.unknown().optional(),traceId:e.string().optional(),timestamp:e.number().optional()}),y=e.object({protocol:f,id:p,error:m,traceId:e.string().optional(),timestamp:e.number().optional()});e.union([g,_,v,y]),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 ee=e=>(e=e.replace(/(?<=https?:\/\/)[^\s]+/g,`xxx`),e=e.replace(/ns-[\w-]+/g,`xxx`),e),b=(e,t=``)=>ee(x(e)||t),x=(e,t=0)=>t>5?void 0:typeof e==`string`?e:!e||typeof e!=`object`?void 0:S(e,t)||T(C(e?.response?.data,t),C(e?.response?.body,t),C(e?.body,t),w(e?.response?.data?.reason),w(e?.response?.reason),e?.response?.data?.message||e?.response?.message||e?.message||e?.response?.data?.msg||e?.response?.msg||e?.msg,w(e?.response?.data?.error),w(e?.response?.error),w(e?.error),x(e?.error,t+1),x(e?.cause,t+1),w(e)),S=(e,t)=>{if(!(!(`reason`in e)&&!(`error`in e)))return E(w(e.reason),x(e.error,t+1))},C=(e,t)=>{if(e){if(typeof e!=`string`)return x(e,t+1);try{let n=JSON.parse(e);return x(n?.error,t+1)||x(n,t+1)}catch{return e||void 0}}},w=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},T=(...e)=>e.find(e=>typeof e==`string`&&e.length>0),E=(...e)=>{let t=e.filter((t,n)=>!!t&&e.indexOf(t)===n);return t.length>0?t.join(`: `):void 0},te=`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(`,`),D=e.enum(te).and(e.string().regex(/^[^/]+\/[^/]+$/)),ne=e.object({fileKey:e.string(),fileName:e.string(),contentType:D,size:e.number(),etag:e.string(),createTime:e.date()}),re=e.object({fileKey:e.string().optional(),path:e.string().optional(),fileName:e.string().optional(),contentType:D.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({...re.omit({fileKey:!0,overwrite:!0,path:!0}).shape});const ie=e.object({...ne.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 O=e.enum([`uploadFile`,`userInfo`,`wecomCorpToken`]).enum,k=e=>[e,null];function A(e,t){return j(e)?[null,e]:[null,{reason:e,error:t}]}function j(e){return!!(e&&typeof e==`object`&&`reason`in e&&`error`in e)}var M=class{constructor(e,t={}){this.channel=e,this.options=t}async userInfo(){try{let[e,t]=(await this.channel.request(o.request,{method:O.userInfo,args:{}},{traceId:this.options.invocationId})).result;return t?A({en:`Failed to get user info`,"zh-CN":`获取用户信息失败`},t):k(e)}catch(e){return A({en:`Failed to get user info`,"zh-CN":`获取用户信息失败`},e)}}async uploadFile(e){try{let[t,n]=(await this.channel.request(o.request,{method:O.uploadFile,args:{contentType:e.contentType,fileName:e.fileName}},{traceId:this.options.invocationId,input:this.toUploadInputStream(e.file)})).result;return n?A({en:`Failed to upload file`,"zh-CN":`上传文件失败`},n):k(ie.parse(t))}catch(e){return A({en:`Failed to upload file`,"zh-CN":`上传文件失败`},e)}}toUploadInputStream(e){return e instanceof n.Readable?e:n.Readable.from(Buffer.from(e))}};const N=e.enum([`en`,`zh-CN`,`zh-Hant`]).enum;e.object({[N.en]:e.string(),[N[`zh-CN`]]:e.string().optional(),[N[`zh-Hant`]]:e.string().optional()});const P=e.object({[N.en]:e.string(),[N[`zh-CN`]]:e.string(),[N[`zh-Hant`]]:e.string()});e.object({pluginId:e.string(),version:e.string(),etag:e.string()});const F=e.literal(`system`).or(e.string()),I=e.object({id:e.string(),name:P});e.array(I);const L=e.enum([`localPool`,`serverless`]).enum;e.object({pluginId:e.string(),version:e.string().optional(),source:F.optional()});const R=`__fastgptChannelReply__`;var z=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=V(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(X(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(q(c)&&(l=c.result,c.hasOutputStream)){let e=Date.now()-s,t=Math.max(1,a-e);u=await this.waitForStream(Z(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:J(e)},t?.traceId))}}}async pipeStream(e,t,n){let r=await this.createWritableStream(e,n);try{for await(let e of ae(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{[R]:!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(G(e)){this.handleResponse(e);return}if(W(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(Y(e.error));return}t.resolve(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(X(e.id),t)});if(K(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(Z(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,{[R]:!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: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:J(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(Y(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 H(this.endpoint,e)}ensureOpen(){if(this.closed)throw Error(`Channel closed`)}};function B(e){return new z(`client`,process,e)}function V(e,t){let n=e,r=e=>{U(e)&&t(e)};return n.on(`message`,r),()=>n.off(`message`,r)}function H(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 U(e){return!!(e&&typeof e==`object`&&`protocol`in e)}function W(e){return`id`in e&&`method`in e}function G(e){return`id`in e&&!(`method`in e)&&(`result`in e||`error`in e)}function K(e){return!!(e&&typeof e==`object`&&R in e&&e[R]===!0)}function q(e){return!!(e&&typeof e==`object`&&R in e&&`hasOutputStream`in e)}function J(e){return typeof e==`string`?{code:h.internalError,message:e}:e instanceof Error?{code:e.code??h.internalError,message:e.message,...e.stack===void 0?{}:{data:{stack:e.stack}}}:e}function Y(e){return Object.assign(Error(e.message),{code:e.code,data:e.data})}function ae(e){return e instanceof a?e.values():e}function X(e){return`request.input:${String(e)}`}function Z(e){return`request.output:${String(e)}`}var oe=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().notify(o.ready,{pid:process.pid,runtimeMode:L.localPool,startedAt:Date.now()})})),this.mode===`dev`&&(this.channel=se().pluginChannel,setImmediate(()=>{this.getChannel().notify(o.ready,{pid:process.pid,runtimeMode:`dev`,startedAt:Date.now()})}))}constructor(){this.init()}};function se(){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 ce(e){return e}var Q=class t extends oe{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 M(this.getChannel(),{invocationId:e.traceId}),streamResponse:e=>{s.send({type:`stream`,data:e})}});s.send({type:`response`,data:r})}catch(e){s.send({data:b(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:b(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 le=({manifest:t,handler:n})=>{let r=Q.getInstance(t);return r.setSecretSchema(n.secretSchema??e.object()),r.registerTool(n),r},$=({manifest:t,children:n,secretSchema:r})=>{let i=Q.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{ce as createToolHandler,le as defineTool,$ as defineToolSet};
|
package/package.json
CHANGED
|
@@ -12,15 +12,24 @@ Use this skill when working on a FastGPT Tool Plugin that imports `@fastgpt-plug
|
|
|
12
12
|
Plugin entry files default-export the instance returned by `defineTool()` or `defineToolSet()`.
|
|
13
13
|
|
|
14
14
|
```ts
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
createToolHandler,
|
|
17
|
+
defineTool,
|
|
18
|
+
type InputSchemaMetaType,
|
|
19
|
+
type OutputSchemaMetaType
|
|
20
|
+
} from '@fastgpt-plugin/sdk-factory';
|
|
16
21
|
import z from 'zod';
|
|
17
22
|
|
|
18
23
|
const handler = createToolHandler({
|
|
19
24
|
inputSchema: z.object({
|
|
20
|
-
text: z.string()
|
|
25
|
+
text: z.string().meta({
|
|
26
|
+
title: 'Text'
|
|
27
|
+
} satisfies InputSchemaMetaType)
|
|
21
28
|
}),
|
|
22
29
|
outputSchema: z.object({
|
|
23
|
-
result: z.string()
|
|
30
|
+
result: z.string().meta({
|
|
31
|
+
title: 'Result'
|
|
32
|
+
} satisfies OutputSchemaMetaType)
|
|
24
33
|
}),
|
|
25
34
|
handler: async (input) => ({
|
|
26
35
|
result: input.text.toUpperCase()
|
|
@@ -53,6 +62,9 @@ export default defineTool({
|
|
|
53
62
|
|
|
54
63
|
- Define handlers with `createToolHandler({ inputSchema, outputSchema, secretSchema?, handler })`.
|
|
55
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
|
+
- Add `.meta({ isSecret: true | false, ... } satisfies SecretSchemaMetaType)` to every `secretSchema` field; set `isSecret: true` for values that must be encrypted at rest.
|
|
56
68
|
- Return an object that matches `outputSchema`; throw errors for failed operations.
|
|
57
69
|
- Add `secretSchema` when plugin configuration needs secrets such as API keys.
|
|
58
70
|
- Read secrets from `ctx.secrets`; the value is typed from `secretSchema`.
|
|
@@ -61,15 +73,22 @@ export default defineTool({
|
|
|
61
73
|
|
|
62
74
|
```ts
|
|
63
75
|
const secretSchema = z.object({
|
|
64
|
-
apiKey: z.string()
|
|
76
|
+
apiKey: z.string().meta({
|
|
77
|
+
title: 'API Key',
|
|
78
|
+
isSecret: true
|
|
79
|
+
} satisfies SecretSchemaMetaType)
|
|
65
80
|
});
|
|
66
81
|
|
|
67
82
|
const handler = createToolHandler({
|
|
68
83
|
inputSchema: z.object({
|
|
69
|
-
query: z.string()
|
|
84
|
+
query: z.string().meta({
|
|
85
|
+
title: 'Query'
|
|
86
|
+
} satisfies InputSchemaMetaType)
|
|
70
87
|
}),
|
|
71
88
|
outputSchema: z.object({
|
|
72
|
-
answer: z.string()
|
|
89
|
+
answer: z.string().meta({
|
|
90
|
+
title: 'Answer'
|
|
91
|
+
} satisfies OutputSchemaMetaType)
|
|
73
92
|
}),
|
|
74
93
|
secretSchema,
|
|
75
94
|
handler: async (input, ctx) => {
|
|
@@ -112,16 +131,33 @@ Keep `pluginId`, child tool `id`, input field names, and output field names stab
|
|
|
112
131
|
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`.
|
|
113
132
|
|
|
114
133
|
```ts
|
|
115
|
-
import {
|
|
134
|
+
import {
|
|
135
|
+
createToolHandler,
|
|
136
|
+
defineToolSet,
|
|
137
|
+
type InputSchemaMetaType,
|
|
138
|
+
type OutputSchemaMetaType,
|
|
139
|
+
type SecretSchemaMetaType
|
|
140
|
+
} from '@fastgpt-plugin/sdk-factory';
|
|
116
141
|
import z from 'zod';
|
|
117
142
|
|
|
118
143
|
const secretSchema = z.object({
|
|
119
|
-
apiKey: z.string()
|
|
144
|
+
apiKey: z.string().meta({
|
|
145
|
+
title: 'API Key',
|
|
146
|
+
isSecret: true
|
|
147
|
+
} satisfies SecretSchemaMetaType)
|
|
120
148
|
});
|
|
121
149
|
|
|
122
150
|
const searchHandler = createToolHandler({
|
|
123
|
-
inputSchema: z.object({
|
|
124
|
-
|
|
151
|
+
inputSchema: z.object({
|
|
152
|
+
query: z.string().meta({
|
|
153
|
+
title: 'Query'
|
|
154
|
+
} satisfies InputSchemaMetaType)
|
|
155
|
+
}),
|
|
156
|
+
outputSchema: z.object({
|
|
157
|
+
items: z.array(z.string()).meta({
|
|
158
|
+
title: 'Items'
|
|
159
|
+
} satisfies OutputSchemaMetaType)
|
|
160
|
+
}),
|
|
125
161
|
secretSchema,
|
|
126
162
|
handler: async (input) => ({ items: [input.query] })
|
|
127
163
|
});
|
|
@@ -160,17 +196,25 @@ export default defineToolSet({
|
|
|
160
196
|
|
|
161
197
|
## Host Invocation
|
|
162
198
|
|
|
163
|
-
Use `ctx.invoke` for host capabilities. The SDK exposes `userInfo()` and `uploadFile()`. These methods return a `Result` tuple shaped as `[result, err]`; check `err` before using `result`.
|
|
199
|
+
Use `ctx.invoke` for host capabilities. The SDK exposes `userInfo()` and `uploadFile()`. 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.
|
|
164
200
|
|
|
165
201
|
```ts
|
|
166
202
|
const uploadHandler = createToolHandler({
|
|
167
203
|
inputSchema: z.object({
|
|
168
|
-
content: z.string()
|
|
204
|
+
content: z.string().meta({
|
|
205
|
+
title: 'Content'
|
|
206
|
+
} satisfies InputSchemaMetaType)
|
|
169
207
|
}),
|
|
170
208
|
outputSchema: z.object({
|
|
171
|
-
accessURL: z.string()
|
|
172
|
-
|
|
173
|
-
|
|
209
|
+
accessURL: z.string().meta({
|
|
210
|
+
title: 'Access URL'
|
|
211
|
+
} satisfies OutputSchemaMetaType),
|
|
212
|
+
fileName: z.string().meta({
|
|
213
|
+
title: 'File Name'
|
|
214
|
+
} satisfies OutputSchemaMetaType),
|
|
215
|
+
size: z.number().meta({
|
|
216
|
+
title: 'Size'
|
|
217
|
+
} satisfies OutputSchemaMetaType)
|
|
174
218
|
}),
|
|
175
219
|
handler: async (input, { invoke }) => {
|
|
176
220
|
const [result, err] = await invoke.uploadFile({
|
|
@@ -179,7 +223,10 @@ const uploadHandler = createToolHandler({
|
|
|
179
223
|
file: Buffer.from(input.content, 'utf-8')
|
|
180
224
|
});
|
|
181
225
|
|
|
182
|
-
if (err
|
|
226
|
+
if (err) {
|
|
227
|
+
throw err;
|
|
228
|
+
}
|
|
229
|
+
if (!result) {
|
|
183
230
|
throw new Error('Failed to upload file');
|
|
184
231
|
}
|
|
185
232
|
|
|
@@ -61,6 +61,9 @@ npx @fastgpt-plugin/cli pack --entry <plugin-directory> --dist <plugin-directory
|
|
|
61
61
|
- 多个相关工具组成一个插件时使用 `defineToolSet()`。
|
|
62
62
|
- 每个 handler 用 `createToolHandler()` 定义。
|
|
63
63
|
- `inputSchema` 和 `outputSchema` 使用 `z.object(...)`。
|
|
64
|
+
- `inputSchema` 字段使用 `.meta({ ... } satisfies InputSchemaMetaType)`。
|
|
65
|
+
- `outputSchema` 字段使用 `.meta({ ... } satisfies OutputSchemaMetaType)`。
|
|
66
|
+
- `secretSchema` 字段使用 `.meta({ isSecret: true | false, ... } satisfies SecretSchemaMetaType)`;需要加密存储的字段标记为 `isSecret: true`。
|
|
64
67
|
- handler 返回值必须匹配 `outputSchema`。
|
|
65
68
|
|
|
66
69
|
manifest 至少包含:
|
|
@@ -87,7 +90,7 @@ manifest 至少包含:
|
|
|
87
90
|
- 用户配置的密钥、API Key、Base URL 等使用 `secretSchema` 描述,通过 `ctx.secrets` 读取。
|
|
88
91
|
- 错误信息应能帮助定位问题,同时避免暴露密钥、令牌和完整上游敏感响应。
|
|
89
92
|
- 需要向用户展示执行进度时使用 `ctx.streamResponse()`。
|
|
90
|
-
- 需要生成文件时使用 `ctx.invoke.uploadFile()
|
|
93
|
+
- 需要生成文件时使用 `ctx.invoke.uploadFile()`;收到 `[result, err]` 的 `err` 时保留原始 `err`,避免覆盖反向调用的宿主错误信息。
|
|
91
94
|
|
|
92
95
|
## 头像规范
|
|
93
96
|
|
|
@@ -111,15 +114,24 @@ CLI 构建时会自动扫描系统工具根目录中的头像文件,并写入
|
|
|
111
114
|
## 单工具模板
|
|
112
115
|
|
|
113
116
|
```ts
|
|
114
|
-
import {
|
|
117
|
+
import {
|
|
118
|
+
createToolHandler,
|
|
119
|
+
defineTool,
|
|
120
|
+
type InputSchemaMetaType,
|
|
121
|
+
type OutputSchemaMetaType
|
|
122
|
+
} from '@fastgpt-plugin/sdk-factory';
|
|
115
123
|
import z from 'zod';
|
|
116
124
|
|
|
117
125
|
const handler = createToolHandler({
|
|
118
126
|
inputSchema: z.object({
|
|
119
|
-
text: z.string().min(1)
|
|
127
|
+
text: z.string().min(1).meta({
|
|
128
|
+
title: 'Text'
|
|
129
|
+
} satisfies InputSchemaMetaType)
|
|
120
130
|
}),
|
|
121
131
|
outputSchema: z.object({
|
|
122
|
-
result: z.string()
|
|
132
|
+
result: z.string().meta({
|
|
133
|
+
title: 'Result'
|
|
134
|
+
} satisfies OutputSchemaMetaType)
|
|
123
135
|
}),
|
|
124
136
|
handler: async (input) => {
|
|
125
137
|
return {
|
|
@@ -152,19 +164,32 @@ export default defineTool({
|
|
|
152
164
|
## 工具集模板
|
|
153
165
|
|
|
154
166
|
```ts
|
|
155
|
-
import {
|
|
167
|
+
import {
|
|
168
|
+
createToolHandler,
|
|
169
|
+
defineToolSet,
|
|
170
|
+
type InputSchemaMetaType,
|
|
171
|
+
type OutputSchemaMetaType,
|
|
172
|
+
type SecretSchemaMetaType
|
|
173
|
+
} from '@fastgpt-plugin/sdk-factory';
|
|
156
174
|
import z from 'zod';
|
|
157
175
|
|
|
158
176
|
const secretSchema = z.object({
|
|
159
|
-
apiKey: z.string().min(1)
|
|
177
|
+
apiKey: z.string().min(1).meta({
|
|
178
|
+
title: 'API Key',
|
|
179
|
+
isSecret: true
|
|
180
|
+
} satisfies SecretSchemaMetaType)
|
|
160
181
|
});
|
|
161
182
|
|
|
162
183
|
const searchHandler = createToolHandler({
|
|
163
184
|
inputSchema: z.object({
|
|
164
|
-
query: z.string().min(1)
|
|
185
|
+
query: z.string().min(1).meta({
|
|
186
|
+
title: 'Query'
|
|
187
|
+
} satisfies InputSchemaMetaType)
|
|
165
188
|
}),
|
|
166
189
|
outputSchema: z.object({
|
|
167
|
-
items: z.array(z.string())
|
|
190
|
+
items: z.array(z.string()).meta({
|
|
191
|
+
title: 'Items'
|
|
192
|
+
} satisfies OutputSchemaMetaType)
|
|
168
193
|
}),
|
|
169
194
|
secretSchema,
|
|
170
195
|
handler: async (input, ctx) => {
|