diff --git a/bun.lock b/bun.lock index 62f5fdd0..6cb03ec3 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "fastgpt-plugins", diff --git a/modules/tool/packages/allTick/children/depthTick/config.ts b/modules/tool/packages/allTick/children/depthTick/config.ts new file mode 100644 index 00000000..42dfe120 --- /dev/null +++ b/modules/tool/packages/allTick/children/depthTick/config.ts @@ -0,0 +1,45 @@ +import { defineTool } from '@tool/type'; +import { FlowNodeInputTypeEnum, WorkflowIOValueTypeEnum } from '@tool/type/fastgpt'; + +export default defineTool({ + name: { + 'zh-CN': '深度行情查询', + en: 'Depth Tick Query' + }, + description: { + 'zh-CN': '获取AllTick的最新盘口深度行情数据(Order Book)', + en: 'Get AllTick latest depth tick data (Order Book)' + }, + toolDescription: + 'Query real-time market depth data including bid/ask prices and volumes for stocks, forex, cryptocurrencies and other financial instruments from AllTick API', + versionList: [ + { + value: '0.1.0', + description: 'Default version', + inputs: [ + { + key: 'symbol', + label: '产品代码', + description: '支持股票、外汇、贵金属、加密货币等,如:"857.HK","UNH.US"', + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string + }, + { + key: 'is_stock', + label: '是否为股票类产品', + description: '是否为股票类产品,决定使用哪个API端点。股票类包括:A股、港股、美股等', + renderTypeList: [FlowNodeInputTypeEnum.switch, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.boolean + } + ], + outputs: [ + { + valueType: WorkflowIOValueTypeEnum.object, + key: 'data', + label: '深度行情数据', + description: '包含产品代码、报价序号、时间戳、买卖盘深度等完整的盘口信息' + } + ] + } + ] +}); diff --git a/modules/tool/packages/allTick/children/depthTick/index.ts b/modules/tool/packages/allTick/children/depthTick/index.ts new file mode 100644 index 00000000..d698ed48 --- /dev/null +++ b/modules/tool/packages/allTick/children/depthTick/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/allTick/children/depthTick/src/index.ts b/modules/tool/packages/allTick/children/depthTick/src/index.ts new file mode 100644 index 00000000..812f1edb --- /dev/null +++ b/modules/tool/packages/allTick/children/depthTick/src/index.ts @@ -0,0 +1,143 @@ +import { z } from 'zod'; + +// Input parameter schema +export const InputType = z.object({ + token: z.string().min(1, 'Please provide a valid API token'), + symbol: z.string().min(1, 'Please provide product code, e.g.: 857.HK, UNH.US'), + is_stock: z + .boolean() + .optional() + .default(true) + .describe('Whether it is a stock product, determines which API endpoint to use') +}); + +// Depth quote data item schema +const DepthItemType = z.object({ + price: z.string().describe('Price'), + volume: z.string().describe('Volume') +}); + +// Single product depth quote schema +const TickItemType = z.object({ + code: z.string().describe('Product code'), + seq: z.string().describe('Quote sequence number'), + tick_time: z.string().describe('Quote timestamp'), + bids: z.array(DepthItemType).describe('Bid depth list'), + asks: z.array(DepthItemType).describe('Ask depth list') +}); + +// API response schema +const ApiResponseType = z.object({ + ret: z.number(), + msg: z.string(), + trace: z.string(), + data: z + .object({ + tick_list: z.array(TickItemType) + }) + .optional() +}); + +// Output parameter schema +export const OutputType = z.object({ + data: z.object({ + tick_list: z.array(TickItemType), + total_count: z.number() + }) +}); + +// Generate unique trace code +function generateTrace(): string { + const uuid = crypto.randomUUID(); + const timestamp = Date.now(); + return `${uuid}-${timestamp}`; +} + +// Build query parameters +function buildQueryData(params: z.infer) { + return { + trace: generateTrace(), + data: { + symbol_list: [{ code: params.symbol }] + } + }; +} + +// Get API endpoint URL +function getApiEndpoint(isStock: boolean): string { + if (isStock) { + return 'https://quote.alltick.io/quote-stock-b-api/depth-tick'; + } else { + return 'https://quote.alltick.io/quote-b-api/depth-tick'; + } +} + +export async function tool(params: z.infer): Promise> { + try { + // Validate input parameters + const validatedParams = InputType.parse(params); + + // Build request data + const queryData = buildQueryData(validatedParams); + const apiUrl = getApiEndpoint(validatedParams.is_stock); + + // Build complete request URL + const requestUrl = `${apiUrl}?token=${encodeURIComponent(validatedParams.token)}&query=${encodeURIComponent(JSON.stringify(queryData))}`; + + // Send API request + const response = await fetch(requestUrl, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'User-Agent': 'FastGPT-AllTick-Plugin/1.0' + } + }); + + if (!response.ok) { + return Promise.reject(new Error(`HTTP error: ${response.status} ${response.statusText}`)); + } + + const responseData = await response.json(); + + // Validate API response format + const validatedResponse = ApiResponseType.parse(responseData); + + // Check API return status + if (validatedResponse.ret !== 200) { + return Promise.reject( + new Error(`API error: ${validatedResponse.msg} (error code: ${validatedResponse.ret})`) + ); + } + + // Check if data exists + if (!validatedResponse.data || !validatedResponse.data.tick_list) { + return Promise.reject( + new Error( + 'Failed to retrieve depth quote data, please check if the product code is correct' + ) + ); + } + + // Return success result + return { + data: { + tick_list: validatedResponse.data.tick_list, + total_count: validatedResponse.data.tick_list.length + } + }; + } catch (error) { + // Error handling - use Promise.reject + if (error instanceof z.ZodError) { + const errorMessages = error.errors + .map((err) => `${err.path.join('.')}: ${err.message}`) + .join('; '); + return Promise.reject(new Error(`Parameter validation failed: ${errorMessages}`)); + } + + if (error instanceof Error) { + return Promise.reject(new Error(`Request failed: ${error.message}`)); + } + + return Promise.reject(new Error('Unknown error, please try again later')); + } +} diff --git a/modules/tool/packages/allTick/children/depthTick/test/index.test.ts b/modules/tool/packages/allTick/children/depthTick/test/index.test.ts new file mode 100644 index 00000000..b70e289f --- /dev/null +++ b/modules/tool/packages/allTick/children/depthTick/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/allTick/children/kline/config.ts b/modules/tool/packages/allTick/children/kline/config.ts new file mode 100644 index 00000000..992d8afe --- /dev/null +++ b/modules/tool/packages/allTick/children/kline/config.ts @@ -0,0 +1,146 @@ +import { defineTool } from '@tool/type'; +import { FlowNodeInputTypeEnum, WorkflowIOValueTypeEnum } from '@tool/type/fastgpt'; + +export default defineTool({ + name: { + 'zh-CN': 'K线数据查询', + en: 'AllTick K-Line Data Query' + }, + description: { + 'zh-CN': '获取AllTick平台的K线图表数据,支持股票、外汇、贵金属、加密货币等多种金融产品', + en: 'Retrieve K-line chart data from AllTick platform, supporting stocks, forex, precious metals, cryptocurrencies and other financial products' + }, + toolDescription: + 'Query K-line (candlestick) chart data from AllTick API for various financial instruments including stocks, forex, precious metals, and cryptocurrencies', + versionList: [ + { + value: '0.1.0', + description: 'Default version', + inputs: [ + { + key: 'code', + label: '产品代码', + description: '金融产品的唯一标识符,支持股票代码、外汇对、贵金属代码、加密货币代码等', + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string + }, + { + key: 'kline_type', + label: 'K线周期类型', + description: + 'K线时间周期设置:\n' + + '• 1: 1分钟线\n' + + '• 2: 5分钟线\n' + + '• 3: 15分钟线\n' + + '• 4: 30分钟线\n' + + '• 5: 1小时线\n' + + '• 6: 2小时线(股票不支持)\n' + + '• 7: 4小时线(股票不支持)\n' + + '• 8: 日K线\n' + + '• 9: 周K线\n' + + '• 10: 月K线\n' + + '注:查询昨日收盘价请使用日K线(8)', + renderTypeList: [FlowNodeInputTypeEnum.select, FlowNodeInputTypeEnum.reference], + list: [ + { + label: '1分钟线', + value: '1' + }, + { + label: '5分钟线', + value: '2' + }, + { + label: '15分钟线', + value: '3' + }, + { + label: '30分钟线', + value: '4' + }, + { + label: '1小时线', + value: '5' + }, + { + label: '2小时线', + value: '6' + }, + { + label: '4小时线', + value: '7' + }, + { + label: '日K线', + value: '8' + }, + { + label: '周K线', + value: '9' + }, + { + label: '月K线', + value: '10' + } + ], + valueType: WorkflowIOValueTypeEnum.number + }, + { + key: 'kline_timestamp_end', + label: 'K线查询截止时间', + description: + 'K线数据查询的时间基准点:\n' + + '• 传入 0:从当前最新交易日开始向前查询\n' + + '• 传入时间戳:从指定时间戳开始向前查询\n' + + '注:时间戳查询仅支持外汇、贵金属、加密货币,股票类产品无效', + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.number + }, + { + key: 'query_kline_num', + label: 'K线数据条数', + description: + '指定查询的K线数据条数,单次请求最多500条。\n' + + '可通过时间戳分批循环查询更多历史数据。\n' + + '提示:查询昨日收盘价时,设置K线周期为8(日K),数据条数为2,返回结果中时间戳较小的即为昨日收盘价', + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.number + }, + { + key: 'adjust_type', + label: '复权类型', + description: + '股票数据的复权处理方式(仅对股票类产品有效):\n• 0: 不复权(除权)\n• 1: 前复权\n注:目前仅支持不复权模式(0)', + renderTypeList: [FlowNodeInputTypeEnum.select, FlowNodeInputTypeEnum.reference], + list: [ + { + label: '不复权(除权)', + value: '0' + }, + { + label: '前复权', + value: '1' + } + ], + valueType: WorkflowIOValueTypeEnum.number + }, + { + key: 'is_stock', + label: '股票类产品开关', + description: '标识当前查询的产品是否为股票类型,用于系统选择合适的API接口进行数据查询', + renderTypeList: [FlowNodeInputTypeEnum.switch, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.boolean + } + ], + outputs: [ + { + valueType: WorkflowIOValueTypeEnum.object, + key: 'data', + label: 'K线数据结果', + description: + '返回完整的K线数据对象,包含产品代码、K线周期类型、K线数据列表以及数据总条数等详细信息' + } + ] + } + ] +}); diff --git a/modules/tool/packages/allTick/children/kline/index.ts b/modules/tool/packages/allTick/children/kline/index.ts new file mode 100644 index 00000000..d698ed48 --- /dev/null +++ b/modules/tool/packages/allTick/children/kline/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/allTick/children/kline/src/index.ts b/modules/tool/packages/allTick/children/kline/src/index.ts new file mode 100644 index 00000000..58a561da --- /dev/null +++ b/modules/tool/packages/allTick/children/kline/src/index.ts @@ -0,0 +1,180 @@ +import { z } from 'zod'; + +// Modified version (fixed) +const KlineTypeEnum = z + .union([ + z.enum(['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']), + z.number().int().min(1).max(10) + ]) + .transform((val) => (typeof val === 'string' ? Number(val) : val)); + +const AdjustTypeEnum = z + .union([z.enum(['0', '1']), z.number().int().min(0).max(1)]) + .transform((val) => (typeof val === 'string' ? Number(val) : val)); + +// Input parameter schema +export const InputType = z.object({ + token: z.string().min(1, 'Please provide a valid API token'), + code: z.string().min(1, 'Please provide product code, e.g.: 857.HK'), + kline_type: KlineTypeEnum.describe( + 'K-line type: 1=1min, 2=5min, 3=15min, 4=30min, 5=1hour, 6=2hour, 7=4hour, 8=daily, 9=weekly, 10=monthly' + ), + query_kline_num: z + .number() + .int() + .min(1) + .max(500) + .default(100) + .describe('Number of K-lines to query, maximum 500'), + kline_timestamp_end: z + .number() + .int() + .optional() + .default(0) + .describe('End timestamp, 0 means latest trading day'), + adjust_type: AdjustTypeEnum.optional() + .default(0) + .describe('Adjustment type: 0=ex-rights, 1=forward adjustment'), + is_stock: z + .boolean() + .optional() + .default(true) + .describe('Whether it is a stock product, determines which API endpoint to use') +}); + +// K-line data item schema +const KlineItemType = z.object({ + timestamp: z.string().describe('Timestamp'), + open_price: z.string().describe('Open price'), + close_price: z.string().describe('Close price'), + high_price: z.string().describe('High price'), + low_price: z.string().describe('Low price'), + volume: z.string().describe('Volume'), + turnover: z.string().optional().describe('Turnover') +}); + +// API response schema +const ApiResponseType = z.object({ + ret: z.number(), + msg: z.string(), + trace: z.string(), + data: z + .object({ + code: z.string(), + kline_type: z.number(), + kline_list: z.array(KlineItemType) + }) + .optional() +}); + +// Output parameter schema +export const OutputType = z.object({ + data: z + .object({ + code: z.string(), + kline_type: z.number(), + kline_list: z.array(KlineItemType), + total_count: z.number() + }) + .optional() +}); + +// Generate unique trace code +function generateTrace(): string { + const uuid = crypto.randomUUID(); + const timestamp = Date.now(); + return `${uuid}-${timestamp}`; +} + +// Build query parameters +function buildQueryData(params: z.infer) { + return { + trace: generateTrace(), + data: { + code: params.code, + kline_type: params.kline_type, + kline_timestamp_end: params.kline_timestamp_end, + query_kline_num: params.query_kline_num, + adjust_type: params.adjust_type + } + }; +} + +// Get API endpoint URL +function getApiEndpoint(isStock: boolean): string { + if (isStock) { + return 'https://quote.alltick.io/quote-stock-b-api/kline'; + } else { + return 'https://quote.alltick.io/quote-b-api/kline'; + } +} + +export async function tool(params: z.infer): Promise> { + try { + // Validate input parameters + const validatedParams = InputType.parse(params); + + // Build request data + const queryData = buildQueryData(validatedParams); + const apiUrl = getApiEndpoint(validatedParams.is_stock); + + // Build complete request URL + const requestUrl = `${apiUrl}?token=${encodeURIComponent(validatedParams.token)}&query=${encodeURIComponent(JSON.stringify(queryData))}`; + + // Send API request + const response = await fetch(requestUrl, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'User-Agent': 'FastGPT-AllTick-Plugin/1.0' + } + }); + + if (!response.ok) { + return Promise.reject(new Error(`HTTP error: ${response.status} ${response.statusText}`)); + } + + const responseData = await response.json(); + + // Validate API response format + const validatedResponse = ApiResponseType.parse(responseData); + + // Check API return status + if (validatedResponse.ret !== 200) { + return Promise.reject( + new Error(`API error: ${validatedResponse.msg} (error code: ${validatedResponse.ret})`) + ); + } + + // Check if data exists + if (!validatedResponse.data || !validatedResponse.data.kline_list) { + return Promise.reject( + new Error('Failed to retrieve K-line data, please check if the product code is correct') + ); + } + + // Return success result + return { + data: { + code: validatedResponse.data.code, + kline_type: validatedResponse.data.kline_type, + kline_list: validatedResponse.data.kline_list, + total_count: validatedResponse.data.kline_list.length + } + }; + } catch (error) { + // Error handling + if (error instanceof z.ZodError) { + const errorMessages = error.errors + .map((err) => `${err.path.join('.')}: ${err.message}`) + .join('; '); + return Promise.reject(new Error(`Parameter validation failed: ${errorMessages}`)); + } + + if (error instanceof Error) { + return Promise.reject(new Error(`Request failed: ${error.message}`)); + } + + return Promise.reject(new Error('Unknown error, please try again later')); + } +} diff --git a/modules/tool/packages/allTick/children/kline/test/index.test.ts b/modules/tool/packages/allTick/children/kline/test/index.test.ts new file mode 100644 index 00000000..b70e289f --- /dev/null +++ b/modules/tool/packages/allTick/children/kline/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/allTick/children/staticInfo/config.ts b/modules/tool/packages/allTick/children/staticInfo/config.ts new file mode 100644 index 00000000..f323d9d1 --- /dev/null +++ b/modules/tool/packages/allTick/children/staticInfo/config.ts @@ -0,0 +1,38 @@ +import { defineTool } from '@tool/type'; +import { FlowNodeInputTypeEnum, WorkflowIOValueTypeEnum } from '@tool/type/fastgpt'; + +export default defineTool({ + name: { + 'zh-CN': '股票基础信息查询', + en: 'Stock Static Info Query' + }, + description: { + 'zh-CN': '批量查询美股、港股、A股产品的基础信息', + en: 'Batch query basic information for US stocks, Hong Kong stocks, and A-shares' + }, + toolDescription: + 'Query basic stock information including company name, book value per share, circulating shares, currency, dividend yield, earnings per share and other fundamental data from AllTick API', + versionList: [ + { + value: '0.1.0', + description: 'Default version', + inputs: [ + { + key: 'symbol', + label: '股票代码', + description: '股票代码,支持美股、港股、A股,如:"857.HK","UNH.US","000001.SZ"', + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string + } + ], + outputs: [ + { + valueType: WorkflowIOValueTypeEnum.object, + key: 'data', + label: '股票基础信息', + description: '包含股票名称、每股净资产、流通股本、交易币种、股息、每股盈利等基础信息' + } + ] + } + ] +}); diff --git a/modules/tool/packages/allTick/children/staticInfo/index.ts b/modules/tool/packages/allTick/children/staticInfo/index.ts new file mode 100644 index 00000000..d698ed48 --- /dev/null +++ b/modules/tool/packages/allTick/children/staticInfo/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/allTick/children/staticInfo/src/index.ts b/modules/tool/packages/allTick/children/staticInfo/src/index.ts new file mode 100644 index 00000000..b72a7208 --- /dev/null +++ b/modules/tool/packages/allTick/children/staticInfo/src/index.ts @@ -0,0 +1,135 @@ +import { z } from 'zod'; + +// Input parameter schema +export const InputType = z.object({ + token: z.string().min(1, 'Please provide a valid API token'), + symbol: z.string().min(1, 'Please provide stock code, e.g.: 857.HK, UNH.US, 000001.SZ') +}); + +// Stock basic information schema +const StaticInfoItemType = z.object({ + board: z.string().optional().describe('Stock board'), + bps: z.string().optional().describe('Book value per share'), + circulating_shares: z.string().optional().describe('Circulating shares'), + currency: z.string().optional().describe('Trading currency'), + dividend_yield: z.string().optional().describe('Dividend yield'), + eps: z.string().optional().describe('Earnings per share'), + eps_ttm: z.string().optional().describe('Earnings per share (TTM)'), + exchange: z.string().optional().describe('Product exchange'), + hk_shares: z.string().optional().describe('Hong Kong shares (HK stocks only)'), + lot_size: z.string().optional().describe('Lot size'), + name_cn: z.string().optional().describe('Simplified Chinese product name'), + name_en: z.string().optional().describe('English product name'), + name_hk: z.string().optional().describe('Traditional Chinese product name'), + symbol: z.string().describe('Product code'), + total_shares: z.string().optional().describe('Total shares') +}); + +// API response schema +const ApiResponseType = z.object({ + ret: z.number(), + msg: z.string(), + trace: z.string(), + data: z + .object({ + static_info_list: z.array(StaticInfoItemType) + }) + .optional() +}); + +// Output parameter schema +export const OutputType = z.object({ + data: z.object({ + static_info_list: z.array(StaticInfoItemType), + total_count: z.number() + }) +}); + +// Generate unique trace code +function generateTrace(): string { + const uuid = crypto.randomUUID(); + const timestamp = Date.now(); + return `${uuid}-${timestamp}`; +} + +// Build query parameters +function buildQueryData(params: z.infer) { + return { + trace: generateTrace(), + data: { + symbol_list: [{ code: params.symbol }] + } + }; +} + +export async function tool(params: z.infer): Promise> { + try { + // Validate input parameters + const validatedParams = InputType.parse(params); + + // Build request data + const queryData = buildQueryData(validatedParams); + + // Use stock basic information API endpoint + const apiUrl = 'https://quote.alltick.io/quote-stock-b-api/static_info'; + + // Build complete request URL + const requestUrl = `${apiUrl}?token=${encodeURIComponent(validatedParams.token)}&query=${encodeURIComponent(JSON.stringify(queryData))}`; + + // Send API request + const response = await fetch(requestUrl, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'User-Agent': 'FastGPT-AllTick-Plugin/1.0' + } + }); + + if (!response.ok) { + return Promise.reject(new Error(`HTTP error: ${response.status} ${response.statusText}`)); + } + + const responseData = await response.json(); + + // Validate API response format + const validatedResponse = ApiResponseType.parse(responseData); + + // Check API return status + if (validatedResponse.ret !== 200) { + return Promise.reject( + new Error(`API error: ${validatedResponse.msg} (error code: ${validatedResponse.ret})`) + ); + } + + // Check if data exists + if (!validatedResponse.data || !validatedResponse.data.static_info_list) { + return Promise.reject( + new Error( + 'Failed to retrieve stock basic information, please check if the stock code is correct' + ) + ); + } + + // Return success result + return { + data: { + static_info_list: validatedResponse.data.static_info_list, + total_count: validatedResponse.data.static_info_list.length + } + }; + } catch (error) { + // Error handling - use Promise.reject + if (error instanceof z.ZodError) { + const errorMessages = error.errors + .map((err) => `${err.path.join('.')}: ${err.message}`) + .join('; '); + return Promise.reject(new Error(`Parameter validation failed: ${errorMessages}`)); + } + + if (error instanceof Error) { + return Promise.reject(new Error(`Request failed: ${error.message}`)); + } + + return Promise.reject(new Error('Unknown error, please try again later')); + } +} diff --git a/modules/tool/packages/allTick/children/staticInfo/test/index.test.ts b/modules/tool/packages/allTick/children/staticInfo/test/index.test.ts new file mode 100644 index 00000000..b70e289f --- /dev/null +++ b/modules/tool/packages/allTick/children/staticInfo/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/allTick/children/tradeTick/config.ts b/modules/tool/packages/allTick/children/tradeTick/config.ts new file mode 100644 index 00000000..d2e13ac7 --- /dev/null +++ b/modules/tool/packages/allTick/children/tradeTick/config.ts @@ -0,0 +1,45 @@ +import { defineTool } from '@tool/type'; +import { FlowNodeInputTypeEnum, WorkflowIOValueTypeEnum } from '@tool/type/fastgpt'; + +export default defineTool({ + name: { + 'zh-CN': '最新成交价查询', + en: 'Latest Trade Price Query' + }, + description: { + 'zh-CN': '获取AllTick的最新成交价数据(最新tick、当前价、最新价)', + en: 'Get AllTick latest trade price data (latest tick, current price, latest price)' + }, + toolDescription: + 'Query real-time latest trade price data including price, volume, turnover and trade direction for stocks, forex, cryptocurrencies and other financial instruments from AllTick API', + versionList: [ + { + value: '0.1.0', + description: 'Default version', + inputs: [ + { + key: 'symbol', + label: '产品代码', + description: '支持股票、外汇、贵金属、加密货币等,如:"857.HK","UNH.US"', + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string + }, + { + key: 'is_stock', + label: '是否为股票类产品', + description: '是否为股票类产品,决定使用哪个API端点。股票类包括:A股、港股、美股等', + renderTypeList: [FlowNodeInputTypeEnum.switch, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.boolean + } + ], + outputs: [ + { + valueType: WorkflowIOValueTypeEnum.object, + key: 'data', + label: '最新成交价数据', + description: '包含产品代码、序号、时间戳、成交价、成交量、成交额、交易方向等信息' + } + ] + } + ] +}); diff --git a/modules/tool/packages/allTick/children/tradeTick/index.ts b/modules/tool/packages/allTick/children/tradeTick/index.ts new file mode 100644 index 00000000..d698ed48 --- /dev/null +++ b/modules/tool/packages/allTick/children/tradeTick/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/allTick/children/tradeTick/src/index.ts b/modules/tool/packages/allTick/children/tradeTick/src/index.ts new file mode 100644 index 00000000..a8081a23 --- /dev/null +++ b/modules/tool/packages/allTick/children/tradeTick/src/index.ts @@ -0,0 +1,139 @@ +import { z } from 'zod'; + +// Input parameter schema +export const InputType = z.object({ + token: z.string().min(1, 'Please provide a valid API token'), + symbol: z.string().min(1, 'Please provide product code, e.g.: 857.HK, UNH.US'), + is_stock: z + .boolean() + .optional() + .default(true) + .describe('Whether it is a stock product, determines which API endpoint to use') +}); + +// Single product latest trade price data schema +const TickItemType = z.object({ + code: z.string().describe('Product code'), + seq: z.string().describe('Sequence number'), + tick_time: z.string().describe('Timestamp'), + price: z.string().describe('Trade price'), + volume: z.string().describe('Trade volume'), + turnover: z.string().describe('Trade turnover'), + trade_direction: z.number().describe('Trade direction: 0=default, 1=Buy, 2=SELL') +}); + +// API response schema +const ApiResponseType = z.object({ + ret: z.number(), + msg: z.string(), + trace: z.string(), + data: z + .object({ + tick_list: z.array(TickItemType) + }) + .optional() +}); + +// Output parameter schema +export const OutputType = z.object({ + data: z.object({ + tick_list: z.array(TickItemType), + total_count: z.number() + }) +}); + +// Generate unique trace code +function generateTrace(): string { + const uuid = crypto.randomUUID(); + const timestamp = Date.now(); + return `${uuid}-${timestamp}`; +} + +// Build query parameters +function buildQueryData(params: z.infer) { + return { + trace: generateTrace(), + data: { + symbol_list: [{ code: params.symbol }] + } + }; +} + +// Get API endpoint URL +function getApiEndpoint(isStock: boolean): string { + if (isStock) { + return 'https://quote.alltick.io/quote-stock-b-api/trade-tick'; + } else { + return 'https://quote.alltick.io/quote-b-api/trade-tick'; + } +} + +export async function tool(params: z.infer): Promise> { + try { + // Validate input parameters + const validatedParams = InputType.parse(params); + + // Build request data + const queryData = buildQueryData(validatedParams); + const apiUrl = getApiEndpoint(validatedParams.is_stock); + + // Build complete request URL + const requestUrl = `${apiUrl}?token=${encodeURIComponent(validatedParams.token)}&query=${encodeURIComponent(JSON.stringify(queryData))}`; + + // Send API request + const response = await fetch(requestUrl, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'User-Agent': 'FastGPT-AllTick-Plugin/1.0' + } + }); + + if (!response.ok) { + return Promise.reject(new Error(`HTTP error: ${response.status} ${response.statusText}`)); + } + + const responseData = await response.json(); + + // Validate API response format + const validatedResponse = ApiResponseType.parse(responseData); + + // Check API return status + if (validatedResponse.ret !== 200) { + return Promise.reject( + new Error(`API error: ${validatedResponse.msg} (error code: ${validatedResponse.ret})`) + ); + } + + // Check if data exists + if (!validatedResponse.data || !validatedResponse.data.tick_list) { + return Promise.reject( + new Error( + 'Failed to retrieve latest trade price data, please check if the product code is correct' + ) + ); + } + + // Return success result + return { + data: { + tick_list: validatedResponse.data.tick_list, + total_count: validatedResponse.data.tick_list.length + } + }; + } catch (error) { + // Error handling - use Promise.reject + if (error instanceof z.ZodError) { + const errorMessages = error.errors + .map((err) => `${err.path.join('.')}: ${err.message}`) + .join('; '); + return Promise.reject(new Error(`Parameter validation failed: ${errorMessages}`)); + } + + if (error instanceof Error) { + return Promise.reject(new Error(`Request failed: ${error.message}`)); + } + + return Promise.reject(new Error('Unknown error, please try again later')); + } +} diff --git a/modules/tool/packages/allTick/children/tradeTick/test/index.test.ts b/modules/tool/packages/allTick/children/tradeTick/test/index.test.ts new file mode 100644 index 00000000..b70e289f --- /dev/null +++ b/modules/tool/packages/allTick/children/tradeTick/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/allTick/config.ts b/modules/tool/packages/allTick/config.ts new file mode 100644 index 00000000..3d1aba10 --- /dev/null +++ b/modules/tool/packages/allTick/config.ts @@ -0,0 +1,26 @@ +import { defineToolSet } from '@tool/type'; +import { ToolTypeEnum } from '@tool/type/tool'; + +export default defineToolSet({ + name: { + 'zh-CN': 'allTick', + en: 'allTick' + }, + type: ToolTypeEnum.tools, + description: { + 'zh-CN': '这是一个allTick工具集', + en: 'This is a allTick tool set' + }, + courseUrl: 'https://alltick.co/zh-CN', + toolDescription: + 'tool description for ai to use, fallback to English description if not provided', + secretInputConfig: [ + { + key: 'token', + label: 'token', + description: '可以在 https://alltick.co/zh-CN 注册获取', + required: true, + inputType: 'secret' + } + ] +}); diff --git a/modules/tool/packages/allTick/index.ts b/modules/tool/packages/allTick/index.ts new file mode 100644 index 00000000..22bccae7 --- /dev/null +++ b/modules/tool/packages/allTick/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/allTick/logo.svg b/modules/tool/packages/allTick/logo.svg new file mode 100644 index 00000000..21541923 --- /dev/null +++ b/modules/tool/packages/allTick/logo.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/modules/tool/packages/allTick/package.json b/modules/tool/packages/allTick/package.json new file mode 100644 index 00000000..a683c3a3 --- /dev/null +++ b/modules/tool/packages/allTick/package.json @@ -0,0 +1,17 @@ +{ + "name": "@fastgpt-plugins/tool-all-tick", + "module": "index.ts", + "type": "module", + "scripts": { + "build": "bun ../../../../scripts/build.ts" + }, + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "zod": "^3.24.2" + } +}