import {
mergeSessionEntry,
setSessionRuntimeModel,
type SessionEntry,
updateSessionStore,
} from "../../config/sessions.js" ;
import type { OpenClawConfig } from "../../config/types.openclaw.js" ;
import { normalizeOptionalString } from "../../shared/string-coerce.js" ;
import { clearCliSession, setCliSessionBinding, setCliSessionId } from "../cli-session.js" ;
import { DEFAULT_CONTEXT_TOKENS } from "../defaults.js" ;
import { isCliProvider } from "../model-selection.js" ;
import { deriveSessionTotalTokens, hasNonzeroUsage } from "../usage.js" ;
type RunResult = Awaited<ReturnType<(typeof import ("../pi-embedded.js" ))["runEmbeddedPiAgent" ]>>;
let usageFormatModulePromise: Promise<typeof import ("../../utils/usage-format.js" )> | undefined;
let contextModulePromise: Promise<typeof import ("../context.js" )> | undefined;
async function getUsageFormatModule() {
usageFormatModulePromise ??= import ("../../utils/usage-format.js" );
return await usageFormatModulePromise;
}
async function getContextModule() {
contextModulePromise ??= import ("../context.js" );
return await contextModulePromise;
}
function resolveNonNegativeNumber(value: number | undefined): number | undefined {
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
}
function resolvePositiveInteger(value: number | undefined): number | undefined {
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0 ) {
return undefined;
}
return Math.floor(value);
}
export async function updateSessionStoreAfterAgentRun(params: {
cfg: OpenClawConfig;
contextTokensOverride?: number;
sessionId: string;
sessionKey: string;
storePath: string;
sessionStore: Record<string, SessionEntry>;
defaultProvider: string;
defaultModel: string;
fallbackProvider?: string;
fallbackModel?: string;
result: RunResult;
}) {
const {
cfg,
sessionId,
sessionKey,
storePath,
sessionStore,
defaultProvider,
defaultModel,
fallbackProvider,
fallbackModel,
result,
} = params;
const usage = result.meta.agentMeta?.usage;
const promptTokens = result.meta.agentMeta?.promptTokens;
const compactionsThisRun = Math.max(0 , result.meta.agentMeta?.compactionCount ?? 0 );
const modelUsed = result.meta.agentMeta?.model ?? fallbackModel ?? defaultModel;
const providerUsed = result.meta.agentMeta?.provider ?? fallbackProvider ?? defaultProvider;
const agentHarnessId = normalizeOptionalString(result.meta.agentMeta?.agentHarnessId);
const runtimeContextTokens = resolvePositiveInteger(result.meta.agentMeta?.contextTokens);
const contextTokens =
runtimeContextTokens !== undefined
? runtimeContextTokens
: typeof params.contextTokensOverride === "number" && params.contextTokensOverride > 0
? params.contextTokensOverride
: ((await getContextModule()).resolveContextTokensForModel({
cfg,
provider: providerUsed,
model: modelUsed,
fallbackContextTokens: DEFAULT_CONTEXT_TOKENS,
allowAsyncLoad: false ,
}) ?? DEFAULT_CONTEXT_TOKENS);
const entry = sessionStore[sessionKey] ?? {
sessionId,
updatedAt: Date.now(),
};
const next: SessionEntry = {
...entry,
sessionId,
updatedAt: Date.now(),
contextTokens,
};
setSessionRuntimeModel(next, {
provider: providerUsed,
model: modelUsed,
});
if (agentHarnessId) {
next.agentHarnessId = agentHarnessId;
} else if (result.meta.executionTrace?.runner === "cli" ) {
next.agentHarnessId = undefined;
}
if (isCliProvider(providerUsed, cfg)) {
const cliSessionBinding = result.meta.agentMeta?.cliSessionBinding;
if (cliSessionBinding?.sessionId?.trim()) {
setCliSessionBinding(next, providerUsed, cliSessionBinding);
} else {
const cliSessionId = result.meta.agentMeta?.sessionId?.trim();
if (cliSessionId) {
setCliSessionId(next, providerUsed, cliSessionId);
}
}
}
next.abortedLastRun = result.meta.aborted ?? false ;
if (result.meta.systemPromptReport) {
next.systemPromptReport = result.meta.systemPromptReport;
}
if (hasNonzeroUsage(usage)) {
const { estimateUsageCost, resolveModelCostConfig } = await getUsageFormatModule();
const input = usage.input ?? 0 ;
const output = usage.output ?? 0 ;
const totalTokens = deriveSessionTotalTokens({
usage: promptTokens ? undefined : usage,
contextTokens,
promptTokens,
});
const runEstimatedCostUsd = resolveNonNegativeNumber(
estimateUsageCost({
usage,
cost: resolveModelCostConfig({
provider: providerUsed,
model: modelUsed,
config: cfg,
}),
}),
);
next.inputTokens = input;
next.outputTokens = output;
if (typeof totalTokens === "number" && Number.isFinite(totalTokens) && totalTokens > 0 ) {
next.totalTokens = totalTokens;
next.totalTokensFresh = true ;
} else {
next.totalTokens = undefined;
next.totalTokensFresh = false ;
}
next.cacheRead = usage.cacheRead ?? 0 ;
next.cacheWrite = usage.cacheWrite ?? 0 ;
// Snapshot cost like tokens (runEstimatedCostUsd is already computed from
// cumulative run usage, so assign directly instead of accumulating).
// Fixes #69347: cost was inflated 1x-72x by accumulating on every persist.
if (runEstimatedCostUsd !== undefined) {
next.estimatedCostUsd = runEstimatedCostUsd;
}
} else if (
typeof entry.totalTokens === "number" &&
Number.isFinite(entry.totalTokens) &&
entry.totalTokens > 0
) {
next.totalTokens = entry.totalTokens;
next.totalTokensFresh = false ;
}
if (compactionsThisRun > 0 ) {
next.compactionCount = (entry.compactionCount ?? 0 ) + compactionsThisRun;
}
const persisted = await updateSessionStore(storePath, (store) => {
const merged = mergeSessionEntry(store[sessionKey], next);
store[sessionKey] = merged;
return merged;
});
sessionStore[sessionKey] = persisted;
}
export async function clearCliSessionInStore(params: {
provider: string;
sessionKey: string;
sessionStore: Record<string, SessionEntry>;
storePath: string;
}): Promise<SessionEntry | undefined> {
const { provider, sessionKey, sessionStore, storePath } = params;
const entry = sessionStore[sessionKey];
if (!entry) {
return undefined;
}
const next = { ...entry };
clearCliSession(next, provider);
next.updatedAt = Date.now();
const persisted = await updateSessionStore(storePath, (store) => {
const merged = mergeSessionEntry(store[sessionKey], next);
store[sessionKey] = merged;
return merged;
});
sessionStore[sessionKey] = persisted;
return persisted;
}
Messung V0.5 in Prozent C=100 H=100 G=100
¤ Dauer der Verarbeitung: 0.12 Sekunden
(vorverarbeitet am 2026-06-10)
¤
*© Formatika GbR, Deutschland