File size: 3,762 Bytes
4440aec | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | 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 },
};
}
|