cron: log model+token usage per run + add usage report script

This commit is contained in:
Rob Dunn
2026-02-16 08:07:51 -07:00
committed by Peter Steinberger
parent edbc68e9f1
commit ddea5458d0
5 changed files with 361 additions and 6 deletions

View File

@@ -116,6 +116,17 @@ export type RunCronAgentTurnResult = {
* messages. See: https://github.com/openclaw/openclaw/issues/15692
*/
delivered?: boolean;
// Telemetry (best-effort)
model?: string;
provider?: string;
usage?: {
input_tokens?: number;
output_tokens?: number;
total_tokens?: number;
cache_read_tokens?: number;
cache_write_tokens?: number;
};
};
export async function runCronIsolatedAgentTurn(params: {
@@ -474,6 +485,20 @@ export async function runCronIsolatedAgentTurn(params: {
const payloads = runResult.payloads ?? [];
// Update token+model fields in the session store.
// Also collect best-effort telemetry for the cron run log.
let telemetry:
| {
model?: string;
provider?: string;
usage?: {
input_tokens?: number;
output_tokens?: number;
total_tokens?: number;
cache_read_tokens?: number;
cache_write_tokens?: number;
};
}
| undefined;
{
const usage = runResult.meta.agentMeta?.usage;
const promptTokens = runResult.meta.agentMeta?.promptTokens;
@@ -504,6 +529,21 @@ export async function runCronIsolatedAgentTurn(params: {
cronSession.sessionEntry.outputTokens = output;
cronSession.sessionEntry.totalTokens = totalTokens;
cronSession.sessionEntry.totalTokensFresh = true;
telemetry = {
model: modelUsed,
provider: providerUsed,
usage: {
input_tokens: input,
output_tokens: output,
total_tokens: totalTokens,
},
};
} else {
telemetry = {
model: modelUsed,
provider: providerUsed,
};
}
await persistSessionEntry();
}
@@ -552,7 +592,7 @@ export async function runCronIsolatedAgentTurn(params: {
});
}
logWarn(`[cron:${params.job.id}] ${resolvedDelivery.error.message}`);
return withRunSession({ status: "ok", summary, outputText });
return withRunSession({ status: "ok", summary, outputText, ...telemetry });
}
if (!resolvedDelivery.to) {
const message = "cron delivery target is missing";
@@ -565,7 +605,7 @@ export async function runCronIsolatedAgentTurn(params: {
});
}
logWarn(`[cron:${params.job.id}] ${message}`);
return withRunSession({ status: "ok", summary, outputText });
return withRunSession({ status: "ok", summary, outputText, ...telemetry });
}
const identity = resolveAgentOutboundIdentity(cfgWithAgentDefaults, agentId);
@@ -643,7 +683,7 @@ export async function runCronIsolatedAgentTurn(params: {
if (activeSubagentRuns > 0) {
// Parent orchestration is still in progress; avoid announcing a partial
// update to the main requester.
return withRunSession({ status: "ok", summary, outputText });
return withRunSession({ status: "ok", summary, outputText, ...telemetry });
}
if (
(hadActiveDescendants || expectedSubagentFollowup) &&
@@ -653,10 +693,10 @@ export async function runCronIsolatedAgentTurn(params: {
) {
// Descendants existed but no post-orchestration synthesis arrived, so
// suppress stale parent text like "on it, pulling everything together".
return withRunSession({ status: "ok", summary, outputText });
return withRunSession({ status: "ok", summary, outputText, ...telemetry });
}
if (synthesizedText.toUpperCase() === SILENT_REPLY_TOKEN.toUpperCase()) {
return withRunSession({ status: "ok", summary, outputText });
return withRunSession({ status: "ok", summary, outputText, ...telemetry });
}
try {
const didAnnounce = await runSubagentAnnounceFlow({
@@ -703,5 +743,5 @@ export async function runCronIsolatedAgentTurn(params: {
}
}
return withRunSession({ status: "ok", summary, outputText, delivered });
return withRunSession({ status: "ok", summary, outputText, delivered, ...telemetry });
}

View File

@@ -13,6 +13,17 @@ export type CronEvent = {
sessionId?: string;
sessionKey?: string;
nextRunAtMs?: number;
// Telemetry (best-effort)
model?: string;
provider?: string;
usage?: {
input_tokens?: number;
output_tokens?: number;
total_tokens?: number;
cache_read_tokens?: number;
cache_write_tokens?: number;
};
};
export type Logger = {
@@ -60,6 +71,17 @@ export type CronServiceDeps = {
* https://github.com/openclaw/openclaw/issues/15692
*/
delivered?: boolean;
// Telemetry (best-effort)
model?: string;
provider?: string;
usage?: {
input_tokens?: number;
output_tokens?: number;
total_tokens?: number;
cache_read_tokens?: number;
cache_write_tokens?: number;
};
}>;
onEvent?: (evt: CronEvent) => void;
};

View File

@@ -216,6 +216,15 @@ export async function onTimer(state: CronServiceState) {
summary?: string;
sessionId?: string;
sessionKey?: string;
model?: string;
provider?: string;
usage?: {
input_tokens?: number;
output_tokens?: number;
total_tokens?: number;
cache_read_tokens?: number;
cache_write_tokens?: number;
};
startedAt: number;
endedAt: number;
}> = [];
@@ -426,6 +435,15 @@ async function executeJobCore(
summary?: string;
sessionId?: string;
sessionKey?: string;
model?: string;
provider?: string;
usage?: {
input_tokens?: number;
output_tokens?: number;
total_tokens?: number;
cache_read_tokens?: number;
cache_write_tokens?: number;
};
}> {
if (job.sessionTarget === "main") {
const text = resolveJobPayloadTextForMain(job);
@@ -515,6 +533,9 @@ async function executeJobCore(
summary: res.summary,
sessionId: res.sessionId,
sessionKey: res.sessionKey,
model: res.model,
provider: res.provider,
usage: res.usage,
};
}
@@ -542,6 +563,15 @@ export async function executeJob(
summary?: string;
sessionId?: string;
sessionKey?: string;
model?: string;
provider?: string;
usage?: {
input_tokens?: number;
output_tokens?: number;
total_tokens?: number;
cache_read_tokens?: number;
cache_write_tokens?: number;
};
};
try {
coreResult = await executeJobCore(state, job);
@@ -574,6 +604,15 @@ function emitJobFinished(
summary?: string;
sessionId?: string;
sessionKey?: string;
model?: string;
provider?: string;
usage?: {
input_tokens?: number;
output_tokens?: number;
total_tokens?: number;
cache_read_tokens?: number;
cache_write_tokens?: number;
};
},
runAtMs: number,
) {
@@ -588,6 +627,9 @@ function emitJobFinished(
runAtMs,
durationMs: job.state.lastDurationMs,
nextRunAtMs: job.state.nextRunAtMs,
model: result.model,
provider: result.provider,
usage: result.usage,
});
}