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 | Description: "OpenAI GPT-5 via GitHub Copilot", | 834 | Description: "OpenAI GPT-5 via GitHub Copilot", |
| 835 | ContextLength: 200000, | 835 | ContextLength: 200000, |
| 836 | MaxCompletionTokens: 32768, | 836 | MaxCompletionTokens: 32768, |
| 837 | SupportedEndpoints: []string{"/chat/completions", "/responses"}, | ||
| 837 | }, | 838 | }, |
| 838 | { | 839 | { |
| 839 | ID: "gpt-5-mini", | 840 | ID: "gpt-5-mini", |
| @@ -845,6 +846,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines | |||
| 845 | Description: "OpenAI GPT-5 Mini via GitHub Copilot", | 846 | Description: "OpenAI GPT-5 Mini via GitHub Copilot", |
| 846 | ContextLength: 128000, | 847 | ContextLength: 128000, |
| 847 | MaxCompletionTokens: 16384, | 848 | MaxCompletionTokens: 16384, |
| 849 | SupportedEndpoints: []string{"/chat/completions", "/responses"}, | ||
| 848 | }, | 850 | }, |
| 849 | { | 851 | { |
| 850 | ID: "gpt-5-codex", | 852 | ID: "gpt-5-codex", |
| @@ -856,6 +858,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines | |||
| 856 | Description: "OpenAI GPT-5 Codex via GitHub Copilot", | 858 | Description: "OpenAI GPT-5 Codex via GitHub Copilot", |
| 857 | ContextLength: 200000, | 859 | ContextLength: 200000, |
| 858 | MaxCompletionTokens: 32768, | 860 | MaxCompletionTokens: 32768, |
| 861 | SupportedEndpoints: []string{"/responses"}, | ||
| 859 | }, | 862 | }, |
| 860 | { | 863 | { |
| 861 | ID: "gpt-5.1", | 864 | ID: "gpt-5.1", |
| @@ -867,6 +870,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines | |||
| 867 | Description: "OpenAI GPT-5.1 via GitHub Copilot", | 870 | Description: "OpenAI GPT-5.1 via GitHub Copilot", |
| 868 | ContextLength: 200000, | 871 | ContextLength: 200000, |
| 869 | MaxCompletionTokens: 32768, | 872 | MaxCompletionTokens: 32768, |
| 873 | SupportedEndpoints: []string{"/chat/completions", "/responses"}, | ||
| 870 | }, | 874 | }, |
| 871 | { | 875 | { |
| 872 | ID: "gpt-5.1-codex", | 876 | ID: "gpt-5.1-codex", |
| @@ -878,6 +882,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines | |||
| 878 | Description: "OpenAI GPT-5.1 Codex via GitHub Copilot", | 882 | Description: "OpenAI GPT-5.1 Codex via GitHub Copilot", |
| 879 | ContextLength: 200000, | 883 | ContextLength: 200000, |
| 880 | MaxCompletionTokens: 32768, | 884 | MaxCompletionTokens: 32768, |
| 885 | SupportedEndpoints: []string{"/responses"}, | ||
| 881 | }, | 886 | }, |
| 882 | { | 887 | { |
| 883 | ID: "gpt-5.1-codex-mini", | 888 | ID: "gpt-5.1-codex-mini", |
| @@ -889,6 +894,19 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines | |||
| 889 | Description: "OpenAI GPT-5.1 Codex Mini via GitHub Copilot", | 894 | Description: "OpenAI GPT-5.1 Codex Mini via GitHub Copilot", |
| 890 | ContextLength: 128000, | 895 | ContextLength: 128000, |
| 891 | MaxCompletionTokens: 16384, | 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 | ID: "gpt-5.2", | 912 | ID: "gpt-5.2", |
| @@ -900,6 +918,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines | |||
| 900 | Description: "OpenAI GPT-5.2 via GitHub Copilot", | 918 | Description: "OpenAI GPT-5.2 via GitHub Copilot", |
| 901 | ContextLength: 200000, | 919 | ContextLength: 200000, |
| 902 | MaxCompletionTokens: 32768, | 920 | MaxCompletionTokens: 32768, |
| 921 | SupportedEndpoints: []string{"/chat/completions", "/responses"}, | ||
| 903 | }, | 922 | }, |
| 904 | { | 923 | { |
| 905 | ID: "gpt-5.2-codex", | 924 | ID: "gpt-5.2-codex", |
| @@ -911,6 +930,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines | |||
| 911 | Description: "OpenAI GPT-5.2 Codex via GitHub Copilot", | 930 | Description: "OpenAI GPT-5.2 Codex via GitHub Copilot", |
| 912 | ContextLength: 200000, | 931 | ContextLength: 200000, |
| 913 | MaxCompletionTokens: 32768, | 932 | MaxCompletionTokens: 32768, |
| 933 | SupportedEndpoints: []string{"/responses"}, | ||
| 914 | }, | 934 | }, |
| 915 | { | 935 | { |
| 916 | ID: "claude-haiku-4.5", | 936 | ID: "claude-haiku-4.5", |
| @@ -922,6 +942,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines | |||
| 922 | Description: "Anthropic Claude Haiku 4.5 via GitHub Copilot", | 942 | Description: "Anthropic Claude Haiku 4.5 via GitHub Copilot", |
| 923 | ContextLength: 200000, | 943 | ContextLength: 200000, |
| 924 | MaxCompletionTokens: 64000, | 944 | MaxCompletionTokens: 64000, |
| 945 | SupportedEndpoints: []string{"/chat/completions"}, | ||
| 925 | }, | 946 | }, |
| 926 | { | 947 | { |
| 927 | ID: "claude-opus-4.1", | 948 | ID: "claude-opus-4.1", |
| @@ -933,6 +954,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines | |||
| 933 | Description: "Anthropic Claude Opus 4.1 via GitHub Copilot", | 954 | Description: "Anthropic Claude Opus 4.1 via GitHub Copilot", |
| 934 | ContextLength: 200000, | 955 | ContextLength: 200000, |
| 935 | MaxCompletionTokens: 32000, | 956 | MaxCompletionTokens: 32000, |
| 957 | SupportedEndpoints: []string{"/chat/completions"}, | ||
| 936 | }, | 958 | }, |
| 937 | { | 959 | { |
| 938 | ID: "claude-opus-4.5", | 960 | ID: "claude-opus-4.5", |
| @@ -944,6 +966,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines | |||
| 944 | Description: "Anthropic Claude Opus 4.5 via GitHub Copilot", | 966 | Description: "Anthropic Claude Opus 4.5 via GitHub Copilot", |
| 945 | ContextLength: 200000, | 967 | ContextLength: 200000, |
| 946 | MaxCompletionTokens: 64000, | 968 | MaxCompletionTokens: 64000, |
| 969 | SupportedEndpoints: []string{"/chat/completions"}, | ||
| 947 | }, | 970 | }, |
| 948 | { | 971 | { |
| 949 | ID: "claude-sonnet-4", | 972 | ID: "claude-sonnet-4", |
| @@ -955,6 +978,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines | |||
| 955 | Description: "Anthropic Claude Sonnet 4 via GitHub Copilot", | 978 | Description: "Anthropic Claude Sonnet 4 via GitHub Copilot", |
| 956 | ContextLength: 200000, | 979 | ContextLength: 200000, |
| 957 | MaxCompletionTokens: 64000, | 980 | MaxCompletionTokens: 64000, |
| 981 | SupportedEndpoints: []string{"/chat/completions"}, | ||
| 958 | }, | 982 | }, |
| 959 | { | 983 | { |
| 960 | ID: "claude-sonnet-4.5", | 984 | ID: "claude-sonnet-4.5", |
| @@ -966,6 +990,7 @@ func GetGitHubCopilotModels() []*ModelInfo { · 5 unmodified lines | |||
| 966 | Description: "Anthropic Claude Sonnet 4.5 via GitHub Copilot", | 990 | Description: "Anthropic Claude Sonnet 4.5 via GitHub Copilot", |
| 967 | ContextLength: 200000, | 991 | ContextLength: 200000, |
| 968 | MaxCompletionTokens: 64000, | 992 | MaxCompletionTokens: 64000, |
| 993 | SupportedEndpoints: []string{"/chat/completions"}, | ||
| 969 | }, | 994 | }, |
| 970 | { | 995 | { |
| 971 | ID: "gemini-2.5-pro", | 996 | ID: "gemini-2.5-pro", |
| @@ -979,13 +1004,24 @@ func GetGitHubCopilotModels() []*ModelInfo { · 7 unmodified lines | |||
| 979 | MaxCompletionTokens: 65536, | 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 | Object: "model", | 1019 | Object: "model", |
| 984 | Created: now, | 1020 | Created: now, |
| 985 | OwnedBy: "github-copilot", | 1021 | OwnedBy: "github-copilot", |
| 986 | Type: "github-copilot", | 1022 | Type: "github-copilot", |
| 987 | DisplayName: "Gemini 3 Pro", | 1023 | DisplayName: "Gemini 3 Flash (Preview)", |
| 988 | Description: "Google Gemini 3 Pro via GitHub Copilot", | 1024 | Description: "Google Gemini 3 Flash Preview via GitHub Copilot", |
| 989 | ContextLength: 1048576, | 1025 | ContextLength: 1048576, |
| 990 | MaxCompletionTokens: 65536, | 1026 | MaxCompletionTokens: 65536, |
| 991 | }, | 1027 | }, |
| @@ -1001,15 +1037,16 @@ func GetGitHubCopilotModels() []*ModelInfo { · 9 unmodified lines | |||
| 1001 | MaxCompletionTokens: 16384, | 1037 | MaxCompletionTokens: 16384, |
| 1002 | }, | 1038 | }, |
| 1003 | { | 1039 | { |
| 1004 | ID: "raptor-mini", | 1040 | ID: "oswe-vscode-prime", |
| 1005 | Object: "model", | 1041 | Object: "model", |
| 1006 | Created: now, | 1042 | Created: now, |
| 1007 | OwnedBy: "github-copilot", | 1043 | OwnedBy: "github-copilot", |
| 1008 | Type: "github-copilot", | 1044 | Type: "github-copilot", |
| 1009 | DisplayName: "Raptor Mini", | 1045 | DisplayName: "Raptor mini (Preview)", |
| 1010 | Description: "Raptor Mini via GitHub Copilot", | 1046 | Description: "Raptor mini via GitHub Copilot", |
| 1011 | ContextLength: 128000, | 1047 | ContextLength: 128000, |
| 1012 | MaxCompletionTokens: 16384, | 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 | MaxCompletionTokens int `json:"max_completion_tokens,omitempty"` | 47 | MaxCompletionTokens int `json:"max_completion_tokens,omitempty"` |
| 48 | // SupportedParameters lists supported parameters | 48 | // SupportedParameters lists supported parameters |
| 49 | SupportedParameters []string `json:"supported_parameters,omitempty"` | 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 | // Thinking holds provider-specific reasoning/thinking budget capabilities. | 53 | // Thinking holds provider-specific reasoning/thinking budget capabilities. |
| 52 | // This is optional and currently used for Gemini thinking budget normalization. | 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 | if len(model.SupportedParameters) > 0 { | 458 | if len(model.SupportedParameters) > 0 { |
| 457 | copyModel.SupportedParameters = append([]string(nil), model.SupportedParameters...) | 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 | return ©Model | 464 | return ©Model |
| 460 | } | 465 | } |
| 461 | 466 | ||
| @@ -968,6 +973,9 @@ func (r *ModelRegistry) convertModelToMap(model *ModelInfo, handlerType string) · 506 unmodified lines | |||
| 968 | if len(model.SupportedParameters) > 0 { | 973 | if len(model.SupportedParameters) > 0 { |
| 969 | result["supported_parameters"] = model.SupportedParameters | 974 | result["supported_parameters"] = model.SupportedParameters |
| 970 | } | 975 | } |
| 976 | if len(model.SupportedEndpoints) > 0 { | ||
| 977 | result["supported_endpoints"] = model.SupportedEndpoints | ||
| 978 | } | ||
| 971 | return result | 979 | return result |
| 972 | 980 | ||
| 973 | case "claude", "kiro", "antigravity": | 981 | case "claude", "kiro", "antigravity": |
| @@ -23,6 +23,7 @@ import ( · 22 unmodified lines | |||
| 23 | const ( | 23 | const ( |
| 24 | githubCopilotBaseURL = "https://api.githubcopilot.com" | 24 | githubCopilotBaseURL = "https://api.githubcopilot.com" |
| 25 | githubCopilotChatPath = "/chat/completions" | 25 | githubCopilotChatPath = "/chat/completions" |
| 26 | githubCopilotResponsesPath = "/responses" | ||
| 26 | githubCopilotAuthType = "github-copilot" | 27 | githubCopilotAuthType = "github-copilot" |
| 27 | githubCopilotTokenCacheTTL = 25 * time.Minute | 28 | githubCopilotTokenCacheTTL = 25 * time.Minute |
| 28 | // tokenExpiryBuffer is the time before expiry when we should refresh the token. | 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 | defer reporter.trackFailure(ctx, &err) | 107 | defer reporter.trackFailure(ctx, &err) |
| 107 | 108 | ||
| 108 | from := opts.SourceFormat | 109 | from := opts.SourceFormat |
| 110 | useResponses := useGitHubCopilotResponsesEndpoint(from) | ||
| 109 | to := sdktranslator.FromString("openai") | 111 | to := sdktranslator.FromString("openai") |
| 112 | if useResponses { | ||
| 113 | to = sdktranslator.FromString("openai-response") | ||
| 114 | } | ||
| 110 | originalPayload := bytes.Clone(req.Payload) | 115 | originalPayload := bytes.Clone(req.Payload) |
| 111 | if len(opts.OriginalRequest) > 0 { | 116 | if len(opts.OriginalRequest) > 0 { |
| 112 | originalPayload = bytes.Clone(opts.OriginalRequest) | 117 | originalPayload = bytes.Clone(opts.OriginalRequest) |
| @@ -117,7 +122,11 @@ func (e *GitHubCopilotExecutor) Execute(ctx context.Context, auth *cliproxyauth. · 4 unmodified lines | |||
| 117 | body = applyPayloadConfigWithRoot(e.cfg, req.Model, to.String(), "", body, originalTranslated) | 122 | body = applyPayloadConfigWithRoot(e.cfg, req.Model, to.String(), "", body, originalTranslated) |
| 118 | body, _ = sjson.SetBytes(body, "stream", false) | 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 | httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)) | 130 | httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)) |
| 122 | if err != nil { | 131 | if err != nil { |
| 123 | return resp, err | 132 | return resp, err |
| @@ -172,6 +181,9 @@ func (e *GitHubCopilotExecutor) Execute(ctx context.Context, auth *cliproxyauth. · 48 unmodified lines | |||
| 172 | appendAPIResponseChunk(ctx, e.cfg, data) | 181 | appendAPIResponseChunk(ctx, e.cfg, data) |
| 173 | 182 | ||
| 174 | detail := parseOpenAIUsage(data) | 183 | detail := parseOpenAIUsage(data) |
| 184 | if useResponses && detail.TotalTokens == 0 { | ||
| 185 | detail = parseOpenAIResponsesUsage(data) | ||
| 186 | } | ||
| 175 | if detail.TotalTokens > 0 { | 187 | if detail.TotalTokens > 0 { |
| 176 | reporter.publish(ctx, detail) | 188 | reporter.publish(ctx, detail) |
| 177 | } | 189 | } |
| @@ -194,7 +206,11 @@ func (e *GitHubCopilotExecutor) ExecuteStream(ctx context.Context, auth *cliprox · 16 unmodified lines | |||
| 194 | defer reporter.trackFailure(ctx, &err) | 206 | defer reporter.trackFailure(ctx, &err) |
| 195 | 207 | ||
| 196 | from := opts.SourceFormat | 208 | from := opts.SourceFormat |
| 209 | useResponses := useGitHubCopilotResponsesEndpoint(from) | ||
| 197 | to := sdktranslator.FromString("openai") | 210 | to := sdktranslator.FromString("openai") |
| 211 | if useResponses { | ||
| 212 | to = sdktranslator.FromString("openai-response") | ||
| 213 | } | ||
| 198 | originalPayload := bytes.Clone(req.Payload) | 214 | originalPayload := bytes.Clone(req.Payload) |
| 199 | if len(opts.OriginalRequest) > 0 { | 215 | if len(opts.OriginalRequest) > 0 { |
| 200 | originalPayload = bytes.Clone(opts.OriginalRequest) | 216 | originalPayload = bytes.Clone(opts.OriginalRequest) |
| @@ -205,9 +221,15 @@ func (e *GitHubCopilotExecutor) ExecuteStream(ctx context.Context, auth *cliprox · 4 unmodified lines | |||
| 205 | body = applyPayloadConfigWithRoot(e.cfg, req.Model, to.String(), "", body, originalTranslated) | 221 | body = applyPayloadConfigWithRoot(e.cfg, req.Model, to.String(), "", body, originalTranslated) |
| 206 | body, _ = sjson.SetBytes(body, "stream", true) | 222 | body, _ = sjson.SetBytes(body, "stream", true) |
| 207 | // Enable stream options for usage stats in stream | 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 | httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)) | 233 | httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)) |
| 212 | if err != nil { | 234 | if err != nil { |
| 213 | return nil, err | 235 | return nil, err |
| @@ -283,6 +305,10 @@ func (e *GitHubCopilotExecutor) ExecuteStream(ctx context.Context, auth *cliprox · 69 unmodified lines | |||
| 283 | } | 305 | } |
| 284 | if detail, ok := parseOpenAIStreamUsage(line); ok { | 306 | if detail, ok := parseOpenAIStreamUsage(line); ok { |
| 285 | reporter.publish(ctx, detail) | 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 | return body | 419 | return body |
| 394 | } | 420 | } |
| 395 | 421 | ||
| 422 | func useGitHubCopilotResponsesEndpoint(sourceFormat sdktranslator.Format) bool { | ||
| 423 | return sourceFormat.String() == "openai-response" | ||
| 424 | } | ||
| 425 | |||
| 396 | // isHTTPSuccess checks if the status code indicates success (2xx). | 426 | // isHTTPSuccess checks if the status code indicates success (2xx). |
| 397 | func isHTTPSuccess(statusCode int) bool { | 427 | func isHTTPSuccess(statusCode int) bool { |
| 398 | return statusCode >= 200 && statusCode < 300 | 428 | return statusCode >= 200 && statusCode < 300 |
| @@ -236,6 +236,54 @@ func parseOpenAIStreamUsage(line []byte) (usage.Detail, bool) { · 235 unmodified lines | |||
| 236 | return detail, true | 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 | func parseClaudeUsage(data []byte) usage.Detail { | 287 | func parseClaudeUsage(data []byte) usage.Detail { |
| 240 | usageNode := gjson.ParseBytes(data).Get("usage") | 288 | usageNode := gjson.ParseBytes(data).Get("usage") |
| 241 | if !usageNode.Exists() { | 289 | if !usageNode.Exists() { |