fix(status): use claude-cli token for usage

This commit is contained in:
Peter Steinberger
2026-01-09 16:17:16 +00:00
parent 1478473537
commit ee70a1d1fb
4 changed files with 151 additions and 18 deletions

View File

@@ -227,6 +227,85 @@ describe("provider usage loading", () => {
);
});
it("prefers claude-cli token for Anthropic usage snapshots", async () => {
await withTempHome(
async () => {
const stateDir = process.env.CLAWDBOT_STATE_DIR;
if (!stateDir) throw new Error("Missing CLAWDBOT_STATE_DIR");
const agentDir = path.join(stateDir, "agents", "main", "agent");
fs.mkdirSync(agentDir, { recursive: true, mode: 0o700 });
fs.writeFileSync(
path.join(agentDir, "auth-profiles.json"),
`${JSON.stringify(
{
version: 1,
profiles: {
"anthropic:default": {
type: "token",
provider: "anthropic",
token: "token-default",
expires: Date.UTC(2100, 0, 1, 0, 0, 0),
},
"anthropic:claude-cli": {
type: "token",
provider: "anthropic",
token: "token-cli",
expires: Date.UTC(2100, 0, 1, 0, 0, 0),
},
},
},
null,
2,
)}\n`,
"utf8",
);
const makeResponse = (status: number, body: unknown): Response => {
const payload =
typeof body === "string" ? body : JSON.stringify(body);
const headers =
typeof body === "string"
? undefined
: { "Content-Type": "application/json" };
return new Response(payload, { status, headers });
};
const mockFetch = vi.fn<
Parameters<typeof fetch>,
ReturnType<typeof fetch>
>(async (input, init) => {
const url =
typeof input === "string"
? input
: input instanceof URL
? input.toString()
: input.url;
if (url.includes("api.anthropic.com/api/oauth/usage")) {
const headers = (init?.headers ?? {}) as Record<string, string>;
expect(headers.Authorization).toBe("Bearer token-cli");
return makeResponse(200, {
five_hour: { utilization: 20, resets_at: "2026-01-07T01:00:00Z" },
});
}
return makeResponse(404, "not found");
});
const summary = await loadProviderUsageSummary({
now: Date.UTC(2026, 0, 7, 0, 0, 0),
providers: ["anthropic"],
agentDir,
fetch: mockFetch,
});
expect(summary.providers).toHaveLength(1);
expect(summary.providers[0]?.provider).toBe("anthropic");
expect(summary.providers[0]?.windows[0]?.label).toBe("5h");
expect(mockFetch).toHaveBeenCalled();
},
{ prefix: "clawdbot-provider-usage-" },
);
});
it("falls back to claude.ai web usage when OAuth scope is missing", async () => {
const cookieSnapshot = process.env.CLAUDE_AI_SESSION_KEY;
process.env.CLAUDE_AI_SESSION_KEY = "sk-ant-web-1";

View File

@@ -3,6 +3,7 @@ import os from "node:os";
import path from "node:path";
import {
CLAUDE_CLI_PROFILE_ID,
ensureAuthProfileStore,
listProfilesForProvider,
resolveApiKeyForProfile,
@@ -802,7 +803,16 @@ async function resolveOAuthToken(params: {
provider: params.provider,
});
for (const profileId of order) {
// Claude CLI creds are the only Anthropic tokens that reliably include the
// `user:profile` scope required for the OAuth usage endpoint.
const candidates =
params.provider === "anthropic" ? [CLAUDE_CLI_PROFILE_ID, ...order] : order;
const deduped: string[] = [];
for (const entry of candidates) {
if (!deduped.includes(entry)) deduped.push(entry);
}
for (const profileId of deduped) {
const cred = store.profiles[profileId];
if (!cred || (cred.type !== "oauth" && cred.type !== "token")) continue;
try {