mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-10 00:11:19 +00:00
* feat(providers): add admin cost history * fix(ui): sync cron raw-copy baseline * fix(usage): harden provider cost credentials * fix(ui): refresh raw-copy baseline * docs: refresh documentation map * fix(ci): sync provider usage baselines * fix(ui): hide zero-cost history bars * docs(changelog): defer provider cost note
135 lines
4.4 KiB
TypeScript
135 lines
4.4 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import { fetchAnthropicAdminUsage, resolveAnthropicUsageAuth } from "./usage.js";
|
|
|
|
function requestUrl(input: string | URL | Request): URL {
|
|
return new URL(input instanceof Request ? input.url : input);
|
|
}
|
|
|
|
describe("Anthropic provider usage", () => {
|
|
it("aggregates provider-reported costs, cache tokens, models, and categories", async () => {
|
|
const fetchFn = vi.fn(async (input: string | URL | Request, _init?: RequestInit) => {
|
|
const url = requestUrl(input);
|
|
if (url.pathname.endsWith("/organizations/cost_report")) {
|
|
return new Response(
|
|
JSON.stringify({
|
|
data: [
|
|
{
|
|
starting_at: "2026-07-06T00:00:00Z",
|
|
ending_at: "2026-07-07T00:00:00Z",
|
|
results: [{ amount: "1234", currency: "USD", description: "Claude API" }],
|
|
},
|
|
],
|
|
has_more: false,
|
|
}),
|
|
{ status: 200 },
|
|
);
|
|
}
|
|
return new Response(
|
|
JSON.stringify({
|
|
data: [
|
|
{
|
|
starting_at: "2026-07-06T00:00:00Z",
|
|
ending_at: "2026-07-07T00:00:00Z",
|
|
results: [
|
|
{
|
|
uncached_input_tokens: 1_000,
|
|
cache_creation: {
|
|
ephemeral_1h_input_tokens: 100,
|
|
ephemeral_5m_input_tokens: 50,
|
|
},
|
|
cache_read_input_tokens: 300,
|
|
output_tokens: 250,
|
|
model: "claude-opus-4-8",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
has_more: false,
|
|
}),
|
|
{ status: 200 },
|
|
);
|
|
});
|
|
|
|
const result = await fetchAnthropicAdminUsage({
|
|
apiKey: "sk-ant-admin-test",
|
|
timeoutMs: 5_000,
|
|
fetchFn: fetchFn as typeof fetch,
|
|
now: Date.parse("2026-07-06T12:00:00Z"),
|
|
periodDays: 2,
|
|
});
|
|
|
|
expect(result).toMatchObject({
|
|
provider: "anthropic",
|
|
plan: "Admin API",
|
|
billing: [{ type: "spend", amount: 12.34, unit: "USD", period: "2d" }],
|
|
costHistory: {
|
|
unit: "USD",
|
|
periodDays: 2,
|
|
daily: [
|
|
{
|
|
date: "2026-07-06",
|
|
amount: 12.34,
|
|
inputTokens: 1_000,
|
|
cacheWriteTokens: 150,
|
|
cacheReadTokens: 300,
|
|
outputTokens: 250,
|
|
totalTokens: 1_700,
|
|
},
|
|
],
|
|
models: [{ name: "claude-opus-4-8", totalTokens: 1_700 }],
|
|
categories: [{ name: "Claude API", amount: 12.34 }],
|
|
},
|
|
});
|
|
for (const [input, init] of fetchFn.mock.calls) {
|
|
const url = requestUrl(input);
|
|
expect(url.searchParams.get("bucket_width")).toBe("1d");
|
|
expect((init as RequestInit).headers).toMatchObject({
|
|
"anthropic-version": "2023-06-01",
|
|
"x-api-key": "sk-ant-admin-test",
|
|
});
|
|
}
|
|
});
|
|
|
|
it("uses explicit Admin API credentials before Claude OAuth", async () => {
|
|
const result = await resolveAnthropicUsageAuth({
|
|
config: {},
|
|
env: { ANTHROPIC_ADMIN_API_KEY: "sk-ant-admin-explicit" },
|
|
provider: "anthropic",
|
|
resolveApiKeyFromConfigAndStore: () => "sk-ant-oat01-fallback",
|
|
resolveOAuthToken: async () => ({ token: "oauth-token" }),
|
|
});
|
|
expect(result).toEqual({
|
|
token: 'openclaw:anthropic-admin:v1:{"token":"sk-ant-admin-explicit"}',
|
|
});
|
|
});
|
|
|
|
it("auto-detects an Admin API key stored in the Anthropic provider profile", async () => {
|
|
const result = await resolveAnthropicUsageAuth({
|
|
config: {},
|
|
env: {},
|
|
provider: "anthropic",
|
|
resolveApiKeyFromConfigAndStore: () => "sk-ant-admin-profile",
|
|
resolveOAuthToken: async () => null,
|
|
});
|
|
expect(result).toEqual({
|
|
token: 'openclaw:anthropic-admin:v1:{"token":"sk-ant-admin-profile"}',
|
|
});
|
|
});
|
|
|
|
it("prefers a stored Admin API key when normal API and OAuth credentials coexist", async () => {
|
|
const result = await resolveAnthropicUsageAuth({
|
|
config: {},
|
|
env: {},
|
|
provider: "anthropic",
|
|
resolveApiKeyFromConfigAndStore: () => "sk-ant-api03-inference",
|
|
resolveApiKeyCandidatesFromConfigAndStore: async () => [
|
|
"sk-ant-api03-inference",
|
|
"sk-ant-admin-billing",
|
|
],
|
|
resolveOAuthToken: async () => ({ token: "oauth-token" }),
|
|
});
|
|
expect(result).toEqual({
|
|
token: 'openclaw:anthropic-admin:v1:{"token":"sk-ant-admin-billing"}',
|
|
});
|
|
});
|
|
});
|