@hasna/economy 0.4.0 → 0.5.1

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.
Files changed (205) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +12 -9
  3. package/dist/chunks/agent-registry-store-tpnhx3wj.js +39 -0
  4. package/dist/chunks/billing-bkpxy4kv.js +13 -0
  5. package/dist/chunks/config-j9mv3ehk.js +15 -0
  6. package/dist/chunks/index-2hwzmfgv.js +3407 -0
  7. package/dist/chunks/index-49f12css.js +124 -0
  8. package/dist/chunks/index-d480p2wh.js +1322 -0
  9. package/dist/chunks/index-ez9thsr7.js +3440 -0
  10. package/dist/chunks/index-hesxq90g.js +62 -0
  11. package/dist/chunks/index-k540hfww.js +258 -0
  12. package/dist/chunks/index-p35cyhc4.js +4 -0
  13. package/dist/chunks/index-r1jv3kpy.js +451 -0
  14. package/dist/chunks/index-w3bvv0ns.js +26 -0
  15. package/dist/chunks/index-xsah93s2.js +2085 -0
  16. package/dist/chunks/index-zpvea08j.js +3372 -0
  17. package/dist/chunks/menubar-6xtkt17h.js +296 -0
  18. package/dist/chunks/open-projects-6z4pq6mx.js +39 -0
  19. package/dist/chunks/pricing-f80p81gr.js +29 -0
  20. package/dist/chunks/pricing-vf2pycmh.js +30 -0
  21. package/dist/chunks/pricing-y21mcjsa.js +30 -0
  22. package/dist/chunks/serve-2knem98x.js +4655 -0
  23. package/dist/chunks/sqlite-store-501f32ts.js +258 -0
  24. package/dist/chunks/sqlite-store-fq4natnf.js +256 -0
  25. package/dist/chunks/sqlite-store-st99garx.js +258 -0
  26. package/dist/chunks/third-party-sqlite-5e85fxa2.js +11 -0
  27. package/dist/chunks/tui-khmnkat2.js +22 -0
  28. package/dist/chunks/watch-4y064yys.js +196 -0
  29. package/dist/chunks/webhooks-k4fh2t9c.js +109 -0
  30. package/dist/cli/commands/brief.d.ts.map +1 -1
  31. package/dist/cli/commands/extras.d.ts.map +1 -1
  32. package/dist/cli/commands/journal.d.ts +3 -0
  33. package/dist/cli/commands/journal.d.ts.map +1 -0
  34. package/dist/cli/commands/menubar.d.ts +97 -4
  35. package/dist/cli/commands/menubar.d.ts.map +1 -1
  36. package/dist/cli/commands/tui.d.ts +1 -1
  37. package/dist/cli/commands/tui.d.ts.map +1 -1
  38. package/dist/cli/commands/watch.d.ts.map +1 -1
  39. package/dist/cli/index.js +1358 -10114
  40. package/dist/db/cloud.d.ts +7 -9
  41. package/dist/db/cloud.d.ts.map +1 -1
  42. package/dist/db/database.d.ts +14 -12
  43. package/dist/db/database.d.ts.map +1 -1
  44. package/dist/db/ingest-concurrency-worker.d.ts +2 -0
  45. package/dist/db/ingest-concurrency-worker.d.ts.map +1 -0
  46. package/dist/db/ingest-errors.d.ts +9 -0
  47. package/dist/db/ingest-errors.d.ts.map +1 -0
  48. package/dist/db/ingest-schema.d.ts +3 -0
  49. package/dist/db/ingest-schema.d.ts.map +1 -0
  50. package/dist/db/ingest-specs.d.ts +8 -0
  51. package/dist/db/ingest-specs.d.ts.map +1 -0
  52. package/dist/db/ingest-test-contract.d.ts +3 -0
  53. package/dist/db/ingest-test-contract.d.ts.map +1 -0
  54. package/dist/db/ingest-validation.d.ts +12 -0
  55. package/dist/db/ingest-validation.d.ts.map +1 -0
  56. package/dist/db/ingest.d.ts +27 -0
  57. package/dist/db/ingest.d.ts.map +1 -0
  58. package/dist/db/legacy-schema.d.ts +7 -0
  59. package/dist/db/legacy-schema.d.ts.map +1 -0
  60. package/dist/db/observation-contract.d.ts +3 -0
  61. package/dist/db/observation-contract.d.ts.map +1 -0
  62. package/dist/db/observation-provenance.d.ts +23 -0
  63. package/dist/db/observation-provenance.d.ts.map +1 -0
  64. package/dist/db/observation-schema.d.ts +24 -0
  65. package/dist/db/observation-schema.d.ts.map +1 -0
  66. package/dist/db/pg-connection.d.ts +10 -0
  67. package/dist/db/pg-connection.d.ts.map +1 -0
  68. package/dist/db/pg-legacy-schema.d.ts +12 -0
  69. package/dist/db/pg-legacy-schema.d.ts.map +1 -0
  70. package/dist/db/pg-migrate.d.ts +4 -9
  71. package/dist/db/pg-migrate.d.ts.map +1 -1
  72. package/dist/db/pg-migrations.d.ts.map +1 -1
  73. package/dist/db/pg-protocol.d.ts +28 -0
  74. package/dist/db/pg-protocol.d.ts.map +1 -0
  75. package/dist/db/sqlite-store.d.ts +4 -0
  76. package/dist/db/sqlite-store.d.ts.map +1 -0
  77. package/dist/db/sync-pg.d.ts +17 -21
  78. package/dist/db/sync-pg.d.ts.map +1 -1
  79. package/dist/db/third-party-sqlite.d.ts +10 -0
  80. package/dist/db/third-party-sqlite.d.ts.map +1 -0
  81. package/dist/index.d.ts +3 -0
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +1171 -2465
  84. package/dist/ingest/claude-quota.d.ts.map +1 -1
  85. package/dist/ingest/claude.d.ts.map +1 -1
  86. package/dist/ingest/codex-quota.d.ts.map +1 -1
  87. package/dist/ingest/codex.d.ts +25 -0
  88. package/dist/ingest/codex.d.ts.map +1 -1
  89. package/dist/ingest/collection-outcome.d.ts +17 -0
  90. package/dist/ingest/collection-outcome.d.ts.map +1 -0
  91. package/dist/ingest/cursor.d.ts.map +1 -1
  92. package/dist/ingest/gemini.d.ts.map +1 -1
  93. package/dist/ingest/hermes.d.ts +1 -1
  94. package/dist/ingest/hermes.d.ts.map +1 -1
  95. package/dist/ingest/loops.d.ts.map +1 -1
  96. package/dist/ingest/opencode.d.ts +1 -1
  97. package/dist/ingest/opencode.d.ts.map +1 -1
  98. package/dist/ingest/pi.d.ts +1 -1
  99. package/dist/ingest/pi.d.ts.map +1 -1
  100. package/dist/ingest/quota-observation.d.ts +16 -0
  101. package/dist/ingest/quota-observation.d.ts.map +1 -0
  102. package/dist/ingest/source-time.d.ts +4 -0
  103. package/dist/ingest/source-time.d.ts.map +1 -0
  104. package/dist/lib/accounts-store.d.ts +39 -13
  105. package/dist/lib/accounts-store.d.ts.map +1 -1
  106. package/dist/lib/analytics.d.ts.map +1 -1
  107. package/dist/lib/api-display-url.d.ts +1 -1
  108. package/dist/lib/autosync-gate.d.ts +12 -5
  109. package/dist/lib/autosync-gate.d.ts.map +1 -1
  110. package/dist/lib/billing-diff.d.ts +18 -40
  111. package/dist/lib/billing-diff.d.ts.map +1 -1
  112. package/dist/lib/brief.d.ts +2 -0
  113. package/dist/lib/brief.d.ts.map +1 -1
  114. package/dist/lib/budget-decimal.d.ts +14 -0
  115. package/dist/lib/budget-decimal.d.ts.map +1 -0
  116. package/dist/lib/cloud-ingest.d.ts +15 -0
  117. package/dist/lib/cloud-ingest.d.ts.map +1 -1
  118. package/dist/lib/cloud-storage.d.ts.map +1 -1
  119. package/dist/lib/ingest-batches.d.ts +21 -0
  120. package/dist/lib/ingest-batches.d.ts.map +1 -0
  121. package/dist/lib/ingest-journal-client.d.ts +37 -0
  122. package/dist/lib/ingest-journal-client.d.ts.map +1 -0
  123. package/dist/lib/ingest-journal-lock.d.ts +5 -0
  124. package/dist/lib/ingest-journal-lock.d.ts.map +1 -0
  125. package/dist/lib/ingest-journal-maintenance.d.ts +92 -0
  126. package/dist/lib/ingest-journal-maintenance.d.ts.map +1 -0
  127. package/dist/lib/ingest-journal-operation.d.ts +55 -0
  128. package/dist/lib/ingest-journal-operation.d.ts.map +1 -0
  129. package/dist/lib/ingest-journal.d.ts +73 -0
  130. package/dist/lib/ingest-journal.d.ts.map +1 -0
  131. package/dist/lib/money-display.d.ts +13 -0
  132. package/dist/lib/money-display.d.ts.map +1 -0
  133. package/dist/lib/periods.d.ts +12 -0
  134. package/dist/lib/periods.d.ts.map +1 -1
  135. package/dist/lib/pricing.d.ts +14 -0
  136. package/dist/lib/pricing.d.ts.map +1 -1
  137. package/dist/lib/savings.d.ts +64 -7
  138. package/dist/lib/savings.d.ts.map +1 -1
  139. package/dist/lib/store/index.d.ts +25 -21
  140. package/dist/lib/store/index.d.ts.map +1 -1
  141. package/dist/lib/summary-contract.d.ts +32 -0
  142. package/dist/lib/summary-contract.d.ts.map +1 -0
  143. package/dist/lib/summary-test-contract.d.ts +3 -0
  144. package/dist/lib/summary-test-contract.d.ts.map +1 -0
  145. package/dist/lib/sync-all.d.ts +4 -0
  146. package/dist/lib/sync-all.d.ts.map +1 -1
  147. package/dist/lib/sync-maintenance.d.ts.map +1 -1
  148. package/dist/lib/sync-report.d.ts +4 -0
  149. package/dist/lib/sync-report.d.ts.map +1 -0
  150. package/dist/lib/test-keychain-fixture.d.ts +9 -0
  151. package/dist/lib/test-keychain-fixture.d.ts.map +1 -0
  152. package/dist/mcp/agent-registry-store.d.ts +30 -0
  153. package/dist/mcp/agent-registry-store.d.ts.map +1 -0
  154. package/dist/mcp/agent-registry.d.ts +9 -16
  155. package/dist/mcp/agent-registry.d.ts.map +1 -1
  156. package/dist/mcp/index.js +2291 -3458
  157. package/dist/mcp/ingest-journal-tool.d.ts +2 -0
  158. package/dist/mcp/ingest-journal-tool.d.ts.map +1 -0
  159. package/dist/mcp/server.d.ts.map +1 -1
  160. package/dist/openapi.d.ts.map +1 -1
  161. package/dist/otel/index.js +1960 -289
  162. package/dist/server/human-auth.d.ts +20 -0
  163. package/dist/server/human-auth.d.ts.map +1 -0
  164. package/dist/server/index.js +10639 -6720
  165. package/dist/server/legacy-inventory.d.ts +52 -0
  166. package/dist/server/legacy-inventory.d.ts.map +1 -0
  167. package/dist/server/legacy-operations.d.ts +64 -0
  168. package/dist/server/legacy-operations.d.ts.map +1 -0
  169. package/dist/server/legacy-plan.d.ts +55 -0
  170. package/dist/server/legacy-plan.d.ts.map +1 -0
  171. package/dist/server/legacy-preservation.d.ts +58 -0
  172. package/dist/server/legacy-preservation.d.ts.map +1 -0
  173. package/dist/server/pg-sync-worker.js +195 -21
  174. package/dist/server/serve.d.ts +20 -16
  175. package/dist/server/serve.d.ts.map +1 -1
  176. package/dist/server/service-admission.d.ts +10 -0
  177. package/dist/server/service-admission.d.ts.map +1 -0
  178. package/dist/server/service-dispatcher.d.ts +45 -0
  179. package/dist/server/service-dispatcher.d.ts.map +1 -0
  180. package/dist/server/service-operations.d.ts +5 -0
  181. package/dist/server/service-operations.d.ts.map +1 -0
  182. package/dist/server/service-protocol.d.ts +50 -0
  183. package/dist/server/service-protocol.d.ts.map +1 -0
  184. package/dist/server/service-worker.d.ts +2 -0
  185. package/dist/server/service-worker.d.ts.map +1 -0
  186. package/dist/server/service-worker.js +8038 -0
  187. package/dist/types/index.d.ts +66 -0
  188. package/dist/types/index.d.ts.map +1 -1
  189. package/docs/README.md +1 -1
  190. package/docs/cli.md +1 -1
  191. package/docs/configuration.md +5 -5
  192. package/docs/cost-reporting.md +49 -0
  193. package/docs/historical-data-reconciliation.md +203 -0
  194. package/docs/ingest-journal.md +108 -0
  195. package/docs/ingestion.md +60 -4
  196. package/docs/mcp.md +2 -2
  197. package/docs/native-budget-save.md +84 -0
  198. package/docs/native-human-auth.md +102 -0
  199. package/docs/postgresql-legacy-schema-repair.md +30 -0
  200. package/docs/rest-api.md +2 -2
  201. package/package.json +25 -13
  202. package/dist/lib/test-hermetic-accounts.d.ts +0 -7
  203. package/dist/lib/test-hermetic-accounts.d.ts.map +0 -1
  204. package/dist/lib/test-hermetic-fleet-env.d.ts +0 -4
  205. package/dist/lib/test-hermetic-fleet-env.d.ts.map +0 -1
@@ -0,0 +1,3407 @@
1
+ // @bun
2
+ // src/db/observation-schema.ts
3
+ var OBSERVATION_COLUMNS = {
4
+ usage_snapshots: {
5
+ provider: "TEXT NOT NULL DEFAULT ''",
6
+ account_key: "TEXT NOT NULL DEFAULT ''",
7
+ observed_at: "TEXT",
8
+ source_timestamp: "TEXT",
9
+ window_start: "TEXT",
10
+ window_end: "TEXT",
11
+ reset_at: "TEXT",
12
+ window_seconds: "INTEGER",
13
+ value_kind: "TEXT NOT NULL DEFAULT 'unknown'"
14
+ },
15
+ subscriptions: {
16
+ account_key: "TEXT NOT NULL DEFAULT ''",
17
+ observed_station: "TEXT",
18
+ observed_at: "TEXT",
19
+ fee_basis: "TEXT NOT NULL DEFAULT 'unknown'",
20
+ included_basis: "TEXT NOT NULL DEFAULT 'unknown'"
21
+ }
22
+ };
23
+ var SCAN_RECEIPTS_SQL = `CREATE TABLE IF NOT EXISTS scan_receipts (
24
+ id TEXT PRIMARY KEY, machine_id TEXT NOT NULL, source TEXT NOT NULL,
25
+ attempted_at TEXT NOT NULL, completed_at TEXT NOT NULL, status TEXT NOT NULL,
26
+ latest_event_at TEXT, parser_version TEXT NOT NULL, error_code TEXT,
27
+ updated_at TEXT NOT NULL
28
+ );
29
+ CREATE INDEX IF NOT EXISTS idx_scan_receipts_machine_source
30
+ ON scan_receipts(machine_id, source, completed_at);`;
31
+ var OBSERVATION_PG_MIGRATIONS = [
32
+ ...Object.entries(OBSERVATION_COLUMNS).flatMap(([table, columns]) => Object.entries(columns).map(([column, type]) => `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${column} ${type}`)),
33
+ SCAN_RECEIPTS_SQL
34
+ ];
35
+
36
+ // src/db/observation-provenance.ts
37
+ import { createHash } from "crypto";
38
+
39
+ // src/db/ingest-errors.ts
40
+ class IngestError extends Error {
41
+ status;
42
+ code;
43
+ field;
44
+ expected;
45
+ constructor(status, code, field, expected) {
46
+ super(`${field}: ${expected}`);
47
+ this.status = status;
48
+ this.code = code;
49
+ this.field = field;
50
+ this.expected = expected;
51
+ this.name = "IngestError";
52
+ }
53
+ }
54
+
55
+ // src/db/observation-provenance.ts
56
+ var SCAN_ERROR_CODES = [
57
+ "SOURCE_MISSING",
58
+ "SOURCE_UNREADABLE",
59
+ "SOURCE_INVALID",
60
+ "LEGACY_AGGREGATE_REQUIRES_MIGRATION",
61
+ "USAGE_EVENTS_UNAVAILABLE",
62
+ "USAGE_EVENTS_INCOMPLETE",
63
+ "PROVIDER_CREDENTIAL_UNAVAILABLE",
64
+ "PROVIDER_CREDENTIAL_UNREADABLE",
65
+ "PROVIDER_AUTH_REFUSED",
66
+ "PROVIDER_REFUSED",
67
+ "PROVIDER_UNAVAILABLE",
68
+ "ACCOUNT_IDENTITY_UNAVAILABLE",
69
+ "SUBSCRIPTION_QUOTA_NOT_APPLICABLE",
70
+ "TEAM_USAGE_ATTRIBUTION_UNAVAILABLE",
71
+ "CROSS_APP_LOCAL_READ_REFUSED",
72
+ "COLLECTION_FAILED"
73
+ ];
74
+ var digest = (parts) => createHash("sha256").update(JSON.stringify(parts)).digest("hex");
75
+ function observationId(input) {
76
+ return digest([input.provider, input.account_key, input.machine_id, input.metric, new Date(input.observed_at).toISOString()]);
77
+ }
78
+ function subscriptionId(input) {
79
+ return digest([input.provider, input.account_key]);
80
+ }
81
+ function scanReceiptId(input) {
82
+ return digest([input.machine_id, input.source, new Date(input.attempted_at).toISOString()]);
83
+ }
84
+ var invalid = (path, expected) => {
85
+ throw new IngestError(400, "INVALID_INGEST", path, expected);
86
+ };
87
+ function required(row, keys, path) {
88
+ for (const key of keys)
89
+ if (typeof row[key] !== "string" || !row[key].trim() || row[key].length > 256) {
90
+ invalid(`${path}.${key}`, "expected nonempty text of at most 256 characters");
91
+ }
92
+ }
93
+ function validateObservationProvenance(table, row, path) {
94
+ if (table === "usage_snapshots") {
95
+ if (!["gauge", "cumulative", "delta", "unknown"].includes(String(row["value_kind"] === undefined ? "unknown" : row["value_kind"])))
96
+ invalid(`${path}.value_kind`, "expected gauge, cumulative, delta or unknown");
97
+ const newProvenance = ["provider", "account_key", "observed_at", "source_timestamp", "window_start", "window_end", "reset_at", "window_seconds"].some((k) => row[k] != null && row[k] !== "") || row["value_kind"] != null && row["value_kind"] !== "unknown";
98
+ if (newProvenance) {
99
+ required(row, ["provider", "account_key", "machine_id", "observed_at", "metric"], path);
100
+ if (row["id"] !== observationId(row))
101
+ invalid(`${path}.id`, "expected SHA-256 identity of provider, account_key, machine_id, metric and observed_at");
102
+ if (row["date"] !== String(row["observed_at"]).slice(0, 10))
103
+ invalid(`${path}.date`, "expected the UTC observation date");
104
+ if (row["window_seconds"] != null && (!Number.isSafeInteger(row["window_seconds"]) || Number(row["window_seconds"]) <= 0))
105
+ invalid(`${path}.window_seconds`, "expected positive integer seconds or null");
106
+ if (row["window_start"] && row["window_end"] && Date.parse(String(row["window_start"])) >= Date.parse(String(row["window_end"])))
107
+ invalid(`${path}.window_end`, "expected a time after window_start");
108
+ }
109
+ }
110
+ if (table === "subscriptions") {
111
+ for (const key of ["fee_basis", "included_basis"])
112
+ if (!["unknown", "observed", "configured"].includes(String(row[key] === undefined ? "unknown" : row[key])))
113
+ invalid(`${path}.${key}`, "expected unknown, observed or configured");
114
+ const identified = row["account_key"] != null && row["account_key"] !== "" || row["observed_at"] != null;
115
+ if (identified || ["fee_basis", "included_basis"].some((k) => row[k] != null && row[k] !== "unknown")) {
116
+ required(row, ["provider", "account_key", "observed_at"], path);
117
+ if (row["id"] !== subscriptionId(row))
118
+ invalid(`${path}.id`, "expected SHA-256 identity of provider and account_key");
119
+ if (row["updated_at"] !== row["observed_at"])
120
+ invalid(`${path}.updated_at`, "expected the observation timestamp");
121
+ }
122
+ }
123
+ if (table === "scan_receipts") {
124
+ required(row, ["machine_id", "source", "attempted_at", "completed_at", "parser_version"], path);
125
+ if (!["success", "unchanged", "missing", "unsupported", "failed"].includes(String(row["status"])))
126
+ invalid(`${path}.status`, "expected success, unchanged, missing, unsupported or failed");
127
+ if (row["id"] !== scanReceiptId(row))
128
+ invalid(`${path}.id`, "expected SHA-256 identity of machine_id, source and attempted_at");
129
+ if (Date.parse(String(row["completed_at"])) < Date.parse(String(row["attempted_at"])))
130
+ invalid(`${path}.completed_at`, "expected a time at or after attempted_at");
131
+ if (row["updated_at"] !== row["completed_at"])
132
+ invalid(`${path}.updated_at`, "expected completed_at");
133
+ if (row["error_code"] != null && (typeof row["error_code"] !== "string" || !SCAN_ERROR_CODES.includes(row["error_code"])))
134
+ invalid(`${path}.error_code`, "expected a supported authored scan error code or null");
135
+ if (["success", "unchanged"].includes(String(row["status"])) && row["error_code"] != null)
136
+ invalid(`${path}.error_code`, "expected null for a successful scan");
137
+ }
138
+ }
139
+
140
+ // src/db/ingest-validation.ts
141
+ import { createHash as createHash2 } from "crypto";
142
+
143
+ // src/db/ingest-specs.ts
144
+ var numOr = (v, d = 0) => {
145
+ if (v == null)
146
+ return d;
147
+ const num = Number(v);
148
+ return Number.isFinite(num) ? num : d;
149
+ };
150
+ var strOr = (v, d = "") => v == null ? d : String(v);
151
+ function ingestSpecs() {
152
+ return [
153
+ {
154
+ table: "requests",
155
+ pk: ["id"],
156
+ columns: [
157
+ "id",
158
+ "agent",
159
+ "session_id",
160
+ "model",
161
+ "input_tokens",
162
+ "output_tokens",
163
+ "cache_read_tokens",
164
+ "cache_create_tokens",
165
+ "cache_create_5m_tokens",
166
+ "cache_create_1h_tokens",
167
+ "cost_usd",
168
+ "cost_basis",
169
+ "duration_ms",
170
+ "timestamp",
171
+ "source_request_id",
172
+ "machine_id",
173
+ "cost_center_id",
174
+ "attribution_tag",
175
+ "account_key",
176
+ "account_tool",
177
+ "account_name",
178
+ "account_email",
179
+ "account_source",
180
+ "updated_at",
181
+ "synced_at"
182
+ ],
183
+ map: (r, now) => [
184
+ strOr(r["id"]),
185
+ strOr(r["agent"]),
186
+ strOr(r["session_id"]),
187
+ strOr(r["model"]),
188
+ numOr(r["input_tokens"]),
189
+ numOr(r["output_tokens"]),
190
+ numOr(r["cache_read_tokens"]),
191
+ numOr(r["cache_create_tokens"]),
192
+ numOr(r["cache_create_5m_tokens"] ?? r["cache_create_tokens"]),
193
+ numOr(r["cache_create_1h_tokens"]),
194
+ numOr(r["cost_usd"]),
195
+ strOr(r["cost_basis"], "estimated"),
196
+ numOr(r["duration_ms"]),
197
+ strOr(r["timestamp"]),
198
+ r["source_request_id"] == null ? null : strOr(r["source_request_id"]),
199
+ strOr(r["machine_id"]),
200
+ r["cost_center_id"] == null ? null : strOr(r["cost_center_id"]),
201
+ strOr(r["attribution_tag"]),
202
+ strOr(r["account_key"]),
203
+ strOr(r["account_tool"]),
204
+ strOr(r["account_name"]),
205
+ strOr(r["account_email"]),
206
+ strOr(r["account_source"]),
207
+ strOr(r["updated_at"], now),
208
+ strOr(r["synced_at"])
209
+ ]
210
+ },
211
+ {
212
+ table: "sessions",
213
+ pk: ["id"],
214
+ columns: [
215
+ "id",
216
+ "agent",
217
+ "project_path",
218
+ "project_name",
219
+ "started_at",
220
+ "ended_at",
221
+ "total_cost_usd",
222
+ "total_tokens",
223
+ "request_count",
224
+ "machine_id",
225
+ "cost_center_id",
226
+ "attribution_tag",
227
+ "account_key",
228
+ "account_tool",
229
+ "account_name",
230
+ "account_email",
231
+ "account_source",
232
+ "updated_at",
233
+ "synced_at"
234
+ ],
235
+ map: (r, now) => [
236
+ strOr(r["id"]),
237
+ strOr(r["agent"]),
238
+ strOr(r["project_path"]),
239
+ strOr(r["project_name"]),
240
+ strOr(r["started_at"]),
241
+ r["ended_at"] == null ? null : strOr(r["ended_at"]),
242
+ numOr(r["total_cost_usd"]),
243
+ numOr(r["total_tokens"]),
244
+ numOr(r["request_count"]),
245
+ strOr(r["machine_id"]),
246
+ r["cost_center_id"] == null ? null : strOr(r["cost_center_id"]),
247
+ strOr(r["attribution_tag"]),
248
+ strOr(r["account_key"]),
249
+ strOr(r["account_tool"]),
250
+ strOr(r["account_name"]),
251
+ strOr(r["account_email"]),
252
+ strOr(r["account_source"]),
253
+ strOr(r["updated_at"], now),
254
+ strOr(r["synced_at"])
255
+ ]
256
+ },
257
+ {
258
+ table: "projects",
259
+ pk: ["id"],
260
+ columns: ["id", "path", "name", "description", "tags", "created_at"],
261
+ map: (r) => [
262
+ strOr(r["id"]),
263
+ strOr(r["path"]),
264
+ strOr(r["name"]),
265
+ r["description"] == null ? null : strOr(r["description"]),
266
+ typeof r["tags"] === "string" ? r["tags"] : JSON.stringify(r["tags"] ?? []),
267
+ strOr(r["created_at"])
268
+ ]
269
+ },
270
+ {
271
+ table: "budgets",
272
+ pk: ["id"],
273
+ columns: ["id", "project_path", "agent", "period", "limit_usd", "alert_at_percent", "cost_center_id", "created_at", "updated_at", "metric"],
274
+ map: (r) => [
275
+ strOr(r["id"]),
276
+ r["project_path"] == null ? null : strOr(r["project_path"]),
277
+ r["agent"] == null ? null : strOr(r["agent"]),
278
+ strOr(r["period"]),
279
+ numOr(r["limit_usd"]),
280
+ numOr(r["alert_at_percent"], 80),
281
+ r["cost_center_id"] == null ? null : strOr(r["cost_center_id"]),
282
+ strOr(r["created_at"]),
283
+ strOr(r["updated_at"]),
284
+ strOr(r["metric"], "legacy_request_cost_usd")
285
+ ]
286
+ },
287
+ {
288
+ table: "goals",
289
+ pk: ["id"],
290
+ columns: ["id", "period", "project_path", "agent", "limit_usd", "created_at", "updated_at"],
291
+ map: (r) => [
292
+ strOr(r["id"]),
293
+ strOr(r["period"]),
294
+ r["project_path"] == null ? null : strOr(r["project_path"]),
295
+ r["agent"] == null ? null : strOr(r["agent"]),
296
+ numOr(r["limit_usd"]),
297
+ strOr(r["created_at"]),
298
+ strOr(r["updated_at"])
299
+ ]
300
+ },
301
+ {
302
+ table: "billing_daily",
303
+ pk: ["date", "provider", "description"],
304
+ columns: ["date", "provider", "description", "cost_usd", "updated_at"],
305
+ map: (r) => [strOr(r["date"]), strOr(r["provider"]), strOr(r["description"]), numOr(r["cost_usd"]), strOr(r["updated_at"])]
306
+ },
307
+ {
308
+ table: "model_pricing",
309
+ pk: ["model"],
310
+ columns: [
311
+ "model",
312
+ "input_per_1m",
313
+ "output_per_1m",
314
+ "cache_read_per_1m",
315
+ "cache_write_per_1m",
316
+ "cache_write_1h_per_1m",
317
+ "cache_storage_per_1m_hour",
318
+ "updated_at"
319
+ ],
320
+ map: (r) => [
321
+ strOr(r["model"]),
322
+ numOr(r["input_per_1m"]),
323
+ numOr(r["output_per_1m"]),
324
+ numOr(r["cache_read_per_1m"]),
325
+ numOr(r["cache_write_per_1m"]),
326
+ numOr(r["cache_write_1h_per_1m"]),
327
+ numOr(r["cache_storage_per_1m_hour"]),
328
+ strOr(r["updated_at"])
329
+ ]
330
+ },
331
+ {
332
+ table: "subscriptions",
333
+ pk: ["id"],
334
+ columns: [
335
+ "id",
336
+ "agent",
337
+ "provider",
338
+ "plan",
339
+ "monthly_fee_usd",
340
+ "included_usage_usd",
341
+ "billing_cycle_start",
342
+ "reset_policy",
343
+ "active",
344
+ "created_at",
345
+ "updated_at",
346
+ "account_key",
347
+ "observed_station",
348
+ "observed_at",
349
+ "fee_basis",
350
+ "included_basis"
351
+ ],
352
+ map: (r) => [
353
+ strOr(r["id"]),
354
+ r["agent"] == null ? null : strOr(r["agent"]),
355
+ strOr(r["provider"]),
356
+ strOr(r["plan"]),
357
+ numOr(r["monthly_fee_usd"]),
358
+ numOr(r["included_usage_usd"]),
359
+ r["billing_cycle_start"] == null ? null : strOr(r["billing_cycle_start"]),
360
+ strOr(r["reset_policy"], "unknown"),
361
+ numOr(r["active"], 1),
362
+ strOr(r["created_at"]),
363
+ strOr(r["updated_at"]),
364
+ strOr(r["account_key"]),
365
+ r["observed_station"] ?? null,
366
+ r["observed_at"] ?? null,
367
+ strOr(r["fee_basis"], "unknown"),
368
+ strOr(r["included_basis"], "unknown")
369
+ ]
370
+ },
371
+ {
372
+ table: "usage_snapshots",
373
+ pk: ["id"],
374
+ columns: [
375
+ "id",
376
+ "agent",
377
+ "date",
378
+ "metric",
379
+ "value",
380
+ "unit",
381
+ "machine_id",
382
+ "updated_at",
383
+ "provider",
384
+ "account_key",
385
+ "observed_at",
386
+ "source_timestamp",
387
+ "window_start",
388
+ "window_end",
389
+ "reset_at",
390
+ "window_seconds",
391
+ "value_kind"
392
+ ],
393
+ map: (r, now) => [
394
+ strOr(r["id"], `${strOr(r["agent"])}-${strOr(r["date"])}-${strOr(r["metric"])}-${strOr(r["machine_id"])}`),
395
+ strOr(r["agent"]),
396
+ strOr(r["date"]),
397
+ strOr(r["metric"]),
398
+ numOr(r["value"]),
399
+ strOr(r["unit"]),
400
+ strOr(r["machine_id"]),
401
+ strOr(r["updated_at"], now),
402
+ strOr(r["provider"]),
403
+ strOr(r["account_key"]),
404
+ r["observed_at"] ?? null,
405
+ r["source_timestamp"] ?? null,
406
+ r["window_start"] ?? null,
407
+ r["window_end"] ?? null,
408
+ r["reset_at"] ?? null,
409
+ r["window_seconds"] ?? null,
410
+ strOr(r["value_kind"], "unknown")
411
+ ]
412
+ },
413
+ {
414
+ table: "scan_receipts",
415
+ pk: ["id"],
416
+ columns: ["id", "machine_id", "source", "attempted_at", "completed_at", "status", "latest_event_at", "parser_version", "error_code", "updated_at"],
417
+ map: (r) => [strOr(r["id"]), strOr(r["machine_id"]), strOr(r["source"]), strOr(r["attempted_at"]), strOr(r["completed_at"]), strOr(r["status"]), r["latest_event_at"] ?? null, strOr(r["parser_version"]), r["error_code"] ?? null, strOr(r["updated_at"], strOr(r["completed_at"]))]
418
+ }
419
+ ];
420
+ }
421
+
422
+ // src/lib/agents.ts
423
+ var AGENTS = [
424
+ "claude",
425
+ "takumi",
426
+ "codex",
427
+ "gemini",
428
+ "opencode",
429
+ "cursor",
430
+ "pi",
431
+ "hermes"
432
+ ];
433
+ function isAgent(value) {
434
+ return AGENTS.includes(value);
435
+ }
436
+
437
+ // src/db/ingest-validation.ts
438
+ var MAX_INGEST_ROWS = 1e4;
439
+ var MAX_INGEST_BYTES = 4 * 1024 * 1024;
440
+ var integers = new Set([
441
+ "input_tokens",
442
+ "output_tokens",
443
+ "cache_read_tokens",
444
+ "cache_create_tokens",
445
+ "cache_create_5m_tokens",
446
+ "cache_create_1h_tokens",
447
+ "duration_ms",
448
+ "total_tokens",
449
+ "request_count",
450
+ "active",
451
+ "window_seconds"
452
+ ]);
453
+ var numeric = new Set([
454
+ ...integers,
455
+ "cost_usd",
456
+ "total_cost_usd",
457
+ "limit_usd",
458
+ "alert_at_percent",
459
+ "input_per_1m",
460
+ "output_per_1m",
461
+ "cache_read_per_1m",
462
+ "cache_write_per_1m",
463
+ "cache_write_1h_per_1m",
464
+ "cache_storage_per_1m_hour",
465
+ "monthly_fee_usd",
466
+ "included_usage_usd",
467
+ "value"
468
+ ]);
469
+ var required2 = {
470
+ requests: ["id", "agent", "session_id", "model", "timestamp"],
471
+ sessions: ["id", "agent", "started_at"],
472
+ projects: ["id", "path", "name", "created_at"],
473
+ budgets: ["id", "period"],
474
+ goals: ["id", "period"],
475
+ billing_daily: ["date", "provider"],
476
+ model_pricing: ["model"],
477
+ subscriptions: ["id", "provider", "plan"],
478
+ usage_snapshots: ["id", "agent", "date", "metric", "unit"],
479
+ scan_receipts: ["id", "machine_id", "source", "attempted_at", "completed_at", "status", "parser_version"]
480
+ };
481
+ function stableJson(value) {
482
+ if (Array.isArray(value))
483
+ return `[${value.map(stableJson).join(",")}]`;
484
+ if (value !== null && typeof value === "object") {
485
+ const record = value;
486
+ return `{${Object.keys(record).sort().map((k) => `${JSON.stringify(k)}:${stableJson(record[k])}`).join(",")}}`;
487
+ }
488
+ return JSON.stringify(value);
489
+ }
490
+ function canonicalIngestBodyHash(body) {
491
+ return createHash2("sha256").update(stableJson(body)).digest("hex");
492
+ }
493
+ function invalid2(field, expected) {
494
+ throw new IngestError(400, "INVALID_INGEST", field, expected);
495
+ }
496
+ function plain(value) {
497
+ return value !== null && typeof value === "object" && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
498
+ }
499
+ function date(value) {
500
+ return /^\d{4}-\d{2}-\d{2}$/.test(value) && Number.isFinite(Date.parse(value)) && new Date(value).toISOString().slice(0, 10) === value;
501
+ }
502
+ function instant(value) {
503
+ return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/.test(value) && date(value.slice(0, 10)) && Number.isFinite(Date.parse(value)) && new Date(value).toISOString().slice(0, 19) === value.slice(0, 19);
504
+ }
505
+ function validateIngest(body) {
506
+ if (!plain(body))
507
+ invalid2("body", "expected an object of supported table arrays");
508
+ const specs = ingestSpecs();
509
+ const allowed = new Set(["schema_version", "idempotency_key", "subscription_mode", "budget_mode", ...specs.map((s) => s.table)]);
510
+ for (const key of Object.keys(body))
511
+ if (!allowed.has(key))
512
+ invalid2("body", "unknown field; expected a supported table, schema_version, subscription_mode, budget_mode or idempotency_key");
513
+ if (body["subscription_mode"] !== undefined && (typeof body["subscription_mode"] !== "string" || !["quota_observation", "configuration"].includes(body["subscription_mode"]) || !Array.isArray(body["subscriptions"]))) {
514
+ invalid2("subscription_mode", "expected quota_observation or configuration with a subscriptions array");
515
+ }
516
+ if (body["schema_version"] !== undefined && body["schema_version"] !== 1)
517
+ invalid2("schema_version", "expected 1");
518
+ if (body["idempotency_key"] !== undefined && (typeof body["idempotency_key"] !== "string" || !/^[A-Za-z0-9._:-]{1,200}$/.test(body["idempotency_key"])))
519
+ invalid2("idempotency_key", "expected 1\u2013200 letters, digits, dot, underscore, colon or hyphen");
520
+ const createBudget = body["budget_mode"] !== undefined;
521
+ if (createBudget) {
522
+ if (body["budget_mode"] !== "create")
523
+ invalid2("budget_mode", "expected create");
524
+ if (body["schema_version"] !== 1)
525
+ invalid2("schema_version", "budget creation requires 1");
526
+ if (typeof body["idempotency_key"] !== "string")
527
+ invalid2("idempotency_key", "budget creation requires a stable caller-owned key");
528
+ if (Object.keys(body).some((k) => !["schema_version", "idempotency_key", "budget_mode", "budgets"].includes(k)))
529
+ invalid2("body", "budget creation accepts only schema_version, idempotency_key, budget_mode and budgets");
530
+ if (!Array.isArray(body["budgets"]) || body["budgets"].length !== 1)
531
+ invalid2("budgets", "budget creation requires exactly one rule");
532
+ }
533
+ let count = 0;
534
+ let bytes = 0;
535
+ const groups = [];
536
+ for (const spec of specs) {
537
+ const input = body[spec.table];
538
+ if (input === undefined)
539
+ continue;
540
+ if (!Array.isArray(input))
541
+ invalid2(spec.table, "expected an array");
542
+ count += input.length;
543
+ if (count > MAX_INGEST_ROWS)
544
+ throw new IngestError(413, "INGEST_TOO_LARGE", "body", `maximum ${MAX_INGEST_ROWS} rows; split into bounded commands`);
545
+ const rows = [];
546
+ for (let i = 0;i < input.length; i++) {
547
+ const raw = input[i];
548
+ const path = `${spec.table}[${i}]`;
549
+ if (!plain(raw))
550
+ invalid2(path, "expected a row object");
551
+ const row = {};
552
+ for (const key of Object.keys(raw)) {
553
+ if (!spec.columns.includes(key))
554
+ invalid2(/^[a-z_]{1,64}$/.test(key) ? `${path}.${key}` : path, "unknown column for this table");
555
+ const value = raw[key];
556
+ if (["observed_at", "source_timestamp", "window_start", "window_end", "reset_at", "attempted_at", "completed_at", "latest_event_at"].includes(key) && value === "")
557
+ invalid2(`${path}.${key}`, "expected an ISO UTC timestamp or null");
558
+ if (numeric.has(key) && !(key === "window_seconds" && value === null)) {
559
+ if (typeof value !== "number" || !Number.isFinite(value) || integers.has(key) && !Number.isSafeInteger(value) || value < 0 && !(spec.table === "billing_daily" && key === "cost_usd"))
560
+ invalid2(`${path}.${key}`, "expected a finite numeric value in the supported range");
561
+ if (key === "active" && value !== 0 && value !== 1)
562
+ invalid2(`${path}.active`, "expected 0 or 1");
563
+ } else if (key === "tags" && Array.isArray(value)) {
564
+ if (value.length > 100 || value.some((t) => typeof t !== "string" || t.length > 256))
565
+ invalid2(`${path}.tags`, "expected at most 100 string tags of at most 256 characters");
566
+ } else if (value !== null && (typeof value !== "string" || value.length > 16384 || value.includes("\x00"))) {
567
+ invalid2(`${path}.${key}`, "expected text of at most 16384 characters without NUL, or null");
568
+ }
569
+ if (typeof value === "string" && (key === "date" || key === "billing_cycle_start") && value !== "" && !date(value))
570
+ invalid2(`${path}.${key}`, "expected a valid YYYY-MM-DD date");
571
+ if (typeof value === "string" && (key.endsWith("_at") || ["timestamp", "source_timestamp", "window_start", "window_end"].includes(key)) && value !== "") {
572
+ if (!instant(value))
573
+ invalid2(`${path}.${key}`, "expected an ISO UTC timestamp ending in Z");
574
+ row[key] = new Date(value).toISOString();
575
+ } else
576
+ row[key] = value;
577
+ }
578
+ for (const key of required2[spec.table] ?? spec.pk) {
579
+ if (typeof row[key] !== "string" || row[key] === "")
580
+ invalid2(`${path}.${key}`, "expected nonempty text");
581
+ }
582
+ for (const key of spec.pk)
583
+ if (typeof (row[key] ?? "") !== "string" || String(row[key] ?? "").length > 1024)
584
+ invalid2(`${path}.${key}`, "expected a primary key of at most 1024 characters");
585
+ if (spec.table === "budgets" && !["daily", "weekly", "monthly"].includes(String(row["period"])))
586
+ invalid2(`${path}.period`, "expected daily, weekly or monthly");
587
+ if (spec.table === "budgets") {
588
+ if (row["metric"] !== undefined && !["legacy_request_cost_usd", "metered_api_usd"].includes(String(row["metric"])))
589
+ invalid2(`${path}.metric`, "expected legacy_request_cost_usd or metered_api_usd");
590
+ if (createBudget) {
591
+ if (String(row["id"]).trim() === "")
592
+ invalid2(`${path}.id`, "expected a nonempty caller-owned rule identity");
593
+ if (row["metric"] !== "metered_api_usd")
594
+ invalid2(`${path}.metric`, "budget creation requires metered_api_usd");
595
+ if (typeof row["limit_usd"] !== "number" || row["limit_usd"] <= 0)
596
+ invalid2(`${path}.limit_usd`, "expected a positive finite USD amount");
597
+ if (!Number.isInteger(row["alert_at_percent"]) || Number(row["alert_at_percent"]) < 1 || Number(row["alert_at_percent"]) > 100)
598
+ invalid2(`${path}.alert_at_percent`, "expected an integer from 1 to 100");
599
+ for (const field of ["created_at", "updated_at"])
600
+ if (typeof row[field] !== "string" || !instant(row[field]))
601
+ invalid2(`${path}.${field}`, "expected an explicit ISO UTC timestamp ending in Z; preserve it on retry");
602
+ for (const field of ["project_path", "cost_center_id"])
603
+ if (row[field] !== undefined && row[field] !== null && (typeof row[field] !== "string" || row[field].trim() === ""))
604
+ invalid2(`${path}.${field}`, "expected nonempty text or null for all scopes");
605
+ if (row["agent"] !== undefined && row["agent"] !== null && (typeof row["agent"] !== "string" || !isAgent(row["agent"])))
606
+ invalid2(`${path}.agent`, "expected a supported Economy agent or null for all agents");
607
+ }
608
+ }
609
+ if (spec.table === "goals" && !["day", "week", "month", "year"].includes(String(row["period"])))
610
+ invalid2(`${path}.period`, "expected day, week, month or year");
611
+ if (spec.table === "scan_receipts" && row["updated_at"] === undefined)
612
+ row["updated_at"] = row["completed_at"];
613
+ validateObservationProvenance(spec.table, row, path);
614
+ bytes += Buffer.byteLength(stableJson(row));
615
+ if (bytes > MAX_INGEST_BYTES)
616
+ throw new IngestError(413, "INGEST_TOO_LARGE", "body", `maximum ${MAX_INGEST_BYTES} JSON bytes; split into bounded commands`);
617
+ rows.push(row);
618
+ }
619
+ groups.push({ spec, rows });
620
+ }
621
+ if (Buffer.byteLength(stableJson(body)) > MAX_INGEST_BYTES)
622
+ throw new IngestError(413, "INGEST_TOO_LARGE", "body", `maximum ${MAX_INGEST_BYTES} JSON bytes; split into bounded commands`);
623
+ return groups;
624
+ }
625
+
626
+ // src/db/ingest-schema.ts
627
+ var INGEST_SCHEMA_SQL = `
628
+ CREATE TABLE IF NOT EXISTS ingest_session_locks (
629
+ session_id TEXT PRIMARY KEY
630
+ );
631
+ CREATE TABLE IF NOT EXISTS ingest_batches (
632
+ operation_key TEXT PRIMARY KEY,
633
+ actor_id TEXT NOT NULL,
634
+ payload_hash TEXT NOT NULL,
635
+ response_json TEXT NOT NULL,
636
+ created_at TEXT NOT NULL
637
+ );
638
+ CREATE TABLE IF NOT EXISTS ingest_preimages (
639
+ id TEXT PRIMARY KEY,
640
+ operation_key TEXT NOT NULL REFERENCES ingest_batches(operation_key),
641
+ table_name TEXT NOT NULL,
642
+ entity_key TEXT NOT NULL,
643
+ record_json TEXT NOT NULL,
644
+ record_sha256 TEXT NOT NULL,
645
+ recorded_at TEXT NOT NULL,
646
+ UNIQUE (operation_key, table_name, entity_key)
647
+ );
648
+ CREATE INDEX IF NOT EXISTS idx_ingest_preimages_entity
649
+ ON ingest_preimages(table_name, entity_key);
650
+ `;
651
+
652
+ // src/db/legacy-schema.ts
653
+ var LEGACY_LEDGER_SQL = `
654
+ CREATE TABLE IF NOT EXISTS legacy_reconciliations (
655
+ operation_key TEXT PRIMARY KEY NOT NULL,
656
+ payload_sha256 TEXT NOT NULL CHECK(length(payload_sha256)=64),
657
+ plan_sha256 TEXT NOT NULL CHECK(length(plan_sha256)=64),
658
+ capsule_sha256 TEXT NOT NULL CHECK(length(capsule_sha256)=64),
659
+ capsule_json TEXT NOT NULL,
660
+ actor_ref TEXT NOT NULL,
661
+ authority_ref TEXT NOT NULL,
662
+ created_at TEXT NOT NULL,
663
+ response_json TEXT NOT NULL
664
+ );
665
+ CREATE TABLE IF NOT EXISTS legacy_reconciliation_targets (
666
+ operation_key TEXT NOT NULL REFERENCES legacy_reconciliations(operation_key),
667
+ request_id TEXT NOT NULL,
668
+ session_id TEXT NOT NULL,
669
+ disposition TEXT NOT NULL CHECK(disposition IN ('quarantine_aggregate','mark_billing_unknown')),
670
+ original_sha256 TEXT NOT NULL CHECK(length(original_sha256)=64),
671
+ PRIMARY KEY(operation_key,request_id)
672
+ );
673
+ CREATE INDEX IF NOT EXISTS idx_legacy_targets_request ON legacy_reconciliation_targets(request_id);
674
+ CREATE INDEX IF NOT EXISTS idx_legacy_targets_session ON legacy_reconciliation_targets(session_id);
675
+ CREATE TABLE IF NOT EXISTS legacy_rollbacks (
676
+ operation_key TEXT PRIMARY KEY NOT NULL,
677
+ apply_key TEXT NOT NULL UNIQUE REFERENCES legacy_reconciliations(operation_key),
678
+ payload_sha256 TEXT NOT NULL CHECK(length(payload_sha256)=64),
679
+ actor_ref TEXT NOT NULL,
680
+ authority_ref TEXT NOT NULL,
681
+ created_at TEXT NOT NULL,
682
+ response_json TEXT NOT NULL
683
+ );
684
+ `;
685
+ var requestColumns = [
686
+ "id",
687
+ "agent",
688
+ "session_id",
689
+ "model",
690
+ "input_tokens",
691
+ "output_tokens",
692
+ "cache_read_tokens",
693
+ "cache_create_tokens",
694
+ "cache_create_5m_tokens",
695
+ "cache_create_1h_tokens",
696
+ "cost_usd",
697
+ "cost_basis",
698
+ "duration_ms",
699
+ "timestamp",
700
+ "source_request_id",
701
+ "machine_id",
702
+ "cost_center_id",
703
+ "attribution_tag",
704
+ "account_key",
705
+ "account_tool",
706
+ "account_name",
707
+ "account_email",
708
+ "account_source",
709
+ "updated_at",
710
+ "synced_at"
711
+ ];
712
+ var sessionColumns = [
713
+ "id",
714
+ "agent",
715
+ "project_path",
716
+ "project_name",
717
+ "started_at",
718
+ "ended_at",
719
+ "total_cost_usd",
720
+ "total_tokens",
721
+ "request_count",
722
+ "machine_id",
723
+ "cost_center_id",
724
+ "attribution_tag",
725
+ "account_key",
726
+ "account_tool",
727
+ "account_name",
728
+ "account_email",
729
+ "account_source",
730
+ "updated_at",
731
+ "synced_at"
732
+ ];
733
+ var q = (s) => '"' + s + '"';
734
+ var requestProjection = requestColumns.map((column) => column === "cost_basis" ? "CASE WHEN EXISTS (SELECT 1 FROM economy_legacy_active a WHERE a.request_id=r.id AND a.disposition='mark_billing_unknown') THEN 'unknown' ELSE r.cost_basis END AS cost_basis" : "r." + q(column)).join(`,
735
+ `);
736
+ var totals = {
737
+ total_cost_usd: "COALESCE(SUM(cost_usd),0)",
738
+ total_tokens: "COALESCE(SUM(input_tokens+output_tokens+cache_read_tokens+cache_create_tokens),0)",
739
+ request_count: "COUNT(*)"
740
+ };
741
+ var sessionProjection = sessionColumns.map((column) => totals[column] ? `CASE WHEN EXISTS (SELECT 1 FROM economy_legacy_active a WHERE a.session_id=s.id)
742
+ THEN (SELECT ${totals[column]} FROM economy_effective_requests r WHERE r.session_id=s.id)
743
+ ELSE s.${q(column)} END AS ${q(column)}` : "s." + q(column)).join(`,
744
+ `);
745
+ var LEGACY_VIEWS_SQL = `
746
+ CREATE VIEW IF NOT EXISTS economy_legacy_active AS
747
+ SELECT t.* FROM legacy_reconciliation_targets t
748
+ WHERE NOT EXISTS (SELECT 1 FROM legacy_rollbacks b WHERE b.apply_key=t.operation_key);
749
+ CREATE VIEW IF NOT EXISTS economy_effective_requests AS
750
+ SELECT ${requestProjection} FROM requests r
751
+ WHERE NOT EXISTS (SELECT 1 FROM economy_legacy_active a WHERE a.request_id=r.id AND a.disposition='quarantine_aggregate');
752
+ CREATE VIEW IF NOT EXISTS economy_effective_sessions AS
753
+ SELECT ${sessionProjection} FROM sessions s
754
+ WHERE NOT EXISTS (SELECT 1 FROM economy_legacy_active a WHERE a.session_id=s.id)
755
+ OR EXISTS (SELECT 1 FROM economy_effective_requests r WHERE r.session_id=s.id);
756
+ `;
757
+ var immutableTables = ["legacy_reconciliations", "legacy_reconciliation_targets", "legacy_rollbacks"];
758
+ var collision = (table) => table === "legacy_reconciliation_targets" ? "operation_key=NEW.operation_key AND request_id=NEW.request_id" : table === "legacy_rollbacks" ? "operation_key=NEW.operation_key OR apply_key=NEW.apply_key" : "operation_key=NEW.operation_key";
759
+ var LEGACY_SQLITE_SQL = LEGACY_LEDGER_SQL + LEGACY_VIEWS_SQL + immutableTables.map((table) => `
760
+ CREATE TRIGGER IF NOT EXISTS ${table}_no_update BEFORE UPDATE ON ${table}
761
+ BEGIN SELECT RAISE(ABORT,'legacy reconciliation history is immutable'); END;
762
+ CREATE TRIGGER IF NOT EXISTS ${table}_no_delete BEFORE DELETE ON ${table}
763
+ BEGIN SELECT RAISE(ABORT,'legacy reconciliation history is immutable'); END;
764
+ CREATE TRIGGER IF NOT EXISTS ${table}_no_replace BEFORE INSERT ON ${table}
765
+ WHEN EXISTS(SELECT 1 FROM ${table} WHERE ${collision(table)})
766
+ BEGIN SELECT RAISE(ABORT,'legacy reconciliation history is immutable'); END;
767
+ `).join("") + `
768
+ CREATE TRIGGER IF NOT EXISTS legacy_target_no_overlap BEFORE INSERT ON legacy_reconciliation_targets
769
+ WHEN EXISTS(SELECT 1 FROM economy_legacy_active a WHERE a.session_id=NEW.session_id AND a.operation_key<>NEW.operation_key)
770
+ OR EXISTS(SELECT 1 FROM economy_legacy_active a WHERE a.request_id=NEW.request_id)
771
+ BEGIN SELECT RAISE(ABORT,'legacy reconciliation is already active for this session'); END;
772
+ CREATE TRIGGER IF NOT EXISTS legacy_request_no_insert BEFORE INSERT ON requests
773
+ WHEN EXISTS(SELECT 1 FROM economy_legacy_active a WHERE a.request_id=NEW.id)
774
+ BEGIN SELECT RAISE(ABORT,'legacy reconciliation protects this request'); END;
775
+ CREATE TRIGGER IF NOT EXISTS legacy_request_no_update BEFORE UPDATE ON requests
776
+ WHEN EXISTS(SELECT 1 FROM economy_legacy_active a WHERE a.request_id=NEW.id OR a.request_id=OLD.id)
777
+ BEGIN SELECT RAISE(ABORT,'legacy reconciliation protects this request'); END;
778
+ CREATE TRIGGER IF NOT EXISTS legacy_request_no_delete BEFORE DELETE ON requests
779
+ WHEN EXISTS(SELECT 1 FROM economy_legacy_active a WHERE a.request_id=OLD.id)
780
+ BEGIN SELECT RAISE(ABORT,'legacy reconciliation protects this request'); END;
781
+ `;
782
+ var pgViews = LEGACY_VIEWS_SQL.replace(/CREATE VIEW IF NOT EXISTS (\w+) AS/g, "CREATE VIEW $1 WITH (security_invoker=true) AS");
783
+ var LEGACY_PG_SQL = LEGACY_LEDGER_SQL + pgViews + `
784
+ CREATE FUNCTION economy_legacy_history_immutable() RETURNS trigger LANGUAGE plpgsql AS $body$
785
+ BEGIN
786
+ RAISE EXCEPTION 'legacy reconciliation history is immutable' USING ERRCODE='23000';
787
+ END
788
+ $body$;
789
+ ` + immutableTables.map((table) => `
790
+ CREATE TRIGGER ${table}_immutable BEFORE UPDATE OR DELETE OR TRUNCATE ON ${table}
791
+ FOR EACH STATEMENT EXECUTE FUNCTION economy_legacy_history_immutable();
792
+ `).join("") + `
793
+ CREATE FUNCTION economy_legacy_request_guard() RETURNS trigger LANGUAGE plpgsql AS $body$
794
+ BEGIN
795
+ IF TG_OP='TRUNCATE' THEN
796
+ IF EXISTS(SELECT 1 FROM economy_legacy_active) THEN
797
+ RAISE EXCEPTION 'legacy reconciliation protects requests' USING ERRCODE='23000';
798
+ END IF;
799
+ RETURN NULL;
800
+ END IF;
801
+ IF TG_OP IN ('INSERT','UPDATE') THEN
802
+ IF EXISTS(SELECT 1 FROM economy_legacy_active WHERE request_id=NEW.id) THEN
803
+ RAISE EXCEPTION 'legacy reconciliation protects this request' USING ERRCODE='23000';
804
+ END IF;
805
+ END IF;
806
+ IF TG_OP IN ('UPDATE','DELETE') THEN
807
+ IF EXISTS(SELECT 1 FROM economy_legacy_active WHERE request_id=OLD.id) THEN
808
+ RAISE EXCEPTION 'legacy reconciliation protects this request' USING ERRCODE='23000';
809
+ END IF;
810
+ END IF;
811
+ IF TG_OP='DELETE' THEN RETURN OLD; END IF;
812
+ RETURN NEW;
813
+ END
814
+ $body$;
815
+ CREATE TRIGGER legacy_request_guard BEFORE INSERT OR UPDATE OR DELETE ON requests
816
+ FOR EACH ROW EXECUTE FUNCTION economy_legacy_request_guard();
817
+ CREATE TRIGGER legacy_request_no_truncate BEFORE TRUNCATE ON requests
818
+ FOR EACH STATEMENT EXECUTE FUNCTION economy_legacy_request_guard();
819
+ CREATE FUNCTION economy_legacy_target_guard() RETURNS trigger LANGUAGE plpgsql AS $body$
820
+ BEGIN
821
+ IF EXISTS(SELECT 1 FROM economy_legacy_active a WHERE a.session_id=NEW.session_id AND a.operation_key<>NEW.operation_key)
822
+ OR EXISTS(SELECT 1 FROM economy_legacy_active a WHERE a.request_id=NEW.request_id) THEN
823
+ RAISE EXCEPTION 'legacy reconciliation is already active for this session' USING ERRCODE='23000';
824
+ END IF;
825
+ RETURN NEW;
826
+ END
827
+ $body$;
828
+ CREATE TRIGGER legacy_target_no_overlap BEFORE INSERT ON legacy_reconciliation_targets
829
+ FOR EACH ROW EXECUTE FUNCTION economy_legacy_target_guard();
830
+ `;
831
+
832
+ // src/db/ingest.ts
833
+ import { createHash as createHash3 } from "crypto";
834
+ var sha = (text) => createHash3("sha256").update(text).digest("hex");
835
+ var quote = (name) => `"${name}"`;
836
+ var predicate = (spec) => spec.pk.map((k) => `${quote(k)} = ?`).join(" AND ");
837
+ var keys = (spec, row) => spec.pk.map((k) => row[k]);
838
+ var conflict = (field, expected) => {
839
+ throw new IngestError(409, "INGEST_CONFLICT", field, expected);
840
+ };
841
+ var sessionDerived = ["total_cost_usd", "total_tokens", "request_count", "ended_at"];
842
+ var sessionFilled = ["started_at", "account_key", "account_tool", "account_name", "account_email", "account_source", "cost_center_id"];
843
+ function ingestCommand(db, body, rollup, context = {}) {
844
+ const groups = validateIngest(body);
845
+ const actor = context.actorId ?? "legacy";
846
+ if (typeof actor !== "string" || actor.length === 0 || actor.length > 2048)
847
+ throw new IngestError(400, "INVALID_INGEST", "actorId", "expected a verified actor identifier");
848
+ const payloadHash = canonicalIngestBodyHash(body);
849
+ const key = typeof body["idempotency_key"] === "string" ? body["idempotency_key"] : payloadHash;
850
+ const operation = sha(stableJson([actor, key]));
851
+ const now = new Date().toISOString();
852
+ const sessionSpec = ingestSpecs().find((s) => s.table === "sessions");
853
+ const touched = new Set;
854
+ for (const { spec, rows } of groups)
855
+ for (const row of rows) {
856
+ if (spec.table === "requests")
857
+ touched.add(String(row["session_id"]));
858
+ if (spec.table === "sessions")
859
+ touched.add(String(row["id"]));
860
+ }
861
+ return db.transaction(() => {
862
+ const reservation = db.run(`INSERT INTO ingest_batches(operation_key,actor_id,payload_hash,response_json,created_at)
863
+ VALUES(?,?,?,?,?) ON CONFLICT(operation_key) DO NOTHING`, operation, actor, payloadHash, "", now);
864
+ if (reservation.changes === 0) {
865
+ const previous = db.get("SELECT payload_hash,response_json FROM ingest_batches WHERE operation_key = ?", operation);
866
+ if (previous?.payload_hash !== payloadHash)
867
+ conflict("idempotency_key", "key already used for different content; use the original command or a new key");
868
+ if (!previous.response_json)
869
+ conflict("idempotency_key", "receipt is not complete; reconcile the original operation");
870
+ return { ...JSON.parse(previous.response_json), replayed: true };
871
+ }
872
+ const result = { ingested: {}, total: 0, stale: 0, unchanged: 0, operation_key: operation, replayed: false };
873
+ const captured = new Set;
874
+ function preserve(spec, row) {
875
+ const entity = stableJson(keys(spec, row));
876
+ const identity = stableJson([spec.table, entity]);
877
+ if (captured.has(identity))
878
+ return;
879
+ const original = stableJson(row);
880
+ const digest2 = sha(original);
881
+ const id = sha(stableJson([operation, spec.table, entity]));
882
+ db.run(`INSERT INTO ingest_preimages(id,operation_key,table_name,entity_key,record_json,record_sha256,recorded_at)
883
+ VALUES(?,?,?,?,?,?,?)`, id, operation, spec.table, entity, original, digest2, now);
884
+ const readback = db.get("SELECT record_json,record_sha256 FROM ingest_preimages WHERE id = ?", id);
885
+ if (readback?.record_json !== original || readback?.record_sha256 !== digest2 || sha(readback.record_json) !== digest2) {
886
+ throw new Error("Ingest predecessor readback failed");
887
+ }
888
+ captured.add(identity);
889
+ }
890
+ function cas(spec, previous, set, args) {
891
+ const columns = Object.keys(previous);
892
+ if (columns.some((k) => !/^[a-z][a-z0-9_]*$/.test(k)))
893
+ throw new Error("Unexpected Economy schema column");
894
+ const where = columns.map((k) => `${quote(k)} IS NOT DISTINCT FROM ?`).join(" AND ");
895
+ const updated = db.run(`UPDATE ${quote(spec.table)} SET ${set} WHERE ${where}`, ...args, ...columns.map((k) => previous[k]));
896
+ if (updated.changes !== 1)
897
+ conflict(spec.table, "stored row changed concurrently; read current state before retrying");
898
+ }
899
+ for (const id of [...touched].sort()) {
900
+ db.run(`INSERT INTO ingest_session_locks(session_id) VALUES(?)
901
+ ON CONFLICT(session_id) DO UPDATE SET session_id = excluded.session_id`, id);
902
+ const previous = db.get("SELECT * FROM sessions WHERE id = ?", id);
903
+ if (previous) {
904
+ preserve(sessionSpec, previous);
905
+ cas(sessionSpec, previous, "id = id", []);
906
+ }
907
+ }
908
+ for (const { spec, rows } of groups) {
909
+ if (rows.length === 0)
910
+ continue;
911
+ const accepted = new Set;
912
+ for (const raw of rows) {
913
+ const values = spec.map(raw, "");
914
+ const row = Object.fromEntries(spec.columns.map((k, i) => [k, values[i]]));
915
+ const pk = keys(spec, row);
916
+ const entity = stableJson(pk);
917
+ if (spec.table === "requests" && db.get("SELECT request_id FROM economy_legacy_active WHERE request_id = ?", row["id"])) {
918
+ conflict("requests.id", "legacy_reconciliation_protects_request; read its recorded disposition before ingesting");
919
+ }
920
+ if (row["agent"] === "hermes") {
921
+ const sid = spec.table === "sessions" ? String(row["id"]) : String(row["session_id"]);
922
+ const legacyId = `${sid}-rollup`;
923
+ if (spec.table === "requests" && row["id"] === legacyId || spec.table === "sessions" && db.get("SELECT id FROM economy_effective_requests WHERE agent='hermes' AND session_id=? AND id=?", sid, legacyId)) {
924
+ conflict(spec.table, "legacy_aggregate_requires_migration");
925
+ }
926
+ }
927
+ if (spec.table === "requests" && row["agent"] === "codex") {
928
+ const sessionId = String(row["session_id"]);
929
+ const legacyId = `${sessionId}-0`;
930
+ const eventPrefix = `${sessionId}-event-`;
931
+ const id = String(row["id"]);
932
+ const mixed = id.startsWith(eventPrefix) ? db.get("SELECT id FROM economy_effective_requests WHERE session_id = ? AND agent = 'codex' AND id = ?", sessionId, legacyId) : id === legacyId ? db.get("SELECT id FROM economy_effective_requests WHERE session_id = ? AND agent = 'codex' AND substr(id,1,?) = ? LIMIT 1", sessionId, eventPrefix.length, eventPrefix) : null;
933
+ if (mixed)
934
+ conflict("requests", "legacy_aggregate_requires_migration");
935
+ }
936
+ const previous = db.get(`SELECT * FROM ${quote(spec.table)} WHERE ${predicate(spec)}`, ...pk);
937
+ if (spec.table === "budgets" && body["budget_mode"] === "create" && previous) {
938
+ conflict("budgets.id", "rule identity already exists; only the original actor and identical command/key can replay creation");
939
+ }
940
+ if (!previous) {
941
+ if (spec.table === "budgets" && row["metric"] === "metered_api_usd" && body["budget_mode"] !== "create") {
942
+ conflict("budget_mode", "new metered API rules require the explicit create command");
943
+ }
944
+ const inserted = db.run(`INSERT INTO ${quote(spec.table)} (${spec.columns.map(quote).join(",")})
945
+ VALUES (${values.map(() => "?").join(",")}) ON CONFLICT DO NOTHING`, ...values);
946
+ if (inserted.changes !== 1)
947
+ conflict(spec.table, "identity or unique key already exists; reconcile before retrying");
948
+ accepted.add(entity);
949
+ continue;
950
+ }
951
+ if (spec.table === "requests" && previous.session_id !== row["session_id"])
952
+ conflict("requests.session_id", "an existing request cannot change its session identity");
953
+ if (spec.table === "sessions" && Number(db.get("SELECT COUNT(*) AS n FROM requests WHERE session_id = ?", row["id"]).n) > 0) {
954
+ for (const column of sessionDerived)
955
+ row[column] = previous[column];
956
+ for (const column of sessionFilled)
957
+ if (row[column] === "" || row[column] === null)
958
+ row[column] = previous[column];
959
+ }
960
+ if (spec.table === "subscriptions" && (row["account_key"] || previous.account_key)) {
961
+ if (previous.provider !== row["provider"] || previous.account_key !== row["account_key"])
962
+ conflict("subscriptions.account_key", "existing subscription identity cannot change");
963
+ row["created_at"] = previous.created_at;
964
+ if (body["subscription_mode"] !== "configuration") {
965
+ for (const column of ["agent", "active", "plan", "billing_cycle_start", "reset_policy"])
966
+ row[column] = previous[column];
967
+ for (const [basis, amount] of [["fee_basis", "monthly_fee_usd"], ["included_basis", "included_usage_usd"]]) {
968
+ if (row[basis] === "unknown" && ["configured", "observed"].includes(String(previous[basis]))) {
969
+ row[basis] = previous[basis];
970
+ row[amount] = previous[amount];
971
+ }
972
+ }
973
+ }
974
+ }
975
+ const contentColumns = spec.columns.filter((c) => c !== "updated_at" && c !== "synced_at");
976
+ const equal = contentColumns.every((c) => stableJson(previous[c]) === stableJson(row[c]));
977
+ if (spec.table === "budgets" && (previous.metric === "metered_api_usd" || row["metric"] === "metered_api_usd")) {
978
+ if (!spec.columns.every((c) => stableJson(previous[c]) === stableJson(row[c])))
979
+ conflict("budgets.id", "metered API rules are create-only; an existing rule cannot be edited or converted");
980
+ result.unchanged++;
981
+ continue;
982
+ }
983
+ if ((spec.table === "scan_receipts" || spec.table === "usage_snapshots" && (row["observed_at"] || previous.observed_at)) && !equal) {
984
+ conflict(spec.table, "an observation identity is immutable; submit a new observation time for new content");
985
+ }
986
+ const priorVersion = typeof previous.updated_at === "string" ? Date.parse(previous.updated_at) : NaN;
987
+ const nextVersion = typeof row["updated_at"] === "string" ? Date.parse(row["updated_at"]) : NaN;
988
+ if (Number.isFinite(priorVersion) && Number.isFinite(nextVersion) && nextVersion < priorVersion) {
989
+ result.stale++;
990
+ continue;
991
+ }
992
+ if (equal && (!Number.isFinite(nextVersion) || Number.isFinite(priorVersion) && nextVersion === priorVersion)) {
993
+ result.unchanged++;
994
+ continue;
995
+ }
996
+ if (!Number.isFinite(nextVersion) || Number.isFinite(priorVersion) && nextVersion === priorVersion) {
997
+ conflict(`${spec.table}.updated_at`, "different content requires an explicit newer source version");
998
+ }
999
+ preserve(spec, previous);
1000
+ const columns = spec.columns.filter((c) => !spec.pk.includes(c));
1001
+ cas(spec, previous, columns.map((c) => `${quote(c)} = ?`).join(","), columns.map((c) => row[c]));
1002
+ accepted.add(entity);
1003
+ }
1004
+ result.ingested[spec.table] = accepted.size;
1005
+ result.total += accepted.size;
1006
+ }
1007
+ for (const id of [...touched].sort()) {
1008
+ const session = db.get("SELECT * FROM sessions WHERE id = ?", id);
1009
+ if (!session || Number(db.get("SELECT COUNT(*) AS n FROM requests WHERE session_id = ?", id).n) === 0)
1010
+ continue;
1011
+ preserve(sessionSpec, session);
1012
+ rollup(db, id);
1013
+ }
1014
+ db.run("UPDATE ingest_batches SET response_json = ? WHERE operation_key = ?", JSON.stringify(result), operation);
1015
+ return result;
1016
+ });
1017
+ }
1018
+
1019
+ // src/lib/budget-decimal.ts
1020
+ function decimal(value) {
1021
+ if (!Number.isFinite(value) || value < 0)
1022
+ throw new Error("Invalid stored metered budget amount");
1023
+ const [mantissa, exponent = "0"] = String(value).split("e");
1024
+ const [whole, fraction = ""] = mantissa.split(".");
1025
+ return { units: BigInt(whole + fraction), exponent: Number(exponent) - fraction.length };
1026
+ }
1027
+ function count(value) {
1028
+ if (typeof value === "number" && !Number.isSafeInteger(value) || !/^[1-9][0-9]{0,18}$/.test(String(value))) {
1029
+ throw new Error("Invalid stored metered budget row count");
1030
+ }
1031
+ return BigInt(value);
1032
+ }
1033
+ function sum(a, b) {
1034
+ const exponent = Math.min(a.exponent, b.exponent);
1035
+ return { units: a.units * 10n ** BigInt(a.exponent - exponent) + b.units * 10n ** BigInt(b.exponent - exponent), exponent };
1036
+ }
1037
+ function atLeast(a, b) {
1038
+ const exponent = Math.min(a.exponent, b.exponent);
1039
+ return a.units * 10n ** BigInt(a.exponent - exponent) >= b.units * 10n ** BigInt(b.exponent - exponent);
1040
+ }
1041
+ function meteredBudgetAmounts(groups, limit, threshold) {
1042
+ if (!Number.isFinite(limit) || limit <= 0 || !Number.isInteger(threshold) || threshold < 1 || threshold > 100) {
1043
+ throw new Error("Invalid stored metered budget threshold");
1044
+ }
1045
+ let total = { units: 0n, exponent: 0 };
1046
+ for (const group of groups) {
1047
+ const amount = decimal(group.cost_usd);
1048
+ total = sum(total, { ...amount, units: amount.units * count(group.entries) });
1049
+ }
1050
+ const limitAmount = decimal(limit);
1051
+ const spend = Number(`${total.units}e${total.exponent}`);
1052
+ const percent = spend / limit * 100;
1053
+ if (!Number.isFinite(spend) || !Number.isFinite(percent))
1054
+ throw new Error("Metered budget display amount exceeds supported numeric range");
1055
+ return {
1056
+ current_spend_usd: spend,
1057
+ percent_used: percent,
1058
+ is_over_limit: atLeast(total, limitAmount),
1059
+ is_over_alert: atLeast({ ...total, units: total.units * 100n }, { ...limitAmount, units: limitAmount.units * BigInt(threshold) })
1060
+ };
1061
+ }
1062
+
1063
+ // src/lib/periods.ts
1064
+ function ymd(date2) {
1065
+ return date2.toISOString().substring(0, 10);
1066
+ }
1067
+ function calendarPeriodBounds(period, now = new Date) {
1068
+ if (!Number.isFinite(now.getTime()))
1069
+ throw new RangeError("Invalid reporting clock");
1070
+ const start = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
1071
+ const end = new Date(start);
1072
+ switch (period) {
1073
+ case "today":
1074
+ end.setUTCDate(end.getUTCDate() + 1);
1075
+ break;
1076
+ case "yesterday":
1077
+ start.setUTCDate(start.getUTCDate() - 1);
1078
+ break;
1079
+ case "week":
1080
+ start.setUTCDate(start.getUTCDate() - start.getUTCDay());
1081
+ end.setTime(start.getTime());
1082
+ end.setUTCDate(end.getUTCDate() + 7);
1083
+ break;
1084
+ case "month":
1085
+ start.setUTCDate(1);
1086
+ end.setTime(start.getTime());
1087
+ end.setUTCMonth(end.getUTCMonth() + 1);
1088
+ break;
1089
+ case "year":
1090
+ start.setUTCMonth(0, 1);
1091
+ end.setTime(start.getTime());
1092
+ end.setUTCFullYear(end.getUTCFullYear() + 1);
1093
+ break;
1094
+ case "all":
1095
+ return { time_zone: "UTC", start_date: null, end_date_exclusive: null };
1096
+ default:
1097
+ throw new RangeError("Invalid reporting period");
1098
+ }
1099
+ return { time_zone: "UTC", start_date: ymd(start), end_date_exclusive: ymd(end) };
1100
+ }
1101
+ function calendarPeriodWhere(period, column, now = new Date) {
1102
+ if (!/^[a-z_][a-z0-9_]*(?:\.[a-z_][a-z0-9_]*)?$/i.test(column)) {
1103
+ throw new TypeError("Invalid reporting column");
1104
+ }
1105
+ const bounds = calendarPeriodBounds(period, now);
1106
+ return bounds.start_date === null ? "1=1" : `${column} >= '${bounds.start_date}' AND ${column} < '${bounds.end_date_exclusive}'`;
1107
+ }
1108
+ function usageSnapshotFilterForPeriod(period) {
1109
+ const bounds = calendarPeriodBounds(period);
1110
+ if (bounds.start_date === null)
1111
+ return {};
1112
+ if (period === "today" || period === "yesterday")
1113
+ return { date: bounds.start_date };
1114
+ return { since: bounds.start_date, before: bounds.end_date_exclusive };
1115
+ }
1116
+ function sessionOverlapWhere(period, alias, now = new Date) {
1117
+ if (!/^[a-z_][a-z0-9_]*$/i.test(alias))
1118
+ throw new TypeError("Invalid session alias");
1119
+ const bounds = calendarPeriodBounds(period, now);
1120
+ return bounds.start_date === null ? "1=1" : `${alias}.started_at < '${bounds.end_date_exclusive}' AND (${alias}.ended_at IS NULL OR ${alias}.ended_at >= '${bounds.start_date}')`;
1121
+ }
1122
+
1123
+ // src/lib/money-display.ts
1124
+ function formatReportedUsd(value) {
1125
+ if (value === null || value === undefined || !Number.isFinite(value))
1126
+ return "unavailable";
1127
+ return "$" + value.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
1128
+ }
1129
+ function formatSourcedUsd(value, basis) {
1130
+ return formatReportedUsd(basis === "configured" || basis === "observed" ? value : null);
1131
+ }
1132
+ function formatPlanMoney(plan) {
1133
+ return {
1134
+ fee: formatSourcedUsd(plan.monthly_fee_usd, plan.fee_basis),
1135
+ included: formatSourcedUsd(plan.included_usage_usd, plan.included_basis)
1136
+ };
1137
+ }
1138
+
1139
+ // src/lib/summary-contract.ts
1140
+ function knownZero(row, prices) {
1141
+ if (row.cost_basis === "metered_api")
1142
+ return true;
1143
+ if (!row.model || [row.input_tokens, row.output_tokens, row.cache_read_tokens, row.cache_create_tokens, row.cache_create_1h_tokens].some((value) => typeof value !== "number" || !Number.isSafeInteger(value) || value < 0))
1144
+ return false;
1145
+ try {
1146
+ const result = assessCostFromRows(prices, row.model, row.input_tokens, row.output_tokens, row.cache_read_tokens, Math.max(0, row.cache_create_tokens - row.cache_create_1h_tokens), row.cache_create_1h_tokens);
1147
+ return result.usd === 0;
1148
+ } catch {
1149
+ return false;
1150
+ }
1151
+ }
1152
+ function hasKnownUsagePrice(row, prices) {
1153
+ return ["metered_api", "subscription_included", "estimated"].includes(row.cost_basis ?? "") && Number.isFinite(row.cost_usd) && row.cost_usd >= 0 && (!(row.zero_cost === 1 || row.cost_usd === 0) || knownZero(row, prices));
1154
+ }
1155
+ function buildCostSummary(period, period_bounds, groups, sessionOnly, requestSessions, prices) {
1156
+ let metered = 0, estimated = 0, included = 0, unknown = 0, unknownBasis = 0, requestRows = 0, tokens = 0, knownRows = 0;
1157
+ for (const row of groups) {
1158
+ requestRows += row.requests;
1159
+ tokens += row.input_tokens + row.output_tokens + row.cache_read_tokens + row.cache_create_tokens;
1160
+ if (!["metered_api", "subscription_included", "estimated"].includes(row.cost_basis ?? "")) {
1161
+ unknownBasis += row.requests;
1162
+ unknown += row.requests;
1163
+ continue;
1164
+ }
1165
+ if (!hasKnownUsagePrice(row, prices)) {
1166
+ unknown += row.requests;
1167
+ continue;
1168
+ }
1169
+ knownRows += row.requests;
1170
+ if (row.cost_basis === "metered_api")
1171
+ metered += row.cost_usd;
1172
+ else if (row.cost_basis === "subscription_included")
1173
+ included += row.cost_usd;
1174
+ else
1175
+ estimated += row.cost_usd;
1176
+ }
1177
+ const sessionValue = Number.isFinite(sessionOnly.cost_usd) && sessionOnly.cost_usd >= 0 ? sessionOnly.cost_usd : 0;
1178
+ const total = metered + estimated + included + sessionValue;
1179
+ if (!Number.isFinite(total))
1180
+ throw new RangeError("Summary monetary subtotal exceeds the supported range");
1181
+ const empty = requestRows === 0 && sessionOnly.sessions === 0;
1182
+ const complete = !empty && unknown === 0 && sessionOnly.sessions === 0 && Number.isFinite(total);
1183
+ const completeness = empty ? "no_records" : complete ? "complete" : knownRows > 0 || sessionValue > 0 ? "partial" : "unavailable";
1184
+ return {
1185
+ total_usd: Number.isFinite(total) ? total : 0,
1186
+ complete_total_usd: complete ? total : null,
1187
+ basis: "known_usage_value_subtotal",
1188
+ completeness,
1189
+ coverage_scope: "selected_stored_records",
1190
+ source_coverage: "not_established",
1191
+ period_bounds,
1192
+ recorded_metered_api_usd: metered,
1193
+ estimated_usage_value_usd: estimated,
1194
+ subscription_included_value_usd: included,
1195
+ session_only_value_usd: sessionValue,
1196
+ unknown_price_requests: unknown,
1197
+ unknown_basis_requests: unknownBasis,
1198
+ request_rows: requestRows,
1199
+ session_only_sessions: sessionOnly.sessions,
1200
+ sessions: requestSessions + sessionOnly.sessions,
1201
+ requests: requestRows + sessionOnly.requests,
1202
+ tokens: tokens + sessionOnly.tokens,
1203
+ period
1204
+ };
1205
+ }
1206
+ function requireCurrentCostSummary(value) {
1207
+ if (!value || typeof value !== "object")
1208
+ throw new Error("SUMMARY_REPORT_UNAVAILABLE: Invalid summary response");
1209
+ const s = value;
1210
+ if (s.basis !== "known_usage_value_subtotal" || s.coverage_scope !== "selected_stored_records" || s.source_coverage !== "not_established" || !["complete", "partial", "unavailable", "no_records"].includes(String(s.completeness)))
1211
+ throw new Error("SUMMARY_REPORT_UNAVAILABLE: The service must support summary coverage");
1212
+ for (const key of [
1213
+ "total_usd",
1214
+ "recorded_metered_api_usd",
1215
+ "estimated_usage_value_usd",
1216
+ "subscription_included_value_usd",
1217
+ "session_only_value_usd",
1218
+ "request_rows",
1219
+ "unknown_price_requests",
1220
+ "unknown_basis_requests",
1221
+ "session_only_sessions",
1222
+ "requests",
1223
+ "sessions",
1224
+ "tokens"
1225
+ ]) {
1226
+ if (typeof s[key] !== "number" || !Number.isFinite(s[key]) || Number(s[key]) < 0)
1227
+ throw new Error("SUMMARY_REPORT_UNAVAILABLE: Invalid field " + key);
1228
+ }
1229
+ if (s.complete_total_usd !== null && (typeof s.complete_total_usd !== "number" || !Number.isFinite(s.complete_total_usd) || s.complete_total_usd < 0))
1230
+ throw new Error("SUMMARY_REPORT_UNAVAILABLE: Invalid complete total");
1231
+ if (s.completeness === "complete" !== (s.complete_total_usd !== null) || s.completeness === "complete" && (s.unknown_price_requests !== 0 || s.session_only_sessions !== 0))
1232
+ throw new Error("SUMMARY_REPORT_UNAVAILABLE: Inconsistent completeness");
1233
+ for (const key of ["request_rows", "unknown_price_requests", "unknown_basis_requests", "session_only_sessions", "requests", "sessions", "tokens"]) {
1234
+ if (!Number.isSafeInteger(s[key]))
1235
+ throw new Error("SUMMARY_REPORT_UNAVAILABLE: Invalid count " + key);
1236
+ }
1237
+ if (Number(s.unknown_price_requests) > Number(s.request_rows) || Number(s.unknown_basis_requests) > Number(s.unknown_price_requests))
1238
+ throw new Error("SUMMARY_REPORT_UNAVAILABLE: Invalid request coverage");
1239
+ const subtotal = Number(s.recorded_metered_api_usd) + Number(s.estimated_usage_value_usd) + Number(s.subscription_included_value_usd) + Number(s.session_only_value_usd);
1240
+ if (s.total_usd !== subtotal || s.complete_total_usd !== null && s.complete_total_usd !== subtotal)
1241
+ throw new Error("SUMMARY_REPORT_UNAVAILABLE: Inconsistent subtotal");
1242
+ const empty = s.request_rows === 0 && s.session_only_sessions === 0;
1243
+ if (s.completeness === "no_records" !== empty)
1244
+ throw new Error("SUMMARY_REPORT_UNAVAILABLE: Inconsistent record coverage");
1245
+ const bounds = s.period_bounds;
1246
+ if (bounds?.time_zone !== "UTC" || !(bounds.start_date === null || typeof bounds.start_date === "string") || !(bounds.end_date_exclusive === null || typeof bounds.end_date_exclusive === "string") || bounds.start_date === null !== (bounds.end_date_exclusive === null))
1247
+ throw new Error("SUMMARY_REPORT_UNAVAILABLE: Missing UTC period bounds");
1248
+ return value;
1249
+ }
1250
+ function formatSummaryValue(value) {
1251
+ try {
1252
+ requireCurrentCostSummary(value);
1253
+ } catch {
1254
+ return "unavailable (summary coverage unsupported)";
1255
+ }
1256
+ if (value.complete_total_usd === null)
1257
+ return `unavailable (known subtotal ${formatReportedUsd(value.total_usd)})`;
1258
+ return `${formatReportedUsd(value.complete_total_usd)} usage value`;
1259
+ }
1260
+ function formatSummaryCoverage(s) {
1261
+ try {
1262
+ requireCurrentCostSummary(s);
1263
+ } catch {
1264
+ return "Summary coverage unsupported; update the service";
1265
+ }
1266
+ const range = s.period_bounds.start_date ? `${s.period_bounds.start_date} to ${s.period_bounds.end_date_exclusive} exclusive` : "all stored dates";
1267
+ return `${s.completeness}; UTC ${range}; selected stored records only; source coverage unverified; ` + `recorded metered ${formatReportedUsd(s.recorded_metered_api_usd)}, estimated ${formatReportedUsd(s.estimated_usage_value_usd)}, ` + `included value ${formatReportedUsd(s.subscription_included_value_usd)}, unallocated session lifetime value ${formatReportedUsd(s.session_only_value_usd)} (${s.session_only_sessions} overlapping sessions); ` + `${s.unknown_price_requests} unknown-price requests, ${s.unknown_basis_requests} unknown-basis requests`;
1268
+ }
1269
+ function requireCurrentBillingSummary(value) {
1270
+ if (!value || typeof value !== "object")
1271
+ throw new Error("BILLING_REPORT_UNAVAILABLE: Invalid billing response");
1272
+ const s = value;
1273
+ if (s.basis !== "recorded_provider_rows_subtotal" || s.source_coverage !== "not_established" || s.coverage_scope !== "selected_stored_records" || !["observed", "no_records"].includes(String(s.status)) || s.complete_total_usd !== null)
1274
+ throw new Error("BILLING_REPORT_UNAVAILABLE: The service must support billing coverage");
1275
+ if (typeof s.total_usd !== "number" || !Number.isFinite(s.total_usd) || !Number.isSafeInteger(s.record_count) || Number(s.record_count) < 0 || s.recorded_total_usd !== null && (typeof s.recorded_total_usd !== "number" || !Number.isFinite(s.recorded_total_usd)))
1276
+ throw new Error("BILLING_REPORT_UNAVAILABLE: Invalid monetary coverage");
1277
+ if (s.status === "observed" !== Number(s.record_count) > 0 || s.recorded_total_usd !== null !== (s.status === "observed"))
1278
+ throw new Error("BILLING_REPORT_UNAVAILABLE: Inconsistent record coverage");
1279
+ return value;
1280
+ }
1281
+ function formatBillingValue(value) {
1282
+ try {
1283
+ const s = requireCurrentBillingSummary(value);
1284
+ return s.recorded_total_usd === null ? "unavailable (no provider rows)" : `${formatReportedUsd(s.recorded_total_usd)} recorded subtotal`;
1285
+ } catch {
1286
+ return "unavailable (billing coverage unsupported)";
1287
+ }
1288
+ }
1289
+
1290
+ // src/db/database.ts
1291
+ import { execFileSync } from "child_process";
1292
+ import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
1293
+ import { hostname, platform } from "os";
1294
+ import { homedir } from "os";
1295
+ import { join, resolve } from "path";
1296
+ import { homedir as pathsResolverHomedir } from "os";
1297
+ import { join as pathsResolverJoin } from "path";
1298
+ var PATHS_RESOLVER_KIND_ENV = {
1299
+ config: "HASNA_CONFIG_HOME",
1300
+ data: "HASNA_DATA_HOME",
1301
+ state: "HASNA_STATE_HOME",
1302
+ cache: "HASNA_CACHE_HOME"
1303
+ };
1304
+ var PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
1305
+ function pathsResolverAssertApp(app) {
1306
+ if (typeof app !== "string" || app.length === 0) {
1307
+ throw new TypeError("paths: app must be a non-empty string");
1308
+ }
1309
+ if (!PATHS_RESOLVER_APP_SLUG_RE.test(app)) {
1310
+ throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
1311
+ }
1312
+ }
1313
+ function pathsResolverAssertKind(kind) {
1314
+ if (!Object.keys(PATHS_RESOLVER_KIND_ENV).includes(kind)) {
1315
+ throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV).join(", ")}`);
1316
+ }
1317
+ }
1318
+ function pathsResolverBaseDir(kind, options) {
1319
+ pathsResolverAssertKind(kind);
1320
+ const env = options.env ?? process.env;
1321
+ const override = env[PATHS_RESOLVER_KIND_ENV[kind]];
1322
+ if (typeof override === "string" && override.length > 0)
1323
+ return override;
1324
+ const home = options.home ?? pathsResolverHomedir();
1325
+ const platform2 = options.platform ?? process.platform;
1326
+ if (platform2 === "darwin") {
1327
+ switch (kind) {
1328
+ case "config":
1329
+ case "data":
1330
+ return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
1331
+ case "cache":
1332
+ return pathsResolverJoin(home, "Library", "Caches", "Hasna");
1333
+ case "state":
1334
+ return pathsResolverJoin(home, "Library", "Logs", "Hasna");
1335
+ }
1336
+ }
1337
+ switch (kind) {
1338
+ case "config":
1339
+ return pathsResolverJoin(home, ".config", "hasna");
1340
+ case "data":
1341
+ return pathsResolverJoin(home, ".local", "share", "hasna");
1342
+ case "state":
1343
+ return pathsResolverJoin(home, ".local", "state", "hasna");
1344
+ case "cache":
1345
+ return pathsResolverJoin(home, ".cache", "hasna");
1346
+ }
1347
+ }
1348
+ function pathsResolverResolve(kind, options) {
1349
+ pathsResolverAssertApp(options.app);
1350
+ const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
1351
+ return pathsResolverJoin(pathsResolverBaseDir(kind, options), appSegment);
1352
+ }
1353
+ function dataDir(options) {
1354
+ return pathsResolverResolve("data", options);
1355
+ }
1356
+ function cacheDir(options) {
1357
+ return pathsResolverResolve("cache", options);
1358
+ }
1359
+ function normalizeMachineId(value) {
1360
+ const id = value?.trim().toLowerCase().split(".")[0];
1361
+ return id && id.length > 0 ? id : null;
1362
+ }
1363
+ function macHostMachineId() {
1364
+ if (platform() !== "darwin")
1365
+ return null;
1366
+ for (const key of ["LocalHostName", "ComputerName", "HostName"]) {
1367
+ try {
1368
+ const value = execFileSync("/usr/sbin/scutil", ["--get", key], {
1369
+ encoding: "utf8",
1370
+ stdio: ["ignore", "pipe", "ignore"],
1371
+ timeout: 1000
1372
+ });
1373
+ const id = normalizeMachineId(value);
1374
+ if (id && id !== "mac" && id !== "localhost")
1375
+ return id;
1376
+ } catch {}
1377
+ }
1378
+ return null;
1379
+ }
1380
+ function getMachineId() {
1381
+ const envMachine = normalizeMachineId(process.env["HASNA_ECONOMY_MACHINE_ID"]);
1382
+ if (envMachine)
1383
+ return envMachine;
1384
+ const hostMachine = normalizeMachineId(hostname()) ?? "unknown";
1385
+ if (hostMachine === "mac" || hostMachine === "localhost")
1386
+ return macHostMachineId() ?? hostMachine;
1387
+ return hostMachine;
1388
+ }
1389
+ function getHomeDir() {
1390
+ const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
1391
+ if (!home)
1392
+ throw new Error("Could not resolve the user home directory");
1393
+ return home;
1394
+ }
1395
+ function getResolverDataRoot() {
1396
+ return dataDir({
1397
+ app: "economy",
1398
+ home: process.env["HOME"] || process.env["USERPROFILE"] || undefined
1399
+ });
1400
+ }
1401
+ function getLegacyDataRoot() {
1402
+ return join(getHomeDir(), ".hasna", "economy");
1403
+ }
1404
+ function adoptResolverDataRoot(resolved, env = process.env) {
1405
+ const dataOverride = env.HASNA_DATA_HOME;
1406
+ if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
1407
+ return true;
1408
+ return existsSync(join(resolved, "economy.db"));
1409
+ }
1410
+ function getExactDataRoot() {
1411
+ const dir = process.env["HASNA_ECONOMY_HOME"] ?? process.env["ECONOMY_HOME"];
1412
+ if (dir && dir.trim())
1413
+ return dir.trim();
1414
+ return;
1415
+ }
1416
+ function getDataDir() {
1417
+ const exact = getExactDataRoot();
1418
+ const effective = exact ? resolve(exact) : adoptResolverDataRoot(getResolverDataRoot()) ? resolve(getResolverDataRoot()) : getLegacyDataRoot();
1419
+ const oldDir = join(getHomeDir(), ".economy");
1420
+ if (existsSync(oldDir) && !existsSync(effective)) {
1421
+ mkdirSync(effective, { recursive: true });
1422
+ for (const file of readdirSync(oldDir)) {
1423
+ const oldPath = join(oldDir, file);
1424
+ if (statSync(oldPath).isFile()) {
1425
+ copyFileSync(oldPath, join(effective, file));
1426
+ }
1427
+ }
1428
+ }
1429
+ mkdirSync(effective, { recursive: true });
1430
+ return effective;
1431
+ }
1432
+ function getDbPath() {
1433
+ if (process.env["HASNA_ECONOMY_DB_PATH"])
1434
+ return process.env["HASNA_ECONOMY_DB_PATH"];
1435
+ if (process.env["ECONOMY_DB"])
1436
+ return process.env["ECONOMY_DB"];
1437
+ return join(getDataDir(), "economy.db");
1438
+ }
1439
+ function quoteSqlIdent(identifier) {
1440
+ return `"${identifier.replace(/"/g, '""')}"`;
1441
+ }
1442
+ function hasColumn(db, table, column) {
1443
+ const columns = db.prepare(`PRAGMA table_info(${quoteSqlIdent(table)})`).all();
1444
+ return columns.some((c) => c.name === column);
1445
+ }
1446
+ function addColumnIfMissing(db, table, column, definition) {
1447
+ if (hasColumn(db, table, column))
1448
+ return false;
1449
+ try {
1450
+ db.exec(`ALTER TABLE ${quoteSqlIdent(table)} ADD COLUMN ${quoteSqlIdent(column)} ${definition}`);
1451
+ return true;
1452
+ } catch (error) {
1453
+ const message = error instanceof Error ? error.message : String(error);
1454
+ if (/duplicate column name/i.test(message))
1455
+ return true;
1456
+ throw error;
1457
+ }
1458
+ }
1459
+ function initSchema(db) {
1460
+ db.exec(INGEST_SCHEMA_SQL);
1461
+ db.exec(`
1462
+ CREATE TABLE IF NOT EXISTS cost_centers (
1463
+ id TEXT PRIMARY KEY,
1464
+ kind TEXT NOT NULL,
1465
+ name TEXT NOT NULL,
1466
+ repo_path TEXT,
1467
+ labels_json TEXT DEFAULT '{}',
1468
+ created_at TEXT NOT NULL,
1469
+ updated_at TEXT NOT NULL
1470
+ );
1471
+
1472
+ CREATE TABLE IF NOT EXISTS requests (
1473
+ id TEXT PRIMARY KEY,
1474
+ agent TEXT NOT NULL,
1475
+ session_id TEXT NOT NULL,
1476
+ model TEXT NOT NULL,
1477
+ input_tokens INTEGER DEFAULT 0,
1478
+ output_tokens INTEGER DEFAULT 0,
1479
+ cache_read_tokens INTEGER DEFAULT 0,
1480
+ cache_create_tokens INTEGER DEFAULT 0,
1481
+ cache_create_5m_tokens INTEGER DEFAULT 0,
1482
+ cache_create_1h_tokens INTEGER DEFAULT 0,
1483
+ cost_usd REAL NOT NULL DEFAULT 0,
1484
+ duration_ms INTEGER DEFAULT 0,
1485
+ timestamp TEXT NOT NULL,
1486
+ source_request_id TEXT,
1487
+ machine_id TEXT DEFAULT '',
1488
+ cost_center_id TEXT,
1489
+ account_key TEXT DEFAULT '',
1490
+ account_tool TEXT DEFAULT '',
1491
+ account_name TEXT DEFAULT '',
1492
+ account_email TEXT DEFAULT '',
1493
+ account_source TEXT DEFAULT ''
1494
+ );
1495
+
1496
+ CREATE TABLE IF NOT EXISTS sessions (
1497
+ id TEXT PRIMARY KEY,
1498
+ agent TEXT NOT NULL,
1499
+ project_path TEXT DEFAULT '',
1500
+ project_name TEXT DEFAULT '',
1501
+ started_at TEXT NOT NULL,
1502
+ ended_at TEXT,
1503
+ total_cost_usd REAL DEFAULT 0,
1504
+ total_tokens INTEGER DEFAULT 0,
1505
+ request_count INTEGER DEFAULT 0,
1506
+ machine_id TEXT DEFAULT '',
1507
+ cost_center_id TEXT,
1508
+ account_key TEXT DEFAULT '',
1509
+ account_tool TEXT DEFAULT '',
1510
+ account_name TEXT DEFAULT '',
1511
+ account_email TEXT DEFAULT '',
1512
+ account_source TEXT DEFAULT ''
1513
+ );
1514
+
1515
+ CREATE TABLE IF NOT EXISTS projects (
1516
+ id TEXT PRIMARY KEY,
1517
+ path TEXT UNIQUE NOT NULL,
1518
+ name TEXT NOT NULL,
1519
+ description TEXT,
1520
+ tags TEXT DEFAULT '[]',
1521
+ created_at TEXT NOT NULL
1522
+ );
1523
+
1524
+ CREATE TABLE IF NOT EXISTS budgets (
1525
+ id TEXT PRIMARY KEY,
1526
+ project_path TEXT,
1527
+ agent TEXT,
1528
+ cost_center_id TEXT,
1529
+ period TEXT NOT NULL,
1530
+ limit_usd REAL NOT NULL,
1531
+ alert_at_percent INTEGER DEFAULT 80,
1532
+ created_at TEXT NOT NULL,
1533
+ updated_at TEXT NOT NULL
1534
+ );
1535
+
1536
+ CREATE TABLE IF NOT EXISTS goals (
1537
+ id TEXT PRIMARY KEY,
1538
+ period TEXT NOT NULL,
1539
+ project_path TEXT,
1540
+ agent TEXT,
1541
+ limit_usd REAL NOT NULL,
1542
+ created_at TEXT NOT NULL,
1543
+ updated_at TEXT NOT NULL
1544
+ );
1545
+
1546
+ CREATE TABLE IF NOT EXISTS ingest_state (
1547
+ source TEXT NOT NULL,
1548
+ key TEXT NOT NULL,
1549
+ value TEXT NOT NULL,
1550
+ PRIMARY KEY (source, key)
1551
+ );
1552
+
1553
+ CREATE INDEX IF NOT EXISTS idx_requests_session ON requests(session_id);
1554
+ CREATE INDEX IF NOT EXISTS idx_requests_timestamp ON requests(timestamp);
1555
+ CREATE INDEX IF NOT EXISTS idx_requests_agent ON requests(agent);
1556
+ CREATE INDEX IF NOT EXISTS idx_sessions_agent ON sessions(agent);
1557
+ CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_path);
1558
+ CREATE INDEX IF NOT EXISTS idx_sessions_started ON sessions(started_at);
1559
+ CREATE INDEX IF NOT EXISTS idx_cost_centers_kind ON cost_centers(kind);
1560
+
1561
+ CREATE TABLE IF NOT EXISTS model_pricing (
1562
+ model TEXT PRIMARY KEY,
1563
+ input_per_1m REAL NOT NULL DEFAULT 0,
1564
+ output_per_1m REAL NOT NULL DEFAULT 0,
1565
+ cache_read_per_1m REAL NOT NULL DEFAULT 0,
1566
+ cache_write_per_1m REAL NOT NULL DEFAULT 0,
1567
+ cache_write_1h_per_1m REAL NOT NULL DEFAULT 0,
1568
+ cache_storage_per_1m_hour REAL NOT NULL DEFAULT 0,
1569
+ updated_at TEXT NOT NULL
1570
+ );
1571
+
1572
+ CREATE TABLE IF NOT EXISTS feedback (
1573
+ id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
1574
+ message TEXT NOT NULL,
1575
+ email TEXT,
1576
+ category TEXT DEFAULT 'general',
1577
+ version TEXT,
1578
+ machine_id TEXT,
1579
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
1580
+ );
1581
+
1582
+ CREATE TABLE IF NOT EXISTS billing_daily (
1583
+ date TEXT NOT NULL,
1584
+ provider TEXT NOT NULL,
1585
+ description TEXT DEFAULT '',
1586
+ cost_usd REAL NOT NULL DEFAULT 0,
1587
+ updated_at TEXT NOT NULL,
1588
+ PRIMARY KEY (date, provider, description)
1589
+ );
1590
+
1591
+ CREATE INDEX IF NOT EXISTS idx_billing_date ON billing_daily(date);
1592
+ CREATE INDEX IF NOT EXISTS idx_billing_provider ON billing_daily(provider);
1593
+
1594
+ CREATE TABLE IF NOT EXISTS subscriptions (
1595
+ id TEXT PRIMARY KEY,
1596
+ agent TEXT,
1597
+ provider TEXT NOT NULL,
1598
+ plan TEXT NOT NULL,
1599
+ monthly_fee_usd REAL NOT NULL DEFAULT 0,
1600
+ included_usage_usd REAL NOT NULL DEFAULT 0,
1601
+ billing_cycle_start TEXT,
1602
+ reset_policy TEXT DEFAULT 'monthly',
1603
+ active INTEGER NOT NULL DEFAULT 1,
1604
+ created_at TEXT NOT NULL,
1605
+ updated_at TEXT NOT NULL
1606
+ );
1607
+
1608
+ CREATE TABLE IF NOT EXISTS usage_snapshots (
1609
+ id TEXT PRIMARY KEY,
1610
+ agent TEXT NOT NULL,
1611
+ date TEXT NOT NULL,
1612
+ metric TEXT NOT NULL,
1613
+ value REAL NOT NULL DEFAULT 0,
1614
+ unit TEXT DEFAULT '',
1615
+ machine_id TEXT DEFAULT '',
1616
+ updated_at TEXT NOT NULL
1617
+ );
1618
+
1619
+ CREATE TABLE IF NOT EXISTS savings_daily (
1620
+ date TEXT NOT NULL,
1621
+ agent TEXT DEFAULT '',
1622
+ api_equivalent_usd REAL NOT NULL DEFAULT 0,
1623
+ subscription_fee_usd REAL NOT NULL DEFAULT 0,
1624
+ included_consumed_usd REAL NOT NULL DEFAULT 0,
1625
+ on_demand_usd REAL NOT NULL DEFAULT 0,
1626
+ saved_usd REAL NOT NULL DEFAULT 0,
1627
+ updated_at TEXT NOT NULL,
1628
+ PRIMARY KEY (date, agent)
1629
+ );
1630
+
1631
+ CREATE TABLE IF NOT EXISTS machines (
1632
+ machine_id TEXT PRIMARY KEY,
1633
+ hostname TEXT NOT NULL,
1634
+ last_seen_at TEXT,
1635
+ last_push_at TEXT,
1636
+ last_pull_at TEXT,
1637
+ economy_version TEXT,
1638
+ updated_at TEXT NOT NULL
1639
+ );
1640
+
1641
+ CREATE INDEX IF NOT EXISTS idx_usage_agent_date ON usage_snapshots(agent, date);
1642
+ CREATE INDEX IF NOT EXISTS idx_savings_date ON savings_daily(date);
1643
+ `);
1644
+ addColumnIfMissing(db, "requests", "machine_id", `TEXT DEFAULT ''`);
1645
+ addColumnIfMissing(db, "sessions", "machine_id", `TEXT DEFAULT ''`);
1646
+ if (addColumnIfMissing(db, "cost_centers", "updated_at", `TEXT DEFAULT ''`)) {
1647
+ db.exec(`UPDATE cost_centers SET updated_at = created_at WHERE updated_at = '' OR updated_at IS NULL`);
1648
+ }
1649
+ addColumnIfMissing(db, "requests", "cost_center_id", `TEXT`);
1650
+ addColumnIfMissing(db, "sessions", "cost_center_id", `TEXT`);
1651
+ addColumnIfMissing(db, "budgets", "cost_center_id", `TEXT`);
1652
+ addColumnIfMissing(db, "budgets", "metric", `TEXT NOT NULL DEFAULT 'legacy_request_cost_usd' CHECK (metric IN ('legacy_request_cost_usd', 'metered_api_usd'))`);
1653
+ if (addColumnIfMissing(db, "requests", "cache_create_5m_tokens", "INTEGER DEFAULT 0")) {
1654
+ db.exec(`UPDATE requests SET cache_create_5m_tokens = cache_create_tokens WHERE cache_create_5m_tokens = 0`);
1655
+ }
1656
+ addColumnIfMissing(db, "requests", "cache_create_1h_tokens", "INTEGER DEFAULT 0");
1657
+ addColumnIfMissing(db, "requests", "cost_basis", `TEXT DEFAULT 'estimated'`);
1658
+ addColumnIfMissing(db, "requests", "attribution_tag", `TEXT DEFAULT ''`);
1659
+ if (addColumnIfMissing(db, "requests", "updated_at", `TEXT DEFAULT ''`)) {
1660
+ db.exec(`UPDATE requests SET updated_at = timestamp WHERE updated_at = '' OR updated_at IS NULL`);
1661
+ }
1662
+ addColumnIfMissing(db, "requests", "synced_at", `TEXT DEFAULT ''`);
1663
+ for (const column of ["account_key", "account_tool", "account_name", "account_email", "account_source"]) {
1664
+ addColumnIfMissing(db, "requests", column, `TEXT DEFAULT ''`);
1665
+ }
1666
+ addColumnIfMissing(db, "sessions", "attribution_tag", `TEXT DEFAULT ''`);
1667
+ if (addColumnIfMissing(db, "sessions", "updated_at", `TEXT DEFAULT ''`)) {
1668
+ db.exec(`UPDATE sessions SET updated_at = started_at WHERE updated_at = '' OR updated_at IS NULL`);
1669
+ }
1670
+ addColumnIfMissing(db, "sessions", "synced_at", `TEXT DEFAULT ''`);
1671
+ for (const column of ["account_key", "account_tool", "account_name", "account_email", "account_source"]) {
1672
+ addColumnIfMissing(db, "sessions", column, `TEXT DEFAULT ''`);
1673
+ }
1674
+ addColumnIfMissing(db, "model_pricing", "cache_write_1h_per_1m", "REAL NOT NULL DEFAULT 0");
1675
+ addColumnIfMissing(db, "model_pricing", "cache_storage_per_1m_hour", "REAL NOT NULL DEFAULT 0");
1676
+ db.exec(`
1677
+ CREATE INDEX IF NOT EXISTS idx_requests_machine ON requests(machine_id);
1678
+ CREATE INDEX IF NOT EXISTS idx_sessions_machine ON sessions(machine_id);
1679
+ CREATE INDEX IF NOT EXISTS idx_requests_account ON requests(account_key);
1680
+ CREATE INDEX IF NOT EXISTS idx_sessions_account ON sessions(account_key);
1681
+ CREATE INDEX IF NOT EXISTS idx_requests_cost_center ON requests(cost_center_id);
1682
+ CREATE INDEX IF NOT EXISTS idx_sessions_cost_center ON sessions(cost_center_id);
1683
+ CREATE INDEX IF NOT EXISTS idx_budgets_cost_center ON budgets(cost_center_id);
1684
+ CREATE INDEX IF NOT EXISTS idx_cost_centers_kind ON cost_centers(kind);
1685
+ `);
1686
+ for (const [table, columns] of Object.entries(OBSERVATION_COLUMNS)) {
1687
+ for (const [column, type] of Object.entries(columns))
1688
+ addColumnIfMissing(db, table, column, type);
1689
+ }
1690
+ db.exec(SCAN_RECEIPTS_SQL);
1691
+ db.exec(LEGACY_SQLITE_SQL);
1692
+ }
1693
+ function periodWhere(period, now = new Date) {
1694
+ return calendarPeriodWhere(period, "timestamp", now);
1695
+ }
1696
+ function sessionPeriodWhere(period) {
1697
+ return calendarPeriodWhere(period, "started_at");
1698
+ }
1699
+ function requestPeriodWhere(period) {
1700
+ return calendarPeriodWhere(period, "timestamp");
1701
+ }
1702
+ function upsertCostCenter(db, center) {
1703
+ const updatedAt = center.updated_at ?? center.created_at;
1704
+ db.prepare(`
1705
+ INSERT OR REPLACE INTO cost_centers
1706
+ (id, kind, name, repo_path, labels_json, created_at, updated_at)
1707
+ VALUES (?, ?, ?, ?, ?, ?, ?)
1708
+ `).run(center.id, center.kind, center.name, center.repo_path ?? null, center.labels_json ?? "{}", center.created_at, updatedAt);
1709
+ }
1710
+ function getCostCenter(db, id) {
1711
+ return db.prepare(`SELECT * FROM cost_centers WHERE id = ?`).get(id);
1712
+ }
1713
+ function upsertRequest(db, req) {
1714
+ const now = req.updated_at ?? req.timestamp;
1715
+ db.prepare(`
1716
+ INSERT OR REPLACE INTO requests
1717
+ (id, agent, session_id, model, input_tokens, output_tokens,
1718
+ cache_read_tokens, cache_create_tokens, cache_create_5m_tokens,
1719
+ cache_create_1h_tokens, cost_usd, cost_basis, duration_ms, timestamp,
1720
+ source_request_id, machine_id, cost_center_id, attribution_tag, account_key, account_tool,
1721
+ account_name, account_email, account_source, updated_at, synced_at)
1722
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1723
+ `).run(req.id, req.agent, req.session_id, req.model, req.input_tokens, req.output_tokens, req.cache_read_tokens, req.cache_create_tokens, req.cache_create_5m_tokens ?? req.cache_create_tokens, req.cache_create_1h_tokens ?? 0, req.cost_usd, req.cost_basis ?? "estimated", req.duration_ms, req.timestamp, req.source_request_id, req.machine_id ?? "", req.cost_center_id ?? null, req.attribution_tag ?? process.env["ECONOMY_TAG"] ?? "", req.account_key ?? "", req.account_tool ?? "", req.account_name ?? "", req.account_email ?? "", req.account_source ?? "", now, req.synced_at ?? "");
1724
+ }
1725
+ function upsertSession(db, session) {
1726
+ const now = session.updated_at ?? session.ended_at ?? session.started_at;
1727
+ db.prepare(`
1728
+ INSERT OR REPLACE INTO sessions
1729
+ (id, agent, project_path, project_name, started_at, ended_at,
1730
+ total_cost_usd, total_tokens, request_count, machine_id, attribution_tag,
1731
+ cost_center_id, account_key, account_tool, account_name, account_email, account_source, updated_at, synced_at)
1732
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1733
+ `).run(session.id, session.agent, session.project_path, session.project_name, session.started_at, session.ended_at ?? null, session.total_cost_usd, session.total_tokens, session.request_count, session.machine_id ?? "", session.attribution_tag ?? process.env["ECONOMY_TAG"] ?? "", session.cost_center_id ?? null, session.account_key ?? "", session.account_tool ?? "", session.account_name ?? "", session.account_email ?? "", session.account_source ?? "", now, session.synced_at ?? "");
1734
+ }
1735
+ function rollupSession(db, sessionId) {
1736
+ db.prepare(`
1737
+ UPDATE sessions SET
1738
+ total_cost_usd = (SELECT COALESCE(SUM(cost_usd), 0) FROM economy_effective_requests WHERE session_id = ?),
1739
+ total_tokens = (SELECT COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) FROM economy_effective_requests WHERE session_id = ?),
1740
+ request_count = (SELECT COUNT(*) FROM economy_effective_requests WHERE session_id = ?),
1741
+ ended_at = (SELECT MAX(timestamp) FROM economy_effective_requests WHERE session_id = ?),
1742
+ started_at = CASE WHEN started_at = '' OR started_at IS NULL
1743
+ THEN (SELECT MIN(timestamp) FROM economy_effective_requests WHERE session_id = ?)
1744
+ ELSE started_at END,
1745
+ account_key = CASE WHEN account_key = '' OR account_key IS NULL
1746
+ THEN COALESCE((SELECT account_key FROM economy_effective_requests WHERE session_id = ? AND account_key != '' ORDER BY timestamp DESC LIMIT 1), '')
1747
+ ELSE account_key END,
1748
+ account_tool = CASE WHEN account_tool = '' OR account_tool IS NULL
1749
+ THEN COALESCE((SELECT account_tool FROM economy_effective_requests WHERE session_id = ? AND account_tool != '' ORDER BY timestamp DESC LIMIT 1), '')
1750
+ ELSE account_tool END,
1751
+ account_name = CASE WHEN account_name = '' OR account_name IS NULL
1752
+ THEN COALESCE((SELECT account_name FROM economy_effective_requests WHERE session_id = ? AND account_name != '' ORDER BY timestamp DESC LIMIT 1), '')
1753
+ ELSE account_name END,
1754
+ account_email = CASE WHEN account_email = '' OR account_email IS NULL
1755
+ THEN COALESCE((SELECT account_email FROM economy_effective_requests WHERE session_id = ? AND account_email != '' ORDER BY timestamp DESC LIMIT 1), '')
1756
+ ELSE account_email END,
1757
+ account_source = CASE WHEN account_source = '' OR account_source IS NULL
1758
+ THEN COALESCE((SELECT account_source FROM economy_effective_requests WHERE session_id = ? AND account_source != '' ORDER BY timestamp DESC LIMIT 1), '')
1759
+ ELSE account_source END,
1760
+ cost_center_id = CASE WHEN cost_center_id = '' OR cost_center_id IS NULL
1761
+ THEN COALESCE((SELECT cost_center_id FROM economy_effective_requests WHERE session_id = ? AND cost_center_id IS NOT NULL AND cost_center_id != '' ORDER BY timestamp DESC LIMIT 1), NULL)
1762
+ ELSE cost_center_id END
1763
+ WHERE id = ?
1764
+ `).run(sessionId, sessionId, sessionId, sessionId, sessionId, sessionId, sessionId, sessionId, sessionId, sessionId, sessionId, sessionId);
1765
+ }
1766
+ function querySessions(db, filter = {}) {
1767
+ const conditions = [];
1768
+ const params = [];
1769
+ if (filter.agent) {
1770
+ conditions.push("agent = ?");
1771
+ params.push(filter.agent);
1772
+ }
1773
+ if (filter.project) {
1774
+ conditions.push("project_path LIKE ?");
1775
+ params.push(`%${filter.project}%`);
1776
+ }
1777
+ if (filter.account) {
1778
+ const q2 = `%${filter.account}%`;
1779
+ conditions.push("(account_key LIKE ? OR account_name LIKE ? OR account_email LIKE ?)");
1780
+ params.push(q2, q2, q2);
1781
+ }
1782
+ if (filter.since) {
1783
+ conditions.push("started_at >= ?");
1784
+ params.push(filter.since);
1785
+ }
1786
+ if (filter.machine) {
1787
+ conditions.push("machine_id = ?");
1788
+ params.push(filter.machine);
1789
+ }
1790
+ if (filter.search) {
1791
+ const q2 = `%${filter.search}%`;
1792
+ conditions.push("(project_name LIKE ? OR agent LIKE ? OR id LIKE ?)");
1793
+ params.push(q2, q2, `${filter.search}%`);
1794
+ }
1795
+ const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
1796
+ const limit = filter.limit ?? 50;
1797
+ const offset = filter.offset ?? 0;
1798
+ return db.prepare(`
1799
+ SELECT * FROM economy_effective_sessions ${where} ORDER BY started_at DESC LIMIT ? OFFSET ?
1800
+ `).all(...params, limit, offset);
1801
+ }
1802
+ function queryTopSessions(db, n = 10, agent, since) {
1803
+ const conditions = [];
1804
+ const params = [];
1805
+ if (agent) {
1806
+ conditions.push("agent = ?");
1807
+ params.push(agent);
1808
+ }
1809
+ if (since) {
1810
+ conditions.push("started_at >= ?");
1811
+ params.push(since);
1812
+ }
1813
+ const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
1814
+ return db.prepare(`SELECT * FROM economy_effective_sessions ${where} ORDER BY total_cost_usd DESC LIMIT ?`).all(...params, n);
1815
+ }
1816
+ function querySummary(db, period, machine, allMachines = false, agent) {
1817
+ const now = new Date;
1818
+ const rWhere = periodWhere(period, now);
1819
+ const sWhere = sessionOverlapWhere(period, "economy_effective_sessions", now);
1820
+ const machineClause = !allMachines && machine ? " AND machine_id = ?" : "";
1821
+ const agentClause = agent ? " AND agent = ?" : "";
1822
+ const params = [];
1823
+ if (!allMachines && machine)
1824
+ params.push(machine);
1825
+ if (agent)
1826
+ params.push(agent);
1827
+ const groups = db.prepare(`
1828
+ SELECT cost_basis, model, CASE WHEN cost_usd = 0 THEN 1 WHEN cost_usd < 0 THEN 2 ELSE 0 END AS zero_cost,
1829
+ SUM(cost_usd) AS cost_usd, COUNT(*) AS requests,
1830
+ COALESCE(SUM(input_tokens),0) AS input_tokens, COALESCE(SUM(output_tokens),0) AS output_tokens,
1831
+ COALESCE(SUM(cache_read_tokens),0) AS cache_read_tokens, COALESCE(SUM(cache_create_tokens),0) AS cache_create_tokens,
1832
+ COALESCE(SUM(cache_create_1h_tokens),0) AS cache_create_1h_tokens
1833
+ FROM economy_effective_requests WHERE ${rWhere}${machineClause}${agentClause}
1834
+ GROUP BY cost_basis, model, CASE WHEN cost_usd = 0 THEN 1 WHEN cost_usd < 0 THEN 2 ELSE 0 END
1835
+ `).all(...params);
1836
+ const sessionOnly = db.prepare(`
1837
+ SELECT COALESCE(SUM(total_cost_usd),0) AS cost_usd, COALESCE(SUM(total_tokens),0) AS tokens,
1838
+ COALESCE(SUM(request_count),0) AS requests, COUNT(*) AS sessions
1839
+ FROM economy_effective_sessions WHERE ${sWhere}${machineClause}${agentClause}
1840
+ AND NOT EXISTS (SELECT 1 FROM economy_effective_requests r WHERE r.session_id = economy_effective_sessions.id)
1841
+ `).get(...params);
1842
+ const requestSessions = db.prepare(`SELECT COUNT(DISTINCT session_id) AS sessions FROM economy_effective_requests WHERE ${rWhere}${machineClause}${agentClause}`).get(...params);
1843
+ return buildCostSummary(period, calendarPeriodBounds(period, now), groups, sessionOnly, requestSessions.sessions, listModelPricing(db));
1844
+ }
1845
+ function queryZeroCostTokenizedModels(db, limit = 10) {
1846
+ return db.prepare(`
1847
+ SELECT agent,
1848
+ model,
1849
+ COUNT(*) as requests,
1850
+ COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
1851
+ MAX(timestamp) as last_seen
1852
+ FROM economy_effective_requests
1853
+ WHERE cost_usd = 0
1854
+ AND (input_tokens > 0 OR output_tokens > 0 OR cache_read_tokens > 0 OR cache_create_tokens > 0)
1855
+ GROUP BY agent, model
1856
+ ORDER BY requests DESC, total_tokens DESC
1857
+ LIMIT ?
1858
+ `).all(limit);
1859
+ }
1860
+ function queryModelBreakdown(db) {
1861
+ return db.prepare(`
1862
+ SELECT model, agent,
1863
+ COUNT(*) as requests,
1864
+ COALESCE(SUM(input_tokens), 0) as input_tokens,
1865
+ COALESCE(SUM(output_tokens), 0) as output_tokens,
1866
+ COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
1867
+ COALESCE(SUM(cost_usd), 0) as cost_usd
1868
+ FROM economy_effective_requests GROUP BY model, agent ORDER BY cost_usd DESC
1869
+ `).all();
1870
+ }
1871
+ function queryAgentBreakdown(db, period = "all", machine) {
1872
+ const requestWhere = requestPeriodWhere(period);
1873
+ const machineClause = machine ? " AND machine_id = ?" : "";
1874
+ const machineParams = machine ? [machine] : [];
1875
+ const groups = new Map;
1876
+ const requestRows = db.prepare(`
1877
+ SELECT agent,
1878
+ COUNT(DISTINCT session_id) as sessions,
1879
+ COUNT(*) as requests,
1880
+ COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
1881
+ COALESCE(SUM(cost_usd), 0) as api_equivalent_usd,
1882
+ COALESCE(SUM(CASE WHEN cost_basis = 'metered_api' THEN cost_usd ELSE 0 END), 0) as metered_api_usd,
1883
+ COALESCE(SUM(CASE WHEN cost_basis = 'subscription_included' THEN cost_usd ELSE 0 END), 0) as subscription_included_usd,
1884
+ COALESCE(SUM(CASE WHEN COALESCE(cost_basis, 'estimated') = 'estimated' THEN cost_usd ELSE 0 END), 0) as estimated_usd,
1885
+ COALESCE(SUM(CASE WHEN cost_basis = 'unknown' THEN cost_usd ELSE 0 END), 0) as unknown_usd,
1886
+ COALESCE(SUM(CASE WHEN cost_basis = 'metered_api' THEN cost_usd ELSE 0 END), 0) as billable_usd,
1887
+ COALESCE(SUM(cost_usd), 0) as cost_usd,
1888
+ MAX(timestamp) as last_active
1889
+ FROM economy_effective_requests
1890
+ WHERE ${requestWhere}${machineClause}
1891
+ GROUP BY agent
1892
+ ORDER BY api_equivalent_usd DESC
1893
+ `).all(...machineParams);
1894
+ for (const row of requestRows) {
1895
+ groups.set(row.agent, row);
1896
+ }
1897
+ const sessionWhere = sessionPeriodWhere(period);
1898
+ const sessionOnlyRows = db.prepare(`
1899
+ SELECT agent,
1900
+ COUNT(*) as sessions,
1901
+ COALESCE(SUM(request_count), 0) as requests,
1902
+ COALESCE(SUM(total_tokens), 0) as total_tokens,
1903
+ COALESCE(SUM(total_cost_usd), 0) as cost_usd,
1904
+ MAX(started_at) as last_active
1905
+ FROM economy_effective_sessions
1906
+ WHERE ${sessionWhere}${machineClause}
1907
+ AND id NOT IN (SELECT DISTINCT session_id FROM economy_effective_requests)
1908
+ GROUP BY agent
1909
+ `).all(...machineParams);
1910
+ for (const row of sessionOnlyRows) {
1911
+ const existing = groups.get(row.agent) ?? {
1912
+ agent: row.agent,
1913
+ sessions: 0,
1914
+ requests: 0,
1915
+ total_tokens: 0,
1916
+ api_equivalent_usd: 0,
1917
+ billable_usd: 0,
1918
+ metered_api_usd: 0,
1919
+ subscription_included_usd: 0,
1920
+ estimated_usd: 0,
1921
+ unknown_usd: 0,
1922
+ cost_usd: 0,
1923
+ last_active: ""
1924
+ };
1925
+ existing.sessions += row.sessions;
1926
+ existing.requests += row.requests;
1927
+ existing.total_tokens += row.total_tokens;
1928
+ existing.api_equivalent_usd += row.cost_usd;
1929
+ existing.estimated_usd += row.cost_usd;
1930
+ existing.cost_usd += row.cost_usd;
1931
+ if (!existing.last_active || row.last_active > existing.last_active)
1932
+ existing.last_active = row.last_active;
1933
+ groups.set(row.agent, existing);
1934
+ }
1935
+ return [...groups.values()].sort((a, b) => b.api_equivalent_usd - a.api_equivalent_usd);
1936
+ }
1937
+ function pathProjectLabel(projectPath) {
1938
+ if (!projectPath)
1939
+ return "";
1940
+ const segments = projectPath.split("/").filter(Boolean);
1941
+ const projectPrefix = /^(open|skill|hook|service|connect|platform|agent|tool|iapp|project|scaffold|capp)-/;
1942
+ for (const seg of segments) {
1943
+ if (projectPrefix.test(seg))
1944
+ return seg;
1945
+ }
1946
+ const generic = new Set([
1947
+ "web",
1948
+ "app",
1949
+ "apps",
1950
+ "packages",
1951
+ "src",
1952
+ "lib",
1953
+ "server",
1954
+ "client",
1955
+ "api",
1956
+ "frontend",
1957
+ "backend",
1958
+ "home",
1959
+ "users",
1960
+ "workspace",
1961
+ "workspaces",
1962
+ "hasna"
1963
+ ]);
1964
+ for (let i = segments.length - 1;i >= 0; i--) {
1965
+ if (!generic.has(segments[i].toLowerCase()))
1966
+ return segments[i];
1967
+ }
1968
+ return null;
1969
+ }
1970
+ function isRepoLikeLabel(label) {
1971
+ return /^(open|skill|hook|service|connect|platform|agent|tool|iapp|project|scaffold|capp)-/.test(label) || label.includes("-");
1972
+ }
1973
+ function labelForPath(projectPath, projectName) {
1974
+ const pathLabel = pathProjectLabel(projectPath);
1975
+ if (pathLabel && (!projectName || projectName.trim() === "" || isRepoLikeLabel(pathLabel)))
1976
+ return pathLabel;
1977
+ if (projectName && projectName.trim() !== "")
1978
+ return projectName;
1979
+ if (pathLabel)
1980
+ return pathLabel;
1981
+ return projectPath;
1982
+ }
1983
+ function groupKeyForPath(projectPath, projectName) {
1984
+ return labelForPath(projectPath, projectName).trim().toLowerCase();
1985
+ }
1986
+ function laterTimestamp(current, candidate) {
1987
+ if (!candidate)
1988
+ return current;
1989
+ return candidate > current ? candidate : current;
1990
+ }
1991
+ function queryProjectBreakdownScoped(db, requestWhere, sessionWhere, requestScopeParams = [], sessionScopeParams = [], machine) {
1992
+ const sessionMachineClause = machine ? " AND (machine_id = ? OR id IN (SELECT DISTINCT session_id FROM economy_effective_requests WHERE machine_id = ?))" : "";
1993
+ const requestMachineClause = machine ? " AND machine_id = ?" : "";
1994
+ const sessionMachineParams = machine ? [machine, machine] : [];
1995
+ const requestMachineParams = machine ? [machine] : [];
1996
+ const sessionOnlyMachineClause = machine ? " AND machine_id = ?" : "";
1997
+ const sessionOnlyMachineParams = machine ? [machine] : [];
1998
+ const sessions = db.prepare(`
1999
+ SELECT id, project_path, project_name
2000
+ FROM economy_effective_sessions
2001
+ WHERE (project_path != '' OR project_name != '')${sessionMachineClause}
2002
+ `).all(...sessionMachineParams);
2003
+ const groups = new Map;
2004
+ const groupKeyBySession = new Map;
2005
+ for (const s of sessions) {
2006
+ const label = labelForPath(s.project_path, s.project_name);
2007
+ if (!label)
2008
+ continue;
2009
+ const key = groupKeyForPath(s.project_path, s.project_name);
2010
+ const g = groups.get(key) ?? { label, samplePath: s.project_path, sessions: 0, requests: 0, total_tokens: 0, cost_usd: 0, last_active: "" };
2011
+ if (!g.samplePath)
2012
+ g.samplePath = s.project_path;
2013
+ groups.set(key, g);
2014
+ groupKeyBySession.set(s.id, key);
2015
+ }
2016
+ const groupFor = (sessionId) => {
2017
+ const key = groupKeyBySession.get(sessionId);
2018
+ return key === undefined ? undefined : groups.get(key);
2019
+ };
2020
+ const requestRows = db.prepare(`
2021
+ SELECT session_id,
2022
+ COUNT(*) as requests,
2023
+ COALESCE(SUM(cost_usd), 0) as cost_usd,
2024
+ COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
2025
+ MAX(timestamp) as last_active
2026
+ FROM economy_effective_requests
2027
+ WHERE ${requestWhere}${requestMachineClause}
2028
+ GROUP BY session_id
2029
+ `).all(...requestScopeParams, ...requestMachineParams);
2030
+ for (const row of requestRows) {
2031
+ const g = groupFor(row.session_id);
2032
+ if (!g)
2033
+ continue;
2034
+ g.sessions += 1;
2035
+ g.requests += row.requests;
2036
+ g.cost_usd += row.cost_usd;
2037
+ g.total_tokens += row.total_tokens;
2038
+ g.last_active = laterTimestamp(g.last_active, row.last_active);
2039
+ }
2040
+ const sessionsWithRequests = new Set(db.prepare(`SELECT DISTINCT session_id FROM economy_effective_requests`).all().map((row) => row.session_id));
2041
+ const sessionOnlyRows = db.prepare(`
2042
+ SELECT id,
2043
+ COALESCE(request_count, 0) as request_count,
2044
+ COALESCE(total_tokens, 0) as total_tokens,
2045
+ COALESCE(total_cost_usd, 0) as total_cost_usd,
2046
+ started_at
2047
+ FROM economy_effective_sessions
2048
+ WHERE ${sessionWhere}${sessionOnlyMachineClause}
2049
+ `).all(...sessionScopeParams, ...sessionOnlyMachineParams);
2050
+ for (const row of sessionOnlyRows) {
2051
+ if (sessionsWithRequests.has(row.id))
2052
+ continue;
2053
+ const g = groupFor(row.id);
2054
+ if (!g)
2055
+ continue;
2056
+ g.sessions += 1;
2057
+ g.requests += row.request_count;
2058
+ g.total_tokens += row.total_tokens;
2059
+ g.cost_usd += row.total_cost_usd;
2060
+ g.last_active = laterTimestamp(g.last_active, row.started_at);
2061
+ }
2062
+ const result = [];
2063
+ for (const g of groups.values()) {
2064
+ if (g.sessions === 0)
2065
+ continue;
2066
+ result.push({
2067
+ project_path: g.samplePath,
2068
+ project_name: g.label,
2069
+ sessions: g.sessions,
2070
+ requests: g.requests,
2071
+ total_tokens: g.total_tokens,
2072
+ cost_usd: g.cost_usd,
2073
+ last_active: g.last_active
2074
+ });
2075
+ }
2076
+ result.sort((a, b) => b.cost_usd - a.cost_usd);
2077
+ return result;
2078
+ }
2079
+ function queryProjectBreakdown(db, period = "all", machine) {
2080
+ return queryProjectBreakdownScoped(db, requestPeriodWhere(period), sessionPeriodWhere(period), [], [], machine);
2081
+ }
2082
+ function normalizeAccountEmail(email) {
2083
+ return (email ?? "").trim().toLowerCase();
2084
+ }
2085
+ function accountIdentityKey(agent, accountKey, accountName, accountEmail) {
2086
+ const identityAgent = (agent || "").trim();
2087
+ const normalizedEmail = normalizeAccountEmail(accountEmail);
2088
+ if (identityAgent && normalizedEmail)
2089
+ return `${identityAgent}:${normalizedEmail}`;
2090
+ if (identityAgent && accountName)
2091
+ return `${identityAgent}:${accountName}`;
2092
+ if (accountKey)
2093
+ return accountKey;
2094
+ return identityAgent ? `${identityAgent}:unknown` : "";
2095
+ }
2096
+ function addAccountBreakdownRow(groups, row, sessionOnly) {
2097
+ const agent = row.agent || row.account_tool;
2098
+ const email = normalizeAccountEmail(row.account_email);
2099
+ const accountName = row.account_name || email || row.account_key;
2100
+ const key = accountIdentityKey(agent, row.account_key, accountName, email);
2101
+ if (!key)
2102
+ return;
2103
+ const group = groups.get(key) ?? {
2104
+ account_key: key,
2105
+ account_tool: agent,
2106
+ account_name: accountName,
2107
+ account_email: email || null,
2108
+ account_source: row.account_source || "unknown",
2109
+ sessionIds: new Set,
2110
+ requests: 0,
2111
+ total_tokens: 0,
2112
+ api_equivalent_usd: 0,
2113
+ metered_api_usd: 0,
2114
+ subscription_included_usd: 0,
2115
+ estimated_usd: 0,
2116
+ unknown_usd: 0,
2117
+ last_active: ""
2118
+ };
2119
+ if (!group.account_email && email)
2120
+ group.account_email = email;
2121
+ if (!group.account_name && accountName)
2122
+ group.account_name = accountName;
2123
+ if ((!group.account_source || group.account_source === "unknown") && row.account_source && row.account_source !== "unknown") {
2124
+ group.account_source = row.account_source;
2125
+ }
2126
+ if (row.session_id)
2127
+ group.sessionIds.add(row.session_id);
2128
+ group.requests += row.requests;
2129
+ group.total_tokens += row.total_tokens;
2130
+ group.api_equivalent_usd += row.cost_usd;
2131
+ if (sessionOnly) {
2132
+ group.estimated_usd += row.cost_usd;
2133
+ } else if (row.cost_basis === "metered_api") {
2134
+ group.metered_api_usd += row.cost_usd;
2135
+ } else if (row.cost_basis === "subscription_included") {
2136
+ group.subscription_included_usd += row.cost_usd;
2137
+ } else if (row.cost_basis === "unknown") {
2138
+ group.unknown_usd += row.cost_usd;
2139
+ } else {
2140
+ group.estimated_usd += row.cost_usd;
2141
+ }
2142
+ if (!group.last_active || row.last_active > group.last_active)
2143
+ group.last_active = row.last_active;
2144
+ groups.set(key, group);
2145
+ }
2146
+ function queryAccountBreakdown(db, period = "all", machine) {
2147
+ const requestWhere = requestPeriodWhere(period);
2148
+ const sessionWhere = sessionPeriodWhere(period);
2149
+ const requestMachineClause = machine ? " AND r.machine_id = ?" : "";
2150
+ const sessionMachineClause = machine ? " AND s.machine_id = ?" : "";
2151
+ const requestMachineParams = machine ? [machine] : [];
2152
+ const sessionMachineParams = machine ? [machine] : [];
2153
+ const groups = new Map;
2154
+ const requestRows = db.prepare(`
2155
+ SELECT
2156
+ r.session_id as session_id,
2157
+ COALESCE(NULLIF(r.agent, ''), NULLIF(s.agent, ''), '') as agent,
2158
+ COALESCE(NULLIF(r.account_key, ''), NULLIF(s.account_key, ''), '') as account_key,
2159
+ COALESCE(NULLIF(r.account_tool, ''), NULLIF(s.account_tool, ''), '') as account_tool,
2160
+ COALESCE(NULLIF(r.account_name, ''), NULLIF(s.account_name, ''), '') as account_name,
2161
+ COALESCE(NULLIF(r.account_email, ''), NULLIF(s.account_email, ''), '') as account_email,
2162
+ COALESCE(NULLIF(r.account_source, ''), NULLIF(s.account_source, ''), 'unknown') as account_source,
2163
+ 1 as requests,
2164
+ COALESCE(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens, 0) as total_tokens,
2165
+ COALESCE(r.cost_usd, 0) as cost_usd,
2166
+ COALESCE(NULLIF(r.cost_basis, ''), 'estimated') as cost_basis,
2167
+ r.timestamp as last_active
2168
+ FROM economy_effective_requests r
2169
+ LEFT JOIN economy_effective_sessions s ON s.id = r.session_id
2170
+ WHERE ${requestWhere}${requestMachineClause}
2171
+ AND (
2172
+ COALESCE(NULLIF(r.account_key, ''), NULLIF(s.account_key, ''), '') != ''
2173
+ OR COALESCE(NULLIF(r.account_tool, ''), NULLIF(s.account_tool, ''), '') != ''
2174
+ OR COALESCE(NULLIF(r.account_name, ''), NULLIF(s.account_name, ''), '') != ''
2175
+ OR COALESCE(NULLIF(r.account_email, ''), NULLIF(s.account_email, ''), '') != ''
2176
+ )
2177
+ `).all(...requestMachineParams);
2178
+ for (const row of requestRows)
2179
+ addAccountBreakdownRow(groups, row, false);
2180
+ const sessionOnlyRows = db.prepare(`
2181
+ SELECT
2182
+ s.id as session_id,
2183
+ s.agent as agent,
2184
+ s.account_key as account_key,
2185
+ s.account_tool as account_tool,
2186
+ s.account_name as account_name,
2187
+ s.account_email as account_email,
2188
+ COALESCE(NULLIF(s.account_source, ''), 'unknown') as account_source,
2189
+ COALESCE(s.request_count, 0) as requests,
2190
+ COALESCE(s.total_tokens, 0) as total_tokens,
2191
+ COALESCE(s.total_cost_usd, 0) as cost_usd,
2192
+ 'estimated' as cost_basis,
2193
+ s.started_at as last_active
2194
+ FROM economy_effective_sessions s
2195
+ WHERE ${sessionWhere}${sessionMachineClause}
2196
+ AND s.id NOT IN (SELECT DISTINCT session_id FROM economy_effective_requests)
2197
+ AND (s.account_key != '' OR s.account_tool != '' OR s.account_name != '' OR s.account_email != '')
2198
+ `).all(...sessionMachineParams);
2199
+ for (const row of sessionOnlyRows)
2200
+ addAccountBreakdownRow(groups, row, true);
2201
+ const result = [...groups.values()].map((group) => ({
2202
+ account_key: group.account_key,
2203
+ account_tool: group.account_tool,
2204
+ account_name: group.account_name,
2205
+ account_email: group.account_email,
2206
+ account_source: group.account_source,
2207
+ sessions: group.sessionIds.size,
2208
+ requests: group.requests,
2209
+ total_tokens: group.total_tokens,
2210
+ api_equivalent_usd: group.api_equivalent_usd,
2211
+ billable_usd: group.metered_api_usd,
2212
+ metered_api_usd: group.metered_api_usd,
2213
+ subscription_included_usd: group.subscription_included_usd,
2214
+ estimated_usd: group.estimated_usd,
2215
+ unknown_usd: group.unknown_usd,
2216
+ cost_usd: group.api_equivalent_usd,
2217
+ last_active: group.last_active
2218
+ }));
2219
+ result.sort((a, b) => b.cost_usd - a.cost_usd);
2220
+ return result;
2221
+ }
2222
+ function kindFromCostCenterId(id) {
2223
+ const prefix = id.split(":", 1)[0] ?? "";
2224
+ return ["loop", "app", "repo", "service", "team"].includes(prefix) ? prefix : "";
2225
+ }
2226
+ function addCostCenterBreakdownRow(groups, row, sessionOnly) {
2227
+ if (!row.cost_center_id)
2228
+ return;
2229
+ const derivedKind = row.kind || kindFromCostCenterId(row.cost_center_id) || "service";
2230
+ const group = groups.get(row.cost_center_id) ?? {
2231
+ cost_center_id: row.cost_center_id,
2232
+ kind: derivedKind,
2233
+ name: row.name || row.cost_center_id,
2234
+ repo_path: row.repo_path ?? null,
2235
+ labels_json: row.labels_json || "{}",
2236
+ sessionIds: new Set,
2237
+ requests: 0,
2238
+ total_tokens: 0,
2239
+ api_equivalent_usd: 0,
2240
+ metered_api_usd: 0,
2241
+ subscription_included_usd: 0,
2242
+ estimated_usd: 0,
2243
+ unknown_usd: 0,
2244
+ last_active: ""
2245
+ };
2246
+ group.kind = derivedKind;
2247
+ if (row.name)
2248
+ group.name = row.name;
2249
+ if (row.repo_path && !group.repo_path)
2250
+ group.repo_path = row.repo_path;
2251
+ if (row.labels_json && row.labels_json !== "{}")
2252
+ group.labels_json = row.labels_json;
2253
+ if (row.session_id)
2254
+ group.sessionIds.add(row.session_id);
2255
+ group.requests += row.requests;
2256
+ group.total_tokens += row.total_tokens;
2257
+ group.api_equivalent_usd += row.cost_usd;
2258
+ if (sessionOnly) {
2259
+ group.estimated_usd += row.cost_usd;
2260
+ } else if (row.cost_basis === "metered_api") {
2261
+ group.metered_api_usd += row.cost_usd;
2262
+ } else if (row.cost_basis === "subscription_included") {
2263
+ group.subscription_included_usd += row.cost_usd;
2264
+ } else if (row.cost_basis === "unknown") {
2265
+ group.unknown_usd += row.cost_usd;
2266
+ } else {
2267
+ group.estimated_usd += row.cost_usd;
2268
+ }
2269
+ if (!group.last_active || row.last_active > group.last_active)
2270
+ group.last_active = row.last_active;
2271
+ groups.set(row.cost_center_id, group);
2272
+ }
2273
+ function queryCostCenterBreakdown(db, period = "all", filter = {}) {
2274
+ const requestWhere = filter.since ? "r.timestamp >= ?" : requestPeriodWhere(period);
2275
+ const sessionWhere = filter.since ? "s.started_at >= ?" : sessionPeriodWhere(period);
2276
+ const requestMachineClause = filter.machine ? " AND r.machine_id = ?" : "";
2277
+ const sessionMachineClause = filter.machine ? " AND s.machine_id = ?" : "";
2278
+ const requestParams = [];
2279
+ const sessionParams = [];
2280
+ if (filter.since) {
2281
+ requestParams.push(filter.since);
2282
+ sessionParams.push(filter.since);
2283
+ }
2284
+ if (filter.machine) {
2285
+ requestParams.push(filter.machine);
2286
+ sessionParams.push(filter.machine);
2287
+ }
2288
+ const groups = new Map;
2289
+ const requestRows = db.prepare(`
2290
+ SELECT
2291
+ COALESCE(NULLIF(r.cost_center_id, ''), NULLIF(s.cost_center_id, '')) as cost_center_id,
2292
+ COALESCE(cc.kind, '') as kind,
2293
+ COALESCE(cc.name, COALESCE(NULLIF(r.cost_center_id, ''), NULLIF(s.cost_center_id, ''))) as name,
2294
+ cc.repo_path as repo_path,
2295
+ COALESCE(cc.labels_json, '{}') as labels_json,
2296
+ r.session_id as session_id,
2297
+ 1 as requests,
2298
+ COALESCE(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens, 0) as total_tokens,
2299
+ COALESCE(r.cost_usd, 0) as cost_usd,
2300
+ COALESCE(NULLIF(r.cost_basis, ''), 'estimated') as cost_basis,
2301
+ r.timestamp as last_active
2302
+ FROM economy_effective_requests r
2303
+ LEFT JOIN economy_effective_sessions s ON s.id = r.session_id
2304
+ LEFT JOIN cost_centers cc ON cc.id = COALESCE(NULLIF(r.cost_center_id, ''), NULLIF(s.cost_center_id, ''))
2305
+ WHERE ${requestWhere}${requestMachineClause}
2306
+ AND COALESCE(NULLIF(r.cost_center_id, ''), NULLIF(s.cost_center_id, '')) IS NOT NULL
2307
+ AND COALESCE(NULLIF(r.cost_center_id, ''), NULLIF(s.cost_center_id, '')) != ''
2308
+ `).all(...requestParams);
2309
+ for (const row of requestRows)
2310
+ addCostCenterBreakdownRow(groups, row, false);
2311
+ const sessionOnlyRows = db.prepare(`
2312
+ SELECT
2313
+ s.cost_center_id as cost_center_id,
2314
+ COALESCE(cc.kind, '') as kind,
2315
+ COALESCE(cc.name, s.cost_center_id) as name,
2316
+ cc.repo_path as repo_path,
2317
+ COALESCE(cc.labels_json, '{}') as labels_json,
2318
+ s.id as session_id,
2319
+ COALESCE(s.request_count, 0) as requests,
2320
+ COALESCE(s.total_tokens, 0) as total_tokens,
2321
+ COALESCE(s.total_cost_usd, 0) as cost_usd,
2322
+ 'estimated' as cost_basis,
2323
+ s.started_at as last_active
2324
+ FROM economy_effective_sessions s
2325
+ LEFT JOIN cost_centers cc ON cc.id = s.cost_center_id
2326
+ WHERE ${sessionWhere}${sessionMachineClause}
2327
+ AND s.id NOT IN (SELECT DISTINCT session_id FROM economy_effective_requests)
2328
+ AND s.cost_center_id IS NOT NULL
2329
+ AND s.cost_center_id != ''
2330
+ `).all(...sessionParams);
2331
+ for (const row of sessionOnlyRows)
2332
+ addCostCenterBreakdownRow(groups, row, true);
2333
+ const result = [...groups.values()].map((group) => ({
2334
+ cost_center_id: group.cost_center_id,
2335
+ kind: group.kind,
2336
+ name: group.name,
2337
+ repo_path: group.repo_path,
2338
+ labels_json: group.labels_json,
2339
+ sessions: group.sessionIds.size,
2340
+ requests: group.requests,
2341
+ total_tokens: group.total_tokens,
2342
+ api_equivalent_usd: group.api_equivalent_usd,
2343
+ billable_usd: group.metered_api_usd,
2344
+ metered_api_usd: group.metered_api_usd,
2345
+ subscription_included_usd: group.subscription_included_usd,
2346
+ estimated_usd: group.estimated_usd,
2347
+ unknown_usd: group.unknown_usd,
2348
+ cost_usd: group.api_equivalent_usd,
2349
+ last_active: group.last_active
2350
+ }));
2351
+ const filtered = filter.kind ? result.filter((row) => row.kind === filter.kind) : result;
2352
+ filtered.sort((a, b) => b.cost_usd - a.cost_usd);
2353
+ return filtered;
2354
+ }
2355
+ function queryProjectBreakdownSince(db, since, machine) {
2356
+ return queryProjectBreakdownScoped(db, "timestamp >= ?", "started_at >= ?", [since], [since], machine);
2357
+ }
2358
+ function queryAgentBreakdownSince(db, since) {
2359
+ return db.prepare(`
2360
+ SELECT agent,
2361
+ COUNT(DISTINCT session_id) as sessions,
2362
+ COUNT(*) as requests,
2363
+ COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
2364
+ COALESCE(SUM(cost_usd), 0) as api_equivalent_usd,
2365
+ COALESCE(SUM(CASE WHEN cost_basis = 'metered_api' THEN cost_usd ELSE 0 END), 0) as billable_usd,
2366
+ COALESCE(SUM(CASE WHEN cost_basis = 'subscription_included' THEN cost_usd ELSE 0 END), 0) as subscription_included_usd,
2367
+ MAX(timestamp) as last_active
2368
+ FROM economy_effective_requests
2369
+ WHERE timestamp >= ?
2370
+ GROUP BY agent
2371
+ ORDER BY api_equivalent_usd DESC
2372
+ `).all(since);
2373
+ }
2374
+ function queryModelBreakdownSince(db, since) {
2375
+ return db.prepare(`
2376
+ SELECT model, agent,
2377
+ COUNT(*) as requests,
2378
+ COALESCE(SUM(input_tokens), 0) as input_tokens,
2379
+ COALESCE(SUM(output_tokens), 0) as output_tokens,
2380
+ COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
2381
+ COALESCE(SUM(cost_usd), 0) as cost_usd
2382
+ FROM economy_effective_requests WHERE timestamp >= ?
2383
+ GROUP BY model, agent ORDER BY cost_usd DESC
2384
+ `).all(since);
2385
+ }
2386
+ function queryAccountBreakdownSince(db, since) {
2387
+ return db.prepare(`
2388
+ WITH request_rows AS (
2389
+ SELECT
2390
+ r.session_id as session_id,
2391
+ COALESCE(NULLIF(r.agent, ''), NULLIF(s.agent, ''), NULLIF(r.account_tool, ''), NULLIF(s.account_tool, ''), 'unknown') as account_agent,
2392
+ COALESCE(NULLIF(r.account_key, ''), NULLIF(s.account_key, ''), '') as raw_account_key,
2393
+ COALESCE(NULLIF(r.account_name, ''), NULLIF(s.account_name, ''), '') as raw_account_name,
2394
+ LOWER(TRIM(COALESCE(NULLIF(r.account_email, ''), NULLIF(s.account_email, ''), ''))) as raw_account_email,
2395
+ COALESCE(NULLIF(r.account_source, ''), NULLIF(s.account_source, ''), 'unknown') as account_source,
2396
+ 1 as requests,
2397
+ COALESCE(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens, 0) as total_tokens,
2398
+ COALESCE(r.cost_usd, 0) as cost_usd,
2399
+ COALESCE(NULLIF(r.cost_basis, ''), 'estimated') as cost_basis,
2400
+ r.timestamp as last_active
2401
+ FROM economy_effective_requests r
2402
+ LEFT JOIN economy_effective_sessions s ON s.id = r.session_id
2403
+ WHERE r.timestamp >= ?
2404
+ AND (
2405
+ COALESCE(NULLIF(r.account_key, ''), NULLIF(s.account_key, ''), '') != ''
2406
+ OR COALESCE(NULLIF(r.account_tool, ''), NULLIF(s.account_tool, ''), '') != ''
2407
+ OR COALESCE(NULLIF(r.account_name, ''), NULLIF(s.account_name, ''), '') != ''
2408
+ OR COALESCE(NULLIF(r.account_email, ''), NULLIF(s.account_email, ''), '') != ''
2409
+ )
2410
+ ),
2411
+ session_only_rows AS (
2412
+ SELECT
2413
+ s.id as session_id,
2414
+ COALESCE(NULLIF(s.agent, ''), NULLIF(s.account_tool, ''), 'unknown') as account_agent,
2415
+ s.account_key as raw_account_key,
2416
+ s.account_name as raw_account_name,
2417
+ LOWER(TRIM(COALESCE(s.account_email, ''))) as raw_account_email,
2418
+ COALESCE(NULLIF(s.account_source, ''), 'unknown') as account_source,
2419
+ COALESCE(s.request_count, 0) as requests,
2420
+ COALESCE(s.total_tokens, 0) as total_tokens,
2421
+ COALESCE(s.total_cost_usd, 0) as cost_usd,
2422
+ 'estimated' as cost_basis,
2423
+ s.started_at as last_active
2424
+ FROM economy_effective_sessions s
2425
+ WHERE s.started_at >= ?
2426
+ AND s.id NOT IN (SELECT DISTINCT session_id FROM economy_effective_requests)
2427
+ AND (s.account_key != '' OR s.account_tool != '' OR s.account_name != '' OR s.account_email != '')
2428
+ ),
2429
+ normalized AS (
2430
+ SELECT
2431
+ CASE
2432
+ WHEN raw_account_email != '' THEN account_agent || ':' || raw_account_email
2433
+ WHEN raw_account_name != '' THEN account_agent || ':' || raw_account_name
2434
+ ELSE raw_account_key
2435
+ END as account_key,
2436
+ account_agent as account_tool,
2437
+ raw_account_name as account_name,
2438
+ raw_account_email as account_email,
2439
+ account_source,
2440
+ session_id,
2441
+ requests,
2442
+ total_tokens,
2443
+ cost_usd,
2444
+ cost_basis,
2445
+ last_active
2446
+ FROM request_rows
2447
+ UNION ALL
2448
+ SELECT
2449
+ CASE
2450
+ WHEN raw_account_email != '' THEN account_agent || ':' || raw_account_email
2451
+ WHEN raw_account_name != '' THEN account_agent || ':' || raw_account_name
2452
+ ELSE raw_account_key
2453
+ END as account_key,
2454
+ account_agent as account_tool,
2455
+ raw_account_name as account_name,
2456
+ raw_account_email as account_email,
2457
+ account_source,
2458
+ session_id,
2459
+ requests,
2460
+ total_tokens,
2461
+ cost_usd,
2462
+ cost_basis,
2463
+ last_active
2464
+ FROM session_only_rows
2465
+ )
2466
+ SELECT account_key,
2467
+ account_tool,
2468
+ COALESCE(MAX(NULLIF(account_name, '')), MAX(NULLIF(account_email, '')), account_key) as account_name,
2469
+ NULLIF(account_email, '') as account_email,
2470
+ COALESCE(MAX(NULLIF(account_source, 'unknown')), 'unknown') as account_source,
2471
+ COUNT(DISTINCT session_id) as sessions,
2472
+ COALESCE(SUM(requests), 0) as requests,
2473
+ COALESCE(SUM(total_tokens), 0) as total_tokens,
2474
+ COALESCE(SUM(cost_usd), 0) as api_equivalent_usd,
2475
+ COALESCE(SUM(CASE WHEN cost_basis = 'metered_api' THEN cost_usd ELSE 0 END), 0) as billable_usd,
2476
+ COALESCE(SUM(CASE WHEN cost_basis = 'metered_api' THEN cost_usd ELSE 0 END), 0) as metered_api_usd,
2477
+ COALESCE(SUM(CASE WHEN cost_basis = 'subscription_included' THEN cost_usd ELSE 0 END), 0) as subscription_included_usd,
2478
+ COALESCE(SUM(CASE WHEN cost_basis NOT IN ('metered_api', 'subscription_included', 'unknown') THEN cost_usd ELSE 0 END), 0) as estimated_usd,
2479
+ COALESCE(SUM(CASE WHEN cost_basis = 'unknown' THEN cost_usd ELSE 0 END), 0) as unknown_usd,
2480
+ COALESCE(SUM(cost_usd), 0) as cost_usd,
2481
+ MAX(last_active) as last_active
2482
+ FROM normalized
2483
+ WHERE account_key != ''
2484
+ GROUP BY account_key, account_tool, account_email
2485
+ ORDER BY api_equivalent_usd DESC
2486
+ `).all(since, since);
2487
+ }
2488
+ function queryDailyBreakdown(db, days = 30, machine) {
2489
+ const machineClause = machine ? " AND machine_id = ?" : "";
2490
+ const params = machine ? [`-${days}`, machine] : [`-${days}`];
2491
+ return db.prepare(`
2492
+ SELECT DATE(timestamp) as date, agent, COALESCE(SUM(cost_usd), 0) as cost_usd
2493
+ FROM economy_effective_requests
2494
+ WHERE timestamp >= DATE('now', ? || ' days')${machineClause}
2495
+ GROUP BY DATE(timestamp), agent
2496
+ ORDER BY date ASC
2497
+ `).all(...params);
2498
+ }
2499
+ function queryHourlyBreakdown(db, machine, hours) {
2500
+ const clauses = hours == null ? [`DATE(timestamp) = DATE('now')`] : [`DATETIME(timestamp) >= DATETIME('now', ?)`];
2501
+ const params = hours == null ? [] : [`-${hours} hours`];
2502
+ if (machine) {
2503
+ clauses.push("machine_id = ?");
2504
+ params.push(machine);
2505
+ }
2506
+ return db.prepare(`
2507
+ SELECT STRFTIME('%H', timestamp) as hour, agent, COALESCE(SUM(cost_usd), 0) as cost_usd
2508
+ FROM economy_effective_requests
2509
+ WHERE ${clauses.join(" AND ")}
2510
+ GROUP BY STRFTIME('%H', timestamp), agent
2511
+ ORDER BY hour ASC
2512
+ `).all(...params);
2513
+ }
2514
+ function upsertProject(db, project) {
2515
+ db.prepare(`
2516
+ INSERT OR REPLACE INTO projects (id, path, name, description, tags, created_at)
2517
+ VALUES (?, ?, ?, ?, ?, ?)
2518
+ `).run(project.id, project.path, project.name, project.description ?? null, JSON.stringify(project.tags), project.created_at);
2519
+ }
2520
+ function getProject(db, path) {
2521
+ const row = db.prepare(`SELECT * FROM projects WHERE path = ?`).get(path);
2522
+ if (!row)
2523
+ return null;
2524
+ return { ...row, tags: JSON.parse(row["tags"] ?? "[]") };
2525
+ }
2526
+ function listProjects(db) {
2527
+ return db.prepare(`SELECT * FROM projects ORDER BY created_at DESC`).all().map((row) => ({ ...row, tags: JSON.parse(row["tags"] ?? "[]") }));
2528
+ }
2529
+ function deleteProject(db, path) {
2530
+ db.prepare(`DELETE FROM projects WHERE path = ?`).run(path);
2531
+ }
2532
+ function upsertBudget(db, budget) {
2533
+ if (budget.metric && budget.metric !== "legacy_request_cost_usd")
2534
+ throw new IngestError(409, "INGEST_CONFLICT", "budget.metric", "metered API rules require the explicit ingest create command");
2535
+ const result = db.prepare(`
2536
+ INSERT INTO budgets
2537
+ (id, project_path, agent, cost_center_id, period, limit_usd, alert_at_percent, created_at, updated_at)
2538
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
2539
+ ON CONFLICT(id) DO UPDATE SET project_path=excluded.project_path, agent=excluded.agent,
2540
+ cost_center_id=excluded.cost_center_id, period=excluded.period, limit_usd=excluded.limit_usd,
2541
+ alert_at_percent=excluded.alert_at_percent, created_at=excluded.created_at, updated_at=excluded.updated_at
2542
+ WHERE budgets.metric='legacy_request_cost_usd'
2543
+ `).run(budget.id, budget.project_path ?? null, budget.agent ?? null, budget.cost_center_id ?? null, budget.period, budget.limit_usd, budget.alert_at_percent, budget.created_at, budget.updated_at);
2544
+ if (result.changes !== 1)
2545
+ throw new IngestError(409, "INGEST_CONFLICT", "budget.id", "existing metered API rules are create-only");
2546
+ }
2547
+ function listBudgets(db) {
2548
+ return db.prepare(`SELECT * FROM budgets ORDER BY created_at DESC`).all();
2549
+ }
2550
+ function deleteBudget(db, id) {
2551
+ db.prepare(`DELETE FROM budgets WHERE id = ?`).run(id);
2552
+ }
2553
+ function getBudgetStatuses(db, now = new Date) {
2554
+ const budgets = listBudgets(db);
2555
+ return budgets.map((b) => {
2556
+ const period = b.period === "daily" ? "today" : b.period === "weekly" ? "week" : "month";
2557
+ let where = calendarPeriodWhere(period, "timestamp", now);
2558
+ if (b.metric === "metered_api_usd")
2559
+ where += ` AND cost_basis = 'metered_api'`;
2560
+ const params = [];
2561
+ if (b.project_path) {
2562
+ where += ` AND session_id IN (SELECT id FROM economy_effective_sessions WHERE project_path = ?)`;
2563
+ params.push(b.project_path);
2564
+ }
2565
+ if (b.agent) {
2566
+ where += ` AND agent = ?`;
2567
+ params.push(b.agent);
2568
+ }
2569
+ if (b.cost_center_id) {
2570
+ where += ` AND COALESCE(NULLIF(cost_center_id, ''), (SELECT NULLIF(s.cost_center_id, '') FROM economy_effective_sessions s WHERE s.id = economy_effective_requests.session_id)) = ?`;
2571
+ params.push(b.cost_center_id);
2572
+ }
2573
+ if (b.metric === "metered_api_usd") {
2574
+ const groups = db.prepare(`SELECT cost_usd,COUNT(*) AS entries FROM economy_effective_requests WHERE ${where} GROUP BY cost_usd`).all(...params);
2575
+ return { ...b, period_bounds: calendarPeriodBounds(period, now), ...meteredBudgetAmounts(groups, b.limit_usd, b.alert_at_percent) };
2576
+ }
2577
+ const row = db.prepare(`SELECT COALESCE(SUM(cost_usd), 0) as spend FROM economy_effective_requests WHERE ${where}`).get(...params);
2578
+ const spend = row.spend;
2579
+ const percent = b.limit_usd > 0 ? spend / b.limit_usd * 100 : 0;
2580
+ return {
2581
+ ...b,
2582
+ period_bounds: calendarPeriodBounds(period, now),
2583
+ current_spend_usd: spend,
2584
+ percent_used: percent,
2585
+ is_over_limit: percent >= 100,
2586
+ is_over_alert: percent >= b.alert_at_percent
2587
+ };
2588
+ });
2589
+ }
2590
+ function upsertGoal(db, goal) {
2591
+ db.prepare(`
2592
+ INSERT OR REPLACE INTO goals
2593
+ (id, period, project_path, agent, limit_usd, created_at, updated_at)
2594
+ VALUES (?, ?, ?, ?, ?, ?, ?)
2595
+ `).run(goal.id, goal.period, goal.project_path ?? null, goal.agent ?? null, goal.limit_usd, goal.created_at, goal.updated_at);
2596
+ }
2597
+ function deleteGoal(db, id) {
2598
+ db.prepare(`DELETE FROM goals WHERE id = ?`).run(id);
2599
+ }
2600
+ function listGoals(db) {
2601
+ return db.prepare(`SELECT * FROM goals ORDER BY created_at DESC`).all();
2602
+ }
2603
+ function getGoalStatuses(db) {
2604
+ const goals = listGoals(db);
2605
+ return goals.map((g) => {
2606
+ const period = g.period === "day" ? "today" : g.period;
2607
+ let spendQuery = `SELECT COALESCE(SUM(cost_usd), 0) as spend FROM economy_effective_requests WHERE ${calendarPeriodWhere(period, "timestamp")}`;
2608
+ const params = [];
2609
+ if (g.project_path) {
2610
+ spendQuery += ` AND session_id IN (SELECT id FROM economy_effective_sessions WHERE project_path = ?)`;
2611
+ params.push(g.project_path);
2612
+ }
2613
+ if (g.agent) {
2614
+ spendQuery += ` AND agent = ?`;
2615
+ params.push(g.agent);
2616
+ }
2617
+ const row = db.prepare(spendQuery).get(...params);
2618
+ const spend = row.spend;
2619
+ const percent = g.limit_usd > 0 ? spend / g.limit_usd * 100 : 0;
2620
+ return {
2621
+ ...g,
2622
+ current_spend_usd: spend,
2623
+ percent_used: percent,
2624
+ is_on_track: percent < 70,
2625
+ is_at_risk: percent >= 70 && percent <= 100,
2626
+ is_over: percent > 100
2627
+ };
2628
+ });
2629
+ }
2630
+ function getIngestState(db, source, key) {
2631
+ const row = db.prepare(`SELECT value FROM ingest_state WHERE source = ? AND key = ?`).get(source, key);
2632
+ return row?.value ?? null;
2633
+ }
2634
+ function loadIngestState(db, source) {
2635
+ const rows = db.prepare(`SELECT key, value FROM ingest_state WHERE source = ?`).all(source);
2636
+ return new Map(rows.map((row) => [row.key, row.value]));
2637
+ }
2638
+ function setIngestState(db, source, key, value) {
2639
+ db.prepare(`INSERT OR REPLACE INTO ingest_state (source, key, value) VALUES (?, ?, ?)`).run(source, key, value);
2640
+ }
2641
+ function queryRequestsSince(db, since) {
2642
+ return db.prepare(`SELECT * FROM economy_effective_requests WHERE timestamp > ? ORDER BY timestamp ASC`).all(since);
2643
+ }
2644
+ function insertFeedback(db, record) {
2645
+ db.prepare(`INSERT INTO feedback (message, email, category, version, machine_id) VALUES (?, ?, ?, ?, ?)`).run(record.message, record.email ?? null, record.category ?? "general", record.version ?? null, record.machine_id ?? null);
2646
+ }
2647
+ function upsertBillingDaily(db, row) {
2648
+ db.prepare(`
2649
+ INSERT OR REPLACE INTO billing_daily (date, provider, description, cost_usd, updated_at)
2650
+ VALUES (?, ?, ?, ?, ?)
2651
+ `).run(row.date, row.provider, row.description, row.cost_usd, row.updated_at);
2652
+ }
2653
+ function clearBillingRange(db, provider, fromDate, toDate) {
2654
+ db.prepare(`DELETE FROM billing_daily WHERE provider = ? AND date >= ? AND date <= ?`).run(provider, fromDate, toDate);
2655
+ }
2656
+ function billingPeriodWhere(period) {
2657
+ return calendarPeriodWhere(period, "date");
2658
+ }
2659
+ function queryBillingSummary(db, period) {
2660
+ const now = new Date;
2661
+ const rows = db.prepare(`SELECT provider, SUM(cost_usd) AS cost, COUNT(*) AS records FROM billing_daily WHERE ${calendarPeriodWhere(period, "date", now)} GROUP BY provider`).all();
2662
+ const by_provider = {};
2663
+ let total = 0, records = 0;
2664
+ for (const row of rows) {
2665
+ by_provider[row.provider] = row.cost;
2666
+ total += row.cost;
2667
+ records += row.records;
2668
+ }
2669
+ return {
2670
+ total_usd: total,
2671
+ recorded_total_usd: records ? total : null,
2672
+ complete_total_usd: null,
2673
+ by_provider,
2674
+ basis: "recorded_provider_rows_subtotal",
2675
+ status: records ? "observed" : "no_records",
2676
+ record_count: records,
2677
+ coverage_scope: "selected_stored_records",
2678
+ source_coverage: "not_established",
2679
+ period_bounds: calendarPeriodBounds(period, now)
2680
+ };
2681
+ }
2682
+ function countBillingRecords(db, period) {
2683
+ const row = db.prepare(`SELECT COUNT(*) as c FROM billing_daily WHERE ${billingPeriodWhere(period)}`).get();
2684
+ return row?.c ?? 0;
2685
+ }
2686
+ function getSessionDetail(db, id) {
2687
+ const session = db.prepare(`SELECT * FROM economy_effective_sessions WHERE id = ? OR id LIKE ?`).get(id, `${id}%`);
2688
+ if (!session)
2689
+ return null;
2690
+ const requests = db.prepare(`SELECT * FROM economy_effective_requests WHERE session_id = ? ORDER BY timestamp ASC`).all(session["id"]);
2691
+ return { session, requests };
2692
+ }
2693
+ function listMachines(db, period = "all") {
2694
+ const rWhere = requestPeriodWhere(period);
2695
+ const sWhere = sessionPeriodWhere(period);
2696
+ return db.prepare(`
2697
+ WITH request_stats AS (
2698
+ SELECT
2699
+ machine_id,
2700
+ COUNT(DISTINCT session_id) as sessions,
2701
+ COUNT(*) as requests,
2702
+ COALESCE(SUM(cost_usd), 0) as total_cost_usd,
2703
+ MAX(timestamp) as last_active
2704
+ FROM economy_effective_requests
2705
+ WHERE machine_id != ''
2706
+ AND ${rWhere}
2707
+ GROUP BY machine_id
2708
+ ),
2709
+ session_only_stats AS (
2710
+ SELECT
2711
+ machine_id,
2712
+ COUNT(*) as sessions,
2713
+ COALESCE(SUM(request_count), 0) as requests,
2714
+ COALESCE(SUM(total_cost_usd), 0) as total_cost_usd,
2715
+ MAX(started_at) as last_active
2716
+ FROM economy_effective_sessions
2717
+ WHERE machine_id != ''
2718
+ AND ${sWhere}
2719
+ AND id NOT IN (SELECT DISTINCT session_id FROM economy_effective_requests)
2720
+ GROUP BY machine_id
2721
+ ),
2722
+ combined AS (
2723
+ SELECT * FROM request_stats
2724
+ UNION ALL
2725
+ SELECT * FROM session_only_stats
2726
+ )
2727
+ SELECT
2728
+ machine_id,
2729
+ COALESCE(SUM(sessions), 0) as sessions,
2730
+ COALESCE(SUM(requests), 0) as requests,
2731
+ COALESCE(SUM(total_cost_usd), 0) as total_cost_usd,
2732
+ MAX(last_active) as last_active
2733
+ FROM combined
2734
+ GROUP BY machine_id
2735
+ ORDER BY total_cost_usd DESC
2736
+ `).all();
2737
+ }
2738
+ function upsertModelPricing(db, p) {
2739
+ db.prepare(`
2740
+ INSERT OR REPLACE INTO model_pricing
2741
+ (model, input_per_1m, output_per_1m, cache_read_per_1m, cache_write_per_1m, cache_write_1h_per_1m, cache_storage_per_1m_hour, updated_at)
2742
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
2743
+ `).run(p.model, p.input_per_1m, p.output_per_1m, p.cache_read_per_1m, p.cache_write_per_1m, p.cache_write_1h_per_1m ?? 0, p.cache_storage_per_1m_hour ?? 0, p.updated_at);
2744
+ }
2745
+ function getModelPricing(db, model) {
2746
+ return db.prepare(`SELECT * FROM model_pricing WHERE model = ?`).get(model);
2747
+ }
2748
+ function listModelPricing(db) {
2749
+ return db.prepare(`SELECT * FROM model_pricing ORDER BY model ASC`).all();
2750
+ }
2751
+ function deleteModelPricing(db, model) {
2752
+ db.prepare(`DELETE FROM model_pricing WHERE model = ?`).run(model);
2753
+ }
2754
+ function seedModelPricing(db, defaults) {
2755
+ const existing = new Set(db.prepare(`SELECT model FROM model_pricing`).all().map((r) => r.model));
2756
+ const now = new Date().toISOString();
2757
+ for (const [model, p] of Object.entries(defaults)) {
2758
+ if (existing.has(model))
2759
+ continue;
2760
+ upsertModelPricing(db, {
2761
+ model,
2762
+ input_per_1m: p.inputPer1M,
2763
+ output_per_1m: p.outputPer1M,
2764
+ cache_read_per_1m: p.cacheReadPer1M,
2765
+ cache_write_per_1m: p.cacheWritePer1M,
2766
+ cache_write_1h_per_1m: p.cacheWrite1hPer1M ?? 0,
2767
+ cache_storage_per_1m_hour: p.cacheStoragePer1MHour ?? 0,
2768
+ updated_at: now
2769
+ });
2770
+ }
2771
+ }
2772
+ function upsertSubscription(db, sub, mode = "configuration") {
2773
+ if (sub.account_key) {
2774
+ bulkIngest(db, { subscriptions: [sub], subscription_mode: mode });
2775
+ return;
2776
+ }
2777
+ const row = { ...sub };
2778
+ const groups = validateIngest({ subscriptions: [row] });
2779
+ const spec = groups[0].spec;
2780
+ const values = spec.map(groups[0].rows[0], "");
2781
+ const columns = spec.columns;
2782
+ const updates = columns.filter((k) => k !== "id" && k !== "created_at");
2783
+ db.run(`INSERT INTO subscriptions (${columns.join(",")}) VALUES (${columns.map(() => "?").join(",")})
2784
+ ON CONFLICT(id) DO UPDATE SET ${updates.map((k) => `${k}=excluded.${k}`).join(",")}
2785
+ WHERE subscriptions.observed_at IS NULL OR (excluded.observed_at IS NOT NULL AND excluded.observed_at > subscriptions.observed_at)`, ...values);
2786
+ }
2787
+ function listSubscriptions(db) {
2788
+ return db.prepare(`SELECT * FROM subscriptions ORDER BY provider, plan`).all();
2789
+ }
2790
+ function deleteSubscription(db, id) {
2791
+ db.prepare(`DELETE FROM subscriptions WHERE id = ?`).run(id);
2792
+ }
2793
+ function upsertUsageSnapshot(db, snap) {
2794
+ const identified = !!(snap.provider && snap.account_key && snap.observed_at);
2795
+ const id = snap.id ?? (identified ? observationId({ provider: snap.provider, account_key: snap.account_key, machine_id: snap.machine_id, metric: snap.metric, observed_at: snap.observed_at }) : `${snap.agent}-${snap.date}-${snap.metric}-${snap.machine_id}`);
2796
+ const row = { ...snap, id, updated_at: snap.updated_at ?? snap.observed_at ?? new Date().toISOString() };
2797
+ if (identified || db.get("SELECT observed_at FROM usage_snapshots WHERE id = ?", id)?.observed_at) {
2798
+ bulkIngest(db, { usage_snapshots: [row] });
2799
+ return;
2800
+ }
2801
+ const group = validateIngest({ usage_snapshots: [row] })[0];
2802
+ const spec = group.spec;
2803
+ db.run(`INSERT INTO usage_snapshots (${spec.columns.join(",")}) VALUES (${spec.columns.map(() => "?").join(",")})
2804
+ ON CONFLICT(id) DO UPDATE SET ${spec.columns.filter((k) => k !== "id").map((k) => `${k}=excluded.${k}`).join(",")}`, ...spec.map(group.rows[0], ""));
2805
+ }
2806
+ function upsertScanReceipt(db, receipt) {
2807
+ bulkIngest(db, { scan_receipts: [{ ...receipt, id: receipt.id || scanReceiptId(receipt), updated_at: receipt.completed_at }] });
2808
+ }
2809
+ function queryScanReceipts(db, options = {}) {
2810
+ const limit = options.limit ?? 100;
2811
+ if (!Number.isInteger(limit) || limit < 1 || limit > 200)
2812
+ throw new Error("scan_receipts.limit: expected an integer from 1 to 200");
2813
+ const where = [], args = [];
2814
+ if (options.machine_id) {
2815
+ where.push("machine_id = ?");
2816
+ args.push(options.machine_id);
2817
+ }
2818
+ if (options.source) {
2819
+ where.push("source = ?");
2820
+ args.push(options.source);
2821
+ }
2822
+ return db.prepare(`SELECT * FROM scan_receipts ${where.length ? "WHERE " + where.join(" AND ") : ""} ORDER BY completed_at DESC, id LIMIT ?`).all(...args, limit);
2823
+ }
2824
+ function queryUsageSnapshots(db, opts = {}) {
2825
+ const conditions = [];
2826
+ const params = [];
2827
+ if (opts.agent) {
2828
+ conditions.push("agent = ?");
2829
+ params.push(opts.agent);
2830
+ }
2831
+ if (opts.date) {
2832
+ conditions.push("date = ?");
2833
+ params.push(opts.date);
2834
+ }
2835
+ if (opts.since) {
2836
+ conditions.push("date >= ?");
2837
+ params.push(opts.since);
2838
+ }
2839
+ if (opts.before) {
2840
+ conditions.push("date < ?");
2841
+ params.push(opts.before);
2842
+ }
2843
+ const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
2844
+ return db.prepare(`SELECT * FROM usage_snapshots ${where} ORDER BY date DESC, agent, metric`).all(...params);
2845
+ }
2846
+ function listMachineRegistry(db) {
2847
+ return db.prepare(`SELECT * FROM machines ORDER BY last_seen_at DESC`).all();
2848
+ }
2849
+ function dedupeRequests(db) {
2850
+ const dupes = db.prepare(`
2851
+ SELECT source_request_id, agent, COALESCE(machine_id, '') as machine_id, MIN(id) as keep_id, COUNT(*) as cnt
2852
+ FROM requests
2853
+ WHERE source_request_id != '' AND source_request_id IS NOT NULL
2854
+ GROUP BY source_request_id, agent, COALESCE(machine_id, '')
2855
+ HAVING cnt > 1
2856
+ `).all();
2857
+ let removed = 0;
2858
+ for (const row of dupes) {
2859
+ const result = db.prepare(`
2860
+ DELETE FROM requests
2861
+ WHERE source_request_id = ?
2862
+ AND agent = ?
2863
+ AND COALESCE(machine_id, '') = ?
2864
+ AND id != ?
2865
+ `).run(row.source_request_id, row.agent, row.machine_id, row.keep_id);
2866
+ removed += result.changes;
2867
+ }
2868
+ return removed;
2869
+ }
2870
+ function bulkIngest(db, body, context = {}) {
2871
+ return ingestCommand(db, body, (adapter, id) => rollupSession(adapter, id), context);
2872
+ }
2873
+
2874
+ // src/lib/pricing.ts
2875
+ var DEFAULT_PRICING = {
2876
+ "claude-opus-4-8": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
2877
+ "claude-opus-4-7": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
2878
+ "claude-opus-4-6": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
2879
+ "claude-opus-4-5": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
2880
+ "claude-opus-4-1": { inputPer1M: 15, outputPer1M: 75, cacheReadPer1M: 1.5, cacheWritePer1M: 18.75, cacheWrite1hPer1M: 30 },
2881
+ "claude-opus-4": { inputPer1M: 15, outputPer1M: 75, cacheReadPer1M: 1.5, cacheWritePer1M: 18.75, cacheWrite1hPer1M: 30 },
2882
+ "claude-sonnet-4-6": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 6 },
2883
+ "claude-sonnet-4-5": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 6 },
2884
+ "claude-sonnet-4": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 6 },
2885
+ "claude-3-7-sonnet": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 6 },
2886
+ "claude-haiku-4-5": { inputPer1M: 1, outputPer1M: 5, cacheReadPer1M: 0.1, cacheWritePer1M: 1.25, cacheWrite1hPer1M: 2 },
2887
+ "claude-3-5-haiku": { inputPer1M: 0.8, outputPer1M: 4, cacheReadPer1M: 0.08, cacheWritePer1M: 1, cacheWrite1hPer1M: 1.6 },
2888
+ "claude-3-opus": { inputPer1M: 15, outputPer1M: 75, cacheReadPer1M: 1.5, cacheWritePer1M: 18.75, cacheWrite1hPer1M: 30 },
2889
+ "claude-3-haiku": { inputPer1M: 0.25, outputPer1M: 1.25, cacheReadPer1M: 0.03, cacheWritePer1M: 0.3, cacheWrite1hPer1M: 0.5 },
2890
+ "gemini-3.1-pro-preview": { inputPer1M: 2, outputPer1M: 12, cacheReadPer1M: 0.2, cacheWritePer1M: 0, cacheStoragePer1MHour: 4.5 },
2891
+ "gemini-3.1-flash-lite-preview": { inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0.025, cacheWritePer1M: 0, cacheStoragePer1MHour: 1 },
2892
+ "gemini-3.1-flash-lite": { inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0.025, cacheWritePer1M: 0, cacheStoragePer1MHour: 1 },
2893
+ "gemini-3-flash-preview": { inputPer1M: 0.5, outputPer1M: 3, cacheReadPer1M: 0.05, cacheWritePer1M: 0, cacheStoragePer1MHour: 1 },
2894
+ "gemini-2.5-pro": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0.125, cacheWritePer1M: 0, cacheStoragePer1MHour: 4.5 },
2895
+ "gemini-2.5-flash": { inputPer1M: 0.3, outputPer1M: 2.5, cacheReadPer1M: 0.03, cacheWritePer1M: 0, cacheStoragePer1MHour: 1 },
2896
+ "gemini-2.5-flash-lite": { inputPer1M: 0.1, outputPer1M: 0.4, cacheReadPer1M: 0.01, cacheWritePer1M: 0, cacheStoragePer1MHour: 1 },
2897
+ "gemini-2.0-flash": { inputPer1M: 0.1, outputPer1M: 0.4, cacheReadPer1M: 0.025, cacheWritePer1M: 0, cacheStoragePer1MHour: 1 },
2898
+ "gemini-2.0-flash-lite": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
2899
+ "google/gemini-3.1-pro-preview": { inputPer1M: 2, outputPer1M: 12, cacheReadPer1M: 0.2, cacheWritePer1M: 0.375 },
2900
+ "google/gemini-3.1-flash-lite-preview": { inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0.025, cacheWritePer1M: 0.08333333333333334 },
2901
+ "google/gemini-3.1-flash-lite": { inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0.025, cacheWritePer1M: 0.08333333333333334 },
2902
+ "google/gemini-3-flash-preview": { inputPer1M: 0.5, outputPer1M: 3, cacheReadPer1M: 0.05, cacheWritePer1M: 0.08333333333333334 },
2903
+ "google/gemini-2.5-pro": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0.125, cacheWritePer1M: 0.375 },
2904
+ "google/gemini-2.5-flash": { inputPer1M: 0.3, outputPer1M: 2.5, cacheReadPer1M: 0.03, cacheWritePer1M: 0.08333333333333334 },
2905
+ "google/gemini-2.5-flash-lite": { inputPer1M: 0.1, outputPer1M: 0.4, cacheReadPer1M: 0.01, cacheWritePer1M: 0.08333333333333334 },
2906
+ "gpt-5.5": { inputPer1M: 5, outputPer1M: 30, cacheReadPer1M: 0.5, cacheWritePer1M: 0 },
2907
+ "gpt-5.5-pro": { inputPer1M: 30, outputPer1M: 180, cacheReadPer1M: 0, cacheWritePer1M: 0 },
2908
+ "gpt-5.4": { inputPer1M: 2.5, outputPer1M: 15, cacheReadPer1M: 0.25, cacheWritePer1M: 0 },
2909
+ "gpt-5.4-pro": { inputPer1M: 30, outputPer1M: 180, cacheReadPer1M: 0, cacheWritePer1M: 0 },
2910
+ "gpt-5.4-mini": { inputPer1M: 0.75, outputPer1M: 4.5, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
2911
+ "gpt-5.4-nano": { inputPer1M: 0.2, outputPer1M: 1.25, cacheReadPer1M: 0.02, cacheWritePer1M: 0 },
2912
+ "gpt-5.3-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.175, cacheWritePer1M: 0 },
2913
+ "gpt-5.2-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.175, cacheWritePer1M: 0 },
2914
+ "gpt-5.2-chat-latest": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.175, cacheWritePer1M: 0 },
2915
+ "gpt-5.2": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.175, cacheWritePer1M: 0 },
2916
+ "gpt-5-codex": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0.125, cacheWritePer1M: 0 },
2917
+ "gpt-5-mini": { inputPer1M: 0.25, outputPer1M: 2, cacheReadPer1M: 0.025, cacheWritePer1M: 0 },
2918
+ "gpt-5": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0.125, cacheWritePer1M: 0 },
2919
+ "gpt-4o": { inputPer1M: 2.5, outputPer1M: 10, cacheReadPer1M: 1.25, cacheWritePer1M: 0 },
2920
+ "gpt-4o-mini": { inputPer1M: 0.15, outputPer1M: 0.6, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
2921
+ o1: { inputPer1M: 15, outputPer1M: 60, cacheReadPer1M: 7.5, cacheWritePer1M: 0 },
2922
+ "o1-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.55, cacheWritePer1M: 0 },
2923
+ o3: { inputPer1M: 2, outputPer1M: 8, cacheReadPer1M: 0.5, cacheWritePer1M: 0 },
2924
+ "o3-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.55, cacheWritePer1M: 0 },
2925
+ "o4-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.275, cacheWritePer1M: 0 },
2926
+ "qwen3.6-plus": { inputPer1M: 0.325, outputPer1M: 1.95, cacheReadPer1M: 0.0325, cacheWritePer1M: 0.40625 },
2927
+ "qwen3.6-flash": { inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0.025, cacheWritePer1M: 0.3125 },
2928
+ "qwen3.6-35b-a3b": { inputPer1M: 0.15, outputPer1M: 1, cacheReadPer1M: 0.05, cacheWritePer1M: 0 },
2929
+ "qwen3.6-max-preview": { inputPer1M: 1.04, outputPer1M: 6.24, cacheReadPer1M: 0.104, cacheWritePer1M: 1.3 },
2930
+ "qwen3.6-27b": { inputPer1M: 0.32, outputPer1M: 3.2, cacheReadPer1M: 0, cacheWritePer1M: 0 },
2931
+ "qwen/qwen3.6-plus": { inputPer1M: 0.325, outputPer1M: 1.95, cacheReadPer1M: 0.0325, cacheWritePer1M: 0.40625 },
2932
+ "qwen/qwen3.6-flash": { inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0.025, cacheWritePer1M: 0.3125 },
2933
+ "qwen/qwen3.6-35b-a3b": { inputPer1M: 0.15, outputPer1M: 1, cacheReadPer1M: 0.05, cacheWritePer1M: 0 },
2934
+ "qwen/qwen3.6-max-preview": { inputPer1M: 1.04, outputPer1M: 6.24, cacheReadPer1M: 0.104, cacheWritePer1M: 1.3 },
2935
+ "qwen/qwen3.6-27b": { inputPer1M: 0.32, outputPer1M: 3.2, cacheReadPer1M: 0, cacheWritePer1M: 0 },
2936
+ "minimax-m2.7": { inputPer1M: 0.3, outputPer1M: 1.2, cacheReadPer1M: 0.06, cacheWritePer1M: 0.375 },
2937
+ "minimax-m2.7-highspeed": { inputPer1M: 0.6, outputPer1M: 2.4, cacheReadPer1M: 0.06, cacheWritePer1M: 0.375 },
2938
+ "minimax/minimax-m2.7": { inputPer1M: 0.299, outputPer1M: 1.2, cacheReadPer1M: 0, cacheWritePer1M: 0 },
2939
+ "minimax-m1": { inputPer1M: 0.4, outputPer1M: 2.2, cacheReadPer1M: 0, cacheWritePer1M: 0 },
2940
+ "minimax/minimax-m1": { inputPer1M: 0.4, outputPer1M: 2.2, cacheReadPer1M: 0, cacheWritePer1M: 0 },
2941
+ "grok-4.3": { inputPer1M: 1.25, outputPer1M: 2.5, cacheReadPer1M: 0.2, cacheWritePer1M: 0 },
2942
+ "grok-latest": { inputPer1M: 1.25, outputPer1M: 2.5, cacheReadPer1M: 0.2, cacheWritePer1M: 0 },
2943
+ "grok-4.20": { inputPer1M: 1.25, outputPer1M: 2.5, cacheReadPer1M: 0.2, cacheWritePer1M: 0 },
2944
+ "grok-4-1-fast": { inputPer1M: 0.2, outputPer1M: 0.5, cacheReadPer1M: 0.05, cacheWritePer1M: 0 },
2945
+ "grok-4-fast": { inputPer1M: 0.2, outputPer1M: 0.5, cacheReadPer1M: 0.05, cacheWritePer1M: 0 },
2946
+ "grok-4": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.75, cacheWritePer1M: 0 },
2947
+ "grok-code-fast-1": { inputPer1M: 0.2, outputPer1M: 1.5, cacheReadPer1M: 0.02, cacheWritePer1M: 0 },
2948
+ "grok-code-fast": { inputPer1M: 0.2, outputPer1M: 1.5, cacheReadPer1M: 0.02, cacheWritePer1M: 0 },
2949
+ "grok-3": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.75, cacheWritePer1M: 0 },
2950
+ "grok-3-mini": { inputPer1M: 0.3, outputPer1M: 0.5, cacheReadPer1M: 0.07, cacheWritePer1M: 0 },
2951
+ "glm-5.1": { inputPer1M: 1.4, outputPer1M: 4.4, cacheReadPer1M: 0.26, cacheWritePer1M: 0 },
2952
+ "glm-5": { inputPer1M: 1, outputPer1M: 3.2, cacheReadPer1M: 0.2, cacheWritePer1M: 0 },
2953
+ "z-ai/glm-5.1": { inputPer1M: 1.05, outputPer1M: 3.5, cacheReadPer1M: 0.525, cacheWritePer1M: 0 },
2954
+ "z-ai/glm-5": { inputPer1M: 0.6, outputPer1M: 1.92, cacheReadPer1M: 0.12, cacheWritePer1M: 0 },
2955
+ "kimi-k2.6": { inputPer1M: 0.95, outputPer1M: 4, cacheReadPer1M: 0.16, cacheWritePer1M: 0 },
2956
+ "kimi-k2.5": { inputPer1M: 0.6, outputPer1M: 3, cacheReadPer1M: 0.1, cacheWritePer1M: 0 },
2957
+ "kimi-k2": { inputPer1M: 0.6, outputPer1M: 2.5, cacheReadPer1M: 0.15, cacheWritePer1M: 0 },
2958
+ "moonshotai/kimi-k2.6": { inputPer1M: 0.75, outputPer1M: 3.5, cacheReadPer1M: 0.15, cacheWritePer1M: 0 },
2959
+ "moonshotai/kimi-k2.5": { inputPer1M: 0.44, outputPer1M: 2, cacheReadPer1M: 0.22, cacheWritePer1M: 0 },
2960
+ "moonshotai/kimi-k2": { inputPer1M: 0.57, outputPer1M: 2.3, cacheReadPer1M: 0, cacheWritePer1M: 0 }
2961
+ };
2962
+ var LEGACY_DEFAULT_PRICING = {
2963
+ "claude-3-5-haiku": { inputPer1M: 1, outputPer1M: 5, cacheReadPer1M: 0.1, cacheWritePer1M: 1.25 },
2964
+ "claude-opus-4": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25 },
2965
+ "gemini-2.5-pro": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0.31, cacheWritePer1M: 0 },
2966
+ "gemini-2.5-flash": { inputPer1M: 0.15, outputPer1M: 0.6, cacheReadPer1M: 0, cacheWritePer1M: 0 },
2967
+ "gemini-2.0-flash": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
2968
+ "gpt-5.3-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
2969
+ "gpt-5.2-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
2970
+ "gpt-5-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
2971
+ "gpt-5-mini": { inputPer1M: 0.3, outputPer1M: 1.2, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
2972
+ "gpt-5.2": { inputPer1M: 2, outputPer1M: 8, cacheReadPer1M: 0.5, cacheWritePer1M: 0 },
2973
+ "o1-mini": { inputPer1M: 3, outputPer1M: 12, cacheReadPer1M: 1.5, cacheWritePer1M: 0 },
2974
+ "grok-3": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0, cacheWritePer1M: 0 },
2975
+ "grok-3-mini": { inputPer1M: 0.3, outputPer1M: 0.5, cacheReadPer1M: 0, cacheWritePer1M: 0 },
2976
+ "qwen3.6-plus": { inputPer1M: 0.8, outputPer1M: 2, cacheReadPer1M: 0, cacheWritePer1M: 0 },
2977
+ "minimax-m2.7": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
2978
+ "minimax-m2.7-highspeed": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
2979
+ "minimax-m1": { inputPer1M: 0.2, outputPer1M: 1.1, cacheReadPer1M: 0, cacheWritePer1M: 0 },
2980
+ "glm-5.1": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
2981
+ "glm-5": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
2982
+ "kimi-k2": { inputPer1M: 0.6, outputPer1M: 0.6, cacheReadPer1M: 0, cacheWritePer1M: 0 },
2983
+ o3: { inputPer1M: 10, outputPer1M: 40, cacheReadPer1M: 2.5, cacheWritePer1M: 0 }
2984
+ };
2985
+ var ADDITIONAL_LEGACY_DEFAULT_PRICING = {
2986
+ "gemini-2.5-pro": [
2987
+ { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0, cacheWritePer1M: 0 }
2988
+ ],
2989
+ "qwen3.6-plus": [
2990
+ { inputPer1M: 0.325, outputPer1M: 1.95, cacheReadPer1M: 0, cacheWritePer1M: 0.40625 },
2991
+ { inputPer1M: 0.325, outputPer1M: 1.95, cacheReadPer1M: 0.05, cacheWritePer1M: 0.40625 }
2992
+ ],
2993
+ "qwen3.6-flash": [
2994
+ { inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0, cacheWritePer1M: 0.3125 }
2995
+ ],
2996
+ "qwen3.6-max-preview": [
2997
+ { inputPer1M: 1.04, outputPer1M: 6.24, cacheReadPer1M: 0, cacheWritePer1M: 1.3 },
2998
+ { inputPer1M: 1.04, outputPer1M: 6.24, cacheReadPer1M: 0.13, cacheWritePer1M: 1.3 }
2999
+ ],
3000
+ "qwen/qwen3.6-plus": [
3001
+ { inputPer1M: 0.325, outputPer1M: 1.95, cacheReadPer1M: 0, cacheWritePer1M: 0.40625 },
3002
+ { inputPer1M: 0.325, outputPer1M: 1.95, cacheReadPer1M: 0.05, cacheWritePer1M: 0.40625 }
3003
+ ],
3004
+ "qwen/qwen3.6-flash": [
3005
+ { inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0, cacheWritePer1M: 0.3125 }
3006
+ ],
3007
+ "qwen/qwen3.6-max-preview": [
3008
+ { inputPer1M: 1.04, outputPer1M: 6.24, cacheReadPer1M: 0, cacheWritePer1M: 1.3 },
3009
+ { inputPer1M: 1.04, outputPer1M: 6.24, cacheReadPer1M: 0.13, cacheWritePer1M: 1.3 }
3010
+ ]
3011
+ };
3012
+ var REMOVED_DEFAULT_PRICING = {
3013
+ "claude-3-5-sonnet": [
3014
+ { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 6 },
3015
+ { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 0 }
3016
+ ],
3017
+ "claude-3-sonnet": [
3018
+ { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 6 },
3019
+ { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 0 }
3020
+ ],
3021
+ "gemini-3.1-pro": [
3022
+ { inputPer1M: 2, outputPer1M: 12, cacheReadPer1M: 0.2, cacheWritePer1M: 0 },
3023
+ { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0.31, cacheWritePer1M: 0 }
3024
+ ],
3025
+ "gemini-1.5-pro": [
3026
+ { inputPer1M: 1.25, outputPer1M: 5, cacheReadPer1M: 0, cacheWritePer1M: 0 }
3027
+ ],
3028
+ "gemini-1.5-flash": [
3029
+ { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 }
3030
+ ],
3031
+ "gpt-5.3-chat": [
3032
+ { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.175, cacheWritePer1M: 0 },
3033
+ { inputPer1M: 2, outputPer1M: 8, cacheReadPer1M: 0.5, cacheWritePer1M: 0 }
3034
+ ],
3035
+ "qwen3.6": [
3036
+ { inputPer1M: 0.3, outputPer1M: 0.6, cacheReadPer1M: 0, cacheWritePer1M: 0 }
3037
+ ]
3038
+ };
3039
+ var FREE_PRICING = {
3040
+ inputPer1M: 0,
3041
+ outputPer1M: 0,
3042
+ cacheReadPer1M: 0,
3043
+ cacheWritePer1M: 0,
3044
+ cacheWrite1hPer1M: 0,
3045
+ cacheStoragePer1MHour: 0
3046
+ };
3047
+ var GEMINI_PROMPT_TIERS = {
3048
+ "gemini-3.1-pro-preview": {
3049
+ threshold: 200000,
3050
+ inputPer1M: 4,
3051
+ outputPer1M: 18,
3052
+ cacheReadPer1M: 0.4
3053
+ },
3054
+ "gemini-2.5-pro": {
3055
+ threshold: 200000,
3056
+ inputPer1M: 2.5,
3057
+ outputPer1M: 15,
3058
+ cacheReadPer1M: 0.25
3059
+ }
3060
+ };
3061
+ var OPENAI_PROMPT_TIERS = {
3062
+ "gpt-5.5": {
3063
+ threshold: 272000,
3064
+ inputMultiplier: 2,
3065
+ outputMultiplier: 1.5,
3066
+ cacheReadMultiplier: 2
3067
+ },
3068
+ "gpt-5.4-pro": {
3069
+ threshold: 272000,
3070
+ inputMultiplier: 2,
3071
+ outputMultiplier: 1.5,
3072
+ cacheReadMultiplier: 2
3073
+ },
3074
+ "gpt-5.4": {
3075
+ threshold: 272000,
3076
+ inputMultiplier: 2,
3077
+ outputMultiplier: 1.5,
3078
+ cacheReadMultiplier: 2
3079
+ }
3080
+ };
3081
+ var QWEN_PROMPT_TIERS = {
3082
+ "qwen3.6-plus": {
3083
+ threshold: 256000,
3084
+ inputPer1M: 1.3,
3085
+ outputPer1M: 3.9,
3086
+ cacheReadPer1M: 0.13,
3087
+ cacheWritePer1M: 1.625
3088
+ },
3089
+ "qwen3.6-flash": {
3090
+ threshold: 256000,
3091
+ inputPer1M: 1,
3092
+ outputPer1M: 4,
3093
+ cacheReadPer1M: 0.1,
3094
+ cacheWritePer1M: 1.25
3095
+ },
3096
+ "qwen3.6-max-preview": {
3097
+ threshold: 128000,
3098
+ inputPer1M: 1.6,
3099
+ outputPer1M: 9.6,
3100
+ cacheReadPer1M: 0.16,
3101
+ cacheWritePer1M: 2
3102
+ }
3103
+ };
3104
+ var MINIMAX_PROMPT_TIERS = {
3105
+ "minimax/minimax-m1": {
3106
+ threshold: Number.POSITIVE_INFINITY
3107
+ },
3108
+ "minimax-m1": {
3109
+ threshold: 200000,
3110
+ inputPer1M: 1.3
3111
+ }
3112
+ };
3113
+ var XAI_PROMPT_TIERS = {
3114
+ "grok-4.3": {
3115
+ threshold: 200000,
3116
+ inputPer1M: 2.5,
3117
+ outputPer1M: 5,
3118
+ cacheReadPer1M: 0.4
3119
+ },
3120
+ "grok-latest": {
3121
+ threshold: 200000,
3122
+ inputPer1M: 2.5,
3123
+ outputPer1M: 5,
3124
+ cacheReadPer1M: 0.4
3125
+ },
3126
+ "grok-4.20": {
3127
+ threshold: 200000,
3128
+ inputPer1M: 2.5,
3129
+ outputPer1M: 5,
3130
+ cacheReadPer1M: 0.4
3131
+ },
3132
+ "grok-4-1-fast": {
3133
+ threshold: 128000,
3134
+ inputPer1M: 0.4,
3135
+ outputPer1M: 1,
3136
+ cacheReadPer1M: 0
3137
+ },
3138
+ "grok-4-fast": {
3139
+ threshold: 128000,
3140
+ inputPer1M: 0.4,
3141
+ outputPer1M: 1,
3142
+ cacheReadPer1M: 0
3143
+ },
3144
+ "grok-4": {
3145
+ threshold: 128000,
3146
+ inputPer1M: 6,
3147
+ outputPer1M: 30,
3148
+ cacheReadPer1M: 0
3149
+ }
3150
+ };
3151
+ function normalizeModelName(raw) {
3152
+ return raw.trim().toLowerCase().replace(/^models\//, "").replace(/^[a-z0-9_.-]+\//, "").replace(/:.+$/, "").replace(/-\d{8}$/, "").replace(/-\d{4}-\d{2}-\d{2}$/, "");
3153
+ }
3154
+ function normalizeModelNamePreservingProvider(raw) {
3155
+ return raw.trim().toLowerCase().replace(/^models\//, "").replace(/:.+$/, "").replace(/-\d{8}$/, "").replace(/-\d{4}-\d{2}-\d{2}$/, "");
3156
+ }
3157
+ function modelLookupKeys(raw) {
3158
+ const withProvider = normalizeModelNamePreservingProvider(raw);
3159
+ const withoutProvider = normalizeModelName(raw);
3160
+ return withProvider === withoutProvider ? [withoutProvider] : [withProvider, withoutProvider];
3161
+ }
3162
+ function bestPrefixMatch(normalized, entries) {
3163
+ let best = null;
3164
+ for (const entry of entries) {
3165
+ const [key] = entry;
3166
+ if (normalized !== key && !normalized.startsWith(`${key}-`))
3167
+ continue;
3168
+ if (!best || key.length > best[0].length)
3169
+ best = entry;
3170
+ }
3171
+ return best?.[1] ?? null;
3172
+ }
3173
+ function bestModelMatch(model, entries) {
3174
+ for (const key of modelLookupKeys(model)) {
3175
+ const match = bestPrefixMatch(key, entries);
3176
+ if (match)
3177
+ return match;
3178
+ }
3179
+ return null;
3180
+ }
3181
+ function exactModelMatch(model, entries) {
3182
+ for (const key of modelLookupKeys(model)) {
3183
+ const match = entries.find(([entryKey]) => entryKey === key);
3184
+ if (match)
3185
+ return match[1];
3186
+ }
3187
+ return null;
3188
+ }
3189
+ function ensurePricingSeeded(db) {
3190
+ seedModelPricing(db, DEFAULT_PRICING);
3191
+ repairLegacySeededPricing(db);
3192
+ repairMissingDefaultCacheWrite1h(db);
3193
+ repairMissingDefaultCacheStorage(db);
3194
+ removeDeprecatedDefaultPricing(db);
3195
+ }
3196
+ function repairLegacySeededPricing(db) {
3197
+ const now = new Date().toISOString();
3198
+ const legacyModels = new Set([
3199
+ ...Object.keys(LEGACY_DEFAULT_PRICING),
3200
+ ...Object.keys(ADDITIONAL_LEGACY_DEFAULT_PRICING)
3201
+ ]);
3202
+ for (const model of legacyModels) {
3203
+ const current = getModelPricing(db, model);
3204
+ const next = DEFAULT_PRICING[model];
3205
+ if (!current || !next)
3206
+ continue;
3207
+ const legacy = LEGACY_DEFAULT_PRICING[model];
3208
+ const legacyRows = [
3209
+ ...legacy ? [legacy] : [],
3210
+ ...ADDITIONAL_LEGACY_DEFAULT_PRICING[model] ?? []
3211
+ ];
3212
+ if (!legacyRows.some((row) => samePricing(current, row)))
3213
+ continue;
3214
+ upsertModelPricing(db, {
3215
+ model,
3216
+ input_per_1m: next.inputPer1M,
3217
+ output_per_1m: next.outputPer1M,
3218
+ cache_read_per_1m: next.cacheReadPer1M,
3219
+ cache_write_per_1m: next.cacheWritePer1M,
3220
+ cache_write_1h_per_1m: next.cacheWrite1hPer1M ?? 0,
3221
+ cache_storage_per_1m_hour: next.cacheStoragePer1MHour ?? 0,
3222
+ updated_at: now
3223
+ });
3224
+ }
3225
+ }
3226
+ function repairMissingDefaultCacheWrite1h(db) {
3227
+ const now = new Date().toISOString();
3228
+ for (const [model, next] of Object.entries(DEFAULT_PRICING)) {
3229
+ if (!next.cacheWrite1hPer1M)
3230
+ continue;
3231
+ const current = getModelPricing(db, model);
3232
+ if (!current)
3233
+ continue;
3234
+ if ((current.cache_write_1h_per_1m ?? 0) !== 0)
3235
+ continue;
3236
+ if (!sameBasePricing(current, next))
3237
+ continue;
3238
+ upsertModelPricing(db, {
3239
+ model,
3240
+ input_per_1m: current.input_per_1m,
3241
+ output_per_1m: current.output_per_1m,
3242
+ cache_read_per_1m: current.cache_read_per_1m,
3243
+ cache_write_per_1m: current.cache_write_per_1m,
3244
+ cache_write_1h_per_1m: next.cacheWrite1hPer1M,
3245
+ cache_storage_per_1m_hour: current.cache_storage_per_1m_hour ?? next.cacheStoragePer1MHour ?? 0,
3246
+ updated_at: now
3247
+ });
3248
+ }
3249
+ }
3250
+ function repairMissingDefaultCacheStorage(db) {
3251
+ const now = new Date().toISOString();
3252
+ for (const [model, next] of Object.entries(DEFAULT_PRICING)) {
3253
+ if (!next.cacheStoragePer1MHour)
3254
+ continue;
3255
+ const current = getModelPricing(db, model);
3256
+ if (!current)
3257
+ continue;
3258
+ if ((current.cache_storage_per_1m_hour ?? 0) !== 0)
3259
+ continue;
3260
+ if (!sameBasePricing(current, next))
3261
+ continue;
3262
+ upsertModelPricing(db, {
3263
+ model,
3264
+ input_per_1m: current.input_per_1m,
3265
+ output_per_1m: current.output_per_1m,
3266
+ cache_read_per_1m: current.cache_read_per_1m,
3267
+ cache_write_per_1m: current.cache_write_per_1m,
3268
+ cache_write_1h_per_1m: current.cache_write_1h_per_1m ?? next.cacheWrite1hPer1M ?? 0,
3269
+ cache_storage_per_1m_hour: next.cacheStoragePer1MHour,
3270
+ updated_at: now
3271
+ });
3272
+ }
3273
+ }
3274
+ function removeDeprecatedDefaultPricing(db) {
3275
+ for (const [model, removedRows] of Object.entries(REMOVED_DEFAULT_PRICING)) {
3276
+ const current = getModelPricing(db, model);
3277
+ if (!current)
3278
+ continue;
3279
+ if (!removedRows.some((row) => samePricing(current, row)))
3280
+ continue;
3281
+ deleteModelPricing(db, model);
3282
+ }
3283
+ }
3284
+ function sameBasePricing(row, pricing) {
3285
+ return row.input_per_1m === pricing.inputPer1M && row.output_per_1m === pricing.outputPer1M && row.cache_read_per_1m === pricing.cacheReadPer1M && row.cache_write_per_1m === pricing.cacheWritePer1M;
3286
+ }
3287
+ function samePricing(row, pricing) {
3288
+ return row.input_per_1m === pricing.inputPer1M && row.output_per_1m === pricing.outputPer1M && row.cache_read_per_1m === pricing.cacheReadPer1M && row.cache_write_per_1m === pricing.cacheWritePer1M && (row.cache_write_1h_per_1m ?? 0) === (pricing.cacheWrite1hPer1M ?? 0) && (row.cache_storage_per_1m_hour ?? 0) === (pricing.cacheStoragePer1MHour ?? 0);
3289
+ }
3290
+ function getPricingFromDb(db, model) {
3291
+ if (isFreeModel(model))
3292
+ return FREE_PRICING;
3293
+ for (const key of modelLookupKeys(model)) {
3294
+ const row = getModelPricing(db, key);
3295
+ if (row)
3296
+ return modelPricingFromDbRow(row);
3297
+ }
3298
+ const allRows = db.prepare(`SELECT * FROM model_pricing`).all();
3299
+ const match = bestModelMatch(model, allRows.map((r) => [r.model, r]));
3300
+ if (!match)
3301
+ return null;
3302
+ return modelPricingFromDbRow(match);
3303
+ }
3304
+ function modelPricingFromDbRow(row) {
3305
+ const seeded = DEFAULT_PRICING[row.model];
3306
+ const cacheWrite1hPer1M = seeded?.cacheWrite1hPer1M && (row.cache_write_1h_per_1m ?? 0) === 0 && sameBasePricing(row, seeded) ? seeded.cacheWrite1hPer1M : row.cache_write_1h_per_1m ?? 0;
3307
+ return {
3308
+ inputPer1M: row.input_per_1m,
3309
+ outputPer1M: row.output_per_1m,
3310
+ cacheReadPer1M: row.cache_read_per_1m,
3311
+ cacheWritePer1M: row.cache_write_per_1m,
3312
+ cacheWrite1hPer1M,
3313
+ cacheStoragePer1MHour: row.cache_storage_per_1m_hour ?? seeded?.cacheStoragePer1MHour ?? 0
3314
+ };
3315
+ }
3316
+ function getPricing(model) {
3317
+ if (isFreeModel(model))
3318
+ return FREE_PRICING;
3319
+ return bestModelMatch(model, Object.entries(DEFAULT_PRICING));
3320
+ }
3321
+ function isFreeModel(model) {
3322
+ return model.trim().toLowerCase().endsWith(":free");
3323
+ }
3324
+ function computeCost(model, inputTokens, outputTokens, cacheReadTokens = 0, cacheWriteTokens = 0, cacheWrite1hTokens = 0, cacheStorageTokenHours = 0) {
3325
+ return requirePricedCost(assessResolvedCost(model, null, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, cacheWrite1hTokens, cacheStorageTokenHours));
3326
+ }
3327
+ function computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens = 0, cacheWriteTokens = 0, cacheWrite1hTokens = 0, cacheStorageTokenHours = 0) {
3328
+ return requirePricedCost(assessCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, cacheWrite1hTokens, cacheStorageTokenHours));
3329
+ }
3330
+
3331
+ class UnknownModelPricingError extends Error {
3332
+ code = "MODEL_PRICING_UNAVAILABLE";
3333
+ constructor() {
3334
+ super("MODEL_PRICING_UNAVAILABLE: Pricing unavailable for model; configure its rates before estimating cost");
3335
+ this.name = "UnknownModelPricingError";
3336
+ }
3337
+ }
3338
+ function requirePricedCost(assessment) {
3339
+ if (assessment.usd === null)
3340
+ throw new UnknownModelPricingError;
3341
+ return assessment.usd;
3342
+ }
3343
+ function assessCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens = 0, cacheWriteTokens = 0, cacheWrite1hTokens = 0, cacheStorageTokenHours = 0) {
3344
+ return assessResolvedCost(model, getPricingFromDb(db, model), inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, cacheWrite1hTokens, cacheStorageTokenHours);
3345
+ }
3346
+ function getPricingFromRows(rows, model) {
3347
+ if (isFreeModel(model))
3348
+ return FREE_PRICING;
3349
+ for (const key of modelLookupKeys(model)) {
3350
+ const row = rows.find((r) => r.model === key);
3351
+ if (row)
3352
+ return modelPricingFromDbRow(row);
3353
+ }
3354
+ const match = bestModelMatch(model, rows.map((r) => [r.model, r]));
3355
+ if (!match)
3356
+ return null;
3357
+ return modelPricingFromDbRow(match);
3358
+ }
3359
+ function estimateCostFromRows(rows, model, inputTokens, outputTokens, cacheReadTokens = 0, cacheWriteTokens = 0, cacheWrite1hTokens = 0, cacheStorageTokenHours = 0) {
3360
+ return requirePricedCost(assessCostFromRows(rows, model, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, cacheWrite1hTokens, cacheStorageTokenHours));
3361
+ }
3362
+ function assessCostFromRows(rows, model, inputTokens, outputTokens, cacheReadTokens = 0, cacheWriteTokens = 0, cacheWrite1hTokens = 0, cacheStorageTokenHours = 0) {
3363
+ return assessResolvedCost(model, getPricingFromRows(rows, model), inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, cacheWrite1hTokens, cacheStorageTokenHours);
3364
+ }
3365
+ function assessResolvedCost(model, configured, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, cacheWrite1hTokens, cacheStorageTokenHours) {
3366
+ const counts = { inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, cacheWrite1hTokens };
3367
+ for (const [field, value] of Object.entries(counts)) {
3368
+ if (!Number.isSafeInteger(value) || value < 0)
3369
+ throw new RangeError(`${field} must be a nonnegative safe integer`);
3370
+ }
3371
+ if (!Number.isFinite(cacheStorageTokenHours) || cacheStorageTokenHours < 0) {
3372
+ throw new RangeError("cacheStorageTokenHours must be finite and nonnegative");
3373
+ }
3374
+ if (isFreeModel(model))
3375
+ return { usd: 0, pricing: "explicit_free" };
3376
+ const pricing = configured ?? getPricing(model);
3377
+ if (!pricing)
3378
+ return { usd: null, pricing: "missing" };
3379
+ if (Object.values(pricing).some((value) => value !== undefined && (!Number.isFinite(value) || value < 0))) {
3380
+ throw new RangeError("Model pricing rates must be finite and nonnegative");
3381
+ }
3382
+ const usd = computeCostWithPricing(model, pricing, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, cacheWrite1hTokens, cacheStorageTokenHours);
3383
+ if (!Number.isFinite(usd))
3384
+ throw new RangeError("Cost estimate exceeds the supported numeric range");
3385
+ return { usd, pricing: configured ? "configured" : "catalog" };
3386
+ }
3387
+ function computeCostWithPricing(model, pricing, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, cacheWrite1hTokens, cacheStorageTokenHours) {
3388
+ if (isFreeModel(model))
3389
+ return 0;
3390
+ let effective = pricing;
3391
+ const promptTier = bestModelMatch(model, Object.entries(GEMINI_PROMPT_TIERS)) ?? bestModelMatch(model, Object.entries(QWEN_PROMPT_TIERS)) ?? bestModelMatch(model, Object.entries(MINIMAX_PROMPT_TIERS)) ?? bestModelMatch(model, Object.entries(XAI_PROMPT_TIERS)) ?? exactModelMatch(model, Object.entries(OPENAI_PROMPT_TIERS));
3392
+ if (promptTier) {
3393
+ const billablePromptTokens = inputTokens + cacheReadTokens + cacheWriteTokens + cacheWrite1hTokens;
3394
+ if (billablePromptTokens > promptTier.threshold) {
3395
+ effective = {
3396
+ ...pricing,
3397
+ inputPer1M: promptTier.inputPer1M ?? pricing.inputPer1M * (promptTier.inputMultiplier ?? 1),
3398
+ outputPer1M: promptTier.outputPer1M ?? pricing.outputPer1M * (promptTier.outputMultiplier ?? 1),
3399
+ cacheReadPer1M: promptTier.cacheReadPer1M ?? pricing.cacheReadPer1M * (promptTier.cacheReadMultiplier ?? 1),
3400
+ cacheWritePer1M: promptTier.cacheWritePer1M ?? pricing.cacheWritePer1M * (promptTier.cacheWriteMultiplier ?? 1)
3401
+ };
3402
+ }
3403
+ }
3404
+ return (inputTokens * effective.inputPer1M + outputTokens * effective.outputPer1M + cacheReadTokens * effective.cacheReadPer1M + cacheWriteTokens * effective.cacheWritePer1M + cacheWrite1hTokens * (effective.cacheWrite1hPer1M ?? effective.cacheWritePer1M) + cacheStorageTokenHours * (effective.cacheStoragePer1MHour ?? 0)) / 1e6;
3405
+ }
3406
+
3407
+ export { observationId, subscriptionId, scanReceiptId, AGENTS, MAX_INGEST_ROWS, MAX_INGEST_BYTES, stableJson, canonicalIngestBodyHash, validateIngest, calendarPeriodBounds, calendarPeriodWhere, usageSnapshotFilterForPeriod, sessionOverlapWhere, dataDir, cacheDir, getMachineId, getHomeDir, getResolverDataRoot, getLegacyDataRoot, adoptResolverDataRoot, getExactDataRoot, getDataDir, getDbPath, initSchema, upsertCostCenter, getCostCenter, upsertRequest, upsertSession, rollupSession, querySessions, queryTopSessions, querySummary, queryZeroCostTokenizedModels, queryModelBreakdown, queryAgentBreakdown, queryProjectBreakdown, queryAccountBreakdown, queryCostCenterBreakdown, queryProjectBreakdownSince, queryAgentBreakdownSince, queryModelBreakdownSince, queryAccountBreakdownSince, queryDailyBreakdown, queryHourlyBreakdown, upsertProject, getProject, listProjects, deleteProject, upsertBudget, listBudgets, deleteBudget, getBudgetStatuses, upsertGoal, deleteGoal, listGoals, getGoalStatuses, getIngestState, loadIngestState, setIngestState, queryRequestsSince, insertFeedback, upsertBillingDaily, clearBillingRange, queryBillingSummary, countBillingRecords, getSessionDetail, listMachines, upsertModelPricing, getModelPricing, listModelPricing, deleteModelPricing, seedModelPricing, upsertSubscription, listSubscriptions, deleteSubscription, upsertUsageSnapshot, upsertScanReceipt, queryScanReceipts, queryUsageSnapshots, listMachineRegistry, dedupeRequests, bulkIngest, DEFAULT_PRICING, normalizeModelName, ensurePricingSeeded, getPricingFromDb, getPricing, computeCost, computeCostFromDb, UnknownModelPricingError, assessCostFromDb, getPricingFromRows, estimateCostFromRows, assessCostFromRows, formatReportedUsd, formatPlanMoney, hasKnownUsagePrice, requireCurrentCostSummary, formatSummaryValue, formatSummaryCoverage, requireCurrentBillingSummary, formatBillingValue };