1
1
"""Langchain tools for workspace operations."""
2
2
3
3
from collections .abc import Callable
4
- from typing import ClassVar , Literal
4
+ from typing import Annotated , ClassVar , Literal , Optional
5
5
6
+ from langchain_core .messages import ToolMessage
7
+ from langchain_core .tools import InjectedToolCallId
6
8
from langchain_core .tools .base import BaseTool
7
9
from pydantic import BaseModel , Field
8
10
@@ -52,10 +54,11 @@ class ViewFileInput(BaseModel):
52
54
"""Input for viewing a file."""
53
55
54
56
filepath : str = Field (..., description = "Path to the file relative to workspace root" )
55
- start_line : int | None = Field (None , description = "Starting line number to view (1-indexed, inclusive)" )
56
- end_line : int | None = Field (None , description = "Ending line number to view (1-indexed, inclusive)" )
57
- max_lines : int | None = Field (None , description = "Maximum number of lines to view at once, defaults to 250" )
58
- line_numbers : bool | None = Field (True , description = "If True, add line numbers to the content (1-indexed)" )
57
+ start_line : Optional [int ] = Field (None , description = "Starting line number to view (1-indexed, inclusive)" )
58
+ end_line : Optional [int ] = Field (None , description = "Ending line number to view (1-indexed, inclusive)" )
59
+ max_lines : Optional [int ] = Field (None , description = "Maximum number of lines to view at once, defaults to 250" )
60
+ line_numbers : Optional [bool ] = Field (True , description = "If True, add line numbers to the content (1-indexed)" )
61
+ tool_call_id : Annotated [str , InjectedToolCallId ]
59
62
60
63
61
64
class ViewFileTool (BaseTool ):
@@ -73,12 +76,13 @@ def __init__(self, codebase: Codebase) -> None:
73
76
74
77
def _run (
75
78
self ,
79
+ tool_call_id : str ,
76
80
filepath : str ,
77
- start_line : int | None = None ,
78
- end_line : int | None = None ,
79
- max_lines : int | None = None ,
80
- line_numbers : bool | None = True ,
81
- ) -> str :
81
+ start_line : Optional [ int ] = None ,
82
+ end_line : Optional [ int ] = None ,
83
+ max_lines : Optional [ int ] = None ,
84
+ line_numbers : Optional [ bool ] = True ,
85
+ ) -> ToolMessage :
82
86
result = view_file (
83
87
self .codebase ,
84
88
filepath ,
@@ -88,14 +92,15 @@ def _run(
88
92
max_lines = max_lines if max_lines is not None else 250 ,
89
93
)
90
94
91
- return result .render ()
95
+ return result .render (tool_call_id )
92
96
93
97
94
98
class ListDirectoryInput (BaseModel ):
95
99
"""Input for listing directory contents."""
96
100
97
101
dirpath : str = Field (default = "./" , description = "Path to directory relative to workspace root" )
98
102
depth : int = Field (default = 1 , description = "How deep to traverse. Use -1 for unlimited depth." )
103
+ tool_call_id : Annotated [str , InjectedToolCallId ]
99
104
100
105
101
106
class ListDirectoryTool (BaseTool ):
@@ -109,9 +114,9 @@ class ListDirectoryTool(BaseTool):
109
114
def __init__ (self , codebase : Codebase ) -> None :
110
115
super ().__init__ (codebase = codebase )
111
116
112
- def _run (self , dirpath : str = "./" , depth : int = 1 ) -> str :
117
+ def _run (self , tool_call_id : str , dirpath : str = "./" , depth : int = 1 ) -> ToolMessage :
113
118
result = list_directory (self .codebase , dirpath , depth )
114
- return result .render ()
119
+ return result .render (tool_call_id )
115
120
116
121
117
122
class SearchInput (BaseModel ):
@@ -126,6 +131,7 @@ class SearchInput(BaseModel):
126
131
page : int = Field (default = 1 , description = "Page number to return (1-based, default: 1)" )
127
132
files_per_page : int = Field (default = 10 , description = "Number of files to return per page (default: 10)" )
128
133
use_regex : bool = Field (default = False , description = "Whether to treat query as a regex pattern (default: False)" )
134
+ tool_call_id : Annotated [str , InjectedToolCallId ]
129
135
130
136
131
137
class SearchTool (BaseTool ):
@@ -139,16 +145,17 @@ class SearchTool(BaseTool):
139
145
def __init__ (self , codebase : Codebase ) -> None :
140
146
super ().__init__ (codebase = codebase )
141
147
142
- def _run (self , query : str , file_extensions : list [str ] | None = None , page : int = 1 , files_per_page : int = 10 , use_regex : bool = False ) -> str :
148
+ def _run (self , tool_call_id : str , query : str , file_extensions : Optional [ list [str ]] = None , page : int = 1 , files_per_page : int = 10 , use_regex : bool = False ) -> ToolMessage :
143
149
result = search (self .codebase , query , file_extensions = file_extensions , page = page , files_per_page = files_per_page , use_regex = use_regex )
144
- return result .render ()
150
+ return result .render (tool_call_id )
145
151
146
152
147
153
class EditFileInput (BaseModel ):
148
154
"""Input for editing a file."""
149
155
150
156
filepath : str = Field (..., description = "Path to the file to edit" )
151
157
content : str = Field (..., description = "New content for the file" )
158
+ tool_call_id : Annotated [str , InjectedToolCallId ]
152
159
153
160
154
161
class EditFileTool (BaseTool ):
@@ -181,9 +188,9 @@ class EditFileTool(BaseTool):
181
188
def __init__ (self , codebase : Codebase ) -> None :
182
189
super ().__init__ (codebase = codebase )
183
190
184
- def _run (self , filepath : str , content : str ) -> str :
191
+ def _run (self , filepath : str , content : str , tool_call_id : str ) -> str :
185
192
result = edit_file (self .codebase , filepath , content )
186
- return result .render ()
193
+ return result .render (tool_call_id )
187
194
188
195
189
196
class CreateFileInput (BaseModel ):
@@ -340,6 +347,7 @@ class SemanticEditInput(BaseModel):
340
347
edit_content : str = Field (..., description = FILE_EDIT_PROMPT )
341
348
start : int = Field (default = 1 , description = "Starting line number (1-indexed, inclusive). Default is 1." )
342
349
end : int = Field (default = - 1 , description = "Ending line number (1-indexed, inclusive). Default is -1 (end of file)." )
350
+ tool_call_id : Annotated [str , InjectedToolCallId ]
343
351
344
352
345
353
class SemanticEditTool (BaseTool ):
@@ -353,10 +361,10 @@ class SemanticEditTool(BaseTool):
353
361
def __init__ (self , codebase : Codebase ) -> None :
354
362
super ().__init__ (codebase = codebase )
355
363
356
- def _run (self , filepath : str , edit_content : str , start : int = 1 , end : int = - 1 ) -> str :
364
+ def _run (self , filepath : str , tool_call_id : str , edit_content : str , start : int = 1 , end : int = - 1 ) -> ToolMessage :
357
365
# Create the the draft editor mini llm
358
366
result = semantic_edit (self .codebase , filepath , edit_content , start = start , end = end )
359
- return result .render ()
367
+ return result .render (tool_call_id )
360
368
361
369
362
370
class RenameFileInput (BaseModel ):
0 commit comments