refactor(agentic): use go-forge library instead of raw HTTP
Replace raw http.Client calls with go-forge typed API: - prep.go: getIssueBody via forge.Issues.Get, pullWikiContent via forge.Wiki.ListPages/GetPage - pr.go: forgeCreatePR via forge.Pulls.Create, commentOnIssue via forge.Issues.CreateComment, listRepoPRs via forge.Pulls.ListAll - scan.go: listOrgRepos via forge.Repos.ListOrgRepos Eliminates manual JSON marshalling, auth headers, pagination loops, and anonymous struct declarations. One Forge client, one auth, type-safe responses. Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
bfe70871a2
commit
7f84ac8348
3 changed files with 64 additions and 185 deletions
|
|
@ -3,13 +3,12 @@
|
|||
package agentic
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
"dappco.re/go/core/forge"
|
||||
forge_types "dappco.re/go/core/forge/types"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
|
|
@ -164,53 +163,20 @@ func (s *PrepSubsystem) buildPRBody(st *WorkspaceStatus) string {
|
|||
}
|
||||
|
||||
func (s *PrepSubsystem) forgeCreatePR(ctx context.Context, org, repo, head, base, title, body string) (string, int, error) {
|
||||
payload, _ := json.Marshal(map[string]any{
|
||||
"title": title,
|
||||
"body": body,
|
||||
"head": head,
|
||||
"base": base,
|
||||
pr, err := s.forge.Pulls.Create(ctx, forge.Params{"owner": org, "repo": repo}, &forge_types.CreatePullRequestOption{
|
||||
Title: title,
|
||||
Body: body,
|
||||
Head: head,
|
||||
Base: base,
|
||||
})
|
||||
|
||||
url := core.Sprintf("%s/api/v1/repos/%s/%s/pulls", s.forgeURL, org, repo)
|
||||
req, _ := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(payload))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "token "+s.forgeToken)
|
||||
|
||||
resp, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
return "", 0, core.E("forgeCreatePR", "request failed", err)
|
||||
return "", 0, core.E("forgeCreatePR", "create PR failed", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 201 {
|
||||
var errBody map[string]any
|
||||
json.NewDecoder(resp.Body).Decode(&errBody)
|
||||
msg, _ := errBody["message"].(string)
|
||||
return "", 0, core.E("forgeCreatePR", core.Sprintf("HTTP %d: %s", resp.StatusCode, msg), nil)
|
||||
}
|
||||
|
||||
var pr struct {
|
||||
Number int `json:"number"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
}
|
||||
json.NewDecoder(resp.Body).Decode(&pr)
|
||||
|
||||
return pr.HTMLURL, pr.Number, nil
|
||||
return pr.HTMLURL, int(pr.Index), nil
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) commentOnIssue(ctx context.Context, org, repo string, issue int, comment string) {
|
||||
payload, _ := json.Marshal(map[string]string{"body": comment})
|
||||
|
||||
url := core.Sprintf("%s/api/v1/repos/%s/%s/issues/%d/comments", s.forgeURL, org, repo, issue)
|
||||
req, _ := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(payload))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "token "+s.forgeToken)
|
||||
|
||||
resp, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resp.Body.Close()
|
||||
s.forge.Issues.CreateComment(ctx, org, repo, int64(issue), comment)
|
||||
}
|
||||
|
||||
// --- agentic_list_prs ---
|
||||
|
|
@ -309,54 +275,30 @@ func (s *PrepSubsystem) listPRs(ctx context.Context, _ *mcp.CallToolRequest, inp
|
|||
}
|
||||
|
||||
func (s *PrepSubsystem) listRepoPRs(ctx context.Context, org, repo, state string) ([]PRInfo, error) {
|
||||
url := core.Sprintf("%s/api/v1/repos/%s/%s/pulls?state=%s&limit=10",
|
||||
s.forgeURL, org, repo, state)
|
||||
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
req.Header.Set("Authorization", "token "+s.forgeToken)
|
||||
|
||||
resp, err := s.client.Do(req)
|
||||
prs, err := s.forge.Pulls.ListAll(ctx, forge.Params{"owner": org, "repo": repo})
|
||||
if err != nil {
|
||||
return nil, core.E("listRepoPRs", "failed to list PRs for "+repo, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, core.E("listRepoPRs", core.Sprintf("HTTP %d listing PRs for %s", resp.StatusCode, repo), nil)
|
||||
}
|
||||
|
||||
var prs []struct {
|
||||
Number int `json:"number"`
|
||||
Title string `json:"title"`
|
||||
State string `json:"state"`
|
||||
Mergeable bool `json:"mergeable"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
Head struct {
|
||||
Ref string `json:"ref"`
|
||||
} `json:"head"`
|
||||
Base struct {
|
||||
Ref string `json:"ref"`
|
||||
} `json:"base"`
|
||||
User struct {
|
||||
Login string `json:"login"`
|
||||
} `json:"user"`
|
||||
Labels []struct {
|
||||
Name string `json:"name"`
|
||||
} `json:"labels"`
|
||||
}
|
||||
json.NewDecoder(resp.Body).Decode(&prs)
|
||||
|
||||
var result []PRInfo
|
||||
for _, pr := range prs {
|
||||
if state != "" && state != "all" && string(pr.State) != state {
|
||||
continue
|
||||
}
|
||||
var labels []string
|
||||
for _, l := range pr.Labels {
|
||||
labels = append(labels, l.Name)
|
||||
}
|
||||
author := ""
|
||||
if pr.User != nil {
|
||||
author = pr.User.UserName
|
||||
}
|
||||
result = append(result, PRInfo{
|
||||
Repo: repo,
|
||||
Number: pr.Number,
|
||||
Number: int(pr.Index),
|
||||
Title: pr.Title,
|
||||
State: pr.State,
|
||||
Author: pr.User.Login,
|
||||
State: string(pr.State),
|
||||
Author: author,
|
||||
Branch: pr.Head.Ref,
|
||||
Base: pr.Base.Ref,
|
||||
Labels: labels,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import (
|
|||
|
||||
"dappco.re/go/agent/pkg/lib"
|
||||
core "dappco.re/go/core"
|
||||
"dappco.re/go/core/forge"
|
||||
coremcp "forge.lthn.ai/core/mcp/pkg/mcp"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
|
@ -34,6 +35,7 @@ type CompletionNotifier interface {
|
|||
// sub := agentic.NewPrep()
|
||||
// sub.RegisterTools(server)
|
||||
type PrepSubsystem struct {
|
||||
forge *forge.Forge
|
||||
forgeURL string
|
||||
forgeToken string
|
||||
brainURL string
|
||||
|
|
@ -65,8 +67,11 @@ func NewPrep() *PrepSubsystem {
|
|||
}
|
||||
}
|
||||
|
||||
forgeURL := envOr("FORGE_URL", "https://forge.lthn.ai")
|
||||
|
||||
return &PrepSubsystem{
|
||||
forgeURL: envOr("FORGE_URL", "https://forge.lthn.ai"),
|
||||
forge: forge.NewForge(forgeURL, forgeToken),
|
||||
forgeURL: forgeURL,
|
||||
forgeToken: forgeToken,
|
||||
brainURL: envOr("CORE_BRAIN_URL", "https://api.lthn.sh"),
|
||||
brainKey: brainKey,
|
||||
|
|
@ -257,6 +262,22 @@ func (s *PrepSubsystem) prepWorkspace(ctx context.Context, _ *mcp.CallToolReques
|
|||
return nil, out, nil
|
||||
}
|
||||
|
||||
// --- Public API for CLI testing ---
|
||||
|
||||
// TestPrepWorkspace exposes prepWorkspace for CLI testing.
|
||||
//
|
||||
// _, out, err := prep.TestPrepWorkspace(ctx, input)
|
||||
func (s *PrepSubsystem) TestPrepWorkspace(ctx context.Context, input PrepInput) (*mcp.CallToolResult, PrepOutput, error) {
|
||||
return s.prepWorkspace(ctx, nil, input)
|
||||
}
|
||||
|
||||
// TestBuildPrompt exposes buildPrompt for CLI testing.
|
||||
//
|
||||
// prompt, memories, consumers := prep.TestBuildPrompt(ctx, input, "dev", repoPath)
|
||||
func (s *PrepSubsystem) TestBuildPrompt(ctx context.Context, input PrepInput, branch, repoPath string) (string, int, int) {
|
||||
return s.buildPrompt(ctx, input, branch, repoPath)
|
||||
}
|
||||
|
||||
// --- Prompt Building ---
|
||||
|
||||
// buildPrompt assembles all context into a single prompt string.
|
||||
|
|
@ -348,30 +369,12 @@ func (s *PrepSubsystem) buildPrompt(ctx context.Context, input PrepInput, branch
|
|||
// --- Context Helpers (return strings, not write files) ---
|
||||
|
||||
func (s *PrepSubsystem) getIssueBody(ctx context.Context, org, repo string, issue int) string {
|
||||
if s.forgeToken == "" {
|
||||
idx := core.Sprintf("%d", issue)
|
||||
iss, err := s.forge.Issues.Get(ctx, forge.Params{"owner": org, "repo": repo, "index": idx})
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
url := core.Sprintf("%s/api/v1/repos/%s/%s/issues/%d", s.forgeURL, org, repo, issue)
|
||||
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
req.Header.Set("Authorization", "token "+s.forgeToken)
|
||||
|
||||
resp, err := s.client.Do(req)
|
||||
if err != nil || resp.StatusCode != 200 {
|
||||
if resp != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var issueData struct {
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
json.NewDecoder(resp.Body).Decode(&issueData)
|
||||
|
||||
return core.Sprintf("# %s\n\n%s", issueData.Title, issueData.Body)
|
||||
return core.Sprintf("# %s\n\n%s", iss.Title, iss.Body)
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) brainRecall(ctx context.Context, repo string) (string, int) {
|
||||
|
|
@ -473,64 +476,26 @@ func (s *PrepSubsystem) getGitLog(repoPath string) string {
|
|||
}
|
||||
|
||||
func (s *PrepSubsystem) pullWikiContent(ctx context.Context, org, repo string) string {
|
||||
if s.forgeToken == "" {
|
||||
pages, err := s.forge.Wiki.ListPages(ctx, org, repo)
|
||||
if err != nil || len(pages) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
url := core.Sprintf("%s/api/v1/repos/%s/%s/wiki/pages", s.forgeURL, org, repo)
|
||||
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
req.Header.Set("Authorization", "token "+s.forgeToken)
|
||||
|
||||
resp, err := s.client.Do(req)
|
||||
if err != nil || resp.StatusCode != 200 {
|
||||
if resp != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var pages []struct {
|
||||
Title string `json:"title"`
|
||||
SubURL string `json:"sub_url"`
|
||||
}
|
||||
json.NewDecoder(resp.Body).Decode(&pages)
|
||||
|
||||
b := core.NewBuilder()
|
||||
for _, page := range pages {
|
||||
subURL := page.SubURL
|
||||
if subURL == "" {
|
||||
subURL = page.Title
|
||||
for _, meta := range pages {
|
||||
name := meta.SubURL
|
||||
if name == "" {
|
||||
name = meta.Title
|
||||
}
|
||||
|
||||
pageURL := core.Sprintf("%s/api/v1/repos/%s/%s/wiki/page/%s", s.forgeURL, org, repo, subURL)
|
||||
pageReq, _ := http.NewRequestWithContext(ctx, "GET", pageURL, nil)
|
||||
pageReq.Header.Set("Authorization", "token "+s.forgeToken)
|
||||
|
||||
pageResp, pErr := s.client.Do(pageReq)
|
||||
if pErr != nil || pageResp.StatusCode != 200 {
|
||||
if pageResp != nil {
|
||||
pageResp.Body.Close()
|
||||
}
|
||||
page, pErr := s.forge.Wiki.GetPage(ctx, org, repo, name)
|
||||
if pErr != nil || page.ContentBase64 == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var pageData struct {
|
||||
ContentBase64 string `json:"content_base64"`
|
||||
}
|
||||
json.NewDecoder(pageResp.Body).Decode(&pageData)
|
||||
pageResp.Body.Close()
|
||||
|
||||
if pageData.ContentBase64 == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
content, _ := base64.StdEncoding.DecodeString(pageData.ContentBase64)
|
||||
b.WriteString("### " + page.Title + "\n\n")
|
||||
content, _ := base64.StdEncoding.DecodeString(page.ContentBase64)
|
||||
b.WriteString("### " + meta.Title + "\n\n")
|
||||
b.WriteString(string(content))
|
||||
b.WriteString("\n\n")
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -104,42 +104,14 @@ func (s *PrepSubsystem) scan(ctx context.Context, _ *mcp.CallToolRequest, input
|
|||
}
|
||||
|
||||
func (s *PrepSubsystem) listOrgRepos(ctx context.Context, org string) ([]string, error) {
|
||||
repos, err := s.forge.Repos.ListOrgRepos(ctx, org)
|
||||
if err != nil {
|
||||
return nil, core.E("scan.listOrgRepos", "failed to list repos", err)
|
||||
}
|
||||
|
||||
var allNames []string
|
||||
page := 1
|
||||
|
||||
for {
|
||||
u := core.Sprintf("%s/api/v1/orgs/%s/repos?limit=50&page=%d", s.forgeURL, org, page)
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, core.E("scan.listOrgRepos", "failed to create request", err)
|
||||
}
|
||||
req.Header.Set("Authorization", "token "+s.forgeToken)
|
||||
|
||||
resp, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, core.E("scan.listOrgRepos", "failed to list repos", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
resp.Body.Close()
|
||||
return nil, core.E("scan.listOrgRepos", core.Sprintf("HTTP %d listing repos", resp.StatusCode), nil)
|
||||
}
|
||||
|
||||
var repos []struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
json.NewDecoder(resp.Body).Decode(&repos)
|
||||
resp.Body.Close()
|
||||
|
||||
for _, r := range repos {
|
||||
allNames = append(allNames, r.Name)
|
||||
}
|
||||
|
||||
// If we got fewer than the limit, we've reached the last page
|
||||
if len(repos) < 50 {
|
||||
break
|
||||
}
|
||||
page++
|
||||
for _, r := range repos {
|
||||
allNames = append(allNames, r.Name)
|
||||
}
|
||||
return allNames, nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue