-
Notifications
You must be signed in to change notification settings - Fork 0
fix(daemon): migrate codex usage API to wham endpoint #267
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -160,34 +160,35 @@ func loadCodexAuth() (*codexAuthData, error) { | |||||||||||||||||||||
| }, nil | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // codexUsageResponse maps the Codex usage API response | ||||||||||||||||||||||
| type codexUsageResponse struct { | ||||||||||||||||||||||
| RateLimits codexRateLimitSnapshot `json:"rateLimits"` | ||||||||||||||||||||||
| // whamUsageResponse maps the response from chatgpt.com/backend-api/wham/usage | ||||||||||||||||||||||
| type whamUsageResponse struct { | ||||||||||||||||||||||
| PlanType string `json:"plan_type"` | ||||||||||||||||||||||
| RateLimit *whamRateLimitCategory `json:"rate_limit"` | ||||||||||||||||||||||
| CodeReviewRateLimit *whamRateLimitCategory `json:"code_review_rate_limit"` | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| type codexRateLimitSnapshot struct { | ||||||||||||||||||||||
| Plan string `json:"plan"` | ||||||||||||||||||||||
| RateLimitWindows []codexRateLimitWindowRaw `json:"rateLimitWindows"` | ||||||||||||||||||||||
| type whamRateLimitCategory struct { | ||||||||||||||||||||||
| Allowed bool `json:"allowed"` | ||||||||||||||||||||||
| LimitReached bool `json:"limit_reached"` | ||||||||||||||||||||||
| PrimaryWindow *whamRateLimitWindow `json:"primary_window"` | ||||||||||||||||||||||
| SecondaryWindow *whamRateLimitWindow `json:"secondary_window"` | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| type codexRateLimitWindowRaw struct { | ||||||||||||||||||||||
| LimitID string `json:"limitId"` | ||||||||||||||||||||||
| UsagePercentage float64 `json:"usagePercentage"` | ||||||||||||||||||||||
| ResetAt int64 `json:"resetAt"` | ||||||||||||||||||||||
| WindowDurationMinutes int `json:"windowDurationMinutes"` | ||||||||||||||||||||||
| type whamRateLimitWindow struct { | ||||||||||||||||||||||
| UsedPercent int `json:"used_percent"` | ||||||||||||||||||||||
| LimitWindowSeconds int `json:"limit_window_seconds"` | ||||||||||||||||||||||
| ResetAfterSeconds int `json:"reset_after_seconds"` | ||||||||||||||||||||||
| ResetAt int64 `json:"reset_at"` | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // fetchCodexUsage calls the Codex usage API and returns rate limit data. | ||||||||||||||||||||||
| func fetchCodexUsage(ctx context.Context, auth *codexAuthData) (*CodexRateLimitData, error) { | ||||||||||||||||||||||
| req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.openai.com/api/codex/usage", nil) | ||||||||||||||||||||||
| req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://chatgpt.com/backend-api/wham/usage", nil) | ||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||
| return nil, err | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| req.Header.Set("Authorization", "Bearer "+auth.AccessToken) | ||||||||||||||||||||||
| if auth.AccountID != "" { | ||||||||||||||||||||||
| req.Header.Set("ChatGPT-Account-Id", auth.AccountID) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| req.Header.Set("User-Agent", "shelltime-daemon") | ||||||||||||||||||||||
|
Comment on lines
191
to
192
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The ChatGPT-Account-Id header was removed in this migration. This header is typically required by ChatGPT backend APIs to correctly identify the target account, especially for users who are members of multiple workspaces or organizations. Since auth.AccountID is still being loaded from the configuration, it should be included in the request headers to ensure the correct usage data is fetched.
Suggested change
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| client := &http.Client{Timeout: 5 * time.Second} | ||||||||||||||||||||||
|
|
@@ -204,27 +205,46 @@ func fetchCodexUsage(ctx context.Context, auth *codexAuthData) (*CodexRateLimitD | |||||||||||||||||||||
| return nil, fmt.Errorf("codex usage API returned status %d", resp.StatusCode) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| var usage codexUsageResponse | ||||||||||||||||||||||
| var usage whamUsageResponse | ||||||||||||||||||||||
| if err := json.NewDecoder(resp.Body).Decode(&usage); err != nil { | ||||||||||||||||||||||
| return nil, fmt.Errorf("failed to decode codex usage response: %w", err) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| windows := make([]CodexRateLimitWindow, len(usage.RateLimits.RateLimitWindows)) | ||||||||||||||||||||||
| for i, w := range usage.RateLimits.RateLimitWindows { | ||||||||||||||||||||||
| windows[i] = CodexRateLimitWindow{ | ||||||||||||||||||||||
| LimitID: w.LimitID, | ||||||||||||||||||||||
| UsagePercentage: w.UsagePercentage, | ||||||||||||||||||||||
| ResetAt: w.ResetAt, | ||||||||||||||||||||||
| WindowDurationMinutes: w.WindowDurationMinutes, | ||||||||||||||||||||||
| var windows []CodexRateLimitWindow | ||||||||||||||||||||||
| type categoryEntry struct { | ||||||||||||||||||||||
| name string | ||||||||||||||||||||||
| category *whamRateLimitCategory | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| for _, cat := range []categoryEntry{ | ||||||||||||||||||||||
| {"rate_limit", usage.RateLimit}, | ||||||||||||||||||||||
| {"code_review_rate_limit", usage.CodeReviewRateLimit}, | ||||||||||||||||||||||
| } { | ||||||||||||||||||||||
| if cat.category == nil { | ||||||||||||||||||||||
| continue | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| if w := cat.category.PrimaryWindow; w != nil { | ||||||||||||||||||||||
| windows = append(windows, mapWhamWindow(cat.name, "primary", w)) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| if w := cat.category.SecondaryWindow; w != nil { | ||||||||||||||||||||||
| windows = append(windows, mapWhamWindow(cat.name, "secondary", w)) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return &CodexRateLimitData{ | ||||||||||||||||||||||
| Plan: usage.RateLimits.Plan, | ||||||||||||||||||||||
| Plan: usage.PlanType, | ||||||||||||||||||||||
| Windows: windows, | ||||||||||||||||||||||
| }, nil | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| func mapWhamWindow(category, position string, w *whamRateLimitWindow) CodexRateLimitWindow { | ||||||||||||||||||||||
| return CodexRateLimitWindow{ | ||||||||||||||||||||||
| LimitID: category + ":" + position, | ||||||||||||||||||||||
| UsagePercentage: float64(w.UsedPercent), | ||||||||||||||||||||||
| ResetAt: w.ResetAt, | ||||||||||||||||||||||
| WindowDurationMinutes: w.LimitWindowSeconds / 60, | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // shortenCodexAPIError converts a Codex usage API error into a short string for statusline display. | ||||||||||||||||||||||
| func shortenCodexAPIError(err error) string { | ||||||||||||||||||||||
| msg := err.Error() | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The UsedPercent field is defined as an int. If the API returns a floating-point number (e.g., 45.5), the JSON decoding will fail with an error. Given that the previous API used float64 for usage percentages, it is safer to use float64 here to ensure robust parsing of the response.