@shareai-lab/kode 1.1.13 → 1.1.16-dev.1
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/entrypoints/cli.js +59 -38
- package/dist/entrypoints/cli.js.map +3 -3
- package/dist/index.js +5 -26
- package/dist/package.json +4 -1
- package/package.json +11 -104
- package/dist/test/testAdapters.js +0 -88
- package/dist/test/testAdapters.js.map +0 -1
- package/src/ProjectOnboarding.tsx +0 -198
- package/src/Tool.ts +0 -83
- package/src/commands/agents.tsx +0 -3416
- package/src/commands/approvedTools.ts +0 -53
- package/src/commands/bug.tsx +0 -20
- package/src/commands/clear.ts +0 -43
- package/src/commands/compact.ts +0 -120
- package/src/commands/config.tsx +0 -19
- package/src/commands/cost.ts +0 -18
- package/src/commands/ctx_viz.ts +0 -209
- package/src/commands/doctor.ts +0 -24
- package/src/commands/help.tsx +0 -19
- package/src/commands/init.ts +0 -37
- package/src/commands/listen.ts +0 -42
- package/src/commands/login.tsx +0 -51
- package/src/commands/logout.tsx +0 -40
- package/src/commands/mcp.ts +0 -41
- package/src/commands/model.tsx +0 -40
- package/src/commands/modelstatus.tsx +0 -20
- package/src/commands/onboarding.tsx +0 -34
- package/src/commands/pr_comments.ts +0 -59
- package/src/commands/refreshCommands.ts +0 -54
- package/src/commands/release-notes.ts +0 -34
- package/src/commands/resume.tsx +0 -31
- package/src/commands/review.ts +0 -49
- package/src/commands/terminalSetup.ts +0 -221
- package/src/commands.ts +0 -139
- package/src/components/ApproveApiKey.tsx +0 -93
- package/src/components/AsciiLogo.tsx +0 -13
- package/src/components/AutoUpdater.tsx +0 -148
- package/src/components/Bug.tsx +0 -367
- package/src/components/Config.tsx +0 -293
- package/src/components/ConsoleOAuthFlow.tsx +0 -327
- package/src/components/Cost.tsx +0 -23
- package/src/components/CostThresholdDialog.tsx +0 -46
- package/src/components/CustomSelect/option-map.ts +0 -42
- package/src/components/CustomSelect/select-option.tsx +0 -78
- package/src/components/CustomSelect/select.tsx +0 -152
- package/src/components/CustomSelect/theme.ts +0 -45
- package/src/components/CustomSelect/use-select-state.ts +0 -414
- package/src/components/CustomSelect/use-select.ts +0 -35
- package/src/components/FallbackToolUseRejectedMessage.tsx +0 -15
- package/src/components/FileEditToolUpdatedMessage.tsx +0 -66
- package/src/components/Help.tsx +0 -215
- package/src/components/HighlightedCode.tsx +0 -33
- package/src/components/InvalidConfigDialog.tsx +0 -113
- package/src/components/Link.tsx +0 -32
- package/src/components/LogSelector.tsx +0 -86
- package/src/components/Logo.tsx +0 -170
- package/src/components/MCPServerApprovalDialog.tsx +0 -100
- package/src/components/MCPServerDialogCopy.tsx +0 -25
- package/src/components/MCPServerMultiselectDialog.tsx +0 -109
- package/src/components/Message.tsx +0 -221
- package/src/components/MessageResponse.tsx +0 -15
- package/src/components/MessageSelector.tsx +0 -211
- package/src/components/ModeIndicator.tsx +0 -88
- package/src/components/ModelConfig.tsx +0 -301
- package/src/components/ModelListManager.tsx +0 -227
- package/src/components/ModelSelector.tsx +0 -3387
- package/src/components/ModelStatusDisplay.tsx +0 -230
- package/src/components/Onboarding.tsx +0 -274
- package/src/components/PressEnterToContinue.tsx +0 -11
- package/src/components/PromptInput.tsx +0 -760
- package/src/components/SentryErrorBoundary.ts +0 -39
- package/src/components/Spinner.tsx +0 -129
- package/src/components/StickerRequestForm.tsx +0 -16
- package/src/components/StructuredDiff.tsx +0 -191
- package/src/components/TextInput.tsx +0 -259
- package/src/components/TodoItem.tsx +0 -47
- package/src/components/TokenWarning.tsx +0 -31
- package/src/components/ToolUseLoader.tsx +0 -40
- package/src/components/TrustDialog.tsx +0 -106
- package/src/components/binary-feedback/BinaryFeedback.tsx +0 -63
- package/src/components/binary-feedback/BinaryFeedbackOption.tsx +0 -111
- package/src/components/binary-feedback/BinaryFeedbackView.tsx +0 -172
- package/src/components/binary-feedback/utils.ts +0 -220
- package/src/components/messages/AssistantBashOutputMessage.tsx +0 -22
- package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +0 -49
- package/src/components/messages/AssistantRedactedThinkingMessage.tsx +0 -19
- package/src/components/messages/AssistantTextMessage.tsx +0 -144
- package/src/components/messages/AssistantThinkingMessage.tsx +0 -40
- package/src/components/messages/AssistantToolUseMessage.tsx +0 -132
- package/src/components/messages/TaskProgressMessage.tsx +0 -32
- package/src/components/messages/TaskToolMessage.tsx +0 -58
- package/src/components/messages/UserBashInputMessage.tsx +0 -28
- package/src/components/messages/UserCommandMessage.tsx +0 -30
- package/src/components/messages/UserKodingInputMessage.tsx +0 -28
- package/src/components/messages/UserPromptMessage.tsx +0 -35
- package/src/components/messages/UserTextMessage.tsx +0 -39
- package/src/components/messages/UserToolResultMessage/UserToolCanceledMessage.tsx +0 -12
- package/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx +0 -36
- package/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx +0 -31
- package/src/components/messages/UserToolResultMessage/UserToolResultMessage.tsx +0 -57
- package/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx +0 -35
- package/src/components/messages/UserToolResultMessage/utils.tsx +0 -56
- package/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx +0 -121
- package/src/components/permissions/FallbackPermissionRequest.tsx +0 -153
- package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +0 -182
- package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +0 -77
- package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +0 -164
- package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +0 -83
- package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +0 -240
- package/src/components/permissions/PermissionRequest.tsx +0 -101
- package/src/components/permissions/PermissionRequestTitle.tsx +0 -69
- package/src/components/permissions/hooks.ts +0 -44
- package/src/components/permissions/toolUseOptions.ts +0 -59
- package/src/components/permissions/utils.ts +0 -23
- package/src/constants/betas.ts +0 -5
- package/src/constants/claude-asterisk-ascii-art.tsx +0 -238
- package/src/constants/figures.ts +0 -4
- package/src/constants/keys.ts +0 -3
- package/src/constants/macros.ts +0 -11
- package/src/constants/modelCapabilities.ts +0 -179
- package/src/constants/models.ts +0 -1025
- package/src/constants/oauth.ts +0 -18
- package/src/constants/product.ts +0 -17
- package/src/constants/prompts.ts +0 -168
- package/src/constants/releaseNotes.ts +0 -7
- package/src/context/PermissionContext.tsx +0 -149
- package/src/context.ts +0 -278
- package/src/cost-tracker.ts +0 -84
- package/src/entrypoints/cli.tsx +0 -1561
- package/src/entrypoints/mcp.ts +0 -175
- package/src/history.ts +0 -25
- package/src/hooks/useApiKeyVerification.ts +0 -59
- package/src/hooks/useArrowKeyHistory.ts +0 -55
- package/src/hooks/useCanUseTool.ts +0 -138
- package/src/hooks/useCancelRequest.ts +0 -39
- package/src/hooks/useDoublePress.ts +0 -41
- package/src/hooks/useExitOnCtrlCD.ts +0 -31
- package/src/hooks/useInterval.ts +0 -25
- package/src/hooks/useLogMessages.ts +0 -16
- package/src/hooks/useLogStartupTime.ts +0 -12
- package/src/hooks/useNotifyAfterTimeout.ts +0 -65
- package/src/hooks/usePermissionRequestLogging.ts +0 -44
- package/src/hooks/useTerminalSize.ts +0 -49
- package/src/hooks/useTextInput.ts +0 -317
- package/src/hooks/useUnifiedCompletion.ts +0 -1405
- package/src/index.ts +0 -34
- package/src/messages.ts +0 -38
- package/src/permissions.ts +0 -268
- package/src/query.ts +0 -720
- package/src/screens/ConfigureNpmPrefix.tsx +0 -197
- package/src/screens/Doctor.tsx +0 -219
- package/src/screens/LogList.tsx +0 -68
- package/src/screens/REPL.tsx +0 -813
- package/src/screens/ResumeConversation.tsx +0 -68
- package/src/services/adapters/base.ts +0 -38
- package/src/services/adapters/chatCompletions.ts +0 -90
- package/src/services/adapters/responsesAPI.ts +0 -170
- package/src/services/browserMocks.ts +0 -66
- package/src/services/claude.ts +0 -2197
- package/src/services/customCommands.ts +0 -704
- package/src/services/fileFreshness.ts +0 -377
- package/src/services/gpt5ConnectionTest.ts +0 -340
- package/src/services/mcpClient.ts +0 -564
- package/src/services/mcpServerApproval.tsx +0 -50
- package/src/services/mentionProcessor.ts +0 -273
- package/src/services/modelAdapterFactory.ts +0 -69
- package/src/services/notifier.ts +0 -40
- package/src/services/oauth.ts +0 -357
- package/src/services/openai.ts +0 -1359
- package/src/services/responseStateManager.ts +0 -90
- package/src/services/sentry.ts +0 -3
- package/src/services/statsig.ts +0 -172
- package/src/services/statsigStorage.ts +0 -86
- package/src/services/systemReminder.ts +0 -507
- package/src/services/vcr.ts +0 -161
- package/src/test/testAdapters.ts +0 -96
- package/src/tools/ArchitectTool/ArchitectTool.tsx +0 -135
- package/src/tools/ArchitectTool/prompt.ts +0 -15
- package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +0 -576
- package/src/tools/BashTool/BashTool.tsx +0 -243
- package/src/tools/BashTool/BashToolResultMessage.tsx +0 -38
- package/src/tools/BashTool/OutputLine.tsx +0 -49
- package/src/tools/BashTool/prompt.ts +0 -174
- package/src/tools/BashTool/utils.ts +0 -56
- package/src/tools/FileEditTool/FileEditTool.tsx +0 -319
- package/src/tools/FileEditTool/prompt.ts +0 -51
- package/src/tools/FileEditTool/utils.ts +0 -58
- package/src/tools/FileReadTool/FileReadTool.tsx +0 -404
- package/src/tools/FileReadTool/prompt.ts +0 -7
- package/src/tools/FileWriteTool/FileWriteTool.tsx +0 -301
- package/src/tools/FileWriteTool/prompt.ts +0 -10
- package/src/tools/GlobTool/GlobTool.tsx +0 -119
- package/src/tools/GlobTool/prompt.ts +0 -8
- package/src/tools/GrepTool/GrepTool.tsx +0 -147
- package/src/tools/GrepTool/prompt.ts +0 -11
- package/src/tools/MCPTool/MCPTool.tsx +0 -107
- package/src/tools/MCPTool/prompt.ts +0 -3
- package/src/tools/MemoryReadTool/MemoryReadTool.tsx +0 -127
- package/src/tools/MemoryReadTool/prompt.ts +0 -3
- package/src/tools/MemoryWriteTool/MemoryWriteTool.tsx +0 -89
- package/src/tools/MemoryWriteTool/prompt.ts +0 -3
- package/src/tools/MultiEditTool/MultiEditTool.tsx +0 -388
- package/src/tools/MultiEditTool/prompt.ts +0 -45
- package/src/tools/NotebookEditTool/NotebookEditTool.tsx +0 -298
- package/src/tools/NotebookEditTool/prompt.ts +0 -3
- package/src/tools/NotebookReadTool/NotebookReadTool.tsx +0 -258
- package/src/tools/NotebookReadTool/prompt.ts +0 -3
- package/src/tools/StickerRequestTool/StickerRequestTool.tsx +0 -107
- package/src/tools/StickerRequestTool/prompt.ts +0 -19
- package/src/tools/TaskTool/TaskTool.tsx +0 -438
- package/src/tools/TaskTool/constants.ts +0 -1
- package/src/tools/TaskTool/prompt.ts +0 -92
- package/src/tools/ThinkTool/ThinkTool.tsx +0 -54
- package/src/tools/ThinkTool/prompt.ts +0 -12
- package/src/tools/TodoWriteTool/TodoWriteTool.tsx +0 -313
- package/src/tools/TodoWriteTool/prompt.ts +0 -63
- package/src/tools/URLFetcherTool/URLFetcherTool.tsx +0 -178
- package/src/tools/URLFetcherTool/cache.ts +0 -55
- package/src/tools/URLFetcherTool/htmlToMarkdown.ts +0 -55
- package/src/tools/URLFetcherTool/prompt.ts +0 -17
- package/src/tools/WebSearchTool/WebSearchTool.tsx +0 -103
- package/src/tools/WebSearchTool/prompt.ts +0 -13
- package/src/tools/WebSearchTool/searchProviders.ts +0 -66
- package/src/tools/lsTool/lsTool.tsx +0 -272
- package/src/tools/lsTool/prompt.ts +0 -2
- package/src/tools.ts +0 -67
- package/src/types/PermissionMode.ts +0 -120
- package/src/types/RequestContext.ts +0 -72
- package/src/types/common.d.ts +0 -2
- package/src/types/conversation.ts +0 -51
- package/src/types/logs.ts +0 -58
- package/src/types/modelCapabilities.ts +0 -64
- package/src/types/notebook.ts +0 -87
- package/src/utils/Cursor.ts +0 -436
- package/src/utils/PersistentShell.ts +0 -552
- package/src/utils/advancedFuzzyMatcher.ts +0 -290
- package/src/utils/agentLoader.ts +0 -278
- package/src/utils/agentStorage.ts +0 -97
- package/src/utils/array.ts +0 -3
- package/src/utils/ask.tsx +0 -99
- package/src/utils/auth.ts +0 -13
- package/src/utils/autoCompactCore.ts +0 -223
- package/src/utils/autoUpdater.ts +0 -458
- package/src/utils/betas.ts +0 -20
- package/src/utils/browser.ts +0 -14
- package/src/utils/cleanup.ts +0 -72
- package/src/utils/commands.ts +0 -261
- package/src/utils/commonUnixCommands.ts +0 -161
- package/src/utils/config.ts +0 -945
- package/src/utils/conversationRecovery.ts +0 -55
- package/src/utils/debugLogger.ts +0 -1235
- package/src/utils/diff.ts +0 -42
- package/src/utils/env.ts +0 -57
- package/src/utils/errors.ts +0 -21
- package/src/utils/exampleCommands.ts +0 -109
- package/src/utils/execFileNoThrow.ts +0 -51
- package/src/utils/expertChatStorage.ts +0 -136
- package/src/utils/file.ts +0 -405
- package/src/utils/fileRecoveryCore.ts +0 -71
- package/src/utils/format.tsx +0 -44
- package/src/utils/fuzzyMatcher.ts +0 -328
- package/src/utils/generators.ts +0 -62
- package/src/utils/git.ts +0 -92
- package/src/utils/globalLogger.ts +0 -77
- package/src/utils/http.ts +0 -10
- package/src/utils/imagePaste.ts +0 -38
- package/src/utils/json.ts +0 -13
- package/src/utils/log.ts +0 -382
- package/src/utils/markdown.ts +0 -213
- package/src/utils/messageContextManager.ts +0 -294
- package/src/utils/messages.tsx +0 -945
- package/src/utils/model.ts +0 -914
- package/src/utils/permissions/filesystem.ts +0 -127
- package/src/utils/responseState.ts +0 -23
- package/src/utils/ripgrep.ts +0 -167
- package/src/utils/secureFile.ts +0 -564
- package/src/utils/sessionState.ts +0 -49
- package/src/utils/state.ts +0 -25
- package/src/utils/style.ts +0 -29
- package/src/utils/terminal.ts +0 -50
- package/src/utils/theme.ts +0 -127
- package/src/utils/thinking.ts +0 -144
- package/src/utils/todoStorage.ts +0 -431
- package/src/utils/tokens.ts +0 -43
- package/src/utils/toolExecutionController.ts +0 -163
- package/src/utils/unaryLogging.ts +0 -26
- package/src/utils/user.ts +0 -37
- package/src/utils/validate.ts +0 -165
|
@@ -1,404 +0,0 @@
|
|
|
1
|
-
import { ImageBlockParam } from '@anthropic-ai/sdk/resources/index.mjs'
|
|
2
|
-
import { statSync } from 'node:fs'
|
|
3
|
-
import { Box, Text } from 'ink'
|
|
4
|
-
import * as path from 'node:path'
|
|
5
|
-
import { extname, relative } from 'node:path'
|
|
6
|
-
import * as React from 'react'
|
|
7
|
-
import { z } from 'zod'
|
|
8
|
-
import { FallbackToolUseRejectedMessage } from '../../components/FallbackToolUseRejectedMessage'
|
|
9
|
-
import { HighlightedCode } from '../../components/HighlightedCode'
|
|
10
|
-
import type { Tool } from '../../Tool'
|
|
11
|
-
import { getCwd } from '../../utils/state'
|
|
12
|
-
import {
|
|
13
|
-
addLineNumbers,
|
|
14
|
-
findSimilarFile,
|
|
15
|
-
normalizeFilePath,
|
|
16
|
-
readTextContent,
|
|
17
|
-
} from '../../utils/file.js'
|
|
18
|
-
import { logError } from '../../utils/log'
|
|
19
|
-
import { getTheme } from '../../utils/theme'
|
|
20
|
-
import { emitReminderEvent } from '../../services/systemReminder'
|
|
21
|
-
import {
|
|
22
|
-
recordFileRead,
|
|
23
|
-
generateFileModificationReminder,
|
|
24
|
-
} from '../../services/fileFreshness'
|
|
25
|
-
import { DESCRIPTION, PROMPT } from './prompt'
|
|
26
|
-
import { hasReadPermission } from '../../utils/permissions/filesystem'
|
|
27
|
-
import { secureFileService } from '../../utils/secureFile'
|
|
28
|
-
|
|
29
|
-
const MAX_LINES_TO_RENDER = 5
|
|
30
|
-
const MAX_OUTPUT_SIZE = 0.25 * 1024 * 1024 // 0.25MB in bytes
|
|
31
|
-
|
|
32
|
-
// Common image extensions
|
|
33
|
-
const IMAGE_EXTENSIONS = new Set([
|
|
34
|
-
'.png',
|
|
35
|
-
'.jpg',
|
|
36
|
-
'.jpeg',
|
|
37
|
-
'.gif',
|
|
38
|
-
'.bmp',
|
|
39
|
-
'.webp',
|
|
40
|
-
])
|
|
41
|
-
|
|
42
|
-
// Maximum dimensions for images
|
|
43
|
-
const MAX_WIDTH = 2000
|
|
44
|
-
const MAX_HEIGHT = 2000
|
|
45
|
-
const MAX_IMAGE_SIZE = 3.75 * 1024 * 1024 // 5MB in bytes, with base64 encoding
|
|
46
|
-
|
|
47
|
-
const inputSchema = z.strictObject({
|
|
48
|
-
file_path: z.string().describe('The absolute path to the file to read'),
|
|
49
|
-
offset: z
|
|
50
|
-
.number()
|
|
51
|
-
.optional()
|
|
52
|
-
.describe(
|
|
53
|
-
'The line number to start reading from. Only provide if the file is too large to read at once',
|
|
54
|
-
),
|
|
55
|
-
limit: z
|
|
56
|
-
.number()
|
|
57
|
-
.optional()
|
|
58
|
-
.describe(
|
|
59
|
-
'The number of lines to read. Only provide if the file is too large to read at once.',
|
|
60
|
-
),
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
export const FileReadTool = {
|
|
64
|
-
name: 'View',
|
|
65
|
-
async description() {
|
|
66
|
-
return DESCRIPTION
|
|
67
|
-
},
|
|
68
|
-
async prompt() {
|
|
69
|
-
return PROMPT
|
|
70
|
-
},
|
|
71
|
-
inputSchema,
|
|
72
|
-
isReadOnly() {
|
|
73
|
-
return true
|
|
74
|
-
},
|
|
75
|
-
isConcurrencySafe() {
|
|
76
|
-
return true // FileRead is read-only, safe for concurrent execution
|
|
77
|
-
},
|
|
78
|
-
userFacingName() {
|
|
79
|
-
return 'Read'
|
|
80
|
-
},
|
|
81
|
-
async isEnabled() {
|
|
82
|
-
return true
|
|
83
|
-
},
|
|
84
|
-
needsPermissions({ file_path }) {
|
|
85
|
-
return !hasReadPermission(file_path || getCwd())
|
|
86
|
-
},
|
|
87
|
-
renderToolUseMessage(input, { verbose }) {
|
|
88
|
-
const { file_path, ...rest } = input
|
|
89
|
-
const entries = [
|
|
90
|
-
['file_path', verbose ? file_path : relative(getCwd(), file_path)],
|
|
91
|
-
...Object.entries(rest),
|
|
92
|
-
]
|
|
93
|
-
return entries
|
|
94
|
-
.map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
|
|
95
|
-
.join(', ')
|
|
96
|
-
},
|
|
97
|
-
renderToolResultMessage(output) {
|
|
98
|
-
const verbose = false // Set default value for verbose
|
|
99
|
-
// TODO: Render recursively
|
|
100
|
-
switch (output.type) {
|
|
101
|
-
case 'image':
|
|
102
|
-
return (
|
|
103
|
-
<Box justifyContent="space-between" overflowX="hidden" width="100%">
|
|
104
|
-
<Box flexDirection="row">
|
|
105
|
-
<Text> ⎿ </Text>
|
|
106
|
-
<Text>Read image</Text>
|
|
107
|
-
</Box>
|
|
108
|
-
</Box>
|
|
109
|
-
)
|
|
110
|
-
case 'text': {
|
|
111
|
-
const { filePath, content, numLines } = output.file
|
|
112
|
-
const contentWithFallback = content || '(No content)'
|
|
113
|
-
return (
|
|
114
|
-
<Box justifyContent="space-between" overflowX="hidden" width="100%">
|
|
115
|
-
<Box flexDirection="row">
|
|
116
|
-
<Text> ⎿ </Text>
|
|
117
|
-
<Box flexDirection="column">
|
|
118
|
-
<HighlightedCode
|
|
119
|
-
code={
|
|
120
|
-
verbose
|
|
121
|
-
? contentWithFallback
|
|
122
|
-
: contentWithFallback
|
|
123
|
-
.split('\n')
|
|
124
|
-
.slice(0, MAX_LINES_TO_RENDER)
|
|
125
|
-
.filter(_ => _.trim() !== '')
|
|
126
|
-
.join('\n')
|
|
127
|
-
}
|
|
128
|
-
language={extname(filePath).slice(1)}
|
|
129
|
-
/>
|
|
130
|
-
{!verbose && numLines > MAX_LINES_TO_RENDER && (
|
|
131
|
-
<Text color={getTheme().secondaryText}>
|
|
132
|
-
... (+{numLines - MAX_LINES_TO_RENDER} lines)
|
|
133
|
-
</Text>
|
|
134
|
-
)}
|
|
135
|
-
</Box>
|
|
136
|
-
</Box>
|
|
137
|
-
</Box>
|
|
138
|
-
)
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
},
|
|
142
|
-
renderToolUseRejectedMessage() {
|
|
143
|
-
return <FallbackToolUseRejectedMessage />
|
|
144
|
-
},
|
|
145
|
-
async validateInput({ file_path, offset, limit }) {
|
|
146
|
-
const fullFilePath = normalizeFilePath(file_path)
|
|
147
|
-
|
|
148
|
-
// Use secure file service to check if file exists and get file info
|
|
149
|
-
const fileCheck = secureFileService.safeGetFileInfo(fullFilePath)
|
|
150
|
-
if (!fileCheck.success) {
|
|
151
|
-
// Try to find a similar file with a different extension
|
|
152
|
-
const similarFilename = findSimilarFile(fullFilePath)
|
|
153
|
-
let message = 'File does not exist.'
|
|
154
|
-
|
|
155
|
-
// If we found a similar file, suggest it to the assistant
|
|
156
|
-
if (similarFilename) {
|
|
157
|
-
message += ` Did you mean ${similarFilename}?`
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return {
|
|
161
|
-
result: false,
|
|
162
|
-
message,
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const stats = fileCheck.stats!
|
|
167
|
-
const fileSize = stats.size
|
|
168
|
-
const ext = path.extname(fullFilePath).toLowerCase()
|
|
169
|
-
|
|
170
|
-
// Skip size check for image files - they have their own size limits
|
|
171
|
-
if (!IMAGE_EXTENSIONS.has(ext)) {
|
|
172
|
-
// If file is too large and no offset/limit provided
|
|
173
|
-
if (fileSize > MAX_OUTPUT_SIZE && !offset && !limit) {
|
|
174
|
-
return {
|
|
175
|
-
result: false,
|
|
176
|
-
message: formatFileSizeError(fileSize),
|
|
177
|
-
meta: { fileSize },
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return { result: true }
|
|
183
|
-
},
|
|
184
|
-
async *call(
|
|
185
|
-
{ file_path, offset = 1, limit = undefined },
|
|
186
|
-
{ readFileTimestamps },
|
|
187
|
-
) {
|
|
188
|
-
const ext = path.extname(file_path).toLowerCase()
|
|
189
|
-
const fullFilePath = normalizeFilePath(file_path)
|
|
190
|
-
|
|
191
|
-
// Record file read for freshness tracking
|
|
192
|
-
recordFileRead(fullFilePath)
|
|
193
|
-
|
|
194
|
-
// Emit file read event for system reminders
|
|
195
|
-
emitReminderEvent('file:read', {
|
|
196
|
-
filePath: fullFilePath,
|
|
197
|
-
extension: ext,
|
|
198
|
-
timestamp: Date.now(),
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
// Update read timestamp, to invalidate stale writes
|
|
202
|
-
readFileTimestamps[fullFilePath] = Date.now()
|
|
203
|
-
|
|
204
|
-
// Check for file modifications and generate reminder if needed
|
|
205
|
-
const modificationReminder = generateFileModificationReminder(fullFilePath)
|
|
206
|
-
if (modificationReminder) {
|
|
207
|
-
emitReminderEvent('file:modified', {
|
|
208
|
-
filePath: fullFilePath,
|
|
209
|
-
reminder: modificationReminder,
|
|
210
|
-
timestamp: Date.now(),
|
|
211
|
-
})
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// If it's an image file, process and return base64 encoded contents
|
|
215
|
-
if (IMAGE_EXTENSIONS.has(ext)) {
|
|
216
|
-
const data = await readImage(fullFilePath, ext)
|
|
217
|
-
yield {
|
|
218
|
-
type: 'result',
|
|
219
|
-
data,
|
|
220
|
-
resultForAssistant: this.renderResultForAssistant(data),
|
|
221
|
-
}
|
|
222
|
-
return
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Handle offset properly - if offset is 0, don't subtract 1
|
|
226
|
-
const lineOffset = offset === 0 ? 0 : offset - 1
|
|
227
|
-
const { content, lineCount, totalLines } = readTextContent(
|
|
228
|
-
fullFilePath,
|
|
229
|
-
lineOffset,
|
|
230
|
-
limit,
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
// Add size validation after reading for non-image files
|
|
234
|
-
if (!IMAGE_EXTENSIONS.has(ext) && content.length > MAX_OUTPUT_SIZE) {
|
|
235
|
-
throw new Error(formatFileSizeError(content.length))
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const data = {
|
|
239
|
-
type: 'text' as const,
|
|
240
|
-
file: {
|
|
241
|
-
filePath: file_path,
|
|
242
|
-
content: content,
|
|
243
|
-
numLines: lineCount,
|
|
244
|
-
startLine: offset,
|
|
245
|
-
totalLines,
|
|
246
|
-
},
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
yield {
|
|
250
|
-
type: 'result',
|
|
251
|
-
data,
|
|
252
|
-
resultForAssistant: this.renderResultForAssistant(data),
|
|
253
|
-
}
|
|
254
|
-
},
|
|
255
|
-
renderResultForAssistant(data) {
|
|
256
|
-
switch (data.type) {
|
|
257
|
-
case 'image':
|
|
258
|
-
return [
|
|
259
|
-
{
|
|
260
|
-
type: 'image',
|
|
261
|
-
source: {
|
|
262
|
-
type: 'base64',
|
|
263
|
-
data: data.file.base64,
|
|
264
|
-
media_type: data.file.type,
|
|
265
|
-
},
|
|
266
|
-
},
|
|
267
|
-
]
|
|
268
|
-
case 'text':
|
|
269
|
-
return addLineNumbers(data.file)
|
|
270
|
-
}
|
|
271
|
-
},
|
|
272
|
-
} satisfies Tool<
|
|
273
|
-
typeof inputSchema,
|
|
274
|
-
| {
|
|
275
|
-
type: 'text'
|
|
276
|
-
file: {
|
|
277
|
-
filePath: string
|
|
278
|
-
content: string
|
|
279
|
-
numLines: number
|
|
280
|
-
startLine: number
|
|
281
|
-
totalLines: number
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
| {
|
|
285
|
-
type: 'image'
|
|
286
|
-
file: { base64: string; type: ImageBlockParam.Source['media_type'] }
|
|
287
|
-
}
|
|
288
|
-
>
|
|
289
|
-
|
|
290
|
-
const formatFileSizeError = (sizeInBytes: number) =>
|
|
291
|
-
`File content (${Math.round(sizeInBytes / 1024)}KB) exceeds maximum allowed size (${Math.round(MAX_OUTPUT_SIZE / 1024)}KB). Please use offset and limit parameters to read specific portions of the file, or use the GrepTool to search for specific content.`
|
|
292
|
-
|
|
293
|
-
function createImageResponse(
|
|
294
|
-
buffer: Buffer,
|
|
295
|
-
ext: string,
|
|
296
|
-
): {
|
|
297
|
-
type: 'image'
|
|
298
|
-
file: { base64: string; type: ImageBlockParam.Source['media_type'] }
|
|
299
|
-
} {
|
|
300
|
-
return {
|
|
301
|
-
type: 'image',
|
|
302
|
-
file: {
|
|
303
|
-
base64: buffer.toString('base64'),
|
|
304
|
-
type: `image/${ext.slice(1)}` as ImageBlockParam.Source['media_type'],
|
|
305
|
-
},
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
async function readImage(
|
|
310
|
-
filePath: string,
|
|
311
|
-
ext: string,
|
|
312
|
-
): Promise<{
|
|
313
|
-
type: 'image'
|
|
314
|
-
file: { base64: string; type: ImageBlockParam.Source['media_type'] }
|
|
315
|
-
}> {
|
|
316
|
-
try {
|
|
317
|
-
const stats = statSync(filePath)
|
|
318
|
-
const sharp = (
|
|
319
|
-
(await import('sharp')) as unknown as { default: typeof import('sharp') }
|
|
320
|
-
).default
|
|
321
|
-
|
|
322
|
-
// Use secure file service to read the file
|
|
323
|
-
const fileReadResult = secureFileService.safeReadFile(filePath, {
|
|
324
|
-
encoding: 'buffer' as BufferEncoding,
|
|
325
|
-
maxFileSize: MAX_IMAGE_SIZE
|
|
326
|
-
})
|
|
327
|
-
|
|
328
|
-
if (!fileReadResult.success) {
|
|
329
|
-
throw new Error(`Failed to read image file: ${fileReadResult.error}`)
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
const image = sharp(fileReadResult.content as Buffer)
|
|
333
|
-
const metadata = await image.metadata()
|
|
334
|
-
|
|
335
|
-
if (!metadata.width || !metadata.height) {
|
|
336
|
-
if (stats.size > MAX_IMAGE_SIZE) {
|
|
337
|
-
const compressedBuffer = await image.jpeg({ quality: 80 }).toBuffer()
|
|
338
|
-
return createImageResponse(compressedBuffer, 'jpeg')
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// Calculate dimensions while maintaining aspect ratio
|
|
343
|
-
let width = metadata.width || 0
|
|
344
|
-
let height = metadata.height || 0
|
|
345
|
-
|
|
346
|
-
// Check if the original file just works
|
|
347
|
-
if (
|
|
348
|
-
stats.size <= MAX_IMAGE_SIZE &&
|
|
349
|
-
width <= MAX_WIDTH &&
|
|
350
|
-
height <= MAX_HEIGHT
|
|
351
|
-
) {
|
|
352
|
-
// Use secure file service to read the file
|
|
353
|
-
const fileReadResult = secureFileService.safeReadFile(filePath, {
|
|
354
|
-
encoding: 'buffer' as BufferEncoding,
|
|
355
|
-
maxFileSize: MAX_IMAGE_SIZE
|
|
356
|
-
})
|
|
357
|
-
|
|
358
|
-
if (!fileReadResult.success) {
|
|
359
|
-
throw new Error(`Failed to read image file: ${fileReadResult.error}`)
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
return createImageResponse(fileReadResult.content as Buffer, ext)
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
if (width > MAX_WIDTH) {
|
|
366
|
-
height = Math.round((height * MAX_WIDTH) / width)
|
|
367
|
-
width = MAX_WIDTH
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
if (height > MAX_HEIGHT) {
|
|
371
|
-
width = Math.round((width * MAX_HEIGHT) / height)
|
|
372
|
-
height = MAX_HEIGHT
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Resize image and convert to buffer
|
|
376
|
-
const resizedImageBuffer = await image
|
|
377
|
-
.resize(width, height, {
|
|
378
|
-
fit: 'inside',
|
|
379
|
-
withoutEnlargement: true,
|
|
380
|
-
})
|
|
381
|
-
.toBuffer()
|
|
382
|
-
|
|
383
|
-
// If still too large after resize, compress quality
|
|
384
|
-
if (resizedImageBuffer.length > MAX_IMAGE_SIZE) {
|
|
385
|
-
const compressedBuffer = await image.jpeg({ quality: 80 }).toBuffer()
|
|
386
|
-
return createImageResponse(compressedBuffer, 'jpeg')
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
return createImageResponse(resizedImageBuffer, ext)
|
|
390
|
-
} catch (e) {
|
|
391
|
-
logError(e)
|
|
392
|
-
// If any error occurs during processing, return original image
|
|
393
|
-
const fileReadResult = secureFileService.safeReadFile(filePath, {
|
|
394
|
-
encoding: 'buffer' as BufferEncoding,
|
|
395
|
-
maxFileSize: MAX_IMAGE_SIZE
|
|
396
|
-
})
|
|
397
|
-
|
|
398
|
-
if (!fileReadResult.success) {
|
|
399
|
-
throw new Error(`Failed to read image file: ${fileReadResult.error}`)
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
return createImageResponse(fileReadResult.content as Buffer, ext)
|
|
403
|
-
}
|
|
404
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { NotebookReadTool } from '../NotebookReadTool/NotebookReadTool'
|
|
2
|
-
|
|
3
|
-
const MAX_LINES_TO_READ = 2000
|
|
4
|
-
const MAX_LINE_LENGTH = 2000
|
|
5
|
-
|
|
6
|
-
export const DESCRIPTION = 'Read a file from the local filesystem.'
|
|
7
|
-
export const PROMPT = `Reads a file from the local filesystem. The file_path parameter must be an absolute path, not a relative path. By default, it reads up to ${MAX_LINES_TO_READ} lines starting from the beginning of the file. You can optionally specify a line offset and limit (especially handy for long files), but it's recommended to read the whole file by not providing these parameters. Any lines longer than ${MAX_LINE_LENGTH} characters will be truncated. For image files, the tool will display the image for you. For Jupyter notebooks (.ipynb files), use the ${NotebookReadTool.name} instead.`
|
|
@@ -1,301 +0,0 @@
|
|
|
1
|
-
import { Hunk } from 'diff'
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, statSync } from 'fs'
|
|
3
|
-
import { Box, Text } from 'ink'
|
|
4
|
-
import { EOL } from 'os'
|
|
5
|
-
import { dirname, extname, isAbsolute, relative, resolve, sep } from 'path'
|
|
6
|
-
import * as React from 'react'
|
|
7
|
-
import { z } from 'zod'
|
|
8
|
-
import { FileEditToolUpdatedMessage } from '../../components/FileEditToolUpdatedMessage'
|
|
9
|
-
import { HighlightedCode } from '../../components/HighlightedCode'
|
|
10
|
-
import { StructuredDiff } from '../../components/StructuredDiff'
|
|
11
|
-
import { FallbackToolUseRejectedMessage } from '../../components/FallbackToolUseRejectedMessage'
|
|
12
|
-
import { logEvent } from '../../services/statsig'
|
|
13
|
-
import type { Tool } from '../../Tool'
|
|
14
|
-
import { intersperse } from '../../utils/array'
|
|
15
|
-
import {
|
|
16
|
-
addLineNumbers,
|
|
17
|
-
detectFileEncoding,
|
|
18
|
-
detectLineEndings,
|
|
19
|
-
detectRepoLineEndings,
|
|
20
|
-
writeTextContent,
|
|
21
|
-
} from '../../utils/file.js'
|
|
22
|
-
import { logError } from '../../utils/log'
|
|
23
|
-
import { getCwd } from '../../utils/state'
|
|
24
|
-
import { getTheme } from '../../utils/theme'
|
|
25
|
-
import { PROMPT } from './prompt'
|
|
26
|
-
import { hasWritePermission } from '../../utils/permissions/filesystem'
|
|
27
|
-
import { getPatch } from '../../utils/diff'
|
|
28
|
-
import { PROJECT_FILE } from '../../constants/product'
|
|
29
|
-
import { emitReminderEvent } from '../../services/systemReminder'
|
|
30
|
-
import { recordFileEdit } from '../../services/fileFreshness'
|
|
31
|
-
|
|
32
|
-
const MAX_LINES_TO_RENDER = 5
|
|
33
|
-
const MAX_LINES_TO_RENDER_FOR_ASSISTANT = 16000
|
|
34
|
-
const TRUNCATED_MESSAGE =
|
|
35
|
-
'<response clipped><NOTE>To save on context only part of this file has been shown to you. You should retry this tool after you have searched inside the file with Grep in order to find the line numbers of what you are looking for.</NOTE>'
|
|
36
|
-
|
|
37
|
-
const inputSchema = z.strictObject({
|
|
38
|
-
file_path: z
|
|
39
|
-
.string()
|
|
40
|
-
.describe(
|
|
41
|
-
'The absolute path to the file to write (must be absolute, not relative)',
|
|
42
|
-
),
|
|
43
|
-
content: z.string().describe('The content to write to the file'),
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
export const FileWriteTool = {
|
|
47
|
-
name: 'Replace',
|
|
48
|
-
async description() {
|
|
49
|
-
return 'Write a file to the local filesystem.'
|
|
50
|
-
},
|
|
51
|
-
userFacingName: () => 'Write',
|
|
52
|
-
async prompt() {
|
|
53
|
-
return PROMPT
|
|
54
|
-
},
|
|
55
|
-
inputSchema,
|
|
56
|
-
async isEnabled() {
|
|
57
|
-
return true
|
|
58
|
-
},
|
|
59
|
-
isReadOnly() {
|
|
60
|
-
return false
|
|
61
|
-
},
|
|
62
|
-
isConcurrencySafe() {
|
|
63
|
-
return false // FileWriteTool modifies state/files, not safe for concurrent execution
|
|
64
|
-
},
|
|
65
|
-
needsPermissions({ file_path }) {
|
|
66
|
-
return !hasWritePermission(file_path)
|
|
67
|
-
},
|
|
68
|
-
renderToolUseMessage(input, { verbose }) {
|
|
69
|
-
return `file_path: ${verbose ? input.file_path : relative(getCwd(), input.file_path)}`
|
|
70
|
-
},
|
|
71
|
-
renderToolUseRejectedMessage({ file_path, content }: any = {}, { columns, verbose }: any = {}) {
|
|
72
|
-
try {
|
|
73
|
-
if (!file_path) {
|
|
74
|
-
return <FallbackToolUseRejectedMessage />
|
|
75
|
-
}
|
|
76
|
-
const fullFilePath = isAbsolute(file_path)
|
|
77
|
-
? file_path
|
|
78
|
-
: resolve(getCwd(), file_path)
|
|
79
|
-
const oldFileExists = existsSync(fullFilePath)
|
|
80
|
-
const enc = oldFileExists ? detectFileEncoding(fullFilePath) : 'utf-8'
|
|
81
|
-
const oldContent = oldFileExists ? readFileSync(fullFilePath, enc) : null
|
|
82
|
-
const type = oldContent ? 'update' : 'create'
|
|
83
|
-
const patch = getPatch({
|
|
84
|
-
filePath: file_path,
|
|
85
|
-
fileContents: oldContent ?? '',
|
|
86
|
-
oldStr: oldContent ?? '',
|
|
87
|
-
newStr: content,
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
return (
|
|
91
|
-
<Box flexDirection="column">
|
|
92
|
-
<Text>
|
|
93
|
-
{' '}⎿{' '}
|
|
94
|
-
<Text color={getTheme().error}>
|
|
95
|
-
User rejected {type === 'update' ? 'update' : 'write'} to{' '}
|
|
96
|
-
</Text>
|
|
97
|
-
<Text bold>
|
|
98
|
-
{verbose ? file_path : relative(getCwd(), file_path)}
|
|
99
|
-
</Text>
|
|
100
|
-
</Text>
|
|
101
|
-
{intersperse(
|
|
102
|
-
patch.map(_ => (
|
|
103
|
-
<Box flexDirection="column" paddingLeft={5} key={_.newStart}>
|
|
104
|
-
<StructuredDiff patch={_} dim={true} width={columns - 12} />
|
|
105
|
-
</Box>
|
|
106
|
-
)),
|
|
107
|
-
i => (
|
|
108
|
-
<Box paddingLeft={5} key={`ellipsis-${i}`}>
|
|
109
|
-
<Text color={getTheme().secondaryText}>...</Text>
|
|
110
|
-
</Box>
|
|
111
|
-
),
|
|
112
|
-
)}
|
|
113
|
-
</Box>
|
|
114
|
-
)
|
|
115
|
-
} catch (e) {
|
|
116
|
-
// Handle the case where while we were showing the diff, the user manually made the change.
|
|
117
|
-
// TODO: Find a way to show the diff in this case
|
|
118
|
-
logError(e)
|
|
119
|
-
return (
|
|
120
|
-
<Box flexDirection="column">
|
|
121
|
-
<Text>{' '}⎿ (No changes)</Text>
|
|
122
|
-
</Box>
|
|
123
|
-
)
|
|
124
|
-
}
|
|
125
|
-
},
|
|
126
|
-
renderToolResultMessage(
|
|
127
|
-
{ filePath, content, structuredPatch, type }
|
|
128
|
-
) {
|
|
129
|
-
const verbose = false // Default to false since verbose is no longer passed
|
|
130
|
-
switch (type) {
|
|
131
|
-
case 'create': {
|
|
132
|
-
const contentWithFallback = content || '(No content)'
|
|
133
|
-
const numLines = content.split(EOL).length
|
|
134
|
-
|
|
135
|
-
return (
|
|
136
|
-
<Box flexDirection="column">
|
|
137
|
-
<Text>
|
|
138
|
-
{' '}⎿ Wrote {numLines} lines to{' '}
|
|
139
|
-
<Text bold>
|
|
140
|
-
{verbose ? filePath : relative(getCwd(), filePath)}
|
|
141
|
-
</Text>
|
|
142
|
-
</Text>
|
|
143
|
-
<Box flexDirection="column" paddingLeft={5}>
|
|
144
|
-
<HighlightedCode
|
|
145
|
-
code={
|
|
146
|
-
verbose
|
|
147
|
-
? contentWithFallback
|
|
148
|
-
: contentWithFallback
|
|
149
|
-
.split('\n')
|
|
150
|
-
.slice(0, MAX_LINES_TO_RENDER)
|
|
151
|
-
.filter(_ => _.trim() !== '')
|
|
152
|
-
.join('\n')
|
|
153
|
-
}
|
|
154
|
-
language={extname(filePath).slice(1)}
|
|
155
|
-
/>
|
|
156
|
-
{!verbose && numLines > MAX_LINES_TO_RENDER && (
|
|
157
|
-
<Text color={getTheme().secondaryText}>
|
|
158
|
-
... (+{numLines - MAX_LINES_TO_RENDER} lines)
|
|
159
|
-
</Text>
|
|
160
|
-
)}
|
|
161
|
-
</Box>
|
|
162
|
-
</Box>
|
|
163
|
-
)
|
|
164
|
-
}
|
|
165
|
-
case 'update':
|
|
166
|
-
return (
|
|
167
|
-
<FileEditToolUpdatedMessage
|
|
168
|
-
filePath={filePath}
|
|
169
|
-
structuredPatch={structuredPatch}
|
|
170
|
-
verbose={verbose}
|
|
171
|
-
/>
|
|
172
|
-
)
|
|
173
|
-
}
|
|
174
|
-
},
|
|
175
|
-
async validateInput({ file_path }, { readFileTimestamps }) {
|
|
176
|
-
const fullFilePath = isAbsolute(file_path)
|
|
177
|
-
? file_path
|
|
178
|
-
: resolve(getCwd(), file_path)
|
|
179
|
-
if (!existsSync(fullFilePath)) {
|
|
180
|
-
return { result: true }
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const readTimestamp = readFileTimestamps[fullFilePath]
|
|
184
|
-
if (!readTimestamp) {
|
|
185
|
-
return {
|
|
186
|
-
result: false,
|
|
187
|
-
message:
|
|
188
|
-
'File has not been read yet. Read it first before writing to it.',
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Check if file exists and get its last modified time
|
|
193
|
-
const stats = statSync(fullFilePath)
|
|
194
|
-
const lastWriteTime = stats.mtimeMs
|
|
195
|
-
if (lastWriteTime > readTimestamp) {
|
|
196
|
-
return {
|
|
197
|
-
result: false,
|
|
198
|
-
message:
|
|
199
|
-
'File has been modified since read, either by the user or by a linter. Read it again before attempting to write it.',
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return { result: true }
|
|
204
|
-
},
|
|
205
|
-
async *call({ file_path, content }, { readFileTimestamps }) {
|
|
206
|
-
const fullFilePath = isAbsolute(file_path)
|
|
207
|
-
? file_path
|
|
208
|
-
: resolve(getCwd(), file_path)
|
|
209
|
-
const dir = dirname(fullFilePath)
|
|
210
|
-
const oldFileExists = existsSync(fullFilePath)
|
|
211
|
-
const enc = oldFileExists ? detectFileEncoding(fullFilePath) : 'utf-8'
|
|
212
|
-
const oldContent = oldFileExists ? readFileSync(fullFilePath, enc) : null
|
|
213
|
-
|
|
214
|
-
const endings = oldFileExists
|
|
215
|
-
? detectLineEndings(fullFilePath)
|
|
216
|
-
: await detectRepoLineEndings(getCwd())
|
|
217
|
-
|
|
218
|
-
mkdirSync(dir, { recursive: true })
|
|
219
|
-
writeTextContent(fullFilePath, content, enc, endings!)
|
|
220
|
-
|
|
221
|
-
// Record Agent edit operation for file freshness tracking
|
|
222
|
-
recordFileEdit(fullFilePath, content)
|
|
223
|
-
|
|
224
|
-
// Update read timestamp, to invalidate stale writes
|
|
225
|
-
readFileTimestamps[fullFilePath] = statSync(fullFilePath).mtimeMs
|
|
226
|
-
|
|
227
|
-
// Log when writing to CLAUDE.md
|
|
228
|
-
if (fullFilePath.endsWith(`${sep}${PROJECT_FILE}`)) {
|
|
229
|
-
logEvent('tengu_write_claudemd', {})
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Emit file edited event for system reminders
|
|
233
|
-
emitReminderEvent('file:edited', {
|
|
234
|
-
filePath: fullFilePath,
|
|
235
|
-
content,
|
|
236
|
-
oldContent: oldContent || '',
|
|
237
|
-
timestamp: Date.now(),
|
|
238
|
-
operation: oldFileExists ? 'update' : 'create',
|
|
239
|
-
})
|
|
240
|
-
|
|
241
|
-
if (oldContent) {
|
|
242
|
-
const patch = getPatch({
|
|
243
|
-
filePath: file_path,
|
|
244
|
-
fileContents: oldContent,
|
|
245
|
-
oldStr: oldContent,
|
|
246
|
-
newStr: content,
|
|
247
|
-
})
|
|
248
|
-
|
|
249
|
-
const data = {
|
|
250
|
-
type: 'update' as const,
|
|
251
|
-
filePath: file_path,
|
|
252
|
-
content,
|
|
253
|
-
structuredPatch: patch,
|
|
254
|
-
}
|
|
255
|
-
yield {
|
|
256
|
-
type: 'result',
|
|
257
|
-
data,
|
|
258
|
-
resultForAssistant: this.renderResultForAssistant(data),
|
|
259
|
-
}
|
|
260
|
-
return
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const data = {
|
|
264
|
-
type: 'create' as const,
|
|
265
|
-
filePath: file_path,
|
|
266
|
-
content,
|
|
267
|
-
structuredPatch: [],
|
|
268
|
-
}
|
|
269
|
-
yield {
|
|
270
|
-
type: 'result',
|
|
271
|
-
data,
|
|
272
|
-
resultForAssistant: this.renderResultForAssistant(data),
|
|
273
|
-
}
|
|
274
|
-
},
|
|
275
|
-
renderResultForAssistant({ filePath, content, type }) {
|
|
276
|
-
switch (type) {
|
|
277
|
-
case 'create':
|
|
278
|
-
return `File created successfully at: ${filePath}`
|
|
279
|
-
case 'update':
|
|
280
|
-
return `The file ${filePath} has been updated. Here's the result of running \`cat -n\` on a snippet of the edited file:
|
|
281
|
-
${addLineNumbers({
|
|
282
|
-
content:
|
|
283
|
-
content.split(/\r?\n/).length > MAX_LINES_TO_RENDER_FOR_ASSISTANT
|
|
284
|
-
? content
|
|
285
|
-
.split(/\r?\n/)
|
|
286
|
-
.slice(0, MAX_LINES_TO_RENDER_FOR_ASSISTANT)
|
|
287
|
-
.join('\n') + TRUNCATED_MESSAGE
|
|
288
|
-
: content,
|
|
289
|
-
startLine: 1,
|
|
290
|
-
})}`
|
|
291
|
-
}
|
|
292
|
-
},
|
|
293
|
-
} satisfies Tool<
|
|
294
|
-
typeof inputSchema,
|
|
295
|
-
{
|
|
296
|
-
type: 'create' | 'update'
|
|
297
|
-
filePath: string
|
|
298
|
-
content: string
|
|
299
|
-
structuredPatch: Hunk[]
|
|
300
|
-
}
|
|
301
|
-
>
|