@openhoo/hoopilot 0.7.4 → 0.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.js +71 -6
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +71 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +71 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -158,7 +158,7 @@ Incoming `x-request-id` headers are preserved on responses. If a request has no
|
|
|
158
158
|
|
|
159
159
|
Hoopilot tracks token usage, request counts, and latency in memory while the server runs, and can report your GitHub Copilot account quota (premium-request "credit" usage).
|
|
160
160
|
|
|
161
|
-
- `GET /metrics` returns Prometheus text (`text/plain; version=0.0.4`). It exposes request counters (`hoopilot_requests_total`), upstream call counters (`hoopilot_upstream_requests_total`), token counters by model and type (`hoopilot_tokens_total{model,type}`), a request-duration histogram (`hoopilot_request_duration_seconds`), an in-flight gauge, and—once `/v1/usage` has been fetched at least once—Copilot quota gauges (`hoopilot_copilot_quota_remaining{category}`, `_entitlement`, `_used`, `_percent_remaining`). Counters reset to zero on restart, which Prometheus handles natively.
|
|
161
|
+
- `GET /metrics` returns Prometheus text (`text/plain; version=0.0.4`). It exposes request counters (`hoopilot_requests_total`), upstream call counters (`hoopilot_upstream_requests_total`), token counters by model and type (`hoopilot_tokens_total{model,type}`), a request-duration histogram (`hoopilot_request_duration_seconds`), an in-flight gauge, and—once `/v1/usage` has been fetched at least once—Copilot quota gauges (`hoopilot_copilot_quota_remaining{category}`, `_entitlement`, `_used`, `_percent_remaining`, `_overage_count`, `_overage_entitlement`, `_unlimited`, `_overage_permitted`, `_has_quota`, `_token_based_billing`, and category reset/snapshot timestamps). Counters reset to zero on restart, which Prometheus handles natively.
|
|
162
162
|
- `GET /v1/usage` returns JSON combining the proxy metrics snapshot with live Copilot quota fetched from GitHub (cached for 60 seconds). If the quota cannot be read, `copilot` is `null` and `copilot_error` explains why, but the proxy metrics are still returned.
|
|
163
163
|
- `hoopilot usage` prints your Copilot plan and quota from the command line.
|
|
164
164
|
|
package/dist/cli.js
CHANGED
|
@@ -293,22 +293,31 @@ function normalizeCopilotUsage(body) {
|
|
|
293
293
|
}
|
|
294
294
|
function normalizeQuotaDetail(detail) {
|
|
295
295
|
const entitlement = numberOrUndefined(detail.entitlement);
|
|
296
|
+
const overageCount = numberOrUndefined(detail.overage_count);
|
|
296
297
|
const remaining = numberOrUndefined(detail.remaining) ?? numberOrUndefined(detail.quota_remaining);
|
|
297
298
|
return removeUndefinedQuota({
|
|
298
299
|
entitlement,
|
|
299
|
-
|
|
300
|
+
hasQuota: typeof detail.has_quota === "boolean" ? detail.has_quota : void 0,
|
|
301
|
+
overageCount,
|
|
302
|
+
overageEntitlement: numberOrUndefined(detail.overage_entitlement),
|
|
300
303
|
overagePermitted: typeof detail.overage_permitted === "boolean" ? detail.overage_permitted : void 0,
|
|
301
304
|
percentRemaining: numberOrUndefined(detail.percent_remaining),
|
|
305
|
+
quotaId: stringOrUndefined(detail.quota_id),
|
|
306
|
+
quotaResetAt: stringOrUndefined(detail.quota_reset_at),
|
|
302
307
|
remaining,
|
|
308
|
+
timestampUtc: stringOrUndefined(detail.timestamp_utc),
|
|
309
|
+
tokenBasedBilling: typeof detail.token_based_billing === "boolean" ? detail.token_based_billing : void 0,
|
|
303
310
|
unlimited: typeof detail.unlimited === "boolean" ? detail.unlimited : void 0,
|
|
304
|
-
used: usedFrom(entitlement, remaining)
|
|
311
|
+
used: usedFrom(entitlement, remaining, overageCount)
|
|
305
312
|
});
|
|
306
313
|
}
|
|
307
|
-
function usedFrom(entitlement, remaining) {
|
|
314
|
+
function usedFrom(entitlement, remaining, overageCount) {
|
|
308
315
|
if (entitlement === void 0 || remaining === void 0) {
|
|
309
316
|
return void 0;
|
|
310
317
|
}
|
|
311
|
-
|
|
318
|
+
const base = entitlement - remaining;
|
|
319
|
+
const overage = remaining === 0 ? overageCount ?? 0 : 0;
|
|
320
|
+
return Math.max(0, base + overage);
|
|
312
321
|
}
|
|
313
322
|
function numberOrUndefined(value) {
|
|
314
323
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
@@ -1094,11 +1103,43 @@ var MetricsRegistry = class {
|
|
|
1094
1103
|
gauge("remaining", "Remaining quota for the Copilot category.", (q) => q.remaining);
|
|
1095
1104
|
gauge("entitlement", "Quota entitlement for the Copilot category.", (q) => q.entitlement);
|
|
1096
1105
|
gauge("used", "Used quota (entitlement minus remaining) for the category.", (q) => q.used);
|
|
1106
|
+
gauge("overage_count", "Overage count for the Copilot category.", (q) => q.overageCount);
|
|
1107
|
+
gauge(
|
|
1108
|
+
"overage_entitlement",
|
|
1109
|
+
"Overage entitlement for the Copilot category.",
|
|
1110
|
+
(q) => q.overageEntitlement
|
|
1111
|
+
);
|
|
1097
1112
|
gauge(
|
|
1098
1113
|
"percent_remaining",
|
|
1099
1114
|
"Percent of quota remaining for the Copilot category.",
|
|
1100
1115
|
(q) => q.percentRemaining
|
|
1101
1116
|
);
|
|
1117
|
+
booleanGauge(
|
|
1118
|
+
"unlimited",
|
|
1119
|
+
"Whether the Copilot quota category is unlimited.",
|
|
1120
|
+
(q) => q.unlimited
|
|
1121
|
+
);
|
|
1122
|
+
booleanGauge(
|
|
1123
|
+
"overage_permitted",
|
|
1124
|
+
"Whether overage is permitted for the Copilot category.",
|
|
1125
|
+
(q) => q.overagePermitted
|
|
1126
|
+
);
|
|
1127
|
+
booleanGauge("has_quota", "Whether the Copilot quota category has a quota.", (q) => q.hasQuota);
|
|
1128
|
+
booleanGauge(
|
|
1129
|
+
"token_based_billing",
|
|
1130
|
+
"Whether the Copilot quota category uses token-based billing.",
|
|
1131
|
+
(q) => q.tokenBasedBilling
|
|
1132
|
+
);
|
|
1133
|
+
dateGauge(
|
|
1134
|
+
"category_reset_timestamp_seconds",
|
|
1135
|
+
"Unix epoch of the Copilot category-specific quota reset.",
|
|
1136
|
+
(q) => q.quotaResetAt
|
|
1137
|
+
);
|
|
1138
|
+
dateGauge(
|
|
1139
|
+
"category_snapshot_timestamp_seconds",
|
|
1140
|
+
"Unix epoch of the Copilot category quota snapshot.",
|
|
1141
|
+
(q) => q.timestampUtc
|
|
1142
|
+
);
|
|
1102
1143
|
const resetMs = usage.quotaResetDate ? Date.parse(usage.quotaResetDate) : Number.NaN;
|
|
1103
1144
|
if (Number.isFinite(resetMs)) {
|
|
1104
1145
|
lines.push(
|
|
@@ -1117,6 +1158,30 @@ var MetricsRegistry = class {
|
|
|
1117
1158
|
})} 1`
|
|
1118
1159
|
);
|
|
1119
1160
|
}
|
|
1161
|
+
function booleanGauge(suffix, help, pick) {
|
|
1162
|
+
const present = categories.filter(([, quota]) => pick(quota) !== void 0);
|
|
1163
|
+
if (present.length === 0) {
|
|
1164
|
+
return;
|
|
1165
|
+
}
|
|
1166
|
+
lines.push(`# HELP hoopilot_copilot_quota_${suffix} ${help}`);
|
|
1167
|
+
lines.push(`# TYPE hoopilot_copilot_quota_${suffix} gauge`);
|
|
1168
|
+
for (const [category, quota] of present) {
|
|
1169
|
+
lines.push(
|
|
1170
|
+
`hoopilot_copilot_quota_${suffix}${labels({ category })} ${pick(quota) ? 1 : 0}`
|
|
1171
|
+
);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
function dateGauge(suffix, help, pick) {
|
|
1175
|
+
const present = categories.map(([category, quota]) => [category, Date.parse(pick(quota) ?? "")]).filter(([, timestamp]) => Number.isFinite(timestamp));
|
|
1176
|
+
if (present.length === 0) {
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
lines.push(`# HELP hoopilot_copilot_quota_${suffix} ${help}`);
|
|
1180
|
+
lines.push(`# TYPE hoopilot_copilot_quota_${suffix} gauge`);
|
|
1181
|
+
for (const [category, timestamp] of present) {
|
|
1182
|
+
lines.push(`hoopilot_copilot_quota_${suffix}${labels({ category })} ${timestamp / 1e3}`);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1120
1185
|
}
|
|
1121
1186
|
};
|
|
1122
1187
|
function observeResponseUsage(response, fallbackModel, onUsage, signal) {
|
|
@@ -1814,8 +1879,8 @@ function metricsResponse(metrics) {
|
|
|
1814
1879
|
});
|
|
1815
1880
|
}
|
|
1816
1881
|
async function handleUsage(metrics, readUsage, signal) {
|
|
1817
|
-
const proxy = metrics.snapshot();
|
|
1818
1882
|
const { copilot, error } = await readUsage(signal);
|
|
1883
|
+
const proxy = metrics.snapshot();
|
|
1819
1884
|
const body = { copilot: copilot ?? null, object: "usage", proxy };
|
|
1820
1885
|
if (error) {
|
|
1821
1886
|
body.copilot_error = error;
|
|
@@ -1840,10 +1905,10 @@ function createUsageReader(client, metrics, now = Date.now, ttlMs = USAGE_CACHE_
|
|
|
1840
1905
|
metrics.recordCopilotQuota(value);
|
|
1841
1906
|
return { copilot: value };
|
|
1842
1907
|
} catch (error) {
|
|
1843
|
-
metrics.recordUpstream(usagePath, false);
|
|
1844
1908
|
if (error instanceof CopilotAuthError) {
|
|
1845
1909
|
return { error: error.message };
|
|
1846
1910
|
}
|
|
1911
|
+
metrics.recordUpstream(usagePath, false);
|
|
1847
1912
|
return { error: errorMessage(error) };
|
|
1848
1913
|
}
|
|
1849
1914
|
};
|