diff --git a/modules/tool/packages/allTick/README.md b/modules/tool/packages/allTick/README.md new file mode 100644 index 00000000..aa6ec764 --- /dev/null +++ b/modules/tool/packages/allTick/README.md @@ -0,0 +1,13 @@ +# example tool + +Write the introduction for your tool here. + +You can insert a picture with following markdown + +```markdown +Insert a assets picture (you need to put you picture file in the assets dir) +![](./assets/pic.png) + +Or insert a public pic +![](https://picsum.photos/200) +``` 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..0ebb760b --- /dev/null +++ b/modules/tool/packages/allTick/config.ts @@ -0,0 +1,25 @@ +import { defineToolSet } from '@tool/type'; +import { ToolTagEnum } from '@tool/type/tags'; + +export default defineToolSet({ + name: { + 'zh-CN': 'allTick', + en: 'allTick' + }, + tags: [ToolTagEnum.enum.tools], + description: { + 'zh-CN': '这是一个 allTick 工具集,金融市场信息的获取工具', + en: 'This is a set of allTick tools for fetching financial market information.' + }, + courseUrl: 'https://alltick.co/zh-CN', + toolDescription: 'This is a set of allTick tools for fetching financial market information.', + 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..9dd1b8c9 --- /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.25.76" + } +} diff --git a/modules/tool/packages/ozon/README.md b/modules/tool/packages/ozon/README.md new file mode 100644 index 00000000..aa6ec764 --- /dev/null +++ b/modules/tool/packages/ozon/README.md @@ -0,0 +1,13 @@ +# example tool + +Write the introduction for your tool here. + +You can insert a picture with following markdown + +```markdown +Insert a assets picture (you need to put you picture file in the assets dir) +![](./assets/pic.png) + +Or insert a public pic +![](https://picsum.photos/200) +``` diff --git a/modules/tool/packages/ozon/bun.lock b/modules/tool/packages/ozon/bun.lock new file mode 100644 index 00000000..5e860e31 --- /dev/null +++ b/modules/tool/packages/ozon/bun.lock @@ -0,0 +1,81 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "@fastgpt-plugins/tool-ozon", + "dependencies": { + "axios": "^1.12.2", + "zod": "^3.25.76", + }, + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5.0.0", + }, + }, + }, + "packages": { + "@types/bun": ["@types/bun@1.3.2", "https://registry.npmmirror.com/@types/bun/-/bun-1.3.2.tgz", { "dependencies": { "bun-types": "1.3.2" } }, "sha512-t15P7k5UIgHKkxwnMNkJbWlh/617rkDGEdSsDbu+qNHTaz9SKf7aC8fiIlUdD5RPpH6GEkP0cK7WlvmrEBRtWg=="], + + "@types/node": ["@types/node@24.10.1", "https://registry.npmmirror.com/@types/node/-/node-24.10.1.tgz", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="], + + "@types/react": ["@types/react@19.2.5", "https://registry.npmmirror.com/@types/react/-/react-19.2.5.tgz", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-keKxkZMqnDicuvFoJbzrhbtdLSPhj/rZThDlKWCDbgXmUg0rEUFtRssDXKYmtXluZlIqiC5VqkCgRwzuyLHKHw=="], + + "asynckit": ["asynckit@0.4.0", "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "axios": ["axios@1.13.2", "https://registry.npmmirror.com/axios/-/axios-1.13.2.tgz", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA=="], + + "bun-types": ["bun-types@1.3.2", "https://registry.npmmirror.com/bun-types/-/bun-types-1.3.2.tgz", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-i/Gln4tbzKNuxP70OWhJRZz1MRfvqExowP7U6JKoI8cntFrtxg7RJK3jvz7wQW54UuvNC8tbKHHri5fy74FVqg=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "combined-stream": ["combined-stream@1.0.8", "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + + "csstype": ["csstype@3.2.2", "https://registry.npmmirror.com/csstype/-/csstype-3.2.2.tgz", {}, "sha512-D80T+tiqkd/8B0xNlbstWDG4x6aqVfO52+OlSUNIdkTvmNw0uQpJLeos2J/2XvpyidAFuTPmpad+tUxLndwj6g=="], + + "delayed-stream": ["delayed-stream@1.0.0", "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "dunder-proto": ["dunder-proto@1.0.1", "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "es-define-property": ["es-define-property@1.0.1", "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + + "follow-redirects": ["follow-redirects@1.15.11", "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="], + + "form-data": ["form-data@4.0.5", "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], + + "function-bind": ["function-bind@1.1.2", "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "gopd": ["gopd@1.2.0", "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "has-symbols": ["has-symbols@1.1.0", "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.2", "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "mime-db": ["mime-db@1.52.0", "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "proxy-from-env": ["proxy-from-env@1.1.0", "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], + + "typescript": ["typescript@5.9.3", "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@7.16.0", "https://registry.npmmirror.com/undici-types/-/undici-types-7.16.0.tgz", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "zod": ["zod@3.25.76", "https://registry.npmmirror.com/zod/-/zod-3.25.76.tgz", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + } +} diff --git a/modules/tool/packages/ozon/children/barcodeAdd/config.ts b/modules/tool/packages/ozon/children/barcodeAdd/config.ts new file mode 100644 index 00000000..1e44a4a3 --- /dev/null +++ b/modules/tool/packages/ozon/children/barcodeAdd/config.ts @@ -0,0 +1,41 @@ +import { defineTool } from '@tool/type'; +import { FlowNodeInputTypeEnum, WorkflowIOValueTypeEnum } from '@tool/type/fastgpt'; + +export default defineTool({ + name: { + 'zh-CN': '为商品绑定条形码', + en: 'Add barcode to product' + }, + description: { + 'zh-CN': + '如果商品已有条形码但未在 Ozon 系统中登记,可通过此方法绑定。 如果没有条形码,您可以通过 /v1/barcode/generate 方法生成。每个商品最多可绑定 100 个条形码。 每个卖家账号每分钟最多可使用该方法 20 次。', + en: 'If a product already has a barcode but it hasn’t been registered in the Ozon system, you can associate it using this method. If it doesn’t have a barcode, you can generate one using the /v1/barcode/generate method. Up to 100 barcodes can be associated with a single product. Each seller account can use this method up to 20 times per minute.' + }, + toolDescription: + 'Add a barcode to a product in Ozon. The barcode should be a unique identifier for the product.', + versionList: [ + { + value: '0.1.0', + description: 'Default version', + inputs: [ + { + key: 'barcodes', + label: '条形码列表', + description: 'JSON 数组,每项包含 { barcode: string; sku: number }', + renderTypeList: [FlowNodeInputTypeEnum.JSONEditor, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.arrayObject, + required: true, + defaultValue: '[{"barcode":"123456789012","sku":123456789012}]' + } + ], + outputs: [ + { + valueType: WorkflowIOValueTypeEnum.object, + key: 'result', + label: '结果', + description: '绑定结果' + } + ] + } + ] +}); diff --git a/modules/tool/packages/ozon/children/barcodeAdd/index.ts b/modules/tool/packages/ozon/children/barcodeAdd/index.ts new file mode 100644 index 00000000..d698ed48 --- /dev/null +++ b/modules/tool/packages/ozon/children/barcodeAdd/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/ozon/children/barcodeAdd/src/index.ts b/modules/tool/packages/ozon/children/barcodeAdd/src/index.ts new file mode 100644 index 00000000..28b4772c --- /dev/null +++ b/modules/tool/packages/ozon/children/barcodeAdd/src/index.ts @@ -0,0 +1,35 @@ +import axios from 'axios'; +import { z } from 'zod'; + +export const InputType = z.object({ + clientId: z.string(), + apiKey: z.string(), + barcodes: z.array( + z.object({ + barcode: z.string(), + sku: z.number() + }) + ) +}); + +export const OutputType = z.object({ + result: z.any() +}); + +export async function tool(props: z.infer): Promise> { + const { clientId, apiKey, barcodes } = props; + + const endpoint = 'https://api-seller.ozon.ru/v1/barcode/add'; + const payload = { barcodes }; + + const { data } = await axios.post(endpoint, payload, { + headers: { + 'Client-Id': clientId, + 'Api-Key': apiKey, + 'Content-Type': 'application/json' + }, + timeout: 60000 + }); + + return { result: data }; +} diff --git a/modules/tool/packages/ozon/children/barcodeAdd/test/index.test.ts b/modules/tool/packages/ozon/children/barcodeAdd/test/index.test.ts new file mode 100644 index 00000000..b70e289f --- /dev/null +++ b/modules/tool/packages/ozon/children/barcodeAdd/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/ozon/children/barcodeGen/config.ts b/modules/tool/packages/ozon/children/barcodeGen/config.ts new file mode 100644 index 00000000..ef272318 --- /dev/null +++ b/modules/tool/packages/ozon/children/barcodeGen/config.ts @@ -0,0 +1,41 @@ +import { defineTool } from '@tool/type'; +import { FlowNodeInputTypeEnum, WorkflowIOValueTypeEnum } from '@tool/type/fastgpt'; + +export default defineTool({ + name: { + 'zh-CN': '创建商品条形码', + en: 'Generate product barcodes' + }, + description: { + 'zh-CN': + '如果商品没有条形码,您可以通过此方法生成条形码。 如果商品已有条形码但未在 Ozon 系统中登记,您可以通过 /v1/barcode/add 方法进行绑定。 每次请求最多可为 100 个商品生成条形码。 每个卖家账号每分钟最多可使用该方法 20 次。', + en: 'Generate barcodes for products without one. If a product already has a barcode but it is not registered in Ozon, use /v1/barcode/add to bind it. Up to 100 products per request. Up to 20 requests per minute per seller account.' + }, + toolDescription: + 'Generate barcodes on Ozon for the provided product_ids and return the API response including any errors.', + versionList: [ + { + value: '0.1.0', + description: 'Default version', + inputs: [ + { + key: 'product_ids', + label: '商品标识符列表', + description: '需要生成条形码的商品标识符(字符串 int64),JSON 数组["product_ids"]', + renderTypeList: [FlowNodeInputTypeEnum.JSONEditor, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.arrayString, + required: true, + defaultValue: '["product_ids"]' + } + ], + outputs: [ + { + valueType: WorkflowIOValueTypeEnum.object, + key: 'result', + label: '结果', + description: '生成结果(包含 errors 等)' + } + ] + } + ] +}); diff --git a/modules/tool/packages/ozon/children/barcodeGen/index.ts b/modules/tool/packages/ozon/children/barcodeGen/index.ts new file mode 100644 index 00000000..d698ed48 --- /dev/null +++ b/modules/tool/packages/ozon/children/barcodeGen/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/ozon/children/barcodeGen/src/index.ts b/modules/tool/packages/ozon/children/barcodeGen/src/index.ts new file mode 100644 index 00000000..31394063 --- /dev/null +++ b/modules/tool/packages/ozon/children/barcodeGen/src/index.ts @@ -0,0 +1,31 @@ +import axios from 'axios'; +import { z } from 'zod'; + +export const InputType = z.object({ + clientId: z.string(), + apiKey: z.string(), + product_ids: z.array(z.string()) +}); + +export const OutputType = z.object({ + result: z.any() +}); + +export async function tool(props: z.infer): Promise> { + const { clientId, apiKey, product_ids } = props; + + const endpoint = 'https://api-seller.ozon.ru/v1/barcode/generate'; + + const payload = { product_ids }; + + const { data } = await axios.post(endpoint, payload, { + headers: { + 'Client-Id': clientId, + 'Api-Key': apiKey, + 'Content-Type': 'application/json' + }, + timeout: 60000 + }); + + return { result: data }; +} diff --git a/modules/tool/packages/ozon/children/barcodeGen/test/index.test.ts b/modules/tool/packages/ozon/children/barcodeGen/test/index.test.ts new file mode 100644 index 00000000..b70e289f --- /dev/null +++ b/modules/tool/packages/ozon/children/barcodeGen/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/ozon/children/productAttrUpdate/config.ts b/modules/tool/packages/ozon/children/productAttrUpdate/config.ts new file mode 100644 index 00000000..95d4e659 --- /dev/null +++ b/modules/tool/packages/ozon/children/productAttrUpdate/config.ts @@ -0,0 +1,50 @@ +import { defineTool } from '@tool/type'; +import { FlowNodeInputTypeEnum, WorkflowIOValueTypeEnum } from '@tool/type/fastgpt'; + +export default defineTool({ + name: { + 'zh-CN': '更新商品特征', + en: 'Update product attributes' + }, + description: { + 'zh-CN': '更新商品特征', + en: 'Update product attributes' + }, + toolDescription: 'Update product attributes on Ozon', + versionList: [ + { + value: '0.1.0', + description: 'Default version', + inputs: [ + { + key: 'attributes', + label: '商品特征', + description: + "商品特征,格式为JSON数组,每个元素为{'complex_id': 0, 'id': 5076, 'values': [{'dictionary_value_id': 971082156, 'value': '麦克风架'}]}", + renderTypeList: [FlowNodeInputTypeEnum.JSONEditor, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.arrayAny, + required: true, + defaultValue: + '[{"complex_id": 0, "id": 5076, "values": [{"dictionary_value_id": 971082156, "value": "麦克风架"}]}]' + }, + { + key: 'offer_id', + label: '商品ID', + description: '商品ID', + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string, + required: true, + defaultValue: '123456789012' + } + ], + outputs: [ + { + valueType: WorkflowIOValueTypeEnum.number, + key: 'task_id', + label: '任务ID', + description: '任务 ID' + } + ] + } + ] +}); diff --git a/modules/tool/packages/ozon/children/productAttrUpdate/index.ts b/modules/tool/packages/ozon/children/productAttrUpdate/index.ts new file mode 100644 index 00000000..d698ed48 --- /dev/null +++ b/modules/tool/packages/ozon/children/productAttrUpdate/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/ozon/children/productAttrUpdate/src/index.ts b/modules/tool/packages/ozon/children/productAttrUpdate/src/index.ts new file mode 100644 index 00000000..e35141e0 --- /dev/null +++ b/modules/tool/packages/ozon/children/productAttrUpdate/src/index.ts @@ -0,0 +1,41 @@ +import axios from 'axios'; +import { z } from 'zod'; + +export const InputType = z.object({ + clientId: z.string(), + apiKey: z.string(), + attributes: z.array(z.any()).default([]), + offer_id: z.string() +}); + +export const OutputType = z.object({ + task_id: z.number() +}); + +export async function tool(props: z.infer): Promise> { + const { clientId, apiKey, attributes, offer_id } = props; + + const endpoint = 'https://api-seller.ozon.ru/v1/product/attributes/update'; + + const payload = { + items: [ + { + offer_id, + attributes + } + ] + }; + + const { data } = await axios.post<{ task_id: number }>(endpoint, payload, { + headers: { + 'Client-Id': clientId, + 'Api-Key': apiKey, + 'Content-Type': 'application/json' + }, + timeout: 60000 + }); + + return { + task_id: data.task_id + }; +} diff --git a/modules/tool/packages/ozon/children/productAttrUpdate/test/index.test.ts b/modules/tool/packages/ozon/children/productAttrUpdate/test/index.test.ts new file mode 100644 index 00000000..b70e289f --- /dev/null +++ b/modules/tool/packages/ozon/children/productAttrUpdate/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/ozon/children/productImport/config.ts b/modules/tool/packages/ozon/children/productImport/config.ts new file mode 100644 index 00000000..d81c7615 --- /dev/null +++ b/modules/tool/packages/ozon/children/productImport/config.ts @@ -0,0 +1,178 @@ +import { defineTool } from '@tool/type'; +import { FlowNodeInputTypeEnum, WorkflowIOValueTypeEnum } from '@tool/type/fastgpt'; + +export default defineTool({ + name: { + 'zh-CN': '创建或更新商品', + en: 'Create or update product' + }, + description: { + 'zh-CN': '创建商品并更新有关商品信息的方法。', + en: 'Create or update product' + }, + toolDescription: + 'Create or update a product with metadata (ID, name, attributes, images, barcode, pricing, dimensions) and return a task_id for status tracking.', + versionList: [ + { + value: '0.1.0', + description: 'Default version', + inputs: [ + { + key: 'offer_id', + label: '商品ID', + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + required: true, + valueType: WorkflowIOValueTypeEnum.string + }, + { + key: 'name', + label: '商品名称', + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + required: true, + valueType: WorkflowIOValueTypeEnum.string + }, + { + key: 'attributes', + label: '商品属性', + description: + '商品属性,格式为JSON数组,每个元素为{"complex_id": 0, "id": 5076, "values": [{"dictionary_value_id": 971082156, "value": "麦克风架"}]}', + renderTypeList: [FlowNodeInputTypeEnum.JSONEditor, FlowNodeInputTypeEnum.reference], + required: true, + valueType: WorkflowIOValueTypeEnum.arrayAny, + defaultValue: + '[{"complex_id": 0, "id": 5076, "values": [{"dictionary_value_id": 971082156, "value": "麦克风架"}]}]' + }, + { + key: 'images', + label: '商品图片', + description: + '商品图片,格式为JSON数组,每个元素为图片URL,图片需要上传到对象存储桶中公开可读', + renderTypeList: [FlowNodeInputTypeEnum.JSONEditor, FlowNodeInputTypeEnum.reference], + required: true, + valueType: WorkflowIOValueTypeEnum.arrayString + }, + { + key: 'barcode', + label: '商品条码', + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string + }, + { + key: 'description_category_id', + label: '商品描述分类ID', + renderTypeList: [FlowNodeInputTypeEnum.numberInput, FlowNodeInputTypeEnum.reference], + required: true, + valueType: WorkflowIOValueTypeEnum.number + }, + { + key: 'type_id', + label: '商品类型ID', + renderTypeList: [FlowNodeInputTypeEnum.numberInput, FlowNodeInputTypeEnum.reference], + required: true, + valueType: WorkflowIOValueTypeEnum.number + }, + { + key: 'currency_code', + label: '货币代码', + renderTypeList: [FlowNodeInputTypeEnum.select, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string, + list: [ + { label: 'RUB', value: 'RUB' }, + { label: 'BYN', value: 'BYN' }, + { label: 'KZT', value: 'KZT' }, + { label: 'EUR', value: 'EUR' }, + { label: 'USD', value: 'USD' }, + { label: 'CNY', value: 'CNY' } + ] + }, + { + key: 'price', + label: '商品价格', + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + required: true, + valueType: WorkflowIOValueTypeEnum.string + }, + { + key: 'old_price', + label: '划线价', + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string + }, + { + key: 'vat', + label: '增值税税率', + renderTypeList: [FlowNodeInputTypeEnum.select, FlowNodeInputTypeEnum.reference], + required: true, + valueType: WorkflowIOValueTypeEnum.string, + list: [ + { label: '0', value: '0' }, + { label: '0.05', value: '0.05' }, + { label: '0.07', value: '0.07' }, + { label: '0.1', value: '0.1' }, + { label: '0.2', value: '0.2' } + ] + }, + { + key: 'dimension_unit', + label: '商品尺寸单位', + renderTypeList: [FlowNodeInputTypeEnum.select, FlowNodeInputTypeEnum.reference], + required: true, + valueType: WorkflowIOValueTypeEnum.string, + list: [ + { label: 'mm', value: 'mm' }, + { label: 'cm', value: 'cm' }, + { label: 'in', value: 'in' } + ] + }, + { + key: 'depth', + label: '商品深度', + renderTypeList: [FlowNodeInputTypeEnum.numberInput, FlowNodeInputTypeEnum.reference], + required: true, + valueType: WorkflowIOValueTypeEnum.number + }, + { + key: 'height', + label: '商品高度', + renderTypeList: [FlowNodeInputTypeEnum.numberInput, FlowNodeInputTypeEnum.reference], + required: true, + valueType: WorkflowIOValueTypeEnum.number + }, + { + key: 'width', + label: '商品宽度', + renderTypeList: [FlowNodeInputTypeEnum.numberInput, FlowNodeInputTypeEnum.reference], + required: true, + valueType: WorkflowIOValueTypeEnum.number + }, + { + key: 'weight_unit', + label: '商品重量单位', + renderTypeList: [FlowNodeInputTypeEnum.select, FlowNodeInputTypeEnum.reference], + required: true, + valueType: WorkflowIOValueTypeEnum.string, + list: [ + { label: 'g', value: 'g' }, + { label: 'kg', value: 'kg' }, + { label: 'lb', value: 'lb' } + ] + }, + { + key: 'weight', + label: '商品重量', + renderTypeList: [FlowNodeInputTypeEnum.numberInput, FlowNodeInputTypeEnum.reference], + required: true, + valueType: WorkflowIOValueTypeEnum.number + } + ], + outputs: [ + { + valueType: WorkflowIOValueTypeEnum.number, + key: 'task_id', + label: '任务ID', + description: '任务 ID' + } + ] + } + ] +}); diff --git a/modules/tool/packages/ozon/children/productImport/index.ts b/modules/tool/packages/ozon/children/productImport/index.ts new file mode 100644 index 00000000..d698ed48 --- /dev/null +++ b/modules/tool/packages/ozon/children/productImport/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/ozon/children/productImport/src/index.ts b/modules/tool/packages/ozon/children/productImport/src/index.ts new file mode 100644 index 00000000..647a4e48 --- /dev/null +++ b/modules/tool/packages/ozon/children/productImport/src/index.ts @@ -0,0 +1,91 @@ +import axios from 'axios'; +import { z } from 'zod'; + +export const InputType = z.object({ + clientId: z.string(), + apiKey: z.string(), + offer_id: z.string(), + name: z.string(), + attributes: z.array(z.any()), + images: z.array(z.string()), + barcode: z.string().optional(), + description_category_id: z.number(), + type_id: z.number(), + currency_code: z.enum(['RUB', 'BYN', 'KZT', 'EUR', 'USD', 'CNY']).default('RUB'), + price: z.string(), + old_price: z.string().optional(), + vat: z.enum(['0', '0.05', '0.07', '0.1', '0.2']), + dimension_unit: z.enum(['mm', 'cm', 'in']).default('cm'), + depth: z.number(), + height: z.number(), + width: z.number(), + weight_unit: z.enum(['g', 'kg', 'lb']).default('kg'), + weight: z.number() +}); + +export const OutputType = z.object({ + task_id: z.number() +}); + +export async function tool(props: z.infer): Promise> { + const { + clientId, + apiKey, + offer_id, + name, + attributes, + images, + barcode, + description_category_id, + type_id, + currency_code, + price, + old_price, + dimension_unit, + depth, + height, + width, + weight_unit, + weight, + vat + } = props; + + const endpoint = 'https://api-seller.ozon.ru/v3/product/import'; + + const payload = { + items: [ + { + offer_id, + name, + attributes, + images: images.map((url) => ({ url })), + barcode, + description_category_id, + type_id, + currency_code, + price, + old_price, + vat, + dimension_unit, + depth, + height, + width, + weight_unit, + weight + } + ] + }; + + const { data } = await axios.post<{ task_id: number }>(endpoint, payload, { + headers: { + 'Client-Id': clientId, + 'Api-Key': apiKey, + 'Content-Type': 'application/json' + }, + timeout: 60000 + }); + + return { + task_id: data.task_id + }; +} diff --git a/modules/tool/packages/ozon/children/productImport/test/index.test.ts b/modules/tool/packages/ozon/children/productImport/test/index.test.ts new file mode 100644 index 00000000..b70e289f --- /dev/null +++ b/modules/tool/packages/ozon/children/productImport/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/ozon/children/productImportBySku/config.ts b/modules/tool/packages/ozon/children/productImportBySku/config.ts new file mode 100644 index 00000000..6b8e8cb4 --- /dev/null +++ b/modules/tool/packages/ozon/children/productImportBySku/config.ts @@ -0,0 +1,113 @@ +import { defineTool } from '@tool/type'; +import { FlowNodeInputTypeEnum, WorkflowIOValueTypeEnum } from '@tool/type/fastgpt'; + +export default defineTool({ + name: { + 'zh-CN': '通过SKU创建商品', + en: 'Create product by SKU' + }, + description: { + 'zh-CN': + '该方法会创建指定SKU的商品卡片副本。 如果卖家禁止复制, 将无法创建卡片副本。无法通过SKU更新商品。', + en: 'Create a product with metadata (ID, name, attributes, images, barcode, pricing, dimensions) and return a task_id for status tracking.' + }, + toolDescription: + 'Create a product with metadata (ID, name, attributes, images, barcode, pricing, dimensions) and return a task_id for status tracking.', + versionList: [ + { + value: '0.1.0', + description: 'Default version', + inputs: [ + { + key: 'sku', + label: 'SKU', + description: '在 Ozon 系统中的商品 ID — SKU', + required: true, + renderTypeList: [FlowNodeInputTypeEnum.numberInput, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.number + }, + { + key: 'name', + label: '商品名称', + description: '商品名称(不超过 500 字符)', + required: true, + maxLength: 500, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string + }, + { + key: 'offer_id', + label: '商品ID', + description: '在卖家系统中的商品ID — 货号(不超过 50 字符)', + required: true, + maxLength: 50, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string + }, + { + key: 'currency_code', + label: '货币代码', + description: + '价格显示的货币,需与个人中心设置一致。默认 RUB。若设置为人民币请传 CNY,否则会报错', + required: false, + defaultValue: 'RUB', + renderTypeList: [FlowNodeInputTypeEnum.select, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string, + list: [ + { label: 'RUB', value: 'RUB' }, + { label: 'BYN', value: 'BYN' }, + { label: 'KZT', value: 'KZT' }, + { label: 'EUR', value: 'EUR' }, + { label: 'USD', value: 'USD' }, + { label: 'CNY', value: 'CNY' } + ] + }, + { + key: 'price', + label: '价格', + description: + '含折扣的商品价格。无折扣时与 old_price 相同。以卢布表示,点号为小数分隔符,最多两位小数', + required: true, + renderTypeList: [FlowNodeInputTypeEnum.numberInput, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.number + }, + { + key: 'old_price', + label: '划线价格', + description: '折扣前的价格(商品卡片上划掉)。以卢布表示,点号为小数分隔符,最多两位小数', + required: false, + renderTypeList: [FlowNodeInputTypeEnum.numberInput, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.number + }, + { + key: 'vat', + label: '增值税税率', + renderTypeList: [FlowNodeInputTypeEnum.select, FlowNodeInputTypeEnum.reference], + required: true, + valueType: WorkflowIOValueTypeEnum.string, + list: [ + { label: '0', value: '0' }, + { label: '0.05', value: '0.05' }, + { label: '0.07', value: '0.07' }, + { label: '0.1', value: '0.1' }, + { label: '0.2', value: '0.2' } + ] + } + ], + outputs: [ + { + valueType: WorkflowIOValueTypeEnum.string, + key: 'task_id', + label: '任务ID', + description: '任务 ID' + }, + { + valueType: WorkflowIOValueTypeEnum.arrayAny, + key: 'unmatched_sku_list', + label: '未匹配SKU列表', + description: '未匹配的SKU列表' + } + ] + } + ] +}); diff --git a/modules/tool/packages/ozon/children/productImportBySku/index.ts b/modules/tool/packages/ozon/children/productImportBySku/index.ts new file mode 100644 index 00000000..d698ed48 --- /dev/null +++ b/modules/tool/packages/ozon/children/productImportBySku/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/ozon/children/productImportBySku/src/index.ts b/modules/tool/packages/ozon/children/productImportBySku/src/index.ts new file mode 100644 index 00000000..b081bcf5 --- /dev/null +++ b/modules/tool/packages/ozon/children/productImportBySku/src/index.ts @@ -0,0 +1,59 @@ +import axios from 'axios'; +import { z } from 'zod'; + +export const InputType = z + .object({ + clientId: z.string(), + apiKey: z.string(), + sku: z.number(), + name: z.string(), + offer_id: z.string(), + currency_code: z.enum(['RUB', 'BYN', 'KZT', 'EUR', 'USD', 'CNY']).optional().default('RUB'), + price: z.number(), + old_price: z.number().optional(), + vat: z.enum(['0', '0.05', '0.07', '0.1', '0.2']).optional().default('0') + }) + .refine((v) => !!v.sku && !!v.name && !!v.offer_id && !!v.price && !!v.vat, { + message: 'sku, name, offer_id, price and vat are required.' + }); + +export const OutputType = z.object({ + task_id: z.string(), + unmatched_sku_list: z.array(z.any()).default([]) +}); + +export async function tool(props: z.infer): Promise> { + const { clientId, apiKey } = props; + + const payload = { + items: [ + { + sku: props.sku, + offer_id: props.offer_id, + name: props.name, + currency_code: props.currency_code ?? 'RUB', + price: String(props.price), + old_price: props.old_price !== undefined ? String(props.old_price) : undefined, + vat: typeof props.vat === 'string' ? Number(props.vat) : props.vat + } + ] + }; + + const endpoint = 'https://api-seller.ozon.ru/v1/product/import-by-sku'; + + const { data } = await axios.post<{ + result: { task_id: number; unmatched_sku_list: any[] }; + }>(endpoint, payload, { + headers: { + 'Client-Id': clientId, + 'Api-Key': apiKey, + 'Content-Type': 'application/json' + }, + timeout: 60000 + }); + + return { + task_id: String(data.result.task_id), + unmatched_sku_list: data.result.unmatched_sku_list || [] + }; +} diff --git a/modules/tool/packages/ozon/children/productImportBySku/test/index.test.ts b/modules/tool/packages/ozon/children/productImportBySku/test/index.test.ts new file mode 100644 index 00000000..b70e289f --- /dev/null +++ b/modules/tool/packages/ozon/children/productImportBySku/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/ozon/children/productImportInfo/config.ts b/modules/tool/packages/ozon/children/productImportInfo/config.ts new file mode 100644 index 00000000..351c3161 --- /dev/null +++ b/modules/tool/packages/ozon/children/productImportInfo/config.ts @@ -0,0 +1,37 @@ +import { defineTool } from '@tool/type'; +import { FlowNodeInputTypeEnum, WorkflowIOValueTypeEnum } from '@tool/type/fastgpt'; + +export default defineTool({ + name: { + 'zh-CN': '查询任务状态', + en: 'Query task status' + }, + description: { + 'zh-CN': '查询任务状态', + en: 'Query task status' + }, + toolDescription: + 'Check the status of a product import/update task by task_id, returning the current state (success or failure) and any error details.', + versionList: [ + { + value: '0.1.0', + description: 'Default version', + inputs: [ + { + key: 'task_id', + label: '任务ID', + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string + } + ], + outputs: [ + { + valueType: WorkflowIOValueTypeEnum.object, + key: 'result', + label: '结果', + description: '任务查询结果' + } + ] + } + ] +}); diff --git a/modules/tool/packages/ozon/children/productImportInfo/index.ts b/modules/tool/packages/ozon/children/productImportInfo/index.ts new file mode 100644 index 00000000..d698ed48 --- /dev/null +++ b/modules/tool/packages/ozon/children/productImportInfo/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/ozon/children/productImportInfo/src/index.ts b/modules/tool/packages/ozon/children/productImportInfo/src/index.ts new file mode 100644 index 00000000..d9b75d0d --- /dev/null +++ b/modules/tool/packages/ozon/children/productImportInfo/src/index.ts @@ -0,0 +1,57 @@ +import axios from 'axios'; +import { z } from 'zod'; + +export const InputType = z.object({ + clientId: z.string(), + apiKey: z.string(), + task_id: z.string() +}); + +export const OutputType = z.object({ + result: z.object({ + items: z.array( + z.object({ + offer_id: z.string(), + product_id: z.number(), + status: z.string(), + errors: z.array(z.any()).default([]) + }) + ), + total: z.number() + }) +}); + +export async function tool({ + clientId, + apiKey, + task_id +}: z.infer): Promise> { + const endpoint = 'https://api-seller.ozon.ru/v1/product/import/info'; + + const payload = { + task_id: Number(task_id) + }; + + const { data } = await axios.post<{ + result: { + items: { + offer_id: string; + product_id: number; + status: string; + errors: any[]; + }[]; + total: number; + }; + }>(endpoint, payload, { + headers: { + 'Client-Id': clientId, + 'Api-Key': apiKey, + 'Content-Type': 'application/json' + }, + timeout: 60000 + }); + + return { + result: data.result + }; +} diff --git a/modules/tool/packages/ozon/children/productImportInfo/test/index.test.ts b/modules/tool/packages/ozon/children/productImportInfo/test/index.test.ts new file mode 100644 index 00000000..b70e289f --- /dev/null +++ b/modules/tool/packages/ozon/children/productImportInfo/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/ozon/children/productImportPrices/config.ts b/modules/tool/packages/ozon/children/productImportPrices/config.ts new file mode 100644 index 00000000..3166a3c1 --- /dev/null +++ b/modules/tool/packages/ozon/children/productImportPrices/config.ts @@ -0,0 +1,161 @@ +import { defineTool } from '@tool/type'; +import { FlowNodeInputTypeEnum, WorkflowIOValueTypeEnum } from '@tool/type/fastgpt'; + +export default defineTool({ + name: { + 'zh-CN': '更新价格', + en: 'Update product prices' + }, + description: { + 'zh-CN': '允许更改一个或多个商品的价格。每个商品的价格每小时不能更新超过 10 次。', + en: 'Update prices for one or more products (max 10 updates per hour per product).' + }, + toolDescription: + "POST /v1/product/import/prices. Update a product's price, old_price, currency, VAT and related pricing strategy flags. If both offer_id and product_id are provided, offer_id takes precedence.", + versionList: [ + { + value: '0.1.0', + description: 'Default version', + inputs: [ + { + key: 'offer_id', + label: '商品编号', + description: + '卖家系统中的商品编号 — 商品代码。与 product_id 二选一,若同时传递则以 offer_id 为准。', + required: false, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string + }, + { + key: 'product_id', + label: '商品标识符', + description: '卖家系统中的商品标识符 — product_id。与 offer_id 二选一。', + required: true, + renderTypeList: [FlowNodeInputTypeEnum.numberInput, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.number + }, + { + key: 'currency_code', + label: '货币代码', + description: + '价格货币,需与个人主页设置一致,默认 RUB。示例:RUB、BYN、KZT、EUR、USD、CNY。', + required: false, + defaultValue: 'RUB', + renderTypeList: [FlowNodeInputTypeEnum.select, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string, + list: [ + { label: 'RUB', value: 'RUB' }, + { label: 'BYN', value: 'BYN' }, + { label: 'KZT', value: 'KZT' }, + { label: 'EUR', value: 'EUR' }, + { label: 'USD', value: 'USD' }, + { label: 'CNY', value: 'CNY' } + ] + }, + { + key: 'price', + label: '价格', + description: '商品当前价格(含折扣)。当 old_price > 0 时需满足最小差额规则。', + required: true, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string + }, + { + key: 'old_price', + label: '划线价', + description: '折扣前价格(商品卡上划掉)。无折扣请传 "0",并将当前价格提交到 price。', + required: false, + defaultValue: '0', + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string + }, + { + key: 'vat', + label: '增值税税率', + description: '当前有效的出价值。可选:0、0.05、0.07、0.1、0.2。', + required: false, + renderTypeList: [FlowNodeInputTypeEnum.select, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string, + list: [ + { label: '0', value: '0' }, + { label: '0.05', value: '0.05' }, + { label: '0.07', value: '0.07' }, + { label: '0.1', value: '0.1' }, + { label: '0.2', value: '0.2' } + ] + }, + { + key: 'net_price', + label: '成本价', + description: '产品成本价。', + required: false, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string + }, + { + key: 'min_price', + label: '最低价', + description: '应用促销活动后的最低价格。启用自动应用活动或价格策略时需设置。', + required: false, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string + }, + { + key: 'manage_elastic_boosting_through_price', + label: '弹性提升按价格管理', + description: + 'true:若 price 符合条件自动加入或提升促销;false:修改 price 不影响参与状态。', + required: false, + defaultValue: false, + renderTypeList: [FlowNodeInputTypeEnum.switch], + valueType: WorkflowIOValueTypeEnum.boolean + }, + { + key: 'min_price_for_auto_actions_enabled', + label: '促销最低价参与', + description: 'true:Ozon 在添加到促销时考虑最低价;未传递则状态不变。', + required: false, + defaultValue: false, + renderTypeList: [FlowNodeInputTypeEnum.switch], + valueType: WorkflowIOValueTypeEnum.boolean + }, + { + key: 'auto_action_enabled', + label: '自动应用活动', + description: 'UNKNOWN 不做更改;ENABLED 启用;DISABLED 关闭。', + required: false, + defaultValue: 'UNKNOWN', + renderTypeList: [FlowNodeInputTypeEnum.select, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string, + list: [ + { label: 'UNKNOWN', value: 'UNKNOWN' }, + { label: 'ENABLED', value: 'ENABLED' }, + { label: 'DISABLED', value: 'DISABLED' } + ] + }, + { + key: 'price_strategy_enabled', + label: '自动应用价格策略', + description: 'UNKNOWN 不做更改;ENABLED 启用;DISABLED 关闭并从策略移除。', + required: false, + defaultValue: 'UNKNOWN', + renderTypeList: [FlowNodeInputTypeEnum.select, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string, + list: [ + { label: 'UNKNOWN', value: 'UNKNOWN' }, + { label: 'ENABLED', value: 'ENABLED' }, + { label: 'DISABLED', value: 'DISABLED' } + ] + } + ], + outputs: [ + { + valueType: WorkflowIOValueTypeEnum.arrayObject, + key: 'result', + label: '价格更新结果', + description: '返回每个商品的更新状态(product_id、offer_id、updated、errors)' + } + ] + } + ] +}); diff --git a/modules/tool/packages/ozon/children/productImportPrices/index.ts b/modules/tool/packages/ozon/children/productImportPrices/index.ts new file mode 100644 index 00000000..d698ed48 --- /dev/null +++ b/modules/tool/packages/ozon/children/productImportPrices/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/ozon/children/productImportPrices/src/index.ts b/modules/tool/packages/ozon/children/productImportPrices/src/index.ts new file mode 100644 index 00000000..7840f2c8 --- /dev/null +++ b/modules/tool/packages/ozon/children/productImportPrices/src/index.ts @@ -0,0 +1,90 @@ +import axios from 'axios'; +import { z } from 'zod'; + +export const InputType = z.object({ + clientId: z.string(), + apiKey: z.string(), + offer_id: z.string().optional(), + product_id: z.number(), + currency_code: z.enum(['RUB', 'BYN', 'KZT', 'EUR', 'USD', 'CNY']).optional().default('RUB'), + price: z.string(), + old_price: z.string().optional().default('0'), + vat: z.enum(['0', '0.05', '0.07', '0.1', '0.2']).optional(), + net_price: z.string().optional(), + min_price: z.string().optional(), + manage_elastic_boosting_through_price: z.boolean().optional().default(false), + min_price_for_auto_actions_enabled: z.boolean().optional().default(false), + auto_action_enabled: z.enum(['UNKNOWN', 'ENABLED', 'DISABLED']).optional().default('UNKNOWN'), + price_strategy_enabled: z.enum(['UNKNOWN', 'ENABLED', 'DISABLED']).optional().default('UNKNOWN') +}); + +export const OutputType = z.object({ + result: z.array( + z.object({ + product_id: z.number(), + offer_id: z.string(), + updated: z.boolean(), + errors: z.array(z.any()).default([]) + }) + ) +}); + +export async function tool(props: z.infer): Promise> { + const { + clientId, + apiKey, + offer_id, + product_id, + currency_code, + price, + old_price, + vat, + net_price, + min_price, + manage_elastic_boosting_through_price, + min_price_for_auto_actions_enabled, + auto_action_enabled, + price_strategy_enabled + } = props; + + const endpoint = 'https://api-seller.ozon.ru/v1/product/import/prices'; + + const payload = { + prices: [ + { + ...(offer_id ? { offer_id } : {}), + product_id, + currency_code, + price, + old_price, + vat, + net_price, + min_price, + manage_elastic_boosting_through_price, + min_price_for_auto_actions_enabled, + auto_action_enabled, + price_strategy_enabled + } + ] + }; + + const { data } = await axios.post<{ + result: { + product_id: number; + offer_id: string; + updated: boolean; + errors: any[]; + }[]; + }>(endpoint, payload, { + headers: { + 'Client-Id': clientId, + 'Api-Key': apiKey, + 'Content-Type': 'application/json' + }, + timeout: 60000 + }); + + return { + result: data.result || [] + }; +} diff --git a/modules/tool/packages/ozon/children/productImportPrices/test/index.test.ts b/modules/tool/packages/ozon/children/productImportPrices/test/index.test.ts new file mode 100644 index 00000000..b70e289f --- /dev/null +++ b/modules/tool/packages/ozon/children/productImportPrices/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/ozon/children/productInfoStocksByWarehouseFbs/config.ts b/modules/tool/packages/ozon/children/productInfoStocksByWarehouseFbs/config.ts new file mode 100644 index 00000000..422ca560 --- /dev/null +++ b/modules/tool/packages/ozon/children/productInfoStocksByWarehouseFbs/config.ts @@ -0,0 +1,55 @@ +import { defineTool } from '@tool/type'; +import { FlowNodeInputTypeEnum, WorkflowIOValueTypeEnum } from '@tool/type/fastgpt'; + +export default defineTool({ + name: { + 'zh-CN': '卖家库存余额(FBS 按仓库)', + en: 'Seller stock balance by warehouse (FBS)' + }, + description: { + 'zh-CN': '在请求中传递 offer_id 或 sku。若同时传递两者,将只使用 sku。', + en: 'Pass either offer_id or sku. If both provided, sku is used.' + }, + toolDescription: + 'Query stock quantities by warehouse for FBS via /v1/product/info/stocks-by-warehouse/fbs.', + versionList: [ + { + value: '0.1.0', + description: 'Default version', + inputs: [ + { + key: 'id_type', + label: '标识类型', + description: '选择使用 sku 或 offer_id 作为查询依据', + renderTypeList: [FlowNodeInputTypeEnum.select, FlowNodeInputTypeEnum.reference], + selectedTypeIndex: 0, + valueType: WorkflowIOValueTypeEnum.string, + required: true, + defaultValue: 'sku', + list: [ + { label: 'sku', value: 'sku' }, + { label: 'offer_id', value: 'offer_id' } + ] + }, + { + key: 'ids', + label: '标识列表', + description: '根据选择的标识类型填写对应的值(JSON 数组,字符串 int64)', + renderTypeList: [FlowNodeInputTypeEnum.JSONEditor, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.arrayString, + required: true, + defaultValue: '["123456789012"]' + } + ], + outputs: [ + { + valueType: WorkflowIOValueTypeEnum.arrayObject, + key: 'result', + label: '库存明细', + description: + '库存商品的数量(包含 sku、offer_id、present、product_id、reserved、warehouse_id、warehouse_name)' + } + ] + } + ] +}); diff --git a/modules/tool/packages/ozon/children/productInfoStocksByWarehouseFbs/index.ts b/modules/tool/packages/ozon/children/productInfoStocksByWarehouseFbs/index.ts new file mode 100644 index 00000000..d698ed48 --- /dev/null +++ b/modules/tool/packages/ozon/children/productInfoStocksByWarehouseFbs/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/ozon/children/productInfoStocksByWarehouseFbs/src/index.ts b/modules/tool/packages/ozon/children/productInfoStocksByWarehouseFbs/src/index.ts new file mode 100644 index 00000000..770d9a2b --- /dev/null +++ b/modules/tool/packages/ozon/children/productInfoStocksByWarehouseFbs/src/index.ts @@ -0,0 +1,55 @@ +import axios from 'axios'; +import { z } from 'zod'; + +export const InputType = z.object({ + clientId: z.string(), + apiKey: z.string(), + id_type: z.enum(['sku', 'offer_id']), + ids: z.array(z.string()) +}); + +export const OutputType = z.object({ + result: z.array( + z.object({ + sku: z.number().optional(), + offer_id: z.string().optional(), + present: z.number(), + product_id: z.number(), + reserved: z.number(), + warehouse_id: z.number(), + warehouse_name: z.string() + }) + ) +}); + +export async function tool(props: z.infer): Promise> { + const { clientId, apiKey, id_type, ids } = props; + + const endpoint = 'https://api-seller.ozon.ru/v1/product/info/stocks-by-warehouse/fbs'; + + const payload: { sku?: string[]; offer_id?: string[] } = + id_type === 'sku' ? { sku: ids } : { offer_id: ids }; + + const { data } = await axios.post<{ + result: { + sku?: number; + offer_id?: string; + present: number; + product_id: number; + reserved: number; + warehouse_id: number; + warehouse_name: string; + }[]; + }>(endpoint, payload, { + headers: { + 'Client-Id': clientId, + 'Api-Key': apiKey, + 'Content-Type': 'application/json' + }, + timeout: 60000 + }); + + return { + result: data.result || [] + }; +} diff --git a/modules/tool/packages/ozon/children/productInfoStocksByWarehouseFbs/test/index.test.ts b/modules/tool/packages/ozon/children/productInfoStocksByWarehouseFbs/test/index.test.ts new file mode 100644 index 00000000..b70e289f --- /dev/null +++ b/modules/tool/packages/ozon/children/productInfoStocksByWarehouseFbs/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/ozon/children/productStocks/config.ts b/modules/tool/packages/ozon/children/productStocks/config.ts new file mode 100644 index 00000000..88ffa91f --- /dev/null +++ b/modules/tool/packages/ozon/children/productStocks/config.ts @@ -0,0 +1,62 @@ +import { defineTool } from '@tool/type'; +import { FlowNodeInputTypeEnum, WorkflowIOValueTypeEnum } from '@tool/type/fastgpt'; + +export default defineTool({ + name: { + 'zh-CN': '更新库存商品的数量', + en: 'Update product stock' + }, + description: { + 'zh-CN': '可以改变一个商品的库存数量信息。', + en: "Update the stock quantity of a product in the seller's inventory." + }, + toolDescription: "Update the stock quantity of a product in the seller's inventory.", + versionList: [ + { + value: '0.1.0', + description: 'Default version', + inputs: [ + { + key: 'offer_id', + label: '商品编号', + description: '在卖家系统中的商品编号 — 商品代码', + required: false, + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string + }, + { + key: 'product_id', + label: '商品标识符', + description: '在卖家系统中的商品标识符 — product_id', + required: true, + renderTypeList: [FlowNodeInputTypeEnum.numberInput, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.number + }, + { + key: 'stock', + label: '库存数量', + description: '扣除预留库存后的可售商品数量', + required: true, + renderTypeList: [FlowNodeInputTypeEnum.numberInput, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.number + }, + { + key: 'warehouse_id', + label: '仓库编号', + description: '仓库编号,可通过 /v1/warehouse/list 获取', + required: true, + renderTypeList: [FlowNodeInputTypeEnum.numberInput, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.number + } + ], + outputs: [ + { + valueType: WorkflowIOValueTypeEnum.arrayObject, + key: 'result', + label: '更新结果', + description: '库存更新结果' + } + ] + } + ] +}); diff --git a/modules/tool/packages/ozon/children/productStocks/index.ts b/modules/tool/packages/ozon/children/productStocks/index.ts new file mode 100644 index 00000000..d698ed48 --- /dev/null +++ b/modules/tool/packages/ozon/children/productStocks/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/ozon/children/productStocks/src/index.ts b/modules/tool/packages/ozon/children/productStocks/src/index.ts new file mode 100644 index 00000000..e30ca261 --- /dev/null +++ b/modules/tool/packages/ozon/children/productStocks/src/index.ts @@ -0,0 +1,61 @@ +import axios from 'axios'; +import { z } from 'zod'; + +export const InputType = z.object({ + clientId: z.string(), + apiKey: z.string(), + offer_id: z.string().optional(), + product_id: z.number(), + stock: z.number(), + warehouse_id: z.number() +}); + +export const OutputType = z.object({ + result: z.array( + z.object({ + warehouse_id: z.number(), + product_id: z.number(), + offer_id: z.string(), + updated: z.boolean(), + errors: z.array(z.any()).default([]) + }) + ) +}); + +export async function tool(props: z.infer): Promise> { + const { clientId, apiKey, offer_id, product_id, stock, warehouse_id } = props; + + const endpoint = 'https://api-seller.ozon.ru/v2/products/stocks'; + + const payload = { + stocks: [ + { + ...(offer_id ? { offer_id } : {}), + product_id, + stock, + warehouse_id + } + ] + }; + + const { data } = await axios.post<{ + result: { + warehouse_id: number; + product_id: number; + offer_id: string; + updated: boolean; + errors: any[]; + }[]; + }>(endpoint, payload, { + headers: { + 'Client-Id': clientId, + 'Api-Key': apiKey, + 'Content-Type': 'application/json' + }, + timeout: 60000 + }); + + return { + result: data.result || [] + }; +} diff --git a/modules/tool/packages/ozon/children/productStocks/test/index.test.ts b/modules/tool/packages/ozon/children/productStocks/test/index.test.ts new file mode 100644 index 00000000..b70e289f --- /dev/null +++ b/modules/tool/packages/ozon/children/productStocks/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/ozon/children/warehouseList/config.ts b/modules/tool/packages/ozon/children/warehouseList/config.ts new file mode 100644 index 00000000..b21f81f8 --- /dev/null +++ b/modules/tool/packages/ozon/children/warehouseList/config.ts @@ -0,0 +1,29 @@ +import { defineTool } from '@tool/type'; +import { WorkflowIOValueTypeEnum } from '@tool/type/fastgpt'; + +export default defineTool({ + name: { + 'zh-CN': '仓库清单', + en: 'Warehouse List' + }, + description: { + 'zh-CN': '方法返回 FBS 和 rFBS 仓库列表。获取 FBO 仓库请使用 /v1/cluster/list。', + en: 'Returns the list of FBS and rFBS warehouses. Use /v1/cluster/list for FBO.' + }, + toolDescription: 'List FBS and rFBS warehouses via /v1/warehouse/list.', + versionList: [ + { + value: '0.1.0', + description: 'Default version', + inputs: [], + outputs: [ + { + valueType: WorkflowIOValueTypeEnum.arrayObject, + key: 'result', + label: '仓库清单', + description: '仓库列表(包含名称、ID、状态、工作日等)' + } + ] + } + ] +}); diff --git a/modules/tool/packages/ozon/children/warehouseList/index.ts b/modules/tool/packages/ozon/children/warehouseList/index.ts new file mode 100644 index 00000000..d698ed48 --- /dev/null +++ b/modules/tool/packages/ozon/children/warehouseList/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/ozon/children/warehouseList/src/index.ts b/modules/tool/packages/ozon/children/warehouseList/src/index.ts new file mode 100644 index 00000000..a1b585cd --- /dev/null +++ b/modules/tool/packages/ozon/children/warehouseList/src/index.ts @@ -0,0 +1,71 @@ +import axios from 'axios'; +import { z } from 'zod'; + +export const InputType = z.object({ + clientId: z.string(), + apiKey: z.string() +}); + +export const OutputType = z.object({ + result: z.array( + z.object({ + has_entrusted_acceptance: z.boolean().optional(), + is_rfbs: z.boolean().optional(), + name: z.string(), + warehouse_id: z.number(), + can_print_act_in_advance: z.boolean().optional(), + first_mile_type: z.object({}).passthrough().optional(), + has_postings_limit: z.boolean().optional(), + is_karantin: z.boolean().optional(), + is_kgt: z.boolean().optional(), + is_timetable_editable: z.boolean().optional(), + min_postings_limit: z.number().optional(), + postings_limit: z.number().optional(), + min_working_days: z.number().optional(), + status: z.string().optional(), + working_days: z.array(z.enum(['1', '2', '3', '4', '5', '6', '7'])).optional() + }) + ) +}); + +export async function tool({ + clientId, + apiKey +}: z.infer): Promise> { + const endpoint = 'https://api-seller.ozon.ru/v1/warehouse/list'; + + const { data } = await axios.post<{ + result: { + has_entrusted_acceptance?: boolean; + is_rfbs?: boolean; + name: string; + warehouse_id: number; + can_print_act_in_advance?: boolean; + first_mile_type?: Record; + has_postings_limit?: boolean; + is_karantin?: boolean; + is_kgt?: boolean; + is_timetable_editable?: boolean; + min_postings_limit?: number; + postings_limit?: number; + min_working_days?: number; + status?: string; + working_days?: ('1' | '2' | '3' | '4' | '5' | '6' | '7')[]; + }[]; + }>( + endpoint, + {}, + { + headers: { + 'Client-Id': clientId, + 'Api-Key': apiKey, + 'Content-Type': 'application/json' + }, + timeout: 60000 + } + ); + + return { + result: data.result || [] + }; +} diff --git a/modules/tool/packages/ozon/children/warehouseList/test/index.test.ts b/modules/tool/packages/ozon/children/warehouseList/test/index.test.ts new file mode 100644 index 00000000..b70e289f --- /dev/null +++ b/modules/tool/packages/ozon/children/warehouseList/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/ozon/config.ts b/modules/tool/packages/ozon/config.ts new file mode 100644 index 00000000..baa02c0b --- /dev/null +++ b/modules/tool/packages/ozon/config.ts @@ -0,0 +1,32 @@ +import { defineToolSet } from '@tool/type'; +import { ToolTagEnum } from '@tool/type/tags'; + +export default defineToolSet({ + name: { + 'zh-CN': 'Ozon Seller 商品管理', + en: 'Ozon Seller Product Management' + }, + tags: [ToolTagEnum.enum.tools], + description: { + 'zh-CN': '这是一个 Ozon Seller 商品管理工具集', + en: 'This is a tool set for Ozon Seller product management' + }, + toolDescription: + '`Ozon Seller product management toolset: supports product creation, product querying (list/details), and upload task status polling; suitable for batch import and automated validation.`', + secretInputConfig: [ + { + key: 'clientId', + label: 'client Id', + description: '可以在 https://www.ozon.ru/my/main 获取', + required: true, + inputType: 'secret' + }, + { + key: 'apiKey', + label: 'API Key', + description: '可以在 https://www.ozon.ru/my/main 获取', + required: true, + inputType: 'secret' + } + ] +}); diff --git a/modules/tool/packages/ozon/index.ts b/modules/tool/packages/ozon/index.ts new file mode 100644 index 00000000..22bccae7 --- /dev/null +++ b/modules/tool/packages/ozon/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/ozon/logo.svg b/modules/tool/packages/ozon/logo.svg new file mode 100644 index 00000000..bc64860e --- /dev/null +++ b/modules/tool/packages/ozon/logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/modules/tool/packages/ozon/package.json b/modules/tool/packages/ozon/package.json new file mode 100644 index 00000000..a290623c --- /dev/null +++ b/modules/tool/packages/ozon/package.json @@ -0,0 +1,18 @@ +{ + "name": "@fastgpt-plugins/tool-ozon", + "module": "index.ts", + "type": "module", + "scripts": { + "build": "bun ../../../../scripts/build.ts" + }, + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "zod": "^3.25.76", + "axios": "^1.12.2" + } +}