Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,117 changes: 1,117 additions & 0 deletions .claude/toolDesign.md

Large diffs are not rendered by default.

18 changes: 6 additions & 12 deletions modules/tool/packages/markdownTransform/test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
import { expect, test, describe, vi, beforeEach } from 'vitest';

// Mock modules BEFORE importing them (must be hoisted)
vi.mock('@tool/utils/uploadFile');
vi.mock('axios');

// Now import the modules
import tool from '..';
import * as uploadFileModule from '@tool/utils/uploadFile';

// Mock the uploadFile function
vi.mock('@tool/utils/uploadFile', () => ({
uploadFile: vi.fn()
}));

// Mock axios for image downloads
vi.mock('axios', () => ({
default: {
get: vi.fn()
}
}));

const mockUploadFile = vi.mocked(uploadFileModule.uploadFile);

// Get the axios mock with proper typing
Expand Down
40 changes: 40 additions & 0 deletions modules/tool/packages/redis/children/del/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { defineTool } from '@tool/type';
import { FlowNodeInputTypeEnum, WorkflowIOValueTypeEnum } from '@tool/type/fastgpt';

export default defineTool({
name: {
'zh-CN': '删除缓存',
en: 'Delete Cache'
},
description: {
'zh-CN': '从 Redis 删除缓存数据',
en: 'Delete cached data from Redis'
},
toolDescription: 'Delete a key and its value from Redis.',

versionList: [
{
value: '0.1.0',
description: 'Initial version',
inputs: [
{
key: 'key',
label: '缓存键',
description: 'Redis 键名',
required: true,
valueType: WorkflowIOValueTypeEnum.string,
renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference],
toolDescription: 'The Redis key to delete'
}
],
outputs: [
{
key: 'deleted',
label: '是否删除',
description: '键是否被删除 (如果键不存在则为 false)',
valueType: WorkflowIOValueTypeEnum.boolean
}
]
}
]
});
10 changes: 10 additions & 0 deletions modules/tool/packages/redis/children/del/index.ts
Original file line number Diff line number Diff line change
@@ -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
});
35 changes: 35 additions & 0 deletions modules/tool/packages/redis/children/del/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { z } from 'zod';
import { createRedisClient, handleRedisError } from '../../../client';

// 输入类型 (包含父级密钥)
export const InputType = z.object({
redisUrl: z.string().url('Invalid Redis URL format'),
key: z.string().min(1, 'Key cannot be empty')
});

// 输出类型
export const OutputType = z.object({
deleted: z.boolean()
});

export async function tool({
redisUrl,
key
}: z.infer<typeof InputType>): Promise<z.infer<typeof OutputType>> {
let client = null;

try {
client = await createRedisClient(redisUrl);
const count = await client.del(key);

return {
deleted: count > 0
};
} catch (error) {
return Promise.reject(handleRedisError(error));
} finally {
if (client) {
await client.quit();
}
}
}
61 changes: 61 additions & 0 deletions modules/tool/packages/redis/children/del/test/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { tool } from '../src';
import * as clientModule from '../../../client';

describe('DELETE Tool', () => {
const testRedisUrl = 'redis://localhost:6379';

beforeEach(() => {
vi.restoreAllMocks();
});

it('should delete an existing key', async () => {
const mockClient = {
del: vi.fn().mockResolvedValue(1), // 1 key deleted
quit: vi.fn().mockResolvedValue('OK')
};

vi.spyOn(clientModule, 'createRedisClient').mockResolvedValue(mockClient as any);

const result = await tool({
redisUrl: testRedisUrl,
key: 'test:del:key'
});

expect(result.deleted).toBe(true);
expect(mockClient.del).toHaveBeenCalledWith('test:del:key');
expect(mockClient.quit).toHaveBeenCalled();
});

it('should return false for non-existent key', async () => {
const mockClient = {
del: vi.fn().mockResolvedValue(0), // 0 keys deleted
quit: vi.fn().mockResolvedValue('OK')
};

vi.spyOn(clientModule, 'createRedisClient').mockResolvedValue(mockClient as any);

const result = await tool({
redisUrl: testRedisUrl,
key: 'non:existent:key'
});

expect(result.deleted).toBe(false);
expect(mockClient.del).toHaveBeenCalledWith('non:existent:key');
expect(mockClient.quit).toHaveBeenCalled();
});

it('should handle connection errors', async () => {
const connectionError = new Error('Redis connection refused');

vi.spyOn(clientModule, 'createRedisClient').mockRejectedValue(connectionError);
vi.spyOn(clientModule, 'handleRedisError').mockReturnValue('Redis connection refused');

await expect(
tool({
redisUrl: 'redis://invalid-host:6379',
key: 'test:key'
})
).rejects.toBe('Redis connection refused');
});
});
46 changes: 46 additions & 0 deletions modules/tool/packages/redis/children/get/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { defineTool } from '@tool/type';
import { FlowNodeInputTypeEnum, WorkflowIOValueTypeEnum } from '@tool/type/fastgpt';

export default defineTool({
name: {
'zh-CN': '获取缓存',
en: 'Get Cache'
},
description: {
'zh-CN': '从 Redis 获取缓存数据',
en: 'Get cached data from Redis'
},
toolDescription: 'Get cached value from Redis by key. Returns null if key does not exist.',

versionList: [
{
value: '0.1.0',
description: 'Initial version',
inputs: [
{
key: 'key',
label: '缓存键',
description: 'Redis 键名',
required: true,
valueType: WorkflowIOValueTypeEnum.string,
renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference],
toolDescription: 'The Redis key to retrieve'
}
],
outputs: [
{
key: 'value',
label: '缓存值',
description: '获取到的缓存数据,如果键不存在则为 null',
valueType: WorkflowIOValueTypeEnum.string
},
{
key: 'exists',
label: '是否存在',
description: '键是否存在',
valueType: WorkflowIOValueTypeEnum.boolean
}
]
}
]
});
10 changes: 10 additions & 0 deletions modules/tool/packages/redis/children/get/index.ts
Original file line number Diff line number Diff line change
@@ -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
});
37 changes: 37 additions & 0 deletions modules/tool/packages/redis/children/get/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { z } from 'zod';
import { createRedisClient, handleRedisError } from '../../../client';

// 输入类型 (包含父级密钥)
export const InputType = z.object({
redisUrl: z.string().url('Invalid Redis URL format'),
key: z.string().min(1, 'Key cannot be empty')
});

// 输出类型
export const OutputType = z.object({
value: z.string().nullable(),
exists: z.boolean()
});

export async function tool({
redisUrl,
key
}: z.infer<typeof InputType>): Promise<z.infer<typeof OutputType>> {
let client = null;

try {
client = await createRedisClient(redisUrl);
const value = await client.get(key);

return {
value,
exists: value !== null
};
} catch (error) {
return Promise.reject(handleRedisError(error));
} finally {
if (client) {
await client.quit();
}
}
}
63 changes: 63 additions & 0 deletions modules/tool/packages/redis/children/get/test/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { tool } from '../src';
import * as clientModule from '../../../client';

describe('GET Tool', () => {
const testRedisUrl = 'redis://localhost:6379';

beforeEach(() => {
vi.restoreAllMocks();
});

it('should get an existing value', async () => {
const mockClient = {
get: vi.fn().mockResolvedValue('test value'),
quit: vi.fn().mockResolvedValue('OK')
};

vi.spyOn(clientModule, 'createRedisClient').mockResolvedValue(mockClient as any);

const result = await tool({
redisUrl: testRedisUrl,
key: 'test:get:key'
});

expect(result.value).toBe('test value');
expect(result.exists).toBe(true);
expect(mockClient.get).toHaveBeenCalledWith('test:get:key');
expect(mockClient.quit).toHaveBeenCalled();
});

it('should return null for non-existent key', async () => {
const mockClient = {
get: vi.fn().mockResolvedValue(null),
quit: vi.fn().mockResolvedValue('OK')
};

vi.spyOn(clientModule, 'createRedisClient').mockResolvedValue(mockClient as any);

const result = await tool({
redisUrl: testRedisUrl,
key: 'non:existent:key'
});

expect(result.value).toBe(null);
expect(result.exists).toBe(false);
expect(mockClient.get).toHaveBeenCalledWith('non:existent:key');
expect(mockClient.quit).toHaveBeenCalled();
});

it('should handle connection errors', async () => {
const connectionError = new Error('Redis connection refused');

vi.spyOn(clientModule, 'createRedisClient').mockRejectedValue(connectionError);
vi.spyOn(clientModule, 'handleRedisError').mockReturnValue('Redis connection refused');

await expect(
tool({
redisUrl: 'redis://invalid-host:6379',
key: 'test:key'
})
).rejects.toBe('Redis connection refused');
});
});
58 changes: 58 additions & 0 deletions modules/tool/packages/redis/children/set/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { defineTool } from '@tool/type';
import { FlowNodeInputTypeEnum, WorkflowIOValueTypeEnum } from '@tool/type/fastgpt';

export default defineTool({
name: {
'zh-CN': '设置缓存',
en: 'Set Cache'
},
description: {
'zh-CN': '设置 Redis 缓存数据,支持过期时间',
en: 'Set Redis cache data with optional TTL'
},
toolDescription: 'Set a value in Redis with optional expiration time (TTL in seconds).',

versionList: [
{
value: '0.1.0',
description: 'Initial version',
inputs: [
{
key: 'key',
label: '缓存键',
description: 'Redis 键名',
required: true,
valueType: WorkflowIOValueTypeEnum.string,
renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference],
toolDescription: 'The Redis key to set'
},
{
key: 'value',
label: '缓存值',
description: '要存储的数据',
required: true,
valueType: WorkflowIOValueTypeEnum.string,
renderTypeList: [FlowNodeInputTypeEnum.textarea, FlowNodeInputTypeEnum.reference],
toolDescription: 'The value to cache'
},
{
key: 'ttl',
label: '过期时间 (秒)',
description: '数据过期时间,单位秒。0 表示永不过期',
valueType: WorkflowIOValueTypeEnum.number,
defaultValue: 0,
renderTypeList: [FlowNodeInputTypeEnum.numberInput, FlowNodeInputTypeEnum.reference],
toolDescription: 'Time to live in seconds (0 = no expiration)'
}
],
outputs: [
{
key: 'success',
label: '设置成功',
description: '是否成功设置',
valueType: WorkflowIOValueTypeEnum.boolean
}
]
}
]
});
Loading