@hasna/economy 0.2.36 → 0.2.37
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/dist/cli/index.js +107 -72
- package/dist/index.js +106 -71
- package/dist/ingest/codex.d.ts +1 -1
- package/dist/ingest/codex.d.ts.map +1 -1
- package/dist/mcp/index.js +106 -71
- package/dist/server/index.js +106 -71
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -102,7 +102,7 @@ economy sync --recalculate
|
|
|
102
102
|
economy sync --backfill-machine
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
-
Full sync also imports active project metadata from `@hasna/projects` when the registry is available.
|
|
105
|
+
Full sync also imports active project metadata from `@hasna/projects` when the registry is available. The Codex source reads both legacy `~/.codex/state_5.sqlite` and current Codewith `~/.codewith/state_5.sqlite` usage stores by default; explicit `HASNA_ECONOMY_CODEX_DB_PATH` and `HASNA_ECONOMY_CODEWITH_DB_PATH` values override those locations.
|
|
106
106
|
|
|
107
107
|
Account attribution is automatic when `@hasna/accounts` has a matching active, applied, or env-dir profile for the agent. Account identity is the email address plus coding agent, so `work@example.com` under Codex and Claude is reported as two accounts. You can also force attribution for a process with `ECONOMY_ACCOUNT=tool:name` or agent-specific overrides such as `ECONOMY_CODEX_ACCOUNT=codex:work`.
|
|
108
108
|
|
package/dist/cli/index.js
CHANGED
|
@@ -2766,8 +2766,40 @@ function codexDbPath() {
|
|
|
2766
2766
|
function codexConfigPath() {
|
|
2767
2767
|
return process.env["HASNA_ECONOMY_CODEX_CONFIG_PATH"] ?? DEFAULT_CODEX_CONFIG_PATH;
|
|
2768
2768
|
}
|
|
2769
|
-
function
|
|
2770
|
-
|
|
2769
|
+
function codewithDbPath() {
|
|
2770
|
+
return process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"] ?? DEFAULT_CODEWITH_DB_PATH;
|
|
2771
|
+
}
|
|
2772
|
+
function codewithConfigPath() {
|
|
2773
|
+
return process.env["HASNA_ECONOMY_CODEWITH_CONFIG_PATH"] ?? DEFAULT_CODEWITH_CONFIG_PATH;
|
|
2774
|
+
}
|
|
2775
|
+
function codexSources() {
|
|
2776
|
+
const explicitCodexPath = process.env["HASNA_ECONOMY_CODEX_DB_PATH"];
|
|
2777
|
+
const explicitCodewithPath = process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"];
|
|
2778
|
+
const sources = [{
|
|
2779
|
+
label: "Codex",
|
|
2780
|
+
dbPath: codexDbPath(),
|
|
2781
|
+
configPath: codexConfigPath(),
|
|
2782
|
+
sessionPrefix: "codex",
|
|
2783
|
+
stateSource: "codex"
|
|
2784
|
+
}];
|
|
2785
|
+
if (!explicitCodexPath || explicitCodewithPath) {
|
|
2786
|
+
sources.push({
|
|
2787
|
+
label: "Codewith",
|
|
2788
|
+
dbPath: codewithDbPath(),
|
|
2789
|
+
configPath: codewithConfigPath(),
|
|
2790
|
+
sessionPrefix: "codex-codewith",
|
|
2791
|
+
stateSource: "codex-codewith"
|
|
2792
|
+
});
|
|
2793
|
+
}
|
|
2794
|
+
const seen = new Set;
|
|
2795
|
+
return sources.filter((source) => {
|
|
2796
|
+
if (seen.has(source.dbPath))
|
|
2797
|
+
return false;
|
|
2798
|
+
seen.add(source.dbPath);
|
|
2799
|
+
return true;
|
|
2800
|
+
});
|
|
2801
|
+
}
|
|
2802
|
+
function readCodexModel(configPath = codexConfigPath()) {
|
|
2771
2803
|
if (!existsSync5(configPath))
|
|
2772
2804
|
return "gpt-5-codex";
|
|
2773
2805
|
try {
|
|
@@ -2885,94 +2917,97 @@ function fallbackEvents(totalTokens) {
|
|
|
2885
2917
|
}];
|
|
2886
2918
|
}
|
|
2887
2919
|
async function ingestCodex(db, verbose = false) {
|
|
2888
|
-
const dbPath = codexDbPath();
|
|
2889
|
-
if (!existsSync5(dbPath)) {
|
|
2890
|
-
if (verbose)
|
|
2891
|
-
console.log("Codex DB not found:", dbPath);
|
|
2892
|
-
return { sessions: 0, requests: 0 };
|
|
2893
|
-
}
|
|
2894
2920
|
const machineId = getMachineId();
|
|
2895
|
-
let codexDb = null;
|
|
2896
2921
|
let ingested = 0;
|
|
2897
2922
|
let requests = 0;
|
|
2898
2923
|
const account = await resolveAccountForAgent("codex");
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
if (
|
|
2924
|
+
for (const source of codexSources()) {
|
|
2925
|
+
if (!existsSync5(source.dbPath)) {
|
|
2926
|
+
if (verbose)
|
|
2927
|
+
console.log(`${source.label} DB not found:`, source.dbPath);
|
|
2928
|
+
continue;
|
|
2929
|
+
}
|
|
2930
|
+
let codexDb = null;
|
|
2931
|
+
try {
|
|
2932
|
+
codexDb = openCodexDb(source.dbPath, verbose);
|
|
2933
|
+
if (!codexDb)
|
|
2909
2934
|
continue;
|
|
2910
|
-
const
|
|
2911
|
-
const
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
request_count: 0,
|
|
2925
|
-
machine_id: machineId
|
|
2926
|
-
}, account));
|
|
2927
|
-
const events = readTokenEvents(thread.rollout_path);
|
|
2928
|
-
const tokenEvents = events.length > 0 ? events : fallbackEvents(thread.tokens_used);
|
|
2929
|
-
const ingestedTokens = tokenEvents.reduce((sum, event) => sum + tokenTotal(event.usage), 0);
|
|
2930
|
-
db.prepare(`DELETE FROM requests WHERE session_id = ?`).run(sessionId);
|
|
2931
|
-
tokenEvents.forEach((event, index) => {
|
|
2932
|
-
const usage = event.usage;
|
|
2933
|
-
const inputTotal = usage.input_tokens ?? 0;
|
|
2934
|
-
const cacheReadTokens = usage.cached_input_tokens ?? 0;
|
|
2935
|
-
const inputTokens = Math.max(inputTotal - cacheReadTokens, 0);
|
|
2936
|
-
const outputTokens = usage.output_tokens ?? Math.max((usage.total_tokens ?? 0) - inputTotal, 0);
|
|
2937
|
-
const costUsd = computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, 0);
|
|
2938
|
-
const timestamp = event.timestamp ?? (thread.created_at ? new Date(thread.created_at * 1000 + index).toISOString() : new Date().toISOString());
|
|
2939
|
-
const requestId = `${sessionId}-${index}`;
|
|
2940
|
-
upsertRequest(db, withAccount({
|
|
2941
|
-
id: requestId,
|
|
2935
|
+
const threads = codexDb.prepare(buildThreadQuery(codexDb)).all();
|
|
2936
|
+
for (const thread of threads) {
|
|
2937
|
+
const model = thread.model ?? readCodexModel(source.configPath);
|
|
2938
|
+
const stateValue = `${CODEX_INGEST_VERSION}:${thread.updated_at}:${thread.tokens_used}:${model}`;
|
|
2939
|
+
const processed = getIngestState(db, source.stateSource, thread.id);
|
|
2940
|
+
if (processed === stateValue)
|
|
2941
|
+
continue;
|
|
2942
|
+
const projectPath = thread.cwd ?? "";
|
|
2943
|
+
const projectName = projectPath ? basename2(projectPath) : "unknown";
|
|
2944
|
+
const sessionId = `${source.sessionPrefix}-${thread.id}`;
|
|
2945
|
+
const startedAt = thread.created_at ? new Date(thread.created_at * 1000).toISOString() : new Date().toISOString();
|
|
2946
|
+
const endedAt = thread.updated_at ? new Date(thread.updated_at * 1000).toISOString() : null;
|
|
2947
|
+
upsertSession(db, withAccount({
|
|
2948
|
+
id: sessionId,
|
|
2942
2949
|
agent: "codex",
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
cost_basis: defaultCostBasisForAgent("codex"),
|
|
2951
|
-
duration_ms: 0,
|
|
2952
|
-
timestamp,
|
|
2953
|
-
source_request_id: requestId,
|
|
2950
|
+
project_path: projectPath,
|
|
2951
|
+
project_name: projectName,
|
|
2952
|
+
started_at: startedAt,
|
|
2953
|
+
ended_at: endedAt,
|
|
2954
|
+
total_cost_usd: 0,
|
|
2955
|
+
total_tokens: 0,
|
|
2956
|
+
request_count: 0,
|
|
2954
2957
|
machine_id: machineId
|
|
2955
2958
|
}, account));
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2959
|
+
const events = readTokenEvents(thread.rollout_path);
|
|
2960
|
+
const tokenEvents = events.length > 0 ? events : fallbackEvents(thread.tokens_used);
|
|
2961
|
+
const ingestedTokens = tokenEvents.reduce((sum, event) => sum + tokenTotal(event.usage), 0);
|
|
2962
|
+
db.prepare(`DELETE FROM requests WHERE session_id = ?`).run(sessionId);
|
|
2963
|
+
tokenEvents.forEach((event, index) => {
|
|
2964
|
+
const usage = event.usage;
|
|
2965
|
+
const inputTotal = usage.input_tokens ?? 0;
|
|
2966
|
+
const cacheReadTokens = usage.cached_input_tokens ?? 0;
|
|
2967
|
+
const inputTokens = Math.max(inputTotal - cacheReadTokens, 0);
|
|
2968
|
+
const outputTokens = usage.output_tokens ?? Math.max((usage.total_tokens ?? 0) - inputTotal, 0);
|
|
2969
|
+
const costUsd = computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, 0);
|
|
2970
|
+
const timestamp = event.timestamp ?? (thread.created_at ? new Date(thread.created_at * 1000 + index).toISOString() : new Date().toISOString());
|
|
2971
|
+
const requestId = `${sessionId}-${index}`;
|
|
2972
|
+
upsertRequest(db, withAccount({
|
|
2973
|
+
id: requestId,
|
|
2974
|
+
agent: "codex",
|
|
2975
|
+
session_id: sessionId,
|
|
2976
|
+
model,
|
|
2977
|
+
input_tokens: inputTokens,
|
|
2978
|
+
output_tokens: outputTokens,
|
|
2979
|
+
cache_read_tokens: cacheReadTokens,
|
|
2980
|
+
cache_create_tokens: 0,
|
|
2981
|
+
cost_usd: costUsd,
|
|
2982
|
+
cost_basis: defaultCostBasisForAgent("codex"),
|
|
2983
|
+
duration_ms: 0,
|
|
2984
|
+
timestamp,
|
|
2985
|
+
source_request_id: requestId,
|
|
2986
|
+
machine_id: machineId
|
|
2987
|
+
}, account));
|
|
2988
|
+
requests++;
|
|
2989
|
+
});
|
|
2990
|
+
rollupSession(db, sessionId);
|
|
2991
|
+
setIngestState(db, source.stateSource, thread.id, stateValue);
|
|
2992
|
+
ingested++;
|
|
2993
|
+
if (verbose)
|
|
2994
|
+
console.log(`${source.label} session ${thread.id}: ${ingestedTokens} tokens on ${model}`);
|
|
2995
|
+
}
|
|
2996
|
+
} finally {
|
|
2997
|
+
codexDb?.close();
|
|
2963
2998
|
}
|
|
2964
|
-
} finally {
|
|
2965
|
-
codexDb?.close();
|
|
2966
2999
|
}
|
|
2967
3000
|
return { sessions: ingested, requests };
|
|
2968
3001
|
}
|
|
2969
|
-
var DEFAULT_CODEX_DB_PATH, DEFAULT_CODEX_CONFIG_PATH, CODEX_INGEST_VERSION = "rollout-aggregate-v3";
|
|
3002
|
+
var DEFAULT_CODEX_DB_PATH, DEFAULT_CODEX_CONFIG_PATH, DEFAULT_CODEWITH_DB_PATH, DEFAULT_CODEWITH_CONFIG_PATH, CODEX_INGEST_VERSION = "rollout-aggregate-v3";
|
|
2970
3003
|
var init_codex = __esm(() => {
|
|
2971
3004
|
init_database();
|
|
2972
3005
|
init_pricing();
|
|
2973
3006
|
init_accounts();
|
|
2974
3007
|
DEFAULT_CODEX_DB_PATH = join7(homedir4(), ".codex", "state_5.sqlite");
|
|
2975
3008
|
DEFAULT_CODEX_CONFIG_PATH = join7(homedir4(), ".codex", "config.toml");
|
|
3009
|
+
DEFAULT_CODEWITH_DB_PATH = join7(homedir4(), ".codewith", "state_5.sqlite");
|
|
3010
|
+
DEFAULT_CODEWITH_CONFIG_PATH = join7(homedir4(), ".codewith", "config.toml");
|
|
2976
3011
|
});
|
|
2977
3012
|
|
|
2978
3013
|
// src/ingest/gemini.ts
|
package/dist/index.js
CHANGED
|
@@ -2496,6 +2496,8 @@ import { join as join4, basename as basename2 } from "path";
|
|
|
2496
2496
|
import { Database as BunDatabase2 } from "bun:sqlite";
|
|
2497
2497
|
var DEFAULT_CODEX_DB_PATH = join4(homedir3(), ".codex", "state_5.sqlite");
|
|
2498
2498
|
var DEFAULT_CODEX_CONFIG_PATH = join4(homedir3(), ".codex", "config.toml");
|
|
2499
|
+
var DEFAULT_CODEWITH_DB_PATH = join4(homedir3(), ".codewith", "state_5.sqlite");
|
|
2500
|
+
var DEFAULT_CODEWITH_CONFIG_PATH = join4(homedir3(), ".codewith", "config.toml");
|
|
2499
2501
|
var CODEX_INGEST_VERSION = "rollout-aggregate-v3";
|
|
2500
2502
|
function codexDbPath() {
|
|
2501
2503
|
return process.env["HASNA_ECONOMY_CODEX_DB_PATH"] ?? DEFAULT_CODEX_DB_PATH;
|
|
@@ -2503,8 +2505,40 @@ function codexDbPath() {
|
|
|
2503
2505
|
function codexConfigPath() {
|
|
2504
2506
|
return process.env["HASNA_ECONOMY_CODEX_CONFIG_PATH"] ?? DEFAULT_CODEX_CONFIG_PATH;
|
|
2505
2507
|
}
|
|
2506
|
-
function
|
|
2507
|
-
|
|
2508
|
+
function codewithDbPath() {
|
|
2509
|
+
return process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"] ?? DEFAULT_CODEWITH_DB_PATH;
|
|
2510
|
+
}
|
|
2511
|
+
function codewithConfigPath() {
|
|
2512
|
+
return process.env["HASNA_ECONOMY_CODEWITH_CONFIG_PATH"] ?? DEFAULT_CODEWITH_CONFIG_PATH;
|
|
2513
|
+
}
|
|
2514
|
+
function codexSources() {
|
|
2515
|
+
const explicitCodexPath = process.env["HASNA_ECONOMY_CODEX_DB_PATH"];
|
|
2516
|
+
const explicitCodewithPath = process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"];
|
|
2517
|
+
const sources = [{
|
|
2518
|
+
label: "Codex",
|
|
2519
|
+
dbPath: codexDbPath(),
|
|
2520
|
+
configPath: codexConfigPath(),
|
|
2521
|
+
sessionPrefix: "codex",
|
|
2522
|
+
stateSource: "codex"
|
|
2523
|
+
}];
|
|
2524
|
+
if (!explicitCodexPath || explicitCodewithPath) {
|
|
2525
|
+
sources.push({
|
|
2526
|
+
label: "Codewith",
|
|
2527
|
+
dbPath: codewithDbPath(),
|
|
2528
|
+
configPath: codewithConfigPath(),
|
|
2529
|
+
sessionPrefix: "codex-codewith",
|
|
2530
|
+
stateSource: "codex-codewith"
|
|
2531
|
+
});
|
|
2532
|
+
}
|
|
2533
|
+
const seen = new Set;
|
|
2534
|
+
return sources.filter((source) => {
|
|
2535
|
+
if (seen.has(source.dbPath))
|
|
2536
|
+
return false;
|
|
2537
|
+
seen.add(source.dbPath);
|
|
2538
|
+
return true;
|
|
2539
|
+
});
|
|
2540
|
+
}
|
|
2541
|
+
function readCodexModel(configPath = codexConfigPath()) {
|
|
2508
2542
|
if (!existsSync5(configPath))
|
|
2509
2543
|
return "gpt-5-codex";
|
|
2510
2544
|
try {
|
|
@@ -2622,84 +2656,85 @@ function fallbackEvents(totalTokens) {
|
|
|
2622
2656
|
}];
|
|
2623
2657
|
}
|
|
2624
2658
|
async function ingestCodex(db, verbose = false) {
|
|
2625
|
-
const dbPath = codexDbPath();
|
|
2626
|
-
if (!existsSync5(dbPath)) {
|
|
2627
|
-
if (verbose)
|
|
2628
|
-
console.log("Codex DB not found:", dbPath);
|
|
2629
|
-
return { sessions: 0, requests: 0 };
|
|
2630
|
-
}
|
|
2631
2659
|
const machineId = getMachineId();
|
|
2632
|
-
let codexDb = null;
|
|
2633
2660
|
let ingested = 0;
|
|
2634
2661
|
let requests = 0;
|
|
2635
2662
|
const account = await resolveAccountForAgent("codex");
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
if (
|
|
2663
|
+
for (const source of codexSources()) {
|
|
2664
|
+
if (!existsSync5(source.dbPath)) {
|
|
2665
|
+
if (verbose)
|
|
2666
|
+
console.log(`${source.label} DB not found:`, source.dbPath);
|
|
2667
|
+
continue;
|
|
2668
|
+
}
|
|
2669
|
+
let codexDb = null;
|
|
2670
|
+
try {
|
|
2671
|
+
codexDb = openCodexDb(source.dbPath, verbose);
|
|
2672
|
+
if (!codexDb)
|
|
2646
2673
|
continue;
|
|
2647
|
-
const
|
|
2648
|
-
const
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
request_count: 0,
|
|
2662
|
-
machine_id: machineId
|
|
2663
|
-
}, account));
|
|
2664
|
-
const events = readTokenEvents(thread.rollout_path);
|
|
2665
|
-
const tokenEvents = events.length > 0 ? events : fallbackEvents(thread.tokens_used);
|
|
2666
|
-
const ingestedTokens = tokenEvents.reduce((sum, event) => sum + tokenTotal(event.usage), 0);
|
|
2667
|
-
db.prepare(`DELETE FROM requests WHERE session_id = ?`).run(sessionId);
|
|
2668
|
-
tokenEvents.forEach((event, index) => {
|
|
2669
|
-
const usage = event.usage;
|
|
2670
|
-
const inputTotal = usage.input_tokens ?? 0;
|
|
2671
|
-
const cacheReadTokens = usage.cached_input_tokens ?? 0;
|
|
2672
|
-
const inputTokens = Math.max(inputTotal - cacheReadTokens, 0);
|
|
2673
|
-
const outputTokens = usage.output_tokens ?? Math.max((usage.total_tokens ?? 0) - inputTotal, 0);
|
|
2674
|
-
const costUsd = computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, 0);
|
|
2675
|
-
const timestamp = event.timestamp ?? (thread.created_at ? new Date(thread.created_at * 1000 + index).toISOString() : new Date().toISOString());
|
|
2676
|
-
const requestId = `${sessionId}-${index}`;
|
|
2677
|
-
upsertRequest(db, withAccount({
|
|
2678
|
-
id: requestId,
|
|
2674
|
+
const threads = codexDb.prepare(buildThreadQuery(codexDb)).all();
|
|
2675
|
+
for (const thread of threads) {
|
|
2676
|
+
const model = thread.model ?? readCodexModel(source.configPath);
|
|
2677
|
+
const stateValue = `${CODEX_INGEST_VERSION}:${thread.updated_at}:${thread.tokens_used}:${model}`;
|
|
2678
|
+
const processed = getIngestState(db, source.stateSource, thread.id);
|
|
2679
|
+
if (processed === stateValue)
|
|
2680
|
+
continue;
|
|
2681
|
+
const projectPath = thread.cwd ?? "";
|
|
2682
|
+
const projectName = projectPath ? basename2(projectPath) : "unknown";
|
|
2683
|
+
const sessionId = `${source.sessionPrefix}-${thread.id}`;
|
|
2684
|
+
const startedAt = thread.created_at ? new Date(thread.created_at * 1000).toISOString() : new Date().toISOString();
|
|
2685
|
+
const endedAt = thread.updated_at ? new Date(thread.updated_at * 1000).toISOString() : null;
|
|
2686
|
+
upsertSession(db, withAccount({
|
|
2687
|
+
id: sessionId,
|
|
2679
2688
|
agent: "codex",
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
cost_basis: defaultCostBasisForAgent("codex"),
|
|
2688
|
-
duration_ms: 0,
|
|
2689
|
-
timestamp,
|
|
2690
|
-
source_request_id: requestId,
|
|
2689
|
+
project_path: projectPath,
|
|
2690
|
+
project_name: projectName,
|
|
2691
|
+
started_at: startedAt,
|
|
2692
|
+
ended_at: endedAt,
|
|
2693
|
+
total_cost_usd: 0,
|
|
2694
|
+
total_tokens: 0,
|
|
2695
|
+
request_count: 0,
|
|
2691
2696
|
machine_id: machineId
|
|
2692
2697
|
}, account));
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2698
|
+
const events = readTokenEvents(thread.rollout_path);
|
|
2699
|
+
const tokenEvents = events.length > 0 ? events : fallbackEvents(thread.tokens_used);
|
|
2700
|
+
const ingestedTokens = tokenEvents.reduce((sum, event) => sum + tokenTotal(event.usage), 0);
|
|
2701
|
+
db.prepare(`DELETE FROM requests WHERE session_id = ?`).run(sessionId);
|
|
2702
|
+
tokenEvents.forEach((event, index) => {
|
|
2703
|
+
const usage = event.usage;
|
|
2704
|
+
const inputTotal = usage.input_tokens ?? 0;
|
|
2705
|
+
const cacheReadTokens = usage.cached_input_tokens ?? 0;
|
|
2706
|
+
const inputTokens = Math.max(inputTotal - cacheReadTokens, 0);
|
|
2707
|
+
const outputTokens = usage.output_tokens ?? Math.max((usage.total_tokens ?? 0) - inputTotal, 0);
|
|
2708
|
+
const costUsd = computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, 0);
|
|
2709
|
+
const timestamp = event.timestamp ?? (thread.created_at ? new Date(thread.created_at * 1000 + index).toISOString() : new Date().toISOString());
|
|
2710
|
+
const requestId = `${sessionId}-${index}`;
|
|
2711
|
+
upsertRequest(db, withAccount({
|
|
2712
|
+
id: requestId,
|
|
2713
|
+
agent: "codex",
|
|
2714
|
+
session_id: sessionId,
|
|
2715
|
+
model,
|
|
2716
|
+
input_tokens: inputTokens,
|
|
2717
|
+
output_tokens: outputTokens,
|
|
2718
|
+
cache_read_tokens: cacheReadTokens,
|
|
2719
|
+
cache_create_tokens: 0,
|
|
2720
|
+
cost_usd: costUsd,
|
|
2721
|
+
cost_basis: defaultCostBasisForAgent("codex"),
|
|
2722
|
+
duration_ms: 0,
|
|
2723
|
+
timestamp,
|
|
2724
|
+
source_request_id: requestId,
|
|
2725
|
+
machine_id: machineId
|
|
2726
|
+
}, account));
|
|
2727
|
+
requests++;
|
|
2728
|
+
});
|
|
2729
|
+
rollupSession(db, sessionId);
|
|
2730
|
+
setIngestState(db, source.stateSource, thread.id, stateValue);
|
|
2731
|
+
ingested++;
|
|
2732
|
+
if (verbose)
|
|
2733
|
+
console.log(`${source.label} session ${thread.id}: ${ingestedTokens} tokens on ${model}`);
|
|
2734
|
+
}
|
|
2735
|
+
} finally {
|
|
2736
|
+
codexDb?.close();
|
|
2700
2737
|
}
|
|
2701
|
-
} finally {
|
|
2702
|
-
codexDb?.close();
|
|
2703
2738
|
}
|
|
2704
2739
|
return { sessions: ingested, requests };
|
|
2705
2740
|
}
|
package/dist/ingest/codex.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SqliteAdapter as Database } from '@hasna/cloud';
|
|
2
|
-
declare function readCodexModel(): string;
|
|
2
|
+
declare function readCodexModel(configPath?: string): string;
|
|
3
3
|
export declare function ingestCodex(db: Database, verbose?: boolean): Promise<{
|
|
4
4
|
sessions: number;
|
|
5
5
|
requests: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../src/ingest/codex.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../src/ingest/codex.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AA4F7D,iBAAS,cAAc,CAAC,UAAU,SAAoB,GAAG,MAAM,CAS9D;AAwGD,wBAAsB,WAAW,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,UAAQ,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA+FhH;AAED,OAAO,EAAE,cAAc,EAAE,CAAA"}
|
package/dist/mcp/index.js
CHANGED
|
@@ -2240,6 +2240,8 @@ import { join as join3, basename as basename2 } from "path";
|
|
|
2240
2240
|
import { Database as BunDatabase } from "bun:sqlite";
|
|
2241
2241
|
var DEFAULT_CODEX_DB_PATH = join3(homedir3(), ".codex", "state_5.sqlite");
|
|
2242
2242
|
var DEFAULT_CODEX_CONFIG_PATH = join3(homedir3(), ".codex", "config.toml");
|
|
2243
|
+
var DEFAULT_CODEWITH_DB_PATH = join3(homedir3(), ".codewith", "state_5.sqlite");
|
|
2244
|
+
var DEFAULT_CODEWITH_CONFIG_PATH = join3(homedir3(), ".codewith", "config.toml");
|
|
2243
2245
|
var CODEX_INGEST_VERSION = "rollout-aggregate-v3";
|
|
2244
2246
|
function codexDbPath() {
|
|
2245
2247
|
return process.env["HASNA_ECONOMY_CODEX_DB_PATH"] ?? DEFAULT_CODEX_DB_PATH;
|
|
@@ -2247,8 +2249,40 @@ function codexDbPath() {
|
|
|
2247
2249
|
function codexConfigPath() {
|
|
2248
2250
|
return process.env["HASNA_ECONOMY_CODEX_CONFIG_PATH"] ?? DEFAULT_CODEX_CONFIG_PATH;
|
|
2249
2251
|
}
|
|
2250
|
-
function
|
|
2251
|
-
|
|
2252
|
+
function codewithDbPath() {
|
|
2253
|
+
return process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"] ?? DEFAULT_CODEWITH_DB_PATH;
|
|
2254
|
+
}
|
|
2255
|
+
function codewithConfigPath() {
|
|
2256
|
+
return process.env["HASNA_ECONOMY_CODEWITH_CONFIG_PATH"] ?? DEFAULT_CODEWITH_CONFIG_PATH;
|
|
2257
|
+
}
|
|
2258
|
+
function codexSources() {
|
|
2259
|
+
const explicitCodexPath = process.env["HASNA_ECONOMY_CODEX_DB_PATH"];
|
|
2260
|
+
const explicitCodewithPath = process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"];
|
|
2261
|
+
const sources = [{
|
|
2262
|
+
label: "Codex",
|
|
2263
|
+
dbPath: codexDbPath(),
|
|
2264
|
+
configPath: codexConfigPath(),
|
|
2265
|
+
sessionPrefix: "codex",
|
|
2266
|
+
stateSource: "codex"
|
|
2267
|
+
}];
|
|
2268
|
+
if (!explicitCodexPath || explicitCodewithPath) {
|
|
2269
|
+
sources.push({
|
|
2270
|
+
label: "Codewith",
|
|
2271
|
+
dbPath: codewithDbPath(),
|
|
2272
|
+
configPath: codewithConfigPath(),
|
|
2273
|
+
sessionPrefix: "codex-codewith",
|
|
2274
|
+
stateSource: "codex-codewith"
|
|
2275
|
+
});
|
|
2276
|
+
}
|
|
2277
|
+
const seen = new Set;
|
|
2278
|
+
return sources.filter((source) => {
|
|
2279
|
+
if (seen.has(source.dbPath))
|
|
2280
|
+
return false;
|
|
2281
|
+
seen.add(source.dbPath);
|
|
2282
|
+
return true;
|
|
2283
|
+
});
|
|
2284
|
+
}
|
|
2285
|
+
function readCodexModel(configPath = codexConfigPath()) {
|
|
2252
2286
|
if (!existsSync3(configPath))
|
|
2253
2287
|
return "gpt-5-codex";
|
|
2254
2288
|
try {
|
|
@@ -2366,84 +2400,85 @@ function fallbackEvents(totalTokens) {
|
|
|
2366
2400
|
}];
|
|
2367
2401
|
}
|
|
2368
2402
|
async function ingestCodex(db, verbose = false) {
|
|
2369
|
-
const dbPath = codexDbPath();
|
|
2370
|
-
if (!existsSync3(dbPath)) {
|
|
2371
|
-
if (verbose)
|
|
2372
|
-
console.log("Codex DB not found:", dbPath);
|
|
2373
|
-
return { sessions: 0, requests: 0 };
|
|
2374
|
-
}
|
|
2375
2403
|
const machineId = getMachineId();
|
|
2376
|
-
let codexDb = null;
|
|
2377
2404
|
let ingested = 0;
|
|
2378
2405
|
let requests = 0;
|
|
2379
2406
|
const account = await resolveAccountForAgent("codex");
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
if (
|
|
2407
|
+
for (const source of codexSources()) {
|
|
2408
|
+
if (!existsSync3(source.dbPath)) {
|
|
2409
|
+
if (verbose)
|
|
2410
|
+
console.log(`${source.label} DB not found:`, source.dbPath);
|
|
2411
|
+
continue;
|
|
2412
|
+
}
|
|
2413
|
+
let codexDb = null;
|
|
2414
|
+
try {
|
|
2415
|
+
codexDb = openCodexDb(source.dbPath, verbose);
|
|
2416
|
+
if (!codexDb)
|
|
2390
2417
|
continue;
|
|
2391
|
-
const
|
|
2392
|
-
const
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
request_count: 0,
|
|
2406
|
-
machine_id: machineId
|
|
2407
|
-
}, account));
|
|
2408
|
-
const events = readTokenEvents(thread.rollout_path);
|
|
2409
|
-
const tokenEvents = events.length > 0 ? events : fallbackEvents(thread.tokens_used);
|
|
2410
|
-
const ingestedTokens = tokenEvents.reduce((sum, event) => sum + tokenTotal(event.usage), 0);
|
|
2411
|
-
db.prepare(`DELETE FROM requests WHERE session_id = ?`).run(sessionId);
|
|
2412
|
-
tokenEvents.forEach((event, index) => {
|
|
2413
|
-
const usage = event.usage;
|
|
2414
|
-
const inputTotal = usage.input_tokens ?? 0;
|
|
2415
|
-
const cacheReadTokens = usage.cached_input_tokens ?? 0;
|
|
2416
|
-
const inputTokens = Math.max(inputTotal - cacheReadTokens, 0);
|
|
2417
|
-
const outputTokens = usage.output_tokens ?? Math.max((usage.total_tokens ?? 0) - inputTotal, 0);
|
|
2418
|
-
const costUsd = computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, 0);
|
|
2419
|
-
const timestamp = event.timestamp ?? (thread.created_at ? new Date(thread.created_at * 1000 + index).toISOString() : new Date().toISOString());
|
|
2420
|
-
const requestId = `${sessionId}-${index}`;
|
|
2421
|
-
upsertRequest(db, withAccount({
|
|
2422
|
-
id: requestId,
|
|
2418
|
+
const threads = codexDb.prepare(buildThreadQuery(codexDb)).all();
|
|
2419
|
+
for (const thread of threads) {
|
|
2420
|
+
const model = thread.model ?? readCodexModel(source.configPath);
|
|
2421
|
+
const stateValue = `${CODEX_INGEST_VERSION}:${thread.updated_at}:${thread.tokens_used}:${model}`;
|
|
2422
|
+
const processed = getIngestState(db, source.stateSource, thread.id);
|
|
2423
|
+
if (processed === stateValue)
|
|
2424
|
+
continue;
|
|
2425
|
+
const projectPath = thread.cwd ?? "";
|
|
2426
|
+
const projectName = projectPath ? basename2(projectPath) : "unknown";
|
|
2427
|
+
const sessionId = `${source.sessionPrefix}-${thread.id}`;
|
|
2428
|
+
const startedAt = thread.created_at ? new Date(thread.created_at * 1000).toISOString() : new Date().toISOString();
|
|
2429
|
+
const endedAt = thread.updated_at ? new Date(thread.updated_at * 1000).toISOString() : null;
|
|
2430
|
+
upsertSession(db, withAccount({
|
|
2431
|
+
id: sessionId,
|
|
2423
2432
|
agent: "codex",
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
cost_basis: defaultCostBasisForAgent("codex"),
|
|
2432
|
-
duration_ms: 0,
|
|
2433
|
-
timestamp,
|
|
2434
|
-
source_request_id: requestId,
|
|
2433
|
+
project_path: projectPath,
|
|
2434
|
+
project_name: projectName,
|
|
2435
|
+
started_at: startedAt,
|
|
2436
|
+
ended_at: endedAt,
|
|
2437
|
+
total_cost_usd: 0,
|
|
2438
|
+
total_tokens: 0,
|
|
2439
|
+
request_count: 0,
|
|
2435
2440
|
machine_id: machineId
|
|
2436
2441
|
}, account));
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2442
|
+
const events = readTokenEvents(thread.rollout_path);
|
|
2443
|
+
const tokenEvents = events.length > 0 ? events : fallbackEvents(thread.tokens_used);
|
|
2444
|
+
const ingestedTokens = tokenEvents.reduce((sum, event) => sum + tokenTotal(event.usage), 0);
|
|
2445
|
+
db.prepare(`DELETE FROM requests WHERE session_id = ?`).run(sessionId);
|
|
2446
|
+
tokenEvents.forEach((event, index) => {
|
|
2447
|
+
const usage = event.usage;
|
|
2448
|
+
const inputTotal = usage.input_tokens ?? 0;
|
|
2449
|
+
const cacheReadTokens = usage.cached_input_tokens ?? 0;
|
|
2450
|
+
const inputTokens = Math.max(inputTotal - cacheReadTokens, 0);
|
|
2451
|
+
const outputTokens = usage.output_tokens ?? Math.max((usage.total_tokens ?? 0) - inputTotal, 0);
|
|
2452
|
+
const costUsd = computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, 0);
|
|
2453
|
+
const timestamp = event.timestamp ?? (thread.created_at ? new Date(thread.created_at * 1000 + index).toISOString() : new Date().toISOString());
|
|
2454
|
+
const requestId = `${sessionId}-${index}`;
|
|
2455
|
+
upsertRequest(db, withAccount({
|
|
2456
|
+
id: requestId,
|
|
2457
|
+
agent: "codex",
|
|
2458
|
+
session_id: sessionId,
|
|
2459
|
+
model,
|
|
2460
|
+
input_tokens: inputTokens,
|
|
2461
|
+
output_tokens: outputTokens,
|
|
2462
|
+
cache_read_tokens: cacheReadTokens,
|
|
2463
|
+
cache_create_tokens: 0,
|
|
2464
|
+
cost_usd: costUsd,
|
|
2465
|
+
cost_basis: defaultCostBasisForAgent("codex"),
|
|
2466
|
+
duration_ms: 0,
|
|
2467
|
+
timestamp,
|
|
2468
|
+
source_request_id: requestId,
|
|
2469
|
+
machine_id: machineId
|
|
2470
|
+
}, account));
|
|
2471
|
+
requests++;
|
|
2472
|
+
});
|
|
2473
|
+
rollupSession(db, sessionId);
|
|
2474
|
+
setIngestState(db, source.stateSource, thread.id, stateValue);
|
|
2475
|
+
ingested++;
|
|
2476
|
+
if (verbose)
|
|
2477
|
+
console.log(`${source.label} session ${thread.id}: ${ingestedTokens} tokens on ${model}`);
|
|
2478
|
+
}
|
|
2479
|
+
} finally {
|
|
2480
|
+
codexDb?.close();
|
|
2444
2481
|
}
|
|
2445
|
-
} finally {
|
|
2446
|
-
codexDb?.close();
|
|
2447
2482
|
}
|
|
2448
2483
|
return { sessions: ingested, requests };
|
|
2449
2484
|
}
|
package/dist/server/index.js
CHANGED
|
@@ -2698,6 +2698,8 @@ import { join as join3, basename as basename2 } from "path";
|
|
|
2698
2698
|
import { Database as BunDatabase } from "bun:sqlite";
|
|
2699
2699
|
var DEFAULT_CODEX_DB_PATH = join3(homedir3(), ".codex", "state_5.sqlite");
|
|
2700
2700
|
var DEFAULT_CODEX_CONFIG_PATH = join3(homedir3(), ".codex", "config.toml");
|
|
2701
|
+
var DEFAULT_CODEWITH_DB_PATH = join3(homedir3(), ".codewith", "state_5.sqlite");
|
|
2702
|
+
var DEFAULT_CODEWITH_CONFIG_PATH = join3(homedir3(), ".codewith", "config.toml");
|
|
2701
2703
|
var CODEX_INGEST_VERSION = "rollout-aggregate-v3";
|
|
2702
2704
|
function codexDbPath() {
|
|
2703
2705
|
return process.env["HASNA_ECONOMY_CODEX_DB_PATH"] ?? DEFAULT_CODEX_DB_PATH;
|
|
@@ -2705,8 +2707,40 @@ function codexDbPath() {
|
|
|
2705
2707
|
function codexConfigPath() {
|
|
2706
2708
|
return process.env["HASNA_ECONOMY_CODEX_CONFIG_PATH"] ?? DEFAULT_CODEX_CONFIG_PATH;
|
|
2707
2709
|
}
|
|
2708
|
-
function
|
|
2709
|
-
|
|
2710
|
+
function codewithDbPath() {
|
|
2711
|
+
return process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"] ?? DEFAULT_CODEWITH_DB_PATH;
|
|
2712
|
+
}
|
|
2713
|
+
function codewithConfigPath() {
|
|
2714
|
+
return process.env["HASNA_ECONOMY_CODEWITH_CONFIG_PATH"] ?? DEFAULT_CODEWITH_CONFIG_PATH;
|
|
2715
|
+
}
|
|
2716
|
+
function codexSources() {
|
|
2717
|
+
const explicitCodexPath = process.env["HASNA_ECONOMY_CODEX_DB_PATH"];
|
|
2718
|
+
const explicitCodewithPath = process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"];
|
|
2719
|
+
const sources = [{
|
|
2720
|
+
label: "Codex",
|
|
2721
|
+
dbPath: codexDbPath(),
|
|
2722
|
+
configPath: codexConfigPath(),
|
|
2723
|
+
sessionPrefix: "codex",
|
|
2724
|
+
stateSource: "codex"
|
|
2725
|
+
}];
|
|
2726
|
+
if (!explicitCodexPath || explicitCodewithPath) {
|
|
2727
|
+
sources.push({
|
|
2728
|
+
label: "Codewith",
|
|
2729
|
+
dbPath: codewithDbPath(),
|
|
2730
|
+
configPath: codewithConfigPath(),
|
|
2731
|
+
sessionPrefix: "codex-codewith",
|
|
2732
|
+
stateSource: "codex-codewith"
|
|
2733
|
+
});
|
|
2734
|
+
}
|
|
2735
|
+
const seen = new Set;
|
|
2736
|
+
return sources.filter((source) => {
|
|
2737
|
+
if (seen.has(source.dbPath))
|
|
2738
|
+
return false;
|
|
2739
|
+
seen.add(source.dbPath);
|
|
2740
|
+
return true;
|
|
2741
|
+
});
|
|
2742
|
+
}
|
|
2743
|
+
function readCodexModel(configPath = codexConfigPath()) {
|
|
2710
2744
|
if (!existsSync3(configPath))
|
|
2711
2745
|
return "gpt-5-codex";
|
|
2712
2746
|
try {
|
|
@@ -2824,84 +2858,85 @@ function fallbackEvents(totalTokens) {
|
|
|
2824
2858
|
}];
|
|
2825
2859
|
}
|
|
2826
2860
|
async function ingestCodex(db, verbose = false) {
|
|
2827
|
-
const dbPath = codexDbPath();
|
|
2828
|
-
if (!existsSync3(dbPath)) {
|
|
2829
|
-
if (verbose)
|
|
2830
|
-
console.log("Codex DB not found:", dbPath);
|
|
2831
|
-
return { sessions: 0, requests: 0 };
|
|
2832
|
-
}
|
|
2833
2861
|
const machineId = getMachineId();
|
|
2834
|
-
let codexDb = null;
|
|
2835
2862
|
let ingested = 0;
|
|
2836
2863
|
let requests = 0;
|
|
2837
2864
|
const account = await resolveAccountForAgent("codex");
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
if (
|
|
2865
|
+
for (const source of codexSources()) {
|
|
2866
|
+
if (!existsSync3(source.dbPath)) {
|
|
2867
|
+
if (verbose)
|
|
2868
|
+
console.log(`${source.label} DB not found:`, source.dbPath);
|
|
2869
|
+
continue;
|
|
2870
|
+
}
|
|
2871
|
+
let codexDb = null;
|
|
2872
|
+
try {
|
|
2873
|
+
codexDb = openCodexDb(source.dbPath, verbose);
|
|
2874
|
+
if (!codexDb)
|
|
2848
2875
|
continue;
|
|
2849
|
-
const
|
|
2850
|
-
const
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
request_count: 0,
|
|
2864
|
-
machine_id: machineId
|
|
2865
|
-
}, account));
|
|
2866
|
-
const events = readTokenEvents(thread.rollout_path);
|
|
2867
|
-
const tokenEvents = events.length > 0 ? events : fallbackEvents(thread.tokens_used);
|
|
2868
|
-
const ingestedTokens = tokenEvents.reduce((sum, event) => sum + tokenTotal(event.usage), 0);
|
|
2869
|
-
db.prepare(`DELETE FROM requests WHERE session_id = ?`).run(sessionId);
|
|
2870
|
-
tokenEvents.forEach((event, index) => {
|
|
2871
|
-
const usage = event.usage;
|
|
2872
|
-
const inputTotal = usage.input_tokens ?? 0;
|
|
2873
|
-
const cacheReadTokens = usage.cached_input_tokens ?? 0;
|
|
2874
|
-
const inputTokens = Math.max(inputTotal - cacheReadTokens, 0);
|
|
2875
|
-
const outputTokens = usage.output_tokens ?? Math.max((usage.total_tokens ?? 0) - inputTotal, 0);
|
|
2876
|
-
const costUsd = computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, 0);
|
|
2877
|
-
const timestamp = event.timestamp ?? (thread.created_at ? new Date(thread.created_at * 1000 + index).toISOString() : new Date().toISOString());
|
|
2878
|
-
const requestId = `${sessionId}-${index}`;
|
|
2879
|
-
upsertRequest(db, withAccount({
|
|
2880
|
-
id: requestId,
|
|
2876
|
+
const threads = codexDb.prepare(buildThreadQuery(codexDb)).all();
|
|
2877
|
+
for (const thread of threads) {
|
|
2878
|
+
const model = thread.model ?? readCodexModel(source.configPath);
|
|
2879
|
+
const stateValue = `${CODEX_INGEST_VERSION}:${thread.updated_at}:${thread.tokens_used}:${model}`;
|
|
2880
|
+
const processed = getIngestState(db, source.stateSource, thread.id);
|
|
2881
|
+
if (processed === stateValue)
|
|
2882
|
+
continue;
|
|
2883
|
+
const projectPath = thread.cwd ?? "";
|
|
2884
|
+
const projectName = projectPath ? basename2(projectPath) : "unknown";
|
|
2885
|
+
const sessionId = `${source.sessionPrefix}-${thread.id}`;
|
|
2886
|
+
const startedAt = thread.created_at ? new Date(thread.created_at * 1000).toISOString() : new Date().toISOString();
|
|
2887
|
+
const endedAt = thread.updated_at ? new Date(thread.updated_at * 1000).toISOString() : null;
|
|
2888
|
+
upsertSession(db, withAccount({
|
|
2889
|
+
id: sessionId,
|
|
2881
2890
|
agent: "codex",
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
cost_basis: defaultCostBasisForAgent("codex"),
|
|
2890
|
-
duration_ms: 0,
|
|
2891
|
-
timestamp,
|
|
2892
|
-
source_request_id: requestId,
|
|
2891
|
+
project_path: projectPath,
|
|
2892
|
+
project_name: projectName,
|
|
2893
|
+
started_at: startedAt,
|
|
2894
|
+
ended_at: endedAt,
|
|
2895
|
+
total_cost_usd: 0,
|
|
2896
|
+
total_tokens: 0,
|
|
2897
|
+
request_count: 0,
|
|
2893
2898
|
machine_id: machineId
|
|
2894
2899
|
}, account));
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2900
|
+
const events = readTokenEvents(thread.rollout_path);
|
|
2901
|
+
const tokenEvents = events.length > 0 ? events : fallbackEvents(thread.tokens_used);
|
|
2902
|
+
const ingestedTokens = tokenEvents.reduce((sum, event) => sum + tokenTotal(event.usage), 0);
|
|
2903
|
+
db.prepare(`DELETE FROM requests WHERE session_id = ?`).run(sessionId);
|
|
2904
|
+
tokenEvents.forEach((event, index) => {
|
|
2905
|
+
const usage = event.usage;
|
|
2906
|
+
const inputTotal = usage.input_tokens ?? 0;
|
|
2907
|
+
const cacheReadTokens = usage.cached_input_tokens ?? 0;
|
|
2908
|
+
const inputTokens = Math.max(inputTotal - cacheReadTokens, 0);
|
|
2909
|
+
const outputTokens = usage.output_tokens ?? Math.max((usage.total_tokens ?? 0) - inputTotal, 0);
|
|
2910
|
+
const costUsd = computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, 0);
|
|
2911
|
+
const timestamp = event.timestamp ?? (thread.created_at ? new Date(thread.created_at * 1000 + index).toISOString() : new Date().toISOString());
|
|
2912
|
+
const requestId = `${sessionId}-${index}`;
|
|
2913
|
+
upsertRequest(db, withAccount({
|
|
2914
|
+
id: requestId,
|
|
2915
|
+
agent: "codex",
|
|
2916
|
+
session_id: sessionId,
|
|
2917
|
+
model,
|
|
2918
|
+
input_tokens: inputTokens,
|
|
2919
|
+
output_tokens: outputTokens,
|
|
2920
|
+
cache_read_tokens: cacheReadTokens,
|
|
2921
|
+
cache_create_tokens: 0,
|
|
2922
|
+
cost_usd: costUsd,
|
|
2923
|
+
cost_basis: defaultCostBasisForAgent("codex"),
|
|
2924
|
+
duration_ms: 0,
|
|
2925
|
+
timestamp,
|
|
2926
|
+
source_request_id: requestId,
|
|
2927
|
+
machine_id: machineId
|
|
2928
|
+
}, account));
|
|
2929
|
+
requests++;
|
|
2930
|
+
});
|
|
2931
|
+
rollupSession(db, sessionId);
|
|
2932
|
+
setIngestState(db, source.stateSource, thread.id, stateValue);
|
|
2933
|
+
ingested++;
|
|
2934
|
+
if (verbose)
|
|
2935
|
+
console.log(`${source.label} session ${thread.id}: ${ingestedTokens} tokens on ${model}`);
|
|
2936
|
+
}
|
|
2937
|
+
} finally {
|
|
2938
|
+
codexDb?.close();
|
|
2902
2939
|
}
|
|
2903
|
-
} finally {
|
|
2904
|
-
codexDb?.close();
|
|
2905
2940
|
}
|
|
2906
2941
|
return { sessions: ingested, requests };
|
|
2907
2942
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/economy",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.37",
|
|
4
4
|
"description": "AI coding cost tracker — CLI + MCP server + REST API + web dashboard for Claude Code, Codex, Gemini, OpenCode, Cursor, Pi, and Hermes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|