This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/server/tools/conversation_search.go
2026-03-17 16:28:47 +00:00

103 lines
3.0 KiB
Go

package tools
import (
"context"
"encoding/json"
"fmt"
"log"
"time"
"git.gobha.me/xcaliber/chat-switchboard/store"
)
// ── Late Registration ────────────────────────
func RegisterConversationSearch(stores store.Stores) {
Register(&conversationSearchTool{stores: stores})
}
// ═══════════════════════════════════════════
// conversation_search
// ═══════════════════════════════════════════
type conversationSearchTool struct {
BaseTool
stores store.Stores
}
func (t *conversationSearchTool) Definition() ToolDef {
return ToolDef{
Name: "conversation_search",
DisplayName: "Search Chat",
Category: "context",
Description: "Search earlier messages in this conversation by keyword. " +
"Useful when the conversation is long or has been summarized and " +
"you need to find specific details from earlier discussion. " +
"Returns matching message excerpts with timestamps.",
Parameters: JSONSchema(map[string]interface{}{
"query": Prop("string", "Search keywords — use terms likely to appear in the messages"),
"max_results": map[string]interface{}{
"type": "integer",
"description": "Maximum results to return (1-20, default 5)",
},
"role_filter": PropEnum(
"Filter by message role (default: all)",
"all", "user", "assistant",
),
}, []string{"query"}),
}
}
func (t *conversationSearchTool) Execute(ctx context.Context, execCtx ExecutionContext, argsJSON string) (string, error) {
var args struct {
Query string `json:"query"`
MaxResults int `json:"max_results"`
RoleFilter string `json:"role_filter"`
}
if err := json.Unmarshal([]byte(argsJSON), &args); err != nil {
return "", fmt.Errorf("invalid arguments: %w", err)
}
if args.Query == "" {
return "", fmt.Errorf("query is required")
}
if args.MaxResults <= 0 || args.MaxResults > 20 {
args.MaxResults = 5
}
storeResults, err := t.stores.Messages.SearchInChannel(ctx, execCtx.ChannelID, args.Query, args.RoleFilter, args.MaxResults)
if err != nil {
return "", fmt.Errorf("search failed: %w", err)
}
type searchResult struct {
MessageID string `json:"message_id"`
Role string `json:"role"`
Excerpt string `json:"excerpt"`
Rank float64 `json:"rank"`
Timestamp string `json:"timestamp"`
}
results := make([]searchResult, 0, len(storeResults))
for _, sr := range storeResults {
results = append(results, searchResult{
MessageID: sr.MessageID,
Role: sr.Role,
Excerpt: sr.Excerpt,
Rank: sr.Rank,
Timestamp: sr.Timestamp.Format(time.RFC3339),
})
}
log.Printf("🔍 conversation_search: %q → %d results in channel %s",
args.Query, len(results), execCtx.ChannelID)
out, _ := json.Marshal(map[string]interface{}{
"results": results,
"query": args.Query,
"total": len(results),
"channel_id": execCtx.ChannelID,
})
return string(out), nil
}