aiexecode 1.0.72 → 1.0.74

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.

@@ -1,9 +1,10 @@
1
1
  import dotenv from "dotenv";
2
- import { request, isContextWindowError, getModelForProvider } from "../system/ai_request.js";
2
+ import { request, shouldRetryWithTrim, getModelForProvider } from "../system/ai_request.js";
3
3
  import { getOrchestratorConversation } from "./orchestrator.js";
4
4
  import { createSystemMessage } from "../util/prompt_loader.js";
5
5
  import { createDebugLogger } from "../util/debug_log.js";
6
6
  import { getCurrentTodos } from "../system/session_memory.js";
7
+ import { cleanupOrphanOutputs, trimConversation } from "../system/conversation_trimmer.js";
7
8
 
8
9
  dotenv.config({ quiet: true });
9
10
 
@@ -116,16 +117,7 @@ function syncOrchestratorConversation() {
116
117
  debugLog(`[syncOrchestratorConversation] END`);
117
118
  }
118
119
 
119
- function trimCompletionJudgeConversation() {
120
- for (let i = 1; i < completionJudgeConversation.length - 1; i++) {
121
- const candidate = completionJudgeConversation[i];
122
- if (candidate?.role !== "system") {
123
- completionJudgeConversation.splice(i, 1);
124
- return true;
125
- }
126
- }
127
- return false;
128
- }
120
+ // 대화 cleanup 및 trim 함수는 conversation_trimmer 모듈에서 가져옴
129
121
 
130
122
  async function dispatchCompletionJudgeRequest(options) {
131
123
  debugLog(`[dispatchCompletionJudgeRequest] START`);
@@ -140,9 +132,10 @@ async function dispatchCompletionJudgeRequest(options) {
140
132
  let attemptCount = 0;
141
133
  while (true) {
142
134
  attemptCount++;
143
- debugLog(`[dispatchCompletionJudgeRequest] Attempt ${attemptCount}`);
135
+ debugLog(`[dispatchCompletionJudgeRequest] Attempt ${attemptCount} - starting cleanup...`);
136
+ cleanupOrphanOutputs(completionJudgeConversation);
144
137
 
145
- const { model, isGpt5Model, taskName } = options;
138
+ debugLog(`[dispatchCompletionJudgeRequest] Conversation has ${completionJudgeConversation.length} entries`);
146
139
 
147
140
  // Conversation 구조 분석
148
141
  const conversationTypes = {};
@@ -151,7 +144,16 @@ async function dispatchCompletionJudgeRequest(options) {
151
144
  conversationTypes[type] = (conversationTypes[type] || 0) + 1;
152
145
  });
153
146
  debugLog(`[dispatchCompletionJudgeRequest] Conversation structure: ${JSON.stringify(conversationTypes)}`);
154
- debugLog(`[dispatchCompletionJudgeRequest] Total conversation entries: ${completionJudgeConversation.length}`);
147
+
148
+ // function_call_output 항목들 확인
149
+ const functionCallOutputs = completionJudgeConversation.filter(item => item.type === 'function_call_output');
150
+ debugLog(`[dispatchCompletionJudgeRequest] Found ${functionCallOutputs.length} function_call_output entries`);
151
+
152
+ functionCallOutputs.forEach((item, index) => {
153
+ debugLog(`[dispatchCompletionJudgeRequest] function_call_output[${index}] - call_id: ${item.call_id}, output length: ${item.output?.length || 0}, output preview: ${item.output?.substring(0, 200)}`);
154
+ });
155
+
156
+ const { model, isGpt5Model, taskName } = options;
155
157
 
156
158
  const requestPayload = {
157
159
  model,
@@ -197,13 +199,13 @@ async function dispatchCompletionJudgeRequest(options) {
197
199
  throw error;
198
200
  }
199
201
 
200
- if (!isContextWindowError(error)) {
201
- debugLog(`[dispatchCompletionJudgeRequest] Not a context window error, re-throwing`);
202
+ if (!shouldRetryWithTrim(error)) {
203
+ debugLog(`[dispatchCompletionJudgeRequest] Not recoverable by trimming, re-throwing`);
202
204
  throw error;
203
205
  }
204
206
 
205
- debugLog(`[dispatchCompletionJudgeRequest] Context window error detected, attempting to trim...`);
206
- const trimmed = trimCompletionJudgeConversation();
207
+ debugLog(`[dispatchCompletionJudgeRequest] Recoverable error detected, attempting to trim...`);
208
+ const trimmed = trimConversation(completionJudgeConversation);
207
209
  debugLog(`[dispatchCompletionJudgeRequest] Trim result: ${trimmed}, new conversation length: ${completionJudgeConversation.length}`);
208
210
  if (!trimmed) {
209
211
  debugLog(`[dispatchCompletionJudgeRequest] Cannot trim further, re-throwing error`);
@@ -3,7 +3,8 @@ import dotenv from "dotenv";
3
3
  import path from 'path';
4
4
  import { truncateWithOmit } from "../util/text_formatter.js";
5
5
  import { createSystemMessage } from "../util/prompt_loader.js";
6
- import { request, isContextWindowError, getModelForProvider } from "../system/ai_request.js";
6
+ import { request, shouldRetryWithTrim, getModelForProvider } from "../system/ai_request.js";
7
+ import { cleanupOrphanOutputs, trimConversation } from "../system/conversation_trimmer.js";
7
8
  import { runPythonCodeSchema, bashSchema } from "../system/code_executer.js";
8
9
  import { readFileSchema, readFileRangeSchema } from "../tools/file_reader.js";
9
10
  import { writeFileSchema, editFileRangeSchema, editFileReplaceSchema } from "../tools/code_editor.js";
@@ -120,18 +121,6 @@ export function recordOrchestratorToolResult({ toolCallId, toolName = '', argume
120
121
  return null;
121
122
  }
122
123
 
123
- if (toolName === 'read_file') {
124
- const filePath = toolArguments.filePath || '';
125
- const absolutePath = path.isAbsolute(filePath)
126
- ? filePath
127
- : path.join(process.cwd(), filePath);
128
-
129
- stdout = JSON.stringify({
130
- absolute_file_path: absolutePath,
131
- content: stdout
132
- });
133
- }
134
-
135
124
  const payload = {
136
125
  tool: toolName || null,
137
126
  call_id: toolCallId,
@@ -154,146 +143,7 @@ export function recordOrchestratorToolResult({ toolCallId, toolName = '', argume
154
143
  return toolResult;
155
144
  }
156
145
 
157
- function extractCallIdsFromItem(item) {
158
- if (!item || typeof item !== 'object') {
159
- return [];
160
- }
161
-
162
- const callIds = new Set();
163
-
164
- const maybeIds = [item.call_id, item.tool_call_id, item.id];
165
- for (const id of maybeIds) {
166
- if (typeof id === 'string' && id.trim().length) {
167
- callIds.add(id);
168
- }
169
- }
170
-
171
- if (item.function && typeof item.function === 'object') {
172
- const fnIds = [item.function.call_id, item.function.id];
173
- for (const id of fnIds) {
174
- if (typeof id === 'string' && id.trim().length) {
175
- callIds.add(id);
176
- }
177
- }
178
- }
179
-
180
- if (Array.isArray(item.tool_calls)) {
181
- for (const toolCall of item.tool_calls) {
182
- extractCallIdsFromItem(toolCall).forEach(id => callIds.add(id));
183
- }
184
- }
185
-
186
- return Array.from(callIds);
187
- }
188
-
189
- function removeConversationEntriesByCallIds(callIds) {
190
- if (!Array.isArray(callIds) || callIds.length === 0) {
191
- return;
192
- }
193
-
194
- const callIdSet = new Set(callIds.filter(id => typeof id === 'string' && id.trim().length));
195
- if (!callIdSet.size) {
196
- return;
197
- }
198
-
199
- for (let index = orchestratorConversation.length - 1; index >= 0; index--) {
200
- const entry = orchestratorConversation[index];
201
- if (!entry || typeof entry !== 'object') {
202
- continue;
203
- }
204
-
205
- if (entry.type === 'function_call_output') {
206
- if (callIdSet.has(entry.call_id)) {
207
- orchestratorConversation.splice(index, 1);
208
- }
209
- continue;
210
- }
211
-
212
- if (Array.isArray(entry.tool_calls) && entry.tool_calls.length) {
213
- const filteredToolCalls = entry.tool_calls.filter(toolCall => {
214
- const ids = extractCallIdsFromItem(toolCall);
215
- return !ids.some(id => callIdSet.has(id));
216
- });
217
-
218
- if (filteredToolCalls.length !== entry.tool_calls.length) {
219
- if (filteredToolCalls.length === 0 && (!entry.content || entry.content.length === 0)) {
220
- orchestratorConversation.splice(index, 1);
221
- } else {
222
- entry.tool_calls = filteredToolCalls;
223
- }
224
- continue;
225
- }
226
- }
227
-
228
- const entryCallIds = extractCallIdsFromItem(entry);
229
- if (entryCallIds.some(id => callIdSet.has(id))) {
230
- orchestratorConversation.splice(index, 1);
231
- }
232
- }
233
- }
234
-
235
- function cleanupOrchestratorConversation() {
236
- debugLog(`[cleanupOrchestratorConversation] Starting cleanup, conversation length: ${orchestratorConversation.length}`);
237
-
238
- const validCallIds = new Set();
239
-
240
- for (const entry of orchestratorConversation) {
241
- if (!entry || typeof entry !== 'object') {
242
- continue;
243
- }
244
-
245
- if (entry.type === 'function_call') {
246
- const ids = extractCallIdsFromItem(entry);
247
- debugLog(`[cleanupOrchestratorConversation] Found function_call with call_ids: ${ids.join(', ')}`);
248
- ids.forEach(id => validCallIds.add(id));
249
- }
250
-
251
- if (Array.isArray(entry.tool_calls)) {
252
- for (const toolCall of entry.tool_calls) {
253
- const ids = extractCallIdsFromItem(toolCall);
254
- debugLog(`[cleanupOrchestratorConversation] Found tool_call with call_ids: ${ids.join(', ')}`);
255
- ids.forEach(id => validCallIds.add(id));
256
- }
257
- }
258
- }
259
-
260
- debugLog(`[cleanupOrchestratorConversation] Valid call IDs: ${Array.from(validCallIds).join(', ')}`);
261
-
262
- for (let index = orchestratorConversation.length - 1; index >= 0; index--) {
263
- const entry = orchestratorConversation[index];
264
- if (entry?.type === 'function_call_output') {
265
- const callId = entry.call_id;
266
- if (!callId || !validCallIds.has(callId)) {
267
- debugLog(`[cleanupOrchestratorConversation] REMOVING function_call_output at index ${index} with call_id: ${callId} (not in validCallIds)`);
268
- orchestratorConversation.splice(index, 1);
269
- } else {
270
- debugLog(`[cleanupOrchestratorConversation] KEEPING function_call_output at index ${index} with call_id: ${callId}, output length: ${entry.output?.length || 0}`);
271
- }
272
- }
273
- }
274
-
275
- debugLog(`[cleanupOrchestratorConversation] Finished cleanup, conversation length: ${orchestratorConversation.length}`);
276
- }
277
-
278
- function trimOrchestratorConversation() {
279
- if (orchestratorConversation.length <= 2) {
280
- return false;
281
- }
282
-
283
- for (let i = 1; i < orchestratorConversation.length - 1; i++) {
284
- const target = orchestratorConversation[i];
285
- const callIds = extractCallIdsFromItem(target);
286
-
287
- orchestratorConversation.splice(i, 1);
288
-
289
- if (callIds.length) {
290
- removeConversationEntriesByCallIds(callIds);
291
- }
292
-
293
- return true;
294
- }
295
- return false;
296
- }
146
+ // 대화 cleanup 및 trim 함수는 conversation_trimmer 모듈에서 가져옴
297
147
 
298
148
  async function dispatchOrchestratorRequest({ toolChoice }) {
299
149
  debugLog(`[dispatchOrchestratorRequest] START - toolChoice: ${toolChoice}`);
@@ -309,7 +159,7 @@ async function dispatchOrchestratorRequest({ toolChoice }) {
309
159
  while (true) {
310
160
  attemptCount++;
311
161
  debugLog(`[dispatchOrchestratorRequest] Attempt ${attemptCount} - starting cleanup...`);
312
- cleanupOrchestratorConversation();
162
+ cleanupOrphanOutputs(orchestratorConversation);
313
163
 
314
164
  debugLog(`[dispatchOrchestratorRequest] Conversation has ${orchestratorConversation.length} entries`);
315
165
 
@@ -370,15 +220,21 @@ async function dispatchOrchestratorRequest({ toolChoice }) {
370
220
  return response;
371
221
  } catch (error) {
372
222
  debugLog(`[dispatchOrchestratorRequest] API request failed: ${error.message}`);
373
- debugLog(`[dispatchOrchestratorRequest] Error type: ${error?.constructor?.name}, code: ${error?.code}, status: ${error?.status}`);
223
+ debugLog(`[dispatchOrchestratorRequest] Error name: ${error.name}, type: ${error?.constructor?.name}`);
224
+
225
+ // AbortError는 즉시 전파 (세션 중단)
226
+ if (error.name === 'AbortError') {
227
+ debugLog(`[dispatchOrchestratorRequest] Request aborted by user, propagating AbortError`);
228
+ throw error;
229
+ }
374
230
 
375
- if (!isContextWindowError(error)) {
376
- debugLog(`[dispatchOrchestratorRequest] Not a context window error, re-throwing`);
231
+ if (!shouldRetryWithTrim(error)) {
232
+ debugLog(`[dispatchOrchestratorRequest] Not recoverable by trimming, re-throwing`);
377
233
  throw error;
378
234
  }
379
235
 
380
- debugLog(`[dispatchOrchestratorRequest] Context window error detected, attempting to trim conversation...`);
381
- const trimmed = trimOrchestratorConversation();
236
+ debugLog(`[dispatchOrchestratorRequest] Recoverable error detected, attempting to trim conversation...`);
237
+ const trimmed = trimConversation(orchestratorConversation);
382
238
  debugLog(`[dispatchOrchestratorRequest] Trim result: ${trimmed}, new conversation length: ${orchestratorConversation.length}`);
383
239
  if (!trimmed) {
384
240
  debugLog(`[dispatchOrchestratorRequest] Cannot trim further, re-throwing error`);
@@ -193,10 +193,36 @@ export async function request(taskName, requestPayload) {
193
193
  }
194
194
 
195
195
  debugLog(`[request] Starting payload transformation...`);
196
+ const isValidJSON = (json) => {
197
+ if (typeof json !== 'string') return false;
198
+ try {
199
+ JSON.parse(json);
200
+ return true;
201
+ } catch {
202
+ return false;
203
+ }
204
+ }
205
+ if (true && payloadCopy.input && Array.isArray(payloadCopy.input)) {
206
+ for (let i = payloadCopy.input.length - 1; i >= 0; i--) {
207
+ const msg = payloadCopy.input[i];
208
+ const { type, call_id, output } = msg;
209
+ if (type !== 'function_call_output') continue;
210
+ const parsedOutput = JSON.parse(output);
211
+ if (!isValidJSON(parsedOutput.stdout)) {
212
+ msg.output = parsedOutput.stdout;
213
+ } else {
214
+ parsedOutput.stdout = JSON.parse(parsedOutput.stdout);
215
+ if (parsedOutput.original_result) {
216
+ parsedOutput.stdout = ({ ...parsedOutput.original_result, ...(parsedOutput.stdout) });
217
+ delete parsedOutput.original_result;
218
+ }
219
+ msg.output = parsedOutput;
220
+ }
221
+ }
222
+ }
196
223
  if (payloadCopy.input && Array.isArray(payloadCopy.input)) {
197
224
  const marker = {};
198
225
  const remove_call_id = {};//[];
199
- const response_message_call_id = {};
200
226
 
201
227
  debugLog(`[request] Processing ${payloadCopy.input.length} input messages for deduplication and formatting`);
202
228
  let processedCount = 0;
@@ -207,105 +233,182 @@ export async function request(taskName, requestPayload) {
207
233
  if (type !== 'function_call_output') continue;
208
234
  processedCount++;
209
235
 
210
- debugLog(`[ai_request] Processing function_call_output, call_id: ${call_id}, output length: ${output?.length || 0}`);
236
+ if ((typeof output) === 'string') continue;
237
+ const { tool, stdout, stderr, exit_code, original_result } = output;
211
238
 
212
- const parsedOutput = JSON.parse(output);
213
- const { tool, stdout, stderr, exit_code } = parsedOutput;
214
-
215
- debugLog(`[ai_request] Parsed output - tool: ${tool}, stdout: ${stdout?.length || 0} bytes, stderr: ${stderr?.length || 0} bytes`);
216
-
217
- if (tool === 'edit_file_range') {
218
- const parsedStdout = JSON.parse(stdout);
219
- if (!parsedStdout.absolute_file_path) continue;
220
- let abolute_path = toAbsolutePath(parsedStdout.absolute_file_path)
221
- const updated_content = parsedStdout.file_stats?.updated_content;
222
- delete parsedStdout.absolute_file_path;
223
- delete parsedStdout.file_stats;
224
- if (marker[abolute_path]) {
225
- parsedStdout.target_file_path;
226
- } else {
227
- if (updated_content) {
228
- parsedStdout.updated_content = formatReadFileStdout({ file_lines: updated_content.split('\n') });
229
- }
230
- }
231
- marker[abolute_path] = true;
232
- msg.output = JSON.stringify(parsedStdout, null, 2);
233
- debugLog(`[ai_request] edit_file_range - new output length: ${msg.output.length}`);
234
- }
235
- else if (tool === 'edit_file_replace') {
236
- const parsedStdout = JSON.parse(stdout);
237
- if (!parsedStdout.absolute_file_path) continue;
238
- let abolute_path = toAbsolutePath(parsedStdout.absolute_file_path)
239
- const updated_content = parsedStdout.file_stats?.updated_content;
240
- delete parsedStdout.absolute_file_path;
241
- delete parsedStdout.file_stats;
242
- if (marker[abolute_path]) {
243
- parsedStdout.target_file_path;
244
- } else {
245
- if (updated_content) {
246
- parsedStdout.updated_content = formatReadFileStdout({ file_lines: updated_content.split('\n') });
247
- }
248
- }
239
+ if (stdout?.operation_successful?.constructor === Boolean && !stdout.operation_successful) continue;
240
+ if (tool === 'edit_file_range' || tool === 'edit_file_replace') {
241
+ let abolute_path = toAbsolutePath(stdout.target_file_path);
242
+ const updated_content = stdout.file_stats?.updated_content;
243
+ delete stdout.file_stats;
244
+ if (!marker[abolute_path] && updated_content) stdout.updated_content = updated_content.split('\n');
249
245
  marker[abolute_path] = true;
250
- msg.output = JSON.stringify(parsedStdout, null, 2);
251
- debugLog(`[ai_request] edit_file_replace - new output length: ${msg.output.length}`);
252
246
  }
253
247
  else if (tool === 'read_file') {
254
- const parsedStdout = JSON.parse(stdout);
255
- if (!parsedStdout.absolute_file_path) continue;
256
- let abolute_path = toAbsolutePath(parsedStdout.absolute_file_path)
257
- const content = parsedStdout.content;
248
+ let abolute_path = toAbsolutePath(stdout.target_file_path);
258
249
  if (marker[abolute_path]) {
259
- remove_call_id[call_id] = true;//.push(call_id);
260
- msg.output = '(표시하지 않음)';
261
- } else {
262
- msg.output = content;
250
+ remove_call_id[call_id] = true;
251
+ stdout.file_content = '(Not displayed)';
263
252
  }
264
253
  marker[abolute_path] = true;
265
- debugLog(`[ai_request] read_file - new output length: ${msg.output.length}`);
266
- // msg.output = 'FG';
267
- } else {
268
- debugLog(`[ai_request] OTHER TOOL (${tool}) - BEFORE: output is full JSON with stderr`);
269
- // run_python_code, bash 등의 경우 stdout과 stderr를 모두 포함
270
- let combinedOutput = '';
271
- if (stdout && stderr) {
272
- combinedOutput = `stdout:\n${stdout}\n\nstderr:\n${stderr}`;
273
- } else if (stdout) {
274
- combinedOutput = stdout;
275
- } else if (stderr) {
276
- combinedOutput = `stderr:\n${stderr}`;
277
- }
278
- msg.output = combinedOutput;
279
- debugLog(`[ai_request] OTHER TOOL (${tool}) - AFTER: output length: ${msg.output.length} bytes (stdout: ${stdout?.length || 0}, stderr: ${stderr?.length || 0})`);
254
+ }
255
+ else {
256
+ // debugLog(`[ai_request] OTHER TOOL (${tool}) - BEFORE: output is full JSON with stderr`);
257
+ // // run_python_code, bash 등의 경우 stdout과 stderr를 모두 포함
258
+ // let combinedOutput = '';
259
+ // if (stdout && stderr) {
260
+ // combinedOutput = `stdout:\n${stdout}\n\nstderr:\n${stderr}`;
261
+ // } else if (stdout) {
262
+ // combinedOutput = stdout;
263
+ // } else if (stderr) {
264
+ // combinedOutput = `stderr:\n${stderr}`;
265
+ // }
266
+ // msg.output = combinedOutput;
267
+ // debugLog(`[ai_request] OTHER TOOL (${tool}) - AFTER: output length: ${msg.output.length} bytes (stdout: ${stdout?.length || 0}, stderr: ${stderr?.length || 0})`);
280
268
  }
281
269
  }
282
270
 
283
271
  debugLog(`[request] Processed ${processedCount} function_call_output entries`);
284
272
  debugLog(`[request] Marked files for deduplication: ${Object.keys(marker).length}`);
285
273
 
286
- let responseMessageCount = 0;
274
+ }
275
+ if (true && payloadCopy.input && Array.isArray(payloadCopy.input)) {
276
+ const response_message_call_id = {};
287
277
  for (let i = payloadCopy.input.length - 1; i >= 0; i--) {
288
278
  const msg = payloadCopy.input[i];
289
279
  const { type, call_id, name } = msg;
290
280
  if (type !== 'function_call') continue;
291
281
  if (name !== 'response_message') continue;
292
282
  response_message_call_id[call_id] = true;
293
- responseMessageCount++;
294
283
  }
295
- debugLog(`[request] Found ${responseMessageCount} response_message function calls`);
296
-
297
- let simplifiedResponseMessages = 0;
298
284
  for (let i = payloadCopy.input.length - 1; i >= 0; i--) {
299
285
  const msg = payloadCopy.input[i];
300
286
  const { type, call_id } = msg;
301
287
  if (type !== 'function_call_output') continue;
302
288
  if (!response_message_call_id[call_id]) continue;
303
- msg.output = JSON.stringify({ message_displayed: true });
304
- simplifiedResponseMessages++;
289
+ msg.output = {
290
+ tool: 'response_message',
291
+ stdout: { message_displayed: true }
292
+ };
305
293
  }
306
- debugLog(`[request] Simplified ${simplifiedResponseMessages} response_message outputs`);
307
294
  }
295
+ // Formatting
296
+ if (true && payloadCopy.input && Array.isArray(payloadCopy.input)) {
297
+ for (let i = payloadCopy.input.length - 1; i >= 0; i--) {
298
+ const msg = payloadCopy.input[i];
299
+ const { type, call_id, output } = msg;
300
+
301
+ if (type !== 'function_call_output') continue;
302
+ if (typeof output === 'string') continue;
303
+ const { tool, stdout, stderr, exit_code, original_result } = output;
304
+ if (false) await safeWriteFile('./__stdout/' + tool + '.' + (call_id) + '.json', JSON.stringify(stdout, null, 2));
305
+
306
+ // read_file
307
+ if (tool === 'read_file') {
308
+ if (!stdout.operation_successful) {
309
+ msg.output = stdout.error_message;
310
+ }
311
+ else {
312
+ if (stdout.file_content.includes('\n')) {
313
+ msg.output = formatReadFileStdout(stdout.file_content);
314
+ } else {
315
+ msg.output = (stdout.file_content);
316
+ }
317
+ }
318
+ continue;
319
+ }
320
+
321
+ // read_file_range
322
+ if (tool === 'read_file_range') {
323
+ if (!stdout.operation_successful) {
324
+ msg.output = stdout.error_message;
325
+ }
326
+ else {
327
+ msg.output = formatReadFileStdout(stdout.file_content, stdout.actual_range.start_line);
328
+ }
329
+ continue;
330
+ }
331
+
332
+ // write_file
333
+ if (tool === 'write_file') {
334
+ if (!stdout.operation_successful) {
335
+ msg.output = stdout.error_message;
336
+ } else {
337
+ msg.output = `File written successfully: ${stdout.target_file_path}`;
338
+ }
339
+ continue;
340
+ }
308
341
 
342
+ // edit_file_replace
343
+ if (tool === 'edit_file_replace') {
344
+ if (!stdout.operation_successful) {
345
+ msg.output = stdout.error_message;
346
+ } else {
347
+ // const isString = stdout?.updated_content?.constructor === String;
348
+ const newObj = {
349
+ unified_diff_patch: stdout.diff_info.unified_diff_patch,
350
+ operation_successful: stdout.operation_successful,
351
+ updated_content: formatReadFileStdout(stdout?.updated_content?.join('\n')),
352
+ };
353
+ if (!(stdout?.updated_content)) delete newObj.updated_content;
354
+ // if (!isString) delete newObj.updated_content;
355
+ msg.output = JSON.stringify(newObj, null, 2);
356
+ }
357
+ continue;
358
+ }
359
+
360
+ // edit_file_range
361
+ if (tool === 'edit_file_range') {
362
+ msg.output = JSON.stringify(stdout, null, 2);
363
+ continue;
364
+ }
365
+
366
+ // ripgrep
367
+ if (tool === 'ripgrep') {
368
+ msg.output = JSON.stringify(stdout, null, 2);
369
+ continue;
370
+ }
371
+
372
+ // glob_search
373
+ if (tool === 'glob_search') {
374
+ msg.output = JSON.stringify(stdout, null, 2);
375
+ continue;
376
+ }
377
+
378
+ // fetch_web_page
379
+ if (tool === 'fetch_web_page') {
380
+ msg.output = JSON.stringify(stdout, null, 2);
381
+ continue;
382
+ }
383
+
384
+ // response_message
385
+ if (tool === 'response_message') {
386
+ msg.output = JSON.stringify(stdout, null, 2);
387
+ continue;
388
+ }
389
+
390
+ // todo_write
391
+ if (tool === 'todo_write') {
392
+ msg.output = JSON.stringify(stdout, null, 2);
393
+ continue;
394
+ }
395
+
396
+ // bash (from code_executer.js)
397
+ if (tool === 'bash') {
398
+ msg.output = JSON.stringify(stdout, null, 2);
399
+ continue;
400
+ }
401
+
402
+ // run_python_code (from code_executer.js)
403
+ if (tool === 'run_python_code') {
404
+ msg.output = JSON.stringify(stdout, null, 2);
405
+ continue;
406
+ }
407
+
408
+ // Default case for unknown tools
409
+ msg.output = JSON.stringify(stdout, null, 2);
410
+ }
411
+ }
309
412
  debugLog(`[request] Payload transformation complete`);
310
413
  let response;
311
414
  let originalRequest;
@@ -424,37 +527,38 @@ export async function request(taskName, requestPayload) {
424
527
  }
425
528
 
426
529
  /**
427
- * OpenAI API 컨텍스트 윈도우 초과 에러인지 확인합니다.
530
+ * API 에러가 대화 trim 재시도로 복구 가능한지 확인합니다.
428
531
  *
429
- * 함수는 OpenAI API의 공식 에러 코드만을 신뢰하며,
430
- * 에러 메시지 문자열 매칭은 사용하지 않습니다.
532
+ * 처리 대상:
533
+ * - context_length_exceeded: 컨텍스트 윈도우 초과
534
+ * - 400 error: 잘못된 요청 (대화가 너무 길어서 발생 가능)
431
535
  *
432
536
  * @param {Error | Object} error - 확인할 에러 객체
433
- * @returns {boolean} 컨텍스트 윈도우 초과 에러 여부
537
+ * @returns {boolean} trim 재시도 가능 여부
434
538
  *
435
539
  * @see https://platform.openai.com/docs/guides/error-codes
436
540
  */
437
- export function isContextWindowError(error) {
541
+ export function shouldRetryWithTrim(error) {
438
542
  if (!error) return false;
439
543
 
440
544
  // OpenAI SDK의 공식 에러 코드 확인
441
- // error.code 또는 error.error.code에서 확인
442
545
  const errorCode = error?.code || error?.error?.code;
443
546
 
444
547
  if (errorCode === 'context_length_exceeded') {
445
- debugLog('[isContextWindowError] Detected: context_length_exceeded');
548
+ debugLog('[shouldRetryWithTrim] Detected: context_length_exceeded');
446
549
  return true;
447
550
  }
448
551
 
449
- // 감지되지 않은 에러 - 디버깅을 위해 로그 남김
552
+ // 400 에러도 trim 재시도 대상
450
553
  if (error?.status === 400 || error?.response?.status === 400) {
451
554
  const errorType = error?.type || error?.error?.type;
452
555
  const errorMessage = error?.message || error?.error?.message || '';
453
556
  debugLog(
454
- `[isContextWindowError] Not detected - ` +
455
- `Status: 400, Type: ${errorType}, Code: ${errorCode}, ` +
557
+ `[shouldRetryWithTrim] Detected 400 error - ` +
558
+ `Type: ${errorType}, Code: ${errorCode}, ` +
456
559
  `Message: ${errorMessage.substring(0, 200)}`
457
560
  );
561
+ return true;
458
562
  }
459
563
 
460
564
  return false;