bonecode 1.2.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/stats.ts CHANGED
@@ -1,10 +1,49 @@
1
1
  /**
2
2
  * Token usage and cost statistics
3
3
  * Ported from opencode's cli/cmd/stats.ts — adapted for BoneCode's Postgres schema.
4
+ *
5
+ * Works on both Postgres (full schema with token columns on sessions) and
6
+ * SQLite fallback (sessions has only id/title/etc; token data lives on messages).
4
7
  */
5
8
 
6
9
  import { Pool } from "pg"
7
10
 
11
+ interface CostColumns {
12
+ cost: boolean
13
+ tokensInput: boolean
14
+ tokensOutput: boolean
15
+ tokensReasoning: boolean
16
+ tokensCacheRead: boolean
17
+ tokensCacheWrite: boolean
18
+ }
19
+
20
+ let _columnsCache: CostColumns | null = null
21
+
22
+ async function detectSessionCostColumns(pool: Pool): Promise<CostColumns> {
23
+ if (_columnsCache) return _columnsCache
24
+
25
+ const probe = async (col: string): Promise<boolean> => {
26
+ try {
27
+ // SELECT col FROM sessions LIMIT 0 — fails if column doesn't exist, succeeds otherwise.
28
+ // Works identically on Postgres and SQLite.
29
+ await pool.query(`SELECT ${col} FROM sessions LIMIT 0`)
30
+ return true
31
+ } catch {
32
+ return false
33
+ }
34
+ }
35
+
36
+ _columnsCache = {
37
+ cost: await probe("cost_usd"),
38
+ tokensInput: await probe("tokens_input"),
39
+ tokensOutput: await probe("tokens_output"),
40
+ tokensReasoning: await probe("tokens_reasoning"),
41
+ tokensCacheRead: await probe("tokens_cache_read"),
42
+ tokensCacheWrite: await probe("tokens_cache_write"),
43
+ }
44
+ return _columnsCache
45
+ }
46
+
8
47
  export interface SessionStats {
9
48
  totalSessions: number
10
49
  totalMessages: number
@@ -50,10 +89,23 @@ export async function aggregateStats(
50
89
  return opts.days
51
90
  })()
52
91
 
92
+ // Detect available columns by introspection — works on both Postgres and SQLite
93
+ const hasCol = await detectSessionCostColumns(pool)
94
+ const sessionSelect = [
95
+ "id",
96
+ "project_id",
97
+ "created_at",
98
+ "updated_at",
99
+ hasCol.cost ? "cost_usd" : "0 AS cost_usd",
100
+ hasCol.tokensInput ? "tokens_input" : "0 AS tokens_input",
101
+ hasCol.tokensOutput ? "tokens_output" : "0 AS tokens_output",
102
+ hasCol.tokensReasoning ? "tokens_reasoning" : "0 AS tokens_reasoning",
103
+ hasCol.tokensCacheRead ? "tokens_cache_read" : "0 AS tokens_cache_read",
104
+ hasCol.tokensCacheWrite ? "tokens_cache_write" : "0 AS tokens_cache_write",
105
+ ].join(", ")
106
+
53
107
  // Load sessions
54
- let sessionQuery = `SELECT id, project_id, cost_usd, tokens_input, tokens_output, tokens_reasoning,
55
- tokens_cache_read, tokens_cache_write, created_at, updated_at
56
- FROM sessions WHERE state != 'deleted'`
108
+ let sessionQuery = `SELECT ${sessionSelect} FROM sessions WHERE state != 'deleted'`
57
109
  const params: unknown[] = []
58
110
  let idx = 1
59
111