History

feat(registry): add supported endpoints for GitHub Copilot models

Enhance model definitions by including supported API endpoints for each model. This allows for better integration and usage tracking with the GitHub Copilot API.

ChrAlpha committed 10 months ago

Commit 0ffcce31 parent f4fcfc54 files · +132 −9Browse files

Unified · Split

4 files
  1. internal/registry/model_definitions.go +43 −6
  2. internal/registry/model_registry.go +8 −0
  3. internal/runtime/executor/github_copilot_executor.go +33 −3
  4. internal/runtime/executor/usage_helpers.go +48 −0
internal/registry/model_definitions.go+43−6
@@ -834,6 +834,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 833 unmodified lines
834834 Description: "OpenAI GPT-5 via GitHub Copilot",
835835 ContextLength: 200000,
836836 MaxCompletionTokens: 32768,
837+ SupportedEndpoints: []string{"/chat/completions", "/responses"},
837838 },
838839 {
839840 ID: "gpt-5-mini",
@@ -845,6 +846,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines
845846 Description: "OpenAI GPT-5 Mini via GitHub Copilot",
846847 ContextLength: 128000,
847848 MaxCompletionTokens: 16384,
849+ SupportedEndpoints: []string{"/chat/completions", "/responses"},
848850 },
849851 {
850852 ID: "gpt-5-codex",
@@ -856,6 +858,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines
856858 Description: "OpenAI GPT-5 Codex via GitHub Copilot",
857859 ContextLength: 200000,
858860 MaxCompletionTokens: 32768,
861+ SupportedEndpoints: []string{"/responses"},
859862 },
860863 {
861864 ID: "gpt-5.1",
@@ -867,6 +870,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines
867870 Description: "OpenAI GPT-5.1 via GitHub Copilot",
868871 ContextLength: 200000,
869872 MaxCompletionTokens: 32768,
873+ SupportedEndpoints: []string{"/chat/completions", "/responses"},
870874 },
871875 {
872876 ID: "gpt-5.1-codex",
@@ -878,6 +882,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines
878882 Description: "OpenAI GPT-5.1 Codex via GitHub Copilot",
879883 ContextLength: 200000,
880884 MaxCompletionTokens: 32768,
885+ SupportedEndpoints: []string{"/responses"},
881886 },
882887 {
883888 ID: "gpt-5.1-codex-mini",
@@ -889,6 +894,19 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines
889894 Description: "OpenAI GPT-5.1 Codex Mini via GitHub Copilot",
890895 ContextLength: 128000,
891896 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"},
892910 },
893911 {
894912 ID: "gpt-5.2",
@@ -900,6 +918,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines
900918 Description: "OpenAI GPT-5.2 via GitHub Copilot",
901919 ContextLength: 200000,
902920 MaxCompletionTokens: 32768,
921+ SupportedEndpoints: []string{"/chat/completions", "/responses"},
903922 },
904923 {
905924 ID: "gpt-5.2-codex",
@@ -911,6 +930,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines
911930 Description: "OpenAI GPT-5.2 Codex via GitHub Copilot",
912931 ContextLength: 200000,
913932 MaxCompletionTokens: 32768,
933+ SupportedEndpoints: []string{"/responses"},
914934 },
915935 {
916936 ID: "claude-haiku-4.5",
@@ -922,6 +942,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines
922942 Description: "Anthropic Claude Haiku 4.5 via GitHub Copilot",
923943 ContextLength: 200000,
924944 MaxCompletionTokens: 64000,
945+ SupportedEndpoints: []string{"/chat/completions"},
925946 },
926947 {
927948 ID: "claude-opus-4.1",
@@ -933,6 +954,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines
933954 Description: "Anthropic Claude Opus 4.1 via GitHub Copilot",
934955 ContextLength: 200000,
935956 MaxCompletionTokens: 32000,
957+ SupportedEndpoints: []string{"/chat/completions"},
936958 },
937959 {
938960 ID: "claude-opus-4.5",
@@ -944,6 +966,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines
944966 Description: "Anthropic Claude Opus 4.5 via GitHub Copilot",
945967 ContextLength: 200000,
946968 MaxCompletionTokens: 64000,
969+ SupportedEndpoints: []string{"/chat/completions"},
947970 },
948971 {
949972 ID: "claude-sonnet-4",
@@ -955,6 +978,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines
955978 Description: "Anthropic Claude Sonnet 4 via GitHub Copilot",
956979 ContextLength: 200000,
957980 MaxCompletionTokens: 64000,
981+ SupportedEndpoints: []string{"/chat/completions"},
958982 },
959983 {
960984 ID: "claude-sonnet-4.5",
@@ -966,6 +990,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines
966990 Description: "Anthropic Claude Sonnet 4.5 via GitHub Copilot",
967991 ContextLength: 200000,
968992 MaxCompletionTokens: 64000,
993+ SupportedEndpoints: []string{"/chat/completions"},
969994 },
970995 {
971996 ID: "gemini-2.5-pro",
@@ -979,13 +1004,24 @@ func GetGitHubCopilotModels() []*ModelInfo { · 7 unmodified lines
9791004 MaxCompletionTokens: 65536,
9801005 },
9811006 {
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",
9831019 Object: "model",
9841020 Created: now,
9851021 OwnedBy: "github-copilot",
9861022 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",
9891025 ContextLength: 1048576,
9901026 MaxCompletionTokens: 65536,
9911027 },
@@ -1001,15 +1037,16 @@ func GetGitHubCopilotModels() []*ModelInfo { · 9 unmodified lines
10011037 MaxCompletionTokens: 16384,
10021038 },
10031039 {
1004− ID: "raptor-mini",
1040+ ID: "oswe-vscode-prime",
10051041 Object: "model",
10061042 Created: now,
10071043 OwnedBy: "github-copilot",
10081044 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",
10111047 ContextLength: 128000,
10121048 MaxCompletionTokens: 16384,
1049+ SupportedEndpoints: []string{"/chat/completions", "/responses"},
10131050 },
10141051 }
10151052 }
internal/registry/model_registry.go+8−0
@@ -47,6 +47,8 @@ type ModelInfo struct { · 46 unmodified lines
4747 MaxCompletionTokens int `json:"max_completion_tokens,omitempty"`
4848 // SupportedParameters lists supported parameters
4949 SupportedParameters []string `json:"supported_parameters,omitempty"`
50+ // SupportedEndpoints lists supported API endpoints (e.g., "/chat/completions", "/responses").
51+ SupportedEndpoints []string `json:"supported_endpoints,omitempty"`
5052
5153 // Thinking holds provider-specific reasoning/thinking budget capabilities.
5254 // This is optional and currently used for Gemini thinking budget normalization.
@@ -456,6 +458,9 @@ func cloneModelInfo(model *ModelInfo) *ModelInfo { · 403 unmodified lines
456458 if len(model.SupportedParameters) > 0 {
457459 copyModel.SupportedParameters = append([]string(nil), model.SupportedParameters...)
458460 }
461+ if len(model.SupportedEndpoints) > 0 {
462+ copyModel.SupportedEndpoints = append([]string(nil), model.SupportedEndpoints...)
463+ }
459464 return &copyModel
460465 }
461466
@@ -968,6 +973,9 @@ func (r *ModelRegistry) convertModelToMap(model *ModelInfo, handlerType string) · 506 unmodified lines
968973 if len(model.SupportedParameters) > 0 {
969974 result["supported_parameters"] = model.SupportedParameters
970975 }
976+ if len(model.SupportedEndpoints) > 0 {
977+ result["supported_endpoints"] = model.SupportedEndpoints
978+ }
971979 return result
972980
973981 case "claude", "kiro", "antigravity":
internal/runtime/executor/github_copilot_executor.go+33−3
@@ -23,6 +23,7 @@ import ( · 22 unmodified lines
2323 const (
2424 githubCopilotBaseURL = "https://api.githubcopilot.com"
2525 githubCopilotChatPath = "/chat/completions"
26+ githubCopilotResponsesPath = "/responses"
2627 githubCopilotAuthType = "github-copilot"
2728 githubCopilotTokenCacheTTL = 25 * time.Minute
2829 // 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
106107 defer reporter.trackFailure(ctx, &err)
107108
108109 from := opts.SourceFormat
110+ useResponses := useGitHubCopilotResponsesEndpoint(from)
109111 to := sdktranslator.FromString("openai")
112+ if useResponses {
113+ to = sdktranslator.FromString("openai-response")
114+ }
110115 originalPayload := bytes.Clone(req.Payload)
111116 if len(opts.OriginalRequest) > 0 {
112117 originalPayload = bytes.Clone(opts.OriginalRequest)
@@ -117,7 +122,11 @@ func (e *GitHubCopilotExecutor) Execute(ctx context.Context, auth *cliproxyauth. · 4 unmodified lines
117122 body = applyPayloadConfigWithRoot(e.cfg, req.Model, to.String(), "", body, originalTranslated)
118123 body, _ = sjson.SetBytes(body, "stream", false)
119124
120− url := githubCopilotBaseURL + githubCopilotChatPath
125+ path := githubCopilotChatPath
126+ if useResponses {
127+ path = githubCopilotResponsesPath
128+ }
129+ url := githubCopilotBaseURL + path
121130 httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
122131 if err != nil {
123132 return resp, err
@@ -172,6 +181,9 @@ func (e *GitHubCopilotExecutor) Execute(ctx context.Context, auth *cliproxyauth. · 48 unmodified lines
172181 appendAPIResponseChunk(ctx, e.cfg, data)
173182
174183 detail := parseOpenAIUsage(data)
184+ if useResponses && detail.TotalTokens == 0 {
185+ detail = parseOpenAIResponsesUsage(data)
186+ }
175187 if detail.TotalTokens > 0 {
176188 reporter.publish(ctx, detail)
177189 }
@@ -194,7 +206,11 @@ func (e *GitHubCopilotExecutor) ExecuteStream(ctx context.Context, auth *cliprox · 16 unmodified lines
194206 defer reporter.trackFailure(ctx, &err)
195207
196208 from := opts.SourceFormat
209+ useResponses := useGitHubCopilotResponsesEndpoint(from)
197210 to := sdktranslator.FromString("openai")
211+ if useResponses {
212+ to = sdktranslator.FromString("openai-response")
213+ }
198214 originalPayload := bytes.Clone(req.Payload)
199215 if len(opts.OriginalRequest) > 0 {
200216 originalPayload = bytes.Clone(opts.OriginalRequest)
@@ -205,9 +221,15 @@ func (e *GitHubCopilotExecutor) ExecuteStream(ctx context.Context, auth *cliprox · 4 unmodified lines
205221 body = applyPayloadConfigWithRoot(e.cfg, req.Model, to.String(), "", body, originalTranslated)
206222 body, _ = sjson.SetBytes(body, "stream", true)
207223 // 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+ }
209227
210− url := githubCopilotBaseURL + githubCopilotChatPath
228+ path := githubCopilotChatPath
229+ if useResponses {
230+ path = githubCopilotResponsesPath
231+ }
232+ url := githubCopilotBaseURL + path
211233 httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
212234 if err != nil {
213235 return nil, err
@@ -283,6 +305,10 @@ func (e *GitHubCopilotExecutor) ExecuteStream(ctx context.Context, auth *cliprox · 69 unmodified lines
283305 }
284306 if detail, ok := parseOpenAIStreamUsage(line); ok {
285307 reporter.publish(ctx, detail)
308+ } else if useResponses {
309+ if detail, ok := parseOpenAIResponsesStreamUsage(line); ok {
310+ reporter.publish(ctx, detail)
311+ }
286312 }
287313 }
288314
@@ -393,6 +419,10 @@ func (e *GitHubCopilotExecutor) normalizeModel(_ string, body []byte) []byte { · 104 unmodified lines
393419 return body
394420 }
395421
422+func useGitHubCopilotResponsesEndpoint(sourceFormat sdktranslator.Format) bool {
423+ return sourceFormat.String() == "openai-response"
424+}
425+
396426 // isHTTPSuccess checks if the status code indicates success (2xx).
397427 func isHTTPSuccess(statusCode int) bool {
398428 return statusCode >= 200 && statusCode < 300
internal/runtime/executor/usage_helpers.go+48−0
@@ -236,6 +236,54 @@ func parseOpenAIStreamUsage(line []byte) (usage.Detail, bool) { · 235 unmodified lines
236236 return detail, true
237237 }
238238
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+
239287 func parseClaudeUsage(data []byte) usage.Detail {
240288 usageNode := gjson.ParseBytes(data).Get("usage")
241289 if !usageNode.Exists() {

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t