aider-ce 0.87.13__py3-none-any.whl → 0.88.0__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.

Potentially problematic release.


This version of aider-ce might be problematic. Click here for more details.

Files changed (60) hide show
  1. aider/__init__.py +1 -1
  2. aider/_version.py +2 -2
  3. aider/args.py +6 -0
  4. aider/coders/architect_coder.py +3 -3
  5. aider/coders/base_coder.py +505 -184
  6. aider/coders/context_coder.py +1 -1
  7. aider/coders/editblock_func_coder.py +2 -2
  8. aider/coders/navigator_coder.py +451 -649
  9. aider/coders/navigator_legacy_prompts.py +49 -284
  10. aider/coders/navigator_prompts.py +46 -473
  11. aider/coders/search_replace.py +0 -0
  12. aider/coders/wholefile_func_coder.py +2 -2
  13. aider/commands.py +56 -44
  14. aider/history.py +14 -12
  15. aider/io.py +354 -117
  16. aider/llm.py +12 -4
  17. aider/main.py +22 -19
  18. aider/mcp/__init__.py +65 -2
  19. aider/mcp/server.py +37 -11
  20. aider/models.py +45 -20
  21. aider/onboarding.py +4 -4
  22. aider/repo.py +7 -7
  23. aider/resources/model-metadata.json +8 -8
  24. aider/scrape.py +2 -2
  25. aider/sendchat.py +185 -15
  26. aider/tools/__init__.py +44 -23
  27. aider/tools/command.py +18 -0
  28. aider/tools/command_interactive.py +18 -0
  29. aider/tools/delete_block.py +23 -0
  30. aider/tools/delete_line.py +19 -1
  31. aider/tools/delete_lines.py +20 -1
  32. aider/tools/extract_lines.py +25 -2
  33. aider/tools/git.py +142 -0
  34. aider/tools/grep.py +47 -2
  35. aider/tools/indent_lines.py +25 -0
  36. aider/tools/insert_block.py +26 -0
  37. aider/tools/list_changes.py +15 -0
  38. aider/tools/ls.py +24 -1
  39. aider/tools/make_editable.py +18 -0
  40. aider/tools/make_readonly.py +19 -0
  41. aider/tools/remove.py +22 -0
  42. aider/tools/replace_all.py +21 -0
  43. aider/tools/replace_line.py +20 -1
  44. aider/tools/replace_lines.py +21 -1
  45. aider/tools/replace_text.py +22 -0
  46. aider/tools/show_numbered_context.py +18 -0
  47. aider/tools/undo_change.py +15 -0
  48. aider/tools/update_todo_list.py +131 -0
  49. aider/tools/view.py +23 -0
  50. aider/tools/view_files_at_glob.py +32 -27
  51. aider/tools/view_files_matching.py +51 -37
  52. aider/tools/view_files_with_symbol.py +41 -54
  53. aider/tools/view_todo_list.py +57 -0
  54. aider/waiting.py +20 -203
  55. {aider_ce-0.87.13.dist-info → aider_ce-0.88.0.dist-info}/METADATA +21 -5
  56. {aider_ce-0.87.13.dist-info → aider_ce-0.88.0.dist-info}/RECORD +59 -56
  57. {aider_ce-0.87.13.dist-info → aider_ce-0.88.0.dist-info}/WHEEL +0 -0
  58. {aider_ce-0.87.13.dist-info → aider_ce-0.88.0.dist-info}/entry_points.txt +0 -0
  59. {aider_ce-0.87.13.dist-info → aider_ce-0.88.0.dist-info}/licenses/LICENSE.txt +0 -0
  60. {aider_ce-0.87.13.dist-info → aider_ce-0.88.0.dist-info}/top_level.txt +0 -0
aider/sendchat.py CHANGED
@@ -6,13 +6,52 @@ def sanity_check_messages(messages):
6
6
  """Check if messages alternate between user and assistant roles.
7
7
  System messages can be interspersed anywhere.
8
8
  Also verifies the last non-system message is from the user.
9
+ Validates tool message sequences.
9
10
  Returns True if valid, False otherwise."""
10
11
  last_role = None
11
12
  last_non_system_role = None
13
+ i = 0
14
+ n = len(messages)
12
15
 
13
- for msg in messages:
16
+ while i < n:
17
+ msg = messages[i]
14
18
  role = msg.get("role")
19
+
20
+ # Handle tool sequences atomically
21
+ if role == "assistant" and "tool_calls" in msg and msg["tool_calls"]:
22
+ # Validate tool sequence
23
+ expected_ids = {call["id"] for call in msg["tool_calls"]}
24
+ i += 1
25
+
26
+ # Check for tool responses
27
+ while i < n and expected_ids:
28
+ next_msg = messages[i]
29
+ if next_msg.get("role") == "tool" and next_msg.get("tool_call_id") in expected_ids:
30
+ expected_ids.discard(next_msg.get("tool_call_id"))
31
+ i += 1
32
+ else:
33
+ break
34
+
35
+ # If we still have expected IDs, the tool sequence is incomplete
36
+ if expected_ids:
37
+ turns = format_messages(messages)
38
+ raise ValueError(
39
+ "Incomplete tool sequence - missing responses for tool calls:\n\n" + turns
40
+ )
41
+
42
+ # Continue to next message after tool sequence
43
+ continue
44
+
45
+ elif role == "tool":
46
+ # Orphaned tool message without preceding assistant tool_calls
47
+ turns = format_messages(messages)
48
+ raise ValueError(
49
+ "Orphaned tool message without preceding assistant tool_calls:\n\n" + turns
50
+ )
51
+
52
+ # Handle normal role alternation
15
53
  if role == "system":
54
+ i += 1
16
55
  continue
17
56
 
18
57
  if last_role and role == last_role:
@@ -21,16 +60,84 @@ def sanity_check_messages(messages):
21
60
 
22
61
  last_role = role
23
62
  last_non_system_role = role
63
+ i += 1
24
64
 
25
65
  # Ensure last non-system message is from user
26
66
  return last_non_system_role == "user"
27
67
 
28
68
 
69
+ def clean_orphaned_tool_messages(messages):
70
+ """Remove orphaned tool messages and incomplete tool sequences.
71
+
72
+ This function removes:
73
+ - Tool messages without a preceding assistant message containing tool_calls
74
+ - Assistant messages with tool_calls that don't have complete tool responses
75
+
76
+ Args:
77
+ messages: List of message dictionaries
78
+
79
+ Returns:
80
+ Cleaned list of messages with orphaned tool sequences removed
81
+ """
82
+ if not messages:
83
+ return messages
84
+
85
+ cleaned = []
86
+ i = 0
87
+ n = len(messages)
88
+
89
+ while i < n:
90
+ msg = messages[i]
91
+ role = msg.get("role")
92
+
93
+ # If it's an assistant message with tool_calls, check if we have complete responses
94
+ if role == "assistant" and "tool_calls" in msg and msg["tool_calls"]:
95
+ # Start of potential tool sequence
96
+ tool_sequence = [msg]
97
+ expected_ids = {call["id"] for call in msg["tool_calls"]}
98
+ j = i + 1
99
+
100
+ # Collect tool responses
101
+ while j < n and expected_ids:
102
+ next_msg = messages[j]
103
+ if next_msg.get("role") == "tool" and next_msg.get("tool_call_id") in expected_ids:
104
+ tool_sequence.append(next_msg)
105
+ expected_ids.discard(next_msg.get("tool_call_id"))
106
+ j += 1
107
+ else:
108
+ break
109
+
110
+ # If we have all tool responses, keep the sequence
111
+ if not expected_ids:
112
+ cleaned.extend(tool_sequence)
113
+ i = j
114
+ else:
115
+ # Incomplete sequence - skip the entire tool sequence
116
+ i = j
117
+ # Don't add anything to cleaned
118
+ continue
119
+
120
+ elif role == "tool":
121
+ # Orphaned tool message without preceding assistant tool_calls - skip it
122
+ i += 1
123
+ continue
124
+ else:
125
+ # Regular message - add it
126
+ cleaned.append(msg)
127
+ i += 1
128
+
129
+ return cleaned
130
+
131
+
29
132
  def ensure_alternating_roles(messages):
30
133
  """Ensure messages alternate between 'assistant' and 'user' roles.
31
134
 
32
135
  Inserts empty messages of the opposite role when consecutive messages
33
- of the same role are found.
136
+ of the same 'user' or 'assistant' role are found. Messages with other
137
+ roles (e.g. 'system', 'tool') are ignored by the alternation logic.
138
+
139
+ Also handles tool call sequences properly - when an assistant message
140
+ contains tool_calls, processes the complete tool sequence atomically.
34
141
 
35
142
  Args:
36
143
  messages: List of message dictionaries with 'role' and 'content' keys.
@@ -41,21 +148,84 @@ def ensure_alternating_roles(messages):
41
148
  if not messages:
42
149
  return messages
43
150
 
44
- fixed_messages = []
151
+ # First clean orphaned tool messages
152
+ messages = clean_orphaned_tool_messages(messages)
153
+
154
+ result = []
155
+ i = 0
156
+ n = len(messages)
45
157
  prev_role = None
46
158
 
47
- for msg in messages:
48
- current_role = msg.get("role") # Get 'role', None if missing
159
+ while i < n:
160
+ msg = messages[i]
161
+ role = msg.get("role")
49
162
 
50
- # If current role same as previous, insert empty message
51
- # of the opposite role
52
- if current_role == prev_role:
53
- if current_role == "user":
54
- fixed_messages.append({"role": "assistant", "content": ""})
55
- else:
56
- fixed_messages.append({"role": "user", "content": ""})
163
+ # Handle tool call sequences atomically
164
+ if role == "assistant" and "tool_calls" in msg and msg["tool_calls"]:
165
+ # Start of tool sequence - collect all related messages
166
+ tool_sequence = [msg]
167
+ expected_ids = {call["id"] for call in msg["tool_calls"]}
168
+ i += 1
169
+
170
+ # Collect tool responses
171
+ while i < n and expected_ids:
172
+ next_msg = messages[i]
173
+ if next_msg.get("role") == "tool" and next_msg.get("tool_call_id") in expected_ids:
174
+ tool_sequence.append(next_msg)
175
+ expected_ids.discard(next_msg.get("tool_call_id"))
176
+ i += 1
177
+ else:
178
+ break
179
+
180
+ # Add missing tool responses as empty
181
+ for tool_id in expected_ids:
182
+ tool_sequence.append({"role": "tool", "tool_call_id": tool_id, "content": ""})
183
+
184
+ # Add the complete tool sequence to result
185
+ for tool_msg in tool_sequence:
186
+ result.append(tool_msg)
187
+
188
+ # Update prev_role to assistant after processing tool sequence
189
+ prev_role = "assistant"
190
+ continue
191
+
192
+ # Handle normal message alternation
193
+ if role in ("user", "assistant"):
194
+ if role == prev_role:
195
+ # Insert empty message of opposite role
196
+ opposite_role = "user" if role == "assistant" else "assistant"
197
+ result.append({"role": opposite_role, "content": ""})
198
+ prev_role = opposite_role
199
+
200
+ result.append(msg)
201
+ prev_role = role
202
+ else:
203
+ # For non-user/assistant roles, just add them directly
204
+ result.append(msg)
205
+
206
+ i += 1
207
+
208
+ # Consolidate consecutive empty messages in a single pass
209
+ consolidated = []
210
+ for msg in result:
211
+ if not consolidated:
212
+ consolidated.append(msg)
213
+ continue
214
+
215
+ last_msg = consolidated[-1]
216
+ current_role = msg.get("role")
217
+ last_role = last_msg.get("role")
218
+
219
+ # Skip consecutive empty messages with the same role
220
+ if (
221
+ current_role in ("user", "assistant")
222
+ and last_role in ("user", "assistant")
223
+ and current_role == last_role
224
+ and msg.get("content") == ""
225
+ and last_msg.get("content") == ""
226
+ ):
227
+ continue
57
228
 
58
- fixed_messages.append(msg)
59
- prev_role = current_role
229
+ consolidated.append(msg)
60
230
 
61
- return fixed_messages
231
+ return consolidated
aider/tools/__init__.py CHANGED
@@ -1,26 +1,47 @@
1
1
  # flake8: noqa: F401
2
2
  # Import tool functions into the aider.tools namespace
3
3
 
4
- from .command import _execute_command
5
- from .command_interactive import _execute_command_interactive
6
- from .delete_block import _execute_delete_block
7
- from .delete_line import _execute_delete_line
8
- from .delete_lines import _execute_delete_lines
9
- from .extract_lines import _execute_extract_lines
10
- from .indent_lines import _execute_indent_lines
11
- from .insert_block import _execute_insert_block
12
- from .list_changes import _execute_list_changes
13
- from .ls import execute_ls
14
- from .make_editable import _execute_make_editable
15
- from .make_readonly import _execute_make_readonly
16
- from .remove import _execute_remove
17
- from .replace_all import _execute_replace_all
18
- from .replace_line import _execute_replace_line
19
- from .replace_lines import _execute_replace_lines
20
- from .replace_text import _execute_replace_text
21
- from .show_numbered_context import execute_show_numbered_context
22
- from .undo_change import _execute_undo_change
23
- from .view import execute_view
24
- from .view_files_at_glob import execute_view_files_at_glob
25
- from .view_files_matching import execute_view_files_matching
26
- from .view_files_with_symbol import _execute_view_files_with_symbol
4
+ from .command import _execute_command, command_schema
5
+ from .command_interactive import (
6
+ _execute_command_interactive,
7
+ command_interactive_schema,
8
+ )
9
+ from .delete_block import _execute_delete_block, delete_block_schema
10
+ from .delete_line import _execute_delete_line, delete_line_schema
11
+ from .delete_lines import _execute_delete_lines, delete_lines_schema
12
+ from .extract_lines import _execute_extract_lines, extract_lines_schema
13
+ from .git import (
14
+ _execute_git_diff,
15
+ _execute_git_log,
16
+ _execute_git_show,
17
+ _execute_git_status,
18
+ git_diff_schema,
19
+ git_log_schema,
20
+ git_show_schema,
21
+ git_status_schema,
22
+ )
23
+ from .grep import _execute_grep, grep_schema
24
+ from .indent_lines import _execute_indent_lines, indent_lines_schema
25
+ from .insert_block import _execute_insert_block, insert_block_schema
26
+ from .list_changes import _execute_list_changes, list_changes_schema
27
+ from .ls import execute_ls, ls_schema
28
+ from .make_editable import _execute_make_editable, make_editable_schema
29
+ from .make_readonly import _execute_make_readonly, make_readonly_schema
30
+ from .remove import _execute_remove, remove_schema
31
+ from .replace_all import _execute_replace_all, replace_all_schema
32
+ from .replace_line import _execute_replace_line, replace_line_schema
33
+ from .replace_lines import _execute_replace_lines, replace_lines_schema
34
+ from .replace_text import _execute_replace_text, replace_text_schema
35
+ from .show_numbered_context import (
36
+ execute_show_numbered_context,
37
+ show_numbered_context_schema,
38
+ )
39
+ from .undo_change import _execute_undo_change, undo_change_schema
40
+ from .update_todo_list import _execute_update_todo_list, update_todo_list_schema
41
+ from .view import execute_view, view_schema
42
+ from .view_files_at_glob import execute_view_files_at_glob, view_files_at_glob_schema
43
+ from .view_files_matching import execute_view_files_matching, view_files_matching_schema
44
+ from .view_files_with_symbol import (
45
+ _execute_view_files_with_symbol,
46
+ view_files_with_symbol_schema,
47
+ )
aider/tools/command.py CHANGED
@@ -1,6 +1,24 @@
1
1
  # Import necessary functions
2
2
  from aider.run_cmd import run_cmd_subprocess
3
3
 
4
+ command_schema = {
5
+ "type": "function",
6
+ "function": {
7
+ "name": "Command",
8
+ "description": "Execute a shell command.",
9
+ "parameters": {
10
+ "type": "object",
11
+ "properties": {
12
+ "command_string": {
13
+ "type": "string",
14
+ "description": "The shell command to execute.",
15
+ },
16
+ },
17
+ "required": ["command_string"],
18
+ },
19
+ },
20
+ }
21
+
4
22
 
5
23
  def _execute_command(coder, command_string):
6
24
  """
@@ -1,6 +1,24 @@
1
1
  # Import necessary functions
2
2
  from aider.run_cmd import run_cmd
3
3
 
4
+ command_interactive_schema = {
5
+ "type": "function",
6
+ "function": {
7
+ "name": "CommandInteractive",
8
+ "description": "Execute a shell command interactively.",
9
+ "parameters": {
10
+ "type": "object",
11
+ "properties": {
12
+ "command_string": {
13
+ "type": "string",
14
+ "description": "The interactive shell command to execute.",
15
+ },
16
+ },
17
+ "required": ["command_string"],
18
+ },
19
+ },
20
+ }
21
+
4
22
 
5
23
  def _execute_command_interactive(coder, command_string):
6
24
  """
@@ -10,6 +10,28 @@ from .tool_utils import (
10
10
  validate_file_for_edit,
11
11
  )
12
12
 
13
+ delete_block_schema = {
14
+ "type": "function",
15
+ "function": {
16
+ "name": "DeleteBlock",
17
+ "description": "Delete a block of lines from a file.",
18
+ "parameters": {
19
+ "type": "object",
20
+ "properties": {
21
+ "file_path": {"type": "string"},
22
+ "start_pattern": {"type": "string"},
23
+ "end_pattern": {"type": "string"},
24
+ "line_count": {"type": "integer"},
25
+ "near_context": {"type": "string"},
26
+ "occurrence": {"type": "integer", "default": 1},
27
+ "change_id": {"type": "string"},
28
+ "dry_run": {"type": "boolean", "default": False},
29
+ },
30
+ "required": ["file_path", "start_pattern"],
31
+ },
32
+ },
33
+ }
34
+
13
35
 
14
36
  def _execute_delete_block(
15
37
  coder,
@@ -103,6 +125,7 @@ def _execute_delete_block(
103
125
  change_id,
104
126
  )
105
127
 
128
+ coder.files_edited_by_tools.add(rel_path)
106
129
  # 8. Format and return result, adding line range to success message
107
130
  success_message = (
108
131
  f"Deleted {num_deleted} lines ({start_line + 1}-{end_line + 1}) (from"
@@ -8,6 +8,24 @@ from .tool_utils import (
8
8
  handle_tool_error,
9
9
  )
10
10
 
11
+ delete_line_schema = {
12
+ "type": "function",
13
+ "function": {
14
+ "name": "DeleteLine",
15
+ "description": "Delete a single line from a file.",
16
+ "parameters": {
17
+ "type": "object",
18
+ "properties": {
19
+ "file_path": {"type": "string"},
20
+ "line_number": {"type": "integer"},
21
+ "change_id": {"type": "string"},
22
+ "dry_run": {"type": "boolean", "default": False},
23
+ },
24
+ "required": ["file_path", "line_number"],
25
+ },
26
+ },
27
+ }
28
+
11
29
 
12
30
  def _execute_delete_line(coder, file_path, line_number, change_id=None, dry_run=False):
13
31
  """
@@ -96,7 +114,7 @@ def _execute_delete_line(coder, file_path, line_number, change_id=None, dry_run=
96
114
  change_id,
97
115
  )
98
116
 
99
- coder.aider_edited_files.add(rel_path)
117
+ coder.files_edited_by_tools.add(rel_path)
100
118
 
101
119
  # Format and return result
102
120
  success_message = f"Deleted line {line_num_int} in {file_path}"
@@ -8,6 +8,25 @@ from .tool_utils import (
8
8
  handle_tool_error,
9
9
  )
10
10
 
11
+ delete_lines_schema = {
12
+ "type": "function",
13
+ "function": {
14
+ "name": "DeleteLines",
15
+ "description": "Delete a range of lines from a file.",
16
+ "parameters": {
17
+ "type": "object",
18
+ "properties": {
19
+ "file_path": {"type": "string"},
20
+ "start_line": {"type": "integer"},
21
+ "end_line": {"type": "integer"},
22
+ "change_id": {"type": "string"},
23
+ "dry_run": {"type": "boolean", "default": False},
24
+ },
25
+ "required": ["file_path", "start_line", "end_line"],
26
+ },
27
+ },
28
+ }
29
+
11
30
 
12
31
  def _execute_delete_lines(coder, file_path, start_line, end_line, change_id=None, dry_run=False):
13
32
  """
@@ -119,7 +138,7 @@ def _execute_delete_lines(coder, file_path, start_line, end_line, change_id=None
119
138
  change_id,
120
139
  )
121
140
 
122
- coder.aider_edited_files.add(rel_path)
141
+ coder.files_edited_by_tools.add(rel_path)
123
142
  num_deleted = end_idx - start_idx + 1
124
143
  # Format and return result
125
144
  success_message = (
@@ -3,6 +3,28 @@ import traceback
3
3
 
4
4
  from .tool_utils import generate_unified_diff_snippet
5
5
 
6
+ extract_lines_schema = {
7
+ "type": "function",
8
+ "function": {
9
+ "name": "ExtractLines",
10
+ "description": "Extract lines from a source file and append them to a target file.",
11
+ "parameters": {
12
+ "type": "object",
13
+ "properties": {
14
+ "source_file_path": {"type": "string"},
15
+ "target_file_path": {"type": "string"},
16
+ "start_pattern": {"type": "string"},
17
+ "end_pattern": {"type": "string"},
18
+ "line_count": {"type": "integer"},
19
+ "near_context": {"type": "string"},
20
+ "occurrence": {"type": "integer", "default": 1},
21
+ "dry_run": {"type": "boolean", "default": False},
22
+ },
23
+ "required": ["source_file_path", "target_file_path", "start_pattern"],
24
+ },
25
+ },
26
+ }
27
+
6
28
 
7
29
  def _execute_extract_lines(
8
30
  coder,
@@ -248,8 +270,9 @@ def _execute_extract_lines(
248
270
  coder.io.tool_error(f"Error tracking target change for ExtractLines: {track_e}")
249
271
 
250
272
  # --- Update Context ---
251
- coder.aider_edited_files.add(rel_source_path)
252
- coder.aider_edited_files.add(rel_target_path)
273
+ coder.files_edited_by_tools.add(rel_source_path)
274
+ coder.files_edited_by_tools.add(rel_target_path)
275
+
253
276
  if not target_exists:
254
277
  # Add the newly created file to editable context
255
278
  coder.abs_fnames.add(abs_target_path)
aider/tools/git.py ADDED
@@ -0,0 +1,142 @@
1
+ from aider.repo import ANY_GIT_ERROR
2
+
3
+ git_diff_schema = {
4
+ "type": "function",
5
+ "function": {
6
+ "name": "git_diff",
7
+ "description": (
8
+ "Show the diff between the current working directory and a git branch or commit."
9
+ ),
10
+ "parameters": {
11
+ "type": "object",
12
+ "properties": {
13
+ "branch": {
14
+ "type": "string",
15
+ "description": "The branch or commit hash to diff against. Defaults to HEAD.",
16
+ },
17
+ },
18
+ "required": [],
19
+ },
20
+ },
21
+ }
22
+
23
+
24
+ def _execute_git_diff(coder, branch=None):
25
+ """
26
+ Show the diff between the current working directory and a git branch or commit.
27
+ """
28
+ if not coder.repo:
29
+ return "Not in a git repository."
30
+
31
+ try:
32
+ if branch:
33
+ diff = coder.repo.diff_commits(False, branch, "HEAD")
34
+ else:
35
+ diff = coder.repo.diff_commits(False, "HEAD", None)
36
+
37
+ if not diff:
38
+ return "No differences found."
39
+ return diff
40
+ except ANY_GIT_ERROR as e:
41
+ coder.io.tool_error(f"Error running git diff: {e}")
42
+ return f"Error running git diff: {e}"
43
+
44
+
45
+ git_log_schema = {
46
+ "type": "function",
47
+ "function": {
48
+ "name": "git_log",
49
+ "description": "Show the git log.",
50
+ "parameters": {
51
+ "type": "object",
52
+ "properties": {
53
+ "limit": {
54
+ "type": "integer",
55
+ "description": "The maximum number of commits to show. Defaults to 10.",
56
+ },
57
+ },
58
+ "required": [],
59
+ },
60
+ },
61
+ }
62
+
63
+
64
+ def _execute_git_log(coder, limit=10):
65
+ """
66
+ Show the git log.
67
+ """
68
+ if not coder.repo:
69
+ return "Not in a git repository."
70
+
71
+ try:
72
+ commits = list(coder.repo.repo.iter_commits(max_count=limit))
73
+ log_output = []
74
+ for commit in commits:
75
+ short_hash = commit.hexsha[:8]
76
+ message = commit.message.strip().split("\n")[0]
77
+ log_output.append(f"{short_hash} {message}")
78
+ return "\n".join(log_output)
79
+ except ANY_GIT_ERROR as e:
80
+ coder.io.tool_error(f"Error running git log: {e}")
81
+ return f"Error running git log: {e}"
82
+
83
+
84
+ git_show_schema = {
85
+ "type": "function",
86
+ "function": {
87
+ "name": "git_show",
88
+ "description": "Show various types of objects (blobs, trees, tags, and commits).",
89
+ "parameters": {
90
+ "type": "object",
91
+ "properties": {
92
+ "object": {
93
+ "type": "string",
94
+ "description": "The object to show. Defaults to HEAD.",
95
+ },
96
+ },
97
+ "required": [],
98
+ },
99
+ },
100
+ }
101
+
102
+
103
+ def _execute_git_show(coder, object="HEAD"):
104
+ """
105
+ Show various types of objects (blobs, trees, tags, and commits).
106
+ """
107
+ if not coder.repo:
108
+ return "Not in a git repository."
109
+
110
+ try:
111
+ return coder.repo.repo.git.show(object)
112
+ except ANY_GIT_ERROR as e:
113
+ coder.io.tool_error(f"Error running git show: {e}")
114
+ return f"Error running git show: {e}"
115
+
116
+
117
+ git_status_schema = {
118
+ "type": "function",
119
+ "function": {
120
+ "name": "git_status",
121
+ "description": "Show the working tree status.",
122
+ "parameters": {
123
+ "type": "object",
124
+ "properties": {},
125
+ "required": [],
126
+ },
127
+ },
128
+ }
129
+
130
+
131
+ def _execute_git_status(coder):
132
+ """
133
+ Show the working tree status.
134
+ """
135
+ if not coder.repo:
136
+ return "Not in a git repository."
137
+
138
+ try:
139
+ return coder.repo.repo.git.status()
140
+ except ANY_GIT_ERROR as e:
141
+ coder.io.tool_error(f"Error running git status: {e}")
142
+ return f"Error running git status: {e}"