feat(brain): add Query client for skill handler context injection
This commit is contained in:
76
internal/brain/client.go
Normal file
76
internal/brain/client.go
Normal file
@@ -0,0 +1,76 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user