@tokenbuddy/tokenbuddy 1.0.36 → 1.0.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/buyer-store.d.ts +7 -2
- package/dist/src/buyer-store.js +46 -7
- package/dist/src/cli.d.ts +1 -0
- package/dist/src/cli.js +15 -7
- package/dist/src/daemon.d.ts +12 -0
- package/dist/src/daemon.js +791 -61
- package/dist/src/doctor-diagnostics.js +1 -6
- package/dist/src/provider-install.d.ts +2 -2
- package/dist/src/provider-install.js +248 -2
- package/dist/src/seller-catalog.d.ts +21 -0
- package/dist/src/seller-catalog.js +17 -0
- package/dist/src/seller-route-planner.d.ts +4 -1
- package/dist/src/seller-route-planner.js +3 -0
- package/dist/src/seller-routing-strategy.d.ts +3 -0
- package/dist/src/terminal-detect.d.ts +1 -1
- package/dist/src/terminal-detect.js +3 -2
- package/dist/src/workdir.d.ts +10 -0
- package/dist/src/workdir.js +26 -0
- package/package.json +15 -2
- package/static/ui/assets/index-Djfl9tw5.js +271 -0
- package/static/ui/assets/index-DkfztCkn.css +1 -0
- package/static/ui/index.html +2 -2
- package/dist/src/buyer-store.d.ts.map +0 -1
- package/dist/src/buyer-store.js.map +0 -1
- package/dist/src/clawtip-bootstrap.d.ts.map +0 -1
- package/dist/src/clawtip-bootstrap.js.map +0 -1
- package/dist/src/cli.d.ts.map +0 -1
- package/dist/src/cli.js.map +0 -1
- package/dist/src/credit-tracker.d.ts.map +0 -1
- package/dist/src/credit-tracker.js.map +0 -1
- package/dist/src/daemon.d.ts.map +0 -1
- package/dist/src/daemon.js.map +0 -1
- package/dist/src/doctor-clawtip-wallet.d.ts.map +0 -1
- package/dist/src/doctor-clawtip-wallet.js.map +0 -1
- package/dist/src/doctor-diagnostics.d.ts.map +0 -1
- package/dist/src/doctor-diagnostics.js.map +0 -1
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js.map +0 -1
- package/dist/src/init-clawtip-activation.d.ts.map +0 -1
- package/dist/src/init-clawtip-activation.js.map +0 -1
- package/dist/src/init-payment-options.d.ts.map +0 -1
- package/dist/src/init-payment-options.js.map +0 -1
- package/dist/src/init-setup.d.ts.map +0 -1
- package/dist/src/init-setup.js.map +0 -1
- package/dist/src/model-index.d.ts.map +0 -1
- package/dist/src/model-index.js.map +0 -1
- package/dist/src/package-update.d.ts.map +0 -1
- package/dist/src/package-update.js.map +0 -1
- package/dist/src/prewarm-cache.d.ts.map +0 -1
- package/dist/src/prewarm-cache.js.map +0 -1
- package/dist/src/prewarm-scheduler.d.ts.map +0 -1
- package/dist/src/prewarm-scheduler.js.map +0 -1
- package/dist/src/provider-install.d.ts.map +0 -1
- package/dist/src/provider-install.js.map +0 -1
- package/dist/src/provider-routing-config.d.ts.map +0 -1
- package/dist/src/provider-routing-config.js.map +0 -1
- package/dist/src/registry-trust.d.ts.map +0 -1
- package/dist/src/registry-trust.js.map +0 -1
- package/dist/src/route-failover.d.ts.map +0 -1
- package/dist/src/route-failover.js.map +0 -1
- package/dist/src/seller-catalog.d.ts.map +0 -1
- package/dist/src/seller-catalog.js.map +0 -1
- package/dist/src/seller-concurrency-limiter.d.ts.map +0 -1
- package/dist/src/seller-concurrency-limiter.js.map +0 -1
- package/dist/src/seller-metadata-cache.d.ts.map +0 -1
- package/dist/src/seller-metadata-cache.js.map +0 -1
- package/dist/src/seller-pool.d.ts.map +0 -1
- package/dist/src/seller-pool.js.map +0 -1
- package/dist/src/seller-route-planner.d.ts.map +0 -1
- package/dist/src/seller-route-planner.js.map +0 -1
- package/dist/src/seller-routing-config.d.ts.map +0 -1
- package/dist/src/seller-routing-config.js.map +0 -1
- package/dist/src/seller-routing-strategy.d.ts.map +0 -1
- package/dist/src/seller-routing-strategy.js.map +0 -1
- package/dist/src/stream-failover.d.ts.map +0 -1
- package/dist/src/stream-failover.js.map +0 -1
- package/dist/src/tb-clawtip-proof.d.ts.map +0 -1
- package/dist/src/tb-clawtip-proof.js.map +0 -1
- package/dist/src/tb-proxyd.d.ts.map +0 -1
- package/dist/src/tb-proxyd.js.map +0 -1
- package/dist/src/terminal-detect.d.ts.map +0 -1
- package/dist/src/terminal-detect.js.map +0 -1
- package/dist/src/terminal-image.d.ts.map +0 -1
- package/dist/src/terminal-image.js.map +0 -1
- package/src/buyer-store.ts +0 -1090
- package/src/clawtip-bootstrap.ts +0 -65
- package/src/cli.ts +0 -2243
- package/src/credit-tracker.ts +0 -295
- package/src/daemon.ts +0 -5475
- package/src/doctor-clawtip-wallet.ts +0 -95
- package/src/doctor-diagnostics.ts +0 -1026
- package/src/index.ts +0 -16
- package/src/init-clawtip-activation.ts +0 -695
- package/src/init-payment-options.ts +0 -373
- package/src/init-setup.ts +0 -165
- package/src/model-index.ts +0 -278
- package/src/package-update.ts +0 -311
- package/src/prewarm-cache.ts +0 -485
- package/src/prewarm-scheduler.ts +0 -675
- package/src/provider-install.ts +0 -1006
- package/src/provider-routing-config.ts +0 -410
- package/src/registry-trust.ts +0 -51
- package/src/route-failover.ts +0 -304
- package/src/seller-catalog.ts +0 -505
- package/src/seller-concurrency-limiter.ts +0 -161
- package/src/seller-metadata-cache.ts +0 -91
- package/src/seller-pool.ts +0 -557
- package/src/seller-route-planner.ts +0 -513
- package/src/seller-routing-config.ts +0 -211
- package/src/seller-routing-strategy.ts +0 -362
- package/src/stream-failover.ts +0 -152
- package/src/tb-clawtip-proof.ts +0 -28
- package/src/tb-proxyd.ts +0 -101
- package/src/terminal-detect.ts +0 -333
- package/src/terminal-image.ts +0 -228
- package/static/ui/assets/index-0MVXD7bH.css +0 -1
- package/static/ui/assets/index-BVbeDEwq.js +0 -271
- package/static/ui/assets/index-BVbeDEwq.js.map +0 -1
- package/tests/cli-routing.test.ts +0 -363
- package/tests/control-plane-ui-endpoints.test.ts +0 -1630
- package/tests/credit-tracker.test.ts +0 -165
- package/tests/daemon-413-fallback.test.ts +0 -92
- package/tests/daemon-classify.test.ts +0 -452
- package/tests/daemon-roles.test.ts +0 -92
- package/tests/daemon-trusted-registry-cache.test.ts +0 -132
- package/tests/e2e.test.ts +0 -366
- package/tests/image-generation-e2e.test.ts +0 -230
- package/tests/model-index.test.ts +0 -198
- package/tests/package-update.test.ts +0 -147
- package/tests/prewarm-cache.test.ts +0 -296
- package/tests/prewarm-scheduler.test.ts +0 -367
- package/tests/provider-routing-config.test.ts +0 -150
- package/tests/registry-trust.test.ts +0 -28
- package/tests/route-failover.test.ts +0 -222
- package/tests/seller-catalog-413.test.ts +0 -120
- package/tests/seller-catalog-utilities.test.ts +0 -124
- package/tests/seller-concurrency-limiter.test.ts +0 -83
- package/tests/seller-metadata-cache.test.ts +0 -89
- package/tests/seller-pool.test.ts +0 -365
- package/tests/seller-route-planner.test.ts +0 -312
- package/tests/seller-routing-config.test.ts +0 -124
- package/tests/seller-routing-strategy.test.ts +0 -167
- package/tests/stream-failover.test.ts +0 -52
- package/tests/thousand-seller.test.ts +0 -151
- package/tests/tokenbuddy.test.ts +0 -4043
- package/tsconfig.json +0 -8
package/src/buyer-store.ts
DELETED
|
@@ -1,1090 +0,0 @@
|
|
|
1
|
-
// @ts-ignore node:sqlite types are not present in the current @types/node release.
|
|
2
|
-
import { DatabaseSync } from "node:sqlite";
|
|
3
|
-
import * as crypto from "crypto";
|
|
4
|
-
import * as fs from "fs";
|
|
5
|
-
import * as os from "os";
|
|
6
|
-
import * as path from "path";
|
|
7
|
-
import { createModuleLogger } from "@tokenbuddy/logging";
|
|
8
|
-
|
|
9
|
-
const logger = createModuleLogger("tb-proxyd");
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* buyer 端缓存的 seller access token + 余额快照。
|
|
13
|
-
* v1.2 PR-fix:每次取出都校验 `expiresAt`,避免把过期 token 发给 seller 导致 401。
|
|
14
|
-
*/
|
|
15
|
-
export interface CachedToken {
|
|
16
|
-
token: string;
|
|
17
|
-
balanceMicros: number;
|
|
18
|
-
reservedMicros: number;
|
|
19
|
-
spentMicros: number;
|
|
20
|
-
balanceSource?: string;
|
|
21
|
-
/**
|
|
22
|
-
* ISO-8601 expiry timestamp sourced from the seller's
|
|
23
|
-
* `/purchase/complete` response. v1.2 PR-fix: the buyer now
|
|
24
|
-
* checks this on every cached-token lookup so we never serve a
|
|
25
|
-
* stale access token to the upstream.
|
|
26
|
-
*/
|
|
27
|
-
expiresAt?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* 持久化的支付方式配置。
|
|
32
|
-
* `isDefault` 用于 `tb init` / 路由层在没有显式 method 时选默认。
|
|
33
|
-
*/
|
|
34
|
-
export interface PaymentConfig {
|
|
35
|
-
method: string;
|
|
36
|
-
enabled: boolean;
|
|
37
|
-
isDefault: boolean;
|
|
38
|
-
config?: Record<string, unknown>;
|
|
39
|
-
updatedAt: string;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* 待结算的 purchase 快照(buyer 端在 `/purchase/create` 之后、`/purchase/complete` 之前的临时状态)。
|
|
44
|
-
*/
|
|
45
|
-
export interface PendingPurchaseInput {
|
|
46
|
-
purchaseId: string;
|
|
47
|
-
sellerKey: string;
|
|
48
|
-
modelId: string;
|
|
49
|
-
paymentMethod: string;
|
|
50
|
-
amountUsdMicros: number;
|
|
51
|
-
status: string;
|
|
52
|
-
paymentReference?: string;
|
|
53
|
-
expiresAt?: string;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* 完成态 purchase 的账本输入。`paymentReference` 会被 hash 之后落库(不存明文)。
|
|
58
|
-
*/
|
|
59
|
-
export interface PurchaseLedgerInput {
|
|
60
|
-
purchaseId: string;
|
|
61
|
-
sellerKey: string;
|
|
62
|
-
modelId: string;
|
|
63
|
-
paymentMethod: string;
|
|
64
|
-
status: string;
|
|
65
|
-
creditMicros: number;
|
|
66
|
-
currency: string;
|
|
67
|
-
paymentAmount?: string;
|
|
68
|
-
paymentAmountMinor?: number;
|
|
69
|
-
paymentCurrency?: string;
|
|
70
|
-
paymentReference?: string;
|
|
71
|
-
completedAt?: string;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* `tb doctor` 与审计日志消费的"安全版" purchase 账本条目。
|
|
76
|
-
* 关键差异:`paymentReference` 已 hash 化为 `paymentReferenceHash`,永不暴露明文。
|
|
77
|
-
*/
|
|
78
|
-
export interface SafePurchaseLedgerEntry {
|
|
79
|
-
purchaseId: string;
|
|
80
|
-
sellerKey: string;
|
|
81
|
-
modelId: string;
|
|
82
|
-
paymentMethod: string;
|
|
83
|
-
status: string;
|
|
84
|
-
creditMicros: number;
|
|
85
|
-
currency: string;
|
|
86
|
-
paymentAmount?: string;
|
|
87
|
-
paymentAmountMinor?: number;
|
|
88
|
-
paymentCurrency?: string;
|
|
89
|
-
paymentReferenceHash?: string;
|
|
90
|
-
createdAt: string;
|
|
91
|
-
completedAt?: string;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* buyer 端单次推理请求的账本输入。
|
|
96
|
-
* `prompt` / `response` 在落库前会 hash,原始内容永不落盘。
|
|
97
|
-
*
|
|
98
|
-
* v1.2 tb-ui v1 (Iter 4):补 7 字段 — 排查 / 性能 / 路由决策溯源必备。
|
|
99
|
-
* - `ttftMs`:首字延迟(从请求发起到首 byte),`forwardProxyRequest` 计算
|
|
100
|
-
* - `fallbackCount`:本请求实际走过的 seller 数(> 1 即发生 failover)
|
|
101
|
-
* - `routeReason`:`planSellerRouteSet().reason`(如 `fullAuto:balanced:routes_3`)
|
|
102
|
-
* - `falloverChain`:failover 路径上 seller id 数组(空 = 主路径成功)
|
|
103
|
-
* - `upstreamStatus`:seller 透传的上游健康状态(`healthy|degraded|unhealthy|unknown`)
|
|
104
|
-
* - `durationMs`:总耗时(req start → response end 或失败)
|
|
105
|
-
* - `paymentMethod`:本请求使用的支付方式(`mock|clawtip`)
|
|
106
|
-
*/
|
|
107
|
-
export interface InferenceLedgerInput {
|
|
108
|
-
requestId: string;
|
|
109
|
-
sellerKey: string;
|
|
110
|
-
modelId: string;
|
|
111
|
-
endpoint: string;
|
|
112
|
-
status: string;
|
|
113
|
-
promptTokens: number;
|
|
114
|
-
completionTokens: number;
|
|
115
|
-
cacheReadTokens?: number;
|
|
116
|
-
billedMicros: number;
|
|
117
|
-
estimatedMicros?: number;
|
|
118
|
-
settledMicros?: number;
|
|
119
|
-
settledUsdMicros?: number;
|
|
120
|
-
priceVersion?: string;
|
|
121
|
-
inputPriceMicrosPer1m?: number;
|
|
122
|
-
outputPriceMicrosPer1m?: number;
|
|
123
|
-
cacheReadPriceMicrosPer1m?: number;
|
|
124
|
-
inputCostMicros?: number;
|
|
125
|
-
outputCostMicros?: number;
|
|
126
|
-
cacheReadCostMicros?: number;
|
|
127
|
-
originalUsdMicros?: number;
|
|
128
|
-
billingMultiplier?: number;
|
|
129
|
-
serviceTier?: string;
|
|
130
|
-
billingUnit?: "tokens" | "images";
|
|
131
|
-
imageCount?: number;
|
|
132
|
-
imageSize?: string;
|
|
133
|
-
imageQuality?: string;
|
|
134
|
-
imageOutputFormat?: string;
|
|
135
|
-
imageOutputTokens?: number;
|
|
136
|
-
imageOutputCostMicros?: number;
|
|
137
|
-
imageCostMicrosPerImage?: number;
|
|
138
|
-
balanceSnapshotMicros?: number;
|
|
139
|
-
balanceSource?: string;
|
|
140
|
-
prompt?: string;
|
|
141
|
-
response?: string;
|
|
142
|
-
ttftMs?: number;
|
|
143
|
-
fallbackCount?: number;
|
|
144
|
-
routeReason?: string;
|
|
145
|
-
falloverChain?: string[];
|
|
146
|
-
upstreamStatus?: string;
|
|
147
|
-
durationMs?: number;
|
|
148
|
-
paymentMethod?: string;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* `tb doctor` 与审计日志消费的"安全版"推理账本条目。
|
|
153
|
-
* 关键差异:`prompt` / `response` 已 hash 化为 `promptHash` / `responseHash`。
|
|
154
|
-
*/
|
|
155
|
-
export interface SafeInferenceLedgerEntry {
|
|
156
|
-
requestId: string;
|
|
157
|
-
sellerKey: string;
|
|
158
|
-
modelId: string;
|
|
159
|
-
endpoint: string;
|
|
160
|
-
status: string;
|
|
161
|
-
promptTokens: number;
|
|
162
|
-
completionTokens: number;
|
|
163
|
-
cacheReadTokens: number;
|
|
164
|
-
billedMicros: number;
|
|
165
|
-
estimatedMicros?: number;
|
|
166
|
-
settledMicros?: number;
|
|
167
|
-
settledUsdMicros?: number;
|
|
168
|
-
priceVersion?: string;
|
|
169
|
-
inputPriceMicrosPer1m?: number;
|
|
170
|
-
outputPriceMicrosPer1m?: number;
|
|
171
|
-
cacheReadPriceMicrosPer1m?: number;
|
|
172
|
-
inputCostMicros?: number;
|
|
173
|
-
outputCostMicros?: number;
|
|
174
|
-
cacheReadCostMicros?: number;
|
|
175
|
-
originalUsdMicros?: number;
|
|
176
|
-
billingMultiplier?: number;
|
|
177
|
-
serviceTier?: string;
|
|
178
|
-
billingUnit?: "tokens" | "images";
|
|
179
|
-
imageCount?: number;
|
|
180
|
-
imageSize?: string;
|
|
181
|
-
imageQuality?: string;
|
|
182
|
-
imageOutputFormat?: string;
|
|
183
|
-
imageOutputTokens?: number;
|
|
184
|
-
imageOutputCostMicros?: number;
|
|
185
|
-
imageCostMicrosPerImage?: number;
|
|
186
|
-
balanceSnapshotMicros?: number;
|
|
187
|
-
balanceSource?: string;
|
|
188
|
-
promptHash?: string;
|
|
189
|
-
responseHash?: string;
|
|
190
|
-
createdAt: string;
|
|
191
|
-
// v1.2 tb-ui v1 (Iter 4):新增 7 字段
|
|
192
|
-
ttftMs?: number;
|
|
193
|
-
fallbackCount?: number;
|
|
194
|
-
routeReason?: string;
|
|
195
|
-
falloverChain?: string[];
|
|
196
|
-
upstreamStatus?: string;
|
|
197
|
-
durationMs?: number;
|
|
198
|
-
paymentMethod?: string;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* seller token 余额快照的入参。
|
|
203
|
-
* 每次推理后由 `BuyerStore.recordTokenBalance` 调用,作为路由决策的"最近一次真实余额"。
|
|
204
|
-
*/
|
|
205
|
-
export interface TokenBalanceSnapshotInput {
|
|
206
|
-
sellerKey: string;
|
|
207
|
-
token?: string;
|
|
208
|
-
tokenClass?: string;
|
|
209
|
-
balanceMicros: number;
|
|
210
|
-
reservedMicros?: number;
|
|
211
|
-
spentMicros?: number;
|
|
212
|
-
balanceSource: string;
|
|
213
|
-
expiresAt?: string;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* `BuyerStore.summary()` 的返回结构,用于 `tb doctor` 展示 DB 健康度。
|
|
218
|
-
*/
|
|
219
|
-
export interface BuyerStoreSummary {
|
|
220
|
-
journalMode: string;
|
|
221
|
-
paymentsCount: number;
|
|
222
|
-
pendingPurchasesCount: number;
|
|
223
|
-
purchaseLedgerCount: number;
|
|
224
|
-
inferenceLedgerCount: number;
|
|
225
|
-
providerRuntimeConfigCount: number;
|
|
226
|
-
daemonRuntimeConfigCount: number;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* `tb init` 时记录的 provider 安装快照(用于后续 `tb rollback`)。
|
|
231
|
-
* `existed` 标记安装前文件是否已存在,避免回滚覆盖用户原始内容。
|
|
232
|
-
*/
|
|
233
|
-
export interface ProviderInstallSnapshot {
|
|
234
|
-
providerId: string;
|
|
235
|
-
files: Array<{
|
|
236
|
-
path: string;
|
|
237
|
-
existed: boolean;
|
|
238
|
-
content?: string;
|
|
239
|
-
}>;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* provider 维度运行时配置(由 `tb init` / `tb config` 写入)。
|
|
244
|
-
* 存的是各 provider(如 opencode / cline)需要的 model mapping 等。
|
|
245
|
-
*/
|
|
246
|
-
export interface ProviderRuntimeConfigRecord<T = unknown> {
|
|
247
|
-
providerId: string;
|
|
248
|
-
config: T;
|
|
249
|
-
createdAt: string;
|
|
250
|
-
updatedAt: string;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* 守护进程维度运行时配置(按 `configKey` 命名空间)。
|
|
255
|
-
* 比如 `seller_routing` / `prewarm` / `inference` 等 daemon 子系统的配置。
|
|
256
|
-
*/
|
|
257
|
-
export interface DaemonRuntimeConfigRecord<T = unknown> {
|
|
258
|
-
configKey: string;
|
|
259
|
-
config: T;
|
|
260
|
-
createdAt: string;
|
|
261
|
-
updatedAt: string;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* `BuyerStore` 构造选项。
|
|
266
|
-
* 优先用 `dbPath`(绝对路径),否则用 `root`(目录)+ 默认 `buyer-store.db`。
|
|
267
|
-
*/
|
|
268
|
-
export interface BuyerStoreOptions {
|
|
269
|
-
root?: string;
|
|
270
|
-
dbPath?: string;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
function nowIso(): string {
|
|
274
|
-
return new Date().toISOString();
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function safeHash(value?: string): string | undefined {
|
|
278
|
-
if (!value) {
|
|
279
|
-
return undefined;
|
|
280
|
-
}
|
|
281
|
-
return crypto.createHash("sha256").update(value).digest("hex");
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
function boolFromSql(value: unknown): boolean {
|
|
285
|
-
return value === 1 || value === true;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
function ensureDirForFile(filePath: string): void {
|
|
289
|
-
const dir = path.dirname(filePath);
|
|
290
|
-
if (dir !== "." && !fs.existsSync(dir)) {
|
|
291
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* 解析 buyer store 的 DB 路径。
|
|
297
|
-
* 优先级:`dbPath` → `TOKENBUDDY_BUYER_STORE` env → `~/.tokenbuddy-store/buyer-store.db`。
|
|
298
|
-
*
|
|
299
|
-
* @param options 解析选项
|
|
300
|
-
* @returns DB 文件绝对路径
|
|
301
|
-
*/
|
|
302
|
-
export function resolveBuyerStorePath(options: BuyerStoreOptions = {}): string {
|
|
303
|
-
if (options.dbPath) {
|
|
304
|
-
return options.dbPath;
|
|
305
|
-
}
|
|
306
|
-
const root = options.root || process.env.TOKENBUDDY_BUYER_STORE || path.join(os.homedir(), ".tokenbuddy-store");
|
|
307
|
-
return path.join(root, "buyer-store.db");
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* buyer 端 SQLite 持久化层。
|
|
312
|
-
* 维护:支付配置、pending purchase、purchase 账本、inference 账本、provider / daemon runtime config、token 余额快照。
|
|
313
|
-
* 内部用 SHA256 摘要替代明文敏感字段(prompt / response / paymentReference / token)。
|
|
314
|
-
*/
|
|
315
|
-
export class BuyerStore {
|
|
316
|
-
private db: DatabaseSync;
|
|
317
|
-
|
|
318
|
-
constructor(options: BuyerStoreOptions | string = {}) {
|
|
319
|
-
const dbPath = typeof options === "string" ? options : resolveBuyerStorePath(options);
|
|
320
|
-
ensureDirForFile(dbPath);
|
|
321
|
-
this.db = new DatabaseSync(dbPath);
|
|
322
|
-
this.db.exec("PRAGMA journal_mode = WAL;");
|
|
323
|
-
this.initSchema();
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
public journalMode(): string {
|
|
327
|
-
const row = this.db.prepare("PRAGMA journal_mode;").get() as { journal_mode: string };
|
|
328
|
-
return row.journal_mode;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
public summary(): BuyerStoreSummary {
|
|
332
|
-
return {
|
|
333
|
-
journalMode: this.journalMode(),
|
|
334
|
-
paymentsCount: this.countRows("payment_config"),
|
|
335
|
-
pendingPurchasesCount: this.countRows("pending_purchases"),
|
|
336
|
-
purchaseLedgerCount: this.countRows("purchase_ledger"),
|
|
337
|
-
inferenceLedgerCount: this.countRows("inference_ledger"),
|
|
338
|
-
providerRuntimeConfigCount: this.countRows("provider_runtime_config"),
|
|
339
|
-
daemonRuntimeConfigCount: this.countRows("daemon_runtime_config")
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
public saveProviderInstallSnapshot(snapshot: ProviderInstallSnapshot): void {
|
|
344
|
-
const updatedAt = nowIso();
|
|
345
|
-
this.db.prepare(
|
|
346
|
-
`INSERT OR REPLACE INTO provider_install_state (
|
|
347
|
-
provider_id, snapshot_json, created_at, updated_at
|
|
348
|
-
) VALUES (
|
|
349
|
-
?, ?,
|
|
350
|
-
COALESCE((SELECT created_at FROM provider_install_state WHERE provider_id = ?), ?),
|
|
351
|
-
?
|
|
352
|
-
)`
|
|
353
|
-
).run(
|
|
354
|
-
snapshot.providerId,
|
|
355
|
-
JSON.stringify(snapshot),
|
|
356
|
-
snapshot.providerId,
|
|
357
|
-
updatedAt,
|
|
358
|
-
updatedAt
|
|
359
|
-
);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
public getProviderInstallSnapshot(providerId: string): ProviderInstallSnapshot | undefined {
|
|
363
|
-
const row = this.db.prepare(
|
|
364
|
-
"SELECT snapshot_json FROM provider_install_state WHERE provider_id = ?"
|
|
365
|
-
).get(providerId) as { snapshot_json: string } | undefined;
|
|
366
|
-
if (!row) {
|
|
367
|
-
return undefined;
|
|
368
|
-
}
|
|
369
|
-
return JSON.parse(row.snapshot_json) as ProviderInstallSnapshot;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
public removeProviderInstallSnapshot(providerId: string): boolean {
|
|
373
|
-
const result = this.db.prepare("DELETE FROM provider_install_state WHERE provider_id = ?").run(providerId) as { changes: number };
|
|
374
|
-
return result.changes > 0;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
public saveProviderRuntimeConfig(providerId: string, config: unknown): void {
|
|
378
|
-
const updatedAt = nowIso();
|
|
379
|
-
this.db.prepare(
|
|
380
|
-
`INSERT OR REPLACE INTO provider_runtime_config (
|
|
381
|
-
provider_id, runtime_json, created_at, updated_at
|
|
382
|
-
) VALUES (
|
|
383
|
-
?, ?,
|
|
384
|
-
COALESCE((SELECT created_at FROM provider_runtime_config WHERE provider_id = ?), ?),
|
|
385
|
-
?
|
|
386
|
-
)`
|
|
387
|
-
).run(providerId, JSON.stringify(config), providerId, updatedAt, updatedAt);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
public getProviderRuntimeConfig<T = unknown>(providerId: string): ProviderRuntimeConfigRecord<T> | undefined {
|
|
391
|
-
const row = this.db.prepare(
|
|
392
|
-
`SELECT provider_id, runtime_json, created_at, updated_at
|
|
393
|
-
FROM provider_runtime_config
|
|
394
|
-
WHERE provider_id = ?`
|
|
395
|
-
).get(providerId) as {
|
|
396
|
-
provider_id: string;
|
|
397
|
-
runtime_json: string;
|
|
398
|
-
created_at: string;
|
|
399
|
-
updated_at: string;
|
|
400
|
-
} | undefined;
|
|
401
|
-
if (!row) {
|
|
402
|
-
return undefined;
|
|
403
|
-
}
|
|
404
|
-
return {
|
|
405
|
-
providerId: row.provider_id,
|
|
406
|
-
config: JSON.parse(row.runtime_json) as T,
|
|
407
|
-
createdAt: row.created_at,
|
|
408
|
-
updatedAt: row.updated_at
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
public removeProviderRuntimeConfig(providerId: string): boolean {
|
|
413
|
-
const result = this.db.prepare("DELETE FROM provider_runtime_config WHERE provider_id = ?").run(providerId) as { changes: number };
|
|
414
|
-
return result.changes > 0;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
public saveDaemonRuntimeConfig(configKey: string, config: unknown): void {
|
|
418
|
-
const updatedAt = nowIso();
|
|
419
|
-
this.db.prepare(
|
|
420
|
-
`INSERT OR REPLACE INTO daemon_runtime_config (
|
|
421
|
-
config_key, config_json, created_at, updated_at
|
|
422
|
-
) VALUES (
|
|
423
|
-
?, ?,
|
|
424
|
-
COALESCE((SELECT created_at FROM daemon_runtime_config WHERE config_key = ?), ?),
|
|
425
|
-
?
|
|
426
|
-
)`
|
|
427
|
-
).run(configKey, JSON.stringify(config), configKey, updatedAt, updatedAt);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
public getDaemonRuntimeConfig<T = unknown>(configKey: string): DaemonRuntimeConfigRecord<T> | undefined {
|
|
431
|
-
const row = this.db.prepare(
|
|
432
|
-
`SELECT config_key, config_json, created_at, updated_at
|
|
433
|
-
FROM daemon_runtime_config
|
|
434
|
-
WHERE config_key = ?`
|
|
435
|
-
).get(configKey) as {
|
|
436
|
-
config_key: string;
|
|
437
|
-
config_json: string;
|
|
438
|
-
created_at: string;
|
|
439
|
-
updated_at: string;
|
|
440
|
-
} | undefined;
|
|
441
|
-
if (!row) {
|
|
442
|
-
return undefined;
|
|
443
|
-
}
|
|
444
|
-
return {
|
|
445
|
-
configKey: row.config_key,
|
|
446
|
-
config: JSON.parse(row.config_json) as T,
|
|
447
|
-
createdAt: row.created_at,
|
|
448
|
-
updatedAt: row.updated_at
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
public removeDaemonRuntimeConfig(configKey: string): boolean {
|
|
453
|
-
const result = this.db.prepare("DELETE FROM daemon_runtime_config WHERE config_key = ?").run(configKey) as { changes: number };
|
|
454
|
-
return result.changes > 0;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
public getToken(sellerKey: string): CachedToken | undefined {
|
|
458
|
-
const stmt = this.db.prepare(
|
|
459
|
-
"SELECT token, balance_micros, reserved_micros, spent_micros, balance_source, expires_at FROM token_cache WHERE seller_key = ?"
|
|
460
|
-
);
|
|
461
|
-
const row = stmt.get(sellerKey) as {
|
|
462
|
-
token: string;
|
|
463
|
-
balance_micros: number;
|
|
464
|
-
reserved_micros: number;
|
|
465
|
-
spent_micros: number;
|
|
466
|
-
balance_source: string | null;
|
|
467
|
-
expires_at: string | null;
|
|
468
|
-
} | undefined;
|
|
469
|
-
if (!row) {
|
|
470
|
-
return undefined;
|
|
471
|
-
}
|
|
472
|
-
return {
|
|
473
|
-
token: row.token,
|
|
474
|
-
balanceMicros: row.balance_micros,
|
|
475
|
-
reservedMicros: row.reserved_micros,
|
|
476
|
-
spentMicros: row.spent_micros,
|
|
477
|
-
balanceSource: row.balance_source || undefined,
|
|
478
|
-
expiresAt: row.expires_at || undefined
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
public saveToken(sellerKey: string, token: string, tokenClass: string, balanceMicros: number, expiresAt: string): void {
|
|
483
|
-
const stmt = this.db.prepare(
|
|
484
|
-
`INSERT OR REPLACE INTO token_cache (
|
|
485
|
-
seller_key, token, token_class, balance_micros, reserved_micros,
|
|
486
|
-
spent_micros, balance_source, expires_at, updated_at
|
|
487
|
-
) VALUES (?, ?, ?, ?, 0, 0, 'purchase_complete', ?, ?)`
|
|
488
|
-
);
|
|
489
|
-
stmt.run(sellerKey, token, tokenClass, balanceMicros, expiresAt, nowIso());
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
public reconcileTokenBalance(input: TokenBalanceSnapshotInput): void {
|
|
493
|
-
const current = this.db.prepare(
|
|
494
|
-
"SELECT token, token_class, expires_at FROM token_cache WHERE seller_key = ?"
|
|
495
|
-
).get(input.sellerKey) as {
|
|
496
|
-
token: string;
|
|
497
|
-
token_class: string;
|
|
498
|
-
expires_at: string;
|
|
499
|
-
} | undefined;
|
|
500
|
-
const token = input.token || current?.token;
|
|
501
|
-
const tokenClass = input.tokenClass || current?.token_class || "seller";
|
|
502
|
-
const expiresAt = input.expiresAt || current?.expires_at || new Date(Date.now() + 86400 * 1000).toISOString();
|
|
503
|
-
if (!token) {
|
|
504
|
-
return;
|
|
505
|
-
}
|
|
506
|
-
this.db.prepare(
|
|
507
|
-
`INSERT OR REPLACE INTO token_cache (
|
|
508
|
-
seller_key, token, token_class, balance_micros, reserved_micros,
|
|
509
|
-
spent_micros, balance_source, expires_at, updated_at
|
|
510
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
511
|
-
).run(
|
|
512
|
-
input.sellerKey,
|
|
513
|
-
token,
|
|
514
|
-
tokenClass,
|
|
515
|
-
input.balanceMicros,
|
|
516
|
-
input.reservedMicros ?? 0,
|
|
517
|
-
input.spentMicros ?? 0,
|
|
518
|
-
input.balanceSource,
|
|
519
|
-
expiresAt,
|
|
520
|
-
nowIso()
|
|
521
|
-
);
|
|
522
|
-
logger.info("token.cache.reconciled", "token cache reconciled from seller balance snapshot", {
|
|
523
|
-
sellerKey: input.sellerKey,
|
|
524
|
-
balanceMicros: input.balanceMicros,
|
|
525
|
-
reservedMicros: input.reservedMicros ?? 0,
|
|
526
|
-
spentMicros: input.spentMicros ?? 0,
|
|
527
|
-
balanceSource: input.balanceSource
|
|
528
|
-
});
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
public markTokenStale(sellerKey: string): void {
|
|
532
|
-
this.db.prepare(
|
|
533
|
-
"UPDATE token_cache SET balance_micros = 0, balance_source = 'stale', updated_at = ? WHERE seller_key = ?"
|
|
534
|
-
).run(nowIso(), sellerKey);
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
public listPayments(): PaymentConfig[] {
|
|
538
|
-
const rows = this.db.prepare(
|
|
539
|
-
`SELECT method, enabled, is_default, config_json, updated_at
|
|
540
|
-
FROM payment_config
|
|
541
|
-
ORDER BY method ASC`
|
|
542
|
-
).all() as Array<{
|
|
543
|
-
method: string;
|
|
544
|
-
enabled: number;
|
|
545
|
-
is_default: number;
|
|
546
|
-
config_json: string | null;
|
|
547
|
-
updated_at: string;
|
|
548
|
-
}>;
|
|
549
|
-
|
|
550
|
-
return rows.map(row => ({
|
|
551
|
-
method: row.method,
|
|
552
|
-
enabled: boolFromSql(row.enabled),
|
|
553
|
-
isDefault: boolFromSql(row.is_default),
|
|
554
|
-
config: row.config_json ? JSON.parse(row.config_json) as Record<string, unknown> : undefined,
|
|
555
|
-
updatedAt: row.updated_at
|
|
556
|
-
}));
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
public savePayment(config: Omit<PaymentConfig, "updatedAt">): void {
|
|
560
|
-
const updatedAt = nowIso();
|
|
561
|
-
if (config.isDefault) {
|
|
562
|
-
this.db.prepare("UPDATE payment_config SET is_default = 0").run();
|
|
563
|
-
}
|
|
564
|
-
this.db.prepare(
|
|
565
|
-
`INSERT OR REPLACE INTO payment_config (
|
|
566
|
-
method, enabled, is_default, config_json, created_at, updated_at
|
|
567
|
-
) VALUES (
|
|
568
|
-
?, ?, ?, ?,
|
|
569
|
-
COALESCE((SELECT created_at FROM payment_config WHERE method = ?), ?),
|
|
570
|
-
?
|
|
571
|
-
)`
|
|
572
|
-
).run(
|
|
573
|
-
config.method,
|
|
574
|
-
config.enabled ? 1 : 0,
|
|
575
|
-
config.isDefault ? 1 : 0,
|
|
576
|
-
config.config ? JSON.stringify(config.config) : null,
|
|
577
|
-
config.method,
|
|
578
|
-
updatedAt,
|
|
579
|
-
updatedAt
|
|
580
|
-
);
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
public getPayment(method: string): PaymentConfig | undefined {
|
|
584
|
-
return this.listPayments().find((payment) => payment.method === method);
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
public removePayment(method: string): boolean {
|
|
588
|
-
const result = this.db.prepare("DELETE FROM payment_config WHERE method = ?").run(method) as { changes: number };
|
|
589
|
-
return result.changes > 0;
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
public upsertPendingPurchase(input: PendingPurchaseInput): void {
|
|
593
|
-
const updatedAt = nowIso();
|
|
594
|
-
this.db.prepare(
|
|
595
|
-
`INSERT OR REPLACE INTO pending_purchases (
|
|
596
|
-
purchase_id, seller_key, model_id, payment_method, amount_usd_micros,
|
|
597
|
-
status, payment_reference_hash, created_at, updated_at, expires_at
|
|
598
|
-
) VALUES (
|
|
599
|
-
?, ?, ?, ?, ?, ?, ?,
|
|
600
|
-
COALESCE((SELECT created_at FROM pending_purchases WHERE purchase_id = ?), ?),
|
|
601
|
-
?, ?
|
|
602
|
-
)`
|
|
603
|
-
).run(
|
|
604
|
-
input.purchaseId,
|
|
605
|
-
input.sellerKey,
|
|
606
|
-
input.modelId,
|
|
607
|
-
input.paymentMethod,
|
|
608
|
-
input.amountUsdMicros,
|
|
609
|
-
input.status,
|
|
610
|
-
safeHash(input.paymentReference) || null,
|
|
611
|
-
input.purchaseId,
|
|
612
|
-
updatedAt,
|
|
613
|
-
updatedAt,
|
|
614
|
-
input.expiresAt || null
|
|
615
|
-
);
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
public listPendingPurchases(): Array<PendingPurchaseInput & { paymentReferenceHash?: string; updatedAt: string }> {
|
|
619
|
-
const rows = this.db.prepare(
|
|
620
|
-
`SELECT purchase_id, seller_key, model_id, payment_method, amount_usd_micros,
|
|
621
|
-
status, payment_reference_hash, updated_at, expires_at
|
|
622
|
-
FROM pending_purchases
|
|
623
|
-
ORDER BY updated_at DESC, purchase_id ASC`
|
|
624
|
-
).all() as Array<{
|
|
625
|
-
purchase_id: string;
|
|
626
|
-
seller_key: string;
|
|
627
|
-
model_id: string;
|
|
628
|
-
payment_method: string;
|
|
629
|
-
amount_usd_micros: number;
|
|
630
|
-
status: string;
|
|
631
|
-
payment_reference_hash: string | null;
|
|
632
|
-
updated_at: string;
|
|
633
|
-
expires_at: string | null;
|
|
634
|
-
}>;
|
|
635
|
-
|
|
636
|
-
return rows.map(row => ({
|
|
637
|
-
purchaseId: row.purchase_id,
|
|
638
|
-
sellerKey: row.seller_key,
|
|
639
|
-
modelId: row.model_id,
|
|
640
|
-
paymentMethod: row.payment_method,
|
|
641
|
-
amountUsdMicros: row.amount_usd_micros,
|
|
642
|
-
status: row.status,
|
|
643
|
-
paymentReferenceHash: row.payment_reference_hash || undefined,
|
|
644
|
-
updatedAt: row.updated_at,
|
|
645
|
-
expiresAt: row.expires_at || undefined
|
|
646
|
-
}));
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
public recordPurchaseLedger(input: PurchaseLedgerInput): void {
|
|
650
|
-
const createdAt = nowIso();
|
|
651
|
-
this.db.prepare(
|
|
652
|
-
`INSERT INTO purchase_ledger (
|
|
653
|
-
purchase_id, seller_key, model_id, payment_method, status, credit_micros,
|
|
654
|
-
currency, payment_amount, payment_amount_minor, payment_currency,
|
|
655
|
-
payment_reference_hash, created_at, completed_at
|
|
656
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
657
|
-
).run(
|
|
658
|
-
input.purchaseId,
|
|
659
|
-
input.sellerKey,
|
|
660
|
-
input.modelId,
|
|
661
|
-
input.paymentMethod,
|
|
662
|
-
input.status,
|
|
663
|
-
input.creditMicros,
|
|
664
|
-
input.currency,
|
|
665
|
-
input.paymentAmount ?? null,
|
|
666
|
-
input.paymentAmountMinor ?? null,
|
|
667
|
-
input.paymentCurrency ?? null,
|
|
668
|
-
safeHash(input.paymentReference) || null,
|
|
669
|
-
createdAt,
|
|
670
|
-
input.completedAt || null
|
|
671
|
-
);
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
public listPurchaseLedger(): SafePurchaseLedgerEntry[] {
|
|
675
|
-
const rows = this.db.prepare(
|
|
676
|
-
`SELECT purchase_id, seller_key, model_id, payment_method, status, credit_micros,
|
|
677
|
-
currency, payment_amount, payment_amount_minor, payment_currency,
|
|
678
|
-
payment_reference_hash, created_at, completed_at
|
|
679
|
-
FROM purchase_ledger
|
|
680
|
-
ORDER BY id ASC`
|
|
681
|
-
).all() as Array<{
|
|
682
|
-
purchase_id: string;
|
|
683
|
-
seller_key: string;
|
|
684
|
-
model_id: string;
|
|
685
|
-
payment_method: string;
|
|
686
|
-
status: string;
|
|
687
|
-
credit_micros: number;
|
|
688
|
-
currency: string;
|
|
689
|
-
payment_amount: string | null;
|
|
690
|
-
payment_amount_minor: number | null;
|
|
691
|
-
payment_currency: string | null;
|
|
692
|
-
payment_reference_hash: string | null;
|
|
693
|
-
created_at: string;
|
|
694
|
-
completed_at: string | null;
|
|
695
|
-
}>;
|
|
696
|
-
|
|
697
|
-
return rows.map(row => ({
|
|
698
|
-
purchaseId: row.purchase_id,
|
|
699
|
-
sellerKey: row.seller_key,
|
|
700
|
-
modelId: row.model_id,
|
|
701
|
-
paymentMethod: row.payment_method,
|
|
702
|
-
status: row.status,
|
|
703
|
-
creditMicros: row.credit_micros,
|
|
704
|
-
currency: row.currency,
|
|
705
|
-
paymentAmount: row.payment_amount || undefined,
|
|
706
|
-
paymentAmountMinor: row.payment_amount_minor ?? undefined,
|
|
707
|
-
paymentCurrency: row.payment_currency || undefined,
|
|
708
|
-
paymentReferenceHash: row.payment_reference_hash || undefined,
|
|
709
|
-
createdAt: row.created_at,
|
|
710
|
-
completedAt: row.completed_at || undefined
|
|
711
|
-
}));
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
public recordInferenceLedger(input: InferenceLedgerInput): void {
|
|
715
|
-
this.db.prepare(
|
|
716
|
-
`INSERT INTO inference_ledger (
|
|
717
|
-
request_id, seller_key, model_id, endpoint, status, prompt_tokens,
|
|
718
|
-
completion_tokens, cache_read_tokens, billed_micros, estimated_micros, settled_micros,
|
|
719
|
-
settled_usd_micros, price_version,
|
|
720
|
-
input_price_micros_per_1m, output_price_micros_per_1m, cache_read_price_micros_per_1m,
|
|
721
|
-
input_cost_micros, output_cost_micros, cache_read_cost_micros,
|
|
722
|
-
original_usd_micros, billing_multiplier, service_tier,
|
|
723
|
-
billing_unit, image_count, image_size, image_quality, image_output_format,
|
|
724
|
-
image_output_tokens, image_output_cost_micros, image_cost_micros_per_image,
|
|
725
|
-
balance_snapshot_micros, balance_source,
|
|
726
|
-
prompt_hash, response_hash, created_at,
|
|
727
|
-
ttft_ms, fallback_count, route_reason, fallover_chain_json, upstream_status,
|
|
728
|
-
duration_ms, payment_method
|
|
729
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
730
|
-
).run(
|
|
731
|
-
input.requestId,
|
|
732
|
-
input.sellerKey,
|
|
733
|
-
input.modelId,
|
|
734
|
-
input.endpoint,
|
|
735
|
-
input.status,
|
|
736
|
-
input.promptTokens,
|
|
737
|
-
input.completionTokens,
|
|
738
|
-
input.cacheReadTokens ?? 0,
|
|
739
|
-
input.billedMicros,
|
|
740
|
-
input.estimatedMicros ?? input.billedMicros,
|
|
741
|
-
input.settledMicros ?? null,
|
|
742
|
-
input.settledUsdMicros ?? null,
|
|
743
|
-
input.priceVersion || null,
|
|
744
|
-
input.inputPriceMicrosPer1m ?? null,
|
|
745
|
-
input.outputPriceMicrosPer1m ?? null,
|
|
746
|
-
input.cacheReadPriceMicrosPer1m ?? null,
|
|
747
|
-
input.inputCostMicros ?? null,
|
|
748
|
-
input.outputCostMicros ?? null,
|
|
749
|
-
input.cacheReadCostMicros ?? null,
|
|
750
|
-
input.originalUsdMicros ?? null,
|
|
751
|
-
input.billingMultiplier ?? null,
|
|
752
|
-
input.serviceTier ?? null,
|
|
753
|
-
input.billingUnit ?? (input.endpoint === "/v1/images/generations" ? "images" : "tokens"),
|
|
754
|
-
input.imageCount ?? null,
|
|
755
|
-
input.imageSize ?? null,
|
|
756
|
-
input.imageQuality ?? null,
|
|
757
|
-
input.imageOutputFormat ?? null,
|
|
758
|
-
input.imageOutputTokens ?? null,
|
|
759
|
-
input.imageOutputCostMicros ?? null,
|
|
760
|
-
input.imageCostMicrosPerImage ?? null,
|
|
761
|
-
input.balanceSnapshotMicros ?? null,
|
|
762
|
-
input.balanceSource || "unknown",
|
|
763
|
-
safeHash(input.prompt) || null,
|
|
764
|
-
safeHash(input.response) || null,
|
|
765
|
-
nowIso(),
|
|
766
|
-
input.ttftMs ?? null,
|
|
767
|
-
input.fallbackCount ?? null,
|
|
768
|
-
input.routeReason ?? null,
|
|
769
|
-
input.falloverChain ? JSON.stringify(input.falloverChain) : null,
|
|
770
|
-
input.upstreamStatus ?? null,
|
|
771
|
-
input.durationMs ?? null,
|
|
772
|
-
input.paymentMethod ?? null
|
|
773
|
-
);
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
public listInferenceLedger(): SafeInferenceLedgerEntry[] {
|
|
777
|
-
const rows = this.db.prepare(
|
|
778
|
-
`SELECT request_id, seller_key, model_id, endpoint, status, prompt_tokens,
|
|
779
|
-
completion_tokens, cache_read_tokens, billed_micros, estimated_micros, settled_micros,
|
|
780
|
-
settled_usd_micros, price_version,
|
|
781
|
-
input_price_micros_per_1m, output_price_micros_per_1m, cache_read_price_micros_per_1m,
|
|
782
|
-
input_cost_micros, output_cost_micros, cache_read_cost_micros,
|
|
783
|
-
original_usd_micros, billing_multiplier, service_tier,
|
|
784
|
-
billing_unit, image_count, image_size, image_quality, image_output_format,
|
|
785
|
-
image_output_tokens, image_output_cost_micros, image_cost_micros_per_image,
|
|
786
|
-
balance_snapshot_micros, balance_source,
|
|
787
|
-
prompt_hash, response_hash, created_at,
|
|
788
|
-
ttft_ms, fallback_count, route_reason, fallover_chain_json, upstream_status,
|
|
789
|
-
duration_ms, payment_method
|
|
790
|
-
FROM inference_ledger
|
|
791
|
-
ORDER BY id ASC`
|
|
792
|
-
).all() as Array<{
|
|
793
|
-
request_id: string;
|
|
794
|
-
seller_key: string;
|
|
795
|
-
model_id: string;
|
|
796
|
-
endpoint: string;
|
|
797
|
-
status: string;
|
|
798
|
-
prompt_tokens: number;
|
|
799
|
-
completion_tokens: number;
|
|
800
|
-
cache_read_tokens: number;
|
|
801
|
-
billed_micros: number;
|
|
802
|
-
estimated_micros: number | null;
|
|
803
|
-
settled_micros: number | null;
|
|
804
|
-
settled_usd_micros: number | null;
|
|
805
|
-
price_version: string | null;
|
|
806
|
-
input_price_micros_per_1m: number | null;
|
|
807
|
-
output_price_micros_per_1m: number | null;
|
|
808
|
-
cache_read_price_micros_per_1m: number | null;
|
|
809
|
-
input_cost_micros: number | null;
|
|
810
|
-
output_cost_micros: number | null;
|
|
811
|
-
cache_read_cost_micros: number | null;
|
|
812
|
-
original_usd_micros: number | null;
|
|
813
|
-
billing_multiplier: number | null;
|
|
814
|
-
service_tier: string | null;
|
|
815
|
-
billing_unit: string | null;
|
|
816
|
-
image_count: number | null;
|
|
817
|
-
image_size: string | null;
|
|
818
|
-
image_quality: string | null;
|
|
819
|
-
image_output_format: string | null;
|
|
820
|
-
image_output_tokens: number | null;
|
|
821
|
-
image_output_cost_micros: number | null;
|
|
822
|
-
image_cost_micros_per_image: number | null;
|
|
823
|
-
balance_snapshot_micros: number | null;
|
|
824
|
-
balance_source: string | null;
|
|
825
|
-
prompt_hash: string | null;
|
|
826
|
-
response_hash: string | null;
|
|
827
|
-
created_at: string;
|
|
828
|
-
ttft_ms: number | null;
|
|
829
|
-
fallback_count: number | null;
|
|
830
|
-
route_reason: string | null;
|
|
831
|
-
fallover_chain_json: string | null;
|
|
832
|
-
upstream_status: string | null;
|
|
833
|
-
duration_ms: number | null;
|
|
834
|
-
payment_method: string | null;
|
|
835
|
-
}>;
|
|
836
|
-
|
|
837
|
-
return rows.map(row => ({
|
|
838
|
-
requestId: row.request_id,
|
|
839
|
-
sellerKey: row.seller_key,
|
|
840
|
-
modelId: row.model_id,
|
|
841
|
-
endpoint: row.endpoint,
|
|
842
|
-
status: row.status,
|
|
843
|
-
promptTokens: row.prompt_tokens,
|
|
844
|
-
completionTokens: row.completion_tokens,
|
|
845
|
-
cacheReadTokens: row.cache_read_tokens,
|
|
846
|
-
billedMicros: row.billed_micros,
|
|
847
|
-
estimatedMicros: row.estimated_micros ?? undefined,
|
|
848
|
-
settledMicros: row.settled_micros ?? undefined,
|
|
849
|
-
settledUsdMicros: row.settled_usd_micros ?? undefined,
|
|
850
|
-
priceVersion: row.price_version || undefined,
|
|
851
|
-
inputPriceMicrosPer1m: row.input_price_micros_per_1m ?? undefined,
|
|
852
|
-
outputPriceMicrosPer1m: row.output_price_micros_per_1m ?? undefined,
|
|
853
|
-
cacheReadPriceMicrosPer1m: row.cache_read_price_micros_per_1m ?? undefined,
|
|
854
|
-
inputCostMicros: row.input_cost_micros ?? undefined,
|
|
855
|
-
outputCostMicros: row.output_cost_micros ?? undefined,
|
|
856
|
-
cacheReadCostMicros: row.cache_read_cost_micros ?? undefined,
|
|
857
|
-
originalUsdMicros: row.original_usd_micros ?? undefined,
|
|
858
|
-
billingMultiplier: row.billing_multiplier ?? undefined,
|
|
859
|
-
serviceTier: row.service_tier || undefined,
|
|
860
|
-
billingUnit: row.billing_unit === "images" ? "images" : row.billing_unit === "tokens" ? "tokens" : undefined,
|
|
861
|
-
imageCount: row.image_count ?? undefined,
|
|
862
|
-
imageSize: row.image_size || undefined,
|
|
863
|
-
imageQuality: row.image_quality || undefined,
|
|
864
|
-
imageOutputFormat: row.image_output_format || undefined,
|
|
865
|
-
imageOutputTokens: row.image_output_tokens ?? undefined,
|
|
866
|
-
imageOutputCostMicros: row.image_output_cost_micros ?? undefined,
|
|
867
|
-
imageCostMicrosPerImage: row.image_cost_micros_per_image ?? undefined,
|
|
868
|
-
balanceSnapshotMicros: row.balance_snapshot_micros ?? undefined,
|
|
869
|
-
balanceSource: row.balance_source || undefined,
|
|
870
|
-
promptHash: row.prompt_hash || undefined,
|
|
871
|
-
responseHash: row.response_hash || undefined,
|
|
872
|
-
createdAt: row.created_at,
|
|
873
|
-
ttftMs: row.ttft_ms ?? undefined,
|
|
874
|
-
fallbackCount: row.fallback_count ?? undefined,
|
|
875
|
-
routeReason: row.route_reason ?? undefined,
|
|
876
|
-
falloverChain: row.fallover_chain_json ? JSON.parse(row.fallover_chain_json) as string[] : undefined,
|
|
877
|
-
upstreamStatus: row.upstream_status ?? undefined,
|
|
878
|
-
durationMs: row.duration_ms ?? undefined,
|
|
879
|
-
paymentMethod: row.payment_method ?? undefined
|
|
880
|
-
}));
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
public close(): void {
|
|
884
|
-
this.db.close();
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
private initSchema(): void {
|
|
888
|
-
this.db.exec(`
|
|
889
|
-
CREATE TABLE IF NOT EXISTS token_cache (
|
|
890
|
-
seller_key TEXT PRIMARY KEY,
|
|
891
|
-
token TEXT NOT NULL,
|
|
892
|
-
token_class TEXT NOT NULL,
|
|
893
|
-
balance_micros INTEGER NOT NULL,
|
|
894
|
-
reserved_micros INTEGER NOT NULL DEFAULT 0,
|
|
895
|
-
spent_micros INTEGER NOT NULL DEFAULT 0,
|
|
896
|
-
balance_source TEXT,
|
|
897
|
-
expires_at TEXT NOT NULL,
|
|
898
|
-
updated_at TEXT NOT NULL
|
|
899
|
-
);
|
|
900
|
-
|
|
901
|
-
CREATE TABLE IF NOT EXISTS payment_config (
|
|
902
|
-
method TEXT PRIMARY KEY,
|
|
903
|
-
enabled INTEGER NOT NULL,
|
|
904
|
-
is_default INTEGER NOT NULL DEFAULT 0,
|
|
905
|
-
config_json TEXT,
|
|
906
|
-
created_at TEXT NOT NULL,
|
|
907
|
-
updated_at TEXT NOT NULL
|
|
908
|
-
);
|
|
909
|
-
|
|
910
|
-
CREATE TABLE IF NOT EXISTS pending_purchases (
|
|
911
|
-
purchase_id TEXT PRIMARY KEY,
|
|
912
|
-
seller_key TEXT NOT NULL,
|
|
913
|
-
model_id TEXT NOT NULL,
|
|
914
|
-
payment_method TEXT NOT NULL,
|
|
915
|
-
amount_usd_micros INTEGER NOT NULL,
|
|
916
|
-
status TEXT NOT NULL,
|
|
917
|
-
payment_reference_hash TEXT,
|
|
918
|
-
created_at TEXT NOT NULL,
|
|
919
|
-
updated_at TEXT NOT NULL,
|
|
920
|
-
expires_at TEXT
|
|
921
|
-
);
|
|
922
|
-
|
|
923
|
-
CREATE TABLE IF NOT EXISTS purchase_ledger (
|
|
924
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
925
|
-
purchase_id TEXT NOT NULL,
|
|
926
|
-
seller_key TEXT NOT NULL,
|
|
927
|
-
model_id TEXT NOT NULL,
|
|
928
|
-
payment_method TEXT NOT NULL,
|
|
929
|
-
status TEXT NOT NULL,
|
|
930
|
-
credit_micros INTEGER NOT NULL,
|
|
931
|
-
currency TEXT NOT NULL,
|
|
932
|
-
payment_amount TEXT,
|
|
933
|
-
payment_amount_minor INTEGER,
|
|
934
|
-
payment_currency TEXT,
|
|
935
|
-
payment_reference_hash TEXT,
|
|
936
|
-
created_at TEXT NOT NULL,
|
|
937
|
-
completed_at TEXT
|
|
938
|
-
);
|
|
939
|
-
|
|
940
|
-
CREATE TABLE IF NOT EXISTS inference_ledger (
|
|
941
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
942
|
-
request_id TEXT NOT NULL,
|
|
943
|
-
seller_key TEXT NOT NULL,
|
|
944
|
-
model_id TEXT NOT NULL,
|
|
945
|
-
endpoint TEXT NOT NULL,
|
|
946
|
-
status TEXT NOT NULL,
|
|
947
|
-
prompt_tokens INTEGER NOT NULL,
|
|
948
|
-
completion_tokens INTEGER NOT NULL,
|
|
949
|
-
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
|
950
|
-
billed_micros INTEGER NOT NULL,
|
|
951
|
-
estimated_micros INTEGER,
|
|
952
|
-
settled_micros INTEGER,
|
|
953
|
-
settled_usd_micros INTEGER,
|
|
954
|
-
price_version TEXT,
|
|
955
|
-
input_price_micros_per_1m INTEGER,
|
|
956
|
-
output_price_micros_per_1m INTEGER,
|
|
957
|
-
cache_read_price_micros_per_1m INTEGER,
|
|
958
|
-
input_cost_micros INTEGER,
|
|
959
|
-
output_cost_micros INTEGER,
|
|
960
|
-
cache_read_cost_micros INTEGER,
|
|
961
|
-
original_usd_micros INTEGER,
|
|
962
|
-
billing_multiplier REAL,
|
|
963
|
-
service_tier TEXT,
|
|
964
|
-
billing_unit TEXT,
|
|
965
|
-
image_count INTEGER,
|
|
966
|
-
image_size TEXT,
|
|
967
|
-
image_quality TEXT,
|
|
968
|
-
image_output_format TEXT,
|
|
969
|
-
image_output_tokens INTEGER,
|
|
970
|
-
image_output_cost_micros INTEGER,
|
|
971
|
-
image_cost_micros_per_image INTEGER,
|
|
972
|
-
balance_snapshot_micros INTEGER,
|
|
973
|
-
balance_source TEXT,
|
|
974
|
-
prompt_hash TEXT,
|
|
975
|
-
response_hash TEXT,
|
|
976
|
-
created_at TEXT NOT NULL,
|
|
977
|
-
ttft_ms INTEGER,
|
|
978
|
-
fallback_count INTEGER,
|
|
979
|
-
route_reason TEXT,
|
|
980
|
-
fallover_chain_json TEXT,
|
|
981
|
-
upstream_status TEXT,
|
|
982
|
-
duration_ms INTEGER,
|
|
983
|
-
payment_method TEXT
|
|
984
|
-
);
|
|
985
|
-
|
|
986
|
-
CREATE TABLE IF NOT EXISTS provider_install_state (
|
|
987
|
-
provider_id TEXT PRIMARY KEY,
|
|
988
|
-
snapshot_json TEXT NOT NULL,
|
|
989
|
-
created_at TEXT NOT NULL,
|
|
990
|
-
updated_at TEXT NOT NULL
|
|
991
|
-
);
|
|
992
|
-
|
|
993
|
-
CREATE TABLE IF NOT EXISTS provider_runtime_config (
|
|
994
|
-
provider_id TEXT PRIMARY KEY,
|
|
995
|
-
runtime_json TEXT NOT NULL,
|
|
996
|
-
created_at TEXT NOT NULL,
|
|
997
|
-
updated_at TEXT NOT NULL
|
|
998
|
-
);
|
|
999
|
-
|
|
1000
|
-
CREATE TABLE IF NOT EXISTS daemon_runtime_config (
|
|
1001
|
-
config_key TEXT PRIMARY KEY,
|
|
1002
|
-
config_json TEXT NOT NULL,
|
|
1003
|
-
created_at TEXT NOT NULL,
|
|
1004
|
-
updated_at TEXT NOT NULL
|
|
1005
|
-
);
|
|
1006
|
-
`);
|
|
1007
|
-
for (const [column, definition] of [
|
|
1008
|
-
["reserved_micros", "INTEGER NOT NULL DEFAULT 0"],
|
|
1009
|
-
["spent_micros", "INTEGER NOT NULL DEFAULT 0"],
|
|
1010
|
-
["balance_source", "TEXT"]
|
|
1011
|
-
]) {
|
|
1012
|
-
this.ensureColumn("token_cache", column, definition);
|
|
1013
|
-
}
|
|
1014
|
-
for (const [column, definition] of [
|
|
1015
|
-
["payment_amount", "TEXT"],
|
|
1016
|
-
["payment_amount_minor", "INTEGER"],
|
|
1017
|
-
["payment_currency", "TEXT"]
|
|
1018
|
-
]) {
|
|
1019
|
-
this.ensureColumn("purchase_ledger", column, definition);
|
|
1020
|
-
}
|
|
1021
|
-
for (const [column, definition] of [
|
|
1022
|
-
["estimated_micros", "INTEGER"],
|
|
1023
|
-
["cache_read_tokens", "INTEGER NOT NULL DEFAULT 0"],
|
|
1024
|
-
["settled_micros", "INTEGER"],
|
|
1025
|
-
["settled_usd_micros", "INTEGER"],
|
|
1026
|
-
["price_version", "TEXT"],
|
|
1027
|
-
["input_price_micros_per_1m", "INTEGER"],
|
|
1028
|
-
["output_price_micros_per_1m", "INTEGER"],
|
|
1029
|
-
["cache_read_price_micros_per_1m", "INTEGER"],
|
|
1030
|
-
["input_cost_micros", "INTEGER"],
|
|
1031
|
-
["output_cost_micros", "INTEGER"],
|
|
1032
|
-
["cache_read_cost_micros", "INTEGER"],
|
|
1033
|
-
["original_usd_micros", "INTEGER"],
|
|
1034
|
-
["billing_multiplier", "REAL"],
|
|
1035
|
-
["service_tier", "TEXT"],
|
|
1036
|
-
["billing_unit", "TEXT"],
|
|
1037
|
-
["image_count", "INTEGER"],
|
|
1038
|
-
["image_size", "TEXT"],
|
|
1039
|
-
["image_quality", "TEXT"],
|
|
1040
|
-
["image_output_format", "TEXT"],
|
|
1041
|
-
["image_output_tokens", "INTEGER"],
|
|
1042
|
-
["image_output_cost_micros", "INTEGER"],
|
|
1043
|
-
["image_cost_micros_per_image", "INTEGER"],
|
|
1044
|
-
["balance_snapshot_micros", "INTEGER"],
|
|
1045
|
-
["balance_source", "TEXT"],
|
|
1046
|
-
["ttft_ms", "INTEGER"],
|
|
1047
|
-
["fallback_count", "INTEGER"],
|
|
1048
|
-
["route_reason", "TEXT"],
|
|
1049
|
-
["fallover_chain_json", "TEXT"],
|
|
1050
|
-
["upstream_status", "TEXT"],
|
|
1051
|
-
["duration_ms", "INTEGER"],
|
|
1052
|
-
["payment_method", "TEXT"]
|
|
1053
|
-
]) {
|
|
1054
|
-
this.ensureColumn("inference_ledger", column, definition);
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
private ensureColumn(table: string, column: string, definition: string): void {
|
|
1059
|
-
const rows = this.db.prepare(`PRAGMA table_info(${table})`).all() as Array<{ name: string }>;
|
|
1060
|
-
if (!rows.some((row) => row.name === column)) {
|
|
1061
|
-
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
private countRows(tableName: string): number {
|
|
1066
|
-
const row = this.db.prepare(`SELECT COUNT(*) AS count FROM ${tableName}`).get() as { count: number };
|
|
1067
|
-
return row.count;
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
/**
|
|
1071
|
-
* v1.2 §18.4: aggregate inference-ledger rows from the last `days`
|
|
1072
|
-
* window and return the top `limit` most-used model ids. The focus-set
|
|
1073
|
-
* builder uses this when no explicit warmup configuration is provided.
|
|
1074
|
-
*/
|
|
1075
|
-
public recentModels(days: number, limit: number): string[] {
|
|
1076
|
-
if (days <= 0 || limit <= 0) {
|
|
1077
|
-
return [];
|
|
1078
|
-
}
|
|
1079
|
-
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
|
|
1080
|
-
const rows = this.db.prepare(
|
|
1081
|
-
`SELECT model_id, COUNT(*) AS uses
|
|
1082
|
-
FROM inference_ledger
|
|
1083
|
-
WHERE created_at >= ? AND model_id IS NOT NULL AND model_id != ''
|
|
1084
|
-
GROUP BY model_id
|
|
1085
|
-
ORDER BY uses DESC, model_id ASC
|
|
1086
|
-
LIMIT ?`
|
|
1087
|
-
).all(cutoff, limit) as Array<{ model_id: string; uses: number }>;
|
|
1088
|
-
return rows.map((row) => row.model_id);
|
|
1089
|
-
}
|
|
1090
|
-
}
|