997 lines
52 KiB
C++
997 lines
52 KiB
C++
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||
|
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||
|
|
|
||
|
|
module;
|
||
|
|
#include <stdlib.h>
|
||
|
|
#include <time.h>
|
||
|
|
#include <unistd.h>
|
||
|
|
|
||
|
|
module Crafter.Review:Seats_impl;
|
||
|
|
import :Seats;
|
||
|
|
import :Json;
|
||
|
|
import :Text;
|
||
|
|
import :Stream;
|
||
|
|
import :Platform;
|
||
|
|
import :Process;
|
||
|
|
import :Crypto;
|
||
|
|
import std;
|
||
|
|
|
||
|
|
namespace fs = std::filesystem;
|
||
|
|
|
||
|
|
namespace Crafter::Review::Seats {
|
||
|
|
namespace {
|
||
|
|
constexpr std::array<std::string_view, 7> Weekdays = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
|
||
|
|
// en-GB, as Intl.DateTimeFormat abbreviates them (September is "Sept").
|
||
|
|
constexpr std::array<std::string_view, 12> Months = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"};
|
||
|
|
constexpr std::array<std::string_view, 4> ModelFamilies = {"fable", "opus", "sonnet", "haiku"};
|
||
|
|
|
||
|
|
const std::string& EnvOf(const std::map<std::string, std::string>& env, const std::string& key) {
|
||
|
|
static const std::string none;
|
||
|
|
const auto found = env.find(key);
|
||
|
|
return found == env.end() ? none : found->second;
|
||
|
|
}
|
||
|
|
|
||
|
|
double AsDouble(const Json& value) {
|
||
|
|
if (value.is_number_float()) return value.get<double>();
|
||
|
|
if (value.is_boolean()) return value.get<bool>() ? 1.0 : 0.0;
|
||
|
|
if (value.is_number_unsigned()) return static_cast<double>(value.get<std::uint64_t>());
|
||
|
|
if (value.is_number_integer()) return static_cast<double>(value.get<std::int64_t>());
|
||
|
|
return 0.0;
|
||
|
|
}
|
||
|
|
|
||
|
|
// The value as a float, or nullopt for one that is none.
|
||
|
|
std::optional<double> FloatOf(const Json& value) {
|
||
|
|
if (value.is_number() || value.is_boolean()) return AsDouble(value);
|
||
|
|
if (value.is_string()) return Dyn::ParseFloat(value.get_ref<const std::string&>());
|
||
|
|
return std::nullopt;
|
||
|
|
}
|
||
|
|
|
||
|
|
// An integer when n is integral, else n.
|
||
|
|
Json IntIfWhole(double n) {
|
||
|
|
if (std::isfinite(n) && n == std::floor(n) && std::fabs(n) < 9.0e18) return Json(static_cast<std::int64_t>(n));
|
||
|
|
return Json(n);
|
||
|
|
}
|
||
|
|
|
||
|
|
// A number field as a float, 0 when it is falsy.
|
||
|
|
double NumberOr0(const Json& value) {
|
||
|
|
return Dyn::Truthy(value) ? AsDouble(value) : 0.0;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool Less(const Json& a, const Json& b) {
|
||
|
|
return AsDouble(a) < AsDouble(b);
|
||
|
|
}
|
||
|
|
|
||
|
|
// An ASCII literal, case-insensitive as re.I reads it, at `at`.
|
||
|
|
bool MatchCi(std::u32string_view text, std::size_t at, std::string_view word) {
|
||
|
|
if (at + word.size() > text.size()) return false;
|
||
|
|
for (std::size_t i = 0; i < word.size(); ++i) {
|
||
|
|
const char p = word[i];
|
||
|
|
if (p >= 'a' && p <= 'z') {
|
||
|
|
if (Dyn::IgnoreCaseLetter(text[at + i]) != p) return false;
|
||
|
|
} else if (text[at + i] != static_cast<char32_t>(static_cast<std::uint8_t>(p))) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool ContainsCi(std::string_view text, std::string_view word) {
|
||
|
|
const std::u32string decoded = Dyn::Decode(text);
|
||
|
|
for (std::size_t at = 0; at + word.size() <= decoded.size(); ++at) {
|
||
|
|
if (MatchCi(decoded, at, word)) return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// seats.CREDIT_RE: the organization's credit balance is gone, or a
|
||
|
|
// spend limit was reached.
|
||
|
|
bool CreditText(std::string_view text) {
|
||
|
|
constexpr std::array<std::string_view, 9> Phrases = {
|
||
|
|
"credit balance too low", "credit balance is too low", "add funds", "reached your api usage limits", "reached your specified api usage limits",
|
||
|
|
"reached your workspace api usage limits", "reached your specified workspace api usage limits", "crossed its monthly api usage threshold",
|
||
|
|
"enforced_spend_limit_reached",
|
||
|
|
};
|
||
|
|
return std::ranges::any_of(Phrases, [&](std::string_view phrase) { return ContainsCi(text, phrase); });
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string ResultText(const Json& result) {
|
||
|
|
return Dyn::StrOr(Dyn::Get(Dyn::Or(result, Dyn::EmptyDict()), "result"), "");
|
||
|
|
}
|
||
|
|
|
||
|
|
bool TokenChar(char c) {
|
||
|
|
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || c == '-';
|
||
|
|
}
|
||
|
|
|
||
|
|
std::size_t RunOf(std::string_view s, std::size_t at, const std::function<bool(char)>& in) {
|
||
|
|
std::size_t end = at;
|
||
|
|
while (end < s.size() && in(s[end])) ++end;
|
||
|
|
return end - at;
|
||
|
|
}
|
||
|
|
|
||
|
|
// re.sub(pattern, replacement, text) for a pattern given as a matcher:
|
||
|
|
// the length of the match at a position, or 0.
|
||
|
|
std::string SubAll(std::string_view text, const std::function<std::size_t(std::string_view, std::size_t)>& match, std::string_view replacement) {
|
||
|
|
std::string out;
|
||
|
|
std::size_t at = 0;
|
||
|
|
while (at < text.size()) {
|
||
|
|
const std::size_t length = match(text, at);
|
||
|
|
if (length > 0) {
|
||
|
|
out += replacement;
|
||
|
|
at += length;
|
||
|
|
} else {
|
||
|
|
out.push_back(text[at]);
|
||
|
|
++at;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
// eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}
|
||
|
|
std::size_t Jwt(std::string_view s, std::size_t at) {
|
||
|
|
if (!s.substr(at).starts_with("eyJ")) return 0;
|
||
|
|
std::size_t i = at + 3;
|
||
|
|
for (std::int32_t part = 0; part < 3; ++part) {
|
||
|
|
if (part > 0) {
|
||
|
|
if (i >= s.size() || s[i] != '.') return 0;
|
||
|
|
++i;
|
||
|
|
}
|
||
|
|
const std::size_t run = RunOf(s, i, TokenChar);
|
||
|
|
if (run < 10) return 0;
|
||
|
|
i += run;
|
||
|
|
}
|
||
|
|
return i - at;
|
||
|
|
}
|
||
|
|
|
||
|
|
// sk-ant-[A-Za-z0-9_-]{20,}
|
||
|
|
std::size_t AnthropicKey(std::string_view s, std::size_t at) {
|
||
|
|
if (!s.substr(at).starts_with("sk-ant-")) return 0;
|
||
|
|
const std::size_t run = RunOf(s, at + 7, TokenChar);
|
||
|
|
return run >= 20 ? 7 + run : 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Bearer\s+[A-Za-z0-9._~+/=-]{16,}
|
||
|
|
std::size_t Bearer(std::string_view s, std::size_t at) {
|
||
|
|
if (!s.substr(at).starts_with("Bearer")) return 0;
|
||
|
|
const std::size_t gap = Dyn::SpaceRun(s, at + 6);
|
||
|
|
if (gap == 0) return 0;
|
||
|
|
const std::size_t run = RunOf(s, at + 6 + gap, [](char c) { return TokenChar(c) || std::string_view("._~+/=").contains(c); });
|
||
|
|
return run >= 16 ? 6 + gap + run : 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
// [A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}, as the regex engine
|
||
|
|
// backs off: the local part runs up to the @; the domain is the
|
||
|
|
// longest prefix of its run that ends in a dot and two letters or more.
|
||
|
|
std::size_t Email(std::string_view s, std::size_t at) {
|
||
|
|
auto local = [](char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || std::string_view("._%+-").contains(c); };
|
||
|
|
auto domain = [](char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '.' || c == '-'; };
|
||
|
|
auto letter = [](char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); };
|
||
|
|
const std::size_t name = RunOf(s, at, local);
|
||
|
|
if (name == 0 || at + name >= s.size() || s[at + name] != '@') return 0;
|
||
|
|
const std::size_t start = at + name + 1;
|
||
|
|
const std::size_t run = RunOf(s, start, domain);
|
||
|
|
for (std::size_t k = run; k-- > 1;) {
|
||
|
|
if (s[start + k] != '.') continue;
|
||
|
|
const std::size_t letters = RunOf(s, start + k + 1, letter);
|
||
|
|
if (letters >= 2) return name + 1 + k + 1 + letters;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
// shutil.which(name, path=...): an executable file on the path.
|
||
|
|
std::optional<std::string> Which(std::string_view name, std::string_view path) {
|
||
|
|
for (const std::string& dir : Dyn::Split(path, ":")) {
|
||
|
|
// An empty PATH entry gives the bare name.
|
||
|
|
const fs::path candidate = dir.empty() ? fs::path(name) : fs::path(dir) / name;
|
||
|
|
std::error_code error;
|
||
|
|
if (fs::is_regular_file(candidate, error) && access(candidate.c_str(), X_OK) == 0) return candidate.string();
|
||
|
|
}
|
||
|
|
return std::nullopt;
|
||
|
|
}
|
||
|
|
|
||
|
|
// _version_key: claude-code-<major>.<minor>.<patch>.
|
||
|
|
std::array<std::int64_t, 3> VersionKey(std::string_view name) {
|
||
|
|
const std::size_t at = name.find("claude-code-");
|
||
|
|
if (at == std::string_view::npos) return {0, 0, 0};
|
||
|
|
for (std::size_t from = at; from != std::string_view::npos; from = name.find("claude-code-", from + 1)) {
|
||
|
|
std::array<std::int64_t, 3> parts {};
|
||
|
|
std::size_t i = from + 12;
|
||
|
|
bool ok = true;
|
||
|
|
for (std::int32_t p = 0; p < 3 && ok; ++p) {
|
||
|
|
if (p > 0) {
|
||
|
|
if (i >= name.size() || name[i] != '.') {
|
||
|
|
ok = false;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
++i;
|
||
|
|
}
|
||
|
|
const std::size_t digits = Dyn::DecimalRun(name, i);
|
||
|
|
if (digits == 0) {
|
||
|
|
ok = false;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
parts[static_cast<std::size_t>(p)] = Dyn::ParseInt(name.substr(i, digits)).value_or(0);
|
||
|
|
i += digits;
|
||
|
|
}
|
||
|
|
if (ok) return parts;
|
||
|
|
}
|
||
|
|
return {0, 0, 0};
|
||
|
|
}
|
||
|
|
|
||
|
|
const Json& Window(const Json& seat, std::string_view key) {
|
||
|
|
return Dyn::Get(Dyn::Or(Dyn::Get(seat, "windows"), Dyn::EmptyDict()), key);
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string SeatName(const Json& seat) {
|
||
|
|
const Json& id = Dyn::At(seat, "seat");
|
||
|
|
return Dyn::Equal(id, Json(0)) ? std::string("local") : std::format("seat {}", Dyn::Str(id));
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string SeatCell(const Json& seat) {
|
||
|
|
const Json& id = Dyn::At(seat, "seat");
|
||
|
|
return Dyn::Equal(id, Json(0)) ? std::string("local") : Dyn::Str(id);
|
||
|
|
}
|
||
|
|
|
||
|
|
std::int64_t Inflight(const Json& board, const std::map<std::int32_t, std::string>& keys, const Json& seat) {
|
||
|
|
const auto key = keys.find(Dyn::At(seat, "seat").get<std::int32_t>());
|
||
|
|
if (key == keys.end() || !board.is_object()) return 0;
|
||
|
|
const Json& record = Dyn::Get(Dyn::Or(Dyn::Get(board, "seats"), Dyn::EmptyDict()), key->second);
|
||
|
|
return static_cast<std::int64_t>(AsDouble(Dyn::Get(Dyn::Or(record, Dyn::EmptyDict()), "inflight")));
|
||
|
|
}
|
||
|
|
|
||
|
|
std::vector<Json> SortedBySeat(const std::vector<Json>& seats) {
|
||
|
|
std::vector<Json> out = seats;
|
||
|
|
std::ranges::stable_sort(out, [](const Json& a, const Json& b) { return Less(Dyn::At(a, "seat"), Dyn::At(b, "seat")); });
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
double JsRound(double x) {
|
||
|
|
return std::floor(x + 0.5);
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string Fmt(const Json& value) {
|
||
|
|
if ((value.is_number() || value.is_boolean())) {
|
||
|
|
const double n = AsDouble(value);
|
||
|
|
if (std::isfinite(n) && n == std::floor(n)) return std::to_string(static_cast<std::int64_t>(n));
|
||
|
|
}
|
||
|
|
return Dyn::Str(value);
|
||
|
|
}
|
||
|
|
|
||
|
|
Json DefaultConfig() {
|
||
|
|
return {{"maxUtil", 85}, {"lensCostPct", 10}, {"waitMaxMin", 15}, {"policy", "spread"}, {"handoverPct", 90}};
|
||
|
|
}
|
||
|
|
|
||
|
|
Json ConfigFromEnv(const std::map<std::string, std::string>& env) {
|
||
|
|
Json config = DefaultConfig();
|
||
|
|
const Json defaults = DefaultConfig();
|
||
|
|
for (const auto& [key, variable] : std::array<std::pair<std::string_view, std::string_view>, 4>{{
|
||
|
|
{"maxUtil", "REVIEW_GATES_SEAT_MAX_UTIL"}, {"lensCostPct", "REVIEW_GATES_LENS_COST_PCT"},
|
||
|
|
{"waitMaxMin", "REVIEW_GATES_SEAT_WAIT_MIN"}, {"handoverPct", "REVIEW_GATES_SEAT_HANDOVER_PCT"}}}) {
|
||
|
|
const auto found = env.find(std::string(variable));
|
||
|
|
Json value = defaults.at(std::string(key));
|
||
|
|
if (found != env.end()) {
|
||
|
|
const std::optional<double> n = Dyn::ParseFloat(found->second);
|
||
|
|
if (n && !Dyn::Strip(found->second).empty() && std::isfinite(*n) && *n >= 0) value = IntIfWhole(*n);
|
||
|
|
}
|
||
|
|
config[std::string(key)] = value;
|
||
|
|
}
|
||
|
|
config["policy"] = EnvOf(env, "REVIEW_GATES_SEAT_POLICY") == "pack" ? "pack" : "spread";
|
||
|
|
return config;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::vector<std::pair<std::int32_t, std::string>> SeatTokensFromEnv(const std::map<std::string, std::string>& env) {
|
||
|
|
std::vector<std::pair<std::int32_t, std::string>> out;
|
||
|
|
for (std::int32_t i = 1; i <= MaxSeats; ++i) {
|
||
|
|
std::string token = Dyn::Strip(EnvOf(env, std::format("CLAUDE_CODE_OAUTH_TOKEN{}", i)));
|
||
|
|
if (token.empty() && i == 1) token = Dyn::Strip(EnvOf(env, "CLAUDE_CODE_OAUTH_TOKEN"));
|
||
|
|
if (!token.empty()) out.emplace_back(i, token);
|
||
|
|
}
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::vector<std::pair<std::int32_t, std::string>> ApiKeysFromEnv(const std::map<std::string, std::string>& env) {
|
||
|
|
std::vector<std::pair<std::int32_t, std::string>> out;
|
||
|
|
for (std::int32_t i = 1; i <= MaxSeats; ++i) {
|
||
|
|
const std::string key = Dyn::Strip(EnvOf(env, std::format("ANTHROPIC_API_KEY{}", i)));
|
||
|
|
if (!key.empty()) out.emplace_back(i, key);
|
||
|
|
}
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::vector<std::pair<std::int32_t, std::string>> OpenRouterKeysFromEnv(const std::map<std::string, std::string>& env) {
|
||
|
|
std::vector<std::pair<std::int32_t, std::string>> out;
|
||
|
|
for (std::int32_t i = 1; i <= MaxSeats; ++i) {
|
||
|
|
std::string key = Dyn::Strip(EnvOf(env, std::format("OPENROUTER_KEY{}", i)));
|
||
|
|
if (key.empty() && i == 1) key = Dyn::Strip(EnvOf(env, "OPENROUTER_KEY"));
|
||
|
|
if (!key.empty()) out.emplace_back(i, key);
|
||
|
|
}
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::map<std::string, std::string> SeatTokensFromEnvFile(std::string_view text) {
|
||
|
|
std::map<std::string, std::string> env;
|
||
|
|
for (const std::string& raw : Dyn::Split(text, "\n")) {
|
||
|
|
// ^\s*(?:export\s+)?(NAME)\s*=\s*(.*?)\s*$
|
||
|
|
std::string_view line(raw);
|
||
|
|
std::size_t at = Dyn::SpaceRun(line, 0);
|
||
|
|
if (line.substr(at).starts_with("export")) {
|
||
|
|
const std::size_t gap = Dyn::SpaceRun(line, at + 6);
|
||
|
|
if (gap > 0) at += 6 + gap;
|
||
|
|
}
|
||
|
|
std::string name;
|
||
|
|
for (const std::string_view stem : {std::string_view("CLAUDE_CODE_OAUTH_TOKEN"), std::string_view("ANTHROPIC_API_KEY"), std::string_view("OPENROUTER_KEY")}) {
|
||
|
|
if (!line.substr(at).starts_with(stem)) continue;
|
||
|
|
std::size_t end = at + stem.size();
|
||
|
|
const bool digit = end < line.size() && line[end] >= '1' && line[end] <= '6';
|
||
|
|
if (digit) ++end;
|
||
|
|
if (stem == "ANTHROPIC_API_KEY" && !digit) break;
|
||
|
|
name = std::string(line.substr(at, end - at));
|
||
|
|
at = end;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
if (name.empty()) continue;
|
||
|
|
at += Dyn::SpaceRun(line, at);
|
||
|
|
if (at >= line.size() || line[at] != '=') continue;
|
||
|
|
++at;
|
||
|
|
at += Dyn::SpaceRun(line, at);
|
||
|
|
std::string value = Dyn::RStrip(line.substr(at));
|
||
|
|
if (value.size() >= 2 && (value.front() == '"' || value.front() == '\'') && value.back() == value.front()) value = value.substr(1, value.size() - 2);
|
||
|
|
env[name] = value;
|
||
|
|
}
|
||
|
|
return env;
|
||
|
|
}
|
||
|
|
|
||
|
|
Json Pct(const Json& utilization) {
|
||
|
|
const std::optional<double> n = FloatOf(utilization);
|
||
|
|
if (!n || !std::isfinite(*n) || *n < 0) return 0;
|
||
|
|
return IntIfWhole(JsRound(*n * 1000) / 10);
|
||
|
|
}
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
std::int64_t IntOf(const Json& value) {
|
||
|
|
const std::optional<double> n = FloatOf(value);
|
||
|
|
if (!n || !std::isfinite(*n)) return 0;
|
||
|
|
return static_cast<std::int64_t>(*n);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Json ParseRateLimitInfo(const Json& raw) {
|
||
|
|
if (!raw.is_object()) return nullptr;
|
||
|
|
Json windows = Json::object();
|
||
|
|
const Json& unified = Dyn::Get(raw, "unifiedWindows");
|
||
|
|
if (unified.is_object()) {
|
||
|
|
for (auto it = unified.begin(); it != unified.end(); ++it) {
|
||
|
|
if (!it.value().is_object()) continue;
|
||
|
|
windows[it.key()] = {{"utilization", Pct(Dyn::Get(it.value(), "utilization"))}, {"resetsAt", IntOf(Dyn::Get(it.value(), "resetsAt"))}};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const std::int64_t resets = IntOf(Dyn::Get(raw, "resetsAt"));
|
||
|
|
return {
|
||
|
|
{"status", Dyn::StrOr(Dyn::Get(raw, "status"), "")},
|
||
|
|
{"rateLimitType", Dyn::Truthy(Dyn::Get(raw, "rateLimitType")) ? Json(Dyn::Str(Dyn::At(raw, "rateLimitType"))) : Json()},
|
||
|
|
{"resetsAt", resets ? Json(resets) : Json()},
|
||
|
|
{"windows", std::move(windows)},
|
||
|
|
// overageDisabledReason, e.g. out_of_credits: a limit is a hard 429,
|
||
|
|
// not a charge.
|
||
|
|
{"overageReason", Dyn::Truthy(Dyn::Get(raw, "overageDisabledReason")) ? Json(Dyn::Str(Dyn::At(raw, "overageDisabledReason"))) : Json()},
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
Json ParseStream(std::string_view text) {
|
||
|
|
Json result;
|
||
|
|
Json rate;
|
||
|
|
Json session;
|
||
|
|
if (Dyn::Strip(text).empty()) return {{"result", nullptr}, {"rateLimit", nullptr}, {"sessionId", nullptr}};
|
||
|
|
for (const std::string& raw : Dyn::Split(text, "\n")) {
|
||
|
|
const std::string line = Dyn::Strip(raw);
|
||
|
|
if (line.empty()) continue;
|
||
|
|
Json o;
|
||
|
|
try {
|
||
|
|
o = ParseLoose(line);
|
||
|
|
} catch (const std::exception&) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
if (!o.is_object()) continue;
|
||
|
|
const Json& id = Dyn::Get(o, "session_id");
|
||
|
|
if (!Dyn::Truthy(session) && id.is_string()) {
|
||
|
|
const std::string& value = id.get_ref<const std::string&>();
|
||
|
|
const bool shaped = value.size() == 36 && std::ranges::all_of(value, [](char c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || c == '-'; });
|
||
|
|
if (shaped) session = id;
|
||
|
|
}
|
||
|
|
if (Dyn::Get(o, "type") == "result") {
|
||
|
|
result = o;
|
||
|
|
} else if (Dyn::Get(o, "type") == "rate_limit_event") {
|
||
|
|
const Json parsed = ParseRateLimitInfo(Dyn::Get(o, "rate_limit_info"));
|
||
|
|
if (Dyn::Truthy(parsed)) rate = parsed;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return {{"result", result}, {"rateLimit", rate}, {"sessionId", session}};
|
||
|
|
}
|
||
|
|
|
||
|
|
std::vector<std::string> ProbeArgs(std::string_view model) {
|
||
|
|
return {"--print", "ok", "--system-prompt", "Reply ok.", "--model", std::string(model), "--max-turns", "1", "--tools", "",
|
||
|
|
"--strict-mcp-config", "--disable-slash-commands", "--output-format", "stream-json", "--verbose"};
|
||
|
|
}
|
||
|
|
|
||
|
|
std::map<std::string, std::string> ProbeEnv(std::string_view token, const std::string& configDir, const std::map<std::string, std::string>& base) {
|
||
|
|
const std::string& home = EnvOf(base, "HOME");
|
||
|
|
return {
|
||
|
|
{"PATH", EnvOf(base, "PATH")},
|
||
|
|
{"HOME", home.empty() ? fs::temp_directory_path().string() : home},
|
||
|
|
{"CLAUDE_CONFIG_DIR", configDir},
|
||
|
|
{"CLAUDE_CODE_OAUTH_TOKEN", std::string(token)},
|
||
|
|
{"MAX_THINKING_TOKENS", "0"},
|
||
|
|
{"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC", "1"},
|
||
|
|
{"DISABLE_AUTOUPDATER", "1"},
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
std::map<std::string, std::string> LocalEnv(const std::map<std::string, std::string>& base) {
|
||
|
|
std::map<std::string, std::string> env = base;
|
||
|
|
for (const std::string_view key : {"CLAUDE_CODE_OAUTH_TOKEN", "ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN"}) env.erase(std::string(key));
|
||
|
|
env["MAX_THINKING_TOKENS"] = "0";
|
||
|
|
env["CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC"] = "1";
|
||
|
|
env["DISABLE_AUTOUPDATER"] = "1";
|
||
|
|
return env;
|
||
|
|
}
|
||
|
|
|
||
|
|
fs::path WriteApiKeyApproval(const fs::path& configDir, std::string_view key) {
|
||
|
|
const fs::path path = configDir / ".claude.json";
|
||
|
|
if (fs::exists(path)) return path;
|
||
|
|
const std::size_t length = Dyn::Length(key);
|
||
|
|
const std::string tail = Dyn::Slice(key, static_cast<std::int64_t>(length) - std::min<std::int64_t>(20, static_cast<std::int64_t>(length)));
|
||
|
|
const Json approval = {{"hasCompletedOnboarding", true}, {"customApiKeyResponses", {{"approved", {tail}}, {"rejected", Json::array()}}}};
|
||
|
|
// O_EXCL: two passes of a fresh seat write it in the same instant; the
|
||
|
|
// contents are the same, so the one that loses leaves the other's.
|
||
|
|
const fs::path temp = std::format("{}.{}.{}.tmp", path.string(), Platform::PublishedPid(), TokenHex(4));
|
||
|
|
{
|
||
|
|
std::ofstream out(temp, std::ios::binary);
|
||
|
|
out << DumpSpaced(approval);
|
||
|
|
}
|
||
|
|
fs::permissions(temp, fs::perms::owner_read | fs::perms::owner_write, fs::perm_options::replace);
|
||
|
|
std::error_code error;
|
||
|
|
if (!fs::exists(path)) fs::rename(temp, path, error);
|
||
|
|
fs::remove(temp, error);
|
||
|
|
return path;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::map<std::string, std::string> ApiEnv(std::string_view key, const std::string& configDir, const std::map<std::string, std::string>& base) {
|
||
|
|
// Minimal by construction: no CLAUDE_CODE_ENTRYPOINT of a launcher (it
|
||
|
|
// turns the subscriber credits' answer into "Credit balance is too
|
||
|
|
// low").
|
||
|
|
std::map<std::string, std::string> env = ProbeEnv("", configDir, base);
|
||
|
|
env.erase("CLAUDE_CODE_OAUTH_TOKEN");
|
||
|
|
env["ANTHROPIC_API_KEY"] = std::string(key);
|
||
|
|
WriteApiKeyApproval(configDir, key);
|
||
|
|
return env;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool LooksLikeCreditExhaustion(const Json& result, std::string_view text) {
|
||
|
|
return CreditText(ResultText(result)) || CreditText(text);
|
||
|
|
}
|
||
|
|
|
||
|
|
bool LooksLikeAuthFailure(const Json& result, std::string_view text) {
|
||
|
|
const Json& status = Dyn::Get(Dyn::Or(result, Dyn::EmptyDict()), "api_error_status");
|
||
|
|
if (Dyn::Equal(status, Json(401)) || Dyn::Equal(status, Json(403))) return true;
|
||
|
|
return Stream::AuthText(ResultText(result)) || Stream::AuthText(text);
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string ClipReason(std::string_view input, std::span<const std::string> secrets) {
|
||
|
|
std::string s(input);
|
||
|
|
for (const std::string& secret : secrets) {
|
||
|
|
if (!secret.empty()) s = Dyn::Replace(s, secret, "[redacted:token]");
|
||
|
|
}
|
||
|
|
s = SubAll(s, Jwt, "[redacted:jwt]");
|
||
|
|
s = SubAll(s, AnthropicKey, "[redacted:token]");
|
||
|
|
s = SubAll(s, Bearer, "Bearer [redacted]");
|
||
|
|
s = SubAll(s, Email, "[redacted:email]");
|
||
|
|
// re.sub(r'\s+', ' ', s).strip()[:160]
|
||
|
|
std::string collapsed;
|
||
|
|
for (std::size_t at = 0; at < s.size();) {
|
||
|
|
const std::size_t gap = Dyn::SpaceRun(s, at);
|
||
|
|
if (gap > 0) {
|
||
|
|
collapsed.push_back(' ');
|
||
|
|
at += gap;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
const std::size_t used = Dyn::CodePointAt(s, at).second;
|
||
|
|
collapsed.append(s, at, used);
|
||
|
|
at += used;
|
||
|
|
}
|
||
|
|
return Dyn::Slice(Dyn::Strip(collapsed), 0, 160);
|
||
|
|
}
|
||
|
|
|
||
|
|
Json ProbeSeat(std::int32_t seat, const std::string& token, const ProbeOptions& options) {
|
||
|
|
const std::string claude = options.claudeBin.empty() ? FindClaudeBin() : options.claudeBin;
|
||
|
|
Json base = {{"seat", seat}, {"probedWith", options.model}, {"windows", Json::object()}};
|
||
|
|
auto with = [&](Json extra) {
|
||
|
|
Json out = base;
|
||
|
|
for (auto it = extra.begin(); it != extra.end(); ++it) out[it.key()] = it.value();
|
||
|
|
return out;
|
||
|
|
};
|
||
|
|
const std::vector<std::string> secrets = {token};
|
||
|
|
std::optional<fs::path> configDir;
|
||
|
|
if (!options.local) {
|
||
|
|
for (;;) {
|
||
|
|
configDir = fs::temp_directory_path() / std::format("rg-seat-{}-{}", seat, TokenHex(4));
|
||
|
|
if (fs::create_directory(*configDir)) break;
|
||
|
|
}
|
||
|
|
fs::permissions(*configDir, fs::perms::owner_all, fs::perm_options::replace);
|
||
|
|
}
|
||
|
|
const std::map<std::string, std::string> parent = Platform::Environment();
|
||
|
|
const std::map<std::string, std::string> env = options.local ? LocalEnv(parent) : (options.api ? ApiEnv(token, configDir->string(), parent) : ProbeEnv(token, configDir->string(), parent));
|
||
|
|
std::vector<std::string> argv = {claude};
|
||
|
|
std::ranges::move(ProbeArgs(options.model), std::back_inserter(argv));
|
||
|
|
if (options.local) {
|
||
|
|
argv.emplace_back("--setting-sources");
|
||
|
|
argv.emplace_back("");
|
||
|
|
argv.emplace_back("--no-session-persistence");
|
||
|
|
}
|
||
|
|
auto cleanup = [&] {
|
||
|
|
std::error_code error;
|
||
|
|
if (configDir) fs::remove_all(*configDir, error);
|
||
|
|
};
|
||
|
|
// A binary that cannot be started is said here, as an OSError, before
|
||
|
|
// the supervisor would only report it as an exit code.
|
||
|
|
const std::string path = claude.contains('/') ? claude : Which(claude, EnvOf(env, "PATH")).value_or("");
|
||
|
|
if (path.empty() || access(path.c_str(), X_OK) != 0) {
|
||
|
|
cleanup();
|
||
|
|
return with({{"state", "unknown"}, {"error", ClipReason(std::format("probe spawn failed: {}: {}", Dyn::ErrnoText(2), Dyn::Repr(claude)), secrets)}});
|
||
|
|
}
|
||
|
|
Process::Captured run;
|
||
|
|
try {
|
||
|
|
run = Process::Run(Process::Supervised(argv), std::nullopt, std::chrono::milliseconds(static_cast<std::int64_t>(options.timeoutS * 1000)), {}, env);
|
||
|
|
} catch (const std::exception& error) {
|
||
|
|
cleanup();
|
||
|
|
return with({{"state", "unknown"}, {"error", ClipReason(std::format("probe spawn failed: {}", error.what()), secrets)}});
|
||
|
|
}
|
||
|
|
cleanup();
|
||
|
|
if (run.timedOut) return with({{"state", "unknown"}, {"error", std::format("probe timeout after {} ms", static_cast<std::int64_t>(options.timeoutS * 1000))}});
|
||
|
|
const std::string out = Dyn::UniversalNewlines(Dyn::DecodeReplace(run.out));
|
||
|
|
const std::string err = Dyn::UniversalNewlines(Dyn::DecodeReplace(run.err));
|
||
|
|
const std::int32_t code = Process::SupervisedReturnCode(run.returnCode);
|
||
|
|
const Json parsed = ParseStream(out);
|
||
|
|
const Json& rate = parsed.at("rateLimit");
|
||
|
|
const Json& result = parsed.at("result");
|
||
|
|
const Json cost = Dyn::Get(Dyn::Or(result, Dyn::EmptyDict()), "total_cost_usd");
|
||
|
|
auto said = [&](std::string_view fallback) {
|
||
|
|
const std::string text = ResultText(result);
|
||
|
|
return !text.empty() ? text : (!err.empty() ? err : std::string(fallback));
|
||
|
|
};
|
||
|
|
if (options.api) {
|
||
|
|
// No rate windows on a Console key: the probe's result line is the
|
||
|
|
// whole reading.
|
||
|
|
if (Dyn::Truthy(result) && !Dyn::Truthy(Dyn::Get(result, "is_error"))) return with({{"state", "ok"}, {"costUsd", cost}});
|
||
|
|
if (LooksLikeCreditExhaustion(result, err)) return with({{"state", "out"}, {"error", ClipReason(said("credit balance too low"), secrets)}});
|
||
|
|
if (LooksLikeAuthFailure(result, err)) return with({{"state", "invalid"}, {"error", ClipReason(said("authentication failed"), secrets)}});
|
||
|
|
return with({{"state", "unknown"}, {"error", ClipReason(std::format("probe failed (exit {}) {}", code, said("")), secrets)}});
|
||
|
|
}
|
||
|
|
if (Dyn::Truthy(rate) && Dyn::Truthy(rate.at("windows"))) {
|
||
|
|
const auto [util, binding] = SeatUtil(rate.at("windows"));
|
||
|
|
Json resets = Dyn::Get(Dyn::Or(Dyn::Get(rate.at("windows"), "five_hour"), Dyn::EmptyDict()), "resetsAt");
|
||
|
|
if (!Dyn::Truthy(resets)) resets = Dyn::Get(rate, "resetsAt");
|
||
|
|
if (rate.at("status") == "rejected") {
|
||
|
|
// The one-word probe was refused: the seat takes nothing until
|
||
|
|
// its reset, whatever its windows read.
|
||
|
|
std::string why = std::format("{} rejected", Dyn::StrOr(Dyn::Get(rate, "rateLimitType"), "window"));
|
||
|
|
if (Dyn::Truthy(Dyn::Get(rate, "overageReason"))) why += std::format(", overage {}", Dyn::Str(rate.at("overageReason")));
|
||
|
|
const Json limitedUtil = AsDouble(util) > 100 ? util : Json(100);
|
||
|
|
return with({{"state", "limited"}, {"windows", rate.at("windows")}, {"util", limitedUtil},
|
||
|
|
{"binding", Dyn::Truthy(Dyn::Get(rate, "rateLimitType")) ? rate.at("rateLimitType") : Json(binding)}, {"resetsAt", resets}, {"error", why}});
|
||
|
|
}
|
||
|
|
return with({{"state", "ok"}, {"windows", rate.at("windows")}, {"util", util}, {"binding", binding}, {"resetsAt", resets}});
|
||
|
|
}
|
||
|
|
if (LooksLikeAuthFailure(result, err)) return with({{"state", "invalid"}, {"error", ClipReason(said("authentication failed"), secrets)}});
|
||
|
|
return with({{"state", "unknown"}, {"error", ClipReason(std::format("no rate_limit_event (exit {}) {}", code, said("")), secrets)}});
|
||
|
|
}
|
||
|
|
|
||
|
|
double Quantize(double util) {
|
||
|
|
return JsRound(util / RankQuantum) * RankQuantum;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::pair<Json, std::string> BindingWindow(const Json& windows) {
|
||
|
|
Json util = 0;
|
||
|
|
std::string binding;
|
||
|
|
for (auto it = windows.begin(); it != windows.end(); ++it) {
|
||
|
|
const Json& value = Dyn::At(it.value(), "utilization");
|
||
|
|
if (Less(util, value) || binding.empty()) {
|
||
|
|
util = value;
|
||
|
|
binding = it.key();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return {util, binding};
|
||
|
|
}
|
||
|
|
|
||
|
|
std::pair<Json, std::string> SeatUtil(const Json& windows) {
|
||
|
|
const auto [util, binding] = BindingWindow(windows);
|
||
|
|
return {Json(static_cast<std::int64_t>(Quantize(AsDouble(util)))), binding};
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string ModelFamily(std::string_view model) {
|
||
|
|
const std::u32string text = Dyn::Decode(model);
|
||
|
|
for (std::size_t at = 0; at < text.size(); ++at) {
|
||
|
|
for (const std::string_view family : ModelFamilies) {
|
||
|
|
if (MatchCi(text, at, family)) return std::string(family);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return "";
|
||
|
|
}
|
||
|
|
|
||
|
|
bool IsFableWindow(std::string_view key) {
|
||
|
|
return key == FableWindow || ModelFamily(key) == "fable";
|
||
|
|
}
|
||
|
|
|
||
|
|
const Json* FableWindowOf(const Json& seat) {
|
||
|
|
const Json& windows = Dyn::Or(Dyn::Get(seat, "windows"), Dyn::EmptyDict());
|
||
|
|
for (auto it = windows.begin(); it != windows.end(); ++it) {
|
||
|
|
if (IsFableWindow(it.key())) return &it.value();
|
||
|
|
}
|
||
|
|
return nullptr;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool LongWindowsOk(const Json& seat) {
|
||
|
|
const Json& windows = Dyn::Or(Dyn::Get(seat, "windows"), Dyn::EmptyDict());
|
||
|
|
for (auto it = windows.begin(); it != windows.end(); ++it) {
|
||
|
|
if (it.key() == "five_hour" || IsFableWindow(it.key())) continue;
|
||
|
|
if (AsDouble(Dyn::At(it.value(), "utilization")) >= LongWindowCutoff) return false;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool IsEligible(const Json& seat, const Json& config) {
|
||
|
|
return Dyn::At(seat, "state") == "ok" && NumberOr0(Dyn::Get(seat, "util")) <= AsDouble(config.at("maxUtil")) && LongWindowsOk(seat);
|
||
|
|
}
|
||
|
|
|
||
|
|
bool FableOpen(const Json& seat, const Json& config) {
|
||
|
|
const Json& fable = Dyn::Or(Dyn::Get(seat, "fable"), Dyn::EmptyDict());
|
||
|
|
if (Dyn::Get(fable, "state") == "limited" || (Dyn::At(seat, "state") == "limited" && IsFableWindow(Dyn::StrOr(Dyn::Get(seat, "binding"), "")))) return false;
|
||
|
|
const Json* w = FableWindowOf(seat);
|
||
|
|
if (!w) return true;
|
||
|
|
const double u = AsDouble(Dyn::At(*w, "utilization"));
|
||
|
|
return u <= AsDouble(config.at("maxUtil")) && u < LongWindowCutoff;
|
||
|
|
}
|
||
|
|
|
||
|
|
Json PickNext(const std::vector<Json>& seats, const Json& config) {
|
||
|
|
if (seats.empty()) return {{"kind", "stop"}, {"status", "error"}, {"reason", "no seat token configured"}};
|
||
|
|
std::vector<Json> usable;
|
||
|
|
for (const Json& s : SortedBySeat(seats)) {
|
||
|
|
if (Dyn::At(s, "state") != "invalid") usable.push_back(s);
|
||
|
|
}
|
||
|
|
if (usable.empty()) {
|
||
|
|
std::vector<std::string> ids;
|
||
|
|
for (const Json& s : seats) ids.push_back(Dyn::Str(Dyn::At(s, "seat")));
|
||
|
|
return {{"kind", "stop"}, {"status", "error"}, {"reason", std::format("no valid seat token (seat{} {} rejected)", seats.size() == 1 ? "" : "s", Dyn::Join(ids, ", "))}};
|
||
|
|
}
|
||
|
|
if (std::ranges::all_of(usable, [](const Json& s) { return Dyn::At(s, "state") == "unknown"; })) {
|
||
|
|
return {{"kind", "seat"}, {"seat", usable[0].at("seat")}, {"source", "round-robin"}};
|
||
|
|
}
|
||
|
|
const double maxUtil = AsDouble(config.at("maxUtil"));
|
||
|
|
const double cost = AsDouble(config.at("lensCostPct"));
|
||
|
|
auto projected = [&](const Json& s) {
|
||
|
|
return Dyn::At(s, "state") == "unknown" || Dyn::Get(s, "util").is_null() ? maxUtil : AsDouble(Dyn::At(s, "util"));
|
||
|
|
};
|
||
|
|
auto longOk = [&](const Json& s) { return Dyn::At(s, "state") == "unknown" || LongWindowsOk(s); };
|
||
|
|
std::vector<const Json*> pool;
|
||
|
|
for (const Json& s : usable) {
|
||
|
|
if (projected(s) + cost <= maxUtil && longOk(s)) pool.push_back(&s);
|
||
|
|
}
|
||
|
|
const Json* chosen = nullptr;
|
||
|
|
auto least = [&](const std::vector<const Json*>& candidates) {
|
||
|
|
const Json* best = candidates.front();
|
||
|
|
for (const Json* s : candidates) {
|
||
|
|
if (projected(*s) < projected(*best)) best = s;
|
||
|
|
}
|
||
|
|
return best;
|
||
|
|
};
|
||
|
|
if (!pool.empty()) {
|
||
|
|
chosen = config.at("policy") == "pack" ? pool.front() : least(pool);
|
||
|
|
} else {
|
||
|
|
// Nothing fits under the ceiling: the most headroom among the seats
|
||
|
|
// whose long windows are open, else any.
|
||
|
|
std::vector<const Json*> candidates;
|
||
|
|
for (const Json& s : usable) {
|
||
|
|
if (longOk(s)) candidates.push_back(&s);
|
||
|
|
}
|
||
|
|
if (candidates.empty()) {
|
||
|
|
for (const Json& s : usable) candidates.push_back(&s);
|
||
|
|
}
|
||
|
|
chosen = least(candidates);
|
||
|
|
}
|
||
|
|
return {{"kind", "seat"}, {"seat", chosen->at("seat")}, {"source", "probe"}};
|
||
|
|
}
|
||
|
|
|
||
|
|
bool NeedsHaiku(const Json& seat) {
|
||
|
|
const Json& state = Dyn::At(seat, "state");
|
||
|
|
return state == "unknown" || state == "invalid" || (state == "limited" && IsFableWindow(Dyn::StrOr(Dyn::Get(seat, "binding"), "")));
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string FableState(const Json& fable) {
|
||
|
|
if (Dyn::At(fable, "state") == "limited") return IsFableWindow(Dyn::StrOr(Dyn::Get(fable, "binding"), "")) ? "limited" : "ok";
|
||
|
|
return Dyn::Str(Dyn::At(fable, "state"));
|
||
|
|
}
|
||
|
|
|
||
|
|
Json MergeFableProbe(Json base, const Json& fable) {
|
||
|
|
if (const Json* w = FableWindowOf(fable)) {
|
||
|
|
Json windows = Json::object();
|
||
|
|
const Json& own = Dyn::Or(Dyn::Get(base, "windows"), Dyn::EmptyDict());
|
||
|
|
for (auto it = own.begin(); it != own.end(); ++it) {
|
||
|
|
if (!IsFableWindow(it.key())) windows[it.key()] = it.value();
|
||
|
|
}
|
||
|
|
windows[std::string(FableWindow)] = *w;
|
||
|
|
base["windows"] = std::move(windows);
|
||
|
|
}
|
||
|
|
if (Dyn::At(base, "state") == "ok") {
|
||
|
|
// The seat's utilisation is its own windows' — the Fable window
|
||
|
|
// gates Fable work only (FableOpen): a drained Fable window makes an
|
||
|
|
// Opus seat, never an idle one.
|
||
|
|
Json own = Json::object();
|
||
|
|
for (auto it = base.at("windows").begin(); it != base.at("windows").end(); ++it) {
|
||
|
|
if (!IsFableWindow(it.key())) own[it.key()] = it.value();
|
||
|
|
}
|
||
|
|
const auto [util, binding] = SeatUtil(own);
|
||
|
|
base["util"] = util;
|
||
|
|
base["binding"] = binding;
|
||
|
|
}
|
||
|
|
base["fable"] = {{"state", FableState(fable)}};
|
||
|
|
const Json& state = Dyn::At(fable, "state");
|
||
|
|
if (state != "ok" && state != "limited" && Dyn::Truthy(Dyn::Get(fable, "error"))) base["fable"]["error"] = fable.at("error");
|
||
|
|
return base;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string FindClaudeBin(const std::map<std::string, std::string>& env, const std::string& home) {
|
||
|
|
for (const std::string_view variable : {"CLAUDE_BIN", "REVIEW_GATES_CLAUDE_BIN"}) {
|
||
|
|
if (const std::string& value = EnvOf(env, std::string(variable)); !value.empty()) return value;
|
||
|
|
}
|
||
|
|
if (const std::string& path = EnvOf(env, "PATH"); !path.empty()) {
|
||
|
|
if (const std::optional<std::string> found = Which("claude", path)) return *found;
|
||
|
|
}
|
||
|
|
std::vector<std::string> found;
|
||
|
|
for (const std::string_view extensions : {".vscode-oss/extensions", ".vscode/extensions", ".vscode-insiders/extensions", ".cursor/extensions"}) {
|
||
|
|
const fs::path dir = fs::path(home) / extensions;
|
||
|
|
std::error_code error;
|
||
|
|
if (!fs::is_directory(dir, error)) continue;
|
||
|
|
std::vector<std::string> names;
|
||
|
|
for (const fs::directory_entry& entry : fs::directory_iterator(dir, error)) {
|
||
|
|
const std::string name = entry.path().filename().string();
|
||
|
|
if (name.starts_with("anthropic.claude-code-")) names.push_back(name);
|
||
|
|
}
|
||
|
|
std::ranges::stable_sort(names, [](const std::string& a, const std::string& b) { return VersionKey(a) > VersionKey(b); });
|
||
|
|
for (const std::string& name : names) {
|
||
|
|
const fs::path binary = dir / name / "resources" / "native-binary" / "claude";
|
||
|
|
if (fs::exists(binary, error)) found.push_back(binary.string());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for (const fs::path local : {fs::path(home) / ".claude" / "local" / "claude", fs::path(home) / ".local" / "bin" / "claude"}) {
|
||
|
|
std::error_code error;
|
||
|
|
if (fs::exists(local, error)) found.push_back(local.string());
|
||
|
|
}
|
||
|
|
return found.empty() ? std::string("claude") : found.front();
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string FindClaudeBin() {
|
||
|
|
return FindClaudeBin(Platform::Environment(), Platform::Home());
|
||
|
|
}
|
||
|
|
|
||
|
|
Zone ZoneNamed(std::string_view name) {
|
||
|
|
// ZoneInfo(name): a zone the system's zoneinfo holds, else none.
|
||
|
|
std::error_code error;
|
||
|
|
const bool known = name == "UTC" || fs::is_regular_file(fs::path("/usr/share/zoneinfo") / name, error);
|
||
|
|
return {known ? std::string(name) : std::string(), std::string(name)};
|
||
|
|
}
|
||
|
|
|
||
|
|
Zone LocalZone() {
|
||
|
|
std::string name = Dyn::LStrip(Platform::GetEnv("TZ").value_or(""), ":");
|
||
|
|
if (name.empty()) {
|
||
|
|
std::error_code error;
|
||
|
|
const std::string target = fs::weakly_canonical("/etc/localtime", error).string();
|
||
|
|
const std::size_t at = target.find("/zoneinfo/");
|
||
|
|
if (at != std::string::npos) name = target.substr(at + 10);
|
||
|
|
}
|
||
|
|
if (!name.empty()) {
|
||
|
|
Zone zone = ZoneNamed(name);
|
||
|
|
if (!zone.name.empty()) return zone;
|
||
|
|
}
|
||
|
|
return {"", name.empty() ? Platform::LocalTime("%Z") : name};
|
||
|
|
}
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
// The broken-down time of `t` in the zone: the C library's, with TZ
|
||
|
|
// set to the zone's name for the call.
|
||
|
|
std::tm InZone(std::time_t t, const Zone& zone) {
|
||
|
|
static std::mutex lock;
|
||
|
|
const std::lock_guard guard(lock);
|
||
|
|
std::tm out {};
|
||
|
|
if (zone.name.empty()) {
|
||
|
|
localtime_r(&t, &out);
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
const char* before = getenv("TZ");
|
||
|
|
const std::optional<std::string> saved = before ? std::optional<std::string>(before) : std::nullopt;
|
||
|
|
setenv("TZ", zone.name.c_str(), 1);
|
||
|
|
tzset();
|
||
|
|
localtime_r(&t, &out);
|
||
|
|
if (saved) {
|
||
|
|
setenv("TZ", saved->c_str(), 1);
|
||
|
|
} else {
|
||
|
|
unsetenv("TZ");
|
||
|
|
}
|
||
|
|
tzset();
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string FormatWhen(double epoch, double now, const Zone& zone) {
|
||
|
|
const auto diffMin = static_cast<std::int64_t>(JsRound((epoch - now) / 60));
|
||
|
|
std::string relative;
|
||
|
|
if (diffMin <= 0) {
|
||
|
|
relative = "now";
|
||
|
|
} else if (diffMin < 60) {
|
||
|
|
relative = std::format("in {} min", diffMin);
|
||
|
|
} else if (diffMin < 48 * 60) {
|
||
|
|
relative = std::format("in {}h {:02d}m", diffMin / 60, diffMin % 60);
|
||
|
|
} else {
|
||
|
|
relative = std::format("in {}d {}h", diffMin / 1440, (diffMin % 1440) / 60);
|
||
|
|
}
|
||
|
|
const std::tm d = InZone(static_cast<std::time_t>(std::floor(epoch)), zone);
|
||
|
|
std::string clock = std::format("{:02d}:{:02d}", d.tm_hour, d.tm_min);
|
||
|
|
if (epoch - now > 24 * 3600) {
|
||
|
|
// Monday is 0 here; tm_wday counts from Sunday.
|
||
|
|
const auto index = static_cast<std::size_t>((d.tm_wday + 6) % 7);
|
||
|
|
clock = std::format("{} {:02d} {}, {}", Weekdays[index], d.tm_mday, Months[static_cast<std::size_t>(d.tm_mon)], clock);
|
||
|
|
}
|
||
|
|
return std::format("{} ({})", clock, relative);
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string RenderTable(const std::vector<std::vector<std::string>>& rows) {
|
||
|
|
std::vector<std::size_t> widths(rows.at(0).size(), 0);
|
||
|
|
for (const std::vector<std::string>& row : rows) {
|
||
|
|
for (std::size_t i = 0; i < widths.size(); ++i) widths[i] = std::max(widths[i], Dyn::Length(row.at(i)));
|
||
|
|
}
|
||
|
|
auto line = [&](const std::vector<std::string>& row) {
|
||
|
|
std::vector<std::string> cells;
|
||
|
|
for (std::size_t i = 0; i < widths.size(); ++i) cells.push_back(Dyn::LJust(row[i], widths[i]));
|
||
|
|
return std::format("| {} |", Dyn::Join(cells, " | "));
|
||
|
|
};
|
||
|
|
std::vector<std::string> out = {line(rows[0])};
|
||
|
|
std::vector<std::string> dashes;
|
||
|
|
for (const std::size_t width : widths) dashes.emplace_back(width + 2, '-');
|
||
|
|
out.push_back(std::format("|{}|", Dyn::Join(dashes, "|")));
|
||
|
|
for (std::size_t r = 1; r < rows.size(); ++r) out.push_back(line(rows[r]));
|
||
|
|
return Dyn::Join(out, "\n");
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string RenderStatusReport(const std::vector<Json>& seats, const Json& config, double now, const Zone& zone, const Json& board,
|
||
|
|
const std::map<std::int32_t, std::string>& keys) {
|
||
|
|
const std::vector<Json> ordered = SortedBySeat(seats);
|
||
|
|
const bool anyInflight = std::ranges::any_of(ordered, [&](const Json& s) { return Inflight(board, keys, s) != 0; });
|
||
|
|
std::vector<std::vector<std::string>> rows;
|
||
|
|
std::vector<std::string> header = {"seat", "5h", "resets", "7d", "resets", "fable 7d"};
|
||
|
|
if (anyInflight) header.emplace_back("in flight");
|
||
|
|
rows.push_back(header);
|
||
|
|
for (const Json& s : ordered) {
|
||
|
|
auto w = [&](std::string_view k) {
|
||
|
|
if (!Dyn::Has(Dyn::Or(Dyn::Get(s, "windows"), Dyn::EmptyDict()), k)) return std::string("—");
|
||
|
|
return std::format("{}%", Fmt(Dyn::At(Window(s, k), "utilization")));
|
||
|
|
};
|
||
|
|
auto when = [&](std::string_view k) {
|
||
|
|
const Json& at = Dyn::Get(Dyn::Or(Window(s, k), Dyn::EmptyDict()), "resetsAt");
|
||
|
|
return Dyn::Truthy(at) ? FormatWhen(AsDouble(at), now, zone) : std::string("—");
|
||
|
|
};
|
||
|
|
const Json* fw = FableWindowOf(s);
|
||
|
|
const Json& f = Dyn::Get(s, "fable");
|
||
|
|
std::string fable;
|
||
|
|
if (Dyn::Truthy(f) && Dyn::Get(f, "state") == "limited") {
|
||
|
|
fable = std::format("limited{}", fw ? std::format(" ({}%)", Fmt(Dyn::At(*fw, "utilization"))) : std::string());
|
||
|
|
} else if (fw) {
|
||
|
|
fable = std::format("{}%", Fmt(Dyn::At(*fw, "utilization")));
|
||
|
|
} else {
|
||
|
|
fable = Dyn::Truthy(f) ? Dyn::Str(Dyn::At(f, "state")) : std::string("—");
|
||
|
|
}
|
||
|
|
std::vector<std::string> row = {SeatCell(s), w("five_hour"), when("five_hour"), w("seven_day"), when("seven_day"), fable};
|
||
|
|
if (anyInflight) {
|
||
|
|
const std::int64_t n = Inflight(board, keys, s);
|
||
|
|
row.push_back(n ? std::to_string(n) : "—");
|
||
|
|
}
|
||
|
|
rows.push_back(std::move(row));
|
||
|
|
}
|
||
|
|
std::vector<Json> eligible;
|
||
|
|
for (const Json& s : ordered) {
|
||
|
|
if (IsEligible(s, config)) eligible.push_back(s);
|
||
|
|
}
|
||
|
|
std::ranges::stable_sort(eligible, [](const Json& a, const Json& b) { return NumberOr0(Dyn::Get(a, "util")) < NumberOr0(Dyn::Get(b, "util")); });
|
||
|
|
const Json pick = PickNext(seats, config);
|
||
|
|
const std::string next = pick.at("kind") == "seat" ? SeatName({{"seat", pick.at("seat")}}) : std::format("{}: {}", Dyn::Str(pick.at("status")), Dyn::Str(pick.at("reason")));
|
||
|
|
std::vector<std::string> limited;
|
||
|
|
std::vector<std::string> unusable;
|
||
|
|
std::vector<std::string> fableDead;
|
||
|
|
for (const Json& s : ordered) {
|
||
|
|
const Json& state = Dyn::At(s, "state");
|
||
|
|
if (state == "limited") {
|
||
|
|
const Json& resets = Dyn::Get(s, "resetsAt");
|
||
|
|
limited.push_back(std::format("{} until {}", SeatName(s), Dyn::Truthy(resets) ? FormatWhen(AsDouble(resets), now, zone) : std::string("?")));
|
||
|
|
}
|
||
|
|
if (state != "ok" && state != "limited") {
|
||
|
|
unusable.push_back(std::format("{} {}{}", SeatName(s), Dyn::Str(state), Dyn::Truthy(Dyn::Get(s, "error")) ? std::format(" ({})", Dyn::Str(s.at("error"))) : std::string()));
|
||
|
|
}
|
||
|
|
const Json& fableOfSeat = Dyn::Or(Dyn::Get(s, "fable"), Dyn::EmptyDict());
|
||
|
|
const Json& fableStateOfSeat = Dyn::Get(fableOfSeat, "state");
|
||
|
|
if ((state == "ok" || state == "limited") && !fableStateOfSeat.is_null() && fableStateOfSeat != "ok" && fableStateOfSeat != "limited") {
|
||
|
|
fableDead.push_back(std::format("{} {}{}", SeatName(s), Dyn::Str(fableStateOfSeat),
|
||
|
|
Dyn::Truthy(Dyn::Get(fableOfSeat, "error")) ? std::format(" ({})", Dyn::Str(fableOfSeat.at("error"))) : std::string()));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const bool fableSeen = std::ranges::any_of(seats, [](const Json& s) { return FableWindowOf(s) != nullptr; });
|
||
|
|
std::vector<Json> fableRoom;
|
||
|
|
for (const Json& s : ordered) {
|
||
|
|
if (Dyn::At(s, "state") == "ok" && FableWindowOf(s) && FableOpen(s, config)) fableRoom.push_back(s);
|
||
|
|
}
|
||
|
|
std::ranges::stable_sort(fableRoom, [](const Json& a, const Json& b) { return Less(Dyn::At(*FableWindowOf(a), "utilization"), Dyn::At(*FableWindowOf(b), "utilization")); });
|
||
|
|
const double maxUtil = AsDouble(config.at("maxUtil"));
|
||
|
|
std::vector<std::string> lines = {RenderTable(rows), ""};
|
||
|
|
lines.push_back(std::format("policy {} · ceiling {}% · handover at {}% · a lens is placed at +{}% · 7d cutoff {}%", Dyn::Str(config.at("policy")), Fmt(config.at("maxUtil")),
|
||
|
|
Fmt(config.at("handoverPct")), Fmt(config.at("lensCostPct")), LongWindowCutoff));
|
||
|
|
std::vector<std::string> eligibleText;
|
||
|
|
for (const Json& s : eligible) eligibleText.push_back(std::format("{} ({}% headroom)", SeatName(s), Fmt(Json(maxUtil - NumberOr0(Dyn::Get(s, "util"))))));
|
||
|
|
lines.push_back(std::format("eligible now: {} → next lens: {}", eligible.empty() ? std::string("none") : Dyn::Join(eligibleText, ", "), next));
|
||
|
|
if (fableSeen) {
|
||
|
|
std::vector<std::string> room;
|
||
|
|
for (const Json& s : fableRoom) room.push_back(std::format("{} ({}% fable headroom)", SeatName(s), Fmt(Json(maxUtil - AsDouble(Dyn::At(*FableWindowOf(s), "utilization"))))));
|
||
|
|
lines.push_back(std::format("fable lenses: {}", fableRoom.empty() ? std::string("no seat with room on its Fable window — Fable lenses run on their fallback model") : Dyn::Join(room, ", ")));
|
||
|
|
}
|
||
|
|
if (!limited.empty()) lines.push_back(std::format("limited: {}", Dyn::Join(limited, "; ")));
|
||
|
|
if (!unusable.empty()) lines.push_back(std::format("not usable: {}", Dyn::Join(unusable, "; ")));
|
||
|
|
if (!fableDead.empty()) lines.push_back(std::format("fable probe: {}", Dyn::Join(fableDead, "; ")));
|
||
|
|
return Dyn::Join(lines, "\n");
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string RenderApiReport(const std::vector<Json>& apiSeats, const Json& apiConfig, const Json& board, const std::map<std::int32_t, std::string>& keys) {
|
||
|
|
std::vector<std::vector<std::string>> rows = {{"seat", "state", "driver spend this cycle", "procs", "cycle from", "ledger: out", "in flight"}};
|
||
|
|
for (const Json& s : SortedBySeat(apiSeats)) {
|
||
|
|
const Json& ledger = Dyn::Or(Dyn::Get(s, "ledger"), Dyn::EmptyDict());
|
||
|
|
const double spent = NumberOr0(Dyn::Get(ledger, "spentUsd"));
|
||
|
|
const std::string state = Dyn::Str(Dyn::At(s, "state")) + (Dyn::Truthy(Dyn::Get(s, "error")) && Dyn::At(s, "state") != "ok" ? std::format(" ({})", Dyn::Str(s.at("error"))) : std::string());
|
||
|
|
const Json& outSince = Dyn::Get(ledger, "outSince");
|
||
|
|
const std::int64_t n = Inflight(board, keys, s);
|
||
|
|
rows.push_back({
|
||
|
|
std::format("api{}", Dyn::Str(Dyn::At(s, "seat"))), state, std::format("${:.2f} of ${}", spent, Fmt(apiConfig.at("budgetUsd"))), Dyn::StrOr(Dyn::Get(ledger, "procs"), "0"),
|
||
|
|
Dyn::StrOr(Dyn::Get(ledger, "cycle"), "—"), Dyn::Truthy(outSince) ? std::format("since {}", Dyn::Replace(Dyn::Slice(Dyn::Str(outSince), 0, 16), "T", " ")) : std::string("—"),
|
||
|
|
n ? std::format("{} of {}", n, Dyn::Str(apiConfig.at("concurrency"))) : std::string("—"),
|
||
|
|
});
|
||
|
|
}
|
||
|
|
return Dyn::Join(std::vector<std::string>{
|
||
|
|
RenderTable(rows),
|
||
|
|
std::format("api seats run first (REVIEW_GATES_SEAT_ORDER={}) · budget ${} per seat per cycle · cycle day {} · {} processes per seat · placed at +${} each",
|
||
|
|
Dyn::Str(apiConfig.at("order")), Fmt(apiConfig.at("budgetUsd")), Dyn::Str(apiConfig.at("cycleDay")), Dyn::Str(apiConfig.at("concurrency")), Fmt(apiConfig.at("lensCostUsd"))),
|
||
|
|
"the balance itself is in the Console (Settings > Billing > Promotional credits); the ledger counts only what the review drivers spent",
|
||
|
|
}, "\n");
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string RenderDrivers(const Json& board, double now, const Zone& zone) {
|
||
|
|
const Json& drivers = Dyn::Or(Dyn::Get(Dyn::Or(board, Dyn::EmptyDict()), "drivers"), Dyn::EmptyDict());
|
||
|
|
if (!Dyn::Truthy(drivers)) return std::format("drivers: none running (the board at {})", Dyn::StrOr(Dyn::Get(Dyn::Or(board, Dyn::EmptyDict()), "path"), "?"));
|
||
|
|
std::vector<std::pair<std::string, const Json*>> sorted;
|
||
|
|
for (auto it = drivers.begin(); it != drivers.end(); ++it) sorted.emplace_back(it.key(), &it.value());
|
||
|
|
std::ranges::stable_sort(sorted, [](const auto& a, const auto& b) { return NumberOr0(Dyn::Get(*a.second, "since")) < NumberOr0(Dyn::Get(*b.second, "since")); });
|
||
|
|
std::vector<std::string> parts;
|
||
|
|
std::int64_t total = 0;
|
||
|
|
for (const auto& [key, driver] : sorted) {
|
||
|
|
const Json& d = *driver;
|
||
|
|
const Json& settings = Dyn::Or(Dyn::Get(d, "settings"), Dyn::EmptyDict());
|
||
|
|
std::string held;
|
||
|
|
if (Dyn::Truthy(Dyn::Get(d, "mb"))) held += std::format(", {:.0f} MB", AsDouble(d.at("mb")));
|
||
|
|
if (Dyn::Truthy(Dyn::Get(d, "stacks"))) {
|
||
|
|
std::vector<std::string> stacks;
|
||
|
|
for (const Json& stack : d.at("stacks")) stacks.push_back(Dyn::Str(stack));
|
||
|
|
std::ranges::sort(stacks);
|
||
|
|
held += std::format(", stack(s) {}", Dyn::Join(stacks, ", "));
|
||
|
|
}
|
||
|
|
const Json unknown = "?";
|
||
|
|
const Json zero = 0;
|
||
|
|
parts.push_back(std::format("pid {} ({}, {}, width {}) since {} - {} in flight{}", Dyn::Split(key, ":")[0], Dyn::Str(Dyn::Has(d, "run") ? d.at("run") : unknown),
|
||
|
|
Dyn::Str(Dyn::Has(d, "effort") ? d.at("effort") : unknown), Dyn::Str(Dyn::Has(settings, "concurrency") ? settings.at("concurrency") : unknown),
|
||
|
|
FormatWhen(NumberOr0(Dyn::Get(d, "since")), now, zone), Dyn::Str(Dyn::Has(d, "inflight") ? d.at("inflight") : zero), held));
|
||
|
|
total += static_cast<std::int64_t>(NumberOr0(Dyn::Get(d, "inflight")));
|
||
|
|
}
|
||
|
|
return std::format("drivers ({} in flight on this machine; the width is the machine's cap): {}", total, Dyn::Join(parts, "; "));
|
||
|
|
}
|
||
|
|
}
|