Skip to content

Commit e148c75

Browse files
ashu17706github-actions[bot]Baseline Userclaude
authored
feat(db): model-aware cost estimation and sidecar cleanup (#48)
* release: v0.4.1 (#42) * chore: new branch (#33) * fix(ci): bench scorecard ci windows fixes (#34) * ci: auto-template and title for dev to main PRs * release: v0.3.2 (dev -> main) (#35) * New branch (#33) * chore: add pending project files * refactor(ingest): centralize ingestion via parser/resolver/store layers * docs: document layered ingest architecture * test(perf): add qmd benchmark harness and non-blocking CI regression check * perf(bench): add ingest hotpath benchmark and record qmd optimization * perf(ingest): batch session writes and add stable benchmark tooling * Add benchmark scorecard to CI summary and sticky PR comment * Fix bench import path and temporarily disable design-contract workflow * CI: checkout qmd submodule in perf bench workflow * Fix Windows path handling in ingest session discovery * Feature/bench scorecard ci windows fixes (#34) * chore: add pending project files * refactor(ingest): centralize ingestion via parser/resolver/store layers * docs: document layered ingest architecture * test(perf): add qmd benchmark harness and non-blocking CI regression check * perf(bench): add ingest hotpath benchmark and record qmd optimization * perf(ingest): batch session writes and add stable benchmark tooling * Add benchmark scorecard to CI summary and sticky PR comment * Fix bench import path and temporarily disable design-contract workflow * CI: checkout qmd submodule in perf bench workflow * Fix Windows path handling in ingest session discovery * CI: run full test matrix only on merge branches * CI: auto-create draft prerelease on successful dev CI * CI: auto-template and title for dev to main PRs * ci: create dev draft release after successful dev test matrix * chore: add e2e dev release flow test marker (#36) * release: v0.3.2 (dev -> main) (#37) * New branch (#33) * chore: add pending project files * refactor(ingest): centralize ingestion via parser/resolver/store layers * docs: document layered ingest architecture * test(perf): add qmd benchmark harness and non-blocking CI regression check * perf(bench): add ingest hotpath benchmark and record qmd optimization * perf(ingest): batch session writes and add stable benchmark tooling * Add benchmark scorecard to CI summary and sticky PR comment * Fix bench import path and temporarily disable design-contract workflow * CI: checkout qmd submodule in perf bench workflow * Fix Windows path handling in ingest session discovery * Feature/bench scorecard ci windows fixes (#34) * chore: add pending project files * refactor(ingest): centralize ingestion via parser/resolver/store layers * docs: document layered ingest architecture * test(perf): add qmd benchmark harness and non-blocking CI regression check * perf(bench): add ingest hotpath benchmark and record qmd optimization * perf(ingest): batch session writes and add stable benchmark tooling * Add benchmark scorecard to CI summary and sticky PR comment * Fix bench import path and temporarily disable design-contract workflow * CI: checkout qmd submodule in perf bench workflow * Fix Windows path handling in ingest session discovery * CI: run full test matrix only on merge branches * CI: auto-create draft prerelease on successful dev CI * CI: auto-template and title for dev to main PRs * CI: create dev draft release after successful dev test matrix * chore: add e2e dev release flow test marker (#36) * docs: update CHANGELOG.md for v0.4.0 [skip ci] * docs: add CI/release workflow architecture and north-star plan * ci: add commit lint, semver metadata, and deterministic release notes * docs: finalize workflow policy docs without backlog sections * ci: scope commit lint to pull request commit ranges only * fix(ci): setup bun before dev draft release metadata step * fix(ci): allow legacy non-conventional history for dev draft metadata * fix(release): align dev-main PR version with latest stable tag * ci: improve workflow and check naming for PR readability * ci: skip PR test job for dev to main release PRs * fix(ci): use import.meta.dir for cross-platform path resolution new URL(import.meta.url).pathname produces /D:/a/... on Windows, causing ENOENT errors. import.meta.dir is Bun's cross-platform alternative. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * ci: add auto-release job for main branch merges After tests pass on main, automatically compute the next semver version and create a GitHub release. Handles squash merges (which lose individual commit types) by defaulting to patch when commits exist but bump is "none". Skips if HEAD is already tagged. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Baseline User <baseline@example.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * ci: trigger auto-release workflow on main Previous squash merge body contained [skip ci] from an old commit message, which prevented GitHub Actions from running. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): add model-aware cost estimation and sidecar cleanup Add MODEL_PRICING map for Claude model families, estimateCost() for per-turn USD estimation, wire estimated_cost_usd into upsertSessionCosts, and add deleteSidecarRows() for force re-ingest cleanup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Baseline User <baseline@example.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4dfce37 commit e148c75

1 file changed

Lines changed: 40 additions & 3 deletions

File tree

src/db.ts

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,31 @@ export function insertError(
719719
).run(messageId, sessionId, errorType, message, createdAt);
720720
}
721721

722+
// Per-million-token pricing by model family
723+
const MODEL_PRICING: Record<string, { input: number; output: number; cacheRead: number }> = {
724+
"claude-opus-4": { input: 15.0, output: 75.0, cacheRead: 1.5 },
725+
"claude-sonnet-4": { input: 3.0, output: 15.0, cacheRead: 0.3 },
726+
"claude-haiku-4": { input: 0.8, output: 4.0, cacheRead: 0.08 },
727+
};
728+
const DEFAULT_PRICING = { input: 3.0, output: 15.0, cacheRead: 0.3 };
729+
730+
export function estimateCost(
731+
model: string,
732+
inputTokens: number,
733+
outputTokens: number,
734+
cacheTokens: number
735+
): number {
736+
// Match model family: "claude-sonnet-4-20250514" → "claude-sonnet-4"
737+
const family = Object.keys(MODEL_PRICING).find((k) => model.startsWith(k));
738+
const pricing = family ? MODEL_PRICING[family] : DEFAULT_PRICING;
739+
return (
740+
(inputTokens * pricing.input +
741+
outputTokens * pricing.output +
742+
cacheTokens * pricing.cacheRead) /
743+
1_000_000
744+
);
745+
}
746+
722747
export function upsertSessionCosts(
723748
db: Database,
724749
sessionId: string,
@@ -728,16 +753,28 @@ export function upsertSessionCosts(
728753
cacheTokens: number,
729754
durationMs: number
730755
): void {
756+
const modelName = model || "unknown";
757+
const cost = estimateCost(modelName, inputTokens, outputTokens, cacheTokens);
731758
db.prepare(
732-
`INSERT INTO smriti_session_costs(session_id, model, total_input_tokens, total_output_tokens, total_cache_tokens, turn_count, total_duration_ms)
733-
VALUES(?, ?, ?, ?, ?, 1, ?)
759+
`INSERT INTO smriti_session_costs(session_id, model, total_input_tokens, total_output_tokens, total_cache_tokens, estimated_cost_usd, turn_count, total_duration_ms)
760+
VALUES(?, ?, ?, ?, ?, ?, 1, ?)
734761
ON CONFLICT(session_id, model) DO UPDATE SET
735762
total_input_tokens = total_input_tokens + excluded.total_input_tokens,
736763
total_output_tokens = total_output_tokens + excluded.total_output_tokens,
737764
total_cache_tokens = total_cache_tokens + excluded.total_cache_tokens,
765+
estimated_cost_usd = estimated_cost_usd + excluded.estimated_cost_usd,
738766
turn_count = turn_count + 1,
739767
total_duration_ms = total_duration_ms + excluded.total_duration_ms`
740-
).run(sessionId, model || "unknown", inputTokens, outputTokens, cacheTokens, durationMs);
768+
).run(sessionId, modelName, inputTokens, outputTokens, cacheTokens, cost, durationMs);
769+
}
770+
771+
export function deleteSidecarRows(db: Database, sessionId: string): void {
772+
db.prepare(`DELETE FROM smriti_tool_usage WHERE session_id = ?`).run(sessionId);
773+
db.prepare(`DELETE FROM smriti_file_operations WHERE session_id = ?`).run(sessionId);
774+
db.prepare(`DELETE FROM smriti_commands WHERE session_id = ?`).run(sessionId);
775+
db.prepare(`DELETE FROM smriti_errors WHERE session_id = ?`).run(sessionId);
776+
db.prepare(`DELETE FROM smriti_git_operations WHERE session_id = ?`).run(sessionId);
777+
db.prepare(`DELETE FROM smriti_session_costs WHERE session_id = ?`).run(sessionId);
741778
}
742779

743780
export function insertGitOperation(

0 commit comments

Comments
 (0)