tokentracker-cli 0.93.3 → 0.93.5
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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dashboard/dist/assets/{AchievementBadge-Bl3bWjZw.js → AchievementBadge-td60VwNO.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-B6FrDjg8.js → AchievementsPage-C3yKXgId.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-D5TJtcI5.js → AchievementsSection-XM39rQkD.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-BFlrt34z.js → BadgeDetailModal-2TgBBPmw.js} +1 -1
- package/dashboard/dist/assets/{BotAnimated-KONFZbQT.js → BotAnimated-CG6_x0d2.js} +1 -1
- package/dashboard/dist/assets/{Card-DDZHhXlQ.js → Card-BUnCudVB.js} +1 -1
- package/dashboard/dist/assets/{ClawdAnimated-DMw54QJ_.js → ClawdAnimated-CNRu6v7I.js} +2 -2
- package/dashboard/dist/assets/{CommandPalette-CxVv0uo_.js → CommandPalette-D5vVqYkW.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-CR2MuFo3.js → CommunityStatsModal-D0V4iTI6.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-B-jvLsxn.js → ConfirmModal-CzxTtnXo.js} +1 -1
- package/dashboard/dist/assets/{Controls-DXA3Zxx0.js → Controls-nR5gzX__.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-0jQPu59U.js → DashboardPage-CpHXkuZn.js} +1 -1
- package/dashboard/dist/assets/{DevicePage-CMR7jT8-.js → DevicePage-Emw9iVMd.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-B1j73B1g.js → DialogClose-Bh-pvxsM.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-DJ-RHxIt.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-eIgptOU-.js → DialogTitle-BG3ngtew.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-B8aisUg4.js → FadeIn-DOEfAW5p.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-B7MEGODn.js → HeaderGithubStar-DslGxMu4.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-DfBzMfW7.js → HoverTooltip-O2OKCBc3.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-BBvSL0Zz.js → IpCheckPage-Dv6LbNU_.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-CcX03ndP.js → LandingPage-BBfteqDF.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardAvatar-Dk4UJx-j.js → LeaderboardAvatar-Ct6QagwN.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-CcIp7sjv.js → LeaderboardPage-CSbIdSm7.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProfileModal-DzXfmNhy.js → LeaderboardProfileModal-BuUWkmBN.js} +3 -3
- package/dashboard/dist/assets/{LeaderboardProfilePage-18ijOFN-.js → LeaderboardProfilePage-fVnV0Lo-.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-ZeRD1J97.js → LimitsPage-BHkkA3xN.js} +1 -1
- package/dashboard/dist/assets/{LocalOnlyNotice-DQ_wPyho.js → LocalOnlyNotice-BLlrqreb.js} +1 -1
- package/dashboard/dist/assets/{LoginCard-DX5-Bxhr.js → LoginCard-CKV1uACo.js} +1 -1
- package/dashboard/dist/assets/{LoginModal-_Rt3qUyK.js → LoginModal-RCpQtFuG.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-CvYcDpeR.js → LoginPage-vMV2kpef.js} +1 -1
- package/dashboard/dist/assets/{PetPage-D0D4mRwd.js → PetPage-CTgwpnsA.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-Bz3yRvv6.js → PopoverPopup-Bddr9R6x.js} +1 -1
- package/dashboard/dist/assets/{ProviderIcon-PauvxgNb.js → ProviderIcon-BeXu2voQ.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-BiC1Us4d.js → ResetPasswordPage-CilGTQWZ.js} +1 -1
- package/dashboard/dist/assets/{SegmentedControl-bW8MsUDI.js → SegmentedControl-r5MP3Uyx.js} +1 -1
- package/dashboard/dist/assets/{Select-BSpIj6-K.js → Select-BBUIbPTj.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-taYIkVIw.js → SelectItemText-CbuN7Sui.js} +1 -1
- package/dashboard/dist/assets/{ServiceStatusPage-GMkqBtrt.js → ServiceStatusPage-WgmQviZa.js} +1 -1
- package/dashboard/dist/assets/{SessionsPage-QydYL4yt.js → SessionsPage-BDkdXQhW.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-BFvCcos0.js → SettingsPage-CsF8cG7I.js} +2 -2
- package/dashboard/dist/assets/{SkillsPage-CKci6NM2.js → SkillsPage-CVGra4ar.js} +1 -1
- package/dashboard/dist/assets/{TokenGalaxy-DMof07_u.js → TokenGalaxy-B0kFIG0N.js} +1 -1
- package/dashboard/dist/assets/{ToolbarRootContext-CAsiI0PJ.js → ToolbarRootContext-BlWkiH2k.js} +1 -1
- package/dashboard/dist/assets/{TrendMonitor-Dj2Uxs9x.js → TrendMonitor-C18_ADhU.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-DOr-nMn1.js → WidgetsPage-Ck7L4vBj.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-BZUv1rX5.js → WrappedPage-Bm5HMj3a.js} +1 -1
- package/dashboard/dist/assets/{__vite-browser-external-WKiEWJ30.js → __vite-browser-external-BCyVFzdP.js} +15 -15
- package/dashboard/dist/assets/{agent-logos-1FaE1SqE.js → agent-logos-PZX7lWJo.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-Bs5oicPH.js → arrow-up-right-DPSDEZPh.js} +1 -1
- package/dashboard/dist/assets/{check-Be2PwUMS.js → check-GGrgivnu.js} +1 -1
- package/dashboard/dist/assets/{chevron-right-DH93vlVo.js → chevron-right-CmSOUizu.js} +1 -1
- package/dashboard/dist/assets/{copy-xOLhiaQa.js → copy-ChMyV-hy.js} +1 -1
- package/dashboard/dist/assets/{download-qUjPQH7V.js → download-DGUkE9Jc.js} +1 -1
- package/dashboard/dist/assets/{external-link-D0nLaYYu.js → external-link-B_W-UDpi.js} +1 -1
- package/dashboard/dist/assets/{flame-DFEEnwot.js → flame-C9ccP-R8.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-BPq1moPg.js → format-tokens-CkXJmbVP.js} +1 -1
- package/dashboard/dist/assets/{icons-DxM91e9c.js → icons-D37cRKGk.js} +1 -1
- package/dashboard/dist/assets/{index-CEl88_eC.js → index-C-_fimm3.js} +1 -1
- package/dashboard/dist/assets/{index-C8Sb5l3M.js → index-C9OzYqU0.js} +1 -1
- package/dashboard/dist/assets/{index-BEi52jGG.js → index-Dt6WIU71.js} +1 -1
- package/dashboard/dist/assets/{info-C43iVFmY.js → info-C3-0C2W0.js} +1 -1
- package/dashboard/dist/assets/{limits-providers-xNF55jgX.js → limits-providers-DB1Zr3WN.js} +1 -1
- package/dashboard/dist/assets/{link-2-CfarpF4W.js → link-2-DqNEbzji.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-CIKyaEoN.js → loader-circle-BYoKm-f5.js} +1 -1
- package/dashboard/dist/assets/{maximize-2-DlTJ_PyX.js → maximize-2-lVQDTDqs.js} +1 -1
- package/dashboard/dist/assets/{modifiers.esm-CXV6l89k.js → modifiers.esm-Bww85WOd.js} +1 -1
- package/dashboard/dist/assets/module-D2D5r9gC.js +3 -0
- package/dashboard/dist/assets/{monitor-smartphone-6dCO7HCP.js → monitor-smartphone--KubNwvP.js} +1 -1
- package/dashboard/dist/assets/{provider-display-DNEUispM.js → provider-display-COV_Ft4A.js} +1 -1
- package/dashboard/dist/assets/{react-us685QUG.js → react-lNnu0y7U.js} +1 -1
- package/dashboard/dist/assets/{search-NcbnjXYq.js → search-CpHNMxpD.js} +1 -1
- package/dashboard/dist/assets/{skills-api-CFSZ441M.js → skills-api-Cf2qRQ1h.js} +1 -1
- package/dashboard/dist/assets/{sortable.esm-DiFLdkJ7.js → sortable.esm-DWdlF_7j.js} +1 -1
- package/dashboard/dist/assets/{store-B-d1Nz34.js → store-DJQqZJtg.js} +1 -1
- package/dashboard/dist/assets/{terminal-D3L94ngT.js → terminal-Cban5byW.js} +1 -1
- package/dashboard/dist/assets/{trash-2-esrIc05L.js → trash-2-nelaUHSh.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-C4nPlCzJ.js → use-community-stats-CBupXLW7.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-iXm2ziaO.js → use-limits-display-prefs-B4y_kzcd.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-DU8WcVsn.js → use-ordered-list-DEV58tGc.js} +1 -1
- package/dashboard/dist/assets/use-reduced-motion-CRf4pBTt.js +1 -0
- package/dashboard/dist/assets/{use-session-efficiency-pref-CpGicS2y.js → use-session-efficiency-pref-wSk1wV6R.js} +1 -1
- package/dashboard/dist/assets/{use-transform-WxJRtugr.js → use-transform-NKavwYrX.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-55w4KAqE.js → use-usage-limits-Blr1HBCj.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-C6HKlFA6.js → useCurrency-B2704dMx.js} +1 -1
- package/dashboard/dist/assets/{useScrollLock-CZ1Na38Y.js → useScrollLock-BuPQ8T9m.js} +1 -1
- package/dashboard/dist/assets/{useTokenFormat-BR7kJQ6x.js → useTokenFormat-DPxAFuMj.js} +1 -1
- package/dashboard/dist/assets/{zap-BcwbN9j5.js → zap-BgtTdM4q.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dashboard/dist/ip-check.html +1 -1
- package/dashboard/dist/leaderboard.html +1 -1
- package/dashboard/dist/share.html +1 -1
- package/package.json +1 -1
- package/src/commands/serve.js +115 -4
- package/src/commands/status.js +3 -3
- package/src/commands/sync.js +146 -3
- package/src/lib/opencode-go-limits.js +85 -30
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +372 -29
- package/src/lib/trae-cn-config.js +11 -2
- package/src/lib/usage-limits.js +119 -1
- package/dashboard/dist/assets/DialogDescription-ZS29ikF4.js +0 -1
- package/dashboard/dist/assets/module-CV1mYFsv.js +0 -3
- package/dashboard/dist/assets/use-reduced-motion-DDhy4yg_.js +0 -1
package/src/lib/rollout.js
CHANGED
|
@@ -2698,7 +2698,8 @@ async function parseOpencodeMessageFile({
|
|
|
2698
2698
|
};
|
|
2699
2699
|
}
|
|
2700
2700
|
|
|
2701
|
-
const
|
|
2701
|
+
const { modelId: fileModelId } = normalizeOpencodeModelFields(msg);
|
|
2702
|
+
const model = fileModelId || DEFAULT_MODEL;
|
|
2702
2703
|
const bucket = getHourlyBucket(hourlyState, source, model, bucketStart);
|
|
2703
2704
|
addTotals(bucket.totals, delta);
|
|
2704
2705
|
touchedBuckets.add(bucketKey(source, model, bucketStart));
|
|
@@ -3289,8 +3290,11 @@ function deriveOpencodeMessageFingerprint({ msg, totals, source }) {
|
|
|
3289
3290
|
const created = coerceEpochMs(msg?.time?.created) || 0;
|
|
3290
3291
|
const completed = coerceEpochMs(msg?.time?.completed) || 0;
|
|
3291
3292
|
if (!created && !completed) return null;
|
|
3292
|
-
|
|
3293
|
-
|
|
3293
|
+
// v1 keeps flat modelID/providerID strings; v2 nests them in
|
|
3294
|
+
// `model: { id, providerID }` — normalize both (see normalizeOpencodeModelFields).
|
|
3295
|
+
const { modelId, providerId } = normalizeOpencodeModelFields(msg);
|
|
3296
|
+
const model = modelId || DEFAULT_MODEL;
|
|
3297
|
+
const provider = providerId;
|
|
3294
3298
|
const raw = [
|
|
3295
3299
|
normalizeSourceInput(source) || "opencode",
|
|
3296
3300
|
created,
|
|
@@ -3402,7 +3406,8 @@ function repairCountedOpencodeForkCopy({
|
|
|
3402
3406
|
if (!timestampMs) return false;
|
|
3403
3407
|
const bucketStart = toUtcHalfHourStart(new Date(timestampMs).toISOString());
|
|
3404
3408
|
if (!bucketStart) return false;
|
|
3405
|
-
const
|
|
3409
|
+
const { modelId: repairModelId } = normalizeOpencodeModelFields(msg);
|
|
3410
|
+
const model = repairModelId || DEFAULT_MODEL;
|
|
3406
3411
|
const counted = { ...totals, conversation_count: 1 };
|
|
3407
3412
|
const bucket = getHourlyBucket(hourlyState, source, model, bucketStart);
|
|
3408
3413
|
subtractTotals(bucket.totals, counted);
|
|
@@ -3635,6 +3640,33 @@ function normalizeModelInput(value) {
|
|
|
3635
3640
|
return trimmed.length > 0 ? trimmed : null;
|
|
3636
3641
|
}
|
|
3637
3642
|
|
|
3643
|
+
// OpenCode v1 stores the model as flat strings on every assistant message
|
|
3644
|
+
// (`modelID`, `providerID`), while OpenCode v2 (the `opencode2` beta, which
|
|
3645
|
+
// moved messages into the `session_message` table) nests them inside a single
|
|
3646
|
+
// object: `model: { id, providerID, variant }`. Some forks also wrote a plain
|
|
3647
|
+
// string into `model`. Resolve both shapes to { modelId, providerId } so
|
|
3648
|
+
// bucket keys and fork fingerprints stay identical across versions.
|
|
3649
|
+
function normalizeOpencodeModelFields(msg) {
|
|
3650
|
+
const directModel =
|
|
3651
|
+
normalizeModelInput(msg?.modelID) ||
|
|
3652
|
+
normalizeModelInput(typeof msg?.model === "string" ? msg.model : null) ||
|
|
3653
|
+
normalizeModelInput(msg?.modelId);
|
|
3654
|
+
if (directModel) {
|
|
3655
|
+
return {
|
|
3656
|
+
modelId: directModel,
|
|
3657
|
+
providerId: normalizeMessageKeyPart(msg?.providerID || msg?.provider || msg?.providerId),
|
|
3658
|
+
};
|
|
3659
|
+
}
|
|
3660
|
+
const nested = msg?.model;
|
|
3661
|
+
if (nested && typeof nested === "object") {
|
|
3662
|
+
return {
|
|
3663
|
+
modelId: normalizeModelInput(nested.id),
|
|
3664
|
+
providerId: normalizeMessageKeyPart(nested.providerID),
|
|
3665
|
+
};
|
|
3666
|
+
}
|
|
3667
|
+
return { modelId: null, providerId: "" };
|
|
3668
|
+
}
|
|
3669
|
+
|
|
3638
3670
|
async function resolveProjectMetaForPath(startDir, cache) {
|
|
3639
3671
|
if (!startDir || typeof startDir !== "string") return null;
|
|
3640
3672
|
if (cache && cache.has(startDir)) return cache.get(startDir);
|
|
@@ -4298,12 +4330,79 @@ async function walkOpencodeMessages(dir, out) {
|
|
|
4298
4330
|
}
|
|
4299
4331
|
|
|
4300
4332
|
// ---------------------------------------------------------------------------
|
|
4301
|
-
// OpenCode SQLite DB reader
|
|
4333
|
+
// OpenCode SQLite DB reader
|
|
4334
|
+
//
|
|
4335
|
+
// Four real database shapes exist in the wild (see PR #501 / opencode2):
|
|
4336
|
+
// A pure v1: `message`(data) + `session_message`(empty) [+ `session`]
|
|
4337
|
+
// B beta-17887: `session_message`(data) + `session_v2`
|
|
4338
|
+
// C upstream v2: `session_message`(data) + `session`
|
|
4339
|
+
// D transitional: `message`(old data) + `session_message`(new data)
|
|
4340
|
+
//
|
|
4341
|
+
// The reader must serve all four without firing a doomed SQL at type A (an
|
|
4342
|
+
// empty `session_message` table still EXISTS — querying it with a JOIN against
|
|
4343
|
+
// `session_v2` that does not exist throws "no such table" every sync, and the
|
|
4344
|
+
// old code silently swallowed that error). The combined probe below answers
|
|
4345
|
+
// both "is there v2 data?" and "which session table exists?" in one round-trip.
|
|
4302
4346
|
// ---------------------------------------------------------------------------
|
|
4303
4347
|
|
|
4348
|
+
const OPENCODE_DB_V1_MESSAGE_SQL =
|
|
4349
|
+
`SELECT id, session_id, time_updated, data FROM message ` +
|
|
4350
|
+
`WHERE json_extract(data, '$.role') = 'assistant' ORDER BY time_created ASC`;
|
|
4351
|
+
|
|
4352
|
+
// Build the v2 query. When a session table exists the LEFT JOIN restores the
|
|
4353
|
+
// project directory for downstream attribution; when it does not (type B
|
|
4354
|
+
// without session_v2, or an exotic fork) the join and directory column are
|
|
4355
|
+
// omitted and tokens are counted as-is.
|
|
4356
|
+
function buildV2Sql(sessionTable) {
|
|
4357
|
+
const joinClause = sessionTable
|
|
4358
|
+
? `LEFT JOIN ${sessionTable} s ON s.id = sm.session_id `
|
|
4359
|
+
: "";
|
|
4360
|
+
const directorySelect = sessionTable
|
|
4361
|
+
? `s.directory AS directory, `
|
|
4362
|
+
: "";
|
|
4363
|
+
return (
|
|
4364
|
+
`SELECT sm.id AS id, sm.session_id AS session_id, sm.time_updated AS time_updated, ` +
|
|
4365
|
+
`${directorySelect}sm.data AS data ` +
|
|
4366
|
+
`FROM session_message sm ${joinClause}` +
|
|
4367
|
+
`WHERE sm.type = 'assistant' ORDER BY sm.time_created ASC`
|
|
4368
|
+
);
|
|
4369
|
+
}
|
|
4370
|
+
|
|
4371
|
+
// One combined probe: hasRows (is there any data in session_message?) plus
|
|
4372
|
+
// sessionTable (which session table exists, session_v2 preferred over session
|
|
4373
|
+
// via DESC). Returns null when the probe itself fails — callers treat that as
|
|
4374
|
+
// v1-only, matching the pre-existing silent-degradation contract.
|
|
4375
|
+
function detectOpencodeMessageLayout(dbPath, sqliteOptions = {}) {
|
|
4376
|
+
let rows;
|
|
4377
|
+
try {
|
|
4378
|
+
rows = readSqliteJsonRows(
|
|
4379
|
+
dbPath,
|
|
4380
|
+
`SELECT (SELECT 1 FROM session_message LIMIT 1) AS hasRows,
|
|
4381
|
+
(SELECT name FROM sqlite_master WHERE name IN ('session','session_v2') ORDER BY name DESC LIMIT 1) AS sessionTable`,
|
|
4382
|
+
{ label: "OpenCode", timeout: 10_000, maxBuffer: 1024 * 1024, ...sqliteOptions },
|
|
4383
|
+
);
|
|
4384
|
+
} catch (_e) {
|
|
4385
|
+
return null;
|
|
4386
|
+
}
|
|
4387
|
+
if (!Array.isArray(rows) || rows.length === 0) return null;
|
|
4388
|
+
const row = rows[0];
|
|
4389
|
+
return {
|
|
4390
|
+
hasRows: Boolean(row?.hasRows),
|
|
4391
|
+
sessionTable: typeof row?.sessionTable === "string" && row.sessionTable.trim()
|
|
4392
|
+
? row.sessionTable.trim()
|
|
4393
|
+
: null,
|
|
4394
|
+
};
|
|
4395
|
+
}
|
|
4396
|
+
|
|
4397
|
+
// Shared provider resolver: v1 rows carry a top-level providerID, v2 rows nest
|
|
4398
|
+
// it under model.providerID. Used by the mimo/zcode discriminators so they work
|
|
4399
|
+
// against both generations without duplicating the resolution logic.
|
|
4400
|
+
function opencodeMessageProvider(data) {
|
|
4401
|
+
return data?.providerID || data?.model?.providerID || "";
|
|
4402
|
+
}
|
|
4403
|
+
|
|
4304
4404
|
function readOpencodeDbMessages(dbPath, sqliteOptions = {}) {
|
|
4305
4405
|
if (!dbPath || !fssync.existsSync(dbPath)) return [];
|
|
4306
|
-
const sql = `SELECT id, session_id, time_updated, data FROM message WHERE json_extract(data, '$.role') = 'assistant' ORDER BY time_created ASC`;
|
|
4307
4406
|
|
|
4308
4407
|
let snapshot = null;
|
|
4309
4408
|
let effectiveDbPath = dbPath;
|
|
@@ -4314,14 +4413,16 @@ function readOpencodeDbMessages(dbPath, sqliteOptions = {}) {
|
|
|
4314
4413
|
} catch (_e) { }
|
|
4315
4414
|
}
|
|
4316
4415
|
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4416
|
+
// Parse raw SQL rows into the shared { id, sessionID, timeUpdated, data }
|
|
4417
|
+
// shape; optionally restore v2's session-level project directory as the
|
|
4418
|
+
// per-message `path.cwd` the rest of the pipeline expects.
|
|
4419
|
+
//
|
|
4420
|
+
// D-state (transitional) union: both the legacy `message` table and the new
|
|
4421
|
+
// `session_message` table may carry rows. Cross-table duplicates are caught
|
|
4422
|
+
// by the existing cursor-key + #426 fingerprint dedup in
|
|
4423
|
+
// parseOpencodeDbIncremental, so unioning here is safe — the downstream
|
|
4424
|
+
// parser collapses identical sessionID|messageID pairs before bucketing.
|
|
4425
|
+
const appendRows = (rows, isV2, out) => {
|
|
4325
4426
|
for (const row of rows) {
|
|
4326
4427
|
if (!row || typeof row.data !== "string") continue;
|
|
4327
4428
|
let data;
|
|
@@ -4338,6 +4439,9 @@ function readOpencodeDbMessages(dbPath, sqliteOptions = {}) {
|
|
|
4338
4439
|
toNonNegativeInt(tokens.output) > 0 ||
|
|
4339
4440
|
toNonNegativeInt(tokens.reasoning) > 0;
|
|
4340
4441
|
if (!hasTokens) continue;
|
|
4442
|
+
if (isV2 && typeof row.directory === "string" && row.directory.trim()) {
|
|
4443
|
+
data.path = { ...(typeof data.path === "object" && data.path ? data.path : {}), cwd: row.directory };
|
|
4444
|
+
}
|
|
4341
4445
|
out.push({
|
|
4342
4446
|
id: row.id || data.id,
|
|
4343
4447
|
sessionID: row.session_id || data.sessionID,
|
|
@@ -4345,6 +4449,39 @@ function readOpencodeDbMessages(dbPath, sqliteOptions = {}) {
|
|
|
4345
4449
|
data,
|
|
4346
4450
|
});
|
|
4347
4451
|
}
|
|
4452
|
+
};
|
|
4453
|
+
|
|
4454
|
+
const readGeneration = (sql) => {
|
|
4455
|
+
try {
|
|
4456
|
+
const rows = readSqliteJsonRows(effectiveDbPath, sql, {
|
|
4457
|
+
label: "OpenCode",
|
|
4458
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
4459
|
+
timeout: 30_000,
|
|
4460
|
+
...sqliteOptions,
|
|
4461
|
+
throwOnReadFailure: true,
|
|
4462
|
+
});
|
|
4463
|
+
return Array.isArray(rows) ? rows : [];
|
|
4464
|
+
} catch (_e) {
|
|
4465
|
+
return null; // query-level failure (e.g. wrong generation's table)
|
|
4466
|
+
}
|
|
4467
|
+
};
|
|
4468
|
+
|
|
4469
|
+
try {
|
|
4470
|
+
// Combined probe drives the orchestration: v1 always runs (it is the
|
|
4471
|
+
// baseline every database carries or degrades to), v2 runs only when the
|
|
4472
|
+
// probe confirms session_message has rows — this avoids firing a JOIN
|
|
4473
|
+
// against a non-existent session table on type-A databases.
|
|
4474
|
+
const probe = detectOpencodeMessageLayout(effectiveDbPath, sqliteOptions);
|
|
4475
|
+
const out = [];
|
|
4476
|
+
|
|
4477
|
+
const rows1 = readGeneration(OPENCODE_DB_V1_MESSAGE_SQL);
|
|
4478
|
+
if (rows1) appendRows(rows1, false, out);
|
|
4479
|
+
|
|
4480
|
+
if (probe?.hasRows) {
|
|
4481
|
+
const rows2 = readGeneration(buildV2Sql(probe.sessionTable));
|
|
4482
|
+
if (rows2) appendRows(rows2, true, out);
|
|
4483
|
+
}
|
|
4484
|
+
|
|
4348
4485
|
return out;
|
|
4349
4486
|
} finally {
|
|
4350
4487
|
if (snapshot) snapshot.cleanup();
|
|
@@ -4369,7 +4506,7 @@ function readOpencodeDbMessages(dbPath, sqliteOptions = {}) {
|
|
|
4369
4506
|
// subsumes it.
|
|
4370
4507
|
function isMimoNativeMessage(data) {
|
|
4371
4508
|
if (!data) return false;
|
|
4372
|
-
const provider = String(data
|
|
4509
|
+
const provider = String(opencodeMessageProvider(data)).toLowerCase();
|
|
4373
4510
|
return provider === "mimo" || provider === "xiaomi";
|
|
4374
4511
|
}
|
|
4375
4512
|
|
|
@@ -4403,7 +4540,7 @@ function readMimoDbMessages(dbPath, sqliteOptions = {}) {
|
|
|
4403
4540
|
// re-count it. Mirrors the Mimo discipline.
|
|
4404
4541
|
function isZcodeNativeMessage(data) {
|
|
4405
4542
|
if (!data) return false;
|
|
4406
|
-
const provider = String(data
|
|
4543
|
+
const provider = String(opencodeMessageProvider(data)).toLowerCase();
|
|
4407
4544
|
if (!provider) return false;
|
|
4408
4545
|
return !(
|
|
4409
4546
|
provider.includes("anthropic") ||
|
|
@@ -4412,10 +4549,195 @@ function isZcodeNativeMessage(data) {
|
|
|
4412
4549
|
);
|
|
4413
4550
|
}
|
|
4414
4551
|
|
|
4552
|
+
const ZCODE_NATIVE_USAGE_COLUMNS = new Set([
|
|
4553
|
+
"id",
|
|
4554
|
+
"logical_request_id",
|
|
4555
|
+
"attempt_index",
|
|
4556
|
+
"session_id",
|
|
4557
|
+
"provider_id",
|
|
4558
|
+
"model_id",
|
|
4559
|
+
"status",
|
|
4560
|
+
"started_at",
|
|
4561
|
+
"input_tokens",
|
|
4562
|
+
"output_tokens",
|
|
4563
|
+
"reasoning_tokens",
|
|
4564
|
+
"cache_creation_input_tokens",
|
|
4565
|
+
"cache_read_input_tokens",
|
|
4566
|
+
]);
|
|
4567
|
+
|
|
4568
|
+
function detectZcodeNativeUsageLayout(dbPath, sqliteOptions = {}) {
|
|
4569
|
+
if (!dbPath || !fssync.existsSync(dbPath)) return null;
|
|
4570
|
+
let rows;
|
|
4571
|
+
try {
|
|
4572
|
+
rows = readSqliteJsonRows(
|
|
4573
|
+
dbPath,
|
|
4574
|
+
`SELECT 'model_usage' AS table_name, name FROM pragma_table_info('model_usage')
|
|
4575
|
+
UNION ALL
|
|
4576
|
+
SELECT 'session' AS table_name, name FROM pragma_table_info('session')`,
|
|
4577
|
+
{
|
|
4578
|
+
label: "ZCode",
|
|
4579
|
+
timeout: 10_000,
|
|
4580
|
+
maxBuffer: 1024 * 1024,
|
|
4581
|
+
...sqliteOptions,
|
|
4582
|
+
throwOnReadFailure: true,
|
|
4583
|
+
},
|
|
4584
|
+
);
|
|
4585
|
+
} catch (_error) {
|
|
4586
|
+
return null;
|
|
4587
|
+
}
|
|
4588
|
+
const modelUsageColumns = new Set(
|
|
4589
|
+
rows
|
|
4590
|
+
.filter((row) => !row?.table_name || row.table_name === "model_usage")
|
|
4591
|
+
.map((row) => String(row?.name || "")),
|
|
4592
|
+
);
|
|
4593
|
+
if (![...ZCODE_NATIVE_USAGE_COLUMNS].every((name) => modelUsageColumns.has(name))) {
|
|
4594
|
+
return null;
|
|
4595
|
+
}
|
|
4596
|
+
const sessionColumns = new Set(
|
|
4597
|
+
rows
|
|
4598
|
+
.filter((row) => row?.table_name === "session")
|
|
4599
|
+
.map((row) => String(row?.name || "")),
|
|
4600
|
+
);
|
|
4601
|
+
return {
|
|
4602
|
+
hasSessionDirectory: sessionColumns.has("id") && sessionColumns.has("directory"),
|
|
4603
|
+
};
|
|
4604
|
+
}
|
|
4605
|
+
|
|
4606
|
+
function hasZcodeNativeUsageSchema(dbPath, sqliteOptions = {}) {
|
|
4607
|
+
if (!dbPath || !fssync.existsSync(dbPath)) return false;
|
|
4608
|
+
let snapshot = null;
|
|
4609
|
+
let effectiveDbPath = dbPath;
|
|
4610
|
+
if (isUncPath(dbPath)) {
|
|
4611
|
+
try {
|
|
4612
|
+
snapshot = snapshotSqliteDb(dbPath);
|
|
4613
|
+
effectiveDbPath = snapshot.path;
|
|
4614
|
+
} catch (_error) {
|
|
4615
|
+
// Fall through to the direct read: some UNC servers support SQLite's
|
|
4616
|
+
// read-only locking semantics even when the WSL bridge does not.
|
|
4617
|
+
}
|
|
4618
|
+
}
|
|
4619
|
+
try {
|
|
4620
|
+
return detectZcodeNativeUsageLayout(effectiveDbPath, sqliteOptions) !== null;
|
|
4621
|
+
} finally {
|
|
4622
|
+
if (snapshot) snapshot.cleanup();
|
|
4623
|
+
}
|
|
4624
|
+
}
|
|
4625
|
+
|
|
4626
|
+
function buildZcodeNativeUsageSql({ hasSessionDirectory }) {
|
|
4627
|
+
const directorySelect = hasSessionDirectory ? ", s.directory AS directory" : "";
|
|
4628
|
+
const directoryJoin = hasSessionDirectory
|
|
4629
|
+
? " LEFT JOIN session AS s ON s.id = mu.session_id"
|
|
4630
|
+
: "";
|
|
4631
|
+
return `SELECT
|
|
4632
|
+
mu.id,
|
|
4633
|
+
mu.logical_request_id,
|
|
4634
|
+
mu.attempt_index,
|
|
4635
|
+
mu.session_id,
|
|
4636
|
+
mu.provider_id,
|
|
4637
|
+
mu.model_id,
|
|
4638
|
+
mu.started_at,
|
|
4639
|
+
mu.input_tokens,
|
|
4640
|
+
mu.output_tokens,
|
|
4641
|
+
mu.reasoning_tokens,
|
|
4642
|
+
mu.cache_creation_input_tokens,
|
|
4643
|
+
mu.cache_read_input_tokens
|
|
4644
|
+
${directorySelect}
|
|
4645
|
+
FROM model_usage AS mu${directoryJoin}
|
|
4646
|
+
WHERE mu.status = 'completed'
|
|
4647
|
+
AND trim(mu.model_id) != ''
|
|
4648
|
+
AND (
|
|
4649
|
+
mu.input_tokens > 0 OR mu.output_tokens > 0 OR mu.reasoning_tokens > 0 OR
|
|
4650
|
+
mu.cache_creation_input_tokens > 0 OR mu.cache_read_input_tokens > 0
|
|
4651
|
+
)
|
|
4652
|
+
ORDER BY mu.started_at ASC, mu.id ASC`;
|
|
4653
|
+
}
|
|
4654
|
+
|
|
4655
|
+
function readZcodeNativeUsageMessages(dbPath, sqliteOptions = {}) {
|
|
4656
|
+
let snapshot = null;
|
|
4657
|
+
let effectiveDbPath = dbPath;
|
|
4658
|
+
if (isUncPath(dbPath)) {
|
|
4659
|
+
try {
|
|
4660
|
+
snapshot = snapshotSqliteDb(dbPath);
|
|
4661
|
+
effectiveDbPath = snapshot.path;
|
|
4662
|
+
} catch (_error) {
|
|
4663
|
+
// Preserve the existing direct-read fallback for transient snapshot
|
|
4664
|
+
// failures and UNC implementations that support SQLite locking.
|
|
4665
|
+
}
|
|
4666
|
+
}
|
|
4667
|
+
|
|
4668
|
+
let rows;
|
|
4669
|
+
try {
|
|
4670
|
+
const layout = detectZcodeNativeUsageLayout(effectiveDbPath, sqliteOptions);
|
|
4671
|
+
if (!layout) return null;
|
|
4672
|
+
rows = readSqliteJsonRows(effectiveDbPath, buildZcodeNativeUsageSql(layout), {
|
|
4673
|
+
label: "ZCode",
|
|
4674
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
4675
|
+
timeout: 30_000,
|
|
4676
|
+
...sqliteOptions,
|
|
4677
|
+
throwOnReadFailure: true,
|
|
4678
|
+
});
|
|
4679
|
+
} catch (_error) {
|
|
4680
|
+
return null;
|
|
4681
|
+
} finally {
|
|
4682
|
+
if (snapshot) snapshot.cleanup();
|
|
4683
|
+
}
|
|
4684
|
+
|
|
4685
|
+
const messages = [];
|
|
4686
|
+
for (const row of rows) {
|
|
4687
|
+
const providerID = String(row?.provider_id || "").trim();
|
|
4688
|
+
const modelID = String(row?.model_id || "").trim();
|
|
4689
|
+
const sessionID = String(row?.session_id || "").trim();
|
|
4690
|
+
const logicalRequestId = String(row?.logical_request_id || "").trim();
|
|
4691
|
+
const attemptIndex = toNonNegativeInt(row?.attempt_index);
|
|
4692
|
+
const id = String(row?.id || "").trim() ||
|
|
4693
|
+
(logicalRequestId ? `${logicalRequestId}#${attemptIndex}` : "");
|
|
4694
|
+
const startedAt = coerceEpochMs(row?.started_at);
|
|
4695
|
+
if (!providerID || !modelID || !sessionID || !id || !startedAt) continue;
|
|
4696
|
+
|
|
4697
|
+
// ZCode's persisted input/output columns are inclusive parents:
|
|
4698
|
+
// cache read/write are subsets of input, and reasoning is a subset of
|
|
4699
|
+
// output. TokenTracker stores those five columns disjointly, so split the
|
|
4700
|
+
// subsets here before entering the shared OpenCode parser. This preserves
|
|
4701
|
+
// sum(input + cache read + cache write + output + reasoning) without
|
|
4702
|
+
// billing either subset twice.
|
|
4703
|
+
const rawInput = toNonNegativeInt(row?.input_tokens);
|
|
4704
|
+
const rawOutput = toNonNegativeInt(row?.output_tokens);
|
|
4705
|
+
const cacheRead = toNonNegativeInt(row?.cache_read_input_tokens);
|
|
4706
|
+
const cacheWrite = toNonNegativeInt(row?.cache_creation_input_tokens);
|
|
4707
|
+
const reasoning = toNonNegativeInt(row?.reasoning_tokens);
|
|
4708
|
+
|
|
4709
|
+
const data = {
|
|
4710
|
+
id,
|
|
4711
|
+
sessionID,
|
|
4712
|
+
role: "assistant",
|
|
4713
|
+
providerID,
|
|
4714
|
+
modelID,
|
|
4715
|
+
time: { created: startedAt, completed: startedAt },
|
|
4716
|
+
tokens: {
|
|
4717
|
+
input: Math.max(0, rawInput - cacheRead - cacheWrite),
|
|
4718
|
+
output: Math.max(0, rawOutput - reasoning),
|
|
4719
|
+
reasoning,
|
|
4720
|
+
cache: {
|
|
4721
|
+
read: cacheRead,
|
|
4722
|
+
write: cacheWrite,
|
|
4723
|
+
},
|
|
4724
|
+
},
|
|
4725
|
+
};
|
|
4726
|
+
if (typeof row?.directory === "string" && row.directory.trim()) {
|
|
4727
|
+
data.path = { cwd: row.directory.trim() };
|
|
4728
|
+
}
|
|
4729
|
+
if (!isZcodeNativeMessage(data)) continue;
|
|
4730
|
+
messages.push({ id, sessionID, timeUpdated: startedAt, data });
|
|
4731
|
+
}
|
|
4732
|
+
return messages;
|
|
4733
|
+
}
|
|
4734
|
+
|
|
4415
4735
|
// Read only genuine ZCode assistant messages (its own GLM models via Z.ai /
|
|
4416
4736
|
// BigModel), dropping any bundled sub-agent turns. See isZcodeNativeMessage.
|
|
4417
4737
|
function readZcodeDbMessages(dbPath, sqliteOptions = {}) {
|
|
4418
4738
|
if (!dbPath || !fssync.existsSync(dbPath)) return [];
|
|
4739
|
+
const nativeMessages = readZcodeNativeUsageMessages(dbPath, sqliteOptions);
|
|
4740
|
+
if (nativeMessages !== null) return nativeMessages;
|
|
4419
4741
|
const all = readOpencodeDbMessages(dbPath, sqliteOptions);
|
|
4420
4742
|
return all.filter((m) => isZcodeNativeMessage(m.data));
|
|
4421
4743
|
}
|
|
@@ -4562,7 +4884,8 @@ async function parseOpencodeDbIncremental({
|
|
|
4562
4884
|
continue;
|
|
4563
4885
|
}
|
|
4564
4886
|
|
|
4565
|
-
const
|
|
4887
|
+
const { modelId: dbModelId } = normalizeOpencodeModelFields(msg);
|
|
4888
|
+
const model = dbModelId || DEFAULT_MODEL;
|
|
4566
4889
|
const bucket = getHourlyBucket(hourlyState, defaultSource, model, bucketStart);
|
|
4567
4890
|
addTotals(bucket.totals, delta);
|
|
4568
4891
|
touchedBuckets.add(bucketKey(defaultSource, model, bucketStart));
|
|
@@ -16614,36 +16937,55 @@ function isCjkCodePoint(code) {
|
|
|
16614
16937
|
// (session transcripts are SQLCipher-encrypted; memory summaries carry no
|
|
16615
16938
|
// token counts), so the token-count-only queue is intentionally never written
|
|
16616
16939
|
// for this provider — the cloud hourly table stays clean.
|
|
16617
|
-
|
|
16940
|
+
// Ordered candidate app-dir names. The CN IDE build installs as "Trae CN" on
|
|
16941
|
+
// Windows; the international build installs as "TRAE SOLO". Both expose the same
|
|
16942
|
+
// iCubeServerData entitlement key, so either is a valid Trae IDE install.
|
|
16943
|
+
function traeCandidateAppDirs(env = process.env) {
|
|
16618
16944
|
const override = env.TOKENTRACKER_TRAE_HOME;
|
|
16619
16945
|
if (typeof override === "string" && override.trim().length > 0) {
|
|
16620
|
-
return override.trim();
|
|
16946
|
+
return [override.trim()];
|
|
16621
16947
|
}
|
|
16622
16948
|
const home = require("node:os").homedir();
|
|
16623
16949
|
if (process.platform === "darwin") {
|
|
16624
|
-
return path.join(home, "Library", "Application Support", "TRAE SOLO");
|
|
16950
|
+
return [path.join(home, "Library", "Application Support", "TRAE SOLO")];
|
|
16625
16951
|
}
|
|
16626
16952
|
if (process.platform === "win32") {
|
|
16627
16953
|
// A Windows box with no APPDATA still keeps Trae under the standard
|
|
16628
16954
|
// roaming profile — falling through to the dot-dir below would point at
|
|
16629
16955
|
// a location Trae never writes.
|
|
16630
|
-
const appData =
|
|
16631
|
-
|
|
16632
|
-
|
|
16633
|
-
|
|
16956
|
+
const appData =
|
|
16957
|
+
typeof env.APPDATA === "string" && env.APPDATA.trim()
|
|
16958
|
+
? env.APPDATA.trim()
|
|
16959
|
+
: path.join(home, "AppData", "Roaming");
|
|
16960
|
+
return [
|
|
16961
|
+
path.join(appData, "TRAE SOLO"),
|
|
16962
|
+
path.join(appData, "Trae CN"),
|
|
16963
|
+
];
|
|
16634
16964
|
}
|
|
16635
16965
|
// Linux and friends: TRAE SOLO ships official builds for macOS/Windows
|
|
16636
16966
|
// only, so there is no verified app-data layout to default to. Fall back to
|
|
16637
16967
|
// a deterministic home-dir path (best-effort detection);
|
|
16638
16968
|
// TOKENTRACKER_TRAE_HOME always wins for unusual installs.
|
|
16639
|
-
return path.join(home, ".trae-solo");
|
|
16969
|
+
return [path.join(home, ".trae-solo")];
|
|
16970
|
+
}
|
|
16971
|
+
|
|
16972
|
+
// Best-effort: the first candidate that exists on disk (used for display/init
|
|
16973
|
+
// detection). When no install is present, returns the primary candidate so
|
|
16974
|
+
// callers still get a deterministic path to report.
|
|
16975
|
+
function resolveTraePath(env = process.env) {
|
|
16976
|
+
const candidates = traeCandidateAppDirs(env);
|
|
16977
|
+
for (const dir of candidates) {
|
|
16978
|
+
try { if (fssync.existsSync(dir)) return dir; } catch (_error) {}
|
|
16979
|
+
}
|
|
16980
|
+
return candidates.length ? candidates[0] : null;
|
|
16640
16981
|
}
|
|
16641
16982
|
|
|
16642
16983
|
function resolveTraeStoragePath(env = process.env) {
|
|
16643
|
-
const
|
|
16644
|
-
|
|
16645
|
-
|
|
16646
|
-
|
|
16984
|
+
for (const dir of traeCandidateAppDirs(env)) {
|
|
16985
|
+
const p = path.join(dir, "User", "globalStorage", "storage.json");
|
|
16986
|
+
try { if (fssync.existsSync(p)) return p; } catch (_error) {}
|
|
16987
|
+
}
|
|
16988
|
+
return null;
|
|
16647
16989
|
}
|
|
16648
16990
|
|
|
16649
16991
|
/**
|
|
@@ -17895,6 +18237,7 @@ module.exports = {
|
|
|
17895
18237
|
readOpencodeDbMessages,
|
|
17896
18238
|
readMimoDbMessages,
|
|
17897
18239
|
readZcodeDbMessages,
|
|
18240
|
+
hasZcodeNativeUsageSchema,
|
|
17898
18241
|
resolveQoderDbPath,
|
|
17899
18242
|
resolveQoderDbPaths,
|
|
17900
18243
|
resolveQoderCnDbPaths,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* TRAE Work CN (国内版) config + usage fetch. Phase A: macOS + test-injected
|
|
4
|
+
* TRAE Work CN (国内版) config + usage fetch. Phase A: macOS + Windows + test-injected
|
|
5
5
|
* paths only. Resolves the CN home, reads the `iCubeAuthInfo://icube.cloudide`
|
|
6
6
|
* blob from User/globalStorage/storage.json (plaintext JSON or Base64 tc v5),
|
|
7
7
|
* and talks to the TRAE CN usage API. JWTs / refresh tokens are never
|
|
@@ -85,7 +85,16 @@ function resolveTraeCnHome({ env = process.env, home = os.homedir(), platform =
|
|
|
85
85
|
if (platform === "darwin") {
|
|
86
86
|
return path.join(home, "Library", "Application Support", TRAE_CN_APP_DIR);
|
|
87
87
|
}
|
|
88
|
-
|
|
88
|
+
if (platform === "win32") {
|
|
89
|
+
// Windows keeps the CN Work app under the roaming profile, mirroring the
|
|
90
|
+
// macOS Application Support layout (APPDATA/TRAE SOLO CN/User/...).
|
|
91
|
+
const appData =
|
|
92
|
+
typeof env.APPDATA === "string" && env.APPDATA.trim()
|
|
93
|
+
? env.APPDATA.trim()
|
|
94
|
+
: path.join(home, "AppData", "Roaming");
|
|
95
|
+
return path.join(appData, TRAE_CN_APP_DIR);
|
|
96
|
+
}
|
|
97
|
+
// No verified CN Work app-data layout on other platforms yet.
|
|
89
98
|
return null;
|
|
90
99
|
}
|
|
91
100
|
|