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
49 lines
1.3 KiB
Go
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
|
|
}
|