Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions tools/registry.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package tools

import (
"sort"

"github.com/codeany-ai/open-agent-sdk-go/types"
)

Expand All @@ -26,21 +28,28 @@ func (r *Registry) Get(name string) types.Tool {
return r.tools[name]
}

// All returns all registered tools.
// All returns all registered tools, sorted by name. The order is stable
// across calls so callers (such as the agent request builder) produce a
// deterministic tools list, which keeps the cacheable request prefix from
// shifting between requests.
func (r *Registry) All() []types.Tool {
result := make([]types.Tool, 0, len(r.tools))
for _, t := range r.tools {
result = append(result, t)
}
sort.Slice(result, func(i, j int) bool {
return result[i].Name() < result[j].Name()
})
return result
}

// Names returns all registered tool names.
// Names returns all registered tool names, sorted for a stable order.
func (r *Registry) Names() []string {
names := make([]string, 0, len(r.tools))
for name := range r.tools {
names = append(names, name)
}
sort.Strings(names)
return names
}

Expand Down
83 changes: 83 additions & 0 deletions tools/registry_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package tools

import (
"sort"
"testing"
)

// registerOutOfOrder builds a registry by inserting tools in an order that
// does not match their sorted name order, so a stable result from All/Names
// cannot be an accident of insertion order.
func registerOutOfOrder() *Registry {
r := NewRegistry()
r.Register(NewWebSearchTool())
r.Register(NewBashTool())
r.Register(NewGlobTool())
r.Register(NewFileReadTool())
r.Register(NewGrepTool())
return r
}

func TestRegistryAllSorted(t *testing.T) {
r := registerOutOfOrder()

got := r.All()
names := make([]string, len(got))
for i, tool := range got {
names[i] = tool.Name()
}

want := append([]string(nil), names...)
sort.Strings(want)

for i := range want {
if names[i] != want[i] {
t.Fatalf("All() not sorted by name: got %v, want %v", names, want)
}
}
}

func TestRegistryNamesSorted(t *testing.T) {
r := registerOutOfOrder()

got := r.Names()
want := append([]string(nil), got...)
sort.Strings(want)

for i := range want {
if got[i] != want[i] {
t.Fatalf("Names() not sorted: got %v, want %v", got, want)
}
}
}

func TestRegistryOrderStableAcrossCalls(t *testing.T) {
r := registerOutOfOrder()

firstAll := r.All()
firstNames := r.Names()

for i := 0; i < 20; i++ {
all := r.All()
if len(all) != len(firstAll) {
t.Fatalf("All() length changed: got %d, want %d", len(all), len(firstAll))
}
for j := range all {
if all[j].Name() != firstAll[j].Name() {
t.Fatalf("All() order changed on call %d at index %d: got %q, want %q",
i, j, all[j].Name(), firstAll[j].Name())
}
}

names := r.Names()
if len(names) != len(firstNames) {
t.Fatalf("Names() length changed: got %d, want %d", len(names), len(firstNames))
}
for j := range names {
if names[j] != firstNames[j] {
t.Fatalf("Names() order changed on call %d at index %d: got %q, want %q",
i, j, names[j], firstNames[j])
}
}
}
}