Skip to content

Commit 12cedb4

Browse files
author
privapps
committed
Add support for customizable HTTP headers and cross-platform builds
- Introduced new build targets for Linux, macOS, and Windows in Makefile. - Updated README with build instructions for different platforms. - Enhanced configuration to include customizable HTTP headers. - Refactored authentication and API request functions to utilize new header configuration. - Set default header values in config loading.
1 parent 798fcfa commit 12cedb4

File tree

11 files changed

+316
-218
lines changed

11 files changed

+316
-218
lines changed

LICENSE

Lines changed: 201 additions & 200 deletions
Large diffs are not rendered by default.

Makefile

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,22 @@ all: build
66
build:
77
go build -ldflags="-s -w -X main.version=$(VERSION)" -o $(BINARY) .
88

9+
# Cross-platform builds
10+
build-linux-amd64:
11+
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w -X main.version=$(VERSION)" -o $(BINARY)-linux-amd64 .
12+
13+
build-linux-arm64:
14+
GOOS=linux GOARCH=arm64 go build -ldflags="-s -w -X main.version=$(VERSION)" -o $(BINARY)-linux-arm64 .
15+
16+
build-darwin-amd64:
17+
GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w -X main.version=$(VERSION)" -o $(BINARY)-darwin-amd64 .
18+
19+
build-darwin-arm64:
20+
GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w -X main.version=$(VERSION)" -o $(BINARY)-darwin-arm64 .
21+
22+
build-windows-amd64:
23+
GOOS=windows GOARCH=amd64 go build -ldflags="-s -w -X main.version=$(VERSION)" -o $(BINARY)-windows-amd64.exe .
24+
925
run: build
1026
./$(BINARY) run
1127

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ make auth # Authenticate with GitHub Copilot
8383
make models # List available models
8484
make config # Show current configuration
8585
make clean # Remove the binary
86+
make build-linux-amd64 # Build for Linux AMD64
87+
make build-linux-arm64 # Build for Linux ARM64
88+
make build-darwin-amd64 # Build for macOS Intel
89+
make build-darwin-arm64 # Build for macOS Apple Silicon
90+
make build-windows-amd64 # Build for Windows AMD64
8691
```
8792

8893
## Installation & Usage
@@ -224,6 +229,7 @@ Chat completion requests are automatically retried to handle transient failures:
224229

225230
## Configuration
226231

232+
227233
The configuration is stored in `~/.local/share/github-copilot-svcs/config.json`:
228234

229235
```json
@@ -233,6 +239,14 @@ The configuration is stored in `~/.local/share/github-copilot-svcs/config.json`:
233239
"copilot_token": "ghu_...",
234240
"expires_at": 1720000000,
235241
"refresh_in": 1500,
242+
"headers": {
243+
"user_agent": "GitHubCopilotChat/0.29.1",
244+
"editor_version": "vscode/1.102.3",
245+
"editor_plugin_version": "copilot-chat/0.29.1",
246+
"copilot_integration_id": "vscode-chat",
247+
"openai_intent": "conversation-edits",
248+
"x_initiator": "user"
249+
},
236250
"timeouts": {
237251
"http_client": 300,
238252
"server_read": 30,
@@ -248,13 +262,29 @@ The configuration is stored in `~/.local/share/github-copilot-svcs/config.json`:
248262
}
249263
```
250264

265+
251266
### Configuration Fields
252267

253268
- `port`: Server port (default: 8081)
254269
- `github_token`: GitHub OAuth token for Copilot access
255270
- `copilot_token`: GitHub Copilot API token
256271
- `expires_at`: Unix timestamp when the Copilot token expires
257272
- `refresh_in`: Seconds until token should be refreshed (typically 1500 = 25 minutes)
273+
- `headers`: (optional) HTTP headers to use for all Copilot API requests (see below)
274+
### HTTP Headers Configuration
275+
276+
The `headers` section allows you to customize the HTTP headers sent to the Copilot API. All fields are optional; defaults are shown below:
277+
278+
| Field | Default Value | Description |
279+
|--------------------------|-------------------------------|--------------------------------------------------|
280+
| `user_agent` | GitHubCopilotChat/0.29.1 | User-Agent header for all requests |
281+
| `editor_version` | vscode/1.102.3 | Editor-Version header |
282+
| `editor_plugin_version` | copilot-chat/0.29.1 | Editor-Plugin-Version header |
283+
| `copilot_integration_id` | vscode-chat | Copilot-Integration-Id header |
284+
| `openai_intent` | conversation-edits | Openai-Intent header |
285+
| `x_initiator` | user | X-Initiator header |
286+
287+
You can override any of these by editing your `config.json`.
258288

259289
### Timeout Configuration
260290

auth.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ const (
1717
copilotAPIKeyURL = "https://api.github.com/copilot_internal/v2/token"
1818
copilotClientID = "Iv1.b507a08c87ecfe98"
1919
copilotScope = "read:user"
20-
userAgent = "GitHubCopilotChat/0.26.7"
2120

2221
// Retry configuration
2322
maxRefreshRetries = 3
@@ -67,7 +66,7 @@ func authenticate(cfg *Config) error {
6766
}
6867
req.Header.Set("Accept", "application/json")
6968
req.Header.Set("Content-Type", "application/json")
70-
req.Header.Set("User-Agent", userAgent)
69+
req.Header.Set("User-Agent", cfg.Headers.UserAgent)
7170

7271
body := fmt.Sprintf(`{"client_id":"%s","scope":"%s"}`, copilotClientID, copilotScope)
7372
req.Body = io.NopCloser(strings.NewReader(body))
@@ -86,14 +85,14 @@ func authenticate(cfg *Config) error {
8685
fmt.Printf("\nTo authenticate, visit: %s\nEnter code: %s\n", dc.VerificationURI, dc.UserCode)
8786

8887
// Step 2: Poll for GitHub token
89-
githubToken, err := pollForGitHubToken(dc.DeviceCode, dc.Interval)
88+
githubToken, err := pollForGitHubToken(cfg, dc.DeviceCode, dc.Interval)
9089
if err != nil {
9190
return err
9291
}
9392
cfg.GitHubToken = githubToken
9493

9594
// Step 3: Exchange GitHub token for Copilot token
96-
copilotToken, expiresAt, refreshIn, err := getCopilotToken(githubToken)
95+
copilotToken, expiresAt, refreshIn, err := getCopilotToken(cfg, githubToken)
9796
if err != nil {
9897
return err
9998
}
@@ -110,7 +109,7 @@ func authenticate(cfg *Config) error {
110109
return nil
111110
}
112111

113-
func pollForGitHubToken(deviceCode string, interval int) (string, error) {
112+
func pollForGitHubToken(cfg *Config, deviceCode string, interval int) (string, error) {
114113
for i := 0; i < 120; i++ { // Poll for 2 minutes max
115114
time.Sleep(time.Duration(interval) * time.Second)
116115

@@ -120,7 +119,7 @@ func pollForGitHubToken(deviceCode string, interval int) (string, error) {
120119
}
121120
req.Header.Set("Accept", "application/json")
122121
req.Header.Set("Content-Type", "application/json")
123-
req.Header.Set("User-Agent", userAgent)
122+
req.Header.Set("User-Agent", cfg.Headers.UserAgent)
124123

125124
body := fmt.Sprintf(`{"client_id":"%s","device_code":"%s","grant_type":"urn:ietf:params:oauth:grant-type:device_code"}`,
126125
copilotClientID, deviceCode)
@@ -150,13 +149,13 @@ func pollForGitHubToken(deviceCode string, interval int) (string, error) {
150149
return "", fmt.Errorf("authentication timed out")
151150
}
152151

153-
func getCopilotToken(githubToken string) (string, int64, int64, error) {
152+
func getCopilotToken(cfg *Config, githubToken string) (string, int64, int64, error) {
154153
req, err := http.NewRequest("GET", copilotAPIKeyURL, nil)
155154
if err != nil {
156155
return "", 0, 0, err
157156
}
158157
req.Header.Set("Authorization", "token "+githubToken)
159-
req.Header.Set("User-Agent", userAgent)
158+
req.Header.Set("User-Agent", cfg.Headers.UserAgent)
160159

161160
resp, err := sharedHTTPClient.Do(req)
162161
if err != nil {
@@ -186,7 +185,7 @@ func refreshToken(cfg *Config) error {
186185
for attempt := 1; attempt <= maxRefreshRetries; attempt++ {
187186
log.Printf("Attempting to refresh Copilot token (attempt %d/%d)", attempt, maxRefreshRetries)
188187

189-
copilotToken, expiresAt, refreshIn, err := getCopilotToken(cfg.GitHubToken)
188+
copilotToken, expiresAt, refreshIn, err := getCopilotToken(cfg, cfg.GitHubToken)
190189
if err != nil {
191190
if attempt == maxRefreshRetries {
192191
log.Printf("Token refresh failed after %d attempts: %v", maxRefreshRetries, err)

cli.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,14 @@ func handleConfig() error {
106106
fmt.Printf("Token expires at: %d\n", cfg.ExpiresAt)
107107
}
108108

109+
fmt.Printf("\nHTTP Headers:\n")
110+
fmt.Printf(" User-Agent: %s\n", cfg.Headers.UserAgent)
111+
fmt.Printf(" Editor-Version: %s\n", cfg.Headers.EditorVersion)
112+
fmt.Printf(" Editor-Plugin-Version: %s\n", cfg.Headers.EditorPluginVersion)
113+
fmt.Printf(" Copilot-Integration-Id: %s\n", cfg.Headers.CopilotIntegrationID)
114+
fmt.Printf(" Openai-Intent: %s\n", cfg.Headers.OpenaiIntent)
115+
fmt.Printf(" X-Initiator: %s\n", cfg.Headers.XInitiator)
116+
109117
return nil
110118
}
111119

config.example.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
{
22
"port": 8081,
3+
"headers": {
4+
"user_agent": "GitHubCopilotChat/0.29.1",
5+
"editor_version": "vscode/1.102.3",
6+
"editor_plugin_version": "copilot-chat/0.29.1",
7+
"copilot_integration_id": "vscode-chat",
8+
"openai_intent": "conversation-edits",
9+
"x_initiator": "user"
10+
},
311
"timeouts": {
412
"http_client": 300,
513
"server_read": 30,

config.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ func loadConfig() (*Config, error) {
1616
// Return default config if file doesn't exist
1717
cfg := &Config{Port: 8081}
1818
setDefaultTimeouts(cfg)
19+
setDefaultHeaders(cfg)
1920
return cfg, nil
2021
}
2122
defer file.Close()
@@ -33,6 +34,9 @@ func loadConfig() (*Config, error) {
3334
// Set default timeouts if not specified
3435
setDefaultTimeouts(&cfg)
3536

37+
// Set default headers if not specified
38+
setDefaultHeaders(&cfg)
39+
3640
return &cfg, nil
3741
}
3842

@@ -70,6 +74,28 @@ func setDefaultTimeouts(cfg *Config) {
7074
}
7175
}
7276

77+
// setDefaultHeaders sets default header values if they are empty
78+
func setDefaultHeaders(cfg *Config) {
79+
if cfg.Headers.UserAgent == "" {
80+
cfg.Headers.UserAgent = "GitHubCopilotChat/0.29.1"
81+
}
82+
if cfg.Headers.EditorVersion == "" {
83+
cfg.Headers.EditorVersion = "vscode/1.102.3"
84+
}
85+
if cfg.Headers.EditorPluginVersion == "" {
86+
cfg.Headers.EditorPluginVersion = "copilot-chat/0.29.1"
87+
}
88+
if cfg.Headers.CopilotIntegrationID == "" {
89+
cfg.Headers.CopilotIntegrationID = "vscode-chat"
90+
}
91+
if cfg.Headers.OpenaiIntent == "" {
92+
cfg.Headers.OpenaiIntent = "conversation-edits"
93+
}
94+
if cfg.Headers.XInitiator == "" {
95+
cfg.Headers.XInitiator = "user"
96+
}
97+
}
98+
7399
func saveConfig(cfg *Config) error {
74100
path, err := getConfigPath()
75101
if err != nil {

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
module github.com/yourname/github-copilot-svcs
1+
module github.com/privapps/github-copilot-svcs
22

33
go 1.21

main.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,16 @@ type Config struct {
1818
ExpiresAt int64 `json:"expires_at"`
1919
RefreshIn int64 `json:"refresh_in"`
2020

21+
// HTTP Headers configuration
22+
Headers struct {
23+
UserAgent string `json:"user_agent"` // Default: "GitHubCopilotChat/0.29.1"
24+
EditorVersion string `json:"editor_version"` // Default: "vscode/1.102.3"
25+
EditorPluginVersion string `json:"editor_plugin_version"` // Default: "copilot-chat/0.29.1"
26+
CopilotIntegrationID string `json:"copilot_integration_id"` // Default: "vscode-chat"
27+
OpenaiIntent string `json:"openai_intent"` // Default: "conversation-edits"
28+
XInitiator string `json:"x_initiator"` // Default: "user"
29+
} `json:"headers"`
30+
2131
// Timeout configurations (in seconds)
2232
Timeouts struct {
2333
HTTPClient int `json:"http_client"` // Default: 300s for streaming responses

models.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,15 @@ type ModelsDevResponse map[string]struct {
2828
}
2929

3030
// fetchModelsFromCopilotAPI tries to get models directly from GitHub Copilot API
31-
func fetchModelsFromCopilotAPI(token string) (*ModelList, error) {
31+
func fetchModelsFromCopilotAPI(cfg *Config, token string) (*ModelList, error) {
3232
req, err := http.NewRequest("GET", "https://api.githubcopilot.com/v1/models", nil)
3333
if err != nil {
3434
return nil, err
3535
}
3636

3737
req.Header.Set("Authorization", "Bearer "+token)
3838
req.Header.Set("Accept", "application/json")
39-
req.Header.Set("User-Agent", userAgent)
39+
req.Header.Set("User-Agent", cfg.Headers.UserAgent)
4040

4141
resp, err := sharedHTTPClient.Do(req)
4242
if err != nil {

0 commit comments

Comments
 (0)