@@ -2,28 +2,101 @@ package httpclient
22
33import (
44 "fmt"
5+ "maps"
56 "net/http"
67 "runtime"
78
89 "github.com/docker/cagent/pkg/version"
910)
1011
12+ type HTTPOptions struct {
13+ Header http.Header
14+ }
15+
16+ type Opt func (* HTTPOptions )
17+
18+ func NewHTTPClient (opts ... Opt ) * http.Client {
19+ httpOptions := HTTPOptions {
20+ Header : make (http.Header ),
21+ }
22+
23+ for _ , opt := range opts {
24+ opt (& httpOptions )
25+ }
26+
27+ // Enforce a consistent User-Agent header
28+ httpOptions .Header .Set ("User-Agent" , fmt .Sprintf ("Cagent/%s (%s; %s)" , version .Version , getNormalizedOS (), getNormalizedArchitecture ()))
29+
30+ return & http.Client {
31+ Transport : & userAgentTransport {
32+ httpOptions : httpOptions ,
33+ rt : http .DefaultTransport ,
34+ },
35+ }
36+ }
37+
38+ func WithHeader (key , value string ) Opt {
39+ return func (o * HTTPOptions ) {
40+ o .Header .Set (key , value )
41+ }
42+ }
43+
44+ func WithProxiedBaseURL (value string ) Opt {
45+ return func (o * HTTPOptions ) {
46+ o .Header .Set ("X-Cagent-Forward" , value )
47+
48+ // Enforce consistent headers (Anthropic client sets similar header already)
49+ o .Header .Set ("X-Cagent-Lang" , "go" )
50+ o .Header .Set ("X-Cagent-OS" , getNormalizedOS ())
51+ o .Header .Set ("X-Cagent-Arch" , getNormalizedArchitecture ())
52+ o .Header .Set ("X-Cagent-Runtime" , "cagent" )
53+ o .Header .Set ("X-Cagent-Runtime-Version" , version .Version )
54+ }
55+ }
56+
1157type userAgentTransport struct {
12- agent string
13- rt http.RoundTripper
58+ httpOptions HTTPOptions
59+ rt http.RoundTripper
1460}
1561
1662func (u * userAgentTransport ) RoundTrip (req * http.Request ) (* http.Response , error ) {
1763 r2 := req .Clone (req .Context ())
18- r2 .Header . Set ( "User-Agent" , u .agent )
64+ maps . Copy ( r2 .Header , u .httpOptions . Header )
1965 return u .rt .RoundTrip (r2 )
2066}
2167
22- func NewHttpClient () * http.Client {
23- return & http.Client {
24- Transport : & userAgentTransport {
25- agent : fmt .Sprintf ("Cagent/%s (%s; %s)" , version .Version , runtime .GOOS , runtime .GOARCH ),
26- rt : http .DefaultTransport ,
27- },
68+ func getNormalizedOS () string {
69+ switch runtime .GOOS {
70+ case "ios" :
71+ return "iOS"
72+ case "android" :
73+ return "Android"
74+ case "darwin" :
75+ return "MacOS"
76+ case "window" :
77+ return "Windows"
78+ case "freebsd" :
79+ return "FreeBSD"
80+ case "openbsd" :
81+ return "OpenBSD"
82+ case "linux" :
83+ return "Linux"
84+ default :
85+ return fmt .Sprintf ("Other:%s" , runtime .GOOS )
86+ }
87+ }
88+
89+ func getNormalizedArchitecture () string {
90+ switch runtime .GOARCH {
91+ case "386" :
92+ return "x32"
93+ case "amd64" :
94+ return "x64"
95+ case "arm" :
96+ return "arm"
97+ case "arm64" :
98+ return "arm64"
99+ default :
100+ return fmt .Sprintf ("other:%s" , runtime .GOARCH )
28101 }
29102}
0 commit comments