Files
MOPAC/internal/quota/estimate.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

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"
}