wcgw 5.5.4__py3-none-any.whl
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.
- wcgw/__init__.py +4 -0
- wcgw/client/__init__.py +0 -0
- wcgw/client/bash_state/bash_state.py +1426 -0
- wcgw/client/bash_state/parser/__init__.py +7 -0
- wcgw/client/bash_state/parser/bash_statement_parser.py +181 -0
- wcgw/client/common.py +51 -0
- wcgw/client/diff-instructions.txt +73 -0
- wcgw/client/encoder/__init__.py +47 -0
- wcgw/client/file_ops/diff_edit.py +619 -0
- wcgw/client/file_ops/extensions.py +137 -0
- wcgw/client/file_ops/search_replace.py +212 -0
- wcgw/client/mcp_server/Readme.md +3 -0
- wcgw/client/mcp_server/__init__.py +32 -0
- wcgw/client/mcp_server/server.py +184 -0
- wcgw/client/memory.py +103 -0
- wcgw/client/modes.py +240 -0
- wcgw/client/repo_ops/display_tree.py +116 -0
- wcgw/client/repo_ops/file_stats.py +152 -0
- wcgw/client/repo_ops/path_prob.py +58 -0
- wcgw/client/repo_ops/paths_model.vocab +20000 -0
- wcgw/client/repo_ops/paths_tokens.model +80042 -0
- wcgw/client/repo_ops/repo_context.py +289 -0
- wcgw/client/schema_generator.py +63 -0
- wcgw/client/tool_prompts.py +98 -0
- wcgw/client/tools.py +1432 -0
- wcgw/py.typed +0 -0
- wcgw/types_.py +318 -0
- wcgw-5.5.4.dist-info/METADATA +339 -0
- wcgw-5.5.4.dist-info/RECORD +38 -0
- wcgw-5.5.4.dist-info/WHEEL +4 -0
- wcgw-5.5.4.dist-info/entry_points.txt +4 -0
- wcgw-5.5.4.dist-info/licenses/LICENSE +213 -0
- wcgw_cli/__init__.py +1 -0
- wcgw_cli/__main__.py +3 -0
- wcgw_cli/anthropic_client.py +486 -0
- wcgw_cli/cli.py +40 -0
- wcgw_cli/openai_client.py +404 -0
- wcgw_cli/openai_utils.py +67 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from collections import deque
|
|
3
|
+
from pathlib import Path # Still needed for other parts
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from pygit2 import GitError, Repository
|
|
7
|
+
from pygit2.enums import SortMode
|
|
8
|
+
|
|
9
|
+
from .display_tree import DirectoryTree
|
|
10
|
+
from .file_stats import load_workspace_stats
|
|
11
|
+
from .path_prob import FastPathAnalyzer
|
|
12
|
+
|
|
13
|
+
curr_folder = Path(__file__).parent
|
|
14
|
+
vocab_file = curr_folder / "paths_model.vocab"
|
|
15
|
+
model_file = curr_folder / "paths_tokens.model"
|
|
16
|
+
PATH_SCORER = FastPathAnalyzer(str(model_file), str(vocab_file))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def find_ancestor_with_git(path: Path) -> Optional[Repository]:
|
|
20
|
+
if path.is_file():
|
|
21
|
+
path = path.parent
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
return Repository(str(path))
|
|
25
|
+
except GitError:
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
MAX_ENTRIES_CHECK = 100_000
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_all_files_max_depth(
|
|
33
|
+
abs_folder: str,
|
|
34
|
+
max_depth: int,
|
|
35
|
+
repo: Optional[Repository],
|
|
36
|
+
) -> list[str]:
|
|
37
|
+
"""BFS implementation using deque that maintains relative paths during traversal.
|
|
38
|
+
Returns (files_list, total_files_found) to track file count."""
|
|
39
|
+
all_files = []
|
|
40
|
+
# Queue stores: (folder_path, depth, rel_path_prefix)
|
|
41
|
+
queue = deque([(abs_folder, 0, "")])
|
|
42
|
+
entries_check = 0
|
|
43
|
+
while queue and entries_check < MAX_ENTRIES_CHECK:
|
|
44
|
+
current_folder, depth, prefix = queue.popleft()
|
|
45
|
+
|
|
46
|
+
if depth > max_depth:
|
|
47
|
+
continue
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
entries = list(os.scandir(current_folder))
|
|
51
|
+
except PermissionError:
|
|
52
|
+
continue
|
|
53
|
+
except OSError:
|
|
54
|
+
continue
|
|
55
|
+
# Split into files and folders with single scan
|
|
56
|
+
files = []
|
|
57
|
+
folders = []
|
|
58
|
+
for entry in entries:
|
|
59
|
+
entries_check += 1
|
|
60
|
+
try:
|
|
61
|
+
is_file = entry.is_file(follow_symlinks=False)
|
|
62
|
+
except OSError:
|
|
63
|
+
continue
|
|
64
|
+
name = entry.name
|
|
65
|
+
rel_path = f"{prefix}{name}" if prefix else name
|
|
66
|
+
|
|
67
|
+
if repo and repo.path_is_ignored(rel_path):
|
|
68
|
+
continue
|
|
69
|
+
|
|
70
|
+
if is_file:
|
|
71
|
+
files.append(rel_path)
|
|
72
|
+
else:
|
|
73
|
+
folders.append((entry.path, rel_path))
|
|
74
|
+
|
|
75
|
+
# Process files first (maintain priority)
|
|
76
|
+
chunk = files[: min(10_000, max(0, MAX_ENTRIES_CHECK - entries_check))]
|
|
77
|
+
all_files.extend(chunk)
|
|
78
|
+
|
|
79
|
+
# Add folders to queue for BFS traversal
|
|
80
|
+
for folder_path, folder_rel_path in folders:
|
|
81
|
+
next_prefix = f"{folder_rel_path}/"
|
|
82
|
+
queue.append((folder_path, depth + 1, next_prefix))
|
|
83
|
+
|
|
84
|
+
return all_files
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_recent_git_files(repo: Repository, count: int = 10) -> list[str]:
|
|
88
|
+
"""
|
|
89
|
+
Get the most recently modified files from git history
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
repo: The git repository
|
|
93
|
+
count: Number of recent files to return
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
List of relative paths to recently modified files
|
|
97
|
+
"""
|
|
98
|
+
# Track seen files to avoid duplicates
|
|
99
|
+
seen_files: set[str] = set()
|
|
100
|
+
recent_files: list[str] = []
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
# Get the HEAD reference and walk through recent commits
|
|
104
|
+
head = repo.head
|
|
105
|
+
for commit in repo.walk(head.target, SortMode.TOPOLOGICAL | SortMode.TIME):
|
|
106
|
+
# Skip merge commits which have multiple parents
|
|
107
|
+
if len(commit.parents) > 1:
|
|
108
|
+
continue
|
|
109
|
+
|
|
110
|
+
# If we have a parent, get the diff between the commit and its parent
|
|
111
|
+
if commit.parents:
|
|
112
|
+
parent = commit.parents[0]
|
|
113
|
+
diff = repo.diff(parent, commit) # type: ignore[attr-defined]
|
|
114
|
+
else:
|
|
115
|
+
# For the first commit, get the diff against an empty tree
|
|
116
|
+
diff = commit.tree.diff_to_tree(context_lines=0)
|
|
117
|
+
|
|
118
|
+
# Process each changed file in the diff
|
|
119
|
+
for patch in diff:
|
|
120
|
+
file_path = patch.delta.new_file.path
|
|
121
|
+
|
|
122
|
+
# Skip if we've already seen this file or if the file was deleted
|
|
123
|
+
repo_path_parent = Path(repo.path).parent
|
|
124
|
+
if (
|
|
125
|
+
file_path in seen_files
|
|
126
|
+
or not (repo_path_parent / file_path).exists()
|
|
127
|
+
):
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
seen_files.add(file_path)
|
|
131
|
+
recent_files.append(file_path)
|
|
132
|
+
|
|
133
|
+
# If we have enough files, stop
|
|
134
|
+
if len(recent_files) >= count:
|
|
135
|
+
return recent_files
|
|
136
|
+
|
|
137
|
+
except Exception:
|
|
138
|
+
# Handle git errors gracefully
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
return recent_files
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def calculate_dynamic_file_limit(total_files: int) -> int:
|
|
145
|
+
# Scale linearly, with minimum and maximum bounds
|
|
146
|
+
min_files = 50
|
|
147
|
+
max_files = 400
|
|
148
|
+
|
|
149
|
+
if total_files <= min_files:
|
|
150
|
+
return min_files
|
|
151
|
+
|
|
152
|
+
scale_factor = (max_files - min_files) / (30000 - min_files)
|
|
153
|
+
|
|
154
|
+
dynamic_limit = min_files + int((total_files - min_files) * scale_factor)
|
|
155
|
+
|
|
156
|
+
return min(max_files, dynamic_limit)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def get_repo_context(file_or_repo_path: str) -> tuple[str, Path]:
|
|
160
|
+
file_or_repo_path_ = Path(file_or_repo_path).absolute()
|
|
161
|
+
|
|
162
|
+
repo = find_ancestor_with_git(file_or_repo_path_)
|
|
163
|
+
recent_git_files: list[str] = []
|
|
164
|
+
|
|
165
|
+
# Determine the context directory
|
|
166
|
+
if repo is not None:
|
|
167
|
+
context_dir = Path(repo.path).parent
|
|
168
|
+
else:
|
|
169
|
+
if file_or_repo_path_.is_file():
|
|
170
|
+
context_dir = file_or_repo_path_.parent
|
|
171
|
+
else:
|
|
172
|
+
context_dir = file_or_repo_path_
|
|
173
|
+
|
|
174
|
+
# Load workspace stats from the context directory
|
|
175
|
+
workspace_stats = load_workspace_stats(str(context_dir))
|
|
176
|
+
|
|
177
|
+
# Get all files and calculate dynamic max files limit once
|
|
178
|
+
all_files = get_all_files_max_depth(str(context_dir), 10, repo)
|
|
179
|
+
|
|
180
|
+
# For Git repositories, get recent files
|
|
181
|
+
if repo is not None:
|
|
182
|
+
dynamic_max_files = calculate_dynamic_file_limit(len(all_files))
|
|
183
|
+
# Get recent git files - get at least 10 or 20% of dynamic_max_files, whichever is larger
|
|
184
|
+
recent_files_count = max(10, int(dynamic_max_files * 0.2))
|
|
185
|
+
recent_git_files = get_recent_git_files(repo, recent_files_count)
|
|
186
|
+
else:
|
|
187
|
+
# We don't want dynamic limit for non git folders like /tmp or ~
|
|
188
|
+
dynamic_max_files = 50
|
|
189
|
+
|
|
190
|
+
# Calculate probabilities in batch
|
|
191
|
+
path_scores = PATH_SCORER.calculate_path_probabilities_batch(all_files)
|
|
192
|
+
|
|
193
|
+
# Create list of (path, score) tuples and sort by score
|
|
194
|
+
path_with_scores = list(zip(all_files, (score[0] for score in path_scores)))
|
|
195
|
+
sorted_files = [
|
|
196
|
+
path for path, _ in sorted(path_with_scores, key=lambda x: x[1], reverse=True)
|
|
197
|
+
]
|
|
198
|
+
|
|
199
|
+
# Start with recent git files, then add other important files
|
|
200
|
+
top_files = []
|
|
201
|
+
|
|
202
|
+
# If we have workspace stats, prioritize the most active files first
|
|
203
|
+
active_files = []
|
|
204
|
+
if workspace_stats is not None:
|
|
205
|
+
# Get files with activity score (weighted count of operations)
|
|
206
|
+
scored_files = []
|
|
207
|
+
for file_path, file_stats in workspace_stats.files.items():
|
|
208
|
+
try:
|
|
209
|
+
# Convert to relative path if possible
|
|
210
|
+
if str(context_dir) in file_path:
|
|
211
|
+
rel_path = os.path.relpath(file_path, str(context_dir))
|
|
212
|
+
else:
|
|
213
|
+
rel_path = file_path
|
|
214
|
+
|
|
215
|
+
# Calculate activity score - weight reads more for this functionality
|
|
216
|
+
activity_score = (
|
|
217
|
+
file_stats.read_count * 2
|
|
218
|
+
+ (file_stats.edit_count)
|
|
219
|
+
+ (file_stats.write_count)
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# Only include files that still exist
|
|
223
|
+
if rel_path in all_files or os.path.exists(file_path):
|
|
224
|
+
scored_files.append((rel_path, activity_score))
|
|
225
|
+
except (ValueError, OSError):
|
|
226
|
+
# Skip files that cause path resolution errors
|
|
227
|
+
continue
|
|
228
|
+
|
|
229
|
+
# Sort by activity score (highest first) and get top 5
|
|
230
|
+
active_files = [
|
|
231
|
+
f for f, _ in sorted(scored_files, key=lambda x: x[1], reverse=True)[:5]
|
|
232
|
+
]
|
|
233
|
+
|
|
234
|
+
# Add active files first
|
|
235
|
+
for file in active_files:
|
|
236
|
+
if file not in top_files and file in all_files:
|
|
237
|
+
top_files.append(file)
|
|
238
|
+
|
|
239
|
+
# Add recent git files next - these should be prioritized
|
|
240
|
+
for file in recent_git_files:
|
|
241
|
+
if file not in top_files and file in all_files:
|
|
242
|
+
top_files.append(file)
|
|
243
|
+
|
|
244
|
+
# Use statistical sorting for the remaining files, but respect dynamic_max_files limit
|
|
245
|
+
# and ensure we don't add duplicates
|
|
246
|
+
if len(top_files) < dynamic_max_files:
|
|
247
|
+
# Only add statistically important files that aren't already in top_files
|
|
248
|
+
for file in sorted_files:
|
|
249
|
+
if file not in top_files and len(top_files) < dynamic_max_files:
|
|
250
|
+
top_files.append(file)
|
|
251
|
+
|
|
252
|
+
directory_printer = DirectoryTree(context_dir, max_files=dynamic_max_files)
|
|
253
|
+
for file in top_files[:dynamic_max_files]:
|
|
254
|
+
directory_printer.expand(file)
|
|
255
|
+
|
|
256
|
+
return directory_printer.display(), context_dir
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
if __name__ == "__main__":
|
|
260
|
+
import cProfile
|
|
261
|
+
import pstats
|
|
262
|
+
import sys
|
|
263
|
+
|
|
264
|
+
from line_profiler import LineProfiler
|
|
265
|
+
|
|
266
|
+
folder = sys.argv[1]
|
|
267
|
+
|
|
268
|
+
# Profile using cProfile for overall function statistics
|
|
269
|
+
profiler = cProfile.Profile()
|
|
270
|
+
profiler.enable()
|
|
271
|
+
result = get_repo_context(folder)[0]
|
|
272
|
+
profiler.disable()
|
|
273
|
+
|
|
274
|
+
# Print cProfile stats
|
|
275
|
+
stats = pstats.Stats(profiler)
|
|
276
|
+
stats.sort_stats("cumulative")
|
|
277
|
+
print("\n=== Function-level profiling ===")
|
|
278
|
+
stats.print_stats(20) # Print top 20 functions
|
|
279
|
+
|
|
280
|
+
# Profile using line_profiler for line-by-line statistics
|
|
281
|
+
lp = LineProfiler()
|
|
282
|
+
lp_wrapper = lp(get_repo_context)
|
|
283
|
+
lp_wrapper(folder)
|
|
284
|
+
|
|
285
|
+
print("\n=== Line-by-line profiling ===")
|
|
286
|
+
lp.print_stats()
|
|
287
|
+
|
|
288
|
+
print("\n=== Result ===")
|
|
289
|
+
print(result)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom JSON schema generator to remove title fields from Pydantic models.
|
|
3
|
+
|
|
4
|
+
This module provides utilities to remove auto-generated title fields from JSON schemas,
|
|
5
|
+
making them more suitable for tool schemas where titles are not needed.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import copy
|
|
9
|
+
from typing import Any, Dict
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def recursive_purge_dict_key(d: Dict[str, Any], k: str) -> None:
|
|
13
|
+
"""
|
|
14
|
+
Remove a key from a dictionary recursively, but only from JSON schema metadata.
|
|
15
|
+
|
|
16
|
+
This function removes the specified key from dictionaries that appear to be
|
|
17
|
+
JSON schema objects (have "type" or "$ref" or are property definitions).
|
|
18
|
+
This prevents removing legitimate data fields that happen to have the same name.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
d: The dictionary to clean
|
|
22
|
+
k: The key to remove (typically "title")
|
|
23
|
+
"""
|
|
24
|
+
if isinstance(d, dict):
|
|
25
|
+
# Only remove the key if this looks like a JSON schema object
|
|
26
|
+
# This includes objects with "type", "$ref", or if we're in a "properties" context
|
|
27
|
+
is_schema_object = (
|
|
28
|
+
"type" in d or
|
|
29
|
+
"$ref" in d or
|
|
30
|
+
any(schema_key in d for schema_key in ["properties", "items", "additionalProperties", "enum", "const", "anyOf", "allOf", "oneOf"])
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
if is_schema_object and k in d:
|
|
34
|
+
del d[k]
|
|
35
|
+
|
|
36
|
+
# Recursively process all values, regardless of key names
|
|
37
|
+
# This ensures we catch all nested structures
|
|
38
|
+
for key, value in d.items():
|
|
39
|
+
if isinstance(value, dict):
|
|
40
|
+
recursive_purge_dict_key(value, k)
|
|
41
|
+
elif isinstance(value, list):
|
|
42
|
+
for item in value:
|
|
43
|
+
if isinstance(item, dict):
|
|
44
|
+
recursive_purge_dict_key(item, k)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def remove_titles_from_schema(schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
48
|
+
"""
|
|
49
|
+
Remove all 'title' keys from a JSON schema dictionary.
|
|
50
|
+
|
|
51
|
+
This function creates a copy of the schema and removes all title keys
|
|
52
|
+
recursively, making it suitable for use with APIs that don't need titles.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
schema: The JSON schema dictionary to clean
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
A new dictionary with all title keys removed
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
schema_copy = copy.deepcopy(schema)
|
|
62
|
+
recursive_purge_dict_key(schema_copy, "title")
|
|
63
|
+
return schema_copy
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from mcp.types import Tool, ToolAnnotations
|
|
4
|
+
|
|
5
|
+
from ..types_ import (
|
|
6
|
+
BashCommand,
|
|
7
|
+
ContextSave,
|
|
8
|
+
FileWriteOrEdit,
|
|
9
|
+
Initialize,
|
|
10
|
+
ReadFiles,
|
|
11
|
+
ReadImage,
|
|
12
|
+
)
|
|
13
|
+
from .schema_generator import remove_titles_from_schema
|
|
14
|
+
|
|
15
|
+
with open(os.path.join(os.path.dirname(__file__), "diff-instructions.txt")) as f:
|
|
16
|
+
diffinstructions = f.read()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
TOOL_PROMPTS = [
|
|
20
|
+
Tool(
|
|
21
|
+
inputSchema=remove_titles_from_schema(Initialize.model_json_schema()),
|
|
22
|
+
name="Initialize",
|
|
23
|
+
description="""
|
|
24
|
+
- Always call this at the start of the conversation before using any of the shell tools from wcgw.
|
|
25
|
+
- Use `any_workspace_path` to initialize the shell in the appropriate project directory.
|
|
26
|
+
- If the user has mentioned a workspace or project root or any other file or folder use it to set `any_workspace_path`.
|
|
27
|
+
- If user has mentioned any files use `initial_files_to_read` to read, use absolute paths only (~ allowed)
|
|
28
|
+
- By default use mode "wcgw"
|
|
29
|
+
- In "code-writer" mode, set the commands and globs which user asked to set, otherwise use 'all'.
|
|
30
|
+
- Use type="first_call" if it's the first call to this tool.
|
|
31
|
+
- Use type="user_asked_mode_change" if in a conversation user has asked to change mode.
|
|
32
|
+
- Use type="reset_shell" if in a conversation shell is not working after multiple tries.
|
|
33
|
+
- Use type="user_asked_change_workspace" if in a conversation user asked to change workspace
|
|
34
|
+
""",
|
|
35
|
+
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=False),
|
|
36
|
+
),
|
|
37
|
+
Tool(
|
|
38
|
+
inputSchema=remove_titles_from_schema(BashCommand.model_json_schema()),
|
|
39
|
+
name="BashCommand",
|
|
40
|
+
description="""
|
|
41
|
+
- Execute a bash command. This is stateful (beware with subsequent calls).
|
|
42
|
+
- Status of the command and the current working directory will always be returned at the end.
|
|
43
|
+
- The first or the last line might be `(...truncated)` if the output is too long.
|
|
44
|
+
- Always run `pwd` if you get any file or directory not found error to make sure you're not lost.
|
|
45
|
+
- Do not run bg commands using "&", instead use this tool.
|
|
46
|
+
- You must not use echo/cat to read/write files, use ReadFiles/FileWriteOrEdit
|
|
47
|
+
- In order to check status of previous command, use `status_check` with empty command argument.
|
|
48
|
+
- Only command is allowed to run at a time. You need to wait for any previous command to finish before running a new one.
|
|
49
|
+
- Programs don't hang easily, so most likely explanation for no output is usually that the program is still running, and you need to check status again.
|
|
50
|
+
- Do not send Ctrl-c before checking for status till 10 minutes or whatever is appropriate for the program to finish.
|
|
51
|
+
- Only run long running commands in background. Each background command is run in a new non-reusable shell.
|
|
52
|
+
- On running a bg command you'll get a bg command id that you should use to get status or interact.
|
|
53
|
+
""",
|
|
54
|
+
annotations=ToolAnnotations(destructiveHint=True, openWorldHint=True),
|
|
55
|
+
),
|
|
56
|
+
Tool(
|
|
57
|
+
inputSchema=remove_titles_from_schema(ReadFiles.model_json_schema()),
|
|
58
|
+
name="ReadFiles",
|
|
59
|
+
description="""
|
|
60
|
+
- Read full file content of one or more files.
|
|
61
|
+
- Provide absolute paths only (~ allowed)
|
|
62
|
+
- Only if the task requires line numbers understanding:
|
|
63
|
+
- You may extract a range of lines. E.g., `/path/to/file:1-10` for lines 1-10. You can drop start or end like `/path/to/file:1-` or `/path/to/file:-10`
|
|
64
|
+
""",
|
|
65
|
+
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=False),
|
|
66
|
+
),
|
|
67
|
+
Tool(
|
|
68
|
+
inputSchema=remove_titles_from_schema(ReadImage.model_json_schema()),
|
|
69
|
+
name="ReadImage",
|
|
70
|
+
description="Read an image from the shell.",
|
|
71
|
+
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=False),
|
|
72
|
+
),
|
|
73
|
+
Tool(
|
|
74
|
+
inputSchema=remove_titles_from_schema(FileWriteOrEdit.model_json_schema()),
|
|
75
|
+
name="FileWriteOrEdit",
|
|
76
|
+
description="""
|
|
77
|
+
- Writes or edits a file based on the percentage of changes.
|
|
78
|
+
- Use absolute path only (~ allowed).
|
|
79
|
+
- First write down percentage of lines that need to be replaced in the file (between 0-100) in percentage_to_change
|
|
80
|
+
- percentage_to_change should be low if mostly new code is to be added. It should be high if a lot of things are to be replaced.
|
|
81
|
+
- If percentage_to_change > 50, provide full file content in text_or_search_replace_blocks
|
|
82
|
+
- If percentage_to_change <= 50, text_or_search_replace_blocks should be search/replace blocks.
|
|
83
|
+
"""
|
|
84
|
+
+ diffinstructions,
|
|
85
|
+
annotations=ToolAnnotations(
|
|
86
|
+
destructiveHint=True, idempotentHint=True, openWorldHint=False
|
|
87
|
+
),
|
|
88
|
+
),
|
|
89
|
+
Tool(
|
|
90
|
+
inputSchema=remove_titles_from_schema(ContextSave.model_json_schema()),
|
|
91
|
+
name="ContextSave",
|
|
92
|
+
description="""
|
|
93
|
+
Saves provided description and file contents of all the relevant file paths or globs in a single text file.
|
|
94
|
+
- Provide random 3 word unqiue id or whatever user provided.
|
|
95
|
+
- Leave project path as empty string if no project path""",
|
|
96
|
+
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=False),
|
|
97
|
+
),
|
|
98
|
+
]
|