@@ -581,6 +581,37 @@ export async function generateSummary(
|
||||
thinkingLevel?: ThinkingLevel,
|
||||
streamFn?: StreamFn,
|
||||
env?: Record<string, string>,
|
||||
): Promise<string> {
|
||||
return (
|
||||
await generateSummaryWithUsage(
|
||||
currentMessages,
|
||||
model,
|
||||
reserveTokens,
|
||||
apiKey,
|
||||
headers,
|
||||
signal,
|
||||
customInstructions,
|
||||
previousSummary,
|
||||
thinkingLevel,
|
||||
streamFn,
|
||||
env,
|
||||
)
|
||||
).text;
|
||||
}
|
||||
|
||||
/** Generate or update a conversation summary and return its provider usage. */
|
||||
export async function generateSummaryWithUsage(
|
||||
currentMessages: AgentMessage[],
|
||||
model: Model<any>,
|
||||
reserveTokens: number,
|
||||
apiKey: string | undefined,
|
||||
headers?: Record<string, string>,
|
||||
signal?: AbortSignal,
|
||||
customInstructions?: string,
|
||||
previousSummary?: string,
|
||||
thinkingLevel?: ThinkingLevel,
|
||||
streamFn?: StreamFn,
|
||||
env?: Record<string, string>,
|
||||
): Promise<{ text: string; usage: Usage }> {
|
||||
const maxTokens = Math.min(
|
||||
Math.floor(0.8 * reserveTokens),
|
||||
@@ -790,7 +821,7 @@ export async function compact(
|
||||
let historyText = "No prior history.";
|
||||
let historyUsage: Usage | undefined;
|
||||
if (messagesToSummarize.length > 0) {
|
||||
const historyResult = await generateSummary(
|
||||
const historyResult = await generateSummaryWithUsage(
|
||||
messagesToSummarize,
|
||||
model,
|
||||
settings.reserveTokens,
|
||||
@@ -822,7 +853,7 @@ export async function compact(
|
||||
summaryUsage = historyUsage ? combineUsage(historyUsage, turnPrefixResult.usage) : turnPrefixResult.usage;
|
||||
} else {
|
||||
// Just generate history summary
|
||||
const result = await generateSummary(
|
||||
const result = await generateSummaryWithUsage(
|
||||
messagesToSummarize,
|
||||
model,
|
||||
settings.reserveTokens,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Usage } from "@earendil-works/pi-ai/compat";
|
||||
import type { SessionEntry } from "./session-manager.ts";
|
||||
|
||||
export interface UsageTotals {
|
||||
input: number;
|
||||
@@ -25,3 +26,45 @@ export function addUsageToTotals(totals: UsageTotals, usage: Usage): void {
|
||||
totals.cacheWrite += usage.cacheWrite;
|
||||
totals.cost += usage.cost.total;
|
||||
}
|
||||
|
||||
export interface UsageCostBreakdownEntry {
|
||||
key: string;
|
||||
cost: number;
|
||||
tokens: number;
|
||||
}
|
||||
|
||||
/** Group attributable assistant usage by model and all other usage into a separate bucket. */
|
||||
export function getUsageCostBreakdown(entries: SessionEntry[]): UsageCostBreakdownEntry[] {
|
||||
const totalsByKey = new Map<string, UsageTotals>();
|
||||
|
||||
for (const entry of entries) {
|
||||
let key: string | undefined;
|
||||
let usage: Usage | undefined;
|
||||
if (entry.type === "message" && entry.message.role === "assistant") {
|
||||
key = `${entry.message.provider}/${entry.message.responseModel ?? entry.message.model}`;
|
||||
usage = entry.message.usage;
|
||||
} else if (entry.type === "message" && entry.message.role === "toolResult" && entry.message.usage) {
|
||||
key = "Tools/summaries";
|
||||
usage = entry.message.usage;
|
||||
} else if ((entry.type === "branch_summary" || entry.type === "compaction") && entry.usage) {
|
||||
key = "Tools/summaries";
|
||||
usage = entry.usage;
|
||||
}
|
||||
if (!key || !usage) continue;
|
||||
|
||||
let totals = totalsByKey.get(key);
|
||||
if (!totals) {
|
||||
totals = createUsageTotals();
|
||||
totalsByKey.set(key, totals);
|
||||
}
|
||||
addUsageToTotals(totals, usage);
|
||||
}
|
||||
|
||||
return Array.from(totalsByKey, ([key, totals]) => ({
|
||||
key,
|
||||
cost: totals.cost,
|
||||
tokens: totals.input + totals.output + totals.cacheRead + totals.cacheWrite,
|
||||
}))
|
||||
.filter((entry) => entry.cost > 0 || entry.tokens > 0)
|
||||
.sort((a, b) => b.cost - a.cost);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user