mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
refactor: dedupe extension and ui helpers
This commit is contained in:
@@ -26,6 +26,23 @@ function createState(request: RequestFn, overrides: Partial<UsageState> = {}): U
|
||||
};
|
||||
}
|
||||
|
||||
function expectSpecificTimezoneCalls(request: ReturnType<typeof vi.fn>, startCall: number): void {
|
||||
expect(request).toHaveBeenNthCalledWith(startCall, "sessions.usage", {
|
||||
startDate: "2026-02-16",
|
||||
endDate: "2026-02-16",
|
||||
mode: "specific",
|
||||
utcOffset: "UTC+5:30",
|
||||
limit: 1000,
|
||||
includeContextWeight: true,
|
||||
});
|
||||
expect(request).toHaveBeenNthCalledWith(startCall + 1, "usage.cost", {
|
||||
startDate: "2026-02-16",
|
||||
endDate: "2026-02-16",
|
||||
mode: "specific",
|
||||
utcOffset: "UTC+5:30",
|
||||
});
|
||||
}
|
||||
|
||||
describe("usage controller date interpretation params", () => {
|
||||
beforeEach(() => {
|
||||
__test.resetLegacyUsageDateParamsCache();
|
||||
@@ -48,20 +65,7 @@ describe("usage controller date interpretation params", () => {
|
||||
|
||||
await loadUsage(state);
|
||||
|
||||
expect(request).toHaveBeenNthCalledWith(1, "sessions.usage", {
|
||||
startDate: "2026-02-16",
|
||||
endDate: "2026-02-16",
|
||||
mode: "specific",
|
||||
utcOffset: "UTC+5:30",
|
||||
limit: 1000,
|
||||
includeContextWeight: true,
|
||||
});
|
||||
expect(request).toHaveBeenNthCalledWith(2, "usage.cost", {
|
||||
startDate: "2026-02-16",
|
||||
endDate: "2026-02-16",
|
||||
mode: "specific",
|
||||
utcOffset: "UTC+5:30",
|
||||
});
|
||||
expectSpecificTimezoneCalls(request, 1);
|
||||
});
|
||||
|
||||
it("sends utc mode without offset when usage timezone is utc", async () => {
|
||||
@@ -124,20 +128,7 @@ describe("usage controller date interpretation params", () => {
|
||||
|
||||
await loadUsage(state);
|
||||
|
||||
expect(request).toHaveBeenNthCalledWith(1, "sessions.usage", {
|
||||
startDate: "2026-02-16",
|
||||
endDate: "2026-02-16",
|
||||
mode: "specific",
|
||||
utcOffset: "UTC+5:30",
|
||||
limit: 1000,
|
||||
includeContextWeight: true,
|
||||
});
|
||||
expect(request).toHaveBeenNthCalledWith(2, "usage.cost", {
|
||||
startDate: "2026-02-16",
|
||||
endDate: "2026-02-16",
|
||||
mode: "specific",
|
||||
utcOffset: "UTC+5:30",
|
||||
});
|
||||
expectSpecificTimezoneCalls(request, 1);
|
||||
expect(request).toHaveBeenNthCalledWith(3, "sessions.usage", {
|
||||
startDate: "2026-02-16",
|
||||
endDate: "2026-02-16",
|
||||
|
||||
@@ -3,8 +3,7 @@ import {
|
||||
defaultTitle,
|
||||
formatToolDetailText,
|
||||
normalizeToolName,
|
||||
resolveActionArg,
|
||||
resolveToolVerbAndDetail,
|
||||
resolveToolVerbAndDetailForArgs,
|
||||
type ToolDisplaySpec as ToolDisplaySpecBase,
|
||||
} from "../../../src/agents/tool-display-common.js";
|
||||
import type { IconName } from "./icons.ts";
|
||||
@@ -126,12 +125,10 @@ export function resolveToolDisplay(params: {
|
||||
const icon = (spec?.icon ?? FALLBACK.icon ?? "puzzle") as IconName;
|
||||
const title = spec?.title ?? defaultTitle(name);
|
||||
const label = spec?.label ?? title;
|
||||
const action = resolveActionArg(params.args);
|
||||
let { verb, detail } = resolveToolVerbAndDetail({
|
||||
let { verb, detail } = resolveToolVerbAndDetailForArgs({
|
||||
toolKey: key,
|
||||
args: params.args,
|
||||
meta: params.meta,
|
||||
action,
|
||||
spec,
|
||||
fallbackDetailKeys: FALLBACK.detailKeys,
|
||||
detailMode: "first",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export type UpdateAvailable = import("../../../src/infra/update-startup.js").UpdateAvailable;
|
||||
import type { CronJobBase } from "../../../src/cron/types-shared.js";
|
||||
import type { ConfigUiHints } from "../../../src/shared/config-ui-hints-types.js";
|
||||
import type {
|
||||
GatewayAgentRow as SharedGatewayAgentRow,
|
||||
@@ -492,22 +493,14 @@ export type CronJobState = {
|
||||
lastFailureAlertAtMs?: number;
|
||||
};
|
||||
|
||||
export type CronJob = {
|
||||
id: string;
|
||||
agentId?: string;
|
||||
sessionKey?: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
enabled: boolean;
|
||||
deleteAfterRun?: boolean;
|
||||
createdAtMs: number;
|
||||
updatedAtMs: number;
|
||||
schedule: CronSchedule;
|
||||
sessionTarget: CronSessionTarget;
|
||||
wakeMode: CronWakeMode;
|
||||
payload: CronPayload;
|
||||
delivery?: CronDelivery;
|
||||
failureAlert?: CronFailureAlert | false;
|
||||
export type CronJob = CronJobBase<
|
||||
CronSchedule,
|
||||
CronSessionTarget,
|
||||
CronWakeMode,
|
||||
CronPayload,
|
||||
CronDelivery,
|
||||
CronFailureAlert | false
|
||||
> & {
|
||||
state?: CronJobState;
|
||||
};
|
||||
|
||||
|
||||
@@ -42,6 +42,52 @@ import {
|
||||
|
||||
export type { UsageColumnId, SessionLogEntry, SessionLogRole };
|
||||
|
||||
function createEmptyUsageTotals(): UsageTotals {
|
||||
return {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
totalTokens: 0,
|
||||
totalCost: 0,
|
||||
inputCost: 0,
|
||||
outputCost: 0,
|
||||
cacheReadCost: 0,
|
||||
cacheWriteCost: 0,
|
||||
missingCostEntries: 0,
|
||||
};
|
||||
}
|
||||
|
||||
function addUsageTotals(
|
||||
acc: UsageTotals,
|
||||
usage: {
|
||||
input: number;
|
||||
output: number;
|
||||
cacheRead: number;
|
||||
cacheWrite: number;
|
||||
totalTokens: number;
|
||||
totalCost: number;
|
||||
inputCost?: number;
|
||||
outputCost?: number;
|
||||
cacheReadCost?: number;
|
||||
cacheWriteCost?: number;
|
||||
missingCostEntries?: number;
|
||||
},
|
||||
): UsageTotals {
|
||||
acc.input += usage.input;
|
||||
acc.output += usage.output;
|
||||
acc.cacheRead += usage.cacheRead;
|
||||
acc.cacheWrite += usage.cacheWrite;
|
||||
acc.totalTokens += usage.totalTokens;
|
||||
acc.totalCost += usage.totalCost;
|
||||
acc.inputCost += usage.inputCost ?? 0;
|
||||
acc.outputCost += usage.outputCost ?? 0;
|
||||
acc.cacheReadCost += usage.cacheReadCost ?? 0;
|
||||
acc.cacheWriteCost += usage.cacheWriteCost ?? 0;
|
||||
acc.missingCostEntries += usage.missingCostEntries ?? 0;
|
||||
return acc;
|
||||
}
|
||||
|
||||
export function renderUsage(props: UsageProps) {
|
||||
// Show loading skeleton if loading and no data yet
|
||||
if (props.loading && !props.totals) {
|
||||
@@ -206,69 +252,15 @@ export function renderUsage(props: UsageProps) {
|
||||
// Compute totals from sessions
|
||||
const computeSessionTotals = (sessions: UsageSessionEntry[]): UsageTotals => {
|
||||
return sessions.reduce(
|
||||
(acc, s) => {
|
||||
if (s.usage) {
|
||||
acc.input += s.usage.input;
|
||||
acc.output += s.usage.output;
|
||||
acc.cacheRead += s.usage.cacheRead;
|
||||
acc.cacheWrite += s.usage.cacheWrite;
|
||||
acc.totalTokens += s.usage.totalTokens;
|
||||
acc.totalCost += s.usage.totalCost;
|
||||
acc.inputCost += s.usage.inputCost ?? 0;
|
||||
acc.outputCost += s.usage.outputCost ?? 0;
|
||||
acc.cacheReadCost += s.usage.cacheReadCost ?? 0;
|
||||
acc.cacheWriteCost += s.usage.cacheWriteCost ?? 0;
|
||||
acc.missingCostEntries += s.usage.missingCostEntries ?? 0;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
totalTokens: 0,
|
||||
totalCost: 0,
|
||||
inputCost: 0,
|
||||
outputCost: 0,
|
||||
cacheReadCost: 0,
|
||||
cacheWriteCost: 0,
|
||||
missingCostEntries: 0,
|
||||
},
|
||||
(acc, s) => (s.usage ? addUsageTotals(acc, s.usage) : acc),
|
||||
createEmptyUsageTotals(),
|
||||
);
|
||||
};
|
||||
|
||||
// Compute totals from daily data for selected days (more accurate than session totals)
|
||||
const computeDailyTotals = (days: string[]): UsageTotals => {
|
||||
const matchingDays = props.costDaily.filter((d) => days.includes(d.date));
|
||||
return matchingDays.reduce(
|
||||
(acc, d) => {
|
||||
acc.input += d.input;
|
||||
acc.output += d.output;
|
||||
acc.cacheRead += d.cacheRead;
|
||||
acc.cacheWrite += d.cacheWrite;
|
||||
acc.totalTokens += d.totalTokens;
|
||||
acc.totalCost += d.totalCost;
|
||||
acc.inputCost += d.inputCost ?? 0;
|
||||
acc.outputCost += d.outputCost ?? 0;
|
||||
acc.cacheReadCost += d.cacheReadCost ?? 0;
|
||||
acc.cacheWriteCost += d.cacheWriteCost ?? 0;
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
totalTokens: 0,
|
||||
totalCost: 0,
|
||||
inputCost: 0,
|
||||
outputCost: 0,
|
||||
cacheReadCost: 0,
|
||||
cacheWriteCost: 0,
|
||||
missingCostEntries: 0,
|
||||
},
|
||||
);
|
||||
return matchingDays.reduce((acc, day) => addUsageTotals(acc, day), createEmptyUsageTotals());
|
||||
};
|
||||
|
||||
// Compute display totals and count based on filters
|
||||
|
||||
Reference in New Issue
Block a user