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:
@@ -147,9 +147,17 @@ export async function generateOpenAIResponse(
|
||||
while (assistantMessage.tool_calls && iterations < 3) {
|
||||
iterations++;
|
||||
|
||||
// 도구 호출 결과 수집
|
||||
const toolResults: OpenAIMessage[] = [];
|
||||
for (const toolCall of assistantMessage.tool_calls) {
|
||||
// 도구 호출을 병렬 실행
|
||||
type ToolResult = {
|
||||
early: true;
|
||||
result: string;
|
||||
toolCall: ToolCall;
|
||||
} | {
|
||||
early: false;
|
||||
message: OpenAIMessage;
|
||||
} | null;
|
||||
|
||||
const toolPromises = assistantMessage.tool_calls.map(async (toolCall): Promise<ToolResult> => {
|
||||
let args: Record<string, unknown>;
|
||||
try {
|
||||
args = JSON.parse(toolCall.function.arguments);
|
||||
@@ -158,27 +166,46 @@ export async function generateOpenAIResponse(
|
||||
toolName: toolCall.function.name,
|
||||
raw: toolCall.function.arguments.slice(0, 200) // 일부만 로깅
|
||||
});
|
||||
continue; // 다음 tool call로 진행
|
||||
return null; // 파싱 실패 시 null 반환
|
||||
}
|
||||
|
||||
const result = await executeTool(toolCall.function.name, args, env, telegramUserId, db);
|
||||
|
||||
// __KEYBOARD__ 마커가 있으면 AI 재해석 없이 바로 반환 (버튼 보존)
|
||||
if (result.includes('__KEYBOARD__')) {
|
||||
return result;
|
||||
// Early return 체크 (__KEYBOARD__, __DIRECT__)
|
||||
if (result.includes('__KEYBOARD__') || result.includes('__DIRECT__')) {
|
||||
return { early: true as const, result, toolCall };
|
||||
}
|
||||
|
||||
// __DIRECT__ 마커가 있으면 AI 재해석 없이 바로 반환 (서버 추천 등)
|
||||
if (result.includes('__DIRECT__')) {
|
||||
return result.replace('__DIRECT__', '').trim();
|
||||
}
|
||||
return {
|
||||
early: false as const,
|
||||
message: {
|
||||
role: 'tool' as const,
|
||||
tool_call_id: toolCall.id,
|
||||
content: result,
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
toolResults.push({
|
||||
role: 'tool',
|
||||
tool_call_id: toolCall.id,
|
||||
content: result,
|
||||
});
|
||||
const results = await Promise.all(toolPromises);
|
||||
|
||||
// Early return 처리
|
||||
const earlyResult = results.find((r): r is { early: true; result: string; toolCall: ToolCall } =>
|
||||
r !== null && r.early === true
|
||||
);
|
||||
if (earlyResult) {
|
||||
if (earlyResult.result.includes('__DIRECT__')) {
|
||||
return earlyResult.result.replace('__DIRECT__', '').trim();
|
||||
}
|
||||
return earlyResult.result;
|
||||
}
|
||||
|
||||
// 정상 결과 처리 (null 제외)
|
||||
const toolResults = results
|
||||
.filter((r): r is { early: false; message: OpenAIMessage } =>
|
||||
r !== null && r.early === false
|
||||
)
|
||||
.map(r => r.message);
|
||||
|
||||
// 대화에 추가
|
||||
messages.push({
|
||||
role: 'assistant',
|
||||
|
||||
Reference in New Issue
Block a user