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.
- package/dist/agents/index.d.ts +1 -2
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +0 -1
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/planner/index.d.ts +0 -4
- package/dist/agents/planner/index.d.ts.map +1 -1
- package/dist/agents/planner/index.js +19 -13
- package/dist/agents/planner/index.js.map +1 -1
- package/dist/cli.js +9 -1
- package/dist/cli.js.map +1 -1
- package/dist/constants.d.ts +3 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +3 -1
- package/dist/constants.js.map +1 -1
- package/dist/core/auth/auth-gate.d.ts.map +1 -1
- package/dist/core/auth/auth-gate.js +3 -3
- package/dist/core/auth/auth-gate.js.map +1 -1
- package/dist/core/background-sync.d.ts +39 -0
- package/dist/core/background-sync.d.ts.map +1 -0
- package/dist/core/background-sync.js +249 -0
- package/dist/core/background-sync.js.map +1 -0
- package/dist/core/compact/compact-manager.d.ts.map +1 -1
- package/dist/core/compact/compact-manager.js +4 -2
- package/dist/core/compact/compact-manager.js.map +1 -1
- package/dist/core/config/config-manager.js +1 -1
- package/dist/core/config/config-manager.js.map +1 -1
- package/dist/core/docs-manager.js +3 -3
- package/dist/core/docs-manager.js.map +1 -1
- package/dist/core/llm/llm-client.d.ts +2 -0
- package/dist/core/llm/llm-client.d.ts.map +1 -1
- package/dist/core/llm/llm-client.js +232 -40
- package/dist/core/llm/llm-client.js.map +1 -1
- package/dist/core/s3-auto-updater.js +3 -3
- package/dist/core/s3-auto-updater.js.map +1 -1
- package/dist/core/session/session-manager.d.ts +0 -1
- package/dist/core/session/session-manager.d.ts.map +1 -1
- package/dist/core/session/session-manager.js +5 -3
- package/dist/core/session/session-manager.js.map +1 -1
- package/dist/core/slash-command-handler.js +1 -1
- package/dist/core/slash-command-handler.js.map +1 -1
- package/dist/core/telemetry/error-reporter.d.ts.map +1 -1
- package/dist/core/telemetry/error-reporter.js +31 -9
- package/dist/core/telemetry/error-reporter.js.map +1 -1
- package/dist/core/usage-tracker.js +1 -1
- package/dist/core/usage-tracker.js.map +1 -1
- package/dist/orchestration/plan-executor.d.ts +0 -1
- package/dist/orchestration/plan-executor.d.ts.map +1 -1
- package/dist/orchestration/plan-executor.js +171 -59
- package/dist/orchestration/plan-executor.js.map +1 -1
- package/dist/prompts/agents/planning.d.ts.map +1 -1
- package/dist/prompts/agents/planning.js +25 -0
- package/dist/prompts/agents/planning.js.map +1 -1
- package/dist/prompts/index.d.ts +1 -3
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +1 -3
- package/dist/prompts/index.js.map +1 -1
- package/dist/prompts/shared/tool-usage.d.ts +2 -0
- package/dist/prompts/shared/tool-usage.d.ts.map +1 -1
- package/dist/prompts/shared/tool-usage.js +35 -1
- package/dist/prompts/shared/tool-usage.js.map +1 -1
- package/dist/prompts/system/plan-execute.d.ts +3 -0
- package/dist/prompts/system/plan-execute.d.ts.map +1 -1
- package/dist/prompts/system/plan-execute.js +47 -19
- package/dist/prompts/system/plan-execute.js.map +1 -1
- package/dist/tools/browser/browser-client.d.ts.map +1 -1
- package/dist/tools/browser/browser-client.js +4 -1
- package/dist/tools/browser/browser-client.js.map +1 -1
- package/dist/tools/llm/index.d.ts +0 -1
- package/dist/tools/llm/index.d.ts.map +1 -1
- package/dist/tools/llm/index.js +0 -1
- package/dist/tools/llm/index.js.map +1 -1
- package/dist/tools/llm/simple/bash-tool.js +1 -1
- package/dist/tools/llm/simple/bash-tool.js.map +1 -1
- package/dist/tools/llm/simple/external-services/external-service-api-clients.d.ts +54 -0
- package/dist/tools/llm/simple/external-services/external-service-api-clients.d.ts.map +1 -0
- package/dist/tools/llm/simple/external-services/external-service-api-clients.js +208 -0
- package/dist/tools/llm/simple/external-services/external-service-api-clients.js.map +1 -0
- package/dist/tools/llm/simple/external-services/free-tools.d.ts +2 -0
- package/dist/tools/llm/simple/external-services/free-tools.d.ts.map +1 -0
- package/dist/tools/llm/simple/external-services/free-tools.js +2 -0
- package/dist/tools/llm/simple/external-services/free-tools.js.map +1 -0
- package/dist/tools/llm/simple/external-services/index.d.ts +3 -0
- package/dist/tools/llm/simple/external-services/index.d.ts.map +1 -0
- package/dist/tools/llm/simple/external-services/index.js +3 -0
- package/dist/tools/llm/simple/external-services/index.js.map +1 -0
- package/dist/tools/llm/simple/external-services/once-tools.d.ts +3 -0
- package/dist/tools/llm/simple/external-services/once-tools.d.ts.map +1 -0
- package/dist/tools/llm/simple/external-services/once-tools.js +106 -0
- package/dist/tools/llm/simple/external-services/once-tools.js.map +1 -0
- package/dist/tools/llm/simple/file-tools.d.ts +1 -1
- package/dist/tools/llm/simple/file-tools.d.ts.map +1 -1
- package/dist/tools/llm/simple/file-tools.js +49 -6
- package/dist/tools/llm/simple/file-tools.js.map +1 -1
- package/dist/tools/llm/simple/index.d.ts +1 -0
- package/dist/tools/llm/simple/index.d.ts.map +1 -1
- package/dist/tools/llm/simple/index.js +1 -0
- package/dist/tools/llm/simple/index.js.map +1 -1
- package/dist/tools/llm/simple/powershell-tool.d.ts.map +1 -1
- package/dist/tools/llm/simple/powershell-tool.js +16 -3
- package/dist/tools/llm/simple/powershell-tool.js.map +1 -1
- package/dist/tools/llm/simple/simple-tool-executor.d.ts +3 -0
- package/dist/tools/llm/simple/simple-tool-executor.d.ts.map +1 -1
- package/dist/tools/llm/simple/simple-tool-executor.js +9 -0
- package/dist/tools/llm/simple/simple-tool-executor.js.map +1 -1
- package/dist/tools/llm/simple/todo-tools.js +1 -1
- package/dist/tools/llm/simple/todo-tools.js.map +1 -1
- package/dist/tools/llm/simple/user-interaction-tools.js +1 -1
- package/dist/tools/llm/simple/user-interaction-tools.js.map +1 -1
- package/dist/tools/office/excel-client.d.ts +1 -0
- package/dist/tools/office/excel-client.d.ts.map +1 -1
- package/dist/tools/office/excel-client.js +7 -4
- package/dist/tools/office/excel-client.js.map +1 -1
- package/dist/tools/office/excel-tools/cells.d.ts.map +1 -1
- package/dist/tools/office/excel-tools/cells.js +5 -1
- package/dist/tools/office/excel-tools/cells.js.map +1 -1
- package/dist/tools/office/excel-tools/sheets.d.ts.map +1 -1
- package/dist/tools/office/excel-tools/sheets.js +6 -1
- package/dist/tools/office/excel-tools/sheets.js.map +1 -1
- package/dist/tools/office/excel-tools/tables.d.ts.map +1 -1
- package/dist/tools/office/excel-tools/tables.js +6 -1
- package/dist/tools/office/excel-tools/tables.js.map +1 -1
- package/dist/tools/office/office-client-base.d.ts +1 -0
- package/dist/tools/office/office-client-base.d.ts.map +1 -1
- package/dist/tools/office/office-client-base.js +12 -1
- package/dist/tools/office/office-client-base.js.map +1 -1
- package/dist/tools/office/powerpoint-client.d.ts +1 -0
- package/dist/tools/office/powerpoint-client.d.ts.map +1 -1
- package/dist/tools/office/powerpoint-client.js +9 -1
- package/dist/tools/office/powerpoint-client.js.map +1 -1
- package/dist/tools/office/word-client.d.ts +1 -0
- package/dist/tools/office/word-client.d.ts.map +1 -1
- package/dist/tools/office/word-client.js +7 -2
- package/dist/tools/office/word-client.js.map +1 -1
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +8 -5
- package/dist/tools/registry.js.map +1 -1
- package/dist/ui/components/ActivityIndicator.d.ts +1 -1
- package/dist/ui/components/ActivityIndicator.d.ts.map +1 -1
- package/dist/ui/components/ActivityIndicator.js +0 -1
- package/dist/ui/components/ActivityIndicator.js.map +1 -1
- package/dist/ui/components/PlanExecuteApp.d.ts +1 -1
- package/dist/ui/components/PlanExecuteApp.d.ts.map +1 -1
- package/dist/ui/components/PlanExecuteApp.js +33 -73
- package/dist/ui/components/PlanExecuteApp.js.map +1 -1
- package/dist/ui/components/dialogs/DocsBrowser.js +2 -2
- package/dist/ui/components/dialogs/DocsBrowser.js.map +1 -1
- package/dist/ui/hooks/useFileBrowserState.js +1 -1
- package/dist/ui/hooks/useFileBrowserState.js.map +1 -1
- package/dist/ui/hooks/useFileList.js +2 -2
- package/dist/ui/hooks/useFileList.js.map +1 -1
- package/dist/ui/ink-entry.js +1 -1
- package/dist/ui/ink-entry.js.map +1 -1
- package/dist/utils/json-stream-logger.js +0 -2
- package/dist/utils/json-stream-logger.js.map +1 -1
- package/dist/utils/logger.d.ts +4 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +18 -0
- package/dist/utils/logger.js.map +1 -1
- package/package.json +3 -1
- package/dist/agents/docs-search/index.d.ts +0 -33
- package/dist/agents/docs-search/index.d.ts.map +0 -1
- package/dist/agents/docs-search/index.js +0 -244
- package/dist/agents/docs-search/index.js.map +0 -1
- package/dist/prompts/agents/docs-search-decision.d.ts +0 -6
- package/dist/prompts/agents/docs-search-decision.d.ts.map +0 -1
- package/dist/prompts/agents/docs-search-decision.js +0 -46
- package/dist/prompts/agents/docs-search-decision.js.map +0 -1
- package/dist/prompts/agents/docs-search.d.ts +0 -4
- package/dist/prompts/agents/docs-search.d.ts.map +0 -1
- package/dist/prompts/agents/docs-search.js +0 -70
- package/dist/prompts/agents/docs-search.js.map +0 -1
- package/dist/tools/llm/agents/docs-search-tools.d.ts +0 -17
- package/dist/tools/llm/agents/docs-search-tools.d.ts.map +0 -1
- package/dist/tools/llm/agents/docs-search-tools.js +0 -265
- package/dist/tools/llm/agents/docs-search-tools.js.map +0 -1
- package/dist/tools/llm/simple/docs-search-agent-tool.d.ts +0 -6
- package/dist/tools/llm/simple/docs-search-agent-tool.d.ts.map +0 -1
- package/dist/tools/llm/simple/docs-search-agent-tool.js +0 -104
- package/dist/tools/llm/simple/docs-search-agent-tool.js.map +0 -1
- package/dist/ui/components/DocsSearchProgress.d.ts +0 -13
- package/dist/ui/components/DocsSearchProgress.d.ts.map +0 -1
- package/dist/ui/components/DocsSearchProgress.js +0 -37
- 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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
474
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
1038
|
+
logger.errorSilent('Unknown Error Type', { error });
|
|
847
1039
|
return new LLMError('알 수 없는 에러가 발생했습니다.', {
|
|
848
1040
|
details: { unknownError: error },
|
|
849
1041
|
});
|