openclaw / src /auto-reply /usage-bar /contract.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
4440aec verified
Raw History Blame Contribute Delete
3.76 kB
import type { PluginHookReplyUsageState } from "../../plugins/hook-types.js";
import type { UsageContract } from "./translator.js";
export function buildUsageContract(
state: PluginHookReplyUsageState,
surface?: string,
): UsageContract {
const usage = state.usage ?? {};
const input = usage.input;
const output = usage.output;
const cacheRead = usage.cacheRead;
const cacheWrite = usage.cacheWrite;
const total = usage.total;
const hasSplitTokens = input !== undefined || output !== undefined;
const hasTotalOnlyTokens = !hasSplitTokens && total !== undefined;
const hasTokens =
hasSplitTokens || cacheRead !== undefined || cacheWrite !== undefined || total !== undefined;
const promptTotal = (cacheRead ?? 0) + (cacheWrite ?? 0) + (input ?? 0);
const cacheHitPct =
promptTotal > 0 ? Math.round(((cacheRead ?? 0) / promptTotal) * 100) : undefined;
const last = state.lastUsage;
const lastPromptTotal = last
? (last.cacheRead ?? 0) + (last.cacheWrite ?? 0) + (last.input ?? 0)
: 0;
const lastCacheHitPct =
last && lastPromptTotal > 0
? Math.round(((last.cacheRead ?? 0) / lastPromptTotal) * 100)
: undefined;
const maxTokens = state.contextTokenBudget;
const usedTokens =
typeof state.contextUsedTokens === "number" && state.contextUsedTokens > 0
? state.contextUsedTokens
: promptTotal > 0
? promptTotal
: undefined;
const pctUsed =
maxTokens && usedTokens !== undefined ? Math.round((usedTokens / maxTokens) * 100) : undefined;
const overrideSource = state.overrideSource ?? null;
const isOverride =
typeof state.overrideSource === "string" &&
state.overrideSource !== "" &&
state.overrideSource !== "auto";
return {
schema: "openclaw.usageLine.v1",
surface: surface ?? null,
agentId: state.agentId ?? null,
chat_type: state.chatType ?? null,
model: {
id: state.model ?? null,
display_name: state.model ?? null,
provider: state.provider ?? null,
reasoning: state.reasoningEffort ?? null,
actual: state.resolvedRef ?? null,
resolved_ref: state.resolvedRef ?? null,
requested: state.requested ?? null,
is_fallback: state.fallbackUsed === true,
is_override: isOverride,
override_source: overrideSource,
auth_mode: state.authMode ?? null,
},
state: {
fast_mode: typeof state.fastMode === "boolean" ? state.fastMode : null,
compactions: typeof state.compactionCount === "number" ? state.compactionCount : null,
},
usage: {
input_tokens: input,
output_tokens: output,
cache_read_tokens: cacheRead,
cache_write_tokens: cacheWrite,
total_tokens: total,
cache_hit_pct: cacheHitPct,
has_tokens: hasTokens,
has_split_tokens: hasSplitTokens,
has_total_only_tokens: hasTotalOnlyTokens,
last: last
? {
input_tokens: last.input,
output_tokens: last.output,
cache_read_tokens: last.cacheRead,
cache_write_tokens: last.cacheWrite,
total_tokens: last.total,
cache_hit_pct: lastCacheHitPct,
}
: undefined,
},
context: {
used_tokens: usedTokens,
max_tokens: maxTokens,
pct_used: pctUsed,
},
cost: {
turn_usd: typeof state.turnUsd === "number" ? state.turnUsd : null,
available: typeof state.turnUsd === "number",
},
timing: {
duration_ms: typeof state.durationMs === "number" ? state.durationMs : null,
},
identity: {
name: state.identity?.name ?? null,
emoji: state.identity?.emoji ?? null,
avatar: state.identity?.avatar ?? null,
},
session: { id: state.sessionId ?? null },
};
}