-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathclient.go
More file actions
149 lines (128 loc) · 3.57 KB
/
Copy pathclient.go
File metadata and controls
149 lines (128 loc) · 3.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package agents
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
)
const DefaultBaseURL = "https://relay.an.dev"
// Option configures an AgentClient.
type Option func(*AgentClient)
// WithBaseURL overrides the default relay URL.
func WithBaseURL(url string) Option {
return func(c *AgentClient) { c.baseURL = url }
}
// WithHTTPClient sets a custom *http.Client.
func WithHTTPClient(hc *http.Client) Option {
return func(c *AgentClient) { c.http = hc }
}
// AgentClient is the main entry point for the 21st relay API.
type AgentClient struct {
apiKey string
baseURL string
http *http.Client
Sandboxes *SandboxesResource
Threads *ThreadsResource
Tokens *TokensResource
}
// NewAgentClient creates a new client with the given API key.
func NewAgentClient(apiKey string, opts ...Option) *AgentClient {
c := &AgentClient{
apiKey: apiKey,
baseURL: DefaultBaseURL,
http: http.DefaultClient,
}
for _, opt := range opts {
opt(c)
}
c.baseURL = strings.TrimRight(c.baseURL, "/")
c.Sandboxes = &SandboxesResource{client: c}
c.Sandboxes.Files = &FilesResource{client: c}
c.Sandboxes.Git = &GitResource{client: c}
c.Threads = &ThreadsResource{client: c}
c.Tokens = &TokensResource{client: c}
return c
}
// APIError represents an error response from the relay API.
type APIError struct {
StatusCode int
Code string
Message string
}
func (e *APIError) Error() string {
if e.Message != "" {
return e.Message
}
return fmt.Sprintf("request failed: %d", e.StatusCode)
}
// IsAPIError checks if err is an *APIError, optionally matching a status code.
func IsAPIError(err error, statusCode ...int) bool {
var apiErr *APIError
if !errors.As(err, &apiErr) {
return false
}
if len(statusCode) > 0 {
return apiErr.StatusCode == statusCode[0]
}
return true
}
// doRequest executes an HTTP request with auth and error handling.
// Returns the raw response on success. Caller must close the body.
func (c *AgentClient) doRequest(ctx context.Context, method, path string, body interface{}) (*http.Response, error) {
var bodyReader io.Reader
if body != nil {
b, err := json.Marshal(body)
if err != nil {
return nil, fmt.Errorf("agents: marshal request: %w", err)
}
bodyReader = bytes.NewReader(b)
}
req, err := http.NewRequestWithContext(ctx, method, c.baseURL+path, bodyReader)
if err != nil {
return nil, fmt.Errorf("agents: create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+c.apiKey)
resp, err := c.http.Do(req)
if err != nil {
return nil, fmt.Errorf("agents: %s %s: %w", method, path, err)
}
if resp.StatusCode >= 400 {
defer resp.Body.Close()
var envelope struct {
Error struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
_ = json.NewDecoder(resp.Body).Decode(&envelope)
return nil, &APIError{
StatusCode: resp.StatusCode,
Code: envelope.Error.Code,
Message: envelope.Error.Message,
}
}
return resp, nil
}
// do executes a request and JSON-decodes the response into dest.
// For 204 No Content, dest is left untouched and nil is returned.
func (c *AgentClient) do(ctx context.Context, method, path string, body, dest interface{}) error {
resp, err := c.doRequest(ctx, method, path, body)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusNoContent {
return nil
}
if dest != nil {
if err := json.NewDecoder(resp.Body).Decode(dest); err != nil {
return fmt.Errorf("agents: decode response: %w", err)
}
}
return nil
}