@hasna/economy 0.3.6 → 0.3.7
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/CHANGELOG.md +35 -0
- package/README.md +50 -4
- package/dist/cli/commands/extras.d.ts.map +1 -1
- package/dist/cli/commands/todos.d.ts +3 -0
- package/dist/cli/commands/todos.d.ts.map +1 -1
- package/dist/cli/index.js +694 -86
- package/dist/db/database.d.ts +8 -1
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/index.js +180 -3
- package/dist/ingest/loops.d.ts +6 -0
- package/dist/ingest/loops.d.ts.map +1 -0
- package/dist/ingest/otel.d.ts +15 -2
- package/dist/ingest/otel.d.ts.map +1 -1
- package/dist/lib/store/index.d.ts +9 -1
- package/dist/lib/store/index.d.ts.map +1 -1
- package/dist/lib/sync-all.d.ts +2 -0
- package/dist/lib/sync-all.d.ts.map +1 -1
- package/dist/lib/webhooks.d.ts.map +1 -1
- package/dist/mcp/index.js +613 -72
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/otel/index.js +193 -23
- package/dist/server/index.js +435 -47
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/types/index.d.ts +37 -4
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/lib/cloud-sync.d.ts +0 -35
- package/dist/lib/cloud-sync.d.ts.map +0 -1
- package/dist/lib/fleet-sync.d.ts +0 -61
- package/dist/lib/fleet-sync.d.ts.map +0 -1
- package/dist/lib/peer-sync.d.ts +0 -21
- package/dist/lib/peer-sync.d.ts.map +0 -1
- package/dist/storage.js +0 -2746
package/dist/mcp/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,QAAQ,YAAY,CAAA;AACjC,eAAO,MAAM,qBAAqB,OAAO,CAAA;AAEzC,wBAAgB,WAAW,IAAI,GAAG,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,QAAQ,YAAY,CAAA;AACjC,eAAO,MAAM,qBAAqB,OAAO,CAAA;AAEzC,wBAAgB,WAAW,IAAI,GAAG,CAg0BjC"}
|
package/dist/otel/index.js
CHANGED
|
@@ -668,6 +668,16 @@ function addColumnIfMissing(db, table, column, definition) {
|
|
|
668
668
|
}
|
|
669
669
|
function initSchema(db) {
|
|
670
670
|
db.exec(`
|
|
671
|
+
CREATE TABLE IF NOT EXISTS cost_centers (
|
|
672
|
+
id TEXT PRIMARY KEY,
|
|
673
|
+
kind TEXT NOT NULL,
|
|
674
|
+
name TEXT NOT NULL,
|
|
675
|
+
repo_path TEXT,
|
|
676
|
+
labels_json TEXT DEFAULT '{}',
|
|
677
|
+
created_at TEXT NOT NULL,
|
|
678
|
+
updated_at TEXT NOT NULL
|
|
679
|
+
);
|
|
680
|
+
|
|
671
681
|
CREATE TABLE IF NOT EXISTS requests (
|
|
672
682
|
id TEXT PRIMARY KEY,
|
|
673
683
|
agent TEXT NOT NULL,
|
|
@@ -684,6 +694,7 @@ function initSchema(db) {
|
|
|
684
694
|
timestamp TEXT NOT NULL,
|
|
685
695
|
source_request_id TEXT,
|
|
686
696
|
machine_id TEXT DEFAULT '',
|
|
697
|
+
cost_center_id TEXT,
|
|
687
698
|
account_key TEXT DEFAULT '',
|
|
688
699
|
account_tool TEXT DEFAULT '',
|
|
689
700
|
account_name TEXT DEFAULT '',
|
|
@@ -702,6 +713,7 @@ function initSchema(db) {
|
|
|
702
713
|
total_tokens INTEGER DEFAULT 0,
|
|
703
714
|
request_count INTEGER DEFAULT 0,
|
|
704
715
|
machine_id TEXT DEFAULT '',
|
|
716
|
+
cost_center_id TEXT,
|
|
705
717
|
account_key TEXT DEFAULT '',
|
|
706
718
|
account_tool TEXT DEFAULT '',
|
|
707
719
|
account_name TEXT DEFAULT '',
|
|
@@ -722,6 +734,7 @@ function initSchema(db) {
|
|
|
722
734
|
id TEXT PRIMARY KEY,
|
|
723
735
|
project_path TEXT,
|
|
724
736
|
agent TEXT,
|
|
737
|
+
cost_center_id TEXT,
|
|
725
738
|
period TEXT NOT NULL,
|
|
726
739
|
limit_usd REAL NOT NULL,
|
|
727
740
|
alert_at_percent INTEGER DEFAULT 80,
|
|
@@ -752,6 +765,7 @@ function initSchema(db) {
|
|
|
752
765
|
CREATE INDEX IF NOT EXISTS idx_sessions_agent ON sessions(agent);
|
|
753
766
|
CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_path);
|
|
754
767
|
CREATE INDEX IF NOT EXISTS idx_sessions_started ON sessions(started_at);
|
|
768
|
+
CREATE INDEX IF NOT EXISTS idx_cost_centers_kind ON cost_centers(kind);
|
|
755
769
|
|
|
756
770
|
CREATE TABLE IF NOT EXISTS model_pricing (
|
|
757
771
|
model TEXT PRIMARY KEY,
|
|
@@ -838,6 +852,12 @@ function initSchema(db) {
|
|
|
838
852
|
`);
|
|
839
853
|
addColumnIfMissing(db, "requests", "machine_id", `TEXT DEFAULT ''`);
|
|
840
854
|
addColumnIfMissing(db, "sessions", "machine_id", `TEXT DEFAULT ''`);
|
|
855
|
+
if (addColumnIfMissing(db, "cost_centers", "updated_at", `TEXT DEFAULT ''`)) {
|
|
856
|
+
db.exec(`UPDATE cost_centers SET updated_at = created_at WHERE updated_at = '' OR updated_at IS NULL`);
|
|
857
|
+
}
|
|
858
|
+
addColumnIfMissing(db, "requests", "cost_center_id", `TEXT`);
|
|
859
|
+
addColumnIfMissing(db, "sessions", "cost_center_id", `TEXT`);
|
|
860
|
+
addColumnIfMissing(db, "budgets", "cost_center_id", `TEXT`);
|
|
841
861
|
if (addColumnIfMissing(db, "requests", "cache_create_5m_tokens", "INTEGER DEFAULT 0")) {
|
|
842
862
|
db.exec(`UPDATE requests SET cache_create_5m_tokens = cache_create_tokens WHERE cache_create_5m_tokens = 0`);
|
|
843
863
|
}
|
|
@@ -866,8 +886,20 @@ function initSchema(db) {
|
|
|
866
886
|
CREATE INDEX IF NOT EXISTS idx_sessions_machine ON sessions(machine_id);
|
|
867
887
|
CREATE INDEX IF NOT EXISTS idx_requests_account ON requests(account_key);
|
|
868
888
|
CREATE INDEX IF NOT EXISTS idx_sessions_account ON sessions(account_key);
|
|
889
|
+
CREATE INDEX IF NOT EXISTS idx_requests_cost_center ON requests(cost_center_id);
|
|
890
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_cost_center ON sessions(cost_center_id);
|
|
891
|
+
CREATE INDEX IF NOT EXISTS idx_budgets_cost_center ON budgets(cost_center_id);
|
|
892
|
+
CREATE INDEX IF NOT EXISTS idx_cost_centers_kind ON cost_centers(kind);
|
|
869
893
|
`);
|
|
870
894
|
}
|
|
895
|
+
function upsertCostCenter(db, center) {
|
|
896
|
+
const updatedAt = center.updated_at ?? center.created_at;
|
|
897
|
+
db.prepare(`
|
|
898
|
+
INSERT OR REPLACE INTO cost_centers
|
|
899
|
+
(id, kind, name, repo_path, labels_json, created_at, updated_at)
|
|
900
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
901
|
+
`).run(center.id, center.kind, center.name, center.repo_path ?? null, center.labels_json ?? "{}", center.created_at, updatedAt);
|
|
902
|
+
}
|
|
871
903
|
function upsertRequest(db, req) {
|
|
872
904
|
const now = req.updated_at ?? new Date().toISOString();
|
|
873
905
|
db.prepare(`
|
|
@@ -875,10 +907,10 @@ function upsertRequest(db, req) {
|
|
|
875
907
|
(id, agent, session_id, model, input_tokens, output_tokens,
|
|
876
908
|
cache_read_tokens, cache_create_tokens, cache_create_5m_tokens,
|
|
877
909
|
cache_create_1h_tokens, cost_usd, cost_basis, duration_ms, timestamp,
|
|
878
|
-
source_request_id, machine_id, attribution_tag, account_key, account_tool,
|
|
910
|
+
source_request_id, machine_id, cost_center_id, attribution_tag, account_key, account_tool,
|
|
879
911
|
account_name, account_email, account_source, updated_at, synced_at)
|
|
880
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
881
|
-
`).run(req.id, req.agent, req.session_id, req.model, req.input_tokens, req.output_tokens, req.cache_read_tokens, req.cache_create_tokens, req.cache_create_5m_tokens ?? req.cache_create_tokens, req.cache_create_1h_tokens ?? 0, req.cost_usd, req.cost_basis ?? "estimated", req.duration_ms, req.timestamp, req.source_request_id, req.machine_id ?? "", req.attribution_tag ?? process.env["ECONOMY_TAG"] ?? "", req.account_key ?? "", req.account_tool ?? "", req.account_name ?? "", req.account_email ?? "", req.account_source ?? "", now, req.synced_at ?? "");
|
|
912
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
913
|
+
`).run(req.id, req.agent, req.session_id, req.model, req.input_tokens, req.output_tokens, req.cache_read_tokens, req.cache_create_tokens, req.cache_create_5m_tokens ?? req.cache_create_tokens, req.cache_create_1h_tokens ?? 0, req.cost_usd, req.cost_basis ?? "estimated", req.duration_ms, req.timestamp, req.source_request_id, req.machine_id ?? "", req.cost_center_id ?? null, req.attribution_tag ?? process.env["ECONOMY_TAG"] ?? "", req.account_key ?? "", req.account_tool ?? "", req.account_name ?? "", req.account_email ?? "", req.account_source ?? "", now, req.synced_at ?? "");
|
|
882
914
|
}
|
|
883
915
|
function upsertSession(db, session) {
|
|
884
916
|
const now = session.updated_at ?? new Date().toISOString();
|
|
@@ -886,9 +918,9 @@ function upsertSession(db, session) {
|
|
|
886
918
|
INSERT OR REPLACE INTO sessions
|
|
887
919
|
(id, agent, project_path, project_name, started_at, ended_at,
|
|
888
920
|
total_cost_usd, total_tokens, request_count, machine_id, attribution_tag,
|
|
889
|
-
account_key, account_tool, account_name, account_email, account_source, updated_at, synced_at)
|
|
890
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
891
|
-
`).run(session.id, session.agent, session.project_path, session.project_name, session.started_at, session.ended_at ?? null, session.total_cost_usd, session.total_tokens, session.request_count, session.machine_id ?? "", session.attribution_tag ?? process.env["ECONOMY_TAG"] ?? "", session.account_key ?? "", session.account_tool ?? "", session.account_name ?? "", session.account_email ?? "", session.account_source ?? "", now, session.synced_at ?? "");
|
|
921
|
+
cost_center_id, account_key, account_tool, account_name, account_email, account_source, updated_at, synced_at)
|
|
922
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
923
|
+
`).run(session.id, session.agent, session.project_path, session.project_name, session.started_at, session.ended_at ?? null, session.total_cost_usd, session.total_tokens, session.request_count, session.machine_id ?? "", session.attribution_tag ?? process.env["ECONOMY_TAG"] ?? "", session.cost_center_id ?? null, session.account_key ?? "", session.account_tool ?? "", session.account_name ?? "", session.account_email ?? "", session.account_source ?? "", now, session.synced_at ?? "");
|
|
892
924
|
}
|
|
893
925
|
function rollupSession(db, sessionId) {
|
|
894
926
|
db.prepare(`
|
|
@@ -914,9 +946,12 @@ function rollupSession(db, sessionId) {
|
|
|
914
946
|
ELSE account_email END,
|
|
915
947
|
account_source = CASE WHEN account_source = '' OR account_source IS NULL
|
|
916
948
|
THEN COALESCE((SELECT account_source FROM requests WHERE session_id = ? AND account_source != '' ORDER BY timestamp DESC LIMIT 1), '')
|
|
917
|
-
ELSE account_source END
|
|
949
|
+
ELSE account_source END,
|
|
950
|
+
cost_center_id = CASE WHEN cost_center_id = '' OR cost_center_id IS NULL
|
|
951
|
+
THEN COALESCE((SELECT cost_center_id FROM requests WHERE session_id = ? AND cost_center_id IS NOT NULL AND cost_center_id != '' ORDER BY timestamp DESC LIMIT 1), NULL)
|
|
952
|
+
ELSE cost_center_id END
|
|
918
953
|
WHERE id = ?
|
|
919
|
-
`).run(sessionId, sessionId, sessionId, sessionId, sessionId, sessionId, sessionId, sessionId, sessionId, sessionId, sessionId);
|
|
954
|
+
`).run(sessionId, sessionId, sessionId, sessionId, sessionId, sessionId, sessionId, sessionId, sessionId, sessionId, sessionId, sessionId);
|
|
920
955
|
}
|
|
921
956
|
function upsertModelPricing(db, p) {
|
|
922
957
|
db.prepare(`
|
|
@@ -995,6 +1030,97 @@ function attrsMap(attributes) {
|
|
|
995
1030
|
}
|
|
996
1031
|
return out;
|
|
997
1032
|
}
|
|
1033
|
+
var COST_CENTER_KINDS = ["loop", "app", "repo", "service", "team"];
|
|
1034
|
+
var PSEUDO_AGENTS = ["app", "service", "repo", "loop"];
|
|
1035
|
+
function isCostCenterKind(value) {
|
|
1036
|
+
return COST_CENTER_KINDS.includes(value);
|
|
1037
|
+
}
|
|
1038
|
+
function asString(value) {
|
|
1039
|
+
if (value == null)
|
|
1040
|
+
return;
|
|
1041
|
+
const text = String(value).trim();
|
|
1042
|
+
return text ? text : undefined;
|
|
1043
|
+
}
|
|
1044
|
+
function firstString(source, keys) {
|
|
1045
|
+
for (const key of keys) {
|
|
1046
|
+
const value = asString(source[key]);
|
|
1047
|
+
if (value)
|
|
1048
|
+
return value;
|
|
1049
|
+
}
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
function normalizeCostCenterKind(raw) {
|
|
1053
|
+
if (!raw)
|
|
1054
|
+
return;
|
|
1055
|
+
const value = raw.toLowerCase();
|
|
1056
|
+
return isCostCenterKind(value) ? value : undefined;
|
|
1057
|
+
}
|
|
1058
|
+
function normalizeAgent(raw, kind) {
|
|
1059
|
+
if (raw && isAgent(raw))
|
|
1060
|
+
return raw;
|
|
1061
|
+
if (raw && PSEUDO_AGENTS.includes(raw))
|
|
1062
|
+
return raw;
|
|
1063
|
+
if (kind && PSEUDO_AGENTS.includes(kind))
|
|
1064
|
+
return kind;
|
|
1065
|
+
return "service";
|
|
1066
|
+
}
|
|
1067
|
+
function slug(value) {
|
|
1068
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "unknown";
|
|
1069
|
+
}
|
|
1070
|
+
function costCenterId(kind, name, explicitId) {
|
|
1071
|
+
if (explicitId)
|
|
1072
|
+
return explicitId;
|
|
1073
|
+
if (!kind || !name)
|
|
1074
|
+
return;
|
|
1075
|
+
return `${kind}:${slug(name)}`;
|
|
1076
|
+
}
|
|
1077
|
+
function projectNameFromPath(projectPath) {
|
|
1078
|
+
if (!projectPath)
|
|
1079
|
+
return;
|
|
1080
|
+
return projectPath.split("/").filter(Boolean).at(-1);
|
|
1081
|
+
}
|
|
1082
|
+
function extractAttribution(source) {
|
|
1083
|
+
const sourceKind = firstString(source, ["source", "source_kind", "ai.source"]);
|
|
1084
|
+
const rawKind = firstString(source, ["cost_center_kind", "cost_center.kind", "economy.cost_center.kind"]) ?? sourceKind;
|
|
1085
|
+
const costCenterKind = normalizeCostCenterKind(rawKind);
|
|
1086
|
+
const costCenter = firstString(source, [
|
|
1087
|
+
"cost_center",
|
|
1088
|
+
"cost_center_name",
|
|
1089
|
+
"cost_center.name",
|
|
1090
|
+
"economy.cost_center",
|
|
1091
|
+
"service.name",
|
|
1092
|
+
"app",
|
|
1093
|
+
"app.name",
|
|
1094
|
+
"repo",
|
|
1095
|
+
"repository"
|
|
1096
|
+
]);
|
|
1097
|
+
const projectPath = firstString(source, ["project_path", "project.path", "repo_path", "repository.path"]);
|
|
1098
|
+
const explicitId = firstString(source, ["cost_center_id", "cost_center.id", "economy.cost_center.id"]);
|
|
1099
|
+
const costBasisRaw = firstString(source, ["cost_basis", "economy.cost_basis"]);
|
|
1100
|
+
const costBasis = costBasisRaw && ["metered_api", "subscription_included", "estimated", "unknown"].includes(costBasisRaw) ? costBasisRaw : undefined;
|
|
1101
|
+
return {
|
|
1102
|
+
agent: normalizeAgent(firstString(source, ["agent", "ai.agent"]) ?? sourceKind, costCenterKind),
|
|
1103
|
+
cost_center: costCenter,
|
|
1104
|
+
cost_center_kind: costCenterKind,
|
|
1105
|
+
cost_center_id: costCenterId(costCenterKind, costCenter, explicitId),
|
|
1106
|
+
attribution_tag: firstString(source, ["attribution_tag", "attribution.tag", "economy.attribution_tag"]),
|
|
1107
|
+
project_path: projectPath,
|
|
1108
|
+
project_name: firstString(source, ["project_name", "project.name"]) ?? projectNameFromPath(projectPath) ?? costCenter,
|
|
1109
|
+
repo: firstString(source, ["repo", "repository", "repository.name"]),
|
|
1110
|
+
account_key: firstString(source, ["account_key", "account.key", "economy.account_key"]),
|
|
1111
|
+
account_tool: firstString(source, ["account_tool", "account.tool", "economy.account_tool"]),
|
|
1112
|
+
account_name: firstString(source, ["account_name", "account.name", "economy.account_name"]),
|
|
1113
|
+
account_email: firstString(source, ["account_email", "account.email", "economy.account_email"]),
|
|
1114
|
+
account_source: firstString(source, ["account_source", "account.source", "economy.account_source"]),
|
|
1115
|
+
cost_basis: costBasis
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
function mergeAttribution(row, attribution) {
|
|
1119
|
+
return {
|
|
1120
|
+
...row,
|
|
1121
|
+
...Object.fromEntries(Object.entries(attribution).filter(([, value]) => value !== undefined && value !== ""))
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
998
1124
|
function metricKind(body) {
|
|
999
1125
|
const name = String(body["name"] ?? "").toLowerCase();
|
|
1000
1126
|
if (name.includes("cost") || name.includes(".usd"))
|
|
@@ -1043,13 +1169,13 @@ function parseOtlpMetrics(body) {
|
|
|
1043
1169
|
continue;
|
|
1044
1170
|
const pointAttrs = attrsMap(dp.attributes);
|
|
1045
1171
|
const merged = { ...resourceAttrs, ...pointAttrs };
|
|
1046
|
-
const
|
|
1047
|
-
const agent =
|
|
1172
|
+
const attribution = extractAttribution(merged);
|
|
1173
|
+
const agent = attribution.agent;
|
|
1048
1174
|
const sessionId = String(merged["session_id"] ?? merged["session.id"] ?? "otel-session");
|
|
1049
1175
|
const model = String(merged["model"] ?? merged["ai.model"] ?? "unknown");
|
|
1050
1176
|
const sourceId = String(merged["request_id"] ?? merged["event.id"] ?? `${sessionId}-${kind}`);
|
|
1051
1177
|
const key = `${agent}:${sourceId}`;
|
|
1052
|
-
const row = partial.get(key) ?? {
|
|
1178
|
+
const row = mergeAttribution(partial.get(key) ?? {
|
|
1053
1179
|
key,
|
|
1054
1180
|
agent,
|
|
1055
1181
|
session_id: sessionId,
|
|
@@ -1059,7 +1185,7 @@ function parseOtlpMetrics(body) {
|
|
|
1059
1185
|
output_tokens: 0,
|
|
1060
1186
|
timestamp: new Date().toISOString(),
|
|
1061
1187
|
source_request_id: sourceId
|
|
1062
|
-
};
|
|
1188
|
+
}, attribution);
|
|
1063
1189
|
const asDouble = dp.asDouble;
|
|
1064
1190
|
const asInt = dp.asInt;
|
|
1065
1191
|
const value = asDouble ?? (asInt != null ? Number(asInt) : 0);
|
|
@@ -1086,16 +1212,27 @@ function parseOtlpMetrics(body) {
|
|
|
1086
1212
|
input_tokens: r.input_tokens ?? 0,
|
|
1087
1213
|
output_tokens: r.output_tokens ?? 0,
|
|
1088
1214
|
timestamp: r.timestamp,
|
|
1089
|
-
source_request_id: r.source_request_id
|
|
1215
|
+
source_request_id: r.source_request_id,
|
|
1216
|
+
cost_basis: r.cost_basis,
|
|
1217
|
+
cost_center: r.cost_center,
|
|
1218
|
+
cost_center_kind: r.cost_center_kind,
|
|
1219
|
+
cost_center_id: r.cost_center_id,
|
|
1220
|
+
attribution_tag: r.attribution_tag,
|
|
1221
|
+
project_path: r.project_path,
|
|
1222
|
+
project_name: r.project_name,
|
|
1223
|
+
repo: r.repo,
|
|
1224
|
+
account_key: r.account_key,
|
|
1225
|
+
account_tool: r.account_tool,
|
|
1226
|
+
account_name: r.account_name,
|
|
1227
|
+
account_email: r.account_email,
|
|
1228
|
+
account_source: r.account_source
|
|
1090
1229
|
}));
|
|
1091
1230
|
}
|
|
1092
1231
|
function parseSimpleIngest(body) {
|
|
1093
1232
|
if (!body || typeof body !== "object")
|
|
1094
1233
|
return null;
|
|
1095
1234
|
const b = body;
|
|
1096
|
-
const
|
|
1097
|
-
if (!isAgent(agentRaw))
|
|
1098
|
-
return null;
|
|
1235
|
+
const attribution = extractAttribution(b);
|
|
1099
1236
|
const cost = Number(b["cost_usd"] ?? 0);
|
|
1100
1237
|
const input = Number(b["input_tokens"] ?? 0);
|
|
1101
1238
|
const output = Number(b["output_tokens"] ?? 0);
|
|
@@ -1104,7 +1241,8 @@ function parseSimpleIngest(body) {
|
|
|
1104
1241
|
const sessionId = String(b["session_id"] ?? "otel-session");
|
|
1105
1242
|
const sourceId = String(b["request_id"] ?? b["source_request_id"] ?? `${sessionId}-${Date.now()}`);
|
|
1106
1243
|
return {
|
|
1107
|
-
|
|
1244
|
+
...attribution,
|
|
1245
|
+
agent: attribution.agent,
|
|
1108
1246
|
session_id: sessionId,
|
|
1109
1247
|
model: String(b["model"] ?? "unknown"),
|
|
1110
1248
|
cost_usd: cost,
|
|
@@ -1114,12 +1252,31 @@ function parseSimpleIngest(body) {
|
|
|
1114
1252
|
source_request_id: sourceId
|
|
1115
1253
|
};
|
|
1116
1254
|
}
|
|
1255
|
+
function ensureCostCenter(db, row) {
|
|
1256
|
+
const id = row.cost_center_id ?? costCenterId(row.cost_center_kind, row.cost_center);
|
|
1257
|
+
if (!id || !row.cost_center_kind || !row.cost_center)
|
|
1258
|
+
return id;
|
|
1259
|
+
const labels = { source: "otel" };
|
|
1260
|
+
if (row.repo)
|
|
1261
|
+
labels["repo"] = row.repo;
|
|
1262
|
+
upsertCostCenter(db, {
|
|
1263
|
+
id,
|
|
1264
|
+
kind: row.cost_center_kind,
|
|
1265
|
+
name: row.cost_center,
|
|
1266
|
+
repo_path: row.project_path ?? null,
|
|
1267
|
+
labels_json: JSON.stringify(labels),
|
|
1268
|
+
created_at: row.timestamp
|
|
1269
|
+
});
|
|
1270
|
+
return id;
|
|
1271
|
+
}
|
|
1117
1272
|
async function ingestOtelRows(db, rows) {
|
|
1118
1273
|
const machineId = getMachineId();
|
|
1119
1274
|
const sessions = new Set;
|
|
1120
1275
|
let requests = 0;
|
|
1121
1276
|
for (const row of rows) {
|
|
1122
|
-
const
|
|
1277
|
+
const costCenterId2 = ensureCostCenter(db, row);
|
|
1278
|
+
const requestScope = slug(costCenterId2 ?? row.cost_center ?? row.project_path ?? row.session_id);
|
|
1279
|
+
const reqId = `otel-${row.agent}-${requestScope}-${row.source_request_id}`;
|
|
1123
1280
|
upsertRequest(db, {
|
|
1124
1281
|
id: reqId,
|
|
1125
1282
|
agent: row.agent,
|
|
@@ -1130,25 +1287,38 @@ async function ingestOtelRows(db, rows) {
|
|
|
1130
1287
|
cache_read_tokens: 0,
|
|
1131
1288
|
cache_create_tokens: 0,
|
|
1132
1289
|
cost_usd: row.cost_usd,
|
|
1133
|
-
cost_basis: "estimated",
|
|
1290
|
+
cost_basis: row.cost_basis ?? "estimated",
|
|
1134
1291
|
duration_ms: 0,
|
|
1135
1292
|
timestamp: row.timestamp,
|
|
1136
1293
|
source_request_id: row.source_request_id,
|
|
1137
1294
|
machine_id: machineId,
|
|
1138
|
-
|
|
1295
|
+
cost_center_id: costCenterId2 ?? null,
|
|
1296
|
+
attribution_tag: row.attribution_tag ?? "otel",
|
|
1297
|
+
account_key: row.account_key ?? "",
|
|
1298
|
+
account_tool: row.account_tool ?? "",
|
|
1299
|
+
account_name: row.account_name ?? "",
|
|
1300
|
+
account_email: row.account_email ?? "",
|
|
1301
|
+
account_source: row.account_source ?? ""
|
|
1139
1302
|
});
|
|
1140
1303
|
if (!sessions.has(row.session_id)) {
|
|
1141
1304
|
upsertSession(db, {
|
|
1142
1305
|
id: row.session_id,
|
|
1143
1306
|
agent: row.agent,
|
|
1144
|
-
project_path: "",
|
|
1145
|
-
project_name: "",
|
|
1307
|
+
project_path: row.project_path ?? "",
|
|
1308
|
+
project_name: row.project_name ?? row.repo ?? row.cost_center ?? "",
|
|
1146
1309
|
started_at: row.timestamp,
|
|
1147
1310
|
ended_at: null,
|
|
1148
1311
|
total_cost_usd: 0,
|
|
1149
1312
|
total_tokens: 0,
|
|
1150
1313
|
request_count: 0,
|
|
1151
|
-
machine_id: machineId
|
|
1314
|
+
machine_id: machineId,
|
|
1315
|
+
cost_center_id: costCenterId2 ?? null,
|
|
1316
|
+
attribution_tag: row.attribution_tag ?? "otel",
|
|
1317
|
+
account_key: row.account_key ?? "",
|
|
1318
|
+
account_tool: row.account_tool ?? "",
|
|
1319
|
+
account_name: row.account_name ?? "",
|
|
1320
|
+
account_email: row.account_email ?? "",
|
|
1321
|
+
account_source: row.account_source ?? ""
|
|
1152
1322
|
});
|
|
1153
1323
|
sessions.add(row.session_id);
|
|
1154
1324
|
}
|