aiexecode 1.0.65 → 1.0.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of aiexecode might be problematic. Click here for more details.
- package/README.md +17 -0
- package/config_template/settings.json +1 -3
- package/index.js +46 -71
- package/package.json +1 -12
- package/payload_viewer/out/404/index.html +1 -1
- package/payload_viewer/out/404.html +1 -1
- package/payload_viewer/out/index.html +1 -1
- package/payload_viewer/out/index.txt +1 -1
- package/payload_viewer/web_server.js +0 -163
- package/src/ai_based/completion_judge.js +96 -5
- package/src/ai_based/orchestrator.js +71 -3
- package/src/ai_based/pip_package_installer.js +14 -12
- package/src/ai_based/pip_package_lookup.js +13 -10
- package/src/commands/apikey.js +8 -34
- package/src/commands/help.js +3 -4
- package/src/commands/model.js +17 -74
- package/src/commands/reasoning_effort.js +1 -1
- package/src/config/feature_flags.js +0 -12
- package/src/{ui → frontend}/App.js +23 -25
- package/src/frontend/README.md +81 -0
- package/src/{ui/components/SuggestionsDisplay.js → frontend/components/AutocompleteMenu.js} +3 -3
- package/src/{ui/components/HistoryItemDisplay.js → frontend/components/ConversationItem.js} +37 -89
- package/src/{ui → frontend}/components/CurrentModelView.js +3 -5
- package/src/{ui → frontend}/components/Footer.js +4 -6
- package/src/{ui → frontend}/components/Header.js +2 -5
- package/src/{ui/components/InputPrompt.js → frontend/components/Input.js} +16 -54
- package/src/frontend/components/ModelListView.js +106 -0
- package/src/{ui → frontend}/components/ModelUpdatedView.js +3 -5
- package/src/{ui → frontend}/components/SessionSpinner.js +3 -3
- package/src/{ui → frontend}/components/SetupWizard.js +8 -101
- package/src/{ui → frontend}/components/ToolApprovalPrompt.js +16 -14
- package/src/frontend/design/themeColors.js +42 -0
- package/src/{ui → frontend}/index.js +7 -7
- package/src/frontend/utils/inputBuffer.js +441 -0
- package/src/{ui/utils/markdownRenderer.js → frontend/utils/markdownParser.js} +3 -3
- package/src/{ui/utils/ConsolePatcher.js → frontend/utils/outputRedirector.js} +9 -9
- package/src/{ui/utils/codeColorizer.js → frontend/utils/syntaxHighlighter.js} +2 -3
- package/src/system/ai_request.js +145 -595
- package/src/system/code_executer.js +111 -16
- package/src/system/file_integrity.js +5 -7
- package/src/system/log.js +3 -3
- package/src/system/mcp_integration.js +15 -13
- package/src/system/output_helper.js +0 -20
- package/src/system/session.js +97 -23
- package/src/system/session_memory.js +2 -82
- package/src/system/system_info.js +1 -1
- package/src/system/ui_events.js +0 -43
- package/src/tools/code_editor.js +17 -2
- package/src/tools/file_reader.js +17 -2
- package/src/tools/glob.js +9 -1
- package/src/tools/response_message.js +0 -2
- package/src/tools/ripgrep.js +9 -1
- package/src/tools/web_downloader.js +9 -1
- package/src/util/config.js +3 -8
- package/src/util/debug_log.js +4 -11
- package/src/util/mcp_config_manager.js +3 -5
- package/src/util/output_formatter.js +0 -47
- package/src/util/prompt_loader.js +3 -4
- package/src/util/safe_fs.js +60 -0
- package/src/util/setup_wizard.js +1 -3
- package/src/util/text_formatter.js +0 -86
- package/src/config/claude_models.js +0 -195
- package/src/ui/README.md +0 -208
- package/src/ui/api.js +0 -167
- package/src/ui/components/AgenticProgressDisplay.js +0 -126
- package/src/ui/components/Composer.js +0 -55
- package/src/ui/components/LoadingIndicator.js +0 -54
- package/src/ui/components/ModelListView.js +0 -214
- package/src/ui/components/Notifications.js +0 -55
- package/src/ui/components/StreamingIndicator.js +0 -36
- package/src/ui/contexts/AppContext.js +0 -25
- package/src/ui/contexts/StreamingContext.js +0 -20
- package/src/ui/contexts/UIStateContext.js +0 -117
- package/src/ui/example-usage.js +0 -180
- package/src/ui/hooks/useTerminalResize.js +0 -39
- package/src/ui/themes/semantic-tokens.js +0 -73
- package/src/ui/utils/text-buffer.js +0 -975
- /package/payload_viewer/out/_next/static/{pvY11afExc4r1kApy4_Gi → Z3AZSKhutj-kS4L8VpcOl}/_buildManifest.js +0 -0
- /package/payload_viewer/out/_next/static/{pvY11afExc4r1kApy4_Gi → Z3AZSKhutj-kS4L8VpcOl}/_clientMiddlewareManifest.json +0 -0
- /package/payload_viewer/out/_next/static/{pvY11afExc4r1kApy4_Gi → Z3AZSKhutj-kS4L8VpcOl}/_ssgManifest.js +0 -0
- /package/src/{ui → frontend}/components/BlankLine.js +0 -0
- /package/src/{ui → frontend}/components/FileDiffViewer.js +0 -0
- /package/src/{ui → frontend}/components/HelpView.js +0 -0
- /package/src/{ui → frontend}/hooks/useCompletion.js +0 -0
- /package/src/{ui → frontend}/hooks/useKeypress.js +0 -0
- /package/src/{ui → frontend}/utils/diffUtils.js +0 -0
- /package/src/{ui → frontend}/utils/renderInkComponent.js +0 -0
package/src/util/config.js
CHANGED
|
@@ -3,7 +3,6 @@ import { homedir } from 'os';
|
|
|
3
3
|
import { join, dirname } from 'path';
|
|
4
4
|
import { safeReadFile, safeWriteFile, safeMkdir, safeReaddir, safeStat, safeCopyFile } from './safe_fs.js';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
|
-
import { DEFAULT_CLAUDE_MODEL } from '../config/claude_models.js';
|
|
7
6
|
import { DEFAULT_OPENAI_MODEL } from '../config/openai_models.js';
|
|
8
7
|
|
|
9
8
|
// Get project root directory (this file is in src/util/, so go up 2 levels)
|
|
@@ -11,7 +10,6 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
11
10
|
const __dirname = dirname(__filename);
|
|
12
11
|
const PROJECT_ROOT = join(__dirname, '..', '..');
|
|
13
12
|
|
|
14
|
-
function consolelog() { }
|
|
15
13
|
/**
|
|
16
14
|
* 홈 디렉토리 경로를 반환
|
|
17
15
|
* @returns {string} 홈 디렉토리 경로
|
|
@@ -43,12 +41,9 @@ export const PAYLOAD_LOG_DIR = join(CONFIG_DIR, 'payload_log');
|
|
|
43
41
|
export const DEBUG_LOG_DIR = join(CONFIG_DIR, 'debuglog');
|
|
44
42
|
export const DEBUG_LOG_FILE = join(CONFIG_DIR, 'debug.txt'); // Deprecated: 호환성을 위해 유지
|
|
45
43
|
const DEFAULT_SETTINGS = {
|
|
46
|
-
AI_PROVIDER: 'openai', // 'openai' or 'anthropic'
|
|
47
44
|
OPENAI_API_KEY: '',
|
|
48
45
|
OPENAI_MODEL: DEFAULT_OPENAI_MODEL,
|
|
49
46
|
OPENAI_REASONING_EFFORT: 'medium', // 'minimal', 'low', 'medium', 'high'
|
|
50
|
-
ANTHROPIC_API_KEY: '',
|
|
51
|
-
ANTHROPIC_MODEL: DEFAULT_CLAUDE_MODEL,
|
|
52
47
|
// 도구 활성화 옵션
|
|
53
48
|
TOOLS_ENABLED: {
|
|
54
49
|
edit_file_range: false, // 기본적으로 비활성화 (edit_file_replace 사용 권장)
|
|
@@ -89,7 +84,7 @@ export async function ensureConfigDirectory() {
|
|
|
89
84
|
})
|
|
90
85
|
);
|
|
91
86
|
} catch (error) {
|
|
92
|
-
|
|
87
|
+
// Failed to ensure config directory - silently ignore
|
|
93
88
|
throw error;
|
|
94
89
|
}
|
|
95
90
|
}
|
|
@@ -112,7 +107,7 @@ export async function loadSettings() {
|
|
|
112
107
|
await safeWriteFile(SETTINGS_FILE, JSON.stringify(DEFAULT_SETTINGS, null, 2), 'utf8');
|
|
113
108
|
return { ...DEFAULT_SETTINGS };
|
|
114
109
|
}
|
|
115
|
-
|
|
110
|
+
// Failed to load settings - silently ignore
|
|
116
111
|
return { ...DEFAULT_SETTINGS };
|
|
117
112
|
}
|
|
118
113
|
}
|
|
@@ -127,7 +122,7 @@ export async function saveSettings(settings) {
|
|
|
127
122
|
try {
|
|
128
123
|
await safeWriteFile(SETTINGS_FILE, JSON.stringify(settings, null, 2), 'utf8');
|
|
129
124
|
} catch (error) {
|
|
130
|
-
|
|
125
|
+
// Failed to save settings - silently ignore
|
|
131
126
|
throw error;
|
|
132
127
|
}
|
|
133
128
|
}
|
package/src/util/debug_log.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { DEBUG_LOG_DIR } from './config.js';
|
|
7
7
|
import { join, dirname } from 'path';
|
|
8
|
-
import {
|
|
8
|
+
import { safeMkdirSync, safeAppendFileSync } from './safe_fs.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* 디버그 로그를 파일에 기록합니다.
|
|
@@ -24,16 +24,9 @@ export function debugLog(logFileName, context, message) {
|
|
|
24
24
|
? `[${timestamp}] [${context}] ${message}\n`
|
|
25
25
|
: `[${timestamp}] ${message}\n`;
|
|
26
26
|
|
|
27
|
-
// 디렉토리 생성 및 로그 쓰기를
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// 디렉토리가 없으면 생성 (recursive: true 이므로 이미 존재해도 에러 없음)
|
|
31
|
-
await safeMkdir(logDir, { recursive: true });
|
|
32
|
-
await safeAppendFile(LOG_FILE, logMessage);
|
|
33
|
-
} catch (err) {
|
|
34
|
-
// Ignore logging errors
|
|
35
|
-
}
|
|
36
|
-
});
|
|
27
|
+
// 디렉토리 생성 및 로그 쓰기를 동기로 처리
|
|
28
|
+
safeMkdirSync(logDir, { recursive: true });
|
|
29
|
+
safeAppendFileSync(LOG_FILE, logMessage);
|
|
37
30
|
} catch (err) {
|
|
38
31
|
// Ignore logging errors
|
|
39
32
|
}
|
|
@@ -4,8 +4,6 @@ import { join } from 'path';
|
|
|
4
4
|
import { createHash } from 'crypto';
|
|
5
5
|
import { CONFIG_DIR } from './config.js';
|
|
6
6
|
|
|
7
|
-
function consolelog() { }
|
|
8
|
-
|
|
9
7
|
/**
|
|
10
8
|
* MCP 설정 파일 경로 반환 (전역 사용자 설정)
|
|
11
9
|
* @returns {string} 설정 파일 경로
|
|
@@ -25,14 +23,14 @@ export async function loadMergedMcpConfig() {
|
|
|
25
23
|
const data = await safeReadFile(path, 'utf8');
|
|
26
24
|
const config = JSON.parse(data);
|
|
27
25
|
|
|
28
|
-
|
|
26
|
+
// Loaded MCP config - silently
|
|
29
27
|
return config.mcpServers || {};
|
|
30
28
|
} catch (error) {
|
|
31
29
|
// 파일이 없으면 빈 객체 반환
|
|
32
30
|
if (error.message && error.message.includes('Failed to read file')) {
|
|
33
31
|
return {};
|
|
34
32
|
}
|
|
35
|
-
|
|
33
|
+
// Failed to load MCP config - silently ignore
|
|
36
34
|
return {};
|
|
37
35
|
}
|
|
38
36
|
}
|
|
@@ -83,7 +81,7 @@ export async function saveMcpConfig(servers) {
|
|
|
83
81
|
};
|
|
84
82
|
|
|
85
83
|
await safeWriteFile(path, JSON.stringify(config, null, 2), 'utf8');
|
|
86
|
-
|
|
84
|
+
// Saved MCP config - silently
|
|
87
85
|
return path;
|
|
88
86
|
}
|
|
89
87
|
|
|
@@ -20,15 +20,6 @@ export function clampOutput(text) {
|
|
|
20
20
|
return text;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
/**
|
|
24
|
-
* 응답 메시지 포맷팅
|
|
25
|
-
* @param {Object} result - 응답 메시지 결과
|
|
26
|
-
* @returns {string} 포맷된 메시지
|
|
27
|
-
*/
|
|
28
|
-
export function formatResponseMessageStdout(result) {
|
|
29
|
-
return result.message || '';
|
|
30
|
-
}
|
|
31
|
-
|
|
32
23
|
/**
|
|
33
24
|
* 파일 읽기 결과에 줄 번호 추가
|
|
34
25
|
* @param {Object} result - 파일 읽기 실행 결과
|
|
@@ -44,38 +35,6 @@ export function formatReadFileStdout(result) {
|
|
|
44
35
|
return stdout;
|
|
45
36
|
}
|
|
46
37
|
|
|
47
|
-
/**
|
|
48
|
-
* 특정 줄 읽기 결과 포맷팅
|
|
49
|
-
* @param {Object} result - 파일 줄 읽기 실행 결과
|
|
50
|
-
* @returns {string} 줄 번호와 내용
|
|
51
|
-
*/
|
|
52
|
-
export function formatReadFileLinesStdout(result) {
|
|
53
|
-
let stdout = '';
|
|
54
|
-
if (result.file_lines) {
|
|
55
|
-
result.file_lines.map((line) => {
|
|
56
|
-
stdout += `${line.line_number}|${line.line_content}\n`;
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
return stdout;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* 디렉토리 목록을 DIR/FILE 형식으로 표시
|
|
64
|
-
* @param {Object} result - 디렉토리 목록 실행 결과
|
|
65
|
-
* @returns {string} 포맷된 디렉토리 목록
|
|
66
|
-
*/
|
|
67
|
-
export function formatListDirectoryStdout(result) {
|
|
68
|
-
let stdout = '';
|
|
69
|
-
if (result.directory_contents) {
|
|
70
|
-
result.directory_contents.forEach((item) => {
|
|
71
|
-
const typeLabel = item.is_directory ? 'DIR' : 'FILE';
|
|
72
|
-
const size = item.is_directory ? '' : ` (${item.size_bytes} bytes)`;
|
|
73
|
-
stdout += `${typeLabel}: ${item.name}${size}\n`;
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
return stdout;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
38
|
/**
|
|
80
39
|
* operation 타입별로 적절한 포맷터 선택
|
|
81
40
|
* @param {string} operation - 도구 작업 타입
|
|
@@ -84,14 +43,8 @@ export function formatListDirectoryStdout(result) {
|
|
|
84
43
|
*/
|
|
85
44
|
export function formatToolStdout(operation, result) {
|
|
86
45
|
switch (operation) {
|
|
87
|
-
case 'response_message':
|
|
88
|
-
return formatResponseMessageStdout(result);
|
|
89
46
|
case 'read_file':
|
|
90
47
|
return formatReadFileStdout(result);
|
|
91
|
-
case 'read_file_lines':
|
|
92
|
-
return formatReadFileLinesStdout(result);
|
|
93
|
-
case 'list_directory':
|
|
94
|
-
return formatListDirectoryStdout(result);
|
|
95
48
|
default:
|
|
96
49
|
return JSON.stringify(result, null, 2);
|
|
97
50
|
}
|
|
@@ -4,7 +4,6 @@ import { fileURLToPath } from "url";
|
|
|
4
4
|
|
|
5
5
|
const moduleDirname = dirname(fileURLToPath(import.meta.url));
|
|
6
6
|
const defaultProjectRoot = dirname(dirname(moduleDirname));
|
|
7
|
-
function consolelog() { }
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
9
|
* 프로젝트 루트 경로를 반환합니다.
|
|
@@ -24,7 +23,7 @@ async function loadSystemPrompt(promptPath) {
|
|
|
24
23
|
const content = await safeReadFile(promptPath, "utf8");
|
|
25
24
|
return content.trim();
|
|
26
25
|
} catch (error) {
|
|
27
|
-
|
|
26
|
+
// Failed to load system prompt - return empty
|
|
28
27
|
return '';
|
|
29
28
|
}
|
|
30
29
|
}
|
|
@@ -53,13 +52,13 @@ async function loadPromptFromPromptsDir(promptFileName) {
|
|
|
53
52
|
// 우선순위 1: CWD/.aiexe/prompts/ (프로젝트별 커스텀 프롬프트)
|
|
54
53
|
const cwdPromptPath = join(process.cwd(), ".aiexe", "prompts", promptFileName);
|
|
55
54
|
if (await fileExists(cwdPromptPath)) {
|
|
56
|
-
|
|
55
|
+
// Loading prompt from project-specific path
|
|
57
56
|
return await loadSystemPrompt(cwdPromptPath);
|
|
58
57
|
}
|
|
59
58
|
|
|
60
59
|
// 우선순위 2: 프로젝트루트/prompts/ (기본 프롬프트)
|
|
61
60
|
const defaultPromptPath = join(getProjectRoot(), "prompts", promptFileName);
|
|
62
|
-
|
|
61
|
+
// Loading prompt from default path
|
|
63
62
|
return await loadSystemPrompt(defaultPromptPath);
|
|
64
63
|
}
|
|
65
64
|
|
package/src/util/safe_fs.js
CHANGED
|
@@ -520,3 +520,63 @@ export async function safeAppendFile(filePath, data, encoding = 'utf8') {
|
|
|
520
520
|
throw new Error(errorMsg);
|
|
521
521
|
}
|
|
522
522
|
}
|
|
523
|
+
|
|
524
|
+
// ============================================================
|
|
525
|
+
// 동기 작업 (Synchronous operations)
|
|
526
|
+
// ============================================================
|
|
527
|
+
|
|
528
|
+
import { mkdirSync, appendFileSync } from 'fs';
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* 디렉토리 생성 (동기)
|
|
532
|
+
* @param {string} dirPath - 생성할 디렉토리 경로
|
|
533
|
+
* @param {Object} options - 추가 옵션
|
|
534
|
+
* @param {boolean} options.recursive - 재귀적으로 생성 여부
|
|
535
|
+
* @returns {string|undefined}
|
|
536
|
+
*/
|
|
537
|
+
export function safeMkdirSync(dirPath, options = {}) {
|
|
538
|
+
const absolutePath = toAbsolutePath(dirPath);
|
|
539
|
+
|
|
540
|
+
if (!validatePath(absolutePath)) {
|
|
541
|
+
throw new Error(`Invalid directory path: ${dirPath}`);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (isProtectedPath(absolutePath)) {
|
|
545
|
+
throw new Error(`Protected path cannot be modified: ${absolutePath}`);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
try {
|
|
549
|
+
return mkdirSync(absolutePath, options);
|
|
550
|
+
} catch (error) {
|
|
551
|
+
// recursive: true 일 때 이미 존재하는 디렉토리는 에러가 아님
|
|
552
|
+
if (error.code === 'EEXIST' && options.recursive) {
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
throw new Error(`Failed to create directory ${absolutePath}: ${error.message}`);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* 파일에 내용 추가 (동기)
|
|
561
|
+
* @param {string} filePath - 파일 경로
|
|
562
|
+
* @param {string|Buffer} data - 추가할 데이터
|
|
563
|
+
* @param {string} encoding - 인코딩 (기본값: 'utf8')
|
|
564
|
+
* @returns {void}
|
|
565
|
+
*/
|
|
566
|
+
export function safeAppendFileSync(filePath, data, encoding = 'utf8') {
|
|
567
|
+
const absolutePath = toAbsolutePath(filePath);
|
|
568
|
+
|
|
569
|
+
if (!validatePath(absolutePath)) {
|
|
570
|
+
throw new Error(`Invalid file path: ${filePath}`);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (isProtectedPath(absolutePath)) {
|
|
574
|
+
throw new Error(`Protected path cannot be modified: ${absolutePath}`);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
try {
|
|
578
|
+
return appendFileSync(absolutePath, data, encoding);
|
|
579
|
+
} catch (error) {
|
|
580
|
+
throw new Error(`Failed to append to file ${absolutePath}: ${error.message}`);
|
|
581
|
+
}
|
|
582
|
+
}
|
package/src/util/setup_wizard.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// 초기 설정 마법사 - 사용자에게 API 키 및 모델 설정을 안내합니다.
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { render } from 'ink';
|
|
4
|
-
import { SetupWizard } from '../
|
|
4
|
+
import { SetupWizard } from '../frontend/components/SetupWizard.js';
|
|
5
5
|
import { loadSettings, saveSettings, SETTINGS_FILE } from './config.js';
|
|
6
6
|
import chalk from 'chalk';
|
|
7
7
|
|
|
@@ -61,8 +61,6 @@ export async function isConfigured() {
|
|
|
61
61
|
|
|
62
62
|
if (settings.AI_PROVIDER === 'openai') {
|
|
63
63
|
return Boolean(settings.OPENAI_API_KEY && settings.OPENAI_API_KEY.trim());
|
|
64
|
-
} else if (settings.AI_PROVIDER === 'anthropic') {
|
|
65
|
-
return Boolean(settings.ANTHROPIC_API_KEY && settings.ANTHROPIC_API_KEY.trim());
|
|
66
64
|
}
|
|
67
65
|
|
|
68
66
|
return false;
|
|
@@ -1,91 +1,5 @@
|
|
|
1
1
|
// 문자열 및 텍스트 포맷팅 유틸리티
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* 텍스트 줄에 들여쓰기 추가
|
|
5
|
-
* @param {string} content - 원본 내용
|
|
6
|
-
* @param {number} indentLevel - 들여쓰기 레벨 (기본값: 0)
|
|
7
|
-
* @returns {string} 들여쓰기가 적용된 내용
|
|
8
|
-
*/
|
|
9
|
-
export function indentLines(content, indentLevel = 0) {
|
|
10
|
-
return content.split('\n').map(line => ' '.repeat(indentLevel) + line).join('\n');
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* JSON 객체를 XML 형식으로 변환
|
|
15
|
-
* @param {Object|string} json - 변환할 JSON 객체
|
|
16
|
-
* @param {number} indentLevel - 들여쓰기 레벨 (기본값: 0)
|
|
17
|
-
* @returns {string} XML 형식 문자열
|
|
18
|
-
*/
|
|
19
|
-
export function tagify(json, indentLevel = 0) {
|
|
20
|
-
if (!json) return '';
|
|
21
|
-
|
|
22
|
-
if (typeof json === 'string') {
|
|
23
|
-
return indentLines(json, indentLevel);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (json.tagname) {
|
|
27
|
-
let openTag = `<${json.tagname}`;
|
|
28
|
-
|
|
29
|
-
if (json.attr && typeof json.attr === 'object') {
|
|
30
|
-
for (const [key, value] of Object.entries(json.attr)) {
|
|
31
|
-
openTag += ` ${key}="${value}"`;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
openTag += '>';
|
|
36
|
-
const closeTag = `</${json.tagname}>`;
|
|
37
|
-
|
|
38
|
-
let result = indentLines(openTag, indentLevel) + '\n';
|
|
39
|
-
|
|
40
|
-
if (json.children && Array.isArray(json.children)) {
|
|
41
|
-
for (const child of json.children) {
|
|
42
|
-
result += tagify(child, indentLevel + 1) + '\n';
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
result += indentLines(closeTag, indentLevel);
|
|
47
|
-
return result;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (json.content) {
|
|
51
|
-
return indentLines(json.content, indentLevel);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return '';
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* 문자열을 안전한 펜스 코드 블록으로 감싸는 함수
|
|
59
|
-
* @param {string} content - 감쌀 내용
|
|
60
|
-
* @param {string} fencedName - 코드 블록 언어 이름
|
|
61
|
-
* @returns {string} 펜스 코드 블록으로 감싼 문자열
|
|
62
|
-
*/
|
|
63
|
-
export function wrapWithSafeFence(content, fencedName) {
|
|
64
|
-
// 내용 내의 최대 연속 백틱 개수 찾기
|
|
65
|
-
let maxConsecutiveTicks = 0;
|
|
66
|
-
let currentTicks = 0;
|
|
67
|
-
|
|
68
|
-
for (let i = 0; i < content.length; i++) {
|
|
69
|
-
if (content[i] === '`') {
|
|
70
|
-
currentTicks++;
|
|
71
|
-
} else {
|
|
72
|
-
if (currentTicks > maxConsecutiveTicks) {
|
|
73
|
-
maxConsecutiveTicks = currentTicks;
|
|
74
|
-
}
|
|
75
|
-
currentTicks = 0;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
// 마지막 연속 백틱도 확인
|
|
79
|
-
if (currentTicks > maxConsecutiveTicks) {
|
|
80
|
-
maxConsecutiveTicks = currentTicks;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// 기존 최대값보다 1개 더 많은 백틱으로 감싸기 (최소 3개)
|
|
84
|
-
const fenceLength = Math.max(3, maxConsecutiveTicks + 1);
|
|
85
|
-
const fence = '`'.repeat(fenceLength);
|
|
86
|
-
return `${fence}\n${fencedName}\n${indentLines(content, 1)}\n${fence}`;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
3
|
/**
|
|
90
4
|
* 긴 텍스트를 제한하고 생략 표시를 추가하는 함수
|
|
91
5
|
* @param {string} text - 원본 텍스트
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Anthropic Claude 모델 설정
|
|
3
|
-
*
|
|
4
|
-
* 모든 Claude 모델 정보를 중앙 집중식으로 관리합니다.
|
|
5
|
-
* 새 모델 추가 시 이 파일만 수정하면 됩니다.
|
|
6
|
-
*
|
|
7
|
-
* 참고: https://docs.anthropic.com/en/docs/about-claude/models/overview
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
export const CLAUDE_MODELS = {
|
|
11
|
-
// ========================================
|
|
12
|
-
// Claude 4.x 시리즈 (최신)
|
|
13
|
-
// ========================================
|
|
14
|
-
'claude-sonnet-4-5-20250929': {
|
|
15
|
-
name: 'Claude Sonnet 4.5',
|
|
16
|
-
description: 'Best model for complex agents and coding (recommended)',
|
|
17
|
-
contextWindow: 200000, // 200K (베타: 1M)
|
|
18
|
-
maxTokens: 64000, // 64K
|
|
19
|
-
pricing: {
|
|
20
|
-
input: 3, // $3 per million tokens
|
|
21
|
-
output: 15 // $15 per million tokens
|
|
22
|
-
},
|
|
23
|
-
speed: 'fast',
|
|
24
|
-
knowledgeCutoff: '2025-01',
|
|
25
|
-
trainingDataCutoff: '2025-07'
|
|
26
|
-
},
|
|
27
|
-
'claude-sonnet-4-20250514': {
|
|
28
|
-
name: 'Claude Sonnet 4',
|
|
29
|
-
description: 'High-performance model',
|
|
30
|
-
contextWindow: 200000, // 200K (베타: 1M)
|
|
31
|
-
maxTokens: 64000, // 64K
|
|
32
|
-
pricing: {
|
|
33
|
-
input: 3,
|
|
34
|
-
output: 15
|
|
35
|
-
},
|
|
36
|
-
speed: 'fast',
|
|
37
|
-
knowledgeCutoff: '2025-01',
|
|
38
|
-
trainingDataCutoff: '2025-07'
|
|
39
|
-
},
|
|
40
|
-
'claude-opus-4-1-20250805': {
|
|
41
|
-
name: 'Claude Opus 4.1',
|
|
42
|
-
description: 'Exceptional model for specialized complex tasks',
|
|
43
|
-
contextWindow: 200000, // 200K
|
|
44
|
-
maxTokens: 32000, // 32K
|
|
45
|
-
pricing: {
|
|
46
|
-
input: 15, // $15 per million tokens
|
|
47
|
-
output: 75 // $75 per million tokens
|
|
48
|
-
},
|
|
49
|
-
speed: 'moderate',
|
|
50
|
-
knowledgeCutoff: '2025-01',
|
|
51
|
-
trainingDataCutoff: '2025-03'
|
|
52
|
-
},
|
|
53
|
-
'claude-opus-4-20250514': {
|
|
54
|
-
name: 'Claude Opus 4',
|
|
55
|
-
description: 'Previous flagship model (very high intelligence)',
|
|
56
|
-
contextWindow: 200000, // 200K
|
|
57
|
-
maxTokens: 32000, // 32K
|
|
58
|
-
pricing: {
|
|
59
|
-
input: 15,
|
|
60
|
-
output: 75
|
|
61
|
-
},
|
|
62
|
-
speed: 'moderate',
|
|
63
|
-
knowledgeCutoff: '2025-01',
|
|
64
|
-
trainingDataCutoff: '2025-03'
|
|
65
|
-
},
|
|
66
|
-
'claude-haiku-4-5-20251001': {
|
|
67
|
-
name: 'Claude Haiku 4.5',
|
|
68
|
-
description: 'Fastest and most intelligent Haiku model (extended thinking support)',
|
|
69
|
-
contextWindow: 200000, // 200K
|
|
70
|
-
maxTokens: 64000, // 64K
|
|
71
|
-
pricing: {
|
|
72
|
-
input: 1, // $1 per million tokens
|
|
73
|
-
output: 5 // $5 per million tokens
|
|
74
|
-
},
|
|
75
|
-
speed: 'fastest',
|
|
76
|
-
knowledgeCutoff: '2025-02',
|
|
77
|
-
trainingDataCutoff: '2025-07'
|
|
78
|
-
},
|
|
79
|
-
|
|
80
|
-
// ========================================
|
|
81
|
-
// Claude 3.x 시리즈
|
|
82
|
-
// ========================================
|
|
83
|
-
'claude-3-7-sonnet-20250219': {
|
|
84
|
-
name: 'Claude Sonnet 3.7',
|
|
85
|
-
description: 'High-performance model with extended thinking support',
|
|
86
|
-
contextWindow: 200000, // 200K
|
|
87
|
-
maxTokens: 64000, // 64K (베타: 128K)
|
|
88
|
-
pricing: {
|
|
89
|
-
input: 3,
|
|
90
|
-
output: 15
|
|
91
|
-
},
|
|
92
|
-
speed: 'fast',
|
|
93
|
-
knowledgeCutoff: '2025-01',
|
|
94
|
-
trainingDataCutoff: '2025-07'
|
|
95
|
-
},
|
|
96
|
-
'claude-3-5-sonnet-20241022': {
|
|
97
|
-
name: 'Claude 3.5 Sonnet',
|
|
98
|
-
description: 'Proven model with balanced performance',
|
|
99
|
-
contextWindow: 200000, // 200K
|
|
100
|
-
maxTokens: 8192, // 8K
|
|
101
|
-
pricing: {
|
|
102
|
-
input: 3,
|
|
103
|
-
output: 15
|
|
104
|
-
},
|
|
105
|
-
speed: 'fast',
|
|
106
|
-
knowledgeCutoff: '2024-04',
|
|
107
|
-
trainingDataCutoff: '2024-04'
|
|
108
|
-
},
|
|
109
|
-
'claude-3-5-haiku-20241022': {
|
|
110
|
-
name: 'Claude 3.5 Haiku',
|
|
111
|
-
description: 'Very fast response speed',
|
|
112
|
-
contextWindow: 200000, // 200K
|
|
113
|
-
maxTokens: 8192, // 8K
|
|
114
|
-
pricing: {
|
|
115
|
-
input: 1,
|
|
116
|
-
output: 5
|
|
117
|
-
},
|
|
118
|
-
speed: 'fastest',
|
|
119
|
-
knowledgeCutoff: '2024-07',
|
|
120
|
-
trainingDataCutoff: '2024-07'
|
|
121
|
-
},
|
|
122
|
-
'claude-3-haiku-20240307': {
|
|
123
|
-
name: 'Claude 3 Haiku',
|
|
124
|
-
description: 'Fast and concise responses',
|
|
125
|
-
contextWindow: 200000, // 200K
|
|
126
|
-
maxTokens: 4096, // 4K
|
|
127
|
-
pricing: {
|
|
128
|
-
input: 0.25,
|
|
129
|
-
output: 1.25
|
|
130
|
-
},
|
|
131
|
-
speed: 'very-fast',
|
|
132
|
-
knowledgeCutoff: '2023-08',
|
|
133
|
-
trainingDataCutoff: '2023-08'
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* 모델 ID로 모델 정보 가져오기
|
|
139
|
-
*/
|
|
140
|
-
export function getClaudeModelInfo(modelId) {
|
|
141
|
-
return CLAUDE_MODELS[modelId] || null;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* 모델 ID로 max_tokens 가져오기
|
|
146
|
-
*/
|
|
147
|
-
export function getClaudeMaxTokens(modelId) {
|
|
148
|
-
const model = CLAUDE_MODELS[modelId];
|
|
149
|
-
return model ? model.maxTokens : 8192; // 기본값 8K
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* 모델 ID로 context window 크기 가져오기
|
|
154
|
-
*/
|
|
155
|
-
export function getClaudeContextWindow(modelId) {
|
|
156
|
-
const model = CLAUDE_MODELS[modelId];
|
|
157
|
-
return model ? model.contextWindow : 200000; // 기본값 200K
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* 모든 Claude 모델 ID 목록 가져오기
|
|
162
|
-
*/
|
|
163
|
-
export function getAllClaudeModelIds() {
|
|
164
|
-
return Object.keys(CLAUDE_MODELS);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Claude 4.x 시리즈 모델 ID 목록
|
|
169
|
-
*/
|
|
170
|
-
export function getClaude4Models() {
|
|
171
|
-
return [
|
|
172
|
-
'claude-sonnet-4-5-20250929',
|
|
173
|
-
'claude-sonnet-4-20250514',
|
|
174
|
-
'claude-opus-4-1-20250805',
|
|
175
|
-
'claude-opus-4-20250514',
|
|
176
|
-
'claude-haiku-4-5-20251001'
|
|
177
|
-
];
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Claude 3.x 시리즈 모델 ID 목록
|
|
182
|
-
*/
|
|
183
|
-
export function getClaude3Models() {
|
|
184
|
-
return [
|
|
185
|
-
'claude-3-7-sonnet-20250219',
|
|
186
|
-
'claude-3-5-sonnet-20241022',
|
|
187
|
-
'claude-3-5-haiku-20241022',
|
|
188
|
-
'claude-3-haiku-20240307'
|
|
189
|
-
];
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* 기본 권장 Claude 모델 ID
|
|
194
|
-
*/
|
|
195
|
-
export const DEFAULT_CLAUDE_MODEL = 'claude-sonnet-4-5-20250929';
|