Skip to content

Commit

Permalink
feat: add change directory tool to CLI (#12)
Browse files Browse the repository at this point in the history
Added a new tool 'change_directory' that allows changing the current working directory. The tool:
- Takes a single path parameter
- Returns the full path on success
- Returns an error if directory doesn't exist

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
github-actions[bot] and github-actions[bot] authored Jan 27, 2025
1 parent a3b73d4 commit b37ffa4
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 0 deletions.
21 changes: 21 additions & 0 deletions internal/agent/anthropic.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,14 @@ func NewAnthropicExecutor(baseUrl string, apiKey string, logger Logger, ignorer
Properties: a.F[any](getRelatedFilesTool.InputSchema["properties"]),
}),
},
&a.BetaToolParam{
Name: a.String(changeDirectoryTool.Name),
Description: a.String(changeDirectoryTool.Description),
InputSchema: a.F(a.BetaToolInputSchemaParam{
Type: a.F(a.BetaToolInputSchemaTypeObject),
Properties: a.F[any](changeDirectoryTool.InputSchema["properties"]),
}),
},
}),
}

Expand Down Expand Up @@ -304,6 +312,19 @@ func (s *anthropicExecutor) Execute(input string) error {
}
s.logger.Printf("getting related files: %s", strings.Join(relatedFilesToolInput.InputFiles, ", "))
result, err = executeGetRelatedFilesTool(relatedFilesToolInput.InputFiles, s.ignorer)
case changeDirectoryTool.Name:
changeDirToolInput := struct {
Path string `json:"path"`
}{}
jsonInput, marshalErr := json.Marshal(block.Input)
if marshalErr != nil {
return fmt.Errorf("failed to marshal change directory tool input: %w", marshalErr)
}
if err := json.Unmarshal(jsonInput, &changeDirToolInput); err != nil {
return fmt.Errorf("failed to unmarshal change directory tool arguments: %w", err)
}
s.logger.Printf("changing directory to: %s", changeDirToolInput.Path)
result, err = executeChangeDirectoryTool(changeDirToolInput.Path)
default:
return fmt.Errorf("unexpected tool use block type: %s", block.Name)
}
Expand Down
17 changes: 17 additions & 0 deletions internal/agent/deepseek.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@ func NewDeepSeekExecutor(baseUrl string, apiKey string, logger Logger, ignorer *
Parameters: oai.F(oai.FunctionParameters(getRelatedFilesTool.InputSchema)),
}),
},
{
Type: oai.F(oai.ChatCompletionToolTypeFunction),
Function: oai.F(oai.FunctionDefinitionParam{
Name: oai.F(changeDirectoryTool.Name),
Description: oai.F(changeDirectoryTool.Description),
Parameters: oai.F(oai.FunctionParameters(changeDirectoryTool.InputSchema)),
}),
},
}),
}

Expand Down Expand Up @@ -178,6 +186,15 @@ func (o *deepseekExecutor) Execute(input string) error {
}
o.logger.Printf("getting related files: %s", strings.Join(relatedFilesToolInput.InputFiles, ", "))
result, err = executeGetRelatedFilesTool(relatedFilesToolInput.InputFiles, o.ignorer)
case changeDirectoryTool.Name:
var changeDirToolInput struct {
Path string `json:"path"`
}
if err := json.Unmarshal([]byte(toolCall.Function.Arguments), &changeDirToolInput); err != nil {
return fmt.Errorf("failed to unmarshal change directory tool arguments: %w", err)
}
o.logger.Printf("changing directory to: %s", changeDirToolInput.Path)
result, err = executeChangeDirectoryTool(changeDirToolInput.Path)
default:
return fmt.Errorf("unexpected tool name: %s", toolCall.Function.Name)
}
Expand Down
27 changes: 27 additions & 0 deletions internal/agent/gemini.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,20 @@ func NewGeminiExecutor(baseUrl string, apiKey string, logger Logger, ignorer *gi
Required: []string{"input_files"},
},
},
{
Name: changeDirectoryTool.Name,
Description: changeDirectoryTool.Description,
Parameters: &genai.Schema{
Type: genai.TypeObject,
Properties: map[string]*genai.Schema{
"path": {
Type: genai.TypeString,
Description: "The path to change to, can be relative or absolute",
},
},
Required: []string{"path"},
},
},
},
},
}
Expand Down Expand Up @@ -256,6 +270,19 @@ func (g *geminiExecutor) Execute(input string) error {
}
g.logger.Printf("getting related files: %s", strings.Join(relatedFilesToolInput.InputFiles, ", "))
result, err = executeGetRelatedFilesTool(relatedFilesToolInput.InputFiles, g.ignorer)
case changeDirectoryTool.Name:
var changeDirToolInput struct {
Path string `json:"path"`
}
jsonInput, marshalErr := json.Marshal(v.Args)
if marshalErr != nil {
return fmt.Errorf("failed to marshal change directory tool input: %w", marshalErr)
}
if err := json.Unmarshal(jsonInput, &changeDirToolInput); err != nil {
return fmt.Errorf("failed to unmarshal change directory tool arguments: %w", err)
}
g.logger.Printf("changing directory to: %s", changeDirToolInput.Path)
result, err = executeChangeDirectoryTool(changeDirToolInput.Path)
default:
return fmt.Errorf("unexpected tool name: %s", v.Name)
}
Expand Down
17 changes: 17 additions & 0 deletions internal/agent/openai.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ func NewOpenAIExecutor(baseUrl string, apiKey string, logger Logger, ignorer *gi
Parameters: oai.F(oai.FunctionParameters(getRelatedFilesTool.InputSchema)),
}),
},
{
Type: oai.F(oai.ChatCompletionToolTypeFunction),
Function: oai.F(oai.FunctionDefinitionParam{
Name: oai.F(changeDirectoryTool.Name),
Description: oai.F(changeDirectoryTool.Description),
Parameters: oai.F(oai.FunctionParameters(changeDirectoryTool.InputSchema)),
}),
},
}),
}

Expand Down Expand Up @@ -191,6 +199,15 @@ func (o *openaiExecutor) Execute(input string) error {
}
o.logger.Printf("getting related files: %s", strings.Join(relatedFilesToolInput.InputFiles, ", "))
result, err = executeGetRelatedFilesTool(relatedFilesToolInput.InputFiles, o.ignorer)
case changeDirectoryTool.Name:
var changeDirToolInput struct {
Path string `json:"path"`
}
if err := json.Unmarshal([]byte(toolCall.Function.Arguments), &changeDirToolInput); err != nil {
return fmt.Errorf("failed to unmarshal change directory tool arguments: %w", err)
}
o.logger.Printf("changing directory to: %s", changeDirToolInput.Path)
result, err = executeChangeDirectoryTool(changeDirToolInput.Path)
default:
return fmt.Errorf("unexpected tool name: %s", toolCall.Function.Name)
}
Expand Down
50 changes: 50 additions & 0 deletions internal/agent/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,24 @@ var getRelatedFilesTool = Tool{
},
}

var changeDirectoryTool = Tool{
Name: "change_directory",
Description: `A tool to change the current working directory
* The tool accepts a single parameter "path" specifying the target directory
* Returns the full path of the new directory if successful
* Returns an error message if the directory doesn't exist`,
InputSchema: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"path": map[string]interface{}{
"type": "string",
"description": "The path to change to, can be relative or absolute",
},
},
"required": []string{"path"},
},
}

type ToolResult struct {
ToolUseID string
Content any
Expand Down Expand Up @@ -319,4 +337,36 @@ func executeGetRelatedFilesTool(inputFiles []string, ignorer *ignore.GitIgnore)
return &ToolResult{
Content: sb.String(),
}, nil
}

// executeChangeDirectoryTool validates and executes the change directory tool
func executeChangeDirectoryTool(path string) (*ToolResult, error) {
// Get absolute path
absPath, err := filepath.Abs(path)
if err != nil {
return &ToolResult{
Content: fmt.Sprintf("Error resolving absolute path: %s", err),
IsError: true,
}, nil
}

// Check if directory exists
if info, err := os.Stat(absPath); err != nil || !info.IsDir() {
return &ToolResult{
Content: fmt.Sprintf("Directory does not exist: %s", absPath),
IsError: true,
}, nil
}

// Change directory
if err := os.Chdir(absPath); err != nil {
return &ToolResult{
Content: fmt.Sprintf("Error changing directory: %s", err),
IsError: true,
}, nil
}

return &ToolResult{
Content: absPath,
}, nil
}

0 comments on commit b37ffa4

Please sign in to comment.