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
75 lines
2.4 KiB
Go
75 lines
2.4 KiB
Go
package quota
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strings"
|
|
)
|
|
|
|
// Multipliers are the z.ai credit formula coefficients (per 10k tokens,
|
|
// docs.z.ai/devpack/overview). Family selects by concrete model name; the
|
|
// flash tier (GLM-5.3-Flash, routed from glm-4.7*) is the cheap one.
|
|
type Multipliers struct {
|
|
Input float64
|
|
CachedInput float64
|
|
Output float64
|
|
}
|
|
|
|
// Flagship multipliers: GLM-5.3 (and everything auto-routed to it).
|
|
func FlagshipMultipliers() Multipliers { return Multipliers{Input: 6.9, CachedInput: 1.7, Output: 24} }
|
|
|
|
// Flash multipliers: GLM-5.3-Flash (and glm-4.7* routing).
|
|
func FlashMultipliers() Multipliers { return Multipliers{Input: 2.3, CachedInput: 0.56, Output: 8} }
|
|
|
|
// MultipliersFor maps a concrete proxy model name to its credit multiplier
|
|
// family. Unknown models read as flagship (conservative: overestimate cost
|
|
// rather than silently burn quota).
|
|
func MultipliersFor(model string) Multipliers {
|
|
m := strings.ToLower(model)
|
|
switch {
|
|
case strings.Contains(m, "flash"):
|
|
return FlashMultipliers()
|
|
default:
|
|
return FlagshipMultipliers()
|
|
}
|
|
}
|
|
|
|
// EstimateCredits computes the z.ai credits one turn consumed from its token
|
|
// usage: (input*in + cached*cache + output*out) / 10000, halved when the
|
|
// turn ran off-peak (z.ai charges 50% outside peak hours).
|
|
func EstimateCredits(model string, promptTokens, cachedTokens, completionTokens int, peak bool) float64 {
|
|
mult := MultipliersFor(model)
|
|
credits := (float64(promptTokens)*mult.Input +
|
|
float64(cachedTokens)*mult.CachedInput +
|
|
float64(completionTokens)*mult.Output) / 10000
|
|
if !peak {
|
|
credits /= 2
|
|
}
|
|
// Round to 6 decimals: keeps redis INCRBYFLOAT values readable and the
|
|
// JSONL compact; sub-microcredit noise is meaningless.
|
|
return math.Round(credits*1e6) / 1e6
|
|
}
|
|
|
|
// EstimateTurnInput is the per-turn usage record the loop hands the gate.
|
|
type EstimateTurnInput struct {
|
|
Model string
|
|
PromptTokens int
|
|
CachedTokens int
|
|
CompletionTokens int
|
|
Peak bool // turn ran inside the peak window
|
|
}
|
|
|
|
// Describe renders a human summary of one turn's credit cost (logs, REPORTs).
|
|
func (e EstimateTurnInput) Describe() string {
|
|
return fmt.Sprintf("%s: %d/%d/%d tokens (in/cached/out) %s = %.4f credits",
|
|
e.Model, e.PromptTokens, e.CachedTokens, e.CompletionTokens,
|
|
peakTag(e.Peak), EstimateCredits(e.Model, e.PromptTokens, e.CachedTokens, e.CompletionTokens, e.Peak))
|
|
}
|
|
|
|
func peakTag(peak bool) string {
|
|
if peak {
|
|
return "peak"
|
|
}
|
|
return "off-peak"
|
|
}
|