hanseol-dev 4.4.0-dev.1 → 4.5.0-dev.0

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.
Files changed (183) hide show
  1. package/dist/agents/index.d.ts +1 -2
  2. package/dist/agents/index.d.ts.map +1 -1
  3. package/dist/agents/index.js +0 -1
  4. package/dist/agents/index.js.map +1 -1
  5. package/dist/agents/planner/index.d.ts +0 -4
  6. package/dist/agents/planner/index.d.ts.map +1 -1
  7. package/dist/agents/planner/index.js +19 -13
  8. package/dist/agents/planner/index.js.map +1 -1
  9. package/dist/cli.js +9 -1
  10. package/dist/cli.js.map +1 -1
  11. package/dist/constants.d.ts +3 -1
  12. package/dist/constants.d.ts.map +1 -1
  13. package/dist/constants.js +3 -1
  14. package/dist/constants.js.map +1 -1
  15. package/dist/core/auth/auth-gate.d.ts.map +1 -1
  16. package/dist/core/auth/auth-gate.js +3 -3
  17. package/dist/core/auth/auth-gate.js.map +1 -1
  18. package/dist/core/background-sync.d.ts +39 -0
  19. package/dist/core/background-sync.d.ts.map +1 -0
  20. package/dist/core/background-sync.js +249 -0
  21. package/dist/core/background-sync.js.map +1 -0
  22. package/dist/core/compact/compact-manager.d.ts.map +1 -1
  23. package/dist/core/compact/compact-manager.js +4 -2
  24. package/dist/core/compact/compact-manager.js.map +1 -1
  25. package/dist/core/config/config-manager.js +1 -1
  26. package/dist/core/config/config-manager.js.map +1 -1
  27. package/dist/core/docs-manager.js +3 -3
  28. package/dist/core/docs-manager.js.map +1 -1
  29. package/dist/core/llm/llm-client.d.ts +2 -0
  30. package/dist/core/llm/llm-client.d.ts.map +1 -1
  31. package/dist/core/llm/llm-client.js +232 -40
  32. package/dist/core/llm/llm-client.js.map +1 -1
  33. package/dist/core/s3-auto-updater.js +3 -3
  34. package/dist/core/s3-auto-updater.js.map +1 -1
  35. package/dist/core/session/session-manager.d.ts +0 -1
  36. package/dist/core/session/session-manager.d.ts.map +1 -1
  37. package/dist/core/session/session-manager.js +5 -3
  38. package/dist/core/session/session-manager.js.map +1 -1
  39. package/dist/core/slash-command-handler.js +1 -1
  40. package/dist/core/slash-command-handler.js.map +1 -1
  41. package/dist/core/telemetry/error-reporter.d.ts.map +1 -1
  42. package/dist/core/telemetry/error-reporter.js +31 -9
  43. package/dist/core/telemetry/error-reporter.js.map +1 -1
  44. package/dist/core/usage-tracker.js +1 -1
  45. package/dist/core/usage-tracker.js.map +1 -1
  46. package/dist/orchestration/plan-executor.d.ts +0 -1
  47. package/dist/orchestration/plan-executor.d.ts.map +1 -1
  48. package/dist/orchestration/plan-executor.js +171 -59
  49. package/dist/orchestration/plan-executor.js.map +1 -1
  50. package/dist/prompts/agents/planning.d.ts.map +1 -1
  51. package/dist/prompts/agents/planning.js +25 -0
  52. package/dist/prompts/agents/planning.js.map +1 -1
  53. package/dist/prompts/index.d.ts +1 -3
  54. package/dist/prompts/index.d.ts.map +1 -1
  55. package/dist/prompts/index.js +1 -3
  56. package/dist/prompts/index.js.map +1 -1
  57. package/dist/prompts/shared/tool-usage.d.ts +2 -0
  58. package/dist/prompts/shared/tool-usage.d.ts.map +1 -1
  59. package/dist/prompts/shared/tool-usage.js +35 -1
  60. package/dist/prompts/shared/tool-usage.js.map +1 -1
  61. package/dist/prompts/system/plan-execute.d.ts +3 -0
  62. package/dist/prompts/system/plan-execute.d.ts.map +1 -1
  63. package/dist/prompts/system/plan-execute.js +47 -19
  64. package/dist/prompts/system/plan-execute.js.map +1 -1
  65. package/dist/tools/browser/browser-client.d.ts.map +1 -1
  66. package/dist/tools/browser/browser-client.js +4 -1
  67. package/dist/tools/browser/browser-client.js.map +1 -1
  68. package/dist/tools/llm/index.d.ts +0 -1
  69. package/dist/tools/llm/index.d.ts.map +1 -1
  70. package/dist/tools/llm/index.js +0 -1
  71. package/dist/tools/llm/index.js.map +1 -1
  72. package/dist/tools/llm/simple/bash-tool.js +1 -1
  73. package/dist/tools/llm/simple/bash-tool.js.map +1 -1
  74. package/dist/tools/llm/simple/external-services/external-service-api-clients.d.ts +54 -0
  75. package/dist/tools/llm/simple/external-services/external-service-api-clients.d.ts.map +1 -0
  76. package/dist/tools/llm/simple/external-services/external-service-api-clients.js +208 -0
  77. package/dist/tools/llm/simple/external-services/external-service-api-clients.js.map +1 -0
  78. package/dist/tools/llm/simple/external-services/free-tools.d.ts +2 -0
  79. package/dist/tools/llm/simple/external-services/free-tools.d.ts.map +1 -0
  80. package/dist/tools/llm/simple/external-services/free-tools.js +2 -0
  81. package/dist/tools/llm/simple/external-services/free-tools.js.map +1 -0
  82. package/dist/tools/llm/simple/external-services/index.d.ts +3 -0
  83. package/dist/tools/llm/simple/external-services/index.d.ts.map +1 -0
  84. package/dist/tools/llm/simple/external-services/index.js +3 -0
  85. package/dist/tools/llm/simple/external-services/index.js.map +1 -0
  86. package/dist/tools/llm/simple/external-services/once-tools.d.ts +3 -0
  87. package/dist/tools/llm/simple/external-services/once-tools.d.ts.map +1 -0
  88. package/dist/tools/llm/simple/external-services/once-tools.js +106 -0
  89. package/dist/tools/llm/simple/external-services/once-tools.js.map +1 -0
  90. package/dist/tools/llm/simple/file-tools.d.ts +1 -1
  91. package/dist/tools/llm/simple/file-tools.d.ts.map +1 -1
  92. package/dist/tools/llm/simple/file-tools.js +49 -6
  93. package/dist/tools/llm/simple/file-tools.js.map +1 -1
  94. package/dist/tools/llm/simple/index.d.ts +1 -0
  95. package/dist/tools/llm/simple/index.d.ts.map +1 -1
  96. package/dist/tools/llm/simple/index.js +1 -0
  97. package/dist/tools/llm/simple/index.js.map +1 -1
  98. package/dist/tools/llm/simple/powershell-tool.d.ts.map +1 -1
  99. package/dist/tools/llm/simple/powershell-tool.js +16 -3
  100. package/dist/tools/llm/simple/powershell-tool.js.map +1 -1
  101. package/dist/tools/llm/simple/simple-tool-executor.d.ts +3 -0
  102. package/dist/tools/llm/simple/simple-tool-executor.d.ts.map +1 -1
  103. package/dist/tools/llm/simple/simple-tool-executor.js +9 -0
  104. package/dist/tools/llm/simple/simple-tool-executor.js.map +1 -1
  105. package/dist/tools/llm/simple/todo-tools.js +1 -1
  106. package/dist/tools/llm/simple/todo-tools.js.map +1 -1
  107. package/dist/tools/llm/simple/user-interaction-tools.js +1 -1
  108. package/dist/tools/llm/simple/user-interaction-tools.js.map +1 -1
  109. package/dist/tools/office/excel-client.d.ts +1 -0
  110. package/dist/tools/office/excel-client.d.ts.map +1 -1
  111. package/dist/tools/office/excel-client.js +7 -4
  112. package/dist/tools/office/excel-client.js.map +1 -1
  113. package/dist/tools/office/excel-tools/cells.d.ts.map +1 -1
  114. package/dist/tools/office/excel-tools/cells.js +5 -1
  115. package/dist/tools/office/excel-tools/cells.js.map +1 -1
  116. package/dist/tools/office/excel-tools/sheets.d.ts.map +1 -1
  117. package/dist/tools/office/excel-tools/sheets.js +6 -1
  118. package/dist/tools/office/excel-tools/sheets.js.map +1 -1
  119. package/dist/tools/office/excel-tools/tables.d.ts.map +1 -1
  120. package/dist/tools/office/excel-tools/tables.js +6 -1
  121. package/dist/tools/office/excel-tools/tables.js.map +1 -1
  122. package/dist/tools/office/office-client-base.d.ts +1 -0
  123. package/dist/tools/office/office-client-base.d.ts.map +1 -1
  124. package/dist/tools/office/office-client-base.js +12 -1
  125. package/dist/tools/office/office-client-base.js.map +1 -1
  126. package/dist/tools/office/powerpoint-client.d.ts +1 -0
  127. package/dist/tools/office/powerpoint-client.d.ts.map +1 -1
  128. package/dist/tools/office/powerpoint-client.js +9 -1
  129. package/dist/tools/office/powerpoint-client.js.map +1 -1
  130. package/dist/tools/office/word-client.d.ts +1 -0
  131. package/dist/tools/office/word-client.d.ts.map +1 -1
  132. package/dist/tools/office/word-client.js +7 -2
  133. package/dist/tools/office/word-client.js.map +1 -1
  134. package/dist/tools/registry.d.ts.map +1 -1
  135. package/dist/tools/registry.js +8 -5
  136. package/dist/tools/registry.js.map +1 -1
  137. package/dist/ui/components/ActivityIndicator.d.ts +1 -1
  138. package/dist/ui/components/ActivityIndicator.d.ts.map +1 -1
  139. package/dist/ui/components/ActivityIndicator.js +0 -1
  140. package/dist/ui/components/ActivityIndicator.js.map +1 -1
  141. package/dist/ui/components/PlanExecuteApp.d.ts +1 -1
  142. package/dist/ui/components/PlanExecuteApp.d.ts.map +1 -1
  143. package/dist/ui/components/PlanExecuteApp.js +33 -73
  144. package/dist/ui/components/PlanExecuteApp.js.map +1 -1
  145. package/dist/ui/components/dialogs/DocsBrowser.js +2 -2
  146. package/dist/ui/components/dialogs/DocsBrowser.js.map +1 -1
  147. package/dist/ui/hooks/useFileBrowserState.js +1 -1
  148. package/dist/ui/hooks/useFileBrowserState.js.map +1 -1
  149. package/dist/ui/hooks/useFileList.js +2 -2
  150. package/dist/ui/hooks/useFileList.js.map +1 -1
  151. package/dist/ui/ink-entry.js +1 -1
  152. package/dist/ui/ink-entry.js.map +1 -1
  153. package/dist/utils/json-stream-logger.js +0 -2
  154. package/dist/utils/json-stream-logger.js.map +1 -1
  155. package/dist/utils/logger.d.ts +4 -0
  156. package/dist/utils/logger.d.ts.map +1 -1
  157. package/dist/utils/logger.js +18 -0
  158. package/dist/utils/logger.js.map +1 -1
  159. package/package.json +3 -1
  160. package/dist/agents/docs-search/index.d.ts +0 -33
  161. package/dist/agents/docs-search/index.d.ts.map +0 -1
  162. package/dist/agents/docs-search/index.js +0 -244
  163. package/dist/agents/docs-search/index.js.map +0 -1
  164. package/dist/prompts/agents/docs-search-decision.d.ts +0 -6
  165. package/dist/prompts/agents/docs-search-decision.d.ts.map +0 -1
  166. package/dist/prompts/agents/docs-search-decision.js +0 -46
  167. package/dist/prompts/agents/docs-search-decision.js.map +0 -1
  168. package/dist/prompts/agents/docs-search.d.ts +0 -4
  169. package/dist/prompts/agents/docs-search.d.ts.map +0 -1
  170. package/dist/prompts/agents/docs-search.js +0 -70
  171. package/dist/prompts/agents/docs-search.js.map +0 -1
  172. package/dist/tools/llm/agents/docs-search-tools.d.ts +0 -17
  173. package/dist/tools/llm/agents/docs-search-tools.d.ts.map +0 -1
  174. package/dist/tools/llm/agents/docs-search-tools.js +0 -265
  175. package/dist/tools/llm/agents/docs-search-tools.js.map +0 -1
  176. package/dist/tools/llm/simple/docs-search-agent-tool.d.ts +0 -6
  177. package/dist/tools/llm/simple/docs-search-agent-tool.d.ts.map +0 -1
  178. package/dist/tools/llm/simple/docs-search-agent-tool.js +0 -104
  179. package/dist/tools/llm/simple/docs-search-agent-tool.js.map +0 -1
  180. package/dist/ui/components/DocsSearchProgress.d.ts +0 -13
  181. package/dist/ui/components/DocsSearchProgress.d.ts.map +0 -1
  182. package/dist/ui/components/DocsSearchProgress.js +0 -37
  183. package/dist/ui/components/DocsSearchProgress.js.map +0 -1
@@ -36,14 +36,33 @@ export class LLMClient {
36
36
  });
37
37
  }
38
38
  preprocessMessages(messages, modelId) {
39
- return messages.map((msg) => {
39
+ let lastAssistantIdx = -1;
40
+ for (let i = messages.length - 1; i >= 0; i--) {
41
+ if (messages[i].role === 'assistant') {
42
+ lastAssistantIdx = i;
43
+ break;
44
+ }
45
+ }
46
+ return messages.map((msg, index) => {
40
47
  if (msg.role !== 'assistant') {
41
48
  return msg;
42
49
  }
43
50
  const msgAny = msg;
44
- let processedMsg = { ...msg };
45
- if (msgAny.reasoning_content && (!msg.content || msg.content.trim() === '')) {
46
- processedMsg.content = msgAny.reasoning_content;
51
+ const processedMsg = { ...msg };
52
+ const isLatestAssistant = index === lastAssistantIdx;
53
+ if (!isLatestAssistant) {
54
+ if (msgAny.reasoning_content) {
55
+ delete processedMsg.reasoning_content;
56
+ }
57
+ if (msgAny.reasoning) {
58
+ delete processedMsg.reasoning;
59
+ }
60
+ if (processedMsg.content) {
61
+ processedMsg.content = processedMsg.content.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
62
+ }
63
+ }
64
+ if (msgAny.reasoning_content && (!processedMsg.content || processedMsg.content.trim() === '')) {
65
+ processedMsg.content = isLatestAssistant ? msgAny.reasoning_content : '';
47
66
  delete processedMsg.reasoning_content;
48
67
  }
49
68
  if (/^gpt-oss-(120b|20b)$/i.test(modelId)) {
@@ -110,7 +129,7 @@ export class LLMClient {
110
129
  const elapsed = logger.endTimer('llm-api-call');
111
130
  logger.flow('API 응답 수신 완료');
112
131
  if (!response.data.choices || !Array.isArray(response.data.choices)) {
113
- logger.error('Invalid response structure - missing choices array', response.data);
132
+ logger.errorSilent('Invalid response structure - missing choices array', response.data);
114
133
  throw new Error('LLM 응답 형식이 올바르지 않습니다. choices 배열이 없습니다.');
115
134
  }
116
135
  logger.httpResponse(response.status, response.statusText, {
@@ -169,7 +188,7 @@ export class LLMClient {
169
188
  }
170
189
  logger.flow('API 호출 실패 - 에러 처리');
171
190
  if (currentAttempt > 1) {
172
- logger.error(`LLM 호출 ${maxRetries}번 재시도 후 최종 실패`, {
191
+ logger.errorSilent(`LLM 호출 ${maxRetries}번 재시도 후 최종 실패`, {
173
192
  error: error.message,
174
193
  attempts: currentAttempt
175
194
  });
@@ -180,7 +199,7 @@ export class LLMClient {
180
199
  url,
181
200
  body: options,
182
201
  });
183
- reportError(handled, { type: 'llm', method: 'chatCompletion', endpoint: url }).catch(() => { });
202
+ reportError(handled, { type: 'llm', method: 'chatCompletion', endpoint: url, modelId: this.model, modelName: this.modelName }).catch(() => { });
184
203
  throw handled;
185
204
  }
186
205
  }
@@ -317,7 +336,7 @@ export class LLMClient {
317
336
  url,
318
337
  body: options,
319
338
  });
320
- reportError(handled, { type: 'llm', method: 'chatCompletionStream', endpoint: url }).catch(() => { });
339
+ reportError(handled, { type: 'llm', method: 'chatCompletionStream', endpoint: url, modelId: this.model, modelName: this.modelName }).catch(() => { });
321
340
  throw handled;
322
341
  }
323
342
  }
@@ -380,24 +399,60 @@ export class LLMClient {
380
399
  }
381
400
  async chatCompletionWithTools(messages, tools, options) {
382
401
  let workingMessages = [...messages];
402
+ const toolLoopMessages = [];
383
403
  const toolCallHistory = [];
384
404
  let iterations = 0;
385
405
  let contextLengthRecoveryAttempted = false;
386
406
  let noToolCallRetries = 0;
387
407
  let finalResponseFailures = 0;
408
+ let consecutiveParseFailures = 0;
388
409
  const MAX_NO_TOOL_CALL_RETRIES = 3;
389
410
  const MAX_FINAL_RESPONSE_FAILURES = 3;
411
+ const MAX_CONSECUTIVE_PARSE_FAILURES = 3;
412
+ const parseFailureToolCallIds = new Set();
413
+ const stripParseFailures = (msgs) => {
414
+ if (parseFailureToolCallIds.size === 0)
415
+ return msgs;
416
+ return msgs.filter(msg => {
417
+ if (msg.role === 'tool' && msg.tool_call_id && parseFailureToolCallIds.has(msg.tool_call_id))
418
+ return false;
419
+ if (msg.role === 'assistant' && msg.tool_calls && msg.tool_calls.length > 0 &&
420
+ msg.tool_calls.every(tc => parseFailureToolCallIds.has(tc.id)))
421
+ return false;
422
+ return true;
423
+ });
424
+ };
425
+ const addMessage = (msg) => {
426
+ workingMessages.push(msg);
427
+ if (options?.rebuildMessages) {
428
+ toolLoopMessages.push(msg);
429
+ }
430
+ };
431
+ const getAllMessages = () => {
432
+ return options?.rebuildMessages
433
+ ? stripParseFailures(toolLoopMessages)
434
+ : stripParseFailures(workingMessages);
435
+ };
390
436
  while (true) {
391
437
  if (this.isInterrupted) {
392
438
  logger.flow('Interrupt detected - stopping tool loop');
393
439
  throw new Error('INTERRUPTED');
394
440
  }
395
441
  iterations++;
442
+ if (options?.rebuildMessages) {
443
+ workingMessages = options.rebuildMessages(toolLoopMessages);
444
+ }
396
445
  if (options?.getPendingMessage && options?.clearPendingMessage) {
397
446
  const pendingMsg = options.getPendingMessage();
398
447
  if (pendingMsg) {
399
448
  logger.flow('Injecting pending user message into conversation');
400
- workingMessages.push({ role: 'user', content: pendingMsg });
449
+ if (options?.rebuildMessages) {
450
+ toolLoopMessages.push({ role: 'user', content: pendingMsg });
451
+ workingMessages = options.rebuildMessages(toolLoopMessages);
452
+ }
453
+ else {
454
+ workingMessages.push({ role: 'user', content: pendingMsg });
455
+ }
401
456
  options.clearPendingMessage();
402
457
  }
403
458
  }
@@ -412,7 +467,19 @@ export class LLMClient {
412
467
  catch (error) {
413
468
  if (error instanceof ContextLengthError && !contextLengthRecoveryAttempted) {
414
469
  contextLengthRecoveryAttempted = true;
415
- logger.flow('ContextLengthError detected - attempting recovery with compact');
470
+ logger.flow('ContextLengthError detected - attempting recovery');
471
+ if (options?.rebuildMessages) {
472
+ let rollbackIdx = toolLoopMessages.length - 1;
473
+ while (rollbackIdx >= 0 && toolLoopMessages[rollbackIdx]?.role === 'tool') {
474
+ rollbackIdx--;
475
+ }
476
+ if (rollbackIdx >= 0 && toolLoopMessages[rollbackIdx]?.tool_calls) {
477
+ toolLoopMessages.length = rollbackIdx;
478
+ logger.debug('Rolled back toolLoopMessages', { newLength: toolLoopMessages.length });
479
+ }
480
+ continue;
481
+ }
482
+ logger.flow('Attempting recovery with compact');
416
483
  let rollbackIdx = workingMessages.length - 1;
417
484
  while (rollbackIdx >= 0 && workingMessages[rollbackIdx]?.role === 'tool') {
418
485
  rollbackIdx--;
@@ -441,7 +508,7 @@ export class LLMClient {
441
508
  continue;
442
509
  }
443
510
  else {
444
- logger.error('Compact failed during recovery', { error: compactResult.error });
511
+ logger.errorSilent('Compact failed during recovery', { error: compactResult.error });
445
512
  throw error;
446
513
  }
447
514
  }
@@ -456,7 +523,7 @@ export class LLMClient {
456
523
  throw new Error('응답에서 choice를 찾을 수 없습니다.');
457
524
  }
458
525
  const assistantMessage = choice.message;
459
- workingMessages.push(assistantMessage);
526
+ addMessage(assistantMessage);
460
527
  if (assistantMessage.tool_calls && assistantMessage.tool_calls.length > 0) {
461
528
  if (assistantMessage.tool_calls.length > 1) {
462
529
  const toolNames = assistantMessage.tool_calls.map(tc => tc.function.name).join(', ');
@@ -464,18 +531,86 @@ export class LLMClient {
464
531
  assistantMessage.tool_calls = [assistantMessage.tool_calls[0]];
465
532
  }
466
533
  for (const toolCall of assistantMessage.tool_calls) {
467
- const toolName = toolCall.function.name;
534
+ const rawToolName = toolCall.function.name;
535
+ const toolName = rawToolName.replace(/<\|.*$/, '').replace(/[^a-zA-Z0-9_-]+$/, '').trim() || rawToolName;
536
+ if (toolName !== rawToolName) {
537
+ logger.warn('Tool name sanitized (model leaked special tokens)', {
538
+ original: rawToolName,
539
+ sanitized: toolName,
540
+ model: this.model,
541
+ });
542
+ toolCall.function.name = toolName;
543
+ reportError(new Error(`Tool name contaminated: ${rawToolName}`), {
544
+ type: 'toolNameContamination',
545
+ original: rawToolName,
546
+ sanitized: toolName,
547
+ modelId: this.model,
548
+ modelName: this.modelName,
549
+ }).catch(() => { });
550
+ }
468
551
  let toolArgs;
469
552
  try {
470
553
  toolArgs = JSON.parse(toolCall.function.arguments);
471
554
  }
472
555
  catch (parseError) {
473
- const errorMsg = `Tool argument parsing failed for ${toolName}`;
474
- logger.error(errorMsg, parseError);
556
+ consecutiveParseFailures++;
557
+ parseFailureToolCallIds.add(toolCall.id);
558
+ const errorMsg = `Tool argument parsing failed for ${toolName}: ${parseError instanceof Error ? parseError.message : 'Unknown error'}`;
559
+ logger.errorSilent('Tool argument parse error', {
560
+ toolName,
561
+ error: errorMsg,
562
+ consecutiveFailures: consecutiveParseFailures,
563
+ });
475
564
  logger.debug('Raw arguments', { raw: toolCall.function.arguments });
476
- workingMessages.push({
565
+ reportError(parseError, {
566
+ type: 'toolArgParsing',
567
+ tool: toolName,
568
+ consecutiveFailures: consecutiveParseFailures,
569
+ modelId: this.model,
570
+ modelName: this.modelName,
571
+ rawArguments: typeof toolCall.function.arguments === 'string' ? toolCall.function.arguments.substring(0, 500) : undefined,
572
+ }).catch(() => { });
573
+ if (consecutiveParseFailures >= MAX_CONSECUTIVE_PARSE_FAILURES) {
574
+ logger.errorSilent('[ABORT] Tool argument parse failed 3 times consecutively. Model may not support JSON function calling.');
575
+ const abortMsg = 'I cannot generate valid JSON tool arguments. Please try a different model that supports JSON function calling.';
576
+ addMessage({
577
+ role: 'tool',
578
+ content: errorMsg,
579
+ tool_call_id: toolCall.id,
580
+ });
581
+ return {
582
+ message: { role: 'assistant', content: abortMsg },
583
+ toolCalls: toolCallHistory,
584
+ allMessages: getAllMessages(),
585
+ };
586
+ }
587
+ const rawArgs = toolCall.function.arguments;
588
+ const rawPreview = typeof rawArgs === 'string' ? rawArgs.substring(0, 300) : String(rawArgs);
589
+ const hintMsg = `Error: Failed to parse tool arguments for "${toolName}".
590
+
591
+ Parse error: ${parseError instanceof Error ? parseError.message : 'Unknown error'}
592
+
593
+ Your raw input was:
594
+ \`\`\`
595
+ ${rawPreview}
596
+ \`\`\`
597
+
598
+ Fix the following issues:
599
+ 1. Arguments MUST be valid JSON (not XML, not plain text)
600
+ 2. All strings must use double quotes ("), not single quotes (')
601
+ 3. No trailing commas after the last property
602
+ 4. No comments inside JSON
603
+ 5. Escape special characters in strings (\\n, \\", \\\\)
604
+
605
+ Correct format example:
606
+ \`\`\`json
607
+ {"reason": "description", "file_path": "src/index.ts"}
608
+ \`\`\`
609
+
610
+ Do NOT use XML tags like <arg_key> or <arg_value>. Retry with valid JSON.`;
611
+ addMessage({
477
612
  role: 'tool',
478
- content: `Error: Failed to parse tool arguments - ${parseError instanceof Error ? parseError.message : 'Unknown error'}`,
613
+ content: hintMsg,
479
614
  tool_call_id: toolCall.id,
480
615
  });
481
616
  toolCallHistory.push({
@@ -485,6 +620,59 @@ export class LLMClient {
485
620
  });
486
621
  continue;
487
622
  }
623
+ const toolDef = tools.find(t => t.function.name === toolName);
624
+ if (toolDef?.function.parameters) {
625
+ const schema = toolDef.function.parameters;
626
+ const schemaErrors = [];
627
+ if (schema.required) {
628
+ for (const req of schema.required) {
629
+ if (toolArgs[req] === undefined || toolArgs[req] === null) {
630
+ const propDef = schema.properties[req];
631
+ schemaErrors.push(`Missing required parameter: "${req}" (expected: ${propDef?.type || 'unknown'})`);
632
+ }
633
+ }
634
+ }
635
+ for (const [key, value] of Object.entries(toolArgs)) {
636
+ const propDef = schema.properties[key];
637
+ if (propDef?.type && value !== null && value !== undefined) {
638
+ const actualType = Array.isArray(value) ? 'array' : typeof value;
639
+ if (actualType !== propDef.type) {
640
+ schemaErrors.push(`"${key}": expected ${propDef.type}, got ${actualType} (${JSON.stringify(value).substring(0, 50)})`);
641
+ }
642
+ }
643
+ }
644
+ if (schemaErrors.length > 0) {
645
+ consecutiveParseFailures++;
646
+ parseFailureToolCallIds.add(toolCall.id);
647
+ if (consecutiveParseFailures >= MAX_CONSECUTIVE_PARSE_FAILURES) {
648
+ const abortMsg = 'Cannot generate valid tool arguments. Please try a different model.';
649
+ addMessage({ role: 'tool', content: schemaErrors.join('\n'), tool_call_id: toolCall.id });
650
+ return {
651
+ message: { role: 'assistant', content: abortMsg },
652
+ toolCalls: toolCallHistory,
653
+ allMessages: getAllMessages(),
654
+ };
655
+ }
656
+ const requiredList = (schema.required || [])
657
+ .map(r => {
658
+ const p = schema.properties[r];
659
+ return ` "${r}": ${p?.type || 'unknown'}`;
660
+ })
661
+ .join('\n');
662
+ const hintMsg = `Error: Schema validation failed for "${toolName}".
663
+
664
+ ${schemaErrors.join('\n')}
665
+
666
+ Required parameters:
667
+ ${requiredList}
668
+
669
+ Retry with correct parameter names and types.`;
670
+ addMessage({ role: 'tool', content: hintMsg, tool_call_id: toolCall.id });
671
+ toolCallHistory.push({ tool: toolName, args: toolArgs, result: 'Error: Schema validation failed' });
672
+ continue;
673
+ }
674
+ }
675
+ consecutiveParseFailures = 0;
488
676
  const { executeFileTool, requestToolApproval } = await import('../../tools/llm/simple/file-tools.js');
489
677
  const approvalResult = await requestToolApproval(toolName, toolArgs);
490
678
  if (approvalResult && typeof approvalResult === 'object' && approvalResult.reject) {
@@ -492,7 +680,7 @@ export class LLMClient {
492
680
  const rejectMessage = approvalResult.comment
493
681
  ? `Tool execution rejected by user. Reason: ${approvalResult.comment}`
494
682
  : 'Tool execution rejected by user.';
495
- workingMessages.push({
683
+ addMessage({
496
684
  role: 'tool',
497
685
  content: rejectMessage,
498
686
  tool_call_id: toolCall.id,
@@ -515,7 +703,7 @@ export class LLMClient {
515
703
  if (toolName === 'final_response') {
516
704
  if (result.success && result.metadata?.['isFinalResponse']) {
517
705
  logger.flow('final_response tool executed successfully - returning');
518
- workingMessages.push({
706
+ addMessage({
519
707
  role: 'tool',
520
708
  content: result.result || '',
521
709
  tool_call_id: toolCall.id,
@@ -531,7 +719,7 @@ export class LLMClient {
531
719
  content: result.result || '',
532
720
  },
533
721
  toolCalls: toolCallHistory,
534
- allMessages: workingMessages,
722
+ allMessages: getAllMessages(),
535
723
  };
536
724
  }
537
725
  else {
@@ -545,7 +733,7 @@ export class LLMClient {
545
733
  return {
546
734
  message: { role: 'assistant', content: fallbackMessage },
547
735
  toolCalls: toolCallHistory,
548
- allMessages: workingMessages,
736
+ allMessages: getAllMessages(),
549
737
  };
550
738
  }
551
739
  }
@@ -553,6 +741,7 @@ export class LLMClient {
553
741
  }
554
742
  catch (toolError) {
555
743
  logger.toolExecution(toolName, toolArgs, undefined, toolError);
744
+ reportError(toolError, { type: 'toolExecution', tool: toolName, modelId: this.model, modelName: this.modelName, toolArgs }).catch(() => { });
556
745
  if (isLLMLogEnabled()) {
557
746
  const errorMsg = toolError instanceof Error ? toolError.message : String(toolError);
558
747
  logger.llmToolResult(toolName, `Error: ${errorMsg}`, false);
@@ -562,7 +751,7 @@ export class LLMClient {
562
751
  error: toolError instanceof Error ? toolError.message : String(toolError),
563
752
  };
564
753
  }
565
- workingMessages.push({
754
+ addMessage({
566
755
  role: 'tool',
567
756
  content: result.success ? result.result || '' : `Error: ${result.error}`,
568
757
  tool_call_id: toolCall.id,
@@ -577,6 +766,9 @@ export class LLMClient {
577
766
  throw new Error('INTERRUPTED');
578
767
  }
579
768
  }
769
+ if (options?.onAfterToolExecution) {
770
+ await options.onAfterToolExecution(toolLoopMessages);
771
+ }
580
772
  continue;
581
773
  }
582
774
  else {
@@ -590,7 +782,7 @@ export class LLMClient {
590
782
  return {
591
783
  message: { role: 'assistant', content: fallbackContent },
592
784
  toolCalls: toolCallHistory,
593
- allMessages: workingMessages,
785
+ allMessages: getAllMessages(),
594
786
  };
595
787
  }
596
788
  const hasMalformedToolCall = assistantMessage.content &&
@@ -602,7 +794,7 @@ export class LLMClient {
602
794
  const retryMessage = hasMalformedToolCall
603
795
  ? 'Your previous response contained a malformed tool call (XML tags in content). You MUST use the proper tool_calls API format. Use final_response tool to deliver your message to the user.'
604
796
  : 'You must use tools for all actions. Use final_response tool to deliver your final message to the user after completing all tasks.';
605
- workingMessages.push({
797
+ addMessage({
606
798
  role: 'user',
607
799
  content: retryMessage,
608
800
  });
@@ -618,14 +810,14 @@ export class LLMClient {
618
810
  };
619
811
  }
620
812
  handleError(error, requestContext) {
621
- logger.error('LLM Client Error', error);
813
+ logger.errorSilent('LLM Client Error', error);
622
814
  if (requestContext) {
623
815
  logger.debug('Request Context', requestContext);
624
816
  }
625
817
  if (axios.isAxiosError(error)) {
626
818
  const axiosError = error;
627
819
  if (axiosError.code === 'ECONNABORTED' || axiosError.message.includes('timeout')) {
628
- logger.error('Request Timeout', {
820
+ logger.errorSilent('Request Timeout', {
629
821
  timeout: this.axiosInstance.defaults.timeout,
630
822
  endpoint: this.baseUrl,
631
823
  });
@@ -644,7 +836,7 @@ export class LLMClient {
644
836
  const errorMessage = data?.error?.message || data?.message || axiosError.message;
645
837
  const errorType = data?.error?.type || 'unknown';
646
838
  const errorCode = data?.error?.code || data?.code;
647
- logger.error('=== API ERROR DETAILS ===', {
839
+ logger.errorSilent('=== API ERROR DETAILS ===', {
648
840
  status,
649
841
  statusText: axiosError.response.statusText,
650
842
  endpoint: this.baseUrl,
@@ -666,7 +858,7 @@ export class LLMClient {
666
858
  errorMessage.includes('maximum context length') ||
667
859
  errorCode === 'context_length_exceeded')) {
668
860
  const maxLength = data?.error?.param?.max_tokens || 'unknown';
669
- logger.error('Context Length Exceeded', {
861
+ logger.errorSilent('Context Length Exceeded', {
670
862
  maxLength,
671
863
  errorMessage,
672
864
  model: this.model,
@@ -683,7 +875,7 @@ export class LLMClient {
683
875
  }
684
876
  if (errorMessage.includes('token') &&
685
877
  (errorMessage.includes('limit') || errorMessage.includes('exceeded'))) {
686
- logger.error('Token Limit Error', {
878
+ logger.errorSilent('Token Limit Error', {
687
879
  errorMessage,
688
880
  model: this.model,
689
881
  });
@@ -699,7 +891,7 @@ export class LLMClient {
699
891
  }
700
892
  if (status === 429) {
701
893
  if (errorType === 'quota_exceeded' && data?.error?.quota) {
702
- logger.error('Quota Exceeded', {
894
+ logger.errorSilent('Quota Exceeded', {
703
895
  period: data.error.quota.period,
704
896
  weekly: data.error.quota.weekly,
705
897
  });
@@ -707,7 +899,7 @@ export class LLMClient {
707
899
  }
708
900
  const retryAfter = axiosError.response.headers['retry-after'];
709
901
  const retrySeconds = retryAfter ? parseInt(retryAfter) : undefined;
710
- logger.error('Rate Limit Exceeded', {
902
+ logger.errorSilent('Rate Limit Exceeded', {
711
903
  retryAfter: retrySeconds,
712
904
  errorMessage,
713
905
  });
@@ -721,7 +913,7 @@ export class LLMClient {
721
913
  });
722
914
  }
723
915
  if (status === 401) {
724
- logger.error('Authentication Failed', {
916
+ logger.errorSilent('Authentication Failed', {
725
917
  endpoint: this.baseUrl,
726
918
  errorMessage,
727
919
  });
@@ -737,7 +929,7 @@ export class LLMClient {
737
929
  });
738
930
  }
739
931
  if (status === 403) {
740
- logger.error('Access Forbidden', {
932
+ logger.errorSilent('Access Forbidden', {
741
933
  endpoint: this.baseUrl,
742
934
  errorMessage,
743
935
  });
@@ -750,7 +942,7 @@ export class LLMClient {
750
942
  });
751
943
  }
752
944
  if (status === 404) {
753
- logger.error('Endpoint Not Found', {
945
+ logger.errorSilent('Endpoint Not Found', {
754
946
  endpoint: this.baseUrl,
755
947
  url: requestContext?.url,
756
948
  errorMessage,
@@ -766,7 +958,7 @@ export class LLMClient {
766
958
  });
767
959
  }
768
960
  if (status >= 500) {
769
- logger.error('Server Error', {
961
+ logger.errorSilent('Server Error', {
770
962
  status,
771
963
  endpoint: this.baseUrl,
772
964
  errorMessage,
@@ -779,7 +971,7 @@ export class LLMClient {
779
971
  isRecoverable: true,
780
972
  });
781
973
  }
782
- logger.error('API Error', {
974
+ logger.errorSilent('API Error', {
783
975
  status,
784
976
  endpoint: this.baseUrl,
785
977
  errorMessage,
@@ -798,7 +990,7 @@ export class LLMClient {
798
990
  }
799
991
  else if (axiosError.request) {
800
992
  const errorCode = axiosError.code;
801
- logger.error('Network Error - No Response', {
993
+ logger.errorSilent('Network Error - No Response', {
802
994
  code: errorCode,
803
995
  endpoint: this.baseUrl,
804
996
  message: axiosError.message,
@@ -825,7 +1017,7 @@ export class LLMClient {
825
1017
  userMessage: `네트워크 연결 실패.\n엔드포인트: ${this.baseUrl}\n에러: ${axiosError.message}`,
826
1018
  });
827
1019
  }
828
- logger.error('Axios Error', {
1020
+ logger.errorSilent('Axios Error', {
829
1021
  code: axiosError.code,
830
1022
  message: axiosError.message,
831
1023
  });
@@ -837,13 +1029,13 @@ export class LLMClient {
837
1029
  });
838
1030
  }
839
1031
  if (error instanceof Error) {
840
- logger.error('Unexpected Error', error);
1032
+ logger.errorSilent('Unexpected Error', error);
841
1033
  return new LLMError(`예상치 못한 에러: ${error.message}`, {
842
1034
  cause: error,
843
1035
  userMessage: `오류가 발생했습니다:\n${error.message}\n\n스택:\n${error.stack}`,
844
1036
  });
845
1037
  }
846
- logger.error('Unknown Error Type', { error });
1038
+ logger.errorSilent('Unknown Error Type', { error });
847
1039
  return new LLMError('알 수 없는 에러가 발생했습니다.', {
848
1040
  details: { unknownError: error },
849
1041
  });