@vybestack/llxprt-code-core 0.1.18 → 0.1.19-beta
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/README.md +1 -0
- package/dist/src/code_assist/converter.d.ts +2 -1
- package/dist/src/code_assist/converter.js +1 -1
- package/dist/src/code_assist/converter.js.map +1 -1
- package/dist/src/code_assist/converter.test.js +48 -1
- package/dist/src/code_assist/converter.test.js.map +1 -1
- package/dist/src/code_assist/oauth2.js +2 -1
- package/dist/src/code_assist/oauth2.js.map +1 -1
- package/dist/src/code_assist/server.test.js +4 -1
- package/dist/src/code_assist/server.test.js.map +1 -1
- package/dist/src/config/config.alwaysAllow.test.d.ts +6 -0
- package/dist/src/config/config.alwaysAllow.test.js +84 -0
- package/dist/src/config/config.alwaysAllow.test.js.map +1 -0
- package/dist/src/config/config.d.ts +80 -1
- package/dist/src/config/config.ephemeral.test.d.ts +6 -0
- package/dist/src/config/config.ephemeral.test.js +152 -0
- package/dist/src/config/config.ephemeral.test.js.map +1 -0
- package/dist/src/config/config.js +135 -0
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +8 -0
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/core/client.d.ts +0 -2
- package/dist/src/core/client.js +10 -85
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/client.test.js +7 -2
- package/dist/src/core/client.test.js.map +1 -1
- package/dist/src/core/contentGenerator.js +1 -1
- package/dist/src/core/contentGenerator.js.map +1 -1
- package/dist/src/core/coreToolScheduler.d.ts +17 -7
- package/dist/src/core/coreToolScheduler.js +121 -18
- package/dist/src/core/coreToolScheduler.js.map +1 -1
- package/dist/src/core/coreToolScheduler.test.js +25 -37
- package/dist/src/core/coreToolScheduler.test.js.map +1 -1
- package/dist/src/core/geminiChat.d.ts +3 -0
- package/dist/src/core/geminiChat.js +17 -13
- package/dist/src/core/geminiChat.js.map +1 -1
- package/dist/src/core/logger.d.ts +1 -0
- package/dist/src/core/logger.js +18 -0
- package/dist/src/core/logger.js.map +1 -1
- package/dist/src/core/logger.test.js +29 -0
- package/dist/src/core/logger.test.js.map +1 -1
- package/dist/src/core/loggingContentGenerator.d.ts +24 -0
- package/dist/src/core/loggingContentGenerator.js +89 -0
- package/dist/src/core/loggingContentGenerator.js.map +1 -0
- package/dist/src/core/nonInteractiveToolExecutor.js +21 -1
- package/dist/src/core/nonInteractiveToolExecutor.js.map +1 -1
- package/dist/src/core/nonInteractiveToolExecutor.test.js +8 -31
- package/dist/src/core/nonInteractiveToolExecutor.test.js.map +1 -1
- package/dist/src/core/subagent.d.ts +230 -0
- package/dist/src/core/subagent.js +453 -0
- package/dist/src/core/subagent.js.map +1 -0
- package/dist/src/core/subagent.test.d.ts +6 -0
- package/dist/src/core/subagent.test.js +519 -0
- package/dist/src/core/subagent.test.js.map +1 -0
- package/dist/src/hooks/tool-render-suppression-hook.d.ts +16 -0
- package/dist/src/hooks/tool-render-suppression-hook.js +26 -0
- package/dist/src/hooks/tool-render-suppression-hook.js.map +1 -0
- package/dist/src/hooks/tool-render-suppression-hook.test.d.ts +6 -0
- package/dist/src/hooks/tool-render-suppression-hook.test.js +59 -0
- package/dist/src/hooks/tool-render-suppression-hook.test.js.map +1 -0
- package/dist/src/ide/ide-client.d.ts +22 -1
- package/dist/src/ide/ide-client.js +161 -17
- package/dist/src/ide/ide-client.js.map +1 -1
- package/dist/src/ide/ideContext.d.ts +127 -32
- package/dist/src/ide/ideContext.js +45 -0
- package/dist/src/ide/ideContext.js.map +1 -1
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.js +6 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/integration-tests/todo-system.test.js +38 -602
- package/dist/src/integration-tests/todo-system.test.js.map +1 -1
- package/dist/src/providers/IProviderManager.d.ts +5 -0
- package/dist/src/providers/LoggingProviderWrapper.d.ts +53 -0
- package/dist/src/providers/LoggingProviderWrapper.js +347 -0
- package/dist/src/providers/LoggingProviderWrapper.js.map +1 -0
- package/dist/src/providers/ProviderManager.d.ts +20 -0
- package/dist/src/providers/ProviderManager.js +214 -1
- package/dist/src/providers/ProviderManager.js.map +1 -1
- package/dist/src/providers/gemini/GeminiProvider.js +5 -5
- package/dist/src/providers/gemini/GeminiProvider.js.map +1 -1
- package/dist/src/providers/logging/ProviderContentExtractor.d.ts +27 -0
- package/dist/src/providers/logging/ProviderContentExtractor.js +198 -0
- package/dist/src/providers/logging/ProviderContentExtractor.js.map +1 -0
- package/dist/src/providers/logging/ProviderPerformanceTracker.d.ts +43 -0
- package/dist/src/providers/logging/ProviderPerformanceTracker.js +98 -0
- package/dist/src/providers/logging/ProviderPerformanceTracker.js.map +1 -0
- package/dist/src/providers/openai/OpenAIProvider.js +8 -1
- package/dist/src/providers/openai/OpenAIProvider.js.map +1 -1
- package/dist/src/providers/openai/OpenAIProvider.test.js +106 -0
- package/dist/src/providers/openai/OpenAIProvider.test.js.map +1 -1
- package/dist/src/providers/types/IProviderConfig.d.ts +5 -0
- package/dist/src/providers/types.d.ts +47 -0
- package/dist/src/services/git-stats-service.d.ts +32 -0
- package/dist/src/services/git-stats-service.js +22 -0
- package/dist/src/services/git-stats-service.js.map +1 -0
- package/dist/src/services/loopDetectionService.js +10 -6
- package/dist/src/services/loopDetectionService.js.map +1 -1
- package/dist/src/services/loopDetectionService.test.js +139 -0
- package/dist/src/services/loopDetectionService.test.js.map +1 -1
- package/dist/src/services/shellExecutionService.js +69 -9
- package/dist/src/services/shellExecutionService.js.map +1 -1
- package/dist/src/services/shellExecutionService.test.js +8 -0
- package/dist/src/services/shellExecutionService.test.js.map +1 -1
- package/dist/src/services/todo-context-tracker.d.ts +31 -0
- package/dist/src/services/todo-context-tracker.js +45 -0
- package/dist/src/services/todo-context-tracker.js.map +1 -0
- package/dist/src/services/tool-call-tracker-service.d.ts +52 -0
- package/dist/src/services/tool-call-tracker-service.js +170 -0
- package/dist/src/services/tool-call-tracker-service.js.map +1 -0
- package/dist/src/services/tool-call-tracker-service.test.d.ts +6 -0
- package/dist/src/services/tool-call-tracker-service.test.js +99 -0
- package/dist/src/services/tool-call-tracker-service.test.js.map +1 -0
- package/dist/src/storage/ConversationFileWriter.d.ts +16 -0
- package/dist/src/storage/ConversationFileWriter.js +69 -0
- package/dist/src/storage/ConversationFileWriter.js.map +1 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +8 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +56 -3
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.d.ts +6 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +187 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -0
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +5 -1
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +11 -0
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -1
- package/dist/src/telemetry/constants.d.ts +5 -0
- package/dist/src/telemetry/constants.js +5 -0
- package/dist/src/telemetry/constants.js.map +1 -1
- package/dist/src/telemetry/loggers.d.ts +5 -1
- package/dist/src/telemetry/loggers.js +87 -1
- package/dist/src/telemetry/loggers.js.map +1 -1
- package/dist/src/telemetry/loggers.test.circular.js +7 -2
- package/dist/src/telemetry/loggers.test.circular.js.map +1 -1
- package/dist/src/telemetry/loggers.test.js +7 -2
- package/dist/src/telemetry/loggers.test.js.map +1 -1
- package/dist/src/telemetry/metrics.d.ts +3 -2
- package/dist/src/telemetry/metrics.js +7 -1
- package/dist/src/telemetry/metrics.js.map +1 -1
- package/dist/src/telemetry/metrics.test.js +50 -0
- package/dist/src/telemetry/metrics.test.js.map +1 -1
- package/dist/src/telemetry/tool-call-decision.d.ts +13 -0
- package/dist/src/telemetry/tool-call-decision.js +29 -0
- package/dist/src/telemetry/tool-call-decision.js.map +1 -0
- package/dist/src/telemetry/types.d.ts +58 -2
- package/dist/src/telemetry/types.js +126 -1
- package/dist/src/telemetry/types.js.map +1 -1
- package/dist/src/telemetry/uiTelemetry.d.ts +4 -1
- package/dist/src/telemetry/uiTelemetry.js +3 -1
- package/dist/src/telemetry/uiTelemetry.js.map +1 -1
- package/dist/src/telemetry/uiTelemetry.test.js +13 -2
- package/dist/src/telemetry/uiTelemetry.test.js.map +1 -1
- package/dist/src/test-utils/tools.d.ts +23 -0
- package/dist/src/test-utils/tools.js +41 -0
- package/dist/src/test-utils/tools.js.map +1 -0
- package/dist/src/tools/diffOptions.d.ts +2 -0
- package/dist/src/tools/diffOptions.js +28 -0
- package/dist/src/tools/diffOptions.js.map +1 -1
- package/dist/src/tools/diffOptions.test.d.ts +6 -0
- package/dist/src/tools/diffOptions.test.js +119 -0
- package/dist/src/tools/diffOptions.test.js.map +1 -0
- package/dist/src/tools/edit.d.ts +9 -33
- package/dist/src/tools/edit.js +167 -132
- package/dist/src/tools/edit.js.map +1 -1
- package/dist/src/tools/edit.test.js +124 -50
- package/dist/src/tools/edit.test.js.map +1 -1
- package/dist/src/tools/glob.d.ts +3 -10
- package/dist/src/tools/glob.js +97 -99
- package/dist/src/tools/glob.js.map +1 -1
- package/dist/src/tools/glob.test.js +37 -26
- package/dist/src/tools/glob.test.js.map +1 -1
- package/dist/src/tools/grep.d.ts +3 -35
- package/dist/src/tools/grep.js +117 -88
- package/dist/src/tools/grep.js.map +1 -1
- package/dist/src/tools/grep.test.js +36 -22
- package/dist/src/tools/grep.test.js.map +1 -1
- package/dist/src/tools/mcp-client.d.ts +14 -3
- package/dist/src/tools/mcp-client.js +82 -6
- package/dist/src/tools/mcp-client.js.map +1 -1
- package/dist/src/tools/mcp-client.test.js +337 -2
- package/dist/src/tools/mcp-client.test.js.map +1 -1
- package/dist/src/tools/memoryTool.d.ts +2 -2
- package/dist/src/tools/memoryTool.js +1 -0
- package/dist/src/tools/memoryTool.js.map +1 -1
- package/dist/src/tools/modifiable-tool.d.ts +8 -5
- package/dist/src/tools/modifiable-tool.js +4 -1
- package/dist/src/tools/modifiable-tool.js.map +1 -1
- package/dist/src/tools/modifiable-tool.test.js +3 -3
- package/dist/src/tools/modifiable-tool.test.js.map +1 -1
- package/dist/src/tools/read-file.d.ts +4 -6
- package/dist/src/tools/read-file.js +87 -46
- package/dist/src/tools/read-file.js.map +1 -1
- package/dist/src/tools/read-file.test.js +207 -126
- package/dist/src/tools/read-file.test.js.map +1 -1
- package/dist/src/tools/read-many-files.js +8 -2
- package/dist/src/tools/read-many-files.js.map +1 -1
- package/dist/src/tools/read-many-files.test.js +16 -0
- package/dist/src/tools/read-many-files.test.js.map +1 -1
- package/dist/src/tools/shell.test.js +17 -0
- package/dist/src/tools/shell.test.js.map +1 -1
- package/dist/src/tools/todo-events.d.ts +22 -0
- package/dist/src/tools/todo-events.js +24 -0
- package/dist/src/tools/todo-events.js.map +1 -0
- package/dist/src/tools/todo-pause.d.ts +22 -0
- package/dist/src/tools/todo-pause.js +93 -0
- package/dist/src/tools/todo-pause.js.map +1 -0
- package/dist/src/tools/todo-pause.spec.d.ts +6 -0
- package/dist/src/tools/todo-pause.spec.js +287 -0
- package/dist/src/tools/todo-pause.spec.js.map +1 -0
- package/dist/src/tools/todo-read.js.map +1 -1
- package/dist/src/tools/todo-schemas.d.ts +232 -4
- package/dist/src/tools/todo-schemas.js +13 -0
- package/dist/src/tools/todo-schemas.js.map +1 -1
- package/dist/src/tools/todo-schemas.test.js +190 -1
- package/dist/src/tools/todo-schemas.test.js.map +1 -1
- package/dist/src/tools/todo-store.d.ts +1 -4
- package/dist/src/tools/todo-store.js +41 -40
- package/dist/src/tools/todo-store.js.map +1 -1
- package/dist/src/tools/todo-store.test.js +34 -40
- package/dist/src/tools/todo-store.test.js.map +1 -1
- package/dist/src/tools/todo-write.d.ts +1 -1
- package/dist/src/tools/todo-write.js +84 -47
- package/dist/src/tools/todo-write.js.map +1 -1
- package/dist/src/tools/todo-write.test.js +23 -9
- package/dist/src/tools/todo-write.test.js.map +1 -1
- package/dist/src/tools/tool-context.d.ts +2 -0
- package/dist/src/tools/tool-error.d.ts +4 -0
- package/dist/src/tools/tool-error.js +4 -0
- package/dist/src/tools/tool-error.js.map +1 -1
- package/dist/src/tools/tool-registry.d.ts +12 -6
- package/dist/src/tools/tool-registry.js +19 -4
- package/dist/src/tools/tool-registry.js.map +1 -1
- package/dist/src/tools/tool-registry.test.js +3 -20
- package/dist/src/tools/tool-registry.test.js.map +1 -1
- package/dist/src/tools/tools.d.ts +134 -39
- package/dist/src/tools/tools.js +115 -10
- package/dist/src/tools/tools.js.map +1 -1
- package/dist/src/tools/web-search.test.js +1 -0
- package/dist/src/tools/web-search.test.js.map +1 -1
- package/dist/src/tools/write-file.d.ts +6 -2
- package/dist/src/tools/write-file.js +106 -16
- package/dist/src/tools/write-file.js.map +1 -1
- package/dist/src/tools/write-file.test.js +25 -7
- package/dist/src/tools/write-file.test.js.map +1 -1
- package/dist/src/types/modelParams.d.ts +4 -0
- package/dist/src/utils/environmentContext.d.ts +21 -0
- package/dist/src/utils/environmentContext.js +90 -0
- package/dist/src/utils/environmentContext.js.map +1 -0
- package/dist/src/utils/environmentContext.test.d.ts +6 -0
- package/dist/src/utils/environmentContext.test.js +139 -0
- package/dist/src/utils/environmentContext.test.js.map +1 -0
- package/dist/src/utils/errors.d.ts +3 -0
- package/dist/src/utils/errors.js +6 -0
- package/dist/src/utils/errors.js.map +1 -1
- package/dist/src/utils/fileUtils.d.ts +7 -0
- package/dist/src/utils/fileUtils.js +11 -10
- package/dist/src/utils/fileUtils.js.map +1 -1
- package/dist/src/utils/fileUtils.test.js +1 -4
- package/dist/src/utils/fileUtils.test.js.map +1 -1
- package/dist/src/utils/filesearch/fileSearch.d.ts +1 -0
- package/dist/src/utils/filesearch/fileSearch.js +27 -5
- package/dist/src/utils/filesearch/fileSearch.js.map +1 -1
- package/dist/src/utils/filesearch/fileSearch.test.js +21 -1
- package/dist/src/utils/filesearch/fileSearch.test.js.map +1 -1
- package/dist/src/utils/memoryDiscovery.js +4 -1
- package/dist/src/utils/memoryDiscovery.js.map +1 -1
- package/dist/src/utils/shell-utils.js +14 -2
- package/dist/src/utils/shell-utils.js.map +1 -1
- package/dist/src/utils/shell-utils.shellReplacement.test.d.ts +6 -0
- package/dist/src/utils/shell-utils.shellReplacement.test.js +149 -0
- package/dist/src/utils/shell-utils.shellReplacement.test.js.map +1 -0
- package/package.json +2 -1
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { describe, expect, it } from 'vitest';
|
|
7
|
+
import { getDiffStat } from './diffOptions.js';
|
|
8
|
+
describe('getDiffStat', () => {
|
|
9
|
+
const fileName = 'test.txt';
|
|
10
|
+
it('should return 0 for all stats when there are no changes', () => {
|
|
11
|
+
const oldStr = 'line1\nline2\n';
|
|
12
|
+
const aiStr = 'line1\nline2\n';
|
|
13
|
+
const userStr = 'line1\nline2\n';
|
|
14
|
+
const diffStat = getDiffStat(fileName, oldStr, aiStr, userStr);
|
|
15
|
+
expect(diffStat).toEqual({
|
|
16
|
+
ai_added_lines: 0,
|
|
17
|
+
ai_removed_lines: 0,
|
|
18
|
+
user_added_lines: 0,
|
|
19
|
+
user_removed_lines: 0,
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
it('should correctly report AI additions', () => {
|
|
23
|
+
const oldStr = 'line1\nline2\n';
|
|
24
|
+
const aiStr = 'line1\nline2\nline3\n';
|
|
25
|
+
const userStr = 'line1\nline2\nline3\n';
|
|
26
|
+
const diffStat = getDiffStat(fileName, oldStr, aiStr, userStr);
|
|
27
|
+
expect(diffStat).toEqual({
|
|
28
|
+
ai_added_lines: 1,
|
|
29
|
+
ai_removed_lines: 0,
|
|
30
|
+
user_added_lines: 0,
|
|
31
|
+
user_removed_lines: 0,
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
it('should correctly report AI removals', () => {
|
|
35
|
+
const oldStr = 'line1\nline2\nline3\n';
|
|
36
|
+
const aiStr = 'line1\nline3\n';
|
|
37
|
+
const userStr = 'line1\nline3\n';
|
|
38
|
+
const diffStat = getDiffStat(fileName, oldStr, aiStr, userStr);
|
|
39
|
+
expect(diffStat).toEqual({
|
|
40
|
+
ai_added_lines: 0,
|
|
41
|
+
ai_removed_lines: 1,
|
|
42
|
+
user_added_lines: 0,
|
|
43
|
+
user_removed_lines: 0,
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
it('should correctly report AI modifications', () => {
|
|
47
|
+
const oldStr = 'line1\nline2\nline3\n';
|
|
48
|
+
const aiStr = 'line1\nline_two\nline3\n';
|
|
49
|
+
const userStr = 'line1\nline_two\nline3\n';
|
|
50
|
+
const diffStat = getDiffStat(fileName, oldStr, aiStr, userStr);
|
|
51
|
+
expect(diffStat).toEqual({
|
|
52
|
+
ai_added_lines: 1,
|
|
53
|
+
ai_removed_lines: 1,
|
|
54
|
+
user_added_lines: 0,
|
|
55
|
+
user_removed_lines: 0,
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
it('should correctly report user additions', () => {
|
|
59
|
+
const oldStr = 'line1\nline2\n';
|
|
60
|
+
const aiStr = 'line1\nline2\nline3\n';
|
|
61
|
+
const userStr = 'line1\nline2\nline3\nline4\n';
|
|
62
|
+
const diffStat = getDiffStat(fileName, oldStr, aiStr, userStr);
|
|
63
|
+
expect(diffStat).toEqual({
|
|
64
|
+
ai_added_lines: 1,
|
|
65
|
+
ai_removed_lines: 0,
|
|
66
|
+
user_added_lines: 1,
|
|
67
|
+
user_removed_lines: 0,
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
it('should correctly report user removals', () => {
|
|
71
|
+
const oldStr = 'line1\nline2\n';
|
|
72
|
+
const aiStr = 'line1\nline2\nline3\n';
|
|
73
|
+
const userStr = 'line1\nline2\n';
|
|
74
|
+
const diffStat = getDiffStat(fileName, oldStr, aiStr, userStr);
|
|
75
|
+
expect(diffStat).toEqual({
|
|
76
|
+
ai_added_lines: 1,
|
|
77
|
+
ai_removed_lines: 0,
|
|
78
|
+
user_added_lines: 0,
|
|
79
|
+
user_removed_lines: 1,
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
it('should correctly report user modifications', () => {
|
|
83
|
+
const oldStr = 'line1\nline2\n';
|
|
84
|
+
const aiStr = 'line1\nline2\nline3\n';
|
|
85
|
+
const userStr = 'line1\nline2\nline_three\n';
|
|
86
|
+
const diffStat = getDiffStat(fileName, oldStr, aiStr, userStr);
|
|
87
|
+
expect(diffStat).toEqual({
|
|
88
|
+
ai_added_lines: 1,
|
|
89
|
+
ai_removed_lines: 0,
|
|
90
|
+
user_added_lines: 1,
|
|
91
|
+
user_removed_lines: 1,
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
it('should handle complex changes from both AI and user', () => {
|
|
95
|
+
const oldStr = 'line1\nline2\nline3\nline4\n';
|
|
96
|
+
const aiStr = 'line_one\nline2\nline_three\nline4\n';
|
|
97
|
+
const userStr = 'line_one\nline_two\nline_three\nline4\nline5\n';
|
|
98
|
+
const diffStat = getDiffStat(fileName, oldStr, aiStr, userStr);
|
|
99
|
+
expect(diffStat).toEqual({
|
|
100
|
+
ai_added_lines: 2,
|
|
101
|
+
ai_removed_lines: 2,
|
|
102
|
+
user_added_lines: 2,
|
|
103
|
+
user_removed_lines: 1,
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
it('should report a single line modification as one addition and one removal', () => {
|
|
107
|
+
const oldStr = 'hello world';
|
|
108
|
+
const aiStr = 'hello universe';
|
|
109
|
+
const userStr = 'hello universe';
|
|
110
|
+
const diffStat = getDiffStat(fileName, oldStr, aiStr, userStr);
|
|
111
|
+
expect(diffStat).toEqual({
|
|
112
|
+
ai_added_lines: 1,
|
|
113
|
+
ai_removed_lines: 1,
|
|
114
|
+
user_added_lines: 0,
|
|
115
|
+
user_removed_lines: 0,
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
//# sourceMappingURL=diffOptions.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diffOptions.test.js","sourceRoot":"","sources":["../../../src/tools/diffOptions.test.ts"],"names":[],"mappings":"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"}
|
package/dist/src/tools/edit.d.ts
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
* Copyright 2025 Google LLC
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import { BaseDeclarativeTool, ToolInvocation, ToolResult } from './tools.js';
|
|
7
7
|
import { Config } from '../config/config.js';
|
|
8
|
-
import {
|
|
8
|
+
import { ModifiableDeclarativeTool, ModifyContext } from './modifiable-tool.js';
|
|
9
|
+
export declare function applyReplacement(currentContent: string | null, oldString: string, newString: string, isNewFile: boolean): string;
|
|
9
10
|
/**
|
|
10
11
|
* Parameters for the Edit tool
|
|
11
12
|
*/
|
|
@@ -31,11 +32,15 @@ export interface EditToolParams {
|
|
|
31
32
|
* Whether the edit was modified manually by the user.
|
|
32
33
|
*/
|
|
33
34
|
modified_by_user?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Initially proposed string.
|
|
37
|
+
*/
|
|
38
|
+
ai_proposed_string?: string;
|
|
34
39
|
}
|
|
35
40
|
/**
|
|
36
41
|
* Implementation of the Edit tool logic
|
|
37
42
|
*/
|
|
38
|
-
export declare class EditTool extends
|
|
43
|
+
export declare class EditTool extends BaseDeclarativeTool<EditToolParams, ToolResult> implements ModifiableDeclarativeTool<EditToolParams> {
|
|
39
44
|
private readonly config;
|
|
40
45
|
static readonly Name = "replace";
|
|
41
46
|
constructor(config: Config);
|
|
@@ -45,35 +50,6 @@ export declare class EditTool extends BaseTool<EditToolParams, ToolResult> imple
|
|
|
45
50
|
* @returns Error message string or null if valid
|
|
46
51
|
*/
|
|
47
52
|
validateToolParams(params: EditToolParams): string | null;
|
|
48
|
-
|
|
49
|
-
* Determines any file locations affected by the tool execution
|
|
50
|
-
* @param params Parameters for the tool execution
|
|
51
|
-
* @returns A list of such paths
|
|
52
|
-
*/
|
|
53
|
-
toolLocations(params: EditToolParams): ToolLocation[];
|
|
54
|
-
private _applyReplacement;
|
|
55
|
-
/**
|
|
56
|
-
* Calculates the potential outcome of an edit operation.
|
|
57
|
-
* @param params Parameters for the edit operation
|
|
58
|
-
* @returns An object describing the potential edit outcome
|
|
59
|
-
* @throws File system errors if reading the file fails unexpectedly (e.g., permissions)
|
|
60
|
-
*/
|
|
61
|
-
private calculateEdit;
|
|
62
|
-
/**
|
|
63
|
-
* Handles the confirmation prompt for the Edit tool in the CLI.
|
|
64
|
-
* It needs to calculate the diff to show the user.
|
|
65
|
-
*/
|
|
66
|
-
shouldConfirmExecute(params: EditToolParams, abortSignal: AbortSignal): Promise<ToolCallConfirmationDetails | false>;
|
|
67
|
-
getDescription(params: EditToolParams): string;
|
|
68
|
-
/**
|
|
69
|
-
* Executes the edit operation with the given parameters.
|
|
70
|
-
* @param params Parameters for the edit operation
|
|
71
|
-
* @returns Result of the edit operation
|
|
72
|
-
*/
|
|
73
|
-
execute(params: EditToolParams, signal: AbortSignal): Promise<ToolResult>;
|
|
74
|
-
/**
|
|
75
|
-
* Creates parent directories if they don't exist
|
|
76
|
-
*/
|
|
77
|
-
private ensureParentDirectoriesExist;
|
|
53
|
+
protected createInvocation(params: EditToolParams): ToolInvocation<EditToolParams, ToolResult>;
|
|
78
54
|
getModifyContext(_: AbortSignal): ModifyContext<EditToolParams>;
|
|
79
55
|
}
|
package/dist/src/tools/edit.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import * as fs from 'fs';
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
import * as Diff from 'diff';
|
|
9
|
-
import {
|
|
9
|
+
import { BaseDeclarativeTool, Icon, ToolConfirmationOutcome, } from './tools.js';
|
|
10
10
|
import { ToolErrorType } from './tool-error.js';
|
|
11
11
|
import { Type } from '@google/genai';
|
|
12
12
|
import { SchemaValidator } from '../utils/schemaValidator.js';
|
|
@@ -14,91 +14,33 @@ import { makeRelative, shortenPath } from '../utils/paths.js';
|
|
|
14
14
|
import { isNodeError } from '../utils/errors.js';
|
|
15
15
|
import { ApprovalMode } from '../config/config.js';
|
|
16
16
|
import { ensureCorrectEdit } from '../utils/editCorrector.js';
|
|
17
|
-
import { DEFAULT_DIFF_OPTIONS } from './diffOptions.js';
|
|
17
|
+
import { DEFAULT_DIFF_OPTIONS, getDiffStat } from './diffOptions.js';
|
|
18
18
|
import { ReadFileTool } from './read-file.js';
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
static Name = 'replace';
|
|
25
|
-
constructor(config) {
|
|
26
|
-
super(EditTool.Name, 'Edit', `Replaces text within a file. By default, replaces a single occurrence, but can replace multiple occurrences when \`expected_replacements\` is specified. This tool requires providing significant context around the change to ensure precise targeting. Always use the ${ReadFileTool.Name} tool to examine the file's current content before attempting a text replacement.
|
|
27
|
-
|
|
28
|
-
The user has the ability to modify the \`new_string\` content. If modified, this will be stated in the response.
|
|
29
|
-
|
|
30
|
-
Expectation for required parameters:
|
|
31
|
-
1. \`file_path\` MUST be an absolute path; otherwise an error will be thrown.
|
|
32
|
-
2. \`old_string\` MUST be the exact literal text to replace (including all whitespace, indentation, newlines, and surrounding code etc.).
|
|
33
|
-
3. \`new_string\` MUST be the exact literal text to replace \`old_string\` with (also including all whitespace, indentation, newlines, and surrounding code etc.). Ensure the resulting code is correct and idiomatic.
|
|
34
|
-
4. NEVER escape \`old_string\` or \`new_string\`, that would break the exact literal text requirement.
|
|
35
|
-
**Important:** If ANY of the above are not satisfied, the tool will fail. CRITICAL for \`old_string\`: Must uniquely identify the single instance to change. Include at least 3 lines of context BEFORE and AFTER the target text, matching whitespace and indentation precisely. If this string matches multiple locations, or does not match exactly, the tool will fail.
|
|
36
|
-
**Multiple replacements:** Set \`expected_replacements\` to the number of occurrences you want to replace. The tool will replace ALL occurrences that match \`old_string\` exactly. Ensure the number of replacements matches your expectation.`, Icon.Pencil, {
|
|
37
|
-
properties: {
|
|
38
|
-
file_path: {
|
|
39
|
-
description: "The absolute path to the file to modify. Must start with '/'.",
|
|
40
|
-
type: Type.STRING,
|
|
41
|
-
},
|
|
42
|
-
old_string: {
|
|
43
|
-
description: 'The exact literal text to replace, preferably unescaped. For single replacements (default), include at least 3 lines of context BEFORE and AFTER the target text, matching whitespace and indentation precisely. For multiple replacements, specify expected_replacements parameter. If this string is not the exact literal text (i.e. you escaped it) or does not match exactly, the tool will fail.',
|
|
44
|
-
type: Type.STRING,
|
|
45
|
-
},
|
|
46
|
-
new_string: {
|
|
47
|
-
description: 'The exact literal text to replace `old_string` with, preferably unescaped. Provide the EXACT text. Ensure the resulting code is correct and idiomatic.',
|
|
48
|
-
type: Type.STRING,
|
|
49
|
-
},
|
|
50
|
-
expected_replacements: {
|
|
51
|
-
type: Type.NUMBER,
|
|
52
|
-
description: 'Number of replacements expected. Defaults to 1 if not specified. Use when you want to replace multiple occurrences.',
|
|
53
|
-
minimum: 1,
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
required: ['file_path', 'old_string', 'new_string'],
|
|
57
|
-
type: Type.OBJECT,
|
|
58
|
-
});
|
|
59
|
-
this.config = config;
|
|
19
|
+
import { IDEConnectionStatus } from '../ide/ide-client.js';
|
|
20
|
+
import { getGitStatsService } from '../services/git-stats-service.js';
|
|
21
|
+
export function applyReplacement(currentContent, oldString, newString, isNewFile) {
|
|
22
|
+
if (isNewFile) {
|
|
23
|
+
return newString;
|
|
60
24
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
* @returns Error message string or null if valid
|
|
65
|
-
*/
|
|
66
|
-
validateToolParams(params) {
|
|
67
|
-
const errors = SchemaValidator.validate(this.schema.parameters, params);
|
|
68
|
-
if (errors) {
|
|
69
|
-
return errors;
|
|
70
|
-
}
|
|
71
|
-
if (!path.isAbsolute(params.file_path)) {
|
|
72
|
-
return `File path must be absolute: ${params.file_path}`;
|
|
73
|
-
}
|
|
74
|
-
const workspaceContext = this.config.getWorkspaceContext();
|
|
75
|
-
if (!workspaceContext.isPathWithinWorkspace(params.file_path)) {
|
|
76
|
-
const directories = workspaceContext.getDirectories();
|
|
77
|
-
return `File path must be within one of the workspace directories: ${directories.join(', ')}`;
|
|
78
|
-
}
|
|
79
|
-
return null;
|
|
25
|
+
if (currentContent === null) {
|
|
26
|
+
// Should not happen if not a new file, but defensively return empty or newString if oldString is also empty
|
|
27
|
+
return oldString === '' ? newString : '';
|
|
80
28
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
* @returns A list of such paths
|
|
85
|
-
*/
|
|
86
|
-
toolLocations(params) {
|
|
87
|
-
return [{ path: params.file_path }];
|
|
29
|
+
// If oldString is empty and it's not a new file, do not modify the content.
|
|
30
|
+
if (oldString === '' && !isNewFile) {
|
|
31
|
+
return currentContent;
|
|
88
32
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
return currentContent.replaceAll(oldString, newString);
|
|
33
|
+
return currentContent.replaceAll(oldString, newString);
|
|
34
|
+
}
|
|
35
|
+
class EditToolInvocation {
|
|
36
|
+
config;
|
|
37
|
+
params;
|
|
38
|
+
constructor(config, params) {
|
|
39
|
+
this.config = config;
|
|
40
|
+
this.params = params;
|
|
41
|
+
}
|
|
42
|
+
toolLocations() {
|
|
43
|
+
return [{ path: this.params.file_path }];
|
|
102
44
|
}
|
|
103
45
|
/**
|
|
104
46
|
* Calculates the potential outcome of an edit operation.
|
|
@@ -185,7 +127,7 @@ Expectation for required parameters:
|
|
|
185
127
|
type: ToolErrorType.READ_CONTENT_FAILURE,
|
|
186
128
|
};
|
|
187
129
|
}
|
|
188
|
-
const newContent =
|
|
130
|
+
const newContent = applyReplacement(currentContent, finalOldString, finalNewString, isNewFile);
|
|
189
131
|
return {
|
|
190
132
|
currentContent,
|
|
191
133
|
newContent,
|
|
@@ -198,20 +140,15 @@ Expectation for required parameters:
|
|
|
198
140
|
* Handles the confirmation prompt for the Edit tool in the CLI.
|
|
199
141
|
* It needs to calculate the diff to show the user.
|
|
200
142
|
*/
|
|
201
|
-
async shouldConfirmExecute(
|
|
143
|
+
async shouldConfirmExecute(abortSignal) {
|
|
202
144
|
const approvalMode = this.config.getApprovalMode();
|
|
203
145
|
if (approvalMode === ApprovalMode.AUTO_EDIT ||
|
|
204
146
|
approvalMode === ApprovalMode.YOLO) {
|
|
205
147
|
return false;
|
|
206
148
|
}
|
|
207
|
-
const validationError = this.validateToolParams(params);
|
|
208
|
-
if (validationError) {
|
|
209
|
-
console.error(`[EditTool Wrapper] Attempted confirmation with invalid parameters: ${validationError}`);
|
|
210
|
-
return false;
|
|
211
|
-
}
|
|
212
149
|
let editData;
|
|
213
150
|
try {
|
|
214
|
-
editData = await this.calculateEdit(params, abortSignal);
|
|
151
|
+
editData = await this.calculateEdit(this.params, abortSignal);
|
|
215
152
|
}
|
|
216
153
|
catch (error) {
|
|
217
154
|
console.error('Failed to calculate edit:', error);
|
|
@@ -220,12 +157,19 @@ Expectation for required parameters:
|
|
|
220
157
|
if (editData.error) {
|
|
221
158
|
return false;
|
|
222
159
|
}
|
|
223
|
-
const fileName = path.basename(params.file_path);
|
|
160
|
+
const fileName = path.basename(this.params.file_path);
|
|
224
161
|
const fileDiff = Diff.createPatch(fileName, editData.currentContent ?? '', editData.newContent, 'Current', 'Proposed', DEFAULT_DIFF_OPTIONS);
|
|
162
|
+
const ideClient = this.config.getIdeClient();
|
|
163
|
+
const ideConfirmation = this.config.getIdeModeFeature() &&
|
|
164
|
+
this.config.getIdeMode() &&
|
|
165
|
+
ideClient?.getConnectionStatus().status === IDEConnectionStatus.Connected
|
|
166
|
+
? ideClient.openDiff(this.params.file_path, editData.newContent)
|
|
167
|
+
: undefined;
|
|
225
168
|
const confirmationDetails = {
|
|
226
169
|
type: 'edit',
|
|
227
|
-
title: `Confirm Edit: ${shortenPath(makeRelative(params.file_path, this.config.getTargetDir()))}`,
|
|
170
|
+
title: `Confirm Edit: ${shortenPath(makeRelative(this.params.file_path, this.config.getTargetDir()))}`,
|
|
228
171
|
fileName,
|
|
172
|
+
filePath: this.params.file_path,
|
|
229
173
|
fileDiff,
|
|
230
174
|
originalContent: editData.currentContent,
|
|
231
175
|
newContent: editData.newContent,
|
|
@@ -233,23 +177,30 @@ Expectation for required parameters:
|
|
|
233
177
|
if (outcome === ToolConfirmationOutcome.ProceedAlways) {
|
|
234
178
|
this.config.setApprovalMode(ApprovalMode.AUTO_EDIT);
|
|
235
179
|
}
|
|
180
|
+
if (ideConfirmation) {
|
|
181
|
+
const result = await ideConfirmation;
|
|
182
|
+
if (result.status === 'accepted' && result.content) {
|
|
183
|
+
// TODO(chrstn): See https://github.com/google-gemini/gemini-cli/pull/5618#discussion_r2255413084
|
|
184
|
+
// for info on a possible race condition where the file is modified on disk while being edited.
|
|
185
|
+
this.params.old_string = editData.currentContent ?? '';
|
|
186
|
+
this.params.new_string = result.content;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
236
189
|
},
|
|
190
|
+
ideConfirmation,
|
|
237
191
|
};
|
|
238
192
|
return confirmationDetails;
|
|
239
193
|
}
|
|
240
|
-
getDescription(
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
const relativePath = makeRelative(params.file_path, this.config.getTargetDir());
|
|
245
|
-
if (params.old_string === '') {
|
|
194
|
+
getDescription() {
|
|
195
|
+
const relativePath = makeRelative(this.params.file_path, this.config.getTargetDir());
|
|
196
|
+
if (this.params.old_string === '') {
|
|
246
197
|
return `Create ${shortenPath(relativePath)}`;
|
|
247
198
|
}
|
|
248
|
-
const oldStringSnippet = params.old_string.split('\n')[0].substring(0, 30) +
|
|
249
|
-
(params.old_string.length > 30 ? '...' : '');
|
|
250
|
-
const newStringSnippet = params.new_string.split('\n')[0].substring(0, 30) +
|
|
251
|
-
(params.new_string.length > 30 ? '...' : '');
|
|
252
|
-
if (params.old_string === params.new_string) {
|
|
199
|
+
const oldStringSnippet = this.params.old_string.split('\n')[0].substring(0, 30) +
|
|
200
|
+
(this.params.old_string.length > 30 ? '...' : '');
|
|
201
|
+
const newStringSnippet = this.params.new_string.split('\n')[0].substring(0, 30) +
|
|
202
|
+
(this.params.new_string.length > 30 ? '...' : '');
|
|
203
|
+
if (this.params.old_string === this.params.new_string) {
|
|
253
204
|
return `No file changes to ${shortenPath(relativePath)}`;
|
|
254
205
|
}
|
|
255
206
|
return `${shortenPath(relativePath)}: ${oldStringSnippet} => ${newStringSnippet}`;
|
|
@@ -259,21 +210,10 @@ Expectation for required parameters:
|
|
|
259
210
|
* @param params Parameters for the edit operation
|
|
260
211
|
* @returns Result of the edit operation
|
|
261
212
|
*/
|
|
262
|
-
async execute(
|
|
263
|
-
const validationError = this.validateToolParams(params);
|
|
264
|
-
if (validationError) {
|
|
265
|
-
return {
|
|
266
|
-
llmContent: `Error: Invalid parameters provided. Reason: ${validationError}`,
|
|
267
|
-
returnDisplay: `Error: ${validationError}`,
|
|
268
|
-
error: {
|
|
269
|
-
message: validationError,
|
|
270
|
-
type: ToolErrorType.INVALID_TOOL_PARAMS,
|
|
271
|
-
},
|
|
272
|
-
};
|
|
273
|
-
}
|
|
213
|
+
async execute(signal) {
|
|
274
214
|
let editData;
|
|
275
215
|
try {
|
|
276
|
-
editData = await this.calculateEdit(params, signal);
|
|
216
|
+
editData = await this.calculateEdit(this.params, signal);
|
|
277
217
|
}
|
|
278
218
|
catch (error) {
|
|
279
219
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
@@ -297,37 +237,62 @@ Expectation for required parameters:
|
|
|
297
237
|
};
|
|
298
238
|
}
|
|
299
239
|
try {
|
|
300
|
-
this.ensureParentDirectoriesExist(params.file_path);
|
|
301
|
-
fs.writeFileSync(params.file_path, editData.newContent, 'utf8');
|
|
240
|
+
this.ensureParentDirectoriesExist(this.params.file_path);
|
|
241
|
+
fs.writeFileSync(this.params.file_path, editData.newContent, 'utf8');
|
|
242
|
+
// Track git stats if logging is enabled and service is available
|
|
243
|
+
let gitStats = null;
|
|
244
|
+
if (this.config.getConversationLoggingEnabled()) {
|
|
245
|
+
const gitStatsService = getGitStatsService();
|
|
246
|
+
if (gitStatsService) {
|
|
247
|
+
try {
|
|
248
|
+
gitStats = await gitStatsService.trackFileEdit(this.params.file_path, editData.currentContent || '', editData.newContent);
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
// Don't fail the edit if git stats tracking fails
|
|
252
|
+
console.warn('Failed to track git stats:', error);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
302
256
|
let displayResult;
|
|
303
257
|
if (editData.isNewFile) {
|
|
304
|
-
displayResult = `Created ${shortenPath(makeRelative(params.file_path, this.config.getTargetDir()))}`;
|
|
258
|
+
displayResult = `Created ${shortenPath(makeRelative(this.params.file_path, this.config.getTargetDir()))}`;
|
|
305
259
|
}
|
|
306
260
|
else {
|
|
307
261
|
// Generate diff for display, even though core logic doesn't technically need it
|
|
308
262
|
// The CLI wrapper will use this part of the ToolResult
|
|
309
|
-
const fileName = path.basename(params.file_path);
|
|
263
|
+
const fileName = path.basename(this.params.file_path);
|
|
310
264
|
const fileDiff = Diff.createPatch(fileName, editData.currentContent ?? '', // Should not be null here if not isNewFile
|
|
311
265
|
editData.newContent, 'Current', 'Proposed', DEFAULT_DIFF_OPTIONS);
|
|
266
|
+
const originallyProposedContent = this.params.ai_proposed_string || this.params.new_string;
|
|
267
|
+
const diffStat = getDiffStat(fileName, editData.currentContent ?? '', originallyProposedContent, this.params.new_string);
|
|
312
268
|
displayResult = {
|
|
313
269
|
fileDiff,
|
|
314
270
|
fileName,
|
|
315
271
|
originalContent: editData.currentContent,
|
|
316
272
|
newContent: editData.newContent,
|
|
273
|
+
diffStat,
|
|
317
274
|
};
|
|
318
275
|
}
|
|
319
276
|
const llmSuccessMessageParts = [
|
|
320
277
|
editData.isNewFile
|
|
321
|
-
? `Created new file: ${params.file_path} with provided content.`
|
|
322
|
-
: `Successfully modified file: ${params.file_path} (${editData.occurrences} replacements).`,
|
|
278
|
+
? `Created new file: ${this.params.file_path} with provided content.`
|
|
279
|
+
: `Successfully modified file: ${this.params.file_path} (${editData.occurrences} replacements).`,
|
|
323
280
|
];
|
|
324
|
-
if (params.modified_by_user) {
|
|
325
|
-
llmSuccessMessageParts.push(`User modified the \`new_string\` content to be: ${params.new_string}.`);
|
|
281
|
+
if (this.params.modified_by_user) {
|
|
282
|
+
llmSuccessMessageParts.push(`User modified the \`new_string\` content to be: ${this.params.new_string}.`);
|
|
326
283
|
}
|
|
327
|
-
|
|
284
|
+
const result = {
|
|
328
285
|
llmContent: llmSuccessMessageParts.join(' '),
|
|
329
286
|
returnDisplay: displayResult,
|
|
330
287
|
};
|
|
288
|
+
// Include git stats in metadata if available
|
|
289
|
+
if (gitStats) {
|
|
290
|
+
result.metadata = {
|
|
291
|
+
...result.metadata,
|
|
292
|
+
gitStats,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
return result;
|
|
331
296
|
}
|
|
332
297
|
catch (error) {
|
|
333
298
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
@@ -350,6 +315,72 @@ Expectation for required parameters:
|
|
|
350
315
|
fs.mkdirSync(dirName, { recursive: true });
|
|
351
316
|
}
|
|
352
317
|
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Implementation of the Edit tool logic
|
|
321
|
+
*/
|
|
322
|
+
export class EditTool extends BaseDeclarativeTool {
|
|
323
|
+
config;
|
|
324
|
+
static Name = 'replace';
|
|
325
|
+
constructor(config) {
|
|
326
|
+
super(EditTool.Name, 'Edit', `Replaces text within a file. By default, replaces a single occurrence, but can replace multiple occurrences when \`expected_replacements\` is specified. This tool requires providing significant context around the change to ensure precise targeting. Always use the ${ReadFileTool.Name} tool to examine the file's current content before attempting a text replacement.
|
|
327
|
+
|
|
328
|
+
The user has the ability to modify the \`new_string\` content. If modified, this will be stated in the response.
|
|
329
|
+
|
|
330
|
+
Expectation for required parameters:
|
|
331
|
+
1. \`file_path\` MUST be an absolute path; otherwise an error will be thrown.
|
|
332
|
+
2. \`old_string\` MUST be the exact literal text to replace (including all whitespace, indentation, newlines, and surrounding code etc.).
|
|
333
|
+
3. \`new_string\` MUST be the exact literal text to replace \`old_string\` with (also including all whitespace, indentation, newlines, and surrounding code etc.). Ensure the resulting code is correct and idiomatic.
|
|
334
|
+
4. NEVER escape \`old_string\` or \`new_string\`, that would break the exact literal text requirement.
|
|
335
|
+
**Important:** If ANY of the above are not satisfied, the tool will fail. CRITICAL for \`old_string\`: Must uniquely identify the single instance to change. Include at least 3 lines of context BEFORE and AFTER the target text, matching whitespace and indentation precisely. If this string matches multiple locations, or does not match exactly, the tool will fail.
|
|
336
|
+
**Multiple replacements:** Set \`expected_replacements\` to the number of occurrences you want to replace. The tool will replace ALL occurrences that match \`old_string\` exactly. Ensure the number of replacements matches your expectation.`, Icon.Pencil, {
|
|
337
|
+
properties: {
|
|
338
|
+
file_path: {
|
|
339
|
+
description: "The absolute path to the file to modify. Must start with '/'.",
|
|
340
|
+
type: Type.STRING,
|
|
341
|
+
},
|
|
342
|
+
old_string: {
|
|
343
|
+
description: 'The exact literal text to replace, preferably unescaped. For single replacements (default), include at least 3 lines of context BEFORE and AFTER the target text, matching whitespace and indentation precisely. For multiple replacements, specify expected_replacements parameter. If this string is not the exact literal text (i.e. you escaped it) or does not match exactly, the tool will fail.',
|
|
344
|
+
type: Type.STRING,
|
|
345
|
+
},
|
|
346
|
+
new_string: {
|
|
347
|
+
description: 'The exact literal text to replace `old_string` with, preferably unescaped. Provide the EXACT text. Ensure the resulting code is correct and idiomatic.',
|
|
348
|
+
type: Type.STRING,
|
|
349
|
+
},
|
|
350
|
+
expected_replacements: {
|
|
351
|
+
type: Type.NUMBER,
|
|
352
|
+
description: 'Number of replacements expected. Defaults to 1 if not specified. Use when you want to replace multiple occurrences.',
|
|
353
|
+
minimum: 1,
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
required: ['file_path', 'old_string', 'new_string'],
|
|
357
|
+
type: Type.OBJECT,
|
|
358
|
+
});
|
|
359
|
+
this.config = config;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Validates the parameters for the Edit tool
|
|
363
|
+
* @param params Parameters to validate
|
|
364
|
+
* @returns Error message string or null if valid
|
|
365
|
+
*/
|
|
366
|
+
validateToolParams(params) {
|
|
367
|
+
const errors = SchemaValidator.validate(this.schema.parameters, params);
|
|
368
|
+
if (errors) {
|
|
369
|
+
return errors;
|
|
370
|
+
}
|
|
371
|
+
if (!path.isAbsolute(params.file_path)) {
|
|
372
|
+
return `File path must be absolute: ${params.file_path}`;
|
|
373
|
+
}
|
|
374
|
+
const workspaceContext = this.config.getWorkspaceContext();
|
|
375
|
+
if (!workspaceContext.isPathWithinWorkspace(params.file_path)) {
|
|
376
|
+
const directories = workspaceContext.getDirectories();
|
|
377
|
+
return `File path must be within one of the workspace directories: ${directories.join(', ')}`;
|
|
378
|
+
}
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
createInvocation(params) {
|
|
382
|
+
return new EditToolInvocation(this.config, params);
|
|
383
|
+
}
|
|
353
384
|
getModifyContext(_) {
|
|
354
385
|
return {
|
|
355
386
|
getFilePath: (params) => params.file_path,
|
|
@@ -366,7 +397,7 @@ Expectation for required parameters:
|
|
|
366
397
|
getProposedContent: async (params) => {
|
|
367
398
|
try {
|
|
368
399
|
const currentContent = fs.readFileSync(params.file_path, 'utf8');
|
|
369
|
-
return
|
|
400
|
+
return applyReplacement(currentContent, params.old_string, params.new_string, params.old_string === '' && currentContent === '');
|
|
370
401
|
}
|
|
371
402
|
catch (err) {
|
|
372
403
|
if (!isNodeError(err) || err.code !== 'ENOENT')
|
|
@@ -374,12 +405,16 @@ Expectation for required parameters:
|
|
|
374
405
|
return '';
|
|
375
406
|
}
|
|
376
407
|
},
|
|
377
|
-
createUpdatedParams: (oldContent, modifiedProposedContent, originalParams) =>
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
408
|
+
createUpdatedParams: (oldContent, modifiedProposedContent, originalParams) => {
|
|
409
|
+
const content = originalParams.new_string;
|
|
410
|
+
return {
|
|
411
|
+
...originalParams,
|
|
412
|
+
ai_proposed_string: content,
|
|
413
|
+
old_string: oldContent,
|
|
414
|
+
new_string: modifiedProposedContent,
|
|
415
|
+
modified_by_user: true,
|
|
416
|
+
};
|
|
417
|
+
},
|
|
383
418
|
};
|
|
384
419
|
}
|
|
385
420
|
}
|