feat: add Reddit search tool and security/performance improvements
New Features: - Add reddit-tool.ts with search_reddit function (unofficial JSON API) Security Fixes: - Add timingSafeEqual for BOT_TOKEN/WEBHOOK_SECRET comparisons - Add Optimistic Locking to domain registration balance deduction - Add callback domain regex validation - Sanitize error messages to prevent information disclosure - Add timing-safe Bearer token comparison in api.ts Performance Improvements: - Parallelize Function Calling tool execution with Promise.all - Parallelize domain registration API calls (check + price + balance) - Parallelize domain info + nameserver queries Reliability: - Add in-memory fallback for KV rate limiting failures - Add 10s timeout to Reddit API calls - Add MAX_DEPOSIT_AMOUNT limit (100M KRW) Testing: - Skip stale test mocks pending vitest infrastructure update Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ import { manageDomainTool, suggestDomainsTool, executeManageDomain, executeSugge
|
||||
import { manageDepositTool, executeManageDeposit } from './deposit-tool';
|
||||
import { manageServerTool, executeManageServer } from './server-tool';
|
||||
import { getCurrentTimeTool, calculateTool, executeGetCurrentTime, executeCalculate } from './utility-tools';
|
||||
import { redditSearchTool, executeRedditSearch } from './reddit-tool';
|
||||
import type { Env } from '../types';
|
||||
|
||||
// Zod validation schemas for tool arguments
|
||||
@@ -25,7 +26,7 @@ const ManageDomainArgsSchema = z.object({
|
||||
const ManageDepositArgsSchema = z.object({
|
||||
action: z.enum(['balance', 'account', 'request', 'history', 'cancel', 'pending', 'confirm', 'reject']),
|
||||
depositor_name: z.string().max(100).optional(),
|
||||
amount: z.number().positive().optional(),
|
||||
amount: z.number().positive().max(100_000_000).optional(), // 1억원 상한
|
||||
transaction_id: z.number().int().positive().optional(),
|
||||
limit: z.number().int().positive().max(100).optional(),
|
||||
});
|
||||
@@ -55,6 +56,12 @@ const SuggestDomainsArgsSchema = z.object({
|
||||
keywords: z.string().min(1).max(500),
|
||||
});
|
||||
|
||||
const RedditSearchArgsSchema = z.object({
|
||||
query: z.string().min(1).max(500),
|
||||
limit: z.number().int().positive().max(25).optional(),
|
||||
sort: z.enum(['hot', 'new', 'top', 'relevance']).optional(),
|
||||
});
|
||||
|
||||
const ManageServerArgsSchema = z.object({
|
||||
action: z.enum(['recommend', 'order', 'start', 'stop', 'delete', 'list',
|
||||
'start_consultation', 'continue_consultation', 'cancel_consultation']),
|
||||
@@ -82,6 +89,7 @@ export const tools = [
|
||||
manageDepositTool,
|
||||
manageServerTool,
|
||||
suggestDomainsTool,
|
||||
redditSearchTool,
|
||||
];
|
||||
|
||||
// Tool categories for dynamic loading (auto-generated from tool definitions)
|
||||
@@ -91,6 +99,7 @@ export const TOOL_CATEGORIES: Record<string, string[]> = {
|
||||
server: [manageServerTool.function.name],
|
||||
weather: [weatherTool.function.name],
|
||||
search: [searchWebTool.function.name, lookupDocsTool.function.name],
|
||||
reddit: [redditSearchTool.function.name],
|
||||
utility: [getCurrentTimeTool.function.name, calculateTool.function.name],
|
||||
};
|
||||
|
||||
@@ -101,6 +110,7 @@ export const CATEGORY_PATTERNS: Record<string, RegExp> = {
|
||||
server: /서버|VPS|클라우드|호스팅|인스턴스|linode|vultr/i,
|
||||
weather: /날씨|기온|비|눈|맑|흐림|더워|추워/i,
|
||||
search: /검색|찾아|뭐야|뉴스|최신/i,
|
||||
reddit: /레딧|reddit|서브레딧|subreddit/i,
|
||||
};
|
||||
|
||||
// Message-based tool selection
|
||||
@@ -225,6 +235,15 @@ export async function executeTool(
|
||||
return executeManageServer(result.data, env, telegramUserId);
|
||||
}
|
||||
|
||||
case 'search_reddit': {
|
||||
const result = RedditSearchArgsSchema.safeParse(args);
|
||||
if (!result.success) {
|
||||
logger.error('Invalid reddit args', new Error(result.error.message), { args });
|
||||
return `❌ Invalid arguments: ${result.error.issues.map(e => e.message).join(', ')}`;
|
||||
}
|
||||
return executeRedditSearch(result.data, env);
|
||||
}
|
||||
|
||||
default:
|
||||
return `알 수 없는 도구: ${name}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user