package tools import ( "context" "fmt" "strings" "github.com/enmanuel/agents/pkg/knowledge" ) // KnowledgeStore is the subset of knowledge.Store needed by knowledge tools. type KnowledgeStore interface { Search(ctx context.Context, query string, limit int) ([]knowledge.SearchResult, error) Get(ctx context.Context, slug string) (*knowledge.Document, error) Put(ctx context.Context, doc knowledge.Document) error List(ctx context.Context) ([]knowledge.Document, error) } // NewKnowledgeSearch creates a tool that searches the knowledge base. func NewKnowledgeSearch(store KnowledgeStore) Tool { return Tool{ Def: Def{ Name: "knowledge_search", Description: "Search your knowledge base for relevant documents. Returns matching snippets ranked by relevance.", Parameters: []Param{ {Name: "query", Type: "string", Description: "Search terms or phrase", Required: true}, {Name: "limit", Type: "integer", Description: "Max results (default 5)", Required: false}, }, }, Exec: func(ctx context.Context, args map[string]any) Result { query := getString(args, "query") if query == "" { return Result{Err: fmt.Errorf("knowledge_search: query is required")} } limit := getInt(args, "limit") if limit <= 0 { limit = 5 } results, err := store.Search(ctx, query, limit) if err != nil { return Result{Err: fmt.Errorf("knowledge_search: %w", err)} } if len(results) == 0 { return Result{Output: "no documents found matching your query"} } var sb strings.Builder for i, r := range results { fmt.Fprintf(&sb, "%d. **%s** (`%s`)\n %s\n", i+1, r.Title, r.Slug, r.Snippet) } return Result{Output: sb.String()} }, } } // NewKnowledgeRead creates a tool that reads a knowledge document. func NewKnowledgeRead(store KnowledgeStore) Tool { return Tool{ Def: Def{ Name: "knowledge_read", Description: "Read the full content of a knowledge document by its slug.", Parameters: []Param{ {Name: "slug", Type: "string", Description: "Document slug (e.g. \"go-patterns\")", Required: true}, }, }, Exec: func(ctx context.Context, args map[string]any) Result { slug := getString(args, "slug") if slug == "" { return Result{Err: fmt.Errorf("knowledge_read: slug is required")} } doc, err := store.Get(ctx, slug) if err != nil { return Result{Err: fmt.Errorf("knowledge_read: %w", err)} } return Result{Output: doc.Content} }, } } // NewKnowledgeWrite creates a tool that writes a knowledge document. func NewKnowledgeWrite(store KnowledgeStore) Tool { return Tool{ Def: Def{ Name: "knowledge_write", Description: "Create or update a knowledge document. Use this to save new knowledge or improve existing documents.", Parameters: []Param{ {Name: "slug", Type: "string", Description: "Document slug (lowercase, hyphens, e.g. \"matrix-tips\")", Required: true}, {Name: "content", Type: "string", Description: "Full markdown content of the document", Required: true}, }, }, Exec: func(ctx context.Context, args map[string]any) Result { slug := getString(args, "slug") content := getString(args, "content") if slug == "" || content == "" { return Result{Err: fmt.Errorf("knowledge_write: slug and content are required")} } err := store.Put(ctx, knowledge.Document{ Slug: slug, Content: content, }) if err != nil { return Result{Err: fmt.Errorf("knowledge_write: %w", err)} } return Result{Output: fmt.Sprintf("document saved: %s (%d bytes)", slug, len(content))} }, } } // NewKnowledgeList creates a tool that lists all knowledge documents. func NewKnowledgeList(store KnowledgeStore) Tool { return Tool{ Def: Def{ Name: "knowledge_list", Description: "List all documents in your knowledge base with their titles.", Parameters: []Param{}, }, Exec: func(ctx context.Context, args map[string]any) Result { docs, err := store.List(ctx) if err != nil { return Result{Err: fmt.Errorf("knowledge_list: %w", err)} } if len(docs) == 0 { return Result{Output: "knowledge base is empty"} } var sb strings.Builder for _, d := range docs { fmt.Fprintf(&sb, "- `%s`: %s (updated %s)\n", d.Slug, d.Title, d.UpdatedAt.Format("2006-01-02")) } return Result{Output: sb.String()} }, } } // getInt extracts an integer argument by name, returning 0 if missing or wrong type. func getInt(args map[string]any, key string) int { v, ok := args[key] if !ok { return 0 } switch n := v.(type) { case float64: return int(n) case int: return n default: return 0 } }