usage/text.bend
cswap-bend-cli@0.1.0.0 · 224 lines · 39 definitions
1import Base
2import cswap-bend-cli@0.1.0.0/http.bend as Http
3import cswap-bend-cli@0.1.0.0/json.bend as Json
4import cswap-bend-cli@0.1.0.0/clock.bend as Clock
5
6def user_agent() -> String:
7 "claude-swap/1.0"
8
9def usage_url() -> String:
10 "https://api.anthropic.com/api/oauth/usage"
11
12def messages_url() -> String:
13 "https://api.anthropic.com/v1/messages"
14
15def token_url() -> String:
16 "https://platform.claude.com/v1/oauth/token"
17
18def client_id() -> String:
19 "9d1c250a-e61b-44d9-88ed-5944d1962f5e"
20
21def probe_model() -> String:
22 "claude-haiku-5-5"
23
24
25
26def usage.config(token: String) -> String:
27 Http.config("GET", usage_url(),
28 [("Authorization", "Bearer " ++ token), ("anthropic-beta", "oauth-2025-04-20"), ("User-Agent", user_agent())],
29 "", 5)
30
31def probe.body() -> String:
32 Json.emit_py(Json.of_pairs([
33 ("model", Json.JStr{probe_model()}),
34 ("max_tokens", Json.JNum{"1"}),
35 ("system", Json.arr([Json.of_pairs([("type", Json.JStr{"text"}), ("text", Json.JStr{"You are Claude Code, Anthropic's official CLI for Claude."})])])),
36 ("messages", Json.arr([Json.of_pairs([("role", Json.JStr{"user"}), ("content", Json.JStr{"."})])]))]))
37
38def probe.config(token: String) -> String:
39 Http.config("POST", messages_url(),
40 [("Authorization", "Bearer " ++ token), ("anthropic-version", "2023-06-01"),
41 ("anthropic-beta", "claude-code-20250219,oauth-2025-04-20"), ("content-type", "application/json"),
42 ("User-Agent", user_agent())],
43 probe.body(), 20)
44
45def refresh.body(refresh_token: String) -> String:
46 Json.emit_py(Json.of_pairs([
47 ("grant_type", Json.JStr{"refresh_token"}), ("refresh_token", Json.JStr{refresh_token}), ("client_id", Json.JStr{client_id()})]))
48
49def refresh.config(refresh_token: String) -> String:
50 Http.config("POST", token_url(),
51 [("Content-Type", "application/json"), ("User-Agent", user_agent())],
52 refresh.body(refresh_token), 10)
53
54
55
56def maybe.none_if(bad: Bool, s: String) -> Maybe<&2, String>:
57 match bad:
58 case True{}:
59 None{}
60 case False{}:
61 Some{s}
62
63def strip0.pick(zero: Bool, more: Bool, c: Char, t: String, rest: String) -> String:
64 match zero more:
65 case True{} True{}:
66 rest
67 case _ _:
68 SCon{c, t}
69
70
71def strip0(s: String) -> String:
72 match s:
73 case SNil{}:
74 "0"
75 case SCon{+c, +t}:
76 strip0.pick(Char.is_eq(c, '0'), Bool.not(String.is_empty(t)), c, t, strip0(t))
77
78def float.mk(neg: Bool, +i: String, +f: String) -> Maybe<&2, String>:
79 maybe.none_if(Bool.not(Clock.all_digits(i) && Clock.all_digits(f) && Bool.not(String.is_empty(i))),
80 Clock.sel.s(neg, "-", "") ++ strip0(i) ++ "." ++ String.reverse(strip0(String.reverse(f))))
81
82def float.parts(neg: Bool, ps: List<&2, String>) -> Maybe<&2, String>:
83 match ps:
84 case Con{i, Nil{}}:
85 float.mk(neg, i, "0")
86 case Con{i, Con{f, Nil{}}}:
87 float.mk(neg, i, f)
88 case _:
89 None{}
90
91def float.sign(+neg: Bool, +s: String) -> Maybe<&2, String>:
92 float.parts(neg, String.split(Clock.sel.s(neg, String.drop(s, 1n), s), '.'))
93
94
95
96
97def float(+s: String) -> Maybe<&2, String>:
98 float.sign(String.starts_with(s, "-"), s)
99
100def split.int(ps: List<&2, String>) -> Maybe<&2, String>:
101 match ps:
102 case Con{i, t}:
103 Some{i}
104 case Nil{}:
105 None{}
106
107
108def div100.fin(+i2: String, +f: String) -> Maybe<&2, String>:
109 float(String.take(i2, Nat.sub(String.length(i2), 2n)) ++ "." ++ String.drop(i2, Nat.sub(String.length(i2), 2n)) ++ f)
110
111def div100.parts(ps: List<&2, String>) -> Maybe<&2, String>:
112 match ps:
113 case Con{i, Con{f, Nil{}}}:
114 div100.fin("00" ++ i, f)
115 case _:
116 None{}
117
118def div100.go(m: Maybe<&2, String>) -> Maybe<&2, String>:
119 match m:
120 case None{}:
121 None{}
122 case Some{n}:
123 div100.parts(String.split(n, '.'))
124
125def div100(+s: String) -> Maybe<&2, String>:
126 div100.go(float(s))
127
128
129
130def pct1.show(+n: U32) -> String:
131 U32.show((n / 10 : U32)) ++ "." ++ U32.show((n % 10 : U32))
132
133def pct1.fin(+i: String, +f: String) -> Maybe<&2, String>:
134 do Maybe<&2, String>:
135 ip : U32 <- U32.read(i)
136 fp : U32 <- Clock.num(f ++ "0000", 0n, 3n)
137 rd : U32 <- Clock.num(f ++ "0000", 3n, 1n)
138 return pct1.show((ip * 1000 + fp + Clock.sel(U32.is_ge(rd, 5), 1, 0) : U32))
139
140def pct1.pick(ok: Bool, +i: String, +f: String) -> Maybe<&2, String>:
141 match ok:
142 case True{}:
143 pct1.fin(i, f)
144 case False{}:
145 None{}
146
147def pct1.parts(ps: List<&2, String>) -> Maybe<&2, String>:
148 match ps:
149 case Con{+i, Nil{}}:
150 pct1.pick(Clock.all_digits(i) && Bool.not(String.is_empty(i)) && Nat.is_le(String.length(i), 6n), i, "")
151 case Con{+i, Con{+f, Nil{}}}:
152 pct1.pick(Clock.all_digits(i) && Clock.all_digits(f) && Bool.not(String.is_empty(i)) && Nat.is_le(String.length(i), 6n), i, f)
153 case _:
154 None{}
155
156def pct1(s: String) -> Maybe<&2, String>:
157 pct1.parts(String.split(s, '.'))
158
159
160
161def rl() -> String:
162 "anthropic-ratelimit-unified-"
163
164def reset.epoch(m: Maybe<&2, U32>) -> Maybe<&2, String>:
165 match m:
166 case None{}:
167 None{}
168 case Some{t}:
169 Some{Clock.iso8601_offset(t)}
170
171def reset.iso(m: Maybe<&2, String>) -> Maybe<&2, String>:
172 match m:
173 case None{}:
174 None{}
175 case Some{s}:
176 reset.epoch(U32.read(s))
177
178def hwin.mk(pct: String, reset: Maybe<&2, String>) -> Maybe<&2, Json.Json>:
179 match reset:
180 case None{}:
181 Some{Json.of_pairs([("utilization", Json.JNum{pct})])}
182 case Some{r}:
183 Some{Json.of_pairs([("utilization", Json.JNum{pct}), ("resets_at", Json.JStr{r})])}
184
185def hwin.pick(pct: Maybe<&2, String>, reset: Maybe<&2, String>) -> Maybe<&2, Json.Json>:
186 match pct:
187 case None{}:
188 None{}
189 case Some{p}:
190 hwin.mk(p, reset)
191
192def hwin.util(u: Maybe<&2, String>, reset: Maybe<&2, String>) -> Maybe<&2, Json.Json>:
193 match u:
194 case None{}:
195 None{}
196 case Some{x}:
197 hwin.pick(pct1(x), reset.iso(reset))
198
199
200
201def hwin(+p: String, +head: String) -> Maybe<&2, Json.Json>:
202 hwin.util(Http.header(rl() ++ p ++ "-utilization:", head), Http.header(rl() ++ p ++ "-reset:", head))
203
204def hdata.set(k: String, w: Maybe<&2, Json.Json>, j: Json.Json) -> Json.Json:
205 match w:
206 case None{}:
207 j
208 case Some{v}:
209 Json.set(k, v, j)
210
211def hdata.keys(ks: List<&2, String>, j: Json.Json) -> Maybe<&2, Json.Json>:
212 match ks:
213 case Nil{}:
214 None{}
215 case Con{h, t}:
216 Some{j}
217
218def hdata.fin(+j: Json.Json) -> Maybe<&2, Json.Json>:
219 hdata.keys(Json.keys(j), j)
220
221
222
223def usage_from_ratelimit_headers(+head: String) -> Maybe<&2, Json.Json>:
224 hdata.fin(hdata.set("seven_day", hwin("7d", head), hdata.set("five_hour", hwin("5h", head), Json.of_pairs([]))))