diff --git a/lib/invoke/README.md b/lib/invoke/README.md new file mode 100644 index 00000000..1bfaa827 --- /dev/null +++ b/lib/invoke/README.md @@ -0,0 +1,271 @@ +# FastGPT Reverse Invocation Framework + +## 概述 + +这是一个通用的反向调用框架,允许运行在 Worker 线程中的工具通过消息传递机制调用 FastGPT 的 API 方法。 + +## 架构 + +``` +Worker Thread Main Thread + | | + | invoke('method', params) | + |------------------------------->| + | | + | 路由到处理器 + | 自动注入 systemVar + | 执行 API 调用 + | | + |<-------------------------------| + | 返回结果或错误 | +``` + +## 快速开始 + +### 在工具中使用 + +```typescript +import { invoke } from '@/invoke'; + +export async function myTool(input: ToolInput): Promise { + // 调用 getAccessToken(systemVar 自动注入) + const accessToken = await invoke('getAccessToken', {}); + + // 使用 token 调用外部 API + const response = await fetch('https://api.example.com/data', { + headers: { + Authorization: `Bearer ${accessToken}` + } + }); + + return { + data: await response.json() + }; +} +``` + +## 添加新方法 + +### 步骤 1: 创建方法实现 + +创建 `lib/invoke/yourMethod.ts`: + +```typescript +import { registerInvokeHandler } from './registry'; +import type { SystemVarType } from '@tool/type/req'; + +// 定义参数类型 +type YourMethodParams = { + param1: string; + param2?: number; +}; + +// 定义返回类型 +type YourMethodResult = { + success: boolean; + data: any; +}; + +// 实现方法 +async function yourMethod( + params: YourMethodParams, + systemVar: SystemVarType +): Promise { + // 可以使用 systemVar 中的信息 + const userId = systemVar.user.id; + const teamId = systemVar.user.teamId; + + // 实现你的逻辑 + const result = await doSomething(params, userId, teamId); + + return { + success: true, + data: result + }; +} + +// 注册方法 +registerInvokeHandler('yourMethod', yourMethod); + +export { yourMethod }; +``` + +### 步骤 2: 在入口文件中导入 + +编辑 `lib/invoke/index.ts`,添加导入: + +```typescript +import './yourMethod'; +``` + +### 步骤 3: 在工具中使用 + +```typescript +import { invoke } from '@/invoke'; + +const result = await invoke('yourMethod', { + param1: 'value', + param2: 123 +}); +``` + +## 可用方法 + +### getAccessToken + +获取 FastGPT 访问令牌。 + +```typescript +const token = await invoke('getAccessToken', {}); +``` + +**参数**: 无(systemVar 自动注入) + +**返回**: `string` - Access token + +## SystemVar 结构 + +SystemVar 会自动注入到每个方法中,包含以下信息: + +```typescript +{ + user: { + id: string; // 用户 ID + username: string; // 用户名 + teamId: string; // 团队 ID + teamName: string; // 团队名称 + membername: string; // 成员名称(tmbId) + contact: string; // 联系方式 + name: string; // 显示名称 + }, + app: { + id: string; // 应用 ID + name: string; // 应用名称 + }, + tool: { + id: string; // 工具 ID + version: string; // 工具版本 + }, + time: string; // 时间戳 +} +``` + +## 技术细节 + +### 超时机制 + +所有 invoke 调用默认 120 秒超时。超时后会自动拒绝 Promise。 + +### 错误处理 + +遵循项目规范,直接抛出错误: + +```typescript +try { + const result = await invoke('someMethod', params); +} catch (error) { + console.error('Invoke failed:', error.message); +} +``` + +### 类型安全 + +使用 TypeScript 泛型指定返回类型: + +```typescript +const token = await invoke('getAccessToken', {}); +const data = await invoke('getData', { id: '123' }); +``` + +## 文件结构 + +``` +lib/invoke/ +├── index.ts # 统一导出入口 +├── registry.ts # 方法注册表 +├── const.ts # 常量配置 +├── accessToken.ts # getAccessToken 方法实现 +└── README.md # 本文档 + +lib/worker/ +├── invoke.ts # invoke 函数实现 +├── worker.ts # Worker 端消息处理 +├── index.ts # Main Thread 消息处理 +└── type.ts # 类型定义 +``` + +## 兼容性 + +- ✅ Bun 运行时 +- ✅ Node.js v22+ +- ✅ 使用标准 Node.js API +- ✅ 完整的 TypeScript 支持 + +## 安全注意事项 + +1. **方法白名单**: 只有注册的方法可以被调用 +2. **参数验证**: 建议使用 Zod 验证输入参数 +3. **权限检查**: 通过 systemVar 验证用户权限 +4. **超时保护**: 自动 120 秒超时防止资源占用 + +## 示例:完整的方法实现 + +```typescript +// lib/invoke/sendNotification.ts +import { z } from 'zod'; +import { registerInvokeHandler } from './registry'; +import type { SystemVarType } from '@tool/type/req'; +import { FastGPTBaseURL } from './const'; + +// 使用 Zod 验证参数 +const SendNotificationParamsSchema = z.object({ + message: z.string().min(1), + type: z.enum(['info', 'warning', 'error']).default('info') +}); + +type SendNotificationParams = z.infer; + +async function sendNotification( + params: SendNotificationParams, + systemVar: SystemVarType +): Promise<{ success: boolean; messageId: string }> { + // 验证参数 + const validated = SendNotificationParamsSchema.parse(params); + + // 调用 FastGPT API + const url = new URL('/api/notification/send', FastGPTBaseURL); + const response = await fetch(url, { + method: 'POST', + headers: { + 'content-type': 'application/json', + 'authtoken': process.env.AUTH_TOKEN || '' + }, + body: JSON.stringify({ + userId: systemVar.user.id, + teamId: systemVar.user.teamId, + message: validated.message, + type: validated.type + }) + }); + + if (!response.ok) { + throw new Error(`Failed to send notification: ${response.statusText}`); + } + + const result = await response.json(); + return result; +} + +registerInvokeHandler('sendNotification', sendNotification); + +export { sendNotification }; +``` + +使用: + +```typescript +await invoke('sendNotification', { + message: 'Processing complete', + type: 'info' +}); +``` diff --git a/lib/invoke/accessToken.ts b/lib/invoke/accessToken.ts new file mode 100644 index 00000000..92c03b39 --- /dev/null +++ b/lib/invoke/accessToken.ts @@ -0,0 +1,48 @@ +import { FastGPTBaseURL } from './const'; +import type { SystemVarType } from '@tool/type/req'; +import { registerInvokeHandler } from './registry'; +import { addLog } from '@/utils/log'; + +// type GetAccessTokenParams = { +// // Currently no additional params needed +// // Future: could add scope, permissions, etc. +// }; + +type RequestAccessTokenBody = { + toolId: string; + teamId: string; + tmbId: string; +}; + +async function requestAccessToken(body: RequestAccessTokenBody): Promise { + const url = new URL('/api/plugin/getAccessToken', FastGPTBaseURL); + addLog.debug('getAccessToken', { url }); + const res = (await fetch(url, { + headers: { + authtoken: process.env.AUTH_TOKEN || '', + 'content-type': 'application/json' + }, + method: 'POST', + body: JSON.stringify(body) + }).then((res) => res.json())) as { data: { accessToken: string } }; + + addLog.debug('getAccessToken', res.data); + if (res.data.accessToken) { + return res.data.accessToken; + } + + throw new Error('Failed to get access token'); +} + +async function getAccessToken(params: any, systemVar: SystemVarType): Promise { + return await requestAccessToken({ + toolId: systemVar.tool.id, + teamId: systemVar.user.teamId, + tmbId: systemVar.user.membername + }); +} + +// Register the method +registerInvokeHandler('getAccessToken', getAccessToken); + +export { getAccessToken, requestAccessToken }; diff --git a/lib/invoke/const.ts b/lib/invoke/const.ts new file mode 100644 index 00000000..6d1552b5 --- /dev/null +++ b/lib/invoke/const.ts @@ -0,0 +1 @@ +export const FastGPTBaseURL = process.env.FASTGPT_BASE_URL || 'http://localhost:3000'; diff --git a/lib/invoke/index.ts b/lib/invoke/index.ts new file mode 100644 index 00000000..9b0bab1b --- /dev/null +++ b/lib/invoke/index.ts @@ -0,0 +1,32 @@ +/** + * FastGPT Reverse Invocation Framework + * + * This module provides a way for tools running in worker threads to invoke + * FastGPT API methods through a message-passing mechanism. + * + * Usage in tools: + * ```typescript + * import { invoke } from '@/invoke'; + * + * const accessToken = await invoke('getAccessToken', {}); + * ``` + * + * To add new methods: + * 1. Create a file in lib/invoke/ (e.g., sendMessage.ts) + * 2. Implement and register the method using registerInvokeHandler + * 3. Import it in this file + */ + +// Export the invoke function for use in tools +export { invoke } from '../worker/invoke'; + +// Export registration function for method implementations +export { registerInvokeHandler } from './registry'; + +// Import all registered methods to ensure they are loaded +import './accessToken'; +import './wecom/getAuthToken'; + +// Future methods can be added here: +// import './sendMessage'; +// import './uploadFile'; diff --git a/lib/invoke/registry.ts b/lib/invoke/registry.ts new file mode 100644 index 00000000..b649b0da --- /dev/null +++ b/lib/invoke/registry.ts @@ -0,0 +1,30 @@ +import type { SystemVarType } from '@tool/type/req'; + +type InvokeHandler = ( + params: TParams, + systemVar: SystemVarType +) => Promise; + +const invokeHandlers = new Map(); + +export function registerInvokeHandler( + method: string, + handler: InvokeHandler +) { + if (invokeHandlers.has(method)) { + console.warn(`Invoke handler for method "${method}" is being overwritten`); + } + invokeHandlers.set(method, handler); +} + +export function getInvokeHandler(method: string): InvokeHandler | undefined { + return invokeHandlers.get(method); +} + +export function hasInvokeHandler(method: string): boolean { + return invokeHandlers.has(method); +} + +export function getAllRegisteredMethods(): string[] { + return Array.from(invokeHandlers.keys()); +} diff --git a/lib/invoke/wecom/getAuthToken.ts b/lib/invoke/wecom/getAuthToken.ts new file mode 100644 index 00000000..16696ce4 --- /dev/null +++ b/lib/invoke/wecom/getAuthToken.ts @@ -0,0 +1,64 @@ +import { z } from 'zod'; +import { registerInvokeHandler } from '../registry'; +import type { SystemVarType } from '@tool/type/req'; +import { FastGPTBaseURL } from '../const'; +import { getAccessToken } from '../accessToken'; + +// 参数校验 +const getCorpTokenParamsSchema = z.object({}); + +type getCorpTokenParams = z.infer; + +// 返回值类型 +type getCorpTokenResult = { + access_token: string; + expires_in: number; +}; + +/** + * 获取企业微信授权 token + * + * @param params.corpId - 企业微信 corpId + * @returns access_token 和过期时间 + */ +async function getCorpToken( + params: getCorpTokenParams, + systemVar: SystemVarType +): Promise { + // 验证参数 + // const validated = getCorpTokenParamsSchema.parse(params); + const access_token = await getAccessToken({}, systemVar); + + // 调用 FastGPT API + const url = new URL('/api/proApi/support/wecom/getCorpToken', FastGPTBaseURL); + const response = await fetch(url, { + method: 'POST', + headers: { + 'content-type': 'application/json', + authorization: `Bearer ${access_token}` + } + }); + + if (!response.ok) { + throw new Error(`Failed to get auth token: ${response.statusText}`); + } + + const result = (await response.json()) as { + data: { + access_token: string; + expires_in: number; + }; + }; + + if (!result.data.access_token) { + throw new Error('Invalid response: missing access_token'); + } + + return result.data; +} + +// 注册方法 +registerInvokeHandler('wecom.getCorpToken', getCorpToken); + +export { getCorpToken }; +export type { getCorpTokenParams, getCorpTokenResult }; diff --git a/lib/worker/index.ts b/lib/worker/index.ts index a8aa140b..0d8f40b1 100644 --- a/lib/worker/index.ts +++ b/lib/worker/index.ts @@ -6,6 +6,8 @@ import { getErrText } from '@tool/utils/err'; import { publicS3Server } from '@/s3'; import { basePath, devToolIds } from '@tool/constants'; import type { StreamDataType, ToolCallbackReturnSchemaType } from '@tool/type/req'; +// Import invoke registry to ensure all methods are registered +import '@/invoke'; type WorkerQueueItem = { id: string; @@ -221,6 +223,36 @@ export async function dispatchWithNewWorker(data: { } }); } + } else if (type === 'invoke') { + try { + const { getInvokeHandler } = await import('@/invoke/registry'); + + const handler = getInvokeHandler(data.method); + if (!handler) { + addLog.error(`Unknown invoke method: ${data.method}`); + throw new Error(`Unknown invoke method: ${data.method}`); + } + + // Call handler with params and systemVar from closure + const result = await handler(data.params, workerData.systemVar as any); + + worker.postMessage({ + type: 'invokeResponse', + data: { + id: data.id, + data: result + } + }); + } catch (error) { + addLog.error(`Invoke ${data.method} error`, error); + worker.postMessage({ + type: 'invokeResponse', + data: { + id: data.id, + error: getErrText(error) + } + }); + } } }); diff --git a/lib/worker/invoke.ts b/lib/worker/invoke.ts new file mode 100644 index 00000000..fe25139f --- /dev/null +++ b/lib/worker/invoke.ts @@ -0,0 +1,67 @@ +import { parentPort } from 'worker_threads'; +import { getNanoid } from '@tool/utils/string'; + +declare global { + var invokeResponseFnMap: Map void>; +} + +/** + * Invoke FastGPT API method from worker thread + * + * @param method - The method name to invoke (e.g., 'getAccessToken') + * @param params - Optional parameters for the method + * @returns Promise that resolves with the method result + * + * @example + * ```typescript + * import { invoke } from '@/invoke'; + * + * const token = await invoke('getAccessToken', {}); + * ``` + */ +export async function invoke(method: string, params?: any): Promise { + const isWorkerThread = typeof parentPort !== 'undefined' && parentPort !== null; + + if (!isWorkerThread) { + throw new Error('invoke() can only be called from worker thread'); + } + + return new Promise((resolve, reject) => { + const id = getNanoid(); + + const timer = setTimeout(() => { + global.invokeResponseFnMap?.delete(id); + reject(new Error(`Invoke ${method} timeout after 120 seconds`)); + }, 120000); // 120s timeout + + // Initialize global Map if not exists + if (!global.invokeResponseFnMap) { + global.invokeResponseFnMap = new Map(); + } + + // Define callback function + const callback = ({ data, error }: { data?: any; error?: string }) => { + clearTimeout(timer); + global.invokeResponseFnMap.delete(id); + + if (error) { + reject(new Error(error)); + } else { + resolve(data as TResult); + } + }; + + // Store callback with unique ID + global.invokeResponseFnMap.set(id, callback); + + // Send message to main thread + parentPort?.postMessage({ + type: 'invoke', + data: { + id, + method, + params: params || {} + } + }); + }); +} diff --git a/lib/worker/type.ts b/lib/worker/type.ts index 122f82cb..c9da6a8f 100644 --- a/lib/worker/type.ts +++ b/lib/worker/type.ts @@ -5,6 +5,7 @@ import { StreamDataSchema, ToolCallbackReturnSchema } from '@tool/type/req'; declare global { var uploadFileResponseFnMap: Map void>; + var invokeResponseFnMap: Map void>; } /** @@ -15,6 +16,14 @@ export const Worker2MainMessageSchema = z.discriminatedUnion('type', [ type: z.literal('uploadFile'), data: z.object({ id: z.string(), file: FileInputSchema }) }), + z.object({ + type: z.literal('invoke'), + data: z.object({ + id: z.string(), + method: z.string(), + params: z.any() + }) + }), z.object({ type: z.literal('log'), data: z.array(z.any()) @@ -50,6 +59,14 @@ export const Main2WorkerMessageSchema = z.discriminatedUnion('type', [ data: FileMetadataSchema.optional(), error: z.string().optional() }) + }), + z.object({ + type: z.literal('invokeResponse'), + data: z.object({ + id: z.string(), + data: z.any().optional(), + error: z.string().optional() + }) }) ]); diff --git a/lib/worker/worker.ts b/lib/worker/worker.ts index 52b6f70c..b7a8eb17 100644 --- a/lib/worker/worker.ts +++ b/lib/worker/worker.ts @@ -84,5 +84,12 @@ parentPort?.on('message', async (params: Main2WorkerMessageType) => { } break; } + case 'invokeResponse': { + const fn = global.invokeResponseFnMap?.get(data.id); + if (fn) { + fn(data); + } + break; + } } }); diff --git a/modules/tool/packages/wecomCorpId/DESIGN.md b/modules/tool/packages/wecomCorpId/DESIGN.md new file mode 100644 index 00000000..524d7f97 --- /dev/null +++ b/modules/tool/packages/wecomCorpId/DESIGN.md @@ -0,0 +1,258 @@ +# 企微获取 Corp Access Token 系统工具 + +## 概述 + +此工具用于获取企业微信的 Corp Access Token,是企业微信 API 调用的基础认证凭证。 + +## 设计目标 + +1. 提供简单易用的企业微信授权 Token 获取接口 +2. 使用 FastGPT 反向调用框架实现跨线程通信 +3. 支持在 FastGPT 工作流中作为系统工具使用 +4. 类型安全、错误处理完善 + +## 架构设计 + +### 调用流程 + +``` +FastGPT Workflow + ↓ +Worker Thread (Tool) + ↓ +invoke('wecom.getAuthToken', { corpId }) + ↓ +Main Thread (lib/worker/index.ts) + ↓ +lib/invoke/wecom/getAuthToken.ts + ↓ +FastGPT API: /api/support/wecom/getAuthToken + ↓ +返回: { access_token, expires_in } +``` + +### 技术栈 + +- **运行时**: Worker Thread (Bun/Node.js v22) +- **类型验证**: Zod +- **通信机制**: FastGPT Invoke Framework +- **API**: FastGPT Internal API + +## 核心实现 + +### 1. 工具实现 (`src/index.ts`) + +```typescript +import { invoke } from '@/invoke'; +import type { GetAuthTokenResult } from '@/invoke/wecom/getAuthToken'; + +export async function tool({ corpId }: { corpId: string }) { + const result = await invoke('wecom.getAuthToken', { + corpId + }); + + return { + access_token: result.access_token, + expires_in: result.expires_in + }; +} +``` + +**关键点**: +- 使用 `invoke` 函数调用 FastGPT API +- 类型安全:使用 `GetAuthTokenResult` 泛型 +- 错误自动抛出到外层处理 + +### 2. 反向调用实现 (`lib/invoke/wecom/getAuthToken.ts`) + +```typescript +async function getAuthToken( + params: { corpId: string }, + systemVar: SystemVarType +): Promise<{ access_token: string; expires_in: number }> { + // 参数验证 + const validated = GetAuthTokenParamsSchema.parse(params); + + // 调用 FastGPT API + const url = new URL('/api/support/wecom/getAuthToken', FastGPTBaseURL); + const response = await fetch(url, { + method: 'POST', + headers: { + 'content-type': 'application/json', + authtoken: process.env.AUTH_TOKEN || '' + }, + body: JSON.stringify({ corpId: validated.corpId }) + }); + + // 返回结果 + return await response.json(); +} + +// 注册到 invoke 框架 +registerInvokeHandler('wecom.getAuthToken', getAuthToken); +``` + +**关键点**: +- 使用 Zod 进行参数验证 +- 调用 FastGPT 内部 API +- 自动注入 systemVar(虽然此方法不需要) +- 通过 `registerInvokeHandler` 注册 + +### 3. 工具配置 (`config.ts`) + +定义工具的元数据: +- 工具名称(中英文) +- 描述信息 +- 输入输出参数定义 +- 版本信息 + +## 数据流 + +### 输入 + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| corpId | string | 是 | 企业微信企业 ID | + +### 输出 + +| 字段 | 类型 | 说明 | +|------|------|------| +| access_token | string | 企业访问令牌 | +| expires_in | number | 过期时间(秒) | + +### FastGPT API 规范 + +**Endpoint**: `POST /api/support/wecom/getAuthToken` + +**Request**: +```json +{ + "corpId": "ww1234567890abcdef" +} +``` + +**Response**: +```json +{ + "access_token": "abc123xyz456token", + "expires_in": 7200 +} +``` + +## 错误处理 + +### 参数验证错误 +```typescript +// corpId 为空 +throw new ZodError("corpId is required") +``` + +### API 调用错误 +```typescript +// HTTP 错误 +throw new Error("Failed to get auth token: 401 Unauthorized") + +// 响应格式错误 +throw new Error("Invalid response: missing access_token") +``` + +### 超时错误 +```typescript +// 120 秒超时 +throw new Error("Invoke wecom.getAuthToken timeout after 120 seconds") +``` + +## 使用场景 + +### 场景 1: 工作流中获取授权 + +1. 用户配置企业微信 corpId +2. 工作流执行时调用此工具 +3. 获取 access_token +4. 将 token 传递给后续节点调用企业微信 API + +### 场景 2: 定时刷新 Token + +1. 通过定时触发器定期执行 +2. 获取新的 access_token +3. 存储到变量或数据库 +4. 其他节点使用最新的 token + +## 性能考虑 + +- **缓存**: 建议在应用层缓存 token,避免频繁请求 +- **过期时间**: 通常为 7200 秒,建议提前 5 分钟刷新 +- **超时**: 120 秒超时保护,避免长时间阻塞 +- **并发**: 支持多个工作流同时调用 + +## 安全注意事项 + +1. **Token 保护**: access_token 应妥善保管,避免泄露 +2. **环境隔离**: 使用 AUTH_TOKEN 验证内部 API 调用 +3. **参数验证**: 使用 Zod 严格验证输入 +4. **错误信息**: 避免在错误消息中暴露敏感信息 + +## 测试策略 + +### 单元测试 +- 参数验证测试 +- 成功场景测试 +- 错误场景测试 + +### 集成测试 +- 完整的工作流调用测试 +- 超时机制测试 +- 并发调用测试 + +### 测试用例示例 + +```typescript +describe('wecomCorpId tool', () => { + it('should get access token with valid corpId', async () => { + const result = await tool({ corpId: 'valid_corp_id' }); + expect(result.access_token).toBeDefined(); + expect(result.expires_in).toBeGreaterThan(0); + }); + + it('should throw error with invalid corpId', async () => { + await expect(tool({ corpId: '' })).rejects.toThrow(); + }); +}); +``` + +## 依赖关系 + +``` +wecomCorpId (此工具) + ↓ +lib/invoke (反向调用框架) + ↓ +lib/worker (Worker 线程管理) + ↓ +FastGPT API (后端 API) +``` + +## 版本计划 + +### v0.1.0 (当前) +- ✅ 基础功能:获取 Corp Access Token +- ✅ 参数验证 +- ✅ 错误处理 +- ✅ 类型定义 + +### v0.2.0 (规划) +- Token 缓存机制 +- 自动刷新功能 +- 更详细的错误信息 + +### v1.0.0 (规划) +- 完整的企业微信 API 支持 +- 多企业管理 +- 性能优化 + +## 相关文档 + +- [FastGPT Invoke Framework](../../../../../lib/invoke/README.md) +- [企业微信 API 文档](https://developer.work.weixin.qq.com/document/) +- [工具开发指南](../../../../../.claude/CLAUDE.md) diff --git a/modules/tool/packages/wecomCorpId/README.md b/modules/tool/packages/wecomCorpId/README.md new file mode 100644 index 00000000..53ef468f --- /dev/null +++ b/modules/tool/packages/wecomCorpId/README.md @@ -0,0 +1,76 @@ +# 企业微信授权工具 (WeChat Work Auth Tool) + +## 简介 + +此工具用于获取企业微信(WeChat Work)的授权 Token,支持在 FastGPT 工作流中使用。 + +⚠️注意:仅支持企微注册的账号/团队使用。 + +## 功能 + +- 通过企业 ID(corpId)获取企业微信访问令牌 +- 返回 access_token 和过期时间 +- 使用 FastGPT 反向调用框架实现 + +## 输入参数 + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| corpId | string | 是 | 企业微信的企业 ID | + +## 输出参数 + +| 参数名 | 类型 | 说明 | +|--------|------|------| +| access_token | string | 企业微信访问令牌 | +| expires_in | number | Token 过期时间(秒) | + +## 使用示例 + +### 在工作流中使用 + +1. 添加"企业微信授权"工具节点 +2. 输入企业 ID(corpId) +3. 获取返回的 access_token 和 expires_in +4. 将 access_token 传递给后续需要调用企业微信 API 的节点 + +### 示例配置 + +```json +{ + "corpId": "ww1234567890abcdef" +} +``` + +### 示例输出 + +```json +{ + "access_token": "abc123xyz456token", + "expires_in": 7200 +} +``` + +## 技术实现 + +此工具使用 FastGPT 的反向调用框架(invoke framework): + +```typescript +import { invoke } from '@/invoke'; + +const result = await invoke('wecom.getAuthToken', { + corpId: 'your_corp_id' +}); +``` + +## 注意事项 + +- access_token 有效期通常为 7200 秒(2 小时) +- 请妥善保管 access_token,避免泄露 +- 建议在 token 过期前提前刷新 + +## 版本历史 + +### 0.1.0 +- 初始版本 +- 支持获取企业微信授权 Token diff --git a/modules/tool/packages/wecomCorpId/config.ts b/modules/tool/packages/wecomCorpId/config.ts new file mode 100644 index 00000000..571e0e92 --- /dev/null +++ b/modules/tool/packages/wecomCorpId/config.ts @@ -0,0 +1,38 @@ +import { defineTool } from '@tool/type'; +import { WorkflowIOValueTypeEnum } from '@tool/type/fastgpt'; +import { ToolTagEnum } from '@tool/type/tags'; + +export default defineTool({ + name: { + 'zh-CN': '企业微信授权', + en: 'WeChat Work Auth' + }, + tags: [ToolTagEnum.enum.tools], + description: { + 'zh-CN': '获取企业微信授权 Token', + en: 'Get WeChat Work authorization token' + }, + toolDescription: + 'Get WeChat Work (WeCom) authorization token by corpId. Returns access_token and expires_in.', + versionList: [ + { + value: '0.1.0', + description: 'Get WeChat Work authorization token', + inputs: [], + outputs: [ + { + valueType: WorkflowIOValueTypeEnum.string, + key: 'access_token', + label: '访问令牌', + description: '企业微信访问令牌' + }, + { + valueType: WorkflowIOValueTypeEnum.number, + key: 'expires_in', + label: '过期时间(秒)', + description: 'Token 过期时间(秒)' + } + ] + } + ] +}); diff --git a/modules/tool/packages/wecomCorpId/index.ts b/modules/tool/packages/wecomCorpId/index.ts new file mode 100644 index 00000000..d698ed48 --- /dev/null +++ b/modules/tool/packages/wecomCorpId/index.ts @@ -0,0 +1,10 @@ +import config from './config'; +import { InputType, OutputType, tool as toolCb } from './src'; +import { exportTool } from '@tool/utils/tool'; + +export default exportTool({ + toolCb, + InputType, + OutputType, + config +}); diff --git a/modules/tool/packages/wecomCorpId/logo.svg b/modules/tool/packages/wecomCorpId/logo.svg new file mode 100644 index 00000000..6ce19494 --- /dev/null +++ b/modules/tool/packages/wecomCorpId/logo.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/modules/tool/packages/wecomCorpId/package.json b/modules/tool/packages/wecomCorpId/package.json new file mode 100644 index 00000000..783eb5b6 --- /dev/null +++ b/modules/tool/packages/wecomCorpId/package.json @@ -0,0 +1,25 @@ +{ + "name": "@fastgpt-plugins/tool-wecom-corp-id", + "version": "0.1.0", + "description": "WeChat Work authorization tool for FastGPT", + "module": "index.ts", + "type": "module", + "scripts": { + "build": "bun ../../../../scripts/build.ts" + }, + "keywords": [ + "fastgpt", + "wechat-work", + "wecom", + "authorization" + ], + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "zod": "^3.25.76" + } +} diff --git a/modules/tool/packages/wecomCorpId/src/index.ts b/modules/tool/packages/wecomCorpId/src/index.ts new file mode 100644 index 00000000..1850de25 --- /dev/null +++ b/modules/tool/packages/wecomCorpId/src/index.ts @@ -0,0 +1,24 @@ +import { z } from 'zod'; +import { getCorpToken } from '@/invoke/wecom/getAuthToken'; +import type { RunToolSecondParamsType } from '@tool/type/req'; + +export const InputType = z.object({}); + +export const OutputType = z.object({ + access_token: z.string(), + expires_in: z.number() +}); + +export async function tool( + _params: z.infer, + { systemVar }: RunToolSecondParamsType +): Promise> { + // 调用 wecom.getAuthToken 获取企业微信授权 token + // const result = await invoke('wecom.getCorpToken'); + const result = await getCorpToken({}, systemVar); + + return { + access_token: result.access_token, + expires_in: result.expires_in + }; +} diff --git a/modules/tool/packages/wecomCorpId/test/index.test.ts b/modules/tool/packages/wecomCorpId/test/index.test.ts new file mode 100644 index 00000000..b70e289f --- /dev/null +++ b/modules/tool/packages/wecomCorpId/test/index.test.ts @@ -0,0 +1,8 @@ +import { expect, test } from 'vitest'; +import tool from '..'; + +test(async () => { + expect(tool.name).toBeDefined(); + expect(tool.description).toBeDefined(); + expect(tool.cb).toBeDefined(); +}); diff --git a/modules/tool/packages/wecomSmartSheet/README.md b/modules/tool/packages/wecomSmartSheet/README.md new file mode 100644 index 00000000..0ebd3087 --- /dev/null +++ b/modules/tool/packages/wecomSmartSheet/README.md @@ -0,0 +1,51 @@ +# 企业微信智能表插件 (WeCom Smart Sheet Plugin) + +这是一个功能强大的企业微信智能表管理工具集,专为 FastGPT 设计。它支持从文档创建到子表、视图、字段及记录的全生命周期管理,并提供了“极简”与“高级”两种模式以适应不同的使用场景。 + +## 核心工具分类 + +### 1. 文档与结构管理 +- **新增智能表 (wecomSmartSheetDoc)**: 在指定空间创建新的智能表格文档。 +- **智能表子表管理 (wecomSmartSheetTable)**: 管理文档内的子表,支持新增、删除、更新和获取子表信息。 +- **智能表视图管理 (wecomSmartSheetView)**: 支持对子表视图进行增删改查,涵盖表格、看板、画册等多种视图类型。 + +### 2. 字段管理 (Fields) +- **极简版字段 (wecomSmartSheetFieldSimple)**: + - **特点**: 通过下拉选项和简单输入即可创建字段。 + - **自动化**: 自动处理不同字段类型的底层 JSON 结构(如日期、评分等默认配置)。 +- **高级版字段 (wecomSmartSheetFieldAdvanced)**: + - **特点**: 支持直接输入 JSON 配置。 + - **场景**: 适合需要精细控制字段属性(如特定校验规则、复杂引用等)的高级用户。 + +### 3. 记录管理 (Records) +- **极简版记录 (wecomSmartSheetRecordSimple)**: + - **特点**: 支持直接传入键值对(Field Title: Value)。 + - **自动化**: 自动查询字段类型并进行必要的数据转换(如文本、链接等)。 +- **高级版记录 (wecomSmartSheetRecordAdvanced)**: + - **特点**: 支持企微原生的记录 JSON 格式。 + - **场景**: 适合批量操作或需要精确控制单元格格式的场景。 + +## 配置说明 + +本工具集已优化为**无需手动激活**。您只需要在工作流中提供以下参数: + +1. **accessToken**: 企业微信的调用凭证。 +2. **docid**: 目标智能表的文档 ID(部分创建工具除外)。 +3. **sheet_id**: 目标子表的 ID。 + +## 快速开始 + +1. **创建文档**: 使用 `wecomSmartSheetDoc` 创建一个新表。 +2. **管理结构**: 使用 `wecomSmartSheetTable` 和 `wecomSmartSheetView` 构建您的数据模型。 +3. **配置字段**: 推荐初学者使用 `wecomSmartSheetFieldSimple` 快速添加字段。 +4. **操作数据**: 使用 `wecomSmartSheetRecordSimple` 进行日常的数据录入和查询。 + +## API 参考 + +本插件基于企业微信官方 API 构建: +- [智能表概览](https://developer.work.weixin.qq.com/document/path/96601) +- [新增文档 API](https://developer.work.weixin.qq.com/document/path/97464) +- [视图管理 API](https://developer.work.weixin.qq.com/document/path/100199) + +--- +*由 FastGPT 团队维护* diff --git a/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetDoc/config.ts b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetDoc/config.ts new file mode 100644 index 00000000..94a3b4fe --- /dev/null +++ b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetDoc/config.ts @@ -0,0 +1,83 @@ +import { defineTool } from '@tool/type'; +import { FlowNodeInputTypeEnum, WorkflowIOValueTypeEnum } from '@tool/type/fastgpt'; + +export default defineTool({ + name: { + 'zh-CN': '新增智能表', + en: 'Create Smart Sheet' + }, + description: { + 'zh-CN': '管理企微智能表文档,支持新建智能表。', + en: 'Manage WeCom Smart Sheet documents, supporting the creation of new smart sheets.' + }, + toolDescription: 'Create a new WeCom Smart Sheet document.', + + versionList: [ + { + value: '0.1.0', + description: 'Initial version', + inputs: [ + { + key: 'accessToken', + label: '调用凭证 (access_token)', + description: '企业微信的调用凭证', + required: true, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'The access token for WeCom API' + }, + { + key: 'doc_name', + label: '文档名称', + description: '要新建的文档名称', + required: true, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'The name of the document to create' + }, + { + key: 'spaceid', + label: '空间 ID (spaceid)', + description: '可选,指定存储的空间 ID', + required: false, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'Optional space ID where the document will be created' + }, + { + key: 'fatherid', + label: '父目录 ID (fatherid)', + description: '可选,父目录 fileid,根目录时为空间 spaceid', + required: false, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'Optional father directory ID' + }, + { + key: 'admin_users', + label: '管理员列表', + description: '可选,文档管理员的 userid 列表,多个用逗号隔开', + required: false, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: + 'Optional list of user IDs for document administrators, separated by commas' + } + ], + outputs: [ + { + key: 'docid', + label: '文档 ID', + description: '新建文档的唯一标识', + valueType: WorkflowIOValueTypeEnum.string + }, + { + key: 'url', + label: '文档链接', + description: '新建文档的访问链接', + valueType: WorkflowIOValueTypeEnum.string + } + ] + } + ] +}); diff --git a/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetDoc/index.ts b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetDoc/index.ts new file mode 100644 index 00000000..d698ed48 --- /dev/null +++ b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetDoc/index.ts @@ -0,0 +1,10 @@ +import config from './config'; +import { InputType, OutputType, tool as toolCb } from './src'; +import { exportTool } from '@tool/utils/tool'; + +export default exportTool({ + toolCb, + InputType, + OutputType, + config +}); diff --git a/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetDoc/src/index.ts b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetDoc/src/index.ts new file mode 100644 index 00000000..3c09c8dd --- /dev/null +++ b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetDoc/src/index.ts @@ -0,0 +1,51 @@ +import { z } from 'zod'; +import axios from 'axios'; + +export const InputType = z.object({ + accessToken: z.string(), + doc_name: z.string(), + spaceid: z.string().optional().nullable(), + fatherid: z.string().optional().nullable(), + admin_users: z.string().optional().nullable() +}); + +export const OutputType = z.object({ + docid: z.string().optional(), + url: z.string().optional(), + result: z.any().optional() +}); + +const WECOM_API_BASE = 'https://qyapi.weixin.qq.com/cgi-bin'; + +export async function tool(props: z.infer): Promise> { + const { accessToken, doc_name, spaceid, fatherid, admin_users } = props; + + const client = axios.create({ + baseURL: WECOM_API_BASE, + params: { access_token: accessToken } + }); + + const response = await client.post('/wedoc/smartsheet/create_doc', { + doc_name, + doc_type: 10, // 10: 智能表格 + spaceid: spaceid || undefined, + fatherid: fatherid || undefined, + admin_users: admin_users + ? admin_users + .split(',') + .filter(Boolean) + .map((u) => u.trim()) + : undefined + }); + + const data = response.data; + if (data.errcode !== 0) { + throw new Error(`WeCom API error: ${data.errmsg} (${data.errcode})`); + } + + return { + docid: data.docid, + url: data.url, + result: data + }; +} diff --git a/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetFieldAdvanced/config.ts b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetFieldAdvanced/config.ts new file mode 100644 index 00000000..b6c3ebae --- /dev/null +++ b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetFieldAdvanced/config.ts @@ -0,0 +1,116 @@ +import { defineTool } from '@tool/type'; +import { FlowNodeInputTypeEnum, WorkflowIOValueTypeEnum } from '@tool/type/fastgpt'; + +export default defineTool({ + name: { + 'zh-CN': '智能表字段管理 (高级版)', + en: 'Smart Sheet Field (Advanced)' + }, + description: { + 'zh-CN': '高级模式管理字段:支持批量新增、更新、删除,支持视图 ID 和分页查询。', + en: 'Advanced field management: bulk add, update, delete, with view ID and pagination support.' + }, + toolDescription: 'Advanced field management in WeCom Smart Sheet using raw JSON configurations.', + + versionList: [ + { + value: '0.1.0', + description: 'Initial advanced version', + inputs: [ + { + key: 'accessToken', + label: '调用凭证 (access_token)', + description: '企业微信的调用凭证', + required: true, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'The access token for WeCom API' + }, + { + key: 'docid', + label: '文档 ID (docid)', + description: '智能表文档的唯一标识', + required: true, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'The unique ID of the smart sheet document' + }, + { + key: 'sheet_id', + label: '子表 ID', + description: '操作所属的子表 ID', + required: true, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'The ID of the sheet where fields belong' + }, + { + key: 'action', + label: '操作类型', + description: '执行的操作:add (新增), del (删除), update (更新), list (查询列表)', + required: true, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.select, FlowNodeInputTypeEnum.reference], + list: [ + { label: '新增字段', value: 'add' }, + { label: '删除字段', value: 'del' }, + { label: '更新字段', value: 'update' }, + { label: '查询字段列表', value: 'list' } + ], + toolDescription: 'The action to perform' + }, + { + key: 'fields', + label: '字段配置 (JSON Array)', + description: '新增或更新时的字段配置数组', + required: false, + valueType: WorkflowIOValueTypeEnum.arrayObject, + renderTypeList: [FlowNodeInputTypeEnum.JSONEditor, FlowNodeInputTypeEnum.reference], + toolDescription: 'Array of field configurations' + }, + { + key: 'field_ids', + label: '字段 ID 列表', + description: '删除操作时的字段 ID 数组', + required: false, + valueType: WorkflowIOValueTypeEnum.arrayString, + renderTypeList: [FlowNodeInputTypeEnum.JSONEditor, FlowNodeInputTypeEnum.reference], + toolDescription: 'Array of field IDs to delete' + }, + { + key: 'view_id', + label: '视图 ID', + description: '查询列表时的视图 ID', + required: false, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'View ID for listing fields' + }, + { + key: 'offset', + label: '偏移量', + description: '查询列表的分页偏移', + required: false, + valueType: WorkflowIOValueTypeEnum.number, + renderTypeList: [FlowNodeInputTypeEnum.numberInput, FlowNodeInputTypeEnum.reference] + }, + { + key: 'limit', + label: '限制条数', + description: '查询列表的分页限制', + required: false, + valueType: WorkflowIOValueTypeEnum.number, + renderTypeList: [FlowNodeInputTypeEnum.numberInput, FlowNodeInputTypeEnum.reference] + } + ], + outputs: [ + { + key: 'result', + label: '结果', + description: '操作结果', + valueType: WorkflowIOValueTypeEnum.any + } + ] + } + ] +}); diff --git a/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetFieldAdvanced/index.ts b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetFieldAdvanced/index.ts new file mode 100644 index 00000000..d698ed48 --- /dev/null +++ b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetFieldAdvanced/index.ts @@ -0,0 +1,10 @@ +import config from './config'; +import { InputType, OutputType, tool as toolCb } from './src'; +import { exportTool } from '@tool/utils/tool'; + +export default exportTool({ + toolCb, + InputType, + OutputType, + config +}); diff --git a/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetFieldAdvanced/src/index.ts b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetFieldAdvanced/src/index.ts new file mode 100644 index 00000000..505d3155 --- /dev/null +++ b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetFieldAdvanced/src/index.ts @@ -0,0 +1,72 @@ +import { z } from 'zod'; +import axios from 'axios'; + +export const InputType = z.object({ + accessToken: z.string(), + docid: z.string(), + sheet_id: z.string(), + action: z.enum(['add', 'del', 'update', 'list']), + fields: z.array(z.any()).optional().nullable(), + field_ids: z.array(z.string()).optional().nullable(), + view_id: z.string().optional().nullable(), + offset: z.number().optional().nullable(), + limit: z.number().optional().nullable() +}); + +export const OutputType = z.object({ + result: z.any() +}); + +const WECOM_API_BASE = 'https://qyapi.weixin.qq.com/cgi-bin'; + +export async function tool(props: z.infer): Promise> { + const { accessToken, docid, sheet_id, action, fields, field_ids, view_id, offset, limit } = props; + + const client = axios.create({ + baseURL: WECOM_API_BASE, + params: { access_token: accessToken } + }); + + let response; + switch (action) { + case 'add': { + if (!fields) throw new Error('fields array is required for add action'); + response = await client.post('/wedoc/smartsheet/add_fields', { + docid, + sheet_id, + fields + }); + break; + } + case 'del': { + if (!field_ids) throw new Error('field_ids array is required for del action'); + response = await client.post('/wedoc/smartsheet/delete_fields', { + docid, + sheet_id, + field_ids + }); + break; + } + case 'update': { + if (!fields) throw new Error('fields array is required for update action'); + response = await client.post('/wedoc/smartsheet/update_fields', { + docid, + sheet_id, + fields + }); + break; + } + case 'list': { + response = await client.post('/wedoc/smartsheet/get_fields', { + docid, + sheet_id, + view_id, + offset, + limit + }); + break; + } + } + + return { result: response?.data || {} }; +} diff --git a/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetFieldSimple/config.ts b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetFieldSimple/config.ts new file mode 100644 index 00000000..28808d8e --- /dev/null +++ b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetFieldSimple/config.ts @@ -0,0 +1,131 @@ +import { defineTool } from '@tool/type'; +import { FlowNodeInputTypeEnum, WorkflowIOValueTypeEnum } from '@tool/type/fastgpt'; + +export default defineTool({ + name: { + 'zh-CN': '智能表字段管理 (极简版)', + en: 'Smart Sheet Field (Simple)' + }, + description: { + 'zh-CN': '极简模式管理字段:支持新增字段、查询列表、根据名称删除。', + en: 'Manage fields in simple mode: add fields, list fields, or delete by name.' + }, + toolDescription: 'Manage fields in a WeCom Smart Sheet using simple names and types.', + + versionList: [ + { + value: '0.1.1', + description: 'Enhanced simple version with type-specific properties', + inputs: [ + { + key: 'accessToken', + label: '调用凭证 (access_token)', + description: '企业微信的调用凭证', + required: true, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'The access token for WeCom API' + }, + { + key: 'docid', + label: '文档 ID (docid)', + description: '智能表文档的唯一标识', + required: true, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'The unique ID of the smart sheet document' + }, + { + key: 'sheet_id', + label: '子表 ID', + description: '操作所属的子表 ID', + required: true, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'The ID of the sheet where fields belong' + }, + { + key: 'action', + label: '操作类型', + description: + '执行的操作:add (新增), del (根据名称删除), update (修改名称), list (查询列表)', + required: true, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.select, FlowNodeInputTypeEnum.reference], + list: [ + { label: '新增字段', value: 'add' }, + { label: '删除字段', value: 'del' }, + { label: '修改字段名称', value: 'update' }, + { label: '查询字段列表', value: 'list' } + ], + toolDescription: 'The action to perform: add, del, update, or list' + }, + { + key: 'field_title', + label: '字段名称 / 旧名称', + description: '字段的名称,或修改前的旧名称', + required: false, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'The name of the field or the old name when updating' + }, + { + key: 'new_field_title', + label: '新字段名称 (修改时使用)', + description: '修改字段名称时的新名称', + required: false, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'The new name for the field' + }, + { + key: 'field_type', + label: '字段类型', + description: '新增字段时的类型', + required: false, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.select, FlowNodeInputTypeEnum.reference], + list: [ + { label: '文本', value: 'FIELD_TYPE_TEXT' }, + { label: '数字', value: 'FIELD_TYPE_NUMBER' }, + { label: '日期', value: 'FIELD_TYPE_DATE_TIME' }, + { label: '单选', value: 'FIELD_TYPE_SINGLE_SELECT' }, + { label: '多选', value: 'FIELD_TYPE_SELECT' }, + { label: '复选框', value: 'FIELD_TYPE_CHECKBOX' }, + { label: '人员', value: 'FIELD_TYPE_USER' }, + { label: '电话', value: 'FIELD_TYPE_PHONE_NUMBER' }, + { label: '链接', value: 'FIELD_TYPE_URL' }, + { label: '评分', value: 'FIELD_TYPE_RATING' } + ], + toolDescription: 'The type of the field' + }, + { + key: 'options', + label: '选项 (单选/多选)', + description: '多个选项用英文逗号隔开,如“男,女”', + required: false, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'Comma separated options for select types' + }, + { + key: 'decimal_places', + label: '小数位数', + description: '数字类型字段的小数位数', + required: false, + valueType: WorkflowIOValueTypeEnum.number, + renderTypeList: [FlowNodeInputTypeEnum.numberInput, FlowNodeInputTypeEnum.reference], + toolDescription: 'Decimal places for number type' + } + ], + outputs: [ + { + key: 'result', + label: '结果', + description: '操作结果', + valueType: WorkflowIOValueTypeEnum.any + } + ] + } + ] +}); diff --git a/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetFieldSimple/index.ts b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetFieldSimple/index.ts new file mode 100644 index 00000000..d698ed48 --- /dev/null +++ b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetFieldSimple/index.ts @@ -0,0 +1,10 @@ +import config from './config'; +import { InputType, OutputType, tool as toolCb } from './src'; +import { exportTool } from '@tool/utils/tool'; + +export default exportTool({ + toolCb, + InputType, + OutputType, + config +}); diff --git a/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetFieldSimple/src/index.ts b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetFieldSimple/src/index.ts new file mode 100644 index 00000000..81942445 --- /dev/null +++ b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetFieldSimple/src/index.ts @@ -0,0 +1,145 @@ +import { z } from 'zod'; +import axios from 'axios'; + +export const InputType = z.object({ + accessToken: z.string(), + docid: z.string(), + sheet_id: z.string(), + action: z.enum(['add', 'del', 'update', 'list']), + field_title: z.string().optional().nullable(), + new_field_title: z.string().optional().nullable(), + field_type: z.string().optional().nullable(), + options: z.string().optional().nullable(), + decimal_places: z.number().optional().nullable() +}); + +export const OutputType = z.object({ + result: z.any() +}); + +const WECOM_API_BASE = 'https://qyapi.weixin.qq.com/cgi-bin'; + +export async function tool(props: z.infer): Promise> { + const { + accessToken, + docid, + sheet_id, + action, + field_title, + new_field_title, + field_type, + options, + decimal_places + } = props; + + const client = axios.create({ + baseURL: WECOM_API_BASE, + params: { access_token: accessToken } + }); + + const getFieldIdByTitle = async (title: string): Promise => { + const res = await client.post('/wedoc/smartsheet/get_fields', { + docid, + sheet_id + }); + const field = res.data.fields?.find((f: any) => f.field_title === title); + return field ? field.field_id : null; + }; + + let response; + switch (action) { + case 'add': { + if (!field_title) throw new Error('field_title is required for add action'); + const type = field_type || 'FIELD_TYPE_TEXT'; + const field: any = { + field_title, + field_type: type + }; + + // Construct property based on type + if (type === 'FIELD_TYPE_NUMBER') { + field.property_number = { + decimal_places: decimal_places ?? 0, + use_separate: true + }; + } else if (type === 'FIELD_TYPE_DATE_TIME') { + field.property_date_time = { + format: 'yyyy/MM/dd', + auto_fill: false + }; + } else if (type === 'FIELD_TYPE_SINGLE_SELECT') { + field.property_single_select = { + options: (options || '') + .split(',') + .filter(Boolean) + .map((t) => ({ text: t.trim() })) + }; + } else if (type === 'FIELD_TYPE_SELECT') { + field.property_select = { + options: (options || '') + .split(',') + .filter(Boolean) + .map((t) => ({ text: t.trim() })) + }; + } else if (type === 'FIELD_TYPE_CHECKBOX') { + field.property_checkbox = { + checked: false + }; + } else if (type === 'FIELD_TYPE_RATING') { + field.property_rating = { + max: 5, + symbol: 'star' + }; + } + + response = await client.post('/wedoc/smartsheet/add_fields', { + docid, + sheet_id, + fields: [field] + }); + break; + } + case 'del': { + if (!field_title) throw new Error('field_title is required for del action'); + const id = await getFieldIdByTitle(field_title); + if (!id) throw new Error(`Field with title "${field_title}" not found`); + + response = await client.post('/wedoc/smartsheet/delete_fields', { + docid, + sheet_id, + field_ids: [id] + }); + break; + } + case 'update': { + if (!field_title || !new_field_title) + throw new Error( + 'field_title (old name) and new_field_title are required for update action' + ); + + const id = await getFieldIdByTitle(field_title); + if (!id) throw new Error(`Field with title "${field_title}" not found`); + + response = await client.post('/wedoc/smartsheet/update_fields', { + docid, + sheet_id, + fields: [ + { + field_id: id, + field_title: new_field_title + } + ] + }); + break; + } + case 'list': { + response = await client.post('/wedoc/smartsheet/get_fields', { + docid, + sheet_id + }); + break; + } + } + + return { result: response?.data || {} }; +} diff --git a/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetRecordAdvanced/config.ts b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetRecordAdvanced/config.ts new file mode 100644 index 00000000..32d94e82 --- /dev/null +++ b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetRecordAdvanced/config.ts @@ -0,0 +1,112 @@ +import { defineTool } from '@tool/type'; +import { FlowNodeInputTypeEnum, WorkflowIOValueTypeEnum } from '@tool/type/fastgpt'; + +export default defineTool({ + name: { + 'zh-CN': '智能表记录管理 (高级版)', + en: 'Smart Sheet Record (Advanced)' + }, + description: { + 'zh-CN': '高级模式管理记录:支持批量新增、更新、删除,支持复杂的过滤和排序。', + en: 'Advanced record management: bulk operations, complex filtering and sorting.' + }, + toolDescription: 'Advanced record management in WeCom Smart Sheet using raw JSON configurations.', + + versionList: [ + { + value: '0.1.0', + description: 'Initial advanced version', + inputs: [ + { + key: 'accessToken', + label: '调用凭证 (access_token)', + description: '企业微信的调用凭证', + required: true, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'The access token for WeCom API' + }, + { + key: 'docid', + label: '文档 ID (docid)', + description: '智能表文档的唯一标识', + required: true, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'The unique ID of the smart sheet document' + }, + { + key: 'sheet_id', + label: '子表 ID', + description: '操作所属的子表 ID', + required: true, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'The ID of the sheet where records belong' + }, + { + key: 'action', + label: '操作类型', + description: '执行的操作:add (新增), del (删除), update (更新), list (查询列表)', + required: true, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.select, FlowNodeInputTypeEnum.reference], + list: [ + { label: '新增记录', value: 'add' }, + { label: '删除记录', value: 'del' }, + { label: '更新记录', value: 'update' }, + { label: '查询记录列表', value: 'list' } + ], + toolDescription: 'The action to perform' + }, + { + key: 'records', + label: '记录列表 (JSON Array)', + description: '新增或更新时的完整记录数组', + required: false, + valueType: WorkflowIOValueTypeEnum.arrayObject, + renderTypeList: [FlowNodeInputTypeEnum.JSONEditor, FlowNodeInputTypeEnum.reference], + toolDescription: 'Array of records in WeCom format' + }, + { + key: 'record_ids', + label: '记录 ID 列表', + description: '删除或查询特定记录时的 ID 数组', + required: false, + valueType: WorkflowIOValueTypeEnum.arrayString, + renderTypeList: [FlowNodeInputTypeEnum.JSONEditor, FlowNodeInputTypeEnum.reference], + toolDescription: 'Array of record IDs' + }, + { + key: 'query_params', + label: '高级查询参数 (JSON)', + description: '查询时的 filter_spec, sort, view_id 等', + required: false, + valueType: WorkflowIOValueTypeEnum.object, + renderTypeList: [FlowNodeInputTypeEnum.JSONEditor, FlowNodeInputTypeEnum.reference], + toolDescription: 'JSON object for filtering, sorting, etc.' + }, + { + key: 'key_type', + label: 'Key 类型', + description: 'CELL_VALUE_KEY_TYPE_FIELD_TITLE 或 CELL_VALUE_KEY_TYPE_FIELD_ID', + required: false, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.select, FlowNodeInputTypeEnum.reference], + list: [ + { label: '使用标题 (Title)', value: 'CELL_VALUE_KEY_TYPE_FIELD_TITLE' }, + { label: '使用 ID', value: 'CELL_VALUE_KEY_TYPE_FIELD_ID' } + ] + } + ], + outputs: [ + { + key: 'result', + label: '结果', + description: '操作结果', + valueType: WorkflowIOValueTypeEnum.any + } + ] + } + ] +}); diff --git a/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetRecordAdvanced/index.ts b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetRecordAdvanced/index.ts new file mode 100644 index 00000000..d698ed48 --- /dev/null +++ b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetRecordAdvanced/index.ts @@ -0,0 +1,10 @@ +import config from './config'; +import { InputType, OutputType, tool as toolCb } from './src'; +import { exportTool } from '@tool/utils/tool'; + +export default exportTool({ + toolCb, + InputType, + OutputType, + config +}); diff --git a/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetRecordAdvanced/src/index.ts b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetRecordAdvanced/src/index.ts new file mode 100644 index 00000000..e8406a98 --- /dev/null +++ b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetRecordAdvanced/src/index.ts @@ -0,0 +1,73 @@ +import { z } from 'zod'; +import axios from 'axios'; + +export const InputType = z.object({ + accessToken: z.string(), + docid: z.string(), + sheet_id: z.string(), + action: z.enum(['add', 'del', 'update', 'list']), + records: z.array(z.any()).optional().nullable(), + record_ids: z.array(z.string()).optional().nullable(), + query_params: z.record(z.any()).optional().nullable(), + key_type: z.string().optional().nullable() +}); + +export const OutputType = z.object({ + result: z.any() +}); + +const WECOM_API_BASE = 'https://qyapi.weixin.qq.com/cgi-bin'; + +export async function tool(props: z.infer): Promise> { + const { accessToken, docid, sheet_id, action, records, record_ids, query_params, key_type } = + props; + + const client = axios.create({ + baseURL: WECOM_API_BASE, + params: { access_token: accessToken } + }); + + let response; + switch (action) { + case 'add': { + if (!records) throw new Error('records array is required for add action'); + response = await client.post('/wedoc/smartsheet/add_records', { + docid, + sheet_id, + key_type: key_type || 'CELL_VALUE_KEY_TYPE_FIELD_TITLE', + records + }); + break; + } + case 'update': { + if (!records) throw new Error('records array is required for update action'); + response = await client.post('/wedoc/smartsheet/update_records', { + docid, + sheet_id, + key_type: key_type || 'CELL_VALUE_KEY_TYPE_FIELD_TITLE', + records + }); + break; + } + case 'del': { + if (!record_ids) throw new Error('record_ids array is required for del action'); + response = await client.post('/wedoc/smartsheet/delete_records', { + docid, + sheet_id, + record_ids + }); + break; + } + case 'list': { + response = await client.post('/wedoc/smartsheet/get_records', { + docid, + sheet_id, + key_type: key_type || 'CELL_VALUE_KEY_TYPE_FIELD_TITLE', + ...query_params + }); + break; + } + } + + return { result: response?.data || {} }; +} diff --git a/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetRecordSimple/config.ts b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetRecordSimple/config.ts new file mode 100644 index 00000000..e3628b65 --- /dev/null +++ b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetRecordSimple/config.ts @@ -0,0 +1,99 @@ +import { defineTool } from '@tool/type'; +import { FlowNodeInputTypeEnum, WorkflowIOValueTypeEnum } from '@tool/type/fastgpt'; + +export default defineTool({ + name: { + 'zh-CN': '智能表记录管理 (极简版)', + en: 'Smart Sheet Record (Simple)' + }, + description: { + 'zh-CN': '极简模式管理记录:支持新增、更新、查询、删除。输入简单 JSON 对象即可操作。', + en: 'Manage records in simple mode: add, update, list, delete. Uses simple JSON objects.' + }, + toolDescription: 'Manage records in a WeCom Smart Sheet using simple data objects.', + + versionList: [ + { + value: '0.1.0', + description: 'Initial simple version', + inputs: [ + { + key: 'accessToken', + label: '调用凭证 (access_token)', + description: '企业微信的调用凭证', + required: true, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'The access token for WeCom API' + }, + { + key: 'docid', + label: '文档 ID (docid)', + description: '智能表文档的唯一标识', + required: true, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'The unique ID of the smart sheet document' + }, + { + key: 'sheet_id', + label: '子表 ID', + description: '操作所属的子表 ID', + required: true, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'The ID of the sheet where records belong' + }, + { + key: 'action', + label: '操作类型', + description: '执行的操作:add (新增), del (删除), update (更新), list (查询列表)', + required: true, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.select, FlowNodeInputTypeEnum.reference], + list: [ + { label: '新增记录', value: 'add' }, + { label: '删除记录', value: 'del' }, + { label: '更新记录', value: 'update' }, + { label: '查询记录列表', value: 'list' } + ], + toolDescription: 'The action to perform' + }, + { + key: 'data', + label: '记录数据', + description: '新增或更新时的键值对,如 {"姓名": "张三", "年龄": 20}', + required: false, + valueType: WorkflowIOValueTypeEnum.object, + renderTypeList: [FlowNodeInputTypeEnum.JSONEditor, FlowNodeInputTypeEnum.reference], + toolDescription: 'Record data as a simple object' + }, + { + key: 'record_id', + label: '记录 ID', + description: '更新或删除时的记录唯一标识', + required: false, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'The ID of the record' + }, + { + key: 'limit', + label: '限制条数', + description: '查询列表时的条数限制 (默认 10)', + required: false, + valueType: WorkflowIOValueTypeEnum.number, + renderTypeList: [FlowNodeInputTypeEnum.numberInput, FlowNodeInputTypeEnum.reference] + } + ], + outputs: [ + { + key: 'result', + label: '结果', + description: '操作结果', + valueType: WorkflowIOValueTypeEnum.any + } + ] + } + ] +}); diff --git a/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetRecordSimple/index.ts b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetRecordSimple/index.ts new file mode 100644 index 00000000..d698ed48 --- /dev/null +++ b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetRecordSimple/index.ts @@ -0,0 +1,10 @@ +import config from './config'; +import { InputType, OutputType, tool as toolCb } from './src'; +import { exportTool } from '@tool/utils/tool'; + +export default exportTool({ + toolCb, + InputType, + OutputType, + config +}); diff --git a/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetRecordSimple/src/index.ts b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetRecordSimple/src/index.ts new file mode 100644 index 00000000..2b3f3f05 --- /dev/null +++ b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetRecordSimple/src/index.ts @@ -0,0 +1,111 @@ +import { z } from 'zod'; +import axios from 'axios'; + +export const InputType = z.object({ + accessToken: z.string(), + docid: z.string(), + sheet_id: z.string(), + action: z.enum(['add', 'del', 'update', 'list']), + data: z.record(z.any()).optional().nullable(), + record_id: z.string().optional().nullable(), + limit: z.number().optional().nullable() +}); + +export const OutputType = z.object({ + result: z.any() +}); + +const WECOM_API_BASE = 'https://qyapi.weixin.qq.com/cgi-bin'; + +export async function tool(props: z.infer): Promise> { + const { accessToken, docid, sheet_id, action, data, record_id, limit } = props; + + const client = axios.create({ + baseURL: WECOM_API_BASE, + params: { access_token: accessToken } + }); + + // Helper to fetch field types to know how to transform + const getFieldInfo = async () => { + const res = await client.post('/wedoc/smartsheet/get_fields', { + docid, + sheet_id + }); + const info: Record = {}; + res.data.fields?.forEach((f: any) => { + info[f.field_title] = f.field_type; + }); + return info; + }; + + const transformValue = (value: any, fieldType: string) => { + if (fieldType === 'FIELD_TYPE_TEXT' || fieldType === 'FIELD_TYPE_URL') { + if (typeof value === 'string') { + return [{ type: 'text', text: value }]; + } + } + // Most other types like NUMBER, CHECKBOX, DATE (timestamp) can be passed directly + return value; + }; + + const transformData = async (dataObj: Record) => { + const fieldInfo = await getFieldInfo(); + const values: Record = {}; + for (const [title, value] of Object.entries(dataObj)) { + const type = fieldInfo[title] || 'FIELD_TYPE_TEXT'; + values[title] = transformValue(value, type); + } + return values; + }; + + let response; + switch (action) { + case 'add': { + if (!data) throw new Error('data is required for add action'); + const transformedValues = await transformData(data); + response = await client.post('/wedoc/smartsheet/add_records', { + docid, + sheet_id, + key_type: 'CELL_VALUE_KEY_TYPE_FIELD_TITLE', + records: [{ values: transformedValues }] + }); + break; + } + case 'update': { + if (!data || !record_id) throw new Error('data and record_id are required for update action'); + const transformedValues = await transformData(data); + response = await client.post('/wedoc/smartsheet/update_records', { + docid, + sheet_id, + key_type: 'CELL_VALUE_KEY_TYPE_FIELD_TITLE', + records: [ + { + record_id, + values: transformedValues + } + ] + }); + break; + } + case 'del': { + if (!record_id) throw new Error('record_id is required for del action'); + response = await client.post('/wedoc/smartsheet/delete_records', { + docid, + sheet_id, + record_ids: [record_id] + }); + break; + } + case 'list': { + response = await client.post('/wedoc/smartsheet/get_records', { + docid, + sheet_id, + limit: limit || 10, + key_type: 'CELL_VALUE_KEY_TYPE_FIELD_TITLE' + }); + break; + } + } + + return { result: response?.data || {} }; +} diff --git a/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetTable/config.ts b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetTable/config.ts new file mode 100644 index 00000000..81cd8be7 --- /dev/null +++ b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetTable/config.ts @@ -0,0 +1,93 @@ +import { defineTool } from '@tool/type'; +import { FlowNodeInputTypeEnum, WorkflowIOValueTypeEnum } from '@tool/type/fastgpt'; + +export default defineTool({ + name: { + 'zh-CN': '智能表子表管理', + en: 'Smart Sheet Table Management' + }, + description: { + 'zh-CN': '管理企业微信智能表中的子表(增删改查)', + en: 'Manage sub-sheets in WeCom Smart Sheet (Add, Delete, Update, Get)' + }, + toolDescription: + 'A unified tool to manage sub-sheets in WeCom Smart Sheet, supporting adding, deleting, updating, and querying sheet information.', + + versionList: [ + { + value: '0.1.0', + description: 'Unified sheet management', + inputs: [ + { + key: 'accessToken', + label: '调用凭证 (access_token)', + description: '企业微信的调用凭证', + required: true, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'The access token for WeCom API' + }, + { + key: 'docid', + label: '文档 ID (docid)', + description: '智能表文档的唯一标识', + required: true, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'The unique ID of the smart sheet document' + }, + { + key: 'action', + label: '操作类型', + description: + '执行的操作:add (添加子表), delete (删除子表), update (更新子表), get (查询子表)', + required: true, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.select, FlowNodeInputTypeEnum.reference], + list: [ + { label: '添加子表', value: 'add' }, + { label: '删除子表', value: 'delete' }, + { label: '更新子表', value: 'update' }, + { label: '查询子表', value: 'get' } + ], + toolDescription: 'The action to perform: add, delete, update, or get' + }, + { + key: 'sheet_id', + label: '子表 ID', + description: '操作所属的子表 ID (删除、更新、查询时使用)', + required: false, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'The ID of the sub-sheet' + }, + { + key: 'title', + label: '子表标题', + description: '子表标题 (添加、更新时使用)', + required: false, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'The title of the sub-sheet' + }, + { + key: 'need_all_type_sheet', + label: '获取所有类型子表', + description: '是否获取所有类型的子表,包含仪表盘和说明页 (查询时使用)', + required: false, + valueType: WorkflowIOValueTypeEnum.boolean, + renderTypeList: [FlowNodeInputTypeEnum.switch, FlowNodeInputTypeEnum.reference], + toolDescription: 'Whether to get all types of sheets (dashboard, external, etc.)' + } + ], + outputs: [ + { + key: 'result', + label: '操作结果', + valueType: WorkflowIOValueTypeEnum.any, + description: 'API 返回的原始结果' + } + ] + } + ] +}); diff --git a/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetTable/index.ts b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetTable/index.ts new file mode 100644 index 00000000..d698ed48 --- /dev/null +++ b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetTable/index.ts @@ -0,0 +1,10 @@ +import config from './config'; +import { InputType, OutputType, tool as toolCb } from './src'; +import { exportTool } from '@tool/utils/tool'; + +export default exportTool({ + toolCb, + InputType, + OutputType, + config +}); diff --git a/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetTable/src/index.ts b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetTable/src/index.ts new file mode 100644 index 00000000..d4e3b85e --- /dev/null +++ b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetTable/src/index.ts @@ -0,0 +1,73 @@ +import { z } from 'zod'; +import axios from 'axios'; + +export const InputType = z.object({ + accessToken: z.string(), + docid: z.string(), + action: z.enum(['add', 'delete', 'update', 'get']), + sheet_id: z.string().optional().nullable(), + title: z.string().optional().nullable(), + need_all_type_sheet: z.boolean().optional().nullable() +}); + +export const OutputType = z.object({ + result: z.any() +}); + +const WECOM_API_BASE = 'https://qyapi.weixin.qq.com/cgi-bin'; + +export async function tool(props: z.infer): Promise> { + const { accessToken, docid, action, sheet_id, title, need_all_type_sheet } = props; + + const client = axios.create({ + baseURL: WECOM_API_BASE, + params: { access_token: accessToken } + }); + + let response; + switch (action) { + case 'add': + if (!title) throw new Error('title is required for add action'); + response = await client.post('/wedoc/smartsheet/add_sheet', { + docid, + properties: { + title + } + }); + break; + case 'delete': + if (!sheet_id) throw new Error('sheet_id is required for delete action'); + response = await client.post('/wedoc/smartsheet/delete_sheet', { + docid, + sheet_id + }); + break; + case 'update': + if (!sheet_id) throw new Error('sheet_id is required for update action'); + response = await client.post('/wedoc/smartsheet/update_sheet', { + docid, + properties: { + sheet_id, + ...(title ? { title } : {}) + } + }); + break; + case 'get': + response = await client.post('/wedoc/smartsheet/get_sheet', { + docid, + ...(sheet_id ? { sheet_id } : {}), + ...(typeof need_all_type_sheet === 'boolean' ? { need_all_type_sheet } : {}) + }); + break; + default: + throw new Error(`Unsupported action: ${action}`); + } + + if (response.data.errcode !== 0) { + throw new Error(`WeCom API Error [${response.data.errcode}]: ${response.data.errmsg}`); + } + + return { + result: response.data + }; +} diff --git a/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetView/config.ts b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetView/config.ts new file mode 100644 index 00000000..2003dc72 --- /dev/null +++ b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetView/config.ts @@ -0,0 +1,107 @@ +import { defineTool } from '@tool/type'; +import { FlowNodeInputTypeEnum, WorkflowIOValueTypeEnum } from '@tool/type/fastgpt'; + +export default defineTool({ + name: { + 'zh-CN': '智能表视图管理', + en: 'Smart Sheet View' + }, + description: { + 'zh-CN': '管理企微智能表视图,支持视图的增删改查。', + en: 'Manage WeCom Smart Sheet views, supporting CRUD operations for views.' + }, + toolDescription: 'Manage views in a WeCom Smart Sheet (add, delete, update, list).', + + versionList: [ + { + value: '0.1.0', + description: 'Initial version', + inputs: [ + { + key: 'accessToken', + label: '调用凭证 (access_token)', + description: '企业微信的调用凭证', + required: true, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'The access token for WeCom API' + }, + { + key: 'docid', + label: '文档 ID (docid)', + description: '智能表文档的唯一标识', + required: true, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'The unique ID of the smart sheet document' + }, + { + key: 'sheet_id', + label: '子表 ID', + description: '操作所属的子表 ID', + required: true, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'The ID of the sheet where views belong' + }, + { + key: 'action', + label: '操作类型', + description: '执行的操作:add (新增), del (删除), update (修改), list (查询列表)', + required: true, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.select, FlowNodeInputTypeEnum.reference], + list: [ + { label: '新增视图', value: 'add' }, + { label: '删除视图', value: 'del' }, + { label: '修改视图', value: 'update' }, + { label: '查询视图列表', value: 'list' } + ], + toolDescription: 'The action to perform: add, del, update, or list' + }, + { + key: 'view_title', + label: '视图标题', + description: '视图的名称', + required: false, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'The title of the view' + }, + { + key: 'view_id', + label: '视图 ID', + description: '视图的唯一标识 (修改/删除时使用)', + required: false, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + toolDescription: 'The unique ID of the view' + }, + { + key: 'view_type', + label: '视图类型', + description: '新增视图时的类型', + required: false, + valueType: WorkflowIOValueTypeEnum.string, + renderTypeList: [FlowNodeInputTypeEnum.select, FlowNodeInputTypeEnum.reference], + list: [ + { label: '表格视图', value: 'VIEW_TYPE_GRID' }, + { label: '看板视图', value: 'VIEW_TYPE_KANBAN' }, + { label: '画册视图', value: 'VIEW_TYPE_GALLERY' }, + { label: '甘特视图', value: 'VIEW_TYPE_GANTT' }, + { label: '日历视图', value: 'VIEW_TYPE_CALENDAR' } + ], + toolDescription: 'The type of the view' + } + ], + outputs: [ + { + key: 'result', + label: '结果', + description: '操作结果', + valueType: WorkflowIOValueTypeEnum.any + } + ] + } + ] +}); diff --git a/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetView/index.ts b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetView/index.ts new file mode 100644 index 00000000..d698ed48 --- /dev/null +++ b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetView/index.ts @@ -0,0 +1,10 @@ +import config from './config'; +import { InputType, OutputType, tool as toolCb } from './src'; +import { exportTool } from '@tool/utils/tool'; + +export default exportTool({ + toolCb, + InputType, + OutputType, + config +}); diff --git a/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetView/src/index.ts b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetView/src/index.ts new file mode 100644 index 00000000..ccf51730 --- /dev/null +++ b/modules/tool/packages/wecomSmartSheet/children/wecomSmartSheetView/src/index.ts @@ -0,0 +1,76 @@ +import { z } from 'zod'; +import axios from 'axios'; + +export const InputType = z.object({ + accessToken: z.string(), + docid: z.string(), + sheet_id: z.string(), + action: z.enum(['add', 'del', 'update', 'list']), + view_title: z.string().optional().nullable(), + view_id: z.string().optional().nullable(), + view_type: z.string().optional().nullable() +}); + +export const OutputType = z.object({ + result: z.any() +}); + +const WECOM_API_BASE = 'https://qyapi.weixin.qq.com/cgi-bin'; + +export async function tool(props: z.infer): Promise> { + const { accessToken, docid, sheet_id, action, view_title, view_id, view_type } = props; + + const client = axios.create({ + baseURL: WECOM_API_BASE, + params: { access_token: accessToken } + }); + + let response; + switch (action) { + case 'add': { + if (!view_title || !view_type) { + throw new Error('view_title and view_type are required for add action'); + } + response = await client.post('/wedoc/smartsheet/add_view', { + docid, + sheet_id, + view_title, + view_type + }); + break; + } + case 'del': { + if (!view_id) { + throw new Error('view_id is required for del action'); + } + response = await client.post('/wedoc/smartsheet/delete_views', { + docid, + sheet_id, + view_ids: [view_id] + }); + break; + } + case 'update': { + if (!view_id) { + throw new Error('view_id is required for update action'); + } + response = await client.post('/wedoc/smartsheet/update_view', { + docid, + sheet_id, + view_id, + view_title: view_title || undefined + }); + break; + } + case 'list': { + response = await client.post('/wedoc/smartsheet/get_views', { + docid, + sheet_id, + view_ids: view_id ? [view_id] : undefined + }); + break; + } + } + + return { result: response?.data || {} }; +} diff --git a/modules/tool/packages/wecomSmartSheet/config.ts b/modules/tool/packages/wecomSmartSheet/config.ts new file mode 100644 index 00000000..7ac91b30 --- /dev/null +++ b/modules/tool/packages/wecomSmartSheet/config.ts @@ -0,0 +1,21 @@ +import { defineToolSet } from '@tool/type'; +import { ToolTagEnum } from '@tool/type/tags'; + +export default defineToolSet({ + name: { + 'zh-CN': '企业微信智能表', + en: 'WeCom Smart Sheet' + }, + tags: [ToolTagEnum.enum.tools], + description: { + 'zh-CN': + '提供企业微信智能表的完整操作功能,包括文档创建、子表管理、视图管理、字段管理及记录 CRUD 等。', + en: 'Provides comprehensive WeCom Smart Sheet operations including document creation, sheet management, view management, field management, and record CRUD.' + }, + toolDescription: `A comprehensive WeCom (Work WeChat) Smart Sheet toolset for managing documents, sheets, views, fields, and records. +Supports complete CRUD operations for efficient data management within WeCom.`, + author: 'FastGPT Team', + courseUrl: 'https://developer.work.weixin.qq.com/document/path/96601', + + secretInputConfig: [] +}); diff --git a/modules/tool/packages/wecomSmartSheet/index.ts b/modules/tool/packages/wecomSmartSheet/index.ts new file mode 100644 index 00000000..22bccae7 --- /dev/null +++ b/modules/tool/packages/wecomSmartSheet/index.ts @@ -0,0 +1,8 @@ +// You should not modify this file, if you need to modify the tool set configuration, please modify the config.ts file + +import config from './config'; +import { exportToolSet } from '@tool/utils/tool'; + +export default exportToolSet({ + config +}); diff --git a/modules/tool/packages/wecomSmartSheet/logo.svg b/modules/tool/packages/wecomSmartSheet/logo.svg new file mode 100644 index 00000000..6d4d12bd --- /dev/null +++ b/modules/tool/packages/wecomSmartSheet/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/modules/tool/packages/wecomSmartSheet/package.json b/modules/tool/packages/wecomSmartSheet/package.json new file mode 100644 index 00000000..ba07c61b --- /dev/null +++ b/modules/tool/packages/wecomSmartSheet/package.json @@ -0,0 +1,18 @@ +{ + "name": "@fastgpt-plugin/wecomSmartSheet", + "version": "0.1.0", + "description": "WeCom Smart Sheet toolset for FastGPT", + "main": "index.ts", + "scripts": { + "build": "bun ../../../../scripts/build.ts" + }, + "dependencies": { + "zod": "^3.25.76" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "devDependencies": { + "@types/bun": "latest" + } +} diff --git a/modules/tool/packages/wecomSmartSheet/test/export.test.ts b/modules/tool/packages/wecomSmartSheet/test/export.test.ts new file mode 100644 index 00000000..494798ca --- /dev/null +++ b/modules/tool/packages/wecomSmartSheet/test/export.test.ts @@ -0,0 +1,93 @@ +import { describe, it, expect } from 'vitest'; +import { tool as tableTool, InputType as TableInput } from '../children/wecomSmartSheetTable/src'; +import { + tool as fieldSimpleTool, + InputType as FieldSimpleInput +} from '../children/wecomSmartSheetFieldSimple/src'; +import { + tool as fieldAdvancedTool, + InputType as FieldAdvancedInput +} from '../children/wecomSmartSheetFieldAdvanced/src'; +import { + tool as recordSimpleTool, + InputType as RecordSimpleInput +} from '../children/wecomSmartSheetRecordSimple/src'; +import { + tool as recordAdvancedTool, + InputType as RecordAdvancedInput +} from '../children/wecomSmartSheetRecordAdvanced/src'; +import { tool as docTool, InputType as DocInput } from '../children/wecomSmartSheetDoc/src'; +import { tool as viewTool, InputType as ViewInput } from '../children/wecomSmartSheetView/src'; + +describe('WeCom Smart Sheet Toolset Export Test', () => { + describe('Function Exports', () => { + it('should export all tool functions', () => { + expect(typeof tableTool).toBe('function'); + expect(typeof fieldSimpleTool).toBe('function'); + expect(typeof fieldAdvancedTool).toBe('function'); + expect(typeof recordSimpleTool).toBe('function'); + expect(typeof recordAdvancedTool).toBe('function'); + expect(typeof docTool).toBe('function'); + expect(typeof viewTool).toBe('function'); + }); + }); + + describe('Schema Validation', () => { + it('Table Tool Schema', () => { + const result = TableInput.safeParse({ accessToken: 'test', docid: 'test', action: 'get' }); + expect(result.success).toBe(true); + }); + + it('Field Simple Tool Schema', () => { + const result = FieldSimpleInput.safeParse({ + accessToken: 'test', + docid: 'test', + sheet_id: 'test', + action: 'add', + field_title: 'test', + field_type: 'FIELD_TYPE_TEXT' + }); + expect(result.success).toBe(true); + }); + + it('Field Advanced Tool Schema', () => { + const result = FieldAdvancedInput.safeParse({ + accessToken: 'test', + docid: 'test', + sheet_id: 'test', + action: 'add', + data: {} + }); + expect(result.success).toBe(true); + }); + + it('Record Simple Tool Schema', () => { + const result = RecordSimpleInput.safeParse({ + accessToken: 'test', + docid: 'test', + sheet_id: 'test', + action: 'add', + data: { title: 'hello' } + }); + expect(result.success).toBe(true); + }); + + it('Doc Tool Schema', () => { + const result = DocInput.safeParse({ + accessToken: 'test', + doc_name: 'test' + }); + expect(result.success).toBe(true); + }); + + it('View Tool Schema', () => { + const result = ViewInput.safeParse({ + accessToken: 'test', + docid: 'test', + sheet_id: 'test', + action: 'list' + }); + expect(result.success).toBe(true); + }); + }); +}); diff --git a/runtime/.env.template b/runtime/.env.template index d12fe58b..a3c04c9e 100644 --- a/runtime/.env.template +++ b/runtime/.env.template @@ -35,3 +35,5 @@ STORAGE_EXTERNAL_ENDPOINT= STORAGE_S3_ENDPOINT=http://127.0.0.1:9000 STORAGE_S3_FORCE_PATH_STYLE=true STORAGE_S3_MAX_RETRIES=3 +# reverse invoking +FASTGPT_BASE_URL=http://localhost:3000