@fastgpt-plugin/sdk-factory 0.0.1-alpha.6 → 0.0.1-alpha.8

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 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 { createToolHandler, defineTool } from '@fastgpt-plugin/sdk-factory';
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({ query: z.string() }),
128
- outputSchema: z.object({ items: z.array(z.string()) }),
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({ content: z.string() }),
134
- outputSchema: z.object({ summary: z.string() }),
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({ prompt: z.string() }),
216
- outputSchema: z.object({ text: z.string() }),
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
- fileName: z.string(),
240
- size: z.number()
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 || !result) {
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(c(e)||t),c=(e,t=0)=>t>5?void 0:typeof e==`string`?e:!e||typeof e!=`object`?void 0:l(e,t)||f(u(e?.response?.data,t),u(e?.response?.body,t),u(e?.body,t),d(e?.response?.data?.reason),d(e?.response?.reason),e?.response?.data?.message||e?.response?.message||e?.message||e?.response?.data?.msg||e?.response?.msg||e?.msg,d(e?.response?.data?.error),d(e?.response?.error),d(e?.error),c(e?.error,t+1),c(e?.cause,t+1),d(e)),l=(e,t)=>{if(!(!(`reason`in e)&&!(`error`in e)))return p(d(e.reason),c(e.error,t+1))},u=(e,t)=>{if(e){if(typeof e!=`string`)return c(e,t+1);try{let n=JSON.parse(e);return c(n?.error,t+1)||c(n,t+1)}catch{return e||void 0}}},d=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},f=(...e)=>e.find(e=>typeof e==`string`&&e.length>0),p=(...e)=>{let t=e.filter((t,n)=>!!t&&e.indexOf(t)===n);return t.length>0?t.join(`: `):void 0},m=`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(`,`),h=e.enum(m).and(e.string().regex(/^[^/]+\/[^/]+$/));e.object({fileKey:e.string(),fileName:e.string(),contentType:h,size:e.number(),etag:e.string(),createTime:e.date()});const g=e.object({fileKey:e.string().optional(),path:e.string().optional(),fileName:e.string().optional(),contentType:h.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({...g.omit({fileKey:!0,overwrite:!0,path:!0}).shape});const _=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 v=e.enum([`uploadFile`,`userInfo`]).enum,y=e=>[e,null];function b(e,t){return x(e)?[null,e]:[null,{reason:e,error:t}]}function x(e){return!!(e&&typeof e==`object`&&`reason`in e&&`error`in e)}const S=`__host_invoke__`,C=`__plugin_ipc_stream__`,w=`__pluginIpcDuplexReply__`;var T=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={}){k(e)?(this.endpoint=e,this.options=t):(this.endpoint=process,this.options=e??{}),this.unsubscribeMessage=D(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(L(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(!M(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(R(i),{timeoutMs:t})}return{requestId:i,result:c.result,...l===void 0?{}:{output:l},...s===void 0?{}:{inputDone:s}}}replyDuplex(e,t,n){return{[w]:!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(L(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:P(e)},t?.traceId))}}}async pipeStream(e,t,n){let r=await this.createWritableStream(e,n);try{for await(let e of I(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=P(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(j(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(F(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(N(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(C,e,t)}async handleDuplexReply(e,t){let n=t.options?.traceId??e.traceId;t.output!==void 0&&this.pipeStream(R(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,{[w]:!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(F(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 O(this.endpoint,e)}ensureOpen(){if(this.closed)throw Error(`IPC channel closed`)}};function E(e){return new T(process,e)}function D(e,t){let n=e,r=e=>{A(e)&&t(e)};return n.on(`message`,r),()=>n.off(`message`,r)}function O(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 k(e){return!!(e&&typeof e==`object`&&`on`in e&&typeof e.on==`function`&&`off`in e&&typeof e.off==`function`)}function A(e){return!!(e&&typeof e==`object`&&`messageType`in e)}function j(e){return e.messageType===`event`&&e.method===C}function M(e){return!!(e&&typeof e==`object`&&w in e&&`hasOutputStream`in e)}function N(e){return!!(e&&typeof e==`object`&&w in e)}function P(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 F(e){return Object.assign(Error(e.message),{code:e.code,stack:e.stack})}function I(e){return e instanceof a?e.values():e}function L(e){return`__plugin_ipc_request_input_stream__:${e}`}function R(e){return`__plugin_ipc_request_output_stream__:${e}`}var z=class{constructor(e,t={}){this.channel=e,this.options=t}async userInfo(){try{let[e,t]=(await this.channel.requestDuplex(S,{method:v.userInfo,args:{}},{requestId:n(),traceId:this.options.invocationId})).result;return t?b({en:`Failed to get user info`,"zh-CN":`获取用户信息失败`},t):y(e)}catch(e){return b({en:`Failed to get user info`,"zh-CN":`获取用户信息失败`},e)}}async uploadFile(e){try{let[t,r]=(await this.channel.requestDuplex(S,{method:v.uploadFile,args:{contentType:e.contentType,fileName:e.fileName}},{requestId:n(),traceId:this.options.invocationId,input:this.toUploadInputStream(e.file)})).result;return r?b({en:`Failed to upload file`,"zh-CN":`上传文件失败`},r):y(_.parse(t))}catch(e){return b({en:`Failed to upload file`,"zh-CN":`上传文件失败`},e)}}toUploadInputStream(e){return e instanceof r.Readable?e:r.Readable.from(Buffer.from(e))}};const B=e.enum([`en`,`zh-CN`,`zh-Hant`]).enum;e.object({[B.en]:e.string(),[B[`zh-CN`]]:e.string().optional(),[B[`zh-Hant`]]:e.string().optional()});const V=e.object({[B.en]:e.string(),[B[`zh-CN`]]:e.string(),[B[`zh-Hant`]]:e.string()});e.object({pluginId:e.string(),version:e.string(),etag:e.string()});const H=e.literal(`system`).or(e.string()),U=e.object({id:e.string(),name:V});e.array(U);const W=e.enum([`localPool`,`serverless`]).enum;e.object({pluginId:e.string(),version:e.string().optional(),source:H.optional()});var G=class{channel;getChannel(){if(!this.channel)throw Error(`Channel is not initialized yet.`);return this.channel}mode;checkRuntimeMode(){process.env.RUNTIME_MODE==W.localPool&&(this.mode=W.localPool),process.env.RUNTIME_MODE===`dev`&&(this.mode=`dev`)}async init(){this.checkRuntimeMode(),this.mode===`localPool`&&(this.channel=E(),setImmediate(()=>this.getChannel().sendReady())),this.mode===`dev`&&(this.channel=K().pluginChannel,setImmediate(()=>this.getChannel().sendReady()))}constructor(){this.init()}};function K(){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 q(e){return e}var J=class t extends G{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 z(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 Y=({manifest:t,handler:n})=>{let r=J.getInstance(t);return r.setSecretSchema(n.secretSchema??e.object()),r.registerTool(n),r},X=({manifest:t,children:n,secretSchema:r})=>{let i=J.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{q as createToolHandler,Y as defineTool,X 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 getWecomCorpToken(){try{let[e,t]=(await this.channel.request(o.request,{method:O.wecomCorpToken,args:{}},{traceId:this.options.invocationId})).result;return t?A({en:`Failed to get wecom corp token`,"zh-CN":`获取企业微信企业令牌失败`},t):k(e)}catch(e){return A({en:`Failed to get wecom corp token`,"zh-CN":`获取企业微信企业令牌失败`},e)}}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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fastgpt-plugin/sdk-factory",
3
- "version": "0.0.1-alpha.6",
3
+ "version": "0.0.1-alpha.8",
4
4
  "devDependencies": {
5
5
  "tsdown": "^0.21.10"
6
6
  },
@@ -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 { createToolHandler, defineTool } from '@fastgpt-plugin/sdk-factory';
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 { createToolHandler, defineToolSet } from '@fastgpt-plugin/sdk-factory';
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({ query: z.string() }),
124
- outputSchema: z.object({ items: z.array(z.string()) }),
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
- fileName: z.string(),
173
- size: z.number()
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 || !result) {
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 { createToolHandler, defineTool } from '@fastgpt-plugin/sdk-factory';
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 { createToolHandler, defineToolSet } from '@fastgpt-plugin/sdk-factory';
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) => {