From 6fbd7e050602ab30ae341ef4b82c8534c4995639 Mon Sep 17 00:00:00 2001 From: Fsocietyhhh <1211904451@qq.com> Date: Fri, 24 Apr 2026 18:55:00 -0700 Subject: [PATCH] fix(stats): track ImageGen / VideoGen costs in usage insights MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before this, only chat LLM calls flowed through `recordUsage()`. The ImageGen and VideoGen tools wrote to the content library but never reported to the global stats file, so: - `franklin insights` undercounted total 30-day spend by the exact amount spent on image + video generation. - The "Top models" breakdown never surfaced `openai/gpt-image-1`, `bytedance/seedance-2.0`, `xai/grok-imagine-video`, etc. even when they were the dominant cost in a session. - Monthly projections in insights were similarly low. On successful generation each tool now fires a fire-and-forget `recordUsage(model, 0, 0, costUsd, 0)`. Input/output tokens stay at 0 because image/video models don't bill by token; the cost is pulled from the live gateway catalog via `findModel` + `estimateCostUsd`, the same path the AskUser cost preview uses. When the catalog lookup fails the tools fall back to hardcoded estimates so stats still get an entry (under-approximate rather than missing). The stats write is intentionally fire-and-forget and error-swallowing: a `~/.blockrun/stats.json` write failure must not turn a paid generation into a user-visible error. Scope is deliberately small — only the two tools, +33 lines, zero schema or behavior change to the existing tracker / insights engine. Any record already written by chat flows continues to work unchanged. --- src/tools/imagegen.ts | 16 ++++++++++++++++ src/tools/videogen.ts | 17 +++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/tools/imagegen.ts b/src/tools/imagegen.ts index 99efb08..4aabb5a 100644 --- a/src/tools/imagegen.ts +++ b/src/tools/imagegen.ts @@ -21,6 +21,8 @@ import type { ContentLibrary } from '../content/library.js'; import { checkImageBudget, recordImageAsset } from '../content/record-image.js'; import { ModelClient } from '../agent/llm.js'; import { analyzeMediaRequest, renderProposalForAskUser } from '../agent/media-router.js'; +import { recordUsage } from '../stats/tracker.js'; +import { findModel, estimateCostUsd } from '../gateway-models.js'; interface ImageGenInput { prompt: string; @@ -220,6 +222,20 @@ function buildExecute(deps: ImageGenDeps) { const sizeKB = (fileSize / 1024).toFixed(1); const revisedPrompt = imageData.revised_prompt ? `\nRevised prompt: ${imageData.revised_prompt}` : ''; + // Stats: record this generation so it shows up in `franklin insights` + // alongside chat spend. Before this, media generations bypassed + // recordUsage entirely (only LLM chat calls were tracked), so the + // insights panel under-reported total spend and never surfaced + // image-generation models in its "top models" list. Fire-and-forget — + // stats write must not fail a user-visible generation. + void (async () => { + try { + const m = await findModel(imageModel); + const estCost = m ? estimateCostUsd(m, { quantity: 1 }) : 0; + recordUsage(imageModel, 0, 0, estCost, 0); + } catch { /* ignore stats errors */ } + })(); + let contentSummary = ''; if (contentId && deps.library) { const rec = recordImageAsset(deps.library, { diff --git a/src/tools/videogen.ts b/src/tools/videogen.ts index 2fba8b7..d6c22ce 100644 --- a/src/tools/videogen.ts +++ b/src/tools/videogen.ts @@ -36,6 +36,8 @@ import { loadChain, API_URLS, VERSION } from '../config.js'; import type { ContentLibrary } from '../content/library.js'; import { ModelClient } from '../agent/llm.js'; import { analyzeMediaRequest, renderProposalForAskUser } from '../agent/media-router.js'; +import { recordUsage } from '../stats/tracker.js'; +import { findModel, estimateCostUsd } from '../gateway-models.js'; interface VideoGenInput { prompt: string; @@ -290,6 +292,21 @@ function buildExecute(deps: VideoGenDeps) { const sizeMB = (fileSize / 1_048_576).toFixed(1); const dur = videoData.duration_seconds ?? duration; + // Stats: record this generation so it shows up in `franklin insights` + // alongside chat spend. Before this, media generations bypassed + // recordUsage entirely, so the insights panel under-reported total + // spend and never surfaced video models in its "top models" list. + // Prefer the live gateway price when the model is in the catalog; + // fall back to the legacy $0.05/s estimate otherwise. Fire-and- + // forget — stats write must not fail a user-visible generation. + void (async () => { + try { + const m = await findModel(videoModel); + const estCost = m ? estimateCostUsd(m, { duration_seconds: dur }) : estimateVideoCostUsd(dur); + recordUsage(videoModel, 0, 0, estCost, 0); + } catch { /* ignore stats errors */ } + })(); + let contentSummary = ''; if (contentId && deps.library) { const rec = deps.library.addAsset(contentId, {