mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-09-20 00:48:00 +00:00
feat: opencode zen adapter + first-run config generation, fix stats/stream bugs
- adapters/opencode.lua: opencode.ai zen free pool adapter — sends the opencode client User-Agent (zen fingerprints clients by UA; non-official clients hit FreeUsageLimitError); pairs with api_key: public - config: no config file ships in the repo; first run generates a default config at the -config path with a random admin key, loopback listen and a keyless zen source (config.EnsureDefault); remove config.example.yaml - lua: seed bundled adapters from the embedded FS instead of a hardcoded name list - ui: widen model kind select (chat was clipped to 'cha') - phase 5 bugfixes: stats ms/s bucket mixing, cleanScopes nil, ctx.Err guards, direct-path ModelAvailable, empty stream body failure, bestImageModel rewrite, transform failure recording, Core.mu, timer, effective model for tool-calls
This commit is contained in:
@ -210,20 +210,25 @@ func (s *Stats) aggregateLocked(r Req) {
|
||||
}
|
||||
incStatus(a, strconv.Itoa(r.Status), r)
|
||||
}
|
||||
// window bucket for quota enforcement (per source-model pair, per unix hour)
|
||||
// window bucket for quota enforcement (per source-model pair, per unix
|
||||
// hour). r.Time is unix MILLISECONDS (audit format); hourSec is seconds,
|
||||
// so convert before bucketing — otherwise the bucket width would be
|
||||
// 3.6s and every WindowTokens cutoff comparison would be off by ~1000x.
|
||||
tok := r.Prompt + r.Compl
|
||||
if tok > 0 && r.Model != "" {
|
||||
key := r.Model
|
||||
if r.Source != "" {
|
||||
key = r.Source + "::" + r.Model
|
||||
}
|
||||
h := r.Time / hourSec
|
||||
h := (r.Time / 1000) / hourSec
|
||||
hm := s.modelHour[key]
|
||||
if hm == nil {
|
||||
hm = map[int64]int64{}
|
||||
s.modelHour[key] = hm
|
||||
}
|
||||
hm[h] += tok
|
||||
// retention: 24*40 = 960 hourly buckets ≈ 40 days of history (covers
|
||||
// the longest "month" quota window)
|
||||
if len(hm) > 24*40 {
|
||||
for k := range hm {
|
||||
if k < h-24*40 {
|
||||
@ -324,10 +329,10 @@ func AutoPeriodSeconds(period string, hours int64) int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// WindowTokens returns the tokens billed for the model within the last `sec`
|
||||
// seconds (0 = since forever).
|
||||
// WindowTokens returns the tokens consumed for one model (optionally pinned
|
||||
// to a single source) within the window; sec <= 0 means all time.
|
||||
// to a single source) within the window; sec <= 0 means all time. Buckets are
|
||||
// whole unix hours, so a sliding window overcounts by up to one hour — an
|
||||
// accepted truncation for quota enforcement.
|
||||
func (s *Stats) WindowTokens(model, source string, sec int64) int64 {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
Reference in New Issue
Block a user