mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 20:32:20 +03:00
* feat: report usage command quotas as percentages Convert @@om-usage and the HTTP usage endpoint to report personal API key quotas as remaining percentages while keeping USD amounts out of the command output. Scale provider quota remaining percentages by the configured quota cutoff so the protected reserve reads as 0% left. Restore provider USD cost drilldown in the quota dashboard.\n\nAlso sync the 3.8.43 i18n changelog mirrors so the docs-sync pre-commit gate remains green.\n\nTests: DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/internal-usage-command.test.ts; DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/api-key-usage-limits.test.ts; DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/provider-window-costs.test.ts; DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/api-manager-usage-command.test.ts tests/unit/apikeys-usage-command.test.ts; npx eslint <changed files>; npm run typecheck:core; npm run build; npm run check:migration-numbering; npm run check:docs-sync; docker build --target runner-base (cherry picked from commitf66abd2028) * fix: honor observed provider quota resets Detect same-resetAt quota resets when provider usage drops back to the reset floor, and prefer that observed snapshot over stale recorded weekly events for provider USD windows and API-key USD quotas.\n\nTests: npx eslint changed files\nTests: npm run typecheck:core\nTests: DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/lib/quota-reset-events.test.ts tests/unit/provider-window-costs.test.ts tests/unit/api-key-usage-limits.test.ts\nTests: npm run build\nTests: docker build --target runner-base --build-arg OMNIROUTE_BUILD_MEMORY_MB=4096 -t omniroute:quota-reset-window-20260702002300 . (cherry picked from commit39c12a6f17) * docs(changelog): credit usage quota percentages extraction from #5863 Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> --------- Co-authored-by: Wital <wital@example.com>
527 lines
16 KiB
TypeScript
527 lines
16 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import {
|
|
buildUsageCommandText,
|
|
extractLastUserText,
|
|
handleInternalUsageCommand,
|
|
handleInternalUsageCommandHttpRequest,
|
|
isInternalUsageCommand,
|
|
} from "../../src/lib/usage/internalUsageCommand.ts";
|
|
|
|
const NOW = Date.parse("2026-06-16T12:00:00.000Z");
|
|
|
|
test("internal usage command only matches the exact trimmed user message", () => {
|
|
assert.equal(isInternalUsageCommand("@@om-usage"), true);
|
|
assert.equal(isInternalUsageCommand(" @@om-usage "), true);
|
|
assert.equal(isInternalUsageCommand("me mostra @@om-usage"), false);
|
|
assert.equal(isInternalUsageCommand("@@om-usage agora"), false);
|
|
assert.equal(isInternalUsageCommand("/@@om-usage"), false);
|
|
assert.equal(isInternalUsageCommand("@@om-usage."), false);
|
|
assert.equal(isInternalUsageCommand("@@om-usage\nabc"), false);
|
|
assert.equal(isInternalUsageCommand("```@@om-usage```"), false);
|
|
assert.equal(isInternalUsageCommand(null), false);
|
|
});
|
|
|
|
test("extractLastUserText supports OpenAI and Anthropic text content", () => {
|
|
assert.equal(
|
|
extractLastUserText({
|
|
messages: [
|
|
{ role: "user", content: "first" },
|
|
{ role: "assistant", content: "middle" },
|
|
{ role: "user", content: [{ type: "text", text: "@@om-usage" }] },
|
|
],
|
|
}),
|
|
"@@om-usage"
|
|
);
|
|
|
|
assert.equal(
|
|
extractLastUserText({
|
|
input: [
|
|
{ role: "assistant", content: "ignored" },
|
|
{ role: "user", content: [{ type: "input_text", text: "hello" }] },
|
|
],
|
|
}),
|
|
"hello"
|
|
);
|
|
});
|
|
|
|
test("buildUsageCommandText formats cached Claude usage windows exactly", async () => {
|
|
const text = await buildUsageCommandText(
|
|
{
|
|
id: "key-1",
|
|
name: "main",
|
|
allowedConnections: ["conn-claude"],
|
|
},
|
|
{
|
|
now: () => NOW,
|
|
getProviderConnectionById: async () => ({
|
|
id: "conn-claude",
|
|
provider: "claude",
|
|
isActive: true,
|
|
}),
|
|
getProviderConnections: async () => [],
|
|
getProviderLimitsCache: () => ({
|
|
plan: "Claude Max",
|
|
quotas: {
|
|
"session (5h)": {
|
|
used: 53,
|
|
total: 100,
|
|
remaining: 47,
|
|
resetAt: new Date(NOW + 9 * 60_000).toISOString(),
|
|
},
|
|
"weekly (7d)": {
|
|
used: 72,
|
|
total: 100,
|
|
remaining: 28,
|
|
resetAt: new Date(NOW + 24 * 60 * 60_000).toISOString(),
|
|
},
|
|
"weekly sonnet (7d)": {
|
|
used: 30,
|
|
total: 100,
|
|
remaining: 70,
|
|
resetAt: new Date(NOW + 24 * 60 * 60_000).toISOString(),
|
|
},
|
|
},
|
|
message: null,
|
|
fetchedAt: new Date(NOW).toISOString(),
|
|
}),
|
|
getAllProviderLimitsCache: () => ({}),
|
|
isValidApiKey: async () => true,
|
|
getApiKeyMetadata: async () => null,
|
|
getQuotaPolicy: async () => ({
|
|
defaultThresholdPercent: 0,
|
|
providerWindowDefaults: {},
|
|
}),
|
|
}
|
|
);
|
|
|
|
assert.equal(
|
|
text,
|
|
[
|
|
"Provider quota",
|
|
"Session",
|
|
"47% left",
|
|
"⏱ reset in 9m",
|
|
"",
|
|
"Weekly",
|
|
"28% left",
|
|
"⏱ reset in 1d 0h 0m",
|
|
].join("\n")
|
|
);
|
|
});
|
|
|
|
test("buildUsageCommandText formats API key USD limits as personal percentages", async () => {
|
|
let usageStatusPreferredProvider: string | null | undefined;
|
|
const text = await buildUsageCommandText(
|
|
{
|
|
id: "key-limited",
|
|
name: "limited",
|
|
usageLimitEnabled: true,
|
|
dailyUsageLimitUsd: 10,
|
|
weeklyUsageLimitUsd: 50,
|
|
},
|
|
{
|
|
now: () => NOW,
|
|
getApiKeyUsageLimitStatus: async (metadata) => {
|
|
usageStatusPreferredProvider = metadata.preferredProvider;
|
|
return {
|
|
enabled: true,
|
|
dailyLimitUsd: 10,
|
|
weeklyLimitUsd: 50,
|
|
dailySpentUsd: 2,
|
|
weeklySpentUsd: 5.25,
|
|
dailyWindowStartIso: "2026-06-16T03:00:00.000Z",
|
|
dailyResetAtIso: "2026-06-17T03:00:00.000Z",
|
|
weeklyWindowStartIso: "2026-06-09T12:00:00.000Z",
|
|
weeklyResetAtIso: "2026-06-23T12:00:00.000Z",
|
|
dailyExceeded: false,
|
|
weeklyExceeded: false,
|
|
};
|
|
},
|
|
getProviderConnectionById: async () => null,
|
|
getProviderConnections: async () => [],
|
|
getProviderLimitsCache: () => null,
|
|
getAllProviderLimitsCache: () => ({}),
|
|
isValidApiKey: async () => true,
|
|
getApiKeyMetadata: async () => null,
|
|
getQuotaPolicy: async () => ({
|
|
defaultThresholdPercent: 0,
|
|
providerWindowDefaults: {},
|
|
}),
|
|
},
|
|
{ preferredProvider: "claude" }
|
|
);
|
|
|
|
assert.equal(usageStatusPreferredProvider, "claude");
|
|
assert.equal(
|
|
text,
|
|
[
|
|
"Personal quota",
|
|
"Daily",
|
|
"80% left",
|
|
"⏱ reset in 15h 0m",
|
|
"",
|
|
"Weekly",
|
|
"90% left",
|
|
"⏱ reset in 7d 0h 0m",
|
|
"",
|
|
"Provider quota",
|
|
"No cached usage data available.",
|
|
].join("\n")
|
|
);
|
|
});
|
|
|
|
test("buildUsageCommandText scales provider quota remaining by configured cutoffs", async () => {
|
|
const text = await buildUsageCommandText(
|
|
{
|
|
id: "key-cutoff",
|
|
name: "cutoff",
|
|
allowedConnections: ["conn-claude"],
|
|
},
|
|
{
|
|
now: () => NOW,
|
|
getProviderConnectionById: async () => ({
|
|
id: "conn-claude",
|
|
provider: "claude",
|
|
isActive: true,
|
|
quotaWindowThresholds: { "weekly (7d)": 10 },
|
|
}),
|
|
getProviderConnections: async () => [],
|
|
getProviderLimitsCache: () => ({
|
|
plan: "Claude Max",
|
|
quotas: {
|
|
"session (5h)": {
|
|
used: 0,
|
|
total: 100,
|
|
resetAt: new Date(NOW + 4 * 60 * 60_000 + 4 * 60_000).toISOString(),
|
|
},
|
|
"weekly (7d)": {
|
|
used: 90,
|
|
total: 100,
|
|
resetAt: new Date(NOW + 24 * 60 * 60_000 + 44 * 60_000).toISOString(),
|
|
},
|
|
},
|
|
message: null,
|
|
fetchedAt: new Date(NOW).toISOString(),
|
|
}),
|
|
getAllProviderLimitsCache: () => ({}),
|
|
isValidApiKey: async () => true,
|
|
getApiKeyMetadata: async () => null,
|
|
getQuotaPolicy: async () => ({
|
|
defaultThresholdPercent: 0,
|
|
providerWindowDefaults: {},
|
|
}),
|
|
}
|
|
);
|
|
|
|
assert.equal(
|
|
text,
|
|
[
|
|
"Provider quota",
|
|
"Session",
|
|
"100% left",
|
|
"⏱ reset in 4h 4m",
|
|
"",
|
|
"Weekly",
|
|
"0% left",
|
|
"⏱ reset in 1d 0h 44m",
|
|
].join("\n")
|
|
);
|
|
});
|
|
|
|
test("handleInternalUsageCommandHttpRequest returns terminal text for an allowed API key", async () => {
|
|
const response = await handleInternalUsageCommandHttpRequest(
|
|
new Request("http://localhost/api/usage/om-usage?provider=claude", {
|
|
headers: { Authorization: "Bearer sk-allowed" },
|
|
}),
|
|
{
|
|
now: () => NOW,
|
|
isValidApiKey: async (apiKey) => apiKey === "sk-allowed",
|
|
getApiKeyMetadata: async () => ({
|
|
id: "key-allowed",
|
|
name: "Claude terminal",
|
|
allowedConnections: ["conn-codex", "conn-claude"],
|
|
allowUsageCommand: true,
|
|
}),
|
|
getProviderConnectionById: async (connectionId) => ({
|
|
id: connectionId,
|
|
provider: connectionId === "conn-claude" ? "claude" : "codex",
|
|
isActive: true,
|
|
}),
|
|
getProviderConnections: async () => [],
|
|
getProviderLimitsCache: (connectionId) =>
|
|
connectionId === "conn-claude"
|
|
? {
|
|
plan: "Claude Max",
|
|
quotas: {
|
|
"session (5h)": {
|
|
used: 74,
|
|
total: 100,
|
|
remaining: 26,
|
|
resetAt: new Date(NOW + 2 * 60 * 60_000).toISOString(),
|
|
},
|
|
"weekly (7d)": {
|
|
used: 25,
|
|
total: 100,
|
|
remaining: 75,
|
|
resetAt: new Date(NOW + 6 * 24 * 60 * 60_000).toISOString(),
|
|
},
|
|
},
|
|
message: null,
|
|
fetchedAt: new Date(NOW).toISOString(),
|
|
}
|
|
: {
|
|
plan: "Codex Pro",
|
|
quotas: {
|
|
weekly: {
|
|
used: 9,
|
|
total: 100,
|
|
remaining: 91,
|
|
resetAt: new Date(NOW + 5 * 24 * 60 * 60_000).toISOString(),
|
|
},
|
|
},
|
|
message: null,
|
|
fetchedAt: new Date(NOW).toISOString(),
|
|
},
|
|
getAllProviderLimitsCache: () => ({}),
|
|
getQuotaPolicy: async () => ({
|
|
defaultThresholdPercent: 0,
|
|
providerWindowDefaults: {},
|
|
}),
|
|
getApiKeyUsageLimitStatus: async () => {
|
|
throw new Error("usage limit lookup must not run for provider quota output");
|
|
},
|
|
}
|
|
);
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(response.headers.get("content-type"), "text/plain; charset=utf-8");
|
|
assert.equal(
|
|
await response.text(),
|
|
[
|
|
"Provider quota",
|
|
"Session",
|
|
"26% left",
|
|
"⏱ reset in 2h 0m",
|
|
"",
|
|
"Weekly",
|
|
"75% left",
|
|
"⏱ reset in 6d 0h 0m",
|
|
].join("\n")
|
|
);
|
|
});
|
|
|
|
test("handleInternalUsageCommandHttpRequest sanitizes internal errors and never leaks stack traces", async () => {
|
|
const response = await handleInternalUsageCommandHttpRequest(
|
|
new Request("http://localhost/api/usage/om-usage", {
|
|
headers: { Authorization: "Bearer sk-boom" },
|
|
}),
|
|
{
|
|
isValidApiKey: async () => {
|
|
throw new Error(
|
|
`boom at /home/diegosouzapw/dev/proxys/OmniRoute/src/lib/usage/internalUsageCommand.ts:1:1`
|
|
);
|
|
},
|
|
getApiKeyMetadata: async () => {
|
|
throw new Error("metadata lookup must not run when auth check throws");
|
|
},
|
|
getProviderConnectionById: async () => null,
|
|
getProviderConnections: async () => [],
|
|
getProviderLimitsCache: () => null,
|
|
getAllProviderLimitsCache: () => ({}),
|
|
getApiKeyUsageLimitStatus: async () => {
|
|
throw new Error("usage limit lookup must not run when auth check throws");
|
|
},
|
|
}
|
|
);
|
|
|
|
assert.equal(response.status, 500);
|
|
assert.equal(response.headers.get("content-type"), "application/json");
|
|
const body = await response.json();
|
|
assert.equal(typeof body.error.message, "string");
|
|
assert.equal(body.error.message.includes("at /"), false);
|
|
assert.equal(body.error.message.includes("internalUsageCommand.ts"), false);
|
|
});
|
|
|
|
test("handleInternalUsageCommandHttpRequest rejects invalid API keys as plain text", async () => {
|
|
const response = await handleInternalUsageCommandHttpRequest(
|
|
new Request("http://localhost/api/usage/om-usage", {
|
|
headers: { Authorization: "Bearer sk-invalid" },
|
|
}),
|
|
{
|
|
isValidApiKey: async () => false,
|
|
getApiKeyMetadata: async () => {
|
|
throw new Error("metadata lookup must not run for invalid keys");
|
|
},
|
|
getProviderConnectionById: async () => null,
|
|
getProviderConnections: async () => [],
|
|
getProviderLimitsCache: () => null,
|
|
getAllProviderLimitsCache: () => ({}),
|
|
getApiKeyUsageLimitStatus: async () => {
|
|
throw new Error("usage limit lookup must not run for invalid keys");
|
|
},
|
|
}
|
|
);
|
|
|
|
assert.equal(response.status, 401);
|
|
assert.equal(response.headers.get("content-type"), "text/plain; charset=utf-8");
|
|
assert.equal(await response.text(), "Usage command requires an authenticated API key.");
|
|
});
|
|
|
|
test("handleInternalUsageCommandHttpRequest rejects API keys without usage command access", async () => {
|
|
const response = await handleInternalUsageCommandHttpRequest(
|
|
new Request("http://localhost/api/usage/om-usage", {
|
|
headers: { Authorization: "Bearer sk-disabled" },
|
|
}),
|
|
{
|
|
isValidApiKey: async () => true,
|
|
getApiKeyMetadata: async () => ({
|
|
id: "key-disabled",
|
|
allowUsageCommand: false,
|
|
}),
|
|
getProviderConnectionById: async () => null,
|
|
getProviderConnections: async () => [],
|
|
getProviderLimitsCache: () => null,
|
|
getAllProviderLimitsCache: () => ({}),
|
|
getApiKeyUsageLimitStatus: async () => {
|
|
throw new Error("usage limit lookup must not run for disabled keys");
|
|
},
|
|
}
|
|
);
|
|
|
|
assert.equal(response.status, 403);
|
|
assert.equal(response.headers.get("content-type"), "text/plain; charset=utf-8");
|
|
assert.equal(await response.text(), "Usage command is disabled for this API key.");
|
|
});
|
|
|
|
test("handleInternalUsageCommand returns disabled response locally without provider routing", async () => {
|
|
const response = await handleInternalUsageCommand(
|
|
new Request("http://localhost/v1/chat/completions", {
|
|
method: "POST",
|
|
headers: { Authorization: "Bearer sk-disabled" },
|
|
}),
|
|
{
|
|
model: "claude-opus-4-8",
|
|
messages: [{ role: "user", content: "@@om-usage" }],
|
|
},
|
|
{
|
|
isValidApiKey: async () => true,
|
|
getApiKeyMetadata: async () => ({
|
|
id: "key-disabled",
|
|
name: "disabled",
|
|
allowedConnections: [],
|
|
allowUsageCommand: false,
|
|
}),
|
|
now: () => NOW,
|
|
getProviderConnectionById: async () => null,
|
|
getProviderConnections: async () => {
|
|
throw new Error("provider connection lookup must not run when disabled");
|
|
},
|
|
getProviderLimitsCache: () => null,
|
|
getAllProviderLimitsCache: () => {
|
|
throw new Error("provider cache lookup must not run when disabled");
|
|
},
|
|
}
|
|
);
|
|
|
|
assert.ok(response, "command should be handled locally");
|
|
assert.equal(response.status, 200);
|
|
const body = (await response.json()) as {
|
|
choices: Array<{ message: { content: string } }>;
|
|
};
|
|
assert.equal(body.choices[0].message.content, "Usage command is disabled for this API key.");
|
|
});
|
|
|
|
test("handleInternalUsageCommand returns enabled usage snapshot locally", async () => {
|
|
const response = await handleInternalUsageCommand(
|
|
new Request("http://localhost/v1/messages", {
|
|
method: "POST",
|
|
headers: {
|
|
"anthropic-version": "2023-06-01",
|
|
"x-api-key": "sk-enabled",
|
|
},
|
|
}),
|
|
{
|
|
model: "claude-opus-4-8",
|
|
messages: [{ role: "user", content: " @@om-usage " }],
|
|
},
|
|
{
|
|
isValidApiKey: async () => true,
|
|
getApiKeyMetadata: async () => ({
|
|
id: "key-enabled",
|
|
name: "enabled",
|
|
allowedConnections: ["conn-claude"],
|
|
allowUsageCommand: true,
|
|
}),
|
|
now: () => NOW,
|
|
getProviderConnectionById: async () => ({
|
|
id: "conn-claude",
|
|
provider: "claude",
|
|
isActive: true,
|
|
}),
|
|
getProviderConnections: async () => [],
|
|
getProviderLimitsCache: () => ({
|
|
plan: "Claude Max",
|
|
quotas: {
|
|
"session (5h)": {
|
|
used: 53,
|
|
total: 100,
|
|
resetAt: new Date(NOW + 9 * 60_000).toISOString(),
|
|
},
|
|
"weekly (7d)": {
|
|
used: 72,
|
|
total: 100,
|
|
resetAt: new Date(NOW + 24 * 60 * 60_000).toISOString(),
|
|
},
|
|
"weekly sonnet (7d)": {
|
|
used: 30,
|
|
total: 100,
|
|
resetAt: new Date(NOW + 24 * 60 * 60_000).toISOString(),
|
|
},
|
|
},
|
|
message: null,
|
|
fetchedAt: new Date(NOW).toISOString(),
|
|
}),
|
|
getAllProviderLimitsCache: () => ({}),
|
|
getQuotaPolicy: async () => ({
|
|
defaultThresholdPercent: 0,
|
|
providerWindowDefaults: {},
|
|
}),
|
|
}
|
|
);
|
|
|
|
assert.ok(response, "command should be handled locally");
|
|
assert.equal(response.status, 200);
|
|
const body = (await response.json()) as {
|
|
content: Array<{ type: string; text: string }>;
|
|
};
|
|
assert.equal(body.content[0].text.includes("Weekly\n28% left\n⏱ reset in 1d 0h 0m"), true);
|
|
});
|
|
|
|
test("handleInternalUsageCommand ignores normal prompts", async () => {
|
|
const response = await handleInternalUsageCommand(
|
|
new Request("http://localhost/v1/chat/completions", {
|
|
method: "POST",
|
|
headers: { Authorization: "Bearer sk-enabled" },
|
|
}),
|
|
{
|
|
model: "claude-opus-4-8",
|
|
messages: [{ role: "user", content: "me mostra @@om-usage" }],
|
|
},
|
|
{
|
|
isValidApiKey: async () => {
|
|
throw new Error("auth must not run for non-exact prompts");
|
|
},
|
|
getApiKeyMetadata: async () => null,
|
|
now: () => NOW,
|
|
getProviderConnectionById: async () => null,
|
|
getProviderConnections: async () => [],
|
|
getProviderLimitsCache: () => null,
|
|
getAllProviderLimitsCache: () => ({}),
|
|
}
|
|
);
|
|
|
|
assert.equal(response, null);
|
|
});
|