aiexecode 1.0.94 → 1.0.96

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.

Files changed (51) hide show
  1. package/README.md +210 -87
  2. package/index.js +33 -1
  3. package/package.json +3 -3
  4. package/payload_viewer/out/404/index.html +1 -1
  5. package/payload_viewer/out/404.html +1 -1
  6. package/payload_viewer/out/_next/static/chunks/{37d0cd2587a38f79.js → b6c0459f3789d25c.js} +1 -1
  7. package/payload_viewer/out/_next/static/chunks/b75131b58f8ca46a.css +3 -0
  8. package/payload_viewer/out/index.html +1 -1
  9. package/payload_viewer/out/index.txt +3 -3
  10. package/payload_viewer/web_server.js +361 -0
  11. package/src/LLMClient/client.js +392 -16
  12. package/src/LLMClient/converters/responses-to-claude.js +67 -18
  13. package/src/LLMClient/converters/responses-to-zai.js +608 -0
  14. package/src/LLMClient/errors.js +18 -4
  15. package/src/LLMClient/index.js +5 -0
  16. package/src/ai_based/completion_judge.js +35 -4
  17. package/src/ai_based/orchestrator.js +146 -35
  18. package/src/commands/agents.js +70 -0
  19. package/src/commands/commands.js +51 -0
  20. package/src/commands/debug.js +52 -0
  21. package/src/commands/help.js +11 -1
  22. package/src/commands/model.js +43 -7
  23. package/src/commands/skills.js +46 -0
  24. package/src/config/ai_models.js +96 -5
  25. package/src/config/constants.js +71 -0
  26. package/src/frontend/components/HelpView.js +106 -2
  27. package/src/frontend/components/SetupWizard.js +53 -8
  28. package/src/frontend/utils/toolUIFormatter.js +261 -0
  29. package/src/system/agents_loader.js +289 -0
  30. package/src/system/ai_request.js +147 -9
  31. package/src/system/command_parser.js +33 -3
  32. package/src/system/conversation_state.js +265 -0
  33. package/src/system/custom_command_loader.js +386 -0
  34. package/src/system/session.js +59 -35
  35. package/src/system/skill_loader.js +318 -0
  36. package/src/system/tool_approval.js +10 -0
  37. package/src/tools/file_reader.js +49 -9
  38. package/src/tools/glob.js +0 -3
  39. package/src/tools/ripgrep.js +5 -7
  40. package/src/tools/skill_tool.js +122 -0
  41. package/src/tools/web_downloader.js +0 -3
  42. package/src/util/clone.js +174 -0
  43. package/src/util/config.js +38 -2
  44. package/src/util/config_migration.js +174 -0
  45. package/src/util/path_validator.js +178 -0
  46. package/src/util/prompt_loader.js +68 -1
  47. package/src/util/safe_fs.js +43 -3
  48. package/payload_viewer/out/_next/static/chunks/ecd2072ebf41611f.css +0 -3
  49. /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → lHmNygVpv4N1VR0LdnwkJ}/_buildManifest.js +0 -0
  50. /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → lHmNygVpv4N1VR0LdnwkJ}/_clientMiddlewareManifest.json +0 -0
  51. /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → lHmNygVpv4N1VR0LdnwkJ}/_ssgManifest.js +0 -0
@@ -5,6 +5,13 @@
5
5
  * 새 모델 추가 시 이 파일만 수정하면 됩니다.
6
6
  */
7
7
 
8
+ /**
9
+ * 공식 지원 Provider 목록
10
+ * 런타임에서 이 목록에 포함된 provider의 모델만 노출됩니다.
11
+ * 다른 provider의 구현 코드는 유지되지만 사용자에게 노출되지 않습니다.
12
+ */
13
+ export const ENABLED_PROVIDERS = ['openai', 'zai'];
14
+
8
15
  export const AI_MODELS = {
9
16
  // ========================================
10
17
  // Claude 시리즈
@@ -62,6 +69,40 @@ export const AI_MODELS = {
62
69
  maxTokens: 4096,
63
70
  },
64
71
 
72
+ // ========================================
73
+ // Z.AI GLM 시리즈 (Anthropic API 호환)
74
+ // - interleaved-thinking 지원
75
+ // - 프롬프트 캐싱 지원 (cache_read_input_tokens)
76
+ // - 서버 측 웹 검색 지원
77
+ // ========================================
78
+ 'glm-4.7': {
79
+ provider: 'zai',
80
+ name: 'GLM-4.7',
81
+ contextWindow: 128000,
82
+ maxTokens: 8192,
83
+ supportsThinking: true,
84
+ supportsCaching: true,
85
+ supportsServerTools: true,
86
+ },
87
+ 'glm-4.5-air': {
88
+ provider: 'zai',
89
+ name: 'GLM-4.5 Air',
90
+ contextWindow: 128000,
91
+ maxTokens: 8192,
92
+ supportsThinking: true,
93
+ supportsCaching: true,
94
+ supportsServerTools: true,
95
+ },
96
+ 'glm-4.5': {
97
+ provider: 'zai',
98
+ name: 'GLM-4.5',
99
+ contextWindow: 128000,
100
+ maxTokens: 8192,
101
+ supportsThinking: true,
102
+ supportsCaching: true,
103
+ supportsServerTools: true,
104
+ },
105
+
65
106
  // ========================================
66
107
  // Google Gemini 시리즈
67
108
  // ========================================
@@ -137,6 +178,14 @@ export function getModelInfo(modelId) {
137
178
  return AI_MODELS[modelId] || null;
138
179
  }
139
180
 
181
+ /**
182
+ * 모델이 공식 지원 provider인지 확인
183
+ */
184
+ export function isModelEnabled(modelId) {
185
+ const model = AI_MODELS[modelId];
186
+ return model ? ENABLED_PROVIDERS.includes(model.provider) : false;
187
+ }
188
+
140
189
  /**
141
190
  * 모델 ID로 max_tokens 가져오기
142
191
  */
@@ -154,16 +203,21 @@ export function getContextWindow(modelId) {
154
203
  }
155
204
 
156
205
  /**
157
- * 모든 모델 ID 목록 가져오기
206
+ * 모든 모델 ID 목록 가져오기 (공식 지원 provider만)
158
207
  */
159
208
  export function getAllModelIds() {
160
- return Object.keys(AI_MODELS);
209
+ return Object.keys(AI_MODELS).filter(
210
+ modelId => ENABLED_PROVIDERS.includes(AI_MODELS[modelId].provider)
211
+ );
161
212
  }
162
213
 
163
214
  /**
164
- * 특정 제조사의 모델 ID 목록 가져오기
215
+ * 특정 제조사의 모델 ID 목록 가져오기 (공식 지원 provider만)
165
216
  */
166
217
  export function getModelsByProvider(provider) {
218
+ if (!ENABLED_PROVIDERS.includes(provider)) {
219
+ return [];
220
+ }
167
221
  return Object.keys(AI_MODELS).filter(
168
222
  modelId => AI_MODELS[modelId].provider === provider
169
223
  );
@@ -177,14 +231,51 @@ export function getGPT5Models() {
177
231
  }
178
232
 
179
233
  /**
180
- * Reasoning 지원 모델 ID 목록
234
+ * Reasoning 지원 모델 ID 목록 (공식 지원 provider만)
181
235
  */
182
236
  export function getReasoningModels() {
183
237
  return Object.keys(AI_MODELS).filter(
184
- modelId => AI_MODELS[modelId].supportsReasoning
238
+ modelId => AI_MODELS[modelId].supportsReasoning &&
239
+ ENABLED_PROVIDERS.includes(AI_MODELS[modelId].provider)
185
240
  );
186
241
  }
187
242
 
243
+ /**
244
+ * Thinking 지원 모델 ID 목록 (Z.AI GLM 등, 공식 지원 provider만)
245
+ */
246
+ export function getThinkingModels() {
247
+ return Object.keys(AI_MODELS).filter(
248
+ modelId => AI_MODELS[modelId].supportsThinking &&
249
+ ENABLED_PROVIDERS.includes(AI_MODELS[modelId].provider)
250
+ );
251
+ }
252
+
253
+ /**
254
+ * 캐싱 지원 모델 ID 목록 (공식 지원 provider만)
255
+ */
256
+ export function getCachingModels() {
257
+ return Object.keys(AI_MODELS).filter(
258
+ modelId => AI_MODELS[modelId].supportsCaching &&
259
+ ENABLED_PROVIDERS.includes(AI_MODELS[modelId].provider)
260
+ );
261
+ }
262
+
263
+ /**
264
+ * 특정 모델이 thinking을 지원하는지 확인
265
+ */
266
+ export function supportsThinking(modelId) {
267
+ const model = AI_MODELS[modelId];
268
+ return model?.supportsThinking || false;
269
+ }
270
+
271
+ /**
272
+ * 특정 모델이 캐싱을 지원하는지 확인
273
+ */
274
+ export function supportsCaching(modelId) {
275
+ const model = AI_MODELS[modelId];
276
+ return model?.supportsCaching || false;
277
+ }
278
+
188
279
  /**
189
280
  * 특정 모델이 특정 reasoning effort를 지원하는지 확인
190
281
  */
@@ -0,0 +1,71 @@
1
+ /**
2
+ * 전역 상수 정의
3
+ * 하드코딩된 값들을 중앙에서 관리합니다.
4
+ */
5
+
6
+ // ============================================
7
+ // Session 관련 상수
8
+ // ============================================
9
+
10
+ /** 서브미션 이름의 최대 길이 */
11
+ export const SUB_MISSION_MAX_LENGTH = 120;
12
+
13
+ /** 기본 최대 반복 횟수 */
14
+ export const DEFAULT_MAX_ITERATIONS = 50;
15
+
16
+ /** reasoning만 있는 응답의 최대 허용 횟수 */
17
+ export const MAX_REASONING_ONLY_RESPONSES = 5;
18
+
19
+ // ============================================
20
+ // Ripgrep 관련 상수
21
+ // ============================================
22
+
23
+ /** 기본 타임아웃 (ms) */
24
+ export const RIPGREP_DEFAULT_TIMEOUT_MS = 120000;
25
+
26
+ /** 기본 최대 매치 수 */
27
+ export const RIPGREP_DEFAULT_MAX_COUNT = 500;
28
+
29
+ /** 최대 출력 크기 (bytes) */
30
+ export const RIPGREP_MAX_OUTPUT_SIZE = 30000;
31
+
32
+ // ============================================
33
+ // File Reader 관련 상수
34
+ // ============================================
35
+
36
+ /** 파일 읽기 최대 줄 수 */
37
+ export const FILE_READER_MAX_LINES = 2000;
38
+
39
+ /** 파일 크기 제한 (bytes) - 10MB */
40
+ export const FILE_READER_MAX_SIZE_BYTES = 10 * 1024 * 1024;
41
+
42
+ // ============================================
43
+ // AI 요청 관련 상수
44
+ // ============================================
45
+
46
+ /** 컨텍스트 trim 후 재시도를 위한 메시지 최소 수 */
47
+ export const MIN_MESSAGES_FOR_RETRY = 3;
48
+
49
+ // ============================================
50
+ // UI/Output 관련 상수
51
+ // ============================================
52
+
53
+ /** 출력 클램핑 최대 길이 */
54
+ export const OUTPUT_CLAMP_MAX_LENGTH = 30000;
55
+
56
+ // ============================================
57
+ // MCP 관련 상수
58
+ // ============================================
59
+
60
+ /** MCP 서버 연결 타임아웃 (ms) */
61
+ export const MCP_CONNECTION_TIMEOUT_MS = 30000;
62
+
63
+ // ============================================
64
+ // 에러 재시도 관련 상수
65
+ // ============================================
66
+
67
+ /** 세션 저장 최대 재시도 횟수 */
68
+ export const SESSION_SAVE_MAX_RETRIES = 3;
69
+
70
+ /** 세션 저장 재시도 지연 (ms) */
71
+ export const SESSION_SAVE_RETRY_DELAY_MS = 1000;
@@ -5,7 +5,7 @@
5
5
  import React from 'react';
6
6
  import { Box, Text } from 'ink';
7
7
 
8
- export function HelpView({ commands }) {
8
+ export function HelpView({ commands, customCommands = [], skills = [] }) {
9
9
  // 커맨드를 카테고리별로 분류
10
10
  const aiCommands = commands.filter(cmd =>
11
11
  ['model', 'reasoning_effort', 'apikey'].includes(cmd.name)
@@ -59,6 +59,96 @@ export function HelpView({ commands }) {
59
59
  );
60
60
  };
61
61
 
62
+ // 커스텀 커맨드 렌더링
63
+ const renderCustomCommands = () => {
64
+ if (customCommands.length === 0) return null;
65
+
66
+ return React.createElement(Box, {
67
+ flexDirection: 'column',
68
+ borderStyle: 'round',
69
+ borderColor: 'gray',
70
+ paddingX: 2,
71
+ paddingY: 1,
72
+ marginBottom: 1
73
+ },
74
+ React.createElement(Text, {
75
+ bold: true,
76
+ color: 'cyan'
77
+ }, '📋 Custom Commands'),
78
+ React.createElement(Text, null),
79
+
80
+ customCommands.map(cmd =>
81
+ React.createElement(Box, {
82
+ key: cmd.name,
83
+ flexDirection: 'column',
84
+ marginBottom: 1
85
+ },
86
+ React.createElement(Box, { flexDirection: 'row', gap: 1 },
87
+ React.createElement(Text, { color: 'green', bold: true }, '•'),
88
+ React.createElement(Text, { color: 'white', bold: true },
89
+ `/${cmd.name}${cmd.argumentHint ? ' ' + cmd.argumentHint : ''}`
90
+ ),
91
+ React.createElement(Text, { color: 'gray', dimColor: true },
92
+ ` (${cmd.source})`
93
+ )
94
+ ),
95
+ cmd.description && React.createElement(Box, {
96
+ flexDirection: 'row',
97
+ marginLeft: 2
98
+ },
99
+ React.createElement(Text, { color: 'gray' },
100
+ cmd.description.substring(0, 60) + (cmd.description.length > 60 ? '...' : '')
101
+ )
102
+ )
103
+ )
104
+ )
105
+ );
106
+ };
107
+
108
+ // 스킬 렌더링
109
+ const renderSkills = () => {
110
+ if (skills.length === 0) return null;
111
+
112
+ return React.createElement(Box, {
113
+ flexDirection: 'column',
114
+ borderStyle: 'round',
115
+ borderColor: 'gray',
116
+ paddingX: 2,
117
+ paddingY: 1,
118
+ marginBottom: 1
119
+ },
120
+ React.createElement(Text, {
121
+ bold: true,
122
+ color: 'cyan'
123
+ }, '🎯 Skills'),
124
+ React.createElement(Text, null),
125
+
126
+ skills.map(skill =>
127
+ React.createElement(Box, {
128
+ key: skill.name,
129
+ flexDirection: 'column',
130
+ marginBottom: 1
131
+ },
132
+ React.createElement(Box, { flexDirection: 'row', gap: 1 },
133
+ React.createElement(Text, { color: 'green', bold: true }, '•'),
134
+ React.createElement(Text, { color: 'white', bold: true }, `/${skill.name}`),
135
+ React.createElement(Text, { color: 'gray', dimColor: true },
136
+ ` (${skill.source})`
137
+ )
138
+ ),
139
+ skill.description && React.createElement(Box, {
140
+ flexDirection: 'row',
141
+ marginLeft: 2
142
+ },
143
+ React.createElement(Text, { color: 'gray' },
144
+ skill.description.substring(0, 60) + (skill.description.length > 60 ? '...' : '')
145
+ )
146
+ )
147
+ )
148
+ )
149
+ );
150
+ };
151
+
62
152
  return React.createElement(Box, {
63
153
  flexDirection: 'column',
64
154
  paddingX: 2,
@@ -89,6 +179,12 @@ export function HelpView({ commands }) {
89
179
  // Other Commands
90
180
  renderCommandGroup('🔧 Other', otherCommands),
91
181
 
182
+ // Custom Commands
183
+ renderCustomCommands(),
184
+
185
+ // Skills
186
+ renderSkills(),
187
+
92
188
  // Footer
93
189
  React.createElement(Box, {
94
190
  flexDirection: 'column',
@@ -104,7 +200,15 @@ export function HelpView({ commands }) {
104
200
  React.createElement(Text, {
105
201
  dimColor: true,
106
202
  italic: true
107
- }, '💡 Press Ctrl+C to cancel current operation')
203
+ }, '💡 Press Ctrl+C to cancel current operation'),
204
+ React.createElement(Text, {
205
+ dimColor: true,
206
+ italic: true
207
+ }, '💡 Custom commands: ~/.aiexe/commands/ or .aiexe/commands/'),
208
+ React.createElement(Text, {
209
+ dimColor: true,
210
+ italic: true
211
+ }, '💡 Skills: ~/.aiexe/skills/ or .aiexe/skills/')
108
212
  )
109
213
  );
110
214
  }
@@ -5,7 +5,24 @@
5
5
  import React, { useState, useRef } from 'react';
6
6
  import { Box, Text, useInput } from 'ink';
7
7
  import { theme } from '../design/themeColors.js';
8
- import { AI_MODELS, getAllModelIds, DEFAULT_MODEL } from '../../config/ai_models.js';
8
+ import { AI_MODELS, getAllModelIds, getModelsByProvider, DEFAULT_MODEL } from '../../config/ai_models.js';
9
+
10
+ /**
11
+ * API 키 스타일로 provider 감지
12
+ * @param {string} apiKey - API 키
13
+ * @returns {string|null} provider 이름 또는 null
14
+ */
15
+ function detectProviderFromApiKey(apiKey) {
16
+ if (!apiKey || typeof apiKey !== 'string') return null;
17
+
18
+ if (apiKey.startsWith('sk-proj-')) {
19
+ return 'openai';
20
+ } else if (/^[a-f0-9]{32}\.[A-Za-z0-9]{16}$/.test(apiKey)) {
21
+ // Z.AI API 키 형식: 32자리 hex + '.' + 16자리 영숫자
22
+ return 'zai';
23
+ }
24
+ return null;
25
+ }
9
26
 
10
27
  const STEPS = {
11
28
  API_KEY: 'api_key',
@@ -17,6 +34,8 @@ export function SetupWizard({ onComplete, onCancel }) {
17
34
  const [step, setStep] = useState(STEPS.API_KEY);
18
35
  const [selectedIndex, setSelectedIndex] = useState(0);
19
36
  const [textInput, setTextInput] = useState('');
37
+ const [detectedProvider, setDetectedProvider] = useState(null);
38
+ const [errorMessage, setErrorMessage] = useState('');
20
39
 
21
40
  // settings를 ref로 관리하여 stale closure 문제 방지
22
41
  const settingsRef = useRef({
@@ -25,6 +44,14 @@ export function SetupWizard({ onComplete, onCancel }) {
25
44
  REASONING_EFFORT: 'medium'
26
45
  });
27
46
 
47
+ // 감지된 provider에 따른 모델 목록
48
+ const getAvailableModels = () => {
49
+ if (detectedProvider) {
50
+ return getModelsByProvider(detectedProvider);
51
+ }
52
+ return getAllModelIds();
53
+ };
54
+
28
55
  // 현재 스텝이 텍스트 입력인지 선택지인지 판단
29
56
  const isTextInputStep = step === STEPS.API_KEY;
30
57
 
@@ -41,14 +68,26 @@ export function SetupWizard({ onComplete, onCancel }) {
41
68
  if (!textInput.trim()) {
42
69
  return;
43
70
  }
44
- settingsRef.current.API_KEY = textInput.trim();
71
+ const apiKey = textInput.trim();
72
+ // API 키 스타일로 provider 감지
73
+ const provider = detectProviderFromApiKey(apiKey);
74
+ if (!provider) {
75
+ // 유효하지 않은 API 키 형식
76
+ setErrorMessage('Invalid API key. Please use OpenAI (sk-proj-...) or Z.AI format.');
77
+ setTextInput('');
78
+ return;
79
+ }
80
+ // 유효한 API 키
81
+ setErrorMessage('');
82
+ settingsRef.current.API_KEY = apiKey;
83
+ setDetectedProvider(provider);
45
84
  setStep(STEPS.MODEL);
46
85
  setTextInput('');
47
86
  setSelectedIndex(0);
48
87
  break;
49
88
 
50
89
  case STEPS.MODEL:
51
- const models = getAllModelIds();
90
+ const models = getAvailableModels();
52
91
  const selectedModel = models[selectedIndex];
53
92
  settingsRef.current.MODEL = selectedModel;
54
93
 
@@ -97,6 +136,10 @@ export function SetupWizard({ onComplete, onCancel }) {
97
136
 
98
137
  if (input && !key.ctrl && !key.meta) {
99
138
  setTextInput(prev => prev + input);
139
+ // 입력 시 에러 메시지 클리어
140
+ if (errorMessage) {
141
+ setErrorMessage('');
142
+ }
100
143
  }
101
144
  return;
102
145
  }
@@ -118,7 +161,7 @@ export function SetupWizard({ onComplete, onCancel }) {
118
161
  const getMaxIndexForStep = (currentStep) => {
119
162
  switch (currentStep) {
120
163
  case STEPS.MODEL:
121
- return getAllModelIds().length - 1;
164
+ return getAvailableModels().length - 1;
122
165
  case STEPS.REASONING_EFFORT:
123
166
  return 3; // 4 options
124
167
  default:
@@ -145,16 +188,18 @@ export function SetupWizard({ onComplete, onCancel }) {
145
188
  case STEPS.API_KEY:
146
189
  return React.createElement(Box, { flexDirection: 'column' },
147
190
  React.createElement(Text, { bold: true, color: theme.text.accent }, '1. API Key:'),
148
- React.createElement(Text, { color: theme.text.secondary }, ' Get your API key from: https://platform.openai.com/account/api-keys or https://console.anthropic.com/settings/keys'),
191
+ React.createElement(Text, { color: theme.text.secondary }, ' Get your API key from: https://platform.openai.com/account/api-keys or https://z.ai/manage-apikey/apikey-list'),
149
192
  React.createElement(Text, null),
150
193
  React.createElement(Box, {
151
194
  borderStyle: 'round',
152
- borderColor: theme.border.focused,
195
+ borderColor: errorMessage ? theme.status.error : theme.border.focused,
153
196
  paddingX: 1
154
197
  },
155
198
  React.createElement(Text, null, textInput ? '*'.repeat(textInput.length) : ' ')
156
199
  ),
157
- React.createElement(Text, null),
200
+ errorMessage
201
+ ? React.createElement(Text, { color: theme.status.error }, errorMessage)
202
+ : React.createElement(Text, null),
158
203
  React.createElement(Text, { dimColor: true }, 'Type your API key and press Enter')
159
204
  );
160
205
 
@@ -163,7 +208,7 @@ export function SetupWizard({ onComplete, onCancel }) {
163
208
  React.createElement(Text, { bold: true, color: theme.text.accent }, '2. Choose Model:'),
164
209
  React.createElement(Text, null),
165
210
  renderOptions(
166
- getAllModelIds().map(modelId => {
211
+ getAvailableModels().map(modelId => {
167
212
  const model = AI_MODELS[modelId];
168
213
  return `${modelId} (${model.name})`;
169
214
  })