Skip to content

Commit 3186729

Browse files
committed
WIP plan_write tool
1 parent 11afe11 commit 3186729

27 files changed

+2081
-246
lines changed

agent/agent.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ func (a *Agent) executeLocalTool(id, name string, input json.RawMessage) message
216216
response = final.String()
217217
} else {
218218
// The main agent invokes tools
219-
response, err = toolDef.Function(input)
219+
response, err = toolDef.Function(input, a.client)
220220
}
221221

222222
if err != nil {

agent/subagent.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"fmt"
77

8+
"github.com/honganh1206/clue/api"
89
"github.com/honganh1206/clue/inference"
910
"github.com/honganh1206/clue/message"
1011
"github.com/honganh1206/clue/tools"
@@ -112,8 +113,9 @@ func (s *Subagent) executeTool(id, name string, input json.RawMessage) message.C
112113
return message.NewToolResultBlock(id, name, errorMsg, true)
113114
}
114115

115-
response, err := toolDef.Function(input)
116+
client := api.NewClient("") // TODO: Very temp
116117

118+
response, err := toolDef.Function(input, client)
117119
if err != nil {
118120
return message.NewToolResultBlock(id, name, err.Error(), true)
119121
}

api/client.go

Lines changed: 1 addition & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,7 @@
11
package api
22

33
import (
4-
"bytes"
5-
"encoding/json"
6-
"fmt"
7-
"io"
84
"net/http"
9-
10-
"github.com/honganh1206/clue/message"
11-
"github.com/honganh1206/clue/server/data/conversation"
125
)
136

147
type Client struct {
@@ -24,117 +17,4 @@ func NewClient(baseURL string) *Client {
2417
baseURL: baseURL,
2518
httpClient: &http.Client{},
2619
}
27-
}
28-
29-
func (c *Client) CreateConversation() (*conversation.Conversation, error) {
30-
resp, err := c.httpClient.Post(c.baseURL+"/conversations", "application/json", nil)
31-
if err != nil {
32-
return nil, fmt.Errorf("failed to create conversation: %w", err)
33-
}
34-
defer resp.Body.Close()
35-
36-
if resp.StatusCode != http.StatusOK {
37-
body, _ := io.ReadAll(resp.Body)
38-
return nil, fmt.Errorf("server error: %s", string(body))
39-
}
40-
41-
var result map[string]string
42-
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
43-
return nil, fmt.Errorf("failed to decode response: %w", err)
44-
}
45-
46-
// TODO: Use NewConversation method?
47-
return &conversation.Conversation{
48-
ID: result["id"],
49-
Messages: make([]*message.Message, 0),
50-
}, nil
51-
}
52-
53-
func (c *Client) ListConversations() ([]conversation.ConversationMetadata, error) {
54-
resp, err := c.httpClient.Get(c.baseURL + "/conversations")
55-
if err != nil {
56-
return nil, fmt.Errorf("failed to list conversations: %w", err)
57-
}
58-
defer resp.Body.Close()
59-
60-
if resp.StatusCode != http.StatusOK {
61-
body, _ := io.ReadAll(resp.Body)
62-
return nil, fmt.Errorf("server error: %s", string(body))
63-
}
64-
65-
var conversations []conversation.ConversationMetadata
66-
if err := json.NewDecoder(resp.Body).Decode(&conversations); err != nil {
67-
return nil, fmt.Errorf("failed to decode response: %w", err)
68-
}
69-
70-
return conversations, nil
71-
}
72-
73-
func (c *Client) GetConversation(id string) (*conversation.Conversation, error) {
74-
resp, err := c.httpClient.Get(c.baseURL + "/conversations/" + id)
75-
if err != nil {
76-
return nil, fmt.Errorf("failed to get conversation: %w", err)
77-
}
78-
defer resp.Body.Close()
79-
80-
if resp.StatusCode == http.StatusNotFound {
81-
return nil, conversation.ErrConversationNotFound
82-
}
83-
84-
if resp.StatusCode != http.StatusOK {
85-
body, _ := io.ReadAll(resp.Body)
86-
return nil, fmt.Errorf("server error: %s", string(body))
87-
}
88-
89-
var conv conversation.Conversation
90-
if err := json.NewDecoder(resp.Body).Decode(&conv); err != nil {
91-
return nil, fmt.Errorf("failed to decode response: %w", err)
92-
}
93-
94-
return &conv, nil
95-
}
96-
97-
func (c *Client) SaveConversation(conv *conversation.Conversation) error {
98-
jsonData, err := json.Marshal(conv)
99-
if err != nil {
100-
return fmt.Errorf("failed to marshal conversation: %w", err)
101-
}
102-
103-
url := fmt.Sprintf("%s/conversations/%s", c.baseURL, conv.ID)
104-
req, err := http.NewRequest(http.MethodPut, url, bytes.NewBuffer(jsonData))
105-
if err != nil {
106-
return fmt.Errorf("failed to create request: %w", err)
107-
}
108-
req.Header.Set("Content-Type", "application/json")
109-
110-
resp, err := c.httpClient.Do(req)
111-
if err != nil {
112-
return fmt.Errorf("failed to save conversation: %w", err)
113-
}
114-
defer resp.Body.Close()
115-
116-
if resp.StatusCode == http.StatusNotFound {
117-
return conversation.ErrConversationNotFound
118-
}
119-
120-
if resp.StatusCode != http.StatusOK {
121-
body, _ := io.ReadAll(resp.Body)
122-
return fmt.Errorf("server error: %s", string(body))
123-
}
124-
125-
return nil
126-
}
127-
128-
func (c *Client) GetLatestConversationID() (string, error) {
129-
conversations, err := c.ListConversations()
130-
if err != nil {
131-
return "", err
132-
}
133-
134-
if len(conversations) == 0 {
135-
return "", conversation.ErrConversationNotFound
136-
}
137-
138-
return conversations[0].ID, nil
139-
}
140-
20+
}

api/conversation_client.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package api
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"net/http"
7+
8+
"github.com/honganh1206/clue/message"
9+
"github.com/honganh1206/clue/server/data/conversation"
10+
)
11+
12+
func (c *Client) CreateConversation() (*conversation.Conversation, error) {
13+
var result map[string]string
14+
if err := c.doRequest(http.MethodPost, "/conversations", nil, &result); err != nil {
15+
return nil, err
16+
}
17+
18+
return &conversation.Conversation{
19+
ID: result["id"],
20+
Messages: make([]*message.Message, 0),
21+
}, nil
22+
}
23+
24+
func (c *Client) ListConversations() ([]conversation.ConversationMetadata, error) {
25+
var conversations []conversation.ConversationMetadata
26+
if err := c.doRequest(http.MethodGet, "/conversations", nil, &conversations); err != nil {
27+
return nil, err
28+
}
29+
30+
return conversations, nil
31+
}
32+
33+
func (c *Client) GetConversation(id string) (*conversation.Conversation, error) {
34+
var conv conversation.Conversation
35+
if err := c.doRequest(http.MethodGet, "/conversations/"+id, nil, &conv); err != nil {
36+
var httpErr *HTTPError
37+
if errors.As(err, &httpErr) && httpErr.StatusCode == http.StatusNotFound {
38+
return nil, conversation.ErrConversationNotFound
39+
}
40+
return nil, err
41+
}
42+
43+
return &conv, nil
44+
}
45+
46+
func (c *Client) SaveConversation(conv *conversation.Conversation) error {
47+
path := fmt.Sprintf("/conversations/%s", conv.ID)
48+
if err := c.doRequest(http.MethodPut, path, conv, nil); err != nil {
49+
var httpErr *HTTPError
50+
if errors.As(err, &httpErr) && httpErr.StatusCode == http.StatusNotFound {
51+
return conversation.ErrConversationNotFound
52+
}
53+
return err
54+
}
55+
56+
return nil
57+
}
58+
59+
func (c *Client) GetLatestConversationID() (string, error) {
60+
conversations, err := c.ListConversations()
61+
if err != nil {
62+
return "", err
63+
}
64+
65+
if len(conversations) == 0 {
66+
return "", conversation.ErrConversationNotFound
67+
}
68+
69+
return conversations[0].ID, nil
70+
}

api/plan_client.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package api
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"net/http"
7+
8+
"github.com/honganh1206/clue/server/data/plan"
9+
)
10+
11+
func (c *Client) CreatePlan(name string) (*plan.Plan, error) {
12+
reqBody := map[string]string{"name": name}
13+
var result map[string]string
14+
if err := c.doRequest(http.MethodPost, "/plans", reqBody, &result); err != nil {
15+
return nil, err
16+
}
17+
18+
return &plan.Plan{
19+
ID: result["id"],
20+
}, nil
21+
}
22+
23+
func (c *Client) ListPlans() ([]plan.PlanInfo, error) {
24+
var plans []plan.PlanInfo
25+
if err := c.doRequest(http.MethodGet, "/plans", nil, &plans); err != nil {
26+
return nil, err
27+
}
28+
29+
return plans, nil
30+
}
31+
32+
func (c *Client) GetPlan(name string) (*plan.Plan, error) {
33+
var p plan.Plan
34+
if err := c.doRequest(http.MethodGet, "/plans/"+name, nil, &p); err != nil {
35+
var httpErr *HTTPError
36+
if errors.As(err, &httpErr) && httpErr.StatusCode == http.StatusNotFound {
37+
return nil, plan.ErrPlanNotFound
38+
}
39+
return nil, err
40+
}
41+
42+
return &p, nil
43+
}
44+
45+
func (c *Client) SavePlan(p *plan.Plan) error {
46+
path := fmt.Sprintf("/plans/%s", p.ID)
47+
if err := c.doRequest(http.MethodPut, path, p, nil); err != nil {
48+
var httpErr *HTTPError
49+
if errors.As(err, &httpErr) && httpErr.StatusCode == http.StatusNotFound {
50+
return plan.ErrPlanNotFound
51+
}
52+
return err
53+
}
54+
55+
return nil
56+
}
57+
58+
func (c *Client) DeletePlan(name string) error {
59+
path := fmt.Sprintf("/plans/%s", name)
60+
if err := c.doRequest(http.MethodDelete, path, nil, nil); err != nil {
61+
var httpErr *HTTPError
62+
if errors.As(err, &httpErr) && httpErr.StatusCode == http.StatusNotFound {
63+
return plan.ErrPlanNotFound
64+
}
65+
return err
66+
}
67+
68+
return nil
69+
}
70+
71+
func (c *Client) DeletePlans(names []string) (map[string]error, error) {
72+
reqBody := map[string][]string{"names": names}
73+
var response struct {
74+
Results map[string]interface{} `json:"results"`
75+
}
76+
77+
if err := c.doRequest(http.MethodDelete, "/plans", reqBody, &response); err != nil {
78+
return nil, err
79+
}
80+
81+
results := make(map[string]error)
82+
for name, errMsg := range response.Results {
83+
if errMsg != nil {
84+
results[name] = fmt.Errorf("%v", errMsg)
85+
} else {
86+
results[name] = nil
87+
}
88+
}
89+
90+
return results, nil
91+
}

api/request.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package api
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
)
10+
11+
func (c *Client) doRequest(method, path string, body, result any) error {
12+
var bodyReader io.Reader
13+
if body != nil {
14+
jsonData, err := json.Marshal(body)
15+
if err != nil {
16+
return fmt.Errorf("failed to marshal request: %w", err)
17+
}
18+
bodyReader = bytes.NewBuffer(jsonData)
19+
}
20+
21+
req, err := http.NewRequest(method, c.baseURL+path, bodyReader)
22+
if err != nil {
23+
return fmt.Errorf("failed to create request: %w", err)
24+
}
25+
26+
if body != nil {
27+
req.Header.Set("Content-Type", "application/json")
28+
}
29+
30+
resp, err := c.httpClient.Do(req)
31+
if err != nil {
32+
return fmt.Errorf("request failed: %w", err)
33+
}
34+
defer resp.Body.Close()
35+
36+
if resp.StatusCode >= 400 {
37+
bodyBytes, _ := io.ReadAll(resp.Body)
38+
return &HTTPError{
39+
StatusCode: resp.StatusCode,
40+
Message: string(bodyBytes),
41+
}
42+
}
43+
44+
if result != nil {
45+
if err := json.NewDecoder(resp.Body).Decode(result); err != nil {
46+
return fmt.Errorf("failed to decode response: %w", err)
47+
}
48+
}
49+
50+
return nil
51+
}
52+
53+
type HTTPError struct {
54+
StatusCode int
55+
Message string
56+
}
57+
58+
func (e *HTTPError) Error() string {
59+
return fmt.Sprintf("server error (%d): %s", e.StatusCode, e.Message)
60+
}

cmd/interactive.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ func interactive(ctx context.Context, convID string, llmClient, llmClientSub inf
2727
&tools.GrepSearchDefinition,
2828
&tools.CodebaseSearchAgentDefinition, // Now handled by subagent
2929
&tools.BashDefinition,
30+
&tools.PlanWriteDefinition,
3031
},
3132
}
3233

0 commit comments

Comments
 (0)