@@ -23,6 +23,22 @@ type Response struct {
Data []Model `json:"data"`
}
+type APITokenResponse struct {
+ Token string `json:"token"`
+ ExpiresAt int64 `json:"expires_at"`
+ Endpoints APITokenResponseEndpoints `json:"endpoints"`
+}
+
+type APITokenResponseEndpoints struct {
+ API string `json:"api"`
+}
+
+type APIToken struct {
+ APIKey string
+ ExpiresAt time.Time
+ APIEndpoint string
+}
+
type Model struct {
ID string `json:"id"`
Name string `json:"name"`
@@ -97,6 +113,7 @@ func run() error {
if err != nil {
return fmt.Errorf("unable to marshal json: %w", err)
}
+ data = append(data, '\n')
if err := os.WriteFile("internal/providers/configs/copilot.json", data, 0o600); err != nil {
return fmt.Errorf("unable to write copilog.json: %w", err)
}
@@ -107,40 +124,85 @@ func fetchCopilotModels() ([]Model, error) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
- req, err := http.NewRequestWithContext(
- ctx,
- "GET",
- "https://api.githubcopilot.com/models",
- nil,
- )
+ oauthToken := copilotToken()
+ if oauthToken == "" {
+ return nil, fmt.Errorf("no OAuth token available")
+ }
+
+ // Step 1: Fetch API token from the token endpoint
+ tokenURL := "https://api.github.com/copilot_internal/v2/token" //nolint:gosec
+ tokenReq, err := http.NewRequestWithContext(ctx, "GET", tokenURL, nil)
if err != nil {
- return nil, fmt.Errorf("unable to create request: %w", err)
+ return nil, fmt.Errorf("unable to create token request: %w", err)
}
- req.Header.Set("Accept", "application/json")
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", copilotToken()))
+ tokenReq.Header.Set("Accept", "application/json")
+ tokenReq.Header.Set("Authorization", fmt.Sprintf("token %s", oauthToken))
+
+ // Use approved integration ID to bypass client check
+ tokenReq.Header.Set("Copilot-Integration-Id", "vscode-chat")
+ tokenReq.Header.Set("User-Agent", "GitHubCopilotChat/0.1")
client := &http.Client{}
- resp, err := client.Do(req)
+ tokenResp, err := client.Do(tokenReq)
+ if err != nil {
+ return nil, fmt.Errorf("unable to make token request: %w", err)
+ }
+ defer tokenResp.Body.Close() //nolint:errcheck
+
+ tokenBody, err := io.ReadAll(tokenResp.Body)
if err != nil {
- return nil, fmt.Errorf("unable to make http request: %w", err)
+ return nil, fmt.Errorf("unable to read token response body: %w", err)
}
- defer resp.Body.Close() //nolint:errcheck
- if resp.StatusCode != http.StatusOK {
- return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
+ if tokenResp.StatusCode != http.StatusOK {
+ return nil, fmt.Errorf("unexpected status code from token endpoint: %d", tokenResp.StatusCode)
}
- bts, err := io.ReadAll(resp.Body)
+ var tokenData APITokenResponse
+ if err := json.Unmarshal(tokenBody, &tokenData); err != nil {
+ return nil, fmt.Errorf("unable to unmarshal token response: %w", err)
+ }
+
+ // Convert to APIToken
+ expiresAt := time.Unix(tokenData.ExpiresAt, 0)
+ apiToken := APIToken{
+ APIKey: tokenData.Token,
+ ExpiresAt: expiresAt,
+ APIEndpoint: tokenData.Endpoints.API,
+ }
+
+ // Step 2: Use the dynamic endpoint from the token to fetch models
+ modelsURL := apiToken.APIEndpoint + "/models"
+ modelsReq, err := http.NewRequestWithContext(ctx, "GET", modelsURL, nil)
+ if err != nil {
+ return nil, fmt.Errorf("unable to create models request: %w", err)
+ }
+ modelsReq.Header.Set("Accept", "application/json")
+ modelsReq.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiToken.APIKey))
+ modelsReq.Header.Set("Copilot-Integration-Id", "vscode-chat")
+ modelsReq.Header.Set("User-Agent", "GitHubCopilotChat/0.1")
+
+ modelsResp, err := client.Do(modelsReq)
+ if err != nil {
+ return nil, fmt.Errorf("unable to make models request: %w", err)
+ }
+ defer modelsResp.Body.Close() //nolint:errcheck
+
+ modelsBody, err := io.ReadAll(modelsResp.Body)
if err != nil {
- return nil, fmt.Errorf("unable to read response body: %w", err)
+ return nil, fmt.Errorf("unable to read models response body: %w", err)
+ }
+
+ if modelsResp.StatusCode != http.StatusOK {
+ return nil, fmt.Errorf("unexpected status code from models endpoint: %d", modelsResp.StatusCode)
}
// for debugging
_ = os.MkdirAll("tmp", 0o700)
- _ = os.WriteFile("tmp/copilot-response.json", bts, 0o600)
+ _ = os.WriteFile("tmp/copilot-response.json", modelsBody, 0o600)
var data Response
- if err := json.Unmarshal(bts, &data); err != nil {
+ if err := json.Unmarshal(modelsBody, &data); err != nil {
return nil, fmt.Errorf("unable to unmarshal json: %w", err)
}
return data.Data, nil
@@ -13,7 +13,7 @@
"cost_per_1m_out": 0,
"cost_per_1m_in_cached": 0,
"cost_per_1m_out_cached": 0,
- "context_window": 144000,
+ "context_window": 200000,
"default_max_tokens": 16000,
"can_reason": false,
"supports_attachments": true,
@@ -26,7 +26,7 @@
"cost_per_1m_out": 0,
"cost_per_1m_in_cached": 0,
"cost_per_1m_out_cached": 0,
- "context_window": 160000,
+ "context_window": 200000,
"default_max_tokens": 16000,
"can_reason": false,
"supports_attachments": true,
@@ -52,7 +52,7 @@
"cost_per_1m_out": 0,
"cost_per_1m_in_cached": 0,
"cost_per_1m_out_cached": 0,
- "context_window": 144000,
+ "context_window": 200000,
"default_max_tokens": 16000,
"can_reason": false,
"supports_attachments": true,
@@ -71,6 +71,32 @@
"supports_attachments": true,
"options": {}
},
+ {
+ "id": "gemini-3-flash-preview",
+ "name": "Gemini 3 Flash (Preview)",
+ "cost_per_1m_in": 0,
+ "cost_per_1m_out": 0,
+ "cost_per_1m_in_cached": 0,
+ "cost_per_1m_out_cached": 0,
+ "context_window": 128000,
+ "default_max_tokens": 64000,
+ "can_reason": false,
+ "supports_attachments": true,
+ "options": {}
+ },
+ {
+ "id": "gemini-3-pro-preview",
+ "name": "Gemini 3 Pro (Preview)",
+ "cost_per_1m_in": 0,
+ "cost_per_1m_out": 0,
+ "cost_per_1m_in_cached": 0,
+ "cost_per_1m_out_cached": 0,
+ "context_window": 128000,
+ "default_max_tokens": 64000,
+ "can_reason": false,
+ "supports_attachments": true,
+ "options": {}
+ },
{
"id": "gpt-3.5-turbo-0613",
"name": "GPT 3.5 Turbo",
@@ -84,6 +110,19 @@
"supports_attachments": false,
"options": {}
},
+ {
+ "id": "gpt-4-0125-preview",
+ "name": "GPT 4 Turbo",
+ "cost_per_1m_in": 0,
+ "cost_per_1m_out": 0,
+ "cost_per_1m_in_cached": 0,
+ "cost_per_1m_out_cached": 0,
+ "context_window": 128000,
+ "default_max_tokens": 4096,
+ "can_reason": false,
+ "supports_attachments": false,
+ "options": {}
+ },
{
"id": "gpt-4-0613",
"name": "GPT 4",
@@ -110,6 +149,19 @@
"supports_attachments": true,
"options": {}
},
+ {
+ "id": "gpt-5-codex",
+ "name": "GPT-5-Codex (Preview)",
+ "cost_per_1m_in": 0,
+ "cost_per_1m_out": 0,
+ "cost_per_1m_in_cached": 0,
+ "cost_per_1m_out_cached": 0,
+ "context_window": 400000,
+ "default_max_tokens": 128000,
+ "can_reason": false,
+ "supports_attachments": true,
+ "options": {}
+ },
{
"id": "gpt-5-mini",
"name": "GPT-5 mini",
@@ -162,6 +214,19 @@
"supports_attachments": true,
"options": {}
},
+ {
+ "id": "gpt-5.1-codex-mini",
+ "name": "GPT-5.1-Codex-Mini",
+ "cost_per_1m_in": 0,
+ "cost_per_1m_out": 0,
+ "cost_per_1m_in_cached": 0,
+ "cost_per_1m_out_cached": 0,
+ "context_window": 400000,
+ "default_max_tokens": 128000,
+ "can_reason": false,
+ "supports_attachments": true,
+ "options": {}
+ },
{
"id": "gpt-5.2",
"name": "GPT-5.2",