Files
MOPAC/internal/quota/client.go
T
mrcharles fc518c475e quota: z.ai credit-bucket back-pressure + resource gate + usage accounting (Redmine 490+491)
The loop now consults quota and host state before every dispatch and
DEFERS gated work with a logged reason instead of letting turns die at
the provider (the 2026-08-28 19:00 quota-wall failure mode, replayed as
a test). Adds internal/quota: 5h/weekly credit buckets (provider poll
when z.ai ships an endpoint - fake-server tested - else locally
estimated from the documented credit formula), TZ-aware peak window
(default 01:00-05:00 America/Chicago weekdays, matching the documented
z.ai peak Mon-Fri 14:00-18:00 Singapore), block/defer thresholds, a
read-only load/mem/disk/IO-PSI monitor, an optional redis shared-state
hop (stdlib RESP2 mini-client) so all instances of an account
coordinate, per-class token+credit accounting in loop.jsonl, and
`harness quota status|probe|gate`. Config: [quota] + [resources]
sections; README runbook covers the redis container and deploy-time
cgroup enforcement.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-29 05:37:15 -05:00

49 lines
1.3 KiB
Go

package quota
import (
"context"
"fmt"
"io"
"net/http"
"strings"
)
// FetchUsage GETs the provider usage endpoint with bearer auth. The key is
// resolved per call from the [quota] key_ref and never logged; error paths
// carry only status codes and redacted/truncated bodies (same discipline as
// the keyproxy hop).
func FetchUsage(ctx context.Context, client *http.Client, usageURL, key string) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, usageURL, nil)
if err != nil {
return nil, fmt.Errorf("usage request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+key)
req.Header.Set("Accept", "application/json")
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("usage poll: %w", redactQuery(err))
}
defer resp.Body.Close()
body, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("usage poll: HTTP %d: %s", resp.StatusCode, truncateBody(string(body)))
}
return body, nil
}
func redactQuery(err error) error {
msg := err.Error()
if i := strings.Index(msg, "?"); i >= 0 {
msg = msg[:i] + "?..."
}
return fmt.Errorf("%s", msg)
}
func truncateBody(s string) string {
s = strings.TrimSpace(s)
if len(s) > 200 {
s = s[:200] + "..."
}
return s
}