shraga 0.1.70 → 0.1.71
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/package.json +1 -1
- package/src/server/claude-usage.ts +22 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shraga",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.71",
|
|
4
4
|
"description": "The teammate you delegate coding to — a self-hostable, multi-user AI coding agent web UI (Claude Code, with a pluggable engine seam).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -122,11 +122,14 @@ export class ClaudeUsageReader {
|
|
|
122
122
|
signal: AbortSignal.timeout(this.options.timeoutMs),
|
|
123
123
|
});
|
|
124
124
|
if (res.status === 429) {
|
|
125
|
-
|
|
125
|
+
// The reason lives in the BODY, not the status: "too many requests" (our own poll rate) and
|
|
126
|
+
// an account/org-level throttle read identically from the outside, and only the first one is
|
|
127
|
+
// ours to fix. Backing off blind once cost a day of a hidden gauge, so surface it.
|
|
128
|
+
this.enterCooldown(res.headers.get('retry-after'), await describeError(res));
|
|
126
129
|
return null;
|
|
127
130
|
}
|
|
128
131
|
if (!res.ok) {
|
|
129
|
-
console.warn(`${TAG} usage endpoint returned ${res.status}; hiding widget`);
|
|
132
|
+
console.warn(`${TAG} usage endpoint returned ${res.status}; hiding widget — ${await describeError(res)}`);
|
|
130
133
|
return null;
|
|
131
134
|
}
|
|
132
135
|
const body: any = await res.json();
|
|
@@ -145,14 +148,17 @@ export class ClaudeUsageReader {
|
|
|
145
148
|
|
|
146
149
|
/** Climb the backoff ladder one rung per consecutive 429, capped at the last rung. `Retry-After`
|
|
147
150
|
* (seconds, or an HTTP-date) only ever EXTENDS the wait — never shortens the rung we earned. */
|
|
148
|
-
private enterCooldown(retryAfter: string | null) {
|
|
151
|
+
private enterCooldown(retryAfter: string | null, detail: string) {
|
|
149
152
|
const ladder = this.options.rateLimitBackoffMs;
|
|
150
153
|
const rung = ladder[Math.min(this.rateLimitStreak, ladder.length - 1)] ?? 60_000;
|
|
151
154
|
this.rateLimitStreak++;
|
|
152
155
|
const hinted = parseRetryAfter(retryAfter);
|
|
153
156
|
const waitMs = Math.max(rung, hinted ?? 0);
|
|
154
157
|
this.cooldownUntil = Date.now() + waitMs;
|
|
155
|
-
console.warn(
|
|
158
|
+
console.warn(
|
|
159
|
+
`${TAG} usage endpoint returned 429 (ua=claude-code/${claudeCodeVersion()}, retry-after=${retryAfter ?? 'none'}); ` +
|
|
160
|
+
`hiding widget and backing off ${Math.round(waitMs / 1000)}s — ${detail}`,
|
|
161
|
+
);
|
|
156
162
|
}
|
|
157
163
|
|
|
158
164
|
/** Re-read per poll from whichever source holds them — the token lives ~8h and Claude Code
|
|
@@ -208,6 +214,18 @@ function parseCredentials(raw: string, source: string): { accessToken: string; s
|
|
|
208
214
|
return { accessToken: oauth.accessToken, subscriptionType: oauth.subscriptionType ?? null };
|
|
209
215
|
}
|
|
210
216
|
|
|
217
|
+
/** Anthropic answers errors as `{ error: { type, message } }`. Never throws and never returns more
|
|
218
|
+
* than a line: this only ever lands in a log, next to a status we already decided to fail on. */
|
|
219
|
+
async function describeError(res: Response): Promise<string> {
|
|
220
|
+
try {
|
|
221
|
+
const raw = (await res.text()).slice(0, 300);
|
|
222
|
+
const parsed = JSON.parse(raw)?.error;
|
|
223
|
+
return parsed?.message ? `${parsed.type ?? 'error'}: ${parsed.message}` : raw || '(empty body)';
|
|
224
|
+
} catch (err) {
|
|
225
|
+
return `(unreadable body: ${(err as Error).message})`;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
211
229
|
/** `Retry-After` is either delta-seconds or an HTTP-date. Anything unparseable => no hint. */
|
|
212
230
|
function parseRetryAfter(raw: string | null): number | null {
|
|
213
231
|
if (!raw) return null;
|