Skip to content

Commit c4d6669

Browse files
authored
feat: refactor provider clients to inject default app headers automatically (#235)
- Move DefaultHeaderTransport and NewHeaders utilities into a shared core/transport package - Add automatic injection of x-app-name and x-app-version headers to outgoing HTTP requests using values from the version package - Inject default headers in provider clients (Anthropic, Gemini, OpenAI) by wrapping HTTP client transports with DefaultHeaderTransport - Remove duplicated DefaultHeaderTransport and NewHeaders implementations from provider code - Set and use the App field in the version package for branding in headers Signed-off-by: appleboy <[email protected]>
1 parent e2447f4 commit c4d6669

File tree

7 files changed

+99
-103
lines changed

7 files changed

+99
-103
lines changed

core/transport/headers.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package transport
2+
3+
import (
4+
"net/http"
5+
"strings"
6+
)
7+
8+
// NewHeaders creates a new http.Header from the given slice of headers.
9+
// Each header in the slice should be in the format "key=value".
10+
// If a header is not in the correct format, it is skipped.
11+
func NewHeaders(headers []string) http.Header {
12+
h := make(http.Header)
13+
for _, header := range headers {
14+
vals := strings.Split(header, "=")
15+
if len(vals) != 2 {
16+
continue
17+
}
18+
h.Add(vals[0], vals[1])
19+
}
20+
return h
21+
}

core/transport/transport.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package transport
2+
3+
import (
4+
"net/http"
5+
)
6+
7+
// DefaultHeaderTransport is an http.RoundTripper that adds the given headers to each request,
8+
// and always injects x-app-name and x-app-version headers.
9+
type DefaultHeaderTransport struct {
10+
Origin http.RoundTripper
11+
Header http.Header
12+
AppName string
13+
AppVersion string
14+
}
15+
16+
// RoundTrip implements the http.RoundTripper interface.
17+
func (t *DefaultHeaderTransport) RoundTrip(req *http.Request) (*http.Response, error) {
18+
for key, values := range t.Header {
19+
for _, value := range values {
20+
req.Header.Add(key, value)
21+
}
22+
}
23+
if t.AppName != "" {
24+
req.Header.Set("x-app-name", t.AppName)
25+
}
26+
if t.AppVersion != "" {
27+
req.Header.Set("x-app-version", t.AppVersion)
28+
}
29+
return t.Origin.RoundTrip(req)
30+
}

provider/anthropic/anthropic.go

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ import (
55
"encoding/json"
66
"errors"
77
"fmt"
8-
"net/http"
98

109
"github.com/appleboy/CodeGPT/core"
10+
"github.com/appleboy/CodeGPT/core/transport"
1111
"github.com/appleboy/CodeGPT/proxy"
12+
"github.com/appleboy/CodeGPT/version"
1213

1314
"github.com/appleboy/com/convert"
1415
"github.com/liushuangls/go-anthropic/v2"
@@ -115,35 +116,7 @@ func (c *Client) GetSummaryPrefix(ctx context.Context, content string) (*core.Re
115116
}, nil
116117
}
117118

118-
// DefaultHeaderTransport is an http.RoundTripper that adds the given headers to
119-
type DefaultHeaderTransport struct {
120-
Origin http.RoundTripper
121-
Header http.Header
122-
}
123-
124-
// RoundTrip implements the http.RoundTripper interface.
125-
func (t *DefaultHeaderTransport) RoundTrip(req *http.Request) (*http.Response, error) {
126-
for key, values := range t.Header {
127-
for _, value := range values {
128-
req.Header.Add(key, value)
129-
}
130-
}
131-
return t.Origin.RoundTrip(req)
132-
}
133-
134119
// New creates a new Client instance with the provided options.
135-
// It validates the configuration, sets up the HTTP transport with optional
136-
// proxy settings, and initializes the Client with the specified parameters.
137-
//
138-
// Parameters:
139-
//
140-
// opts - A variadic list of Option functions to configure the Client.
141-
//
142-
// Returns:
143-
//
144-
// c - A pointer to the newly created Client instance.
145-
// err - An error if the configuration is invalid or if there is an issue
146-
// setting up the HTTP transport or proxy.
147120
func New(opts ...Option) (c *Client, err error) {
148121
// Create a new config object with the given options.
149122
cfg := newConfig(opts...)
@@ -163,6 +136,14 @@ func New(opts ...Option) (c *Client, err error) {
163136
return nil, fmt.Errorf("can't create a new HTTP client: %w", err)
164137
}
165138

139+
// Inject x-app-name and x-app-version headers using core/transport.DefaultHeaderTransport
140+
httpClient.Transport = &transport.DefaultHeaderTransport{
141+
Origin: httpClient.Transport,
142+
Header: nil,
143+
AppName: version.App,
144+
AppVersion: version.Version,
145+
}
146+
166147
// Create a new client instance with the necessary fields.
167148
engine := &Client{
168149
client: anthropic.NewClient(

provider/gemini/gemini.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ package gemini
33
import (
44
"context"
55
"fmt"
6+
"net/http"
67
"strings"
78

89
"github.com/appleboy/CodeGPT/core"
10+
"github.com/appleboy/CodeGPT/core/transport"
11+
"github.com/appleboy/CodeGPT/version"
912
"github.com/appleboy/com/convert"
1013

1114
"github.com/google/generative-ai-go/genai"
@@ -117,7 +120,17 @@ func New(ctx context.Context, opts ...Option) (c *Client, err error) {
117120
temperature: cfg.temperature,
118121
}
119122

120-
client, err := genai.NewClient(ctx, option.WithAPIKey(cfg.token))
123+
// Inject x-app-name and x-app-version headers using core/transport.DefaultHeaderTransport
124+
httpClient := &http.Client{
125+
Transport: &transport.DefaultHeaderTransport{
126+
Origin: http.DefaultTransport,
127+
Header: nil,
128+
AppName: version.App,
129+
AppVersion: version.Version,
130+
},
131+
}
132+
133+
client, err := genai.NewClient(ctx, option.WithAPIKey(cfg.token), option.WithHTTPClient(httpClient))
121134
if err != nil {
122135
return nil, err
123136
}

provider/openai/client.go

Lines changed: 0 additions & 54 deletions
This file was deleted.

provider/openai/openai.go

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import (
77
"strings"
88

99
"github.com/appleboy/CodeGPT/core"
10+
"github.com/appleboy/CodeGPT/core/transport"
1011
"github.com/appleboy/CodeGPT/proxy"
12+
"github.com/appleboy/CodeGPT/version"
1113

1214
openai "github.com/sashabaranov/go-openai"
1315
)
@@ -19,23 +21,13 @@ var _ core.Generative = (*Client)(nil)
1921

2022
// Client is a struct that represents an OpenAI client.
2123
type Client struct {
22-
client *openai.Client
23-
model string
24-
maxTokens int
25-
temperature float32
26-
27-
// An alternative to sampling with temperature, called nucleus sampling,
28-
// where the model considers the results of the tokens with top_p probability mass.
29-
// So 0.1 means only the tokens comprising the top 10% probability mass are considered.
30-
topP float32
31-
// Number between -2.0 and 2.0.
32-
// Positive values penalize new tokens based on whether they appear in the text so far,
33-
// increasing the model's likelihood to talk about new topics.
34-
presencePenalty float32
35-
// Number between -2.0 and 2.0.
36-
// Positive values penalize new tokens based on their existing frequency in the text so far,
37-
// decreasing the model's likelihood to repeat the same line verbatim.
24+
client *openai.Client
25+
model string
26+
maxTokens int
27+
temperature float32
28+
topP float32
3829
frequencyPenalty float32
30+
presencePenalty float32
3931
}
4032

4133
type Response struct {
@@ -205,9 +197,12 @@ func New(opts ...Option) (*Client, error) {
205197

206198
// Create a new client instance with the necessary fields.
207199
engine := &Client{
208-
model: cfg.model,
209-
maxTokens: cfg.maxTokens,
210-
temperature: cfg.temperature,
200+
model: cfg.model,
201+
maxTokens: cfg.maxTokens,
202+
temperature: cfg.temperature,
203+
topP: cfg.topP,
204+
frequencyPenalty: cfg.frequencyPenalty,
205+
presencePenalty: cfg.presencePenalty,
211206
}
212207

213208
// Create a new OpenAI config object with the given API token and other optional fields.
@@ -230,6 +225,15 @@ func New(opts ...Option) (*Client, error) {
230225
return nil, fmt.Errorf("can't create a new HTTP client: %w", err)
231226
}
232227

228+
// Inject x-app-name and x-app-version headers using core/transport.DefaultHeaderTransport
229+
// Always wrap the proxy's httpClient.Transport
230+
httpClient.Transport = &transport.DefaultHeaderTransport{
231+
Origin: httpClient.Transport,
232+
Header: nil,
233+
AppName: version.App,
234+
AppVersion: version.Version,
235+
}
236+
233237
// Set the OpenAI client to use the default configuration with Azure-specific options, if the provider is Azure.
234238
if cfg.provider == core.Azure {
235239
defaultAzureConfig := openai.DefaultAzureConfig(cfg.token, cfg.baseURL)

version/version.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package version
22

33
var (
4+
App string = "CodeGPT"
45
Version string
56
GitCommit string
67
BuildTime string

0 commit comments

Comments
 (0)