Skip to content

Commit

Permalink
feat: add tool command
Browse files Browse the repository at this point in the history
  • Loading branch information
b4nst committed Aug 19, 2024
1 parent 0b30320 commit 2435f22
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 27 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ build: build-go build-docs ## Build the project

# Run the Go project
.PHONY: run
run: build ## Build and run the Go project
run: build-go ## Build and run the Go project
./$(BUILD_DIR)/$(BINARY_NAME)

# Clean up the build artifacts
Expand Down
2 changes: 1 addition & 1 deletion cmd/ayo/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func (c *Chat) Run(ctx context.Context, cli *CLI) error {
log := log.FromContext(ctx).With("caller", logPackagePrefix+"Chat:Run")

log.Debug("loading tools", "dir", cli.Toolbox)
tools, err := tool.LoadTools(cli.Toolbox)
tools, err := tool.LoadAll(cli.Toolbox)
if err != nil {
return err
}
Expand Down
1 change: 1 addition & 0 deletions cmd/ayo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type CLI struct {

Chat Chat `cmd:"" help:"Send a message to the chatbot" default:"withargs"`
Version Version `cmd:"" help:"Show the version information"`
Tool Tool `cmd:"" help:"Get information about a tool"`
}

func main() {
Expand Down
64 changes: 64 additions & 0 deletions cmd/ayo/tool.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package main

import (
"fmt"
"strings"
"text/tabwriter"

"github.com/alecthomas/kong"

"github.com/banst/ayo/pkg/tool"
)

type Tool struct {
Tools []string `arg:"" optional:"" help:"Filename of the tool to get information about. If empty, all tools from the toolbox are listed." type:"existingfile"` //nolint:lll // Struct tags are long.
}

func (t *Tool) Run(context *kong.Context, cli *CLI) error {
tw := tabwriter.NewWriter(context.Stdout, 0, 0, 3, ' ', 0)

if len(t.Tools) == 0 {
var err error
t.Tools, err = tool.ListFiles(cli.Toolbox)
if err != nil {
return err
}
}

for i, tf := range t.Tools {
if i > 0 {
if _, err := fmt.Fprintln(tw); err != nil {
return err
}
}

if _, err := fmt.Fprintf(tw, "File:\t%s\n", tf); err != nil {
return err
}

if t, err := tool.Load(tf); err != nil {
if _, err := fmt.Fprintf(tw, "Error:\t%s\n", err); err != nil {
return err
}
} else {
params := []string{}
for name, p := range t.Function.Parameters.Properties {
params = append(params, fmt.Sprintf("%s %s", name, p.Type))
}
function := fmt.Sprintf("%s(%s)", t.Function.Name, strings.Join(params, ", "))

cmd := strings.Join(append([]string{t.Cmd}, t.Args...), " ")

format := "Function:\t%s\nDescription:\t%s\nCommand:\t%s\n"
if _, err := fmt.Fprintf(tw, format, function, t.Function.Description, cmd); err != nil {
return err
}
}

if err := tw.Flush(); err != nil {
return err
}
}

return nil
}
7 changes: 7 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,20 @@ toolchain go1.22.6
require (
github.com/alecthomas/kong v0.9.0
github.com/lmittmann/tint v1.0.5
github.com/muesli/termenv v0.15.2
github.com/ollama/ollama v0.3.6
github.com/stretchr/testify v1.9.0
golang.org/x/sync v0.3.0
)

require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
golang.org/x/sys v0.20.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
15 changes: 15 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ github.com/alecthomas/kong v0.9.0 h1:G5diXxc85KvoV2f0ZRVuMsi45IrBgx9zDNGNj165aPA
github.com/alecthomas/kong v0.9.0/go.mod h1:Y47y5gKfHp1hDc7CH7OeXgLIpp+Q2m1Ni0L5s3bI8Os=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
Expand All @@ -12,14 +14,27 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/lmittmann/tint v1.0.5 h1:NQclAutOfYsqs2F1Lenue6OoWCajs5wJcP3DfWVpePw=
github.com/lmittmann/tint v1.0.5/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/ollama/ollama v0.3.6 h1:nA/N0AmjP327po5cZDGLqI40nl+aeei0pD0dLa92ypE=
github.com/ollama/ollama v0.3.6/go.mod h1:YrWoNkFnPOYsnDvsf/Ztb1wxU9/IXrNsQHqcxbY2r94=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
45 changes: 27 additions & 18 deletions pkg/tool/tool.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"golang.org/x/sync/errgroup"
)

const Ext = ".json"

type Tool struct {
api.Tool
Cmd string `json:"cmd"`
Expand All @@ -26,8 +28,8 @@ func (t *Tool) Run(values map[string]any) ([]byte, error) {
return exec.Command(t.Cmd, args...).CombinedOutput() //nolint:gosec // This is a tool runner.
}

// LoadTool loads a tool from a json file.
func LoadTool(file string) (*Tool, error) {
// Load loads a tool from a json file.
func Load(file string) (*Tool, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
Expand All @@ -43,22 +45,10 @@ func LoadTool(file string) (*Tool, error) {
return &tool, nil
}

// LoadTools loads a list of tools from a directory.
func LoadTools(dir string) (map[string]*Tool, error) {
// LoadAll loads a list of tools from a directory.
func LoadAll(dir string) (map[string]*Tool, error) {
// Get the list of json files in the directory
var files []string
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && filepath.Ext(path) == ".json" {
files = append(files, path)
}
return nil
})
if err != nil {
return nil, err
}
files, err := ListFiles(dir)

// Load the tools concurrently, pool of 10 goroutines max
tools := make(map[string]*Tool, len(files))
Expand All @@ -68,7 +58,7 @@ func LoadTools(dir string) (map[string]*Tool, error) {
for _, f := range files {
file := f // capture the loop variable
eg.Go(func() error {
tool, err := LoadTool(file) //nolint:govet // Shadowing err is fine here.
tool, err := Load(file) //nolint:govet // Shadowing err is fine here.
if err != nil {
return err
}
Expand All @@ -85,3 +75,22 @@ func LoadTools(dir string) (map[string]*Tool, error) {
}
return tools, nil
}

// ListToolFiles lists the tool files in a directory.
func ListFiles(dir string) ([]string, error) {
// Get the list of json files in the directory
var files []string
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && filepath.Ext(path) == Ext {
files = append(files, path)
}
return nil
})
if err != nil {
return nil, err
}
return files, nil
}
14 changes: 7 additions & 7 deletions pkg/tool/tool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func TestLoadTool(t *testing.T) {
t.Run("missing file", func(t *testing.T) {
t.Parallel()

_, err := LoadTool("missing.json")
_, err := Load("missing.json")
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "no such file or directory")
}
Expand All @@ -30,7 +30,7 @@ func TestLoadTool(t *testing.T) {
_, err = f.WriteString("bad json")
require.NoError(t, err)

_, err = LoadTool(f.Name())
_, err = Load(f.Name())
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "invalid character")
}
Expand All @@ -55,7 +55,7 @@ func TestLoadTool(t *testing.T) {
err = json.NewEncoder(f).Encode(tool)
require.NoError(t, err)

actual, err := LoadTool(f.Name())
actual, err := Load(f.Name())
assert.NoError(t, err)
assert.Equal(t, tool, actual)
})
Expand All @@ -67,7 +67,7 @@ func TestLoadTools(t *testing.T) {
t.Run("missing directory", func(t *testing.T) {
t.Parallel()

_, err := LoadTools("missing")
_, err := LoadAll("missing")
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "no such file or directory")
}
Expand All @@ -77,7 +77,7 @@ func TestLoadTools(t *testing.T) {
t.Parallel()
dir := t.TempDir()

tools, err := LoadTools(dir)
tools, err := LoadAll(dir)
assert.NoError(t, err)
assert.Empty(t, tools)
})
Expand All @@ -91,7 +91,7 @@ func TestLoadTools(t *testing.T) {
_, err = f.WriteString("bad json")
require.NoError(t, err)

_, err = LoadTools(dir)
_, err = LoadAll(dir)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "invalid character")
}
Expand Down Expand Up @@ -132,7 +132,7 @@ func TestLoadTools(t *testing.T) {
require.NoError(t, err)
}

actual, err := LoadTools(dir)
actual, err := LoadAll(dir)
assert.NoError(t, err)
assert.Equal(t, tools, actual)
})
Expand Down

0 comments on commit 2435f22

Please sign in to comment.