Files
kandev/apps/backend/internal/agent/usage/client_codex.go
T
João Salavisa fa4a48d5e7 chore: remove orphan helpers, setters, and deprecated wrappers (#1085)
* chore: remove orphan executor tracing file and deprecated ResolveScript wrappers

- Delete internal/agentctl/tracing/executor.go (7 TraceExecutor* funcs)
- Delete internal/orchestrator/executor/script_template.go (deprecated wrapper)
- Delete scriptengine.ResolveScript convenience function

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: remove orphan setter methods across healthpoll, jira, linear, slack, updates

- Delete Poller.SetInterval from integrations/healthpoll (no callers)
- Delete Poller.SetIssueTickHook from jira and linear (no callers)
- Delete Trigger.SetInterval and Trigger.SetMatchHook from slack (no callers)
- Delete Service.SetPollInterval from system/updates (no callers)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: remove orphan wrapper functions and accessor method

- Delete updates.FetchLatestRelease shim (wraps FetchLatestReleaseFrom, no callers)
- Delete usage.NewClaudeUsageClient and NewCodexUsageClient shims (no callers)
- Delete database.Service.Path() accessor (no callers)
- Update ErrGitHubRateLimited godoc to reference FetchLatestReleaseFrom

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: remove unwired handleListExecutors MCP handler

The sibling profile handlers (list/create/update/delete) are registered
via RegisterFunc in handlers.go. handleListExecutors never was — dead flow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: remove stale hook-field comments after setter deletion

SetIssueTickHook (jira, linear) and SetMatchHook (slack) were removed
in the deadcode cleanup. Their struct fields still claimed "tests use
this" — no test or caller can set these fields now.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: remove orphan hook fields and fire methods after setter deletion

issueTickHook (jira/linear) and matchHook (slack) are unexported and
never set after their public setters were removed; fireIssueTickHook /
fireHook were permanent no-ops.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 09:32:52 +01:00

107 lines
2.8 KiB
Go

package usage
import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"strconv"
"time"
)
const codexUsageURL = "https://chatgpt.com/backend-api/wham/usage"
// CodexUsageClient fetches utilization from the OpenAI Codex usage API.
// It reads the bearer token from ~/.config/codex/auth.json and inspects
// response headers for utilization percentages. Fail-open: if the token is
// missing or the request fails, nil is returned (no utilization data).
type CodexUsageClient struct {
authPath string
httpClient *http.Client
}
// NewCodexUsageClientWithPath creates a client with an explicit auth path (for tests).
func NewCodexUsageClientWithPath(authPath string) *CodexUsageClient {
return &CodexUsageClient{
authPath: authPath,
httpClient: &http.Client{Timeout: 10 * time.Second},
}
}
// AuthPath returns the path this client reads credentials from.
func (c *CodexUsageClient) AuthPath() string {
return c.authPath
}
type codexAuthJSON struct {
Token string `json:"token"`
}
// FetchUsage implements ProviderUsageClient.
// Returns nil, nil if the auth file is missing or the token cannot be read —
// this is the fail-open path for Codex.
func (c *CodexUsageClient) FetchUsage(ctx context.Context) (*ProviderUsage, error) {
token, err := c.readToken()
if err != nil {
// Fail-open: no Codex auth available.
return nil, nil //nolint:nilerr
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, codexUsageURL, nil)
if err != nil {
return nil, nil //nolint:nilerr
}
req.Header.Set("Authorization", "Bearer "+token)
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, nil //nolint:nilerr
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
return nil, nil
}
primary := parseHeaderPercent(resp.Header.Get("x-codex-primary-used-percent"))
secondary := parseHeaderPercent(resp.Header.Get("x-codex-secondary-used-percent"))
now := time.Now()
windows := []UtilizationWindow{
{Label: "primary", UtilizationPct: primary, ResetAt: now.Add(24 * time.Hour)},
{Label: "secondary", UtilizationPct: secondary, ResetAt: now.Add(7 * 24 * time.Hour)},
}
return &ProviderUsage{
Provider: "openai",
Windows: windows,
FetchedAt: now,
}, nil
}
func (c *CodexUsageClient) readToken() (string, error) {
data, err := os.ReadFile(c.authPath)
if err != nil {
return "", fmt.Errorf("read %s: %w", c.authPath, err)
}
var auth codexAuthJSON
if err := json.Unmarshal(data, &auth); err != nil {
return "", fmt.Errorf("parse %s: %w", c.authPath, err)
}
if auth.Token == "" {
return "", fmt.Errorf("empty token in %s", c.authPath)
}
return auth.Token, nil
}
func parseHeaderPercent(v string) float64 {
if v == "" {
return 0
}
f, err := strconv.ParseFloat(v, 64)
if err != nil {
return 0
}
return f
}