policy/algo.bend
cswap-bend-cli@0.1.0.0 · 163 lines · 22 definitions
1import Base
2import cswap-bend-cli@0.1.0.0/policy/types.bend as T
3import cswap-bend-cli@0.1.0.0/policy/usage.bend as U
4import cswap-bend-cli@0.1.0.0/policy/rank.bend as R
5import cswap-bend-cli@0.1.0.0/policy/exhaust.bend as E
6import cswap-bend-cli@0.1.0.0/policy/decide.bend as D
7import cswap-bend-cli@0.1.0.0/policy/safe.bend as SF
8
9
10
11
12
13
14
15
16
17
18
19
20type Via is Data:
21 VAuto{}
22 VRotate{}
23
24def via.auto(+v: Via) -> Bool:
25 match v:
26 case VAuto{}:
27 True{}
28 case VRotate{}:
29 False{}
30
31def drain(+algo: SF.Algo) -> Bool:
32 match algo:
33 case SF.ADrainFirst{}:
34 True{}
35 case o:
36 False{}
37
38def need.under(+under: Bool, +thr: Nat) -> Nat:
39 match under:
40 case True{}:
41 Nat.add(Nat.sub(T.full(), thr), 1n)
42 case False{}:
43 1n
44
45
46
47
48def need.of(+algo: SF.Algo, +via: Via, +a: Nat, +thr: Nat) -> Nat:
49 need.under(drain(algo) || (via.auto(via) && Nat.is_gt(a, 0n)), thr)
50
51def mode.of(+algo: SF.Algo) -> T.Mode:
52 match algo:
53 case SF.ASoonestReset{}:
54 T.MReset{}
55 case o:
56 T.MHead{}
57
58def trig.via(+via: Via, +a: Nat) -> T.Trigger:
59 match via:
60 case VAuto{}:
61 D.trig.of(a)
62 case VRotate{}:
63 T.Proactive{}
64
65def sw(+m: Maybe<&2, SF.Cd>, +algo: SF.Algo, +t: T.Trigger, +next: T.Decision) -> T.Decision:
66 match m:
67 case None{}:
68 next
69 case Some{c}:
70 match c:
71 case SF.Cd{slot, k1, k2, h}:
72 T.Switch{slot, t, T.trigger.name(t), h, mode.of(algo), None{}, None{}}
73
74
75
76def nosw(+d: T.Decision) -> T.Decision:
77 match d:
78 case T.NoSwitch{r, detail}:
79 T.NoSwitch{r, detail}
80 case o:
81 T.NoSwitch{T.NoCandidates{}, ""}
82
83def go.pick(+algo: SF.Algo, +via: Via, +a: Nat, +e: D.Env) -> T.Decision:
84 match e:
85 case D.Env{active, as, us, now, cfg, st, mem}:
86 sw(SF.pick(algo, as, active, us, now, D.cfg.models(cfg), need.of(algo, via, a, D.cfg.threshold(cfg))), algo, trig.via(via, a),
87 nosw(D.none.cf(T.Proactive{}, None{}, as, active, us, now, D.cfg.models(cfg))))
88
89def go.cool(+cool: Bool, +algo: SF.Algo, +via: Via, +a: Nat, +e: D.Env) -> T.Decision:
90 match cool:
91 case True{}:
92 T.NoSwitch{T.Cooldown{}, ""}
93 case False{}:
94 go.pick(algo, via, a, e)
95
96
97def go.auto(+a: Nat, +algo: SF.Algo, +via: Via, +e: D.Env) -> T.Decision:
98 match a:
99 case 0n:
100 go.pick(algo, via, a, e)
101 case 1n+p:
102 go.cool(D.in_cooldown(D.st.last_at(D.env.st(e)), D.env.now(e), D.cfg.cooldown(D.env.cfg(e))), algo, via, a, e)
103
104def go.via(+algo: SF.Algo, +via: Via, +a: Nat, +e: D.Env) -> T.Decision:
105 match via:
106 case VAuto{}:
107 go.auto(a, algo, via, e)
108 case VRotate{}:
109 go.pick(algo, via, a, e)
110
111def go.below(+below: Bool, +algo: SF.Algo, +via: Via, +a: Nat, +e: D.Env) -> T.Decision:
112 match below:
113 case True{}:
114 T.NoSwitch{T.BelowThreshold{}, D.below.detail(a, D.env.cfg(e))}
115 case False{}:
116 go.via(algo, via, a, e)
117
118def go.known(+ah: Maybe<&2, Nat>, +algo: SF.Algo, +via: Via, +e: D.Env) -> T.Decision:
119 match ah:
120 case None{}:
121 T.NoSwitch{T.ActiveUsageUnknown{}, "headroom unknown; a profile strategy never switches away from an account it cannot read (use 'cswap switch NUM')"}
122 case Some{a}:
123 go.below(via.auto(via) && Nat.is_lt(Nat.sub(T.full(), a), D.cfg.threshold(D.env.cfg(e))), algo, via, a, e)
124
125def go.run(+algo: SF.Algo, +via: Via, +e: D.Env) -> T.Decision:
126 go.known(D.env.ah(e), algo, via, e)
127
128def go.kind(+k: T.AcctKind, +algo: SF.Algo, +via: Via, +e: D.Env) -> T.Decision:
129 match k:
130 case T.ApiKey{}:
131 T.NoSwitch{T.ActiveApiKey{}, "API-key accounts have no quota to watch"}
132 case o:
133 go.run(algo, via, e)
134
135def go.acct(+m: Maybe<&2, T.Account>, +algo: SF.Algo, +via: Via, +e: D.Env) -> T.Decision:
136 match m:
137 case None{}:
138 T.NoSwitch{T.UnmanagedActive{}, "run 'cswap --add-account' to include it in rotation"}
139 case Some{a}:
140 match a:
141 case T.Account{slot, email, kind, disabled}:
142 go.kind(kind, algo, via, e)
143
144def core(+algo: SF.Algo, +via: Via, +e: D.Env) -> T.Decision:
145 go.acct(U.account.of(D.env.active(e), D.env.as(e)), algo, via, e)
146
147
148def decide(+algo: SF.Algo, +active: Nat, +accounts: List<&2, T.Account>, +usage: List<&2, T.Usage>, +now: Nat, +cfg: T.Config, +st: T.State, +mem: T.Mem) -> T.Decision:
149 match algo:
150 case SF.ABest{}:
151 D.decide(active, accounts, usage, now, cfg, st, mem)
152 case o:
153 core(o, VAuto{}, D.Env{active, accounts, usage, now, cfg, st, mem})
154
155
156
157def rotate(+algo: SF.Algo, +active: Nat, +accounts: List<&2, T.Account>, +usage: List<&2, T.Usage>, +now: Nat, +cfg: T.Config) -> T.Decision:
158 core(algo, VRotate{}, D.Env{active, accounts, usage, now, cfg, T.fresh_state(), T.fresh_mem()})
159
160
161
162def decide_cfg(+name: String, +active: Nat, +accounts: List<&2, T.Account>, +usage: List<&2, T.Usage>, +now: Nat, +cfg: T.Config, +st: T.State, +mem: T.Mem) -> T.Decision:
163 decide(Maybe.default(&2, SF.Algo, SF.parse(name), SF.ABest{}), active, accounts, usage, now, cfg, st, mem)