Enhance model definitions by including supported API endpoints for each model. This allows for better integration and usage tracking with the GitHub Copilot API.
Unified · Split
| @@ -834,6 +834,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 833 unmodified lines | ||
| 834 | 834 | Description: "OpenAI GPT-5 via GitHub Copilot", |
| 835 | 835 | ContextLength: 200000, |
| 836 | 836 | MaxCompletionTokens: 32768, |
| 837 | + SupportedEndpoints: []string{"/chat/completions", "/responses"}, | |
| 837 | 838 | }, |
| 838 | 839 | { |
| 839 | 840 | ID: "gpt-5-mini", |
| @@ -845,6 +846,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines | ||
| 845 | 846 | Description: "OpenAI GPT-5 Mini via GitHub Copilot", |
| 846 | 847 | ContextLength: 128000, |
| 847 | 848 | MaxCompletionTokens: 16384, |
| 849 | + SupportedEndpoints: []string{"/chat/completions", "/responses"}, | |
| 848 | 850 | }, |
| 849 | 851 | { |
| 850 | 852 | ID: "gpt-5-codex", |
| @@ -856,6 +858,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines | ||
| 856 | 858 | Description: "OpenAI GPT-5 Codex via GitHub Copilot", |
| 857 | 859 | ContextLength: 200000, |
| 858 | 860 | MaxCompletionTokens: 32768, |
| 861 | + SupportedEndpoints: []string{"/responses"}, | |
| 859 | 862 | }, |
| 860 | 863 | { |
| 861 | 864 | ID: "gpt-5.1", |
| @@ -867,6 +870,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines | ||
| 867 | 870 | Description: "OpenAI GPT-5.1 via GitHub Copilot", |
| 868 | 871 | ContextLength: 200000, |
| 869 | 872 | MaxCompletionTokens: 32768, |
| 873 | + SupportedEndpoints: []string{"/chat/completions", "/responses"}, | |
| 870 | 874 | }, |
| 871 | 875 | { |
| 872 | 876 | ID: "gpt-5.1-codex", |
| @@ -878,6 +882,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines | ||
| 878 | 882 | Description: "OpenAI GPT-5.1 Codex via GitHub Copilot", |
| 879 | 883 | ContextLength: 200000, |
| 880 | 884 | MaxCompletionTokens: 32768, |
| 885 | + SupportedEndpoints: []string{"/responses"}, | |
| 881 | 886 | }, |
| 882 | 887 | { |
| 883 | 888 | ID: "gpt-5.1-codex-mini", |
| @@ -889,6 +894,19 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines | ||
| 889 | 894 | Description: "OpenAI GPT-5.1 Codex Mini via GitHub Copilot", |
| 890 | 895 | ContextLength: 128000, |
| 891 | 896 | MaxCompletionTokens: 16384, |
| 897 | + SupportedEndpoints: []string{"/responses"}, | |
| 898 | + }, | |
| 899 | + { | |
| 900 | + ID: "gpt-5.1-codex-max", | |
| 901 | + Object: "model", | |
| 902 | + Created: now, | |
| 903 | + OwnedBy: "github-copilot", | |
| 904 | + Type: "github-copilot", | |
| 905 | + DisplayName: "GPT-5.1 Codex Max", | |
| 906 | + Description: "OpenAI GPT-5.1 Codex Max via GitHub Copilot", | |
| 907 | + ContextLength: 200000, | |
| 908 | + MaxCompletionTokens: 32768, | |
| 909 | + SupportedEndpoints: []string{"/responses"}, | |
| 892 | 910 | }, |
| 893 | 911 | { |
| 894 | 912 | ID: "gpt-5.2", |
| @@ -900,6 +918,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines | ||
| 900 | 918 | Description: "OpenAI GPT-5.2 via GitHub Copilot", |
| 901 | 919 | ContextLength: 200000, |
| 902 | 920 | MaxCompletionTokens: 32768, |
| 921 | + SupportedEndpoints: []string{"/chat/completions", "/responses"}, | |
| 903 | 922 | }, |
| 904 | 923 | { |
| 905 | 924 | ID: "gpt-5.2-codex", |
| @@ -911,6 +930,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines | ||
| 911 | 930 | Description: "OpenAI GPT-5.2 Codex via GitHub Copilot", |
| 912 | 931 | ContextLength: 200000, |
| 913 | 932 | MaxCompletionTokens: 32768, |
| 933 | + SupportedEndpoints: []string{"/responses"}, | |
| 914 | 934 | }, |
| 915 | 935 | { |
| 916 | 936 | ID: "claude-haiku-4.5", |
| @@ -922,6 +942,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines | ||
| 922 | 942 | Description: "Anthropic Claude Haiku 4.5 via GitHub Copilot", |
| 923 | 943 | ContextLength: 200000, |
| 924 | 944 | MaxCompletionTokens: 64000, |
| 945 | + SupportedEndpoints: []string{"/chat/completions"}, | |
| 925 | 946 | }, |
| 926 | 947 | { |
| 927 | 948 | ID: "claude-opus-4.1", |
| @@ -933,6 +954,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines | ||
| 933 | 954 | Description: "Anthropic Claude Opus 4.1 via GitHub Copilot", |
| 934 | 955 | ContextLength: 200000, |
| 935 | 956 | MaxCompletionTokens: 32000, |
| 957 | + SupportedEndpoints: []string{"/chat/completions"}, | |
| 936 | 958 | }, |
| 937 | 959 | { |
| 938 | 960 | ID: "claude-opus-4.5", |
| @@ -944,6 +966,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines | ||
| 944 | 966 | Description: "Anthropic Claude Opus 4.5 via GitHub Copilot", |
| 945 | 967 | ContextLength: 200000, |
| 946 | 968 | MaxCompletionTokens: 64000, |
| 969 | + SupportedEndpoints: []string{"/chat/completions"}, | |
| 947 | 970 | }, |
| 948 | 971 | { |
| 949 | 972 | ID: "claude-sonnet-4", |
| @@ -955,6 +978,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines | ||
| 955 | 978 | Description: "Anthropic Claude Sonnet 4 via GitHub Copilot", |
| 956 | 979 | ContextLength: 200000, |
| 957 | 980 | MaxCompletionTokens: 64000, |
| 981 | + SupportedEndpoints: []string{"/chat/completions"}, | |
| 958 | 982 | }, |
| 959 | 983 | { |
| 960 | 984 | ID: "claude-sonnet-4.5", |
| @@ -966,6 +990,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines | ||
| 966 | 990 | Description: "Anthropic Claude Sonnet 4.5 via GitHub Copilot", |
| 967 | 991 | ContextLength: 200000, |
| 968 | 992 | MaxCompletionTokens: 64000, |
| 993 | + SupportedEndpoints: []string{"/chat/completions"}, | |
| 969 | 994 | }, |
| 970 | 995 | { |
| 971 | 996 | ID: "gemini-2.5-pro", |
| @@ -979,13 +1004,24 @@ func GetGitHubCopilotModels() []*ModelInfo { · 7 unmodified lines | ||
| 979 | 1004 | MaxCompletionTokens: 65536, |
| 980 | 1005 | }, |
| 981 | 1006 | { |
| 982 | − ID: "gemini-3-pro", | |
| 1007 | + ID: "gemini-3-pro-preview", | |
| 1008 | + Object: "model", | |
| 1009 | + Created: now, | |
| 1010 | + OwnedBy: "github-copilot", | |
| 1011 | + Type: "github-copilot", | |
| 1012 | + DisplayName: "Gemini 3 Pro (Preview)", | |
| 1013 | + Description: "Google Gemini 3 Pro Preview via GitHub Copilot", | |
| 1014 | + ContextLength: 1048576, | |
| 1015 | + MaxCompletionTokens: 65536, | |
| 1016 | + }, | |
| 1017 | + { | |
| 1018 | + ID: "gemini-3-flash-preview", | |
| 983 | 1019 | Object: "model", |
| 984 | 1020 | Created: now, |
| 985 | 1021 | OwnedBy: "github-copilot", |
| 986 | 1022 | Type: "github-copilot", |
| 987 | − DisplayName: "Gemini 3 Pro", | |
| 988 | − Description: "Google Gemini 3 Pro via GitHub Copilot", | |
| 1023 | + DisplayName: "Gemini 3 Flash (Preview)", | |
| 1024 | + Description: "Google Gemini 3 Flash Preview via GitHub Copilot", | |
| 989 | 1025 | ContextLength: 1048576, |
| 990 | 1026 | MaxCompletionTokens: 65536, |
| 991 | 1027 | }, |
| @@ -1001,15 +1037,16 @@ func GetGitHubCopilotModels() []*ModelInfo { · 9 unmodified lines | ||
| 1001 | 1037 | MaxCompletionTokens: 16384, |
| 1002 | 1038 | }, |
| 1003 | 1039 | { |
| 1004 | − ID: "raptor-mini", | |
| 1040 | + ID: "oswe-vscode-prime", | |
| 1005 | 1041 | Object: "model", |
| 1006 | 1042 | Created: now, |
| 1007 | 1043 | OwnedBy: "github-copilot", |
| 1008 | 1044 | Type: "github-copilot", |
| 1009 | − DisplayName: "Raptor Mini", | |
| 1010 | − Description: "Raptor Mini via GitHub Copilot", | |
| 1045 | + DisplayName: "Raptor mini (Preview)", | |
| 1046 | + Description: "Raptor mini via GitHub Copilot", | |
| 1011 | 1047 | ContextLength: 128000, |
| 1012 | 1048 | MaxCompletionTokens: 16384, |
| 1049 | + SupportedEndpoints: []string{"/chat/completions", "/responses"}, | |
| 1013 | 1050 | }, |
| 1014 | 1051 | } |
| 1015 | 1052 | } |
| @@ -47,6 +47,8 @@ type ModelInfo struct { · 46 unmodified lines | ||
| 47 | 47 | MaxCompletionTokens int `json:"max_completion_tokens,omitempty"` |
| 48 | 48 | // SupportedParameters lists supported parameters |
| 49 | 49 | SupportedParameters []string `json:"supported_parameters,omitempty"` |
| 50 | + // SupportedEndpoints lists supported API endpoints (e.g., "/chat/completions", "/responses"). | |
| 51 | + SupportedEndpoints []string `json:"supported_endpoints,omitempty"` | |
| 50 | 52 | |
| 51 | 53 | // Thinking holds provider-specific reasoning/thinking budget capabilities. |
| 52 | 54 | // This is optional and currently used for Gemini thinking budget normalization. |
| @@ -456,6 +458,9 @@ func cloneModelInfo(model *ModelInfo) *ModelInfo { · 403 unmodified lines | ||
| 456 | 458 | if len(model.SupportedParameters) > 0 { |
| 457 | 459 | copyModel.SupportedParameters = append([]string(nil), model.SupportedParameters...) |
| 458 | 460 | } |
| 461 | + if len(model.SupportedEndpoints) > 0 { | |
| 462 | + copyModel.SupportedEndpoints = append([]string(nil), model.SupportedEndpoints...) | |
| 463 | + } | |
| 459 | 464 | return ©Model |
| 460 | 465 | } |
| 461 | 466 | |
| @@ -968,6 +973,9 @@ func (r *ModelRegistry) convertModelToMap(model *ModelInfo, handlerType string) · 506 unmodified lines | ||
| 968 | 973 | if len(model.SupportedParameters) > 0 { |
| 969 | 974 | result["supported_parameters"] = model.SupportedParameters |
| 970 | 975 | } |
| 976 | + if len(model.SupportedEndpoints) > 0 { | |
| 977 | + result["supported_endpoints"] = model.SupportedEndpoints | |
| 978 | + } | |
| 971 | 979 | return result |
| 972 | 980 | |
| 973 | 981 | case "claude", "kiro", "antigravity": |
| @@ -23,6 +23,7 @@ import ( · 22 unmodified lines | ||
| 23 | 23 | const ( |
| 24 | 24 | githubCopilotBaseURL = "https://api.githubcopilot.com" |
| 25 | 25 | githubCopilotChatPath = "/chat/completions" |
| 26 | + githubCopilotResponsesPath = "/responses" | |
| 26 | 27 | githubCopilotAuthType = "github-copilot" |
| 27 | 28 | githubCopilotTokenCacheTTL = 25 * time.Minute |
| 28 | 29 | // tokenExpiryBuffer is the time before expiry when we should refresh the token. |
| @@ -106,7 +107,11 @@ func (e *GitHubCopilotExecutor) Execute(ctx context.Context, auth *cliproxyauth. · 77 unmodified lines | ||
| 106 | 107 | defer reporter.trackFailure(ctx, &err) |
| 107 | 108 | |
| 108 | 109 | from := opts.SourceFormat |
| 110 | + useResponses := useGitHubCopilotResponsesEndpoint(from) | |
| 109 | 111 | to := sdktranslator.FromString("openai") |
| 112 | + if useResponses { | |
| 113 | + to = sdktranslator.FromString("openai-response") | |
| 114 | + } | |
| 110 | 115 | originalPayload := bytes.Clone(req.Payload) |
| 111 | 116 | if len(opts.OriginalRequest) > 0 { |
| 112 | 117 | originalPayload = bytes.Clone(opts.OriginalRequest) |
| @@ -117,7 +122,11 @@ func (e *GitHubCopilotExecutor) Execute(ctx context.Context, auth *cliproxyauth. · 4 unmodified lines | ||
| 117 | 122 | body = applyPayloadConfigWithRoot(e.cfg, req.Model, to.String(), "", body, originalTranslated) |
| 118 | 123 | body, _ = sjson.SetBytes(body, "stream", false) |
| 119 | 124 | |
| 120 | − url := githubCopilotBaseURL + githubCopilotChatPath | |
| 125 | + path := githubCopilotChatPath | |
| 126 | + if useResponses { | |
| 127 | + path = githubCopilotResponsesPath | |
| 128 | + } | |
| 129 | + url := githubCopilotBaseURL + path | |
| 121 | 130 | httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)) |
| 122 | 131 | if err != nil { |
| 123 | 132 | return resp, err |
| @@ -172,6 +181,9 @@ func (e *GitHubCopilotExecutor) Execute(ctx context.Context, auth *cliproxyauth. · 48 unmodified lines | ||
| 172 | 181 | appendAPIResponseChunk(ctx, e.cfg, data) |
| 173 | 182 | |
| 174 | 183 | detail := parseOpenAIUsage(data) |
| 184 | + if useResponses && detail.TotalTokens == 0 { | |
| 185 | + detail = parseOpenAIResponsesUsage(data) | |
| 186 | + } | |
| 175 | 187 | if detail.TotalTokens > 0 { |
| 176 | 188 | reporter.publish(ctx, detail) |
| 177 | 189 | } |
| @@ -194,7 +206,11 @@ func (e *GitHubCopilotExecutor) ExecuteStream(ctx context.Context, auth *cliprox · 16 unmodified lines | ||
| 194 | 206 | defer reporter.trackFailure(ctx, &err) |
| 195 | 207 | |
| 196 | 208 | from := opts.SourceFormat |
| 209 | + useResponses := useGitHubCopilotResponsesEndpoint(from) | |
| 197 | 210 | to := sdktranslator.FromString("openai") |
| 211 | + if useResponses { | |
| 212 | + to = sdktranslator.FromString("openai-response") | |
| 213 | + } | |
| 198 | 214 | originalPayload := bytes.Clone(req.Payload) |
| 199 | 215 | if len(opts.OriginalRequest) > 0 { |
| 200 | 216 | originalPayload = bytes.Clone(opts.OriginalRequest) |
| @@ -205,9 +221,15 @@ func (e *GitHubCopilotExecutor) ExecuteStream(ctx context.Context, auth *cliprox · 4 unmodified lines | ||
| 205 | 221 | body = applyPayloadConfigWithRoot(e.cfg, req.Model, to.String(), "", body, originalTranslated) |
| 206 | 222 | body, _ = sjson.SetBytes(body, "stream", true) |
| 207 | 223 | // Enable stream options for usage stats in stream |
| 208 | − body, _ = sjson.SetBytes(body, "stream_options.include_usage", true) | |
| 224 | + if !useResponses { | |
| 225 | + body, _ = sjson.SetBytes(body, "stream_options.include_usage", true) | |
| 226 | + } | |
| 209 | 227 | |
| 210 | − url := githubCopilotBaseURL + githubCopilotChatPath | |
| 228 | + path := githubCopilotChatPath | |
| 229 | + if useResponses { | |
| 230 | + path = githubCopilotResponsesPath | |
| 231 | + } | |
| 232 | + url := githubCopilotBaseURL + path | |
| 211 | 233 | httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)) |
| 212 | 234 | if err != nil { |
| 213 | 235 | return nil, err |
| @@ -283,6 +305,10 @@ func (e *GitHubCopilotExecutor) ExecuteStream(ctx context.Context, auth *cliprox · 69 unmodified lines | ||
| 283 | 305 | } |
| 284 | 306 | if detail, ok := parseOpenAIStreamUsage(line); ok { |
| 285 | 307 | reporter.publish(ctx, detail) |
| 308 | + } else if useResponses { | |
| 309 | + if detail, ok := parseOpenAIResponsesStreamUsage(line); ok { | |
| 310 | + reporter.publish(ctx, detail) | |
| 311 | + } | |
| 286 | 312 | } |
| 287 | 313 | } |
| 288 | 314 | |
| @@ -393,6 +419,10 @@ func (e *GitHubCopilotExecutor) normalizeModel(_ string, body []byte) []byte { · 104 unmodified lines | ||
| 393 | 419 | return body |
| 394 | 420 | } |
| 395 | 421 | |
| 422 | +func useGitHubCopilotResponsesEndpoint(sourceFormat sdktranslator.Format) bool { | |
| 423 | + return sourceFormat.String() == "openai-response" | |
| 424 | +} | |
| 425 | + | |
| 396 | 426 | // isHTTPSuccess checks if the status code indicates success (2xx). |
| 397 | 427 | func isHTTPSuccess(statusCode int) bool { |
| 398 | 428 | return statusCode >= 200 && statusCode < 300 |
| @@ -236,6 +236,54 @@ func parseOpenAIStreamUsage(line []byte) (usage.Detail, bool) { · 235 unmodified lines | ||
| 236 | 236 | return detail, true |
| 237 | 237 | } |
| 238 | 238 | |
| 239 | +func parseOpenAIResponsesUsage(data []byte) usage.Detail { | |
| 240 | + usageNode := gjson.ParseBytes(data).Get("usage") | |
| 241 | + if !usageNode.Exists() { | |
| 242 | + return usage.Detail{} | |
| 243 | + } | |
| 244 | + detail := usage.Detail{ | |
| 245 | + InputTokens: usageNode.Get("input_tokens").Int(), | |
| 246 | + OutputTokens: usageNode.Get("output_tokens").Int(), | |
| 247 | + TotalTokens: usageNode.Get("total_tokens").Int(), | |
| 248 | + } | |
| 249 | + if detail.TotalTokens == 0 { | |
| 250 | + detail.TotalTokens = detail.InputTokens + detail.OutputTokens | |
| 251 | + } | |
| 252 | + if cached := usageNode.Get("input_tokens_details.cached_tokens"); cached.Exists() { | |
| 253 | + detail.CachedTokens = cached.Int() | |
| 254 | + } | |
| 255 | + if reasoning := usageNode.Get("output_tokens_details.reasoning_tokens"); reasoning.Exists() { | |
| 256 | + detail.ReasoningTokens = reasoning.Int() | |
| 257 | + } | |
| 258 | + return detail | |
| 259 | +} | |
| 260 | + | |
| 261 | +func parseOpenAIResponsesStreamUsage(line []byte) (usage.Detail, bool) { | |
| 262 | + payload := jsonPayload(line) | |
| 263 | + if len(payload) == 0 || !gjson.ValidBytes(payload) { | |
| 264 | + return usage.Detail{}, false | |
| 265 | + } | |
| 266 | + usageNode := gjson.GetBytes(payload, "usage") | |
| 267 | + if !usageNode.Exists() { | |
| 268 | + return usage.Detail{}, false | |
| 269 | + } | |
| 270 | + detail := usage.Detail{ | |
| 271 | + InputTokens: usageNode.Get("input_tokens").Int(), | |
| 272 | + OutputTokens: usageNode.Get("output_tokens").Int(), | |
| 273 | + TotalTokens: usageNode.Get("total_tokens").Int(), | |
| 274 | + } | |
| 275 | + if detail.TotalTokens == 0 { | |
| 276 | + detail.TotalTokens = detail.InputTokens + detail.OutputTokens | |
| 277 | + } | |
| 278 | + if cached := usageNode.Get("input_tokens_details.cached_tokens"); cached.Exists() { | |
| 279 | + detail.CachedTokens = cached.Int() | |
| 280 | + } | |
| 281 | + if reasoning := usageNode.Get("output_tokens_details.reasoning_tokens"); reasoning.Exists() { | |
| 282 | + detail.ReasoningTokens = reasoning.Int() | |
| 283 | + } | |
| 284 | + return detail, true | |
| 285 | +} | |
| 286 | + | |
| 239 | 287 | func parseClaudeUsage(data []byte) usage.Detail { |
| 240 | 288 | usageNode := gjson.ParseBytes(data).Get("usage") |
| 241 | 289 | if !usageNode.Exists() { |