Rate limits: - Add PoolRPS/PoolTPM/PoolTokensMonth/PoolCostMonth pool kinds - Provider defaults for Mistral/Anthropic/OpenAI/Google (tier-aware) - Config override via [rate_limits.<provider>] TOML section - Pools auto-attached to arms on registration Elf tree view (CC-style): - Structured elf.Progress type replaces flat string channel - Tree with ├─/└─ branches, per-elf stats (tool uses, tokens) - Live activity updates: tool calls, "generating… (N chars)" - Completed elfs stay in tree with "Done (duration)" until turn ends - Suppress raw elf output from chat (tree + LLM summary instead) - Remove background elf mode (wait: false) — always wait - Truncate elf results to 2000 chars for parent context - Parallel hint in system prompt and tool description Permission prompts: - Show actual command in prompt: "bash wants to execute: find . -name '*.go'" - Compact hint in separator bar: "⚠ bash: find . | wc -l [y/n]" - PermReqMsg carries tool name + args Other: - Fix /model not updating status bar (session.Local.SetModel) - Add make targets: run, check, install - Update deps: BurntSushi/toml v1.6.0, chroma v2.23.1, x/text v0.35.0, cloud.google.com/go v0.123.0
128 lines
3.5 KiB
Go
128 lines
3.5 KiB
Go
package router
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"somegit.dev/Owlibou/gnoma/internal/provider"
|
|
)
|
|
|
|
// PoolsFromRateLimits creates limit pools for an arm based on rate limits.
|
|
// Each non-zero limit dimension becomes a pool attached to the arm.
|
|
func PoolsFromRateLimits(armID ArmID, rl provider.RateLimits) []*LimitPool {
|
|
now := time.Now()
|
|
var pools []*LimitPool
|
|
|
|
if rl.RPS > 0 {
|
|
pools = append(pools, &LimitPool{
|
|
ID: fmt.Sprintf("%s/rps", armID),
|
|
Kind: PoolRPS,
|
|
TotalLimit: rl.RPS,
|
|
ResetPeriod: time.Second,
|
|
ResetAt: now.Add(time.Second),
|
|
ArmRates: map[ArmID]float64{armID: 1.0 / 1000.0}, // 1 request per call, normalized
|
|
ScarcityK: 3.0,
|
|
})
|
|
}
|
|
|
|
if rl.RPM > 0 {
|
|
pools = append(pools, &LimitPool{
|
|
ID: fmt.Sprintf("%s/rpm", armID),
|
|
Kind: PoolRPM,
|
|
TotalLimit: float64(rl.RPM),
|
|
ResetPeriod: time.Minute,
|
|
ResetAt: now.Add(time.Minute),
|
|
ArmRates: map[ArmID]float64{armID: 1.0 / 1000.0},
|
|
ScarcityK: 2.0,
|
|
})
|
|
}
|
|
|
|
if rl.RPD > 0 {
|
|
pools = append(pools, &LimitPool{
|
|
ID: fmt.Sprintf("%s/rpd", armID),
|
|
Kind: PoolRPD,
|
|
TotalLimit: float64(rl.RPD),
|
|
ResetPeriod: 24 * time.Hour,
|
|
ResetAt: nextMidnight(),
|
|
ArmRates: map[ArmID]float64{armID: 1.0 / 1000.0},
|
|
ScarcityK: 2.0,
|
|
})
|
|
}
|
|
|
|
if rl.TPM > 0 {
|
|
pools = append(pools, &LimitPool{
|
|
ID: fmt.Sprintf("%s/tpm", armID),
|
|
Kind: PoolTPM,
|
|
TotalLimit: float64(rl.TPM),
|
|
ResetPeriod: time.Minute,
|
|
ResetAt: now.Add(time.Minute),
|
|
ArmRates: map[ArmID]float64{armID: 1.0}, // 1 token per token
|
|
ScarcityK: 2.0,
|
|
})
|
|
}
|
|
|
|
// Anthropic-style split: ITPM+OTPM. Use TPM pool kind for the more
|
|
// restrictive one (output) since that's what throttles agentic use.
|
|
if rl.ITPM > 0 && rl.TPM == 0 {
|
|
pools = append(pools, &LimitPool{
|
|
ID: fmt.Sprintf("%s/itpm", armID),
|
|
Kind: PoolTPM,
|
|
TotalLimit: float64(rl.ITPM),
|
|
ResetPeriod: time.Minute,
|
|
ResetAt: now.Add(time.Minute),
|
|
ArmRates: map[ArmID]float64{armID: 0.6}, // ~60% of tokens are input
|
|
ScarcityK: 2.0,
|
|
})
|
|
}
|
|
if rl.OTPM > 0 {
|
|
pools = append(pools, &LimitPool{
|
|
ID: fmt.Sprintf("%s/otpm", armID),
|
|
Kind: PoolTPM,
|
|
TotalLimit: float64(rl.OTPM),
|
|
ResetPeriod: time.Minute,
|
|
ResetAt: now.Add(time.Minute),
|
|
ArmRates: map[ArmID]float64{armID: 0.4}, // ~40% of tokens are output
|
|
ScarcityK: 3.0, // output is more precious
|
|
})
|
|
}
|
|
|
|
if rl.TokensMonth > 0 {
|
|
pools = append(pools, &LimitPool{
|
|
ID: fmt.Sprintf("%s/tokens-month", armID),
|
|
Kind: PoolTokensMonth,
|
|
TotalLimit: float64(rl.TokensMonth),
|
|
ResetPeriod: 30 * 24 * time.Hour,
|
|
ResetAt: nextMonthStart(),
|
|
ArmRates: map[ArmID]float64{armID: 1.0},
|
|
ScarcityK: 4.0, // aggressive hoarding for monthly budgets
|
|
})
|
|
}
|
|
|
|
if rl.SpendCap > 0 {
|
|
pools = append(pools, &LimitPool{
|
|
ID: fmt.Sprintf("%s/spend-cap", armID),
|
|
Kind: PoolCostMonth,
|
|
TotalLimit: rl.SpendCap,
|
|
ResetPeriod: 30 * 24 * time.Hour,
|
|
ResetAt: nextMonthStart(),
|
|
ArmRates: map[ArmID]float64{}, // cost tracked externally per arm
|
|
ScarcityK: 4.0,
|
|
})
|
|
}
|
|
|
|
return pools
|
|
}
|
|
|
|
func nextMidnight() time.Time {
|
|
now := time.Now()
|
|
return time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location())
|
|
}
|
|
|
|
func nextMonthStart() time.Time {
|
|
now := time.Now()
|
|
if now.Month() == 12 {
|
|
return time.Date(now.Year()+1, 1, 1, 0, 0, 0, 0, now.Location())
|
|
}
|
|
return time.Date(now.Year(), now.Month()+1, 1, 0, 0, 0, 0, now.Location())
|
|
}
|