Files
hyperguild/internal/brain/client.go

77 lines
2.1 KiB
Go

// internal/brain/client.go
// Package brain provides a lightweight client for querying the ingestion server.
// Skill handlers call Query before spawning workers to inject relevant knowledge
// from the brain into the task prompt. Errors are suppressed — the brain is
// optional context; its absence must never block a skill invocation.
package brain
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
"strings"
)
type queryResult struct {
Path string `json:"path"`
Title string `json:"title"`
Excerpt string `json:"excerpt"`
Score int `json:"score"`
}
// Query calls the ingestion server and returns relevant knowledge as a
// formatted string ready to prepend to a worker task prompt.
// Returns empty string (no error) when baseURL or query is empty,
// when the brain is unreachable, or when no results are found.
func Query(ctx context.Context, baseURL, query string, limit int) (string, error) {
if baseURL == "" || strings.TrimSpace(query) == "" {
return "", nil
}
if limit <= 0 {
limit = 3
}
body, _ := json.Marshal(map[string]any{"query": query, "limit": limit})
req, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL+"/query", bytes.NewReader(body))
if err != nil {
slog.Warn("brain: build request failed", "err", err)
return "", nil
}
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
slog.Warn("brain: ingestion server unreachable", "err", err)
return "", nil
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
slog.Warn("brain: ingestion server returned non-OK", "status", resp.StatusCode)
return "", nil
}
out, _ := io.ReadAll(resp.Body)
var result struct {
Results []queryResult `json:"results"`
}
if err := json.Unmarshal(out, &result); err != nil || len(result.Results) == 0 {
return "", nil
}
var b strings.Builder
b.WriteString("## Relevant knowledge\n\n")
for _, r := range result.Results {
title := r.Title
if title == "" {
title = r.Path
}
fmt.Fprintf(&b, "### %s\n%s\n\n", title, r.Excerpt)
}
return b.String(), nil
}