@agent-finops/core 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/activitySnapshot.d.ts +676 -0
- package/dist/activitySnapshot.js +1220 -0
- package/dist/activitySnapshotCache.d.ts +54 -0
- package/dist/activitySnapshotCache.js +489 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/localAgentLogs.d.ts +30 -0
- package/dist/localAgentLogs.js +805 -56
- package/dist/providerConnectors.d.ts +10 -0
- package/dist/providerConnectors.js +43 -6
- package/package.json +1 -1
|
@@ -20,6 +20,15 @@ export type ProviderQaPagination = {
|
|
|
20
20
|
note?: string;
|
|
21
21
|
};
|
|
22
22
|
export type ProviderCoverageStatus = "complete" | "partial";
|
|
23
|
+
/**
|
|
24
|
+
* Exact provider interval requested by a sync. It is intentionally absent
|
|
25
|
+
* when the caller leaves the end open: a successful narrow/open-ended read
|
|
26
|
+
* must never be promoted into an assumed 30-day coverage claim.
|
|
27
|
+
*/
|
|
28
|
+
export type ProviderCoverageInterval = {
|
|
29
|
+
coverageStart: string;
|
|
30
|
+
coverageEnd: string;
|
|
31
|
+
};
|
|
23
32
|
export type ProviderFinancialSummary = {
|
|
24
33
|
providerReportedBilledUsd: number | null;
|
|
25
34
|
apiEquivalentEstimatedUsd: number | null;
|
|
@@ -65,6 +74,7 @@ export type ProviderConnectorResult = {
|
|
|
65
74
|
records: UsageRecord[];
|
|
66
75
|
fetchedAt: string;
|
|
67
76
|
coverage: ProviderCoverageStatus;
|
|
77
|
+
coverageInterval?: ProviderCoverageInterval;
|
|
68
78
|
financials: ProviderFinancialSummary;
|
|
69
79
|
completeness: "verified" | "estimated" | "detected_unverified" | "missing";
|
|
70
80
|
qa: ProviderQaSummary;
|
|
@@ -314,6 +314,12 @@ export function normalizeCursorSpendResponse(response, options) {
|
|
|
314
314
|
});
|
|
315
315
|
}
|
|
316
316
|
export async function fetchProviderUsageRecords(input) {
|
|
317
|
+
// Validate explicit bounds before resolving a credential or making a request.
|
|
318
|
+
// The interval-aware OpenAI/Anthropic result paths call the same pure helper
|
|
319
|
+
// to attach normalized bounds only after a successful fetch. Copilot and
|
|
320
|
+
// Cursor do not currently constrain their reads to these requested bounds,
|
|
321
|
+
// so they deliberately return no coverage interval.
|
|
322
|
+
requestedCoverageInterval(input);
|
|
317
323
|
const token = (input.tokenResolver ?? defaultTokenResolver)(input.authReference);
|
|
318
324
|
const fetcher = input.fetcher ?? defaultFetcher;
|
|
319
325
|
const sourceId = input.sourceId ?? `${input.provider}-provider-api`;
|
|
@@ -400,7 +406,7 @@ async function fetchOpenAi(input, token, fetcher, sourceId) {
|
|
|
400
406
|
...costFetch.pages.flatMap((page) => normalizeOpenAiCostResponse(page, { sourceId, observedFrom: "OpenAI organization costs API" })),
|
|
401
407
|
...usageFetch.pages.flatMap((page) => normalizeOpenAiUsageResponse(page, { sourceId, observedFrom: "OpenAI organization usage API" }))
|
|
402
408
|
];
|
|
403
|
-
return providerResult("openai", sourceId, input.authReference, records, qaSummary("openai", [costFetch, usageFetch]));
|
|
409
|
+
return providerResult("openai", sourceId, input.authReference, records, qaSummary("openai", [costFetch, usageFetch]), requestedCoverageInterval(input));
|
|
404
410
|
}
|
|
405
411
|
async function fetchAnthropic(input, token, fetcher, sourceId) {
|
|
406
412
|
const costRequest = {
|
|
@@ -417,7 +423,7 @@ async function fetchAnthropic(input, token, fetcher, sourceId) {
|
|
|
417
423
|
...costFetch.pages.flatMap((page) => normalizeAnthropicCostResponse(page, { sourceId, observedFrom: "Anthropic Admin Cost Report" })),
|
|
418
424
|
...claudeCodeFetches.flatMap((fetchResult) => fetchResult.pages.flatMap((page) => normalizeAnthropicClaudeCodeUsageResponse(page, { sourceId, observedFrom: "Anthropic Claude Code Usage Report", accountId: input.accountId })))
|
|
419
425
|
];
|
|
420
|
-
return providerResult("anthropic", sourceId, input.authReference, records, qaSummary("anthropic", [costFetch, ...claudeCodeFetches]));
|
|
426
|
+
return providerResult("anthropic", sourceId, input.authReference, records, qaSummary("anthropic", [costFetch, ...claudeCodeFetches]), requestedCoverageInterval(input));
|
|
421
427
|
}
|
|
422
428
|
async function fetchGitHubCopilot(input, token, fetcher, sourceId) {
|
|
423
429
|
const accountId = input.org ?? input.enterprise;
|
|
@@ -1257,7 +1263,7 @@ function sumAmounts(records) {
|
|
|
1257
1263
|
.filter((amount) => typeof amount === "number");
|
|
1258
1264
|
return amounts.length > 0 ? amounts.reduce((sum, amount) => sum + amount, 0) : null;
|
|
1259
1265
|
}
|
|
1260
|
-
function providerResult(provider, sourceId, authReference, records, qa) {
|
|
1266
|
+
function providerResult(provider, sourceId, authReference, records, qa, coverageInterval) {
|
|
1261
1267
|
const resolvedQa = qa ?? qaSummary(provider, []);
|
|
1262
1268
|
const coverage = resolvedQa.coverage
|
|
1263
1269
|
?? (resolvedQa.pagination.every((pagination) => pagination.stoppedBecause === "complete") ? "complete" : "partial");
|
|
@@ -1276,11 +1282,42 @@ function providerResult(provider, sourceId, authReference, records, qa) {
|
|
|
1276
1282
|
records,
|
|
1277
1283
|
fetchedAt: new Date().toISOString(),
|
|
1278
1284
|
coverage,
|
|
1285
|
+
...(coverageInterval ? { coverageInterval } : {}),
|
|
1279
1286
|
financials,
|
|
1280
1287
|
completeness,
|
|
1281
1288
|
qa: resolvedQa
|
|
1282
1289
|
};
|
|
1283
1290
|
}
|
|
1291
|
+
function requestedCoverageInterval(input) {
|
|
1292
|
+
if (!Number.isFinite(input.startTime) || !Number.isInteger(input.startTime) || input.startTime < 0) {
|
|
1293
|
+
throw new Error("Provider coverage startTime requires a non-negative whole-second timestamp.");
|
|
1294
|
+
}
|
|
1295
|
+
const coverageStart = new Date(input.startTime * 1_000);
|
|
1296
|
+
if (Number.isNaN(coverageStart.getTime())) {
|
|
1297
|
+
throw new Error("Provider coverage interval falls outside the supported timestamp range.");
|
|
1298
|
+
}
|
|
1299
|
+
if (input.endTime === undefined) {
|
|
1300
|
+
if (coverageStart.getTime() > Date.now()) {
|
|
1301
|
+
throw new Error("Provider coverage startTime cannot be in the future.");
|
|
1302
|
+
}
|
|
1303
|
+
return undefined;
|
|
1304
|
+
}
|
|
1305
|
+
if (!Number.isFinite(input.endTime) || !Number.isInteger(input.endTime) ||
|
|
1306
|
+
input.endTime < input.startTime) {
|
|
1307
|
+
throw new Error("Provider coverage interval requires non-negative whole-second bounds with endTime at or after startTime.");
|
|
1308
|
+
}
|
|
1309
|
+
const coverageEnd = new Date(input.endTime * 1_000);
|
|
1310
|
+
if (Number.isNaN(coverageEnd.getTime())) {
|
|
1311
|
+
throw new Error("Provider coverage interval falls outside the supported timestamp range.");
|
|
1312
|
+
}
|
|
1313
|
+
if (coverageEnd.getTime() > Date.now()) {
|
|
1314
|
+
throw new Error("Provider coverage endTime cannot be in the future.");
|
|
1315
|
+
}
|
|
1316
|
+
return {
|
|
1317
|
+
coverageStart: coverageStart.toISOString(),
|
|
1318
|
+
coverageEnd: coverageEnd.toISOString()
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1284
1321
|
export function createProviderConnection(input) {
|
|
1285
1322
|
const source = createProviderConnectorStub(input.provider, "provider_api", input.fetchedAt);
|
|
1286
1323
|
const total = input.totalUsd === null ? "an unavailable financial headline" : formatProviderUsd(input.totalUsd);
|
|
@@ -1328,7 +1365,7 @@ function buildOpenAiCostsUrl(startTime, endTime) {
|
|
|
1328
1365
|
url.searchParams.append("group_by", "project_id");
|
|
1329
1366
|
url.searchParams.append("group_by", "line_item");
|
|
1330
1367
|
url.searchParams.append("group_by", "api_key_id");
|
|
1331
|
-
if (endTime)
|
|
1368
|
+
if (endTime !== undefined)
|
|
1332
1369
|
url.searchParams.set("end_time", String(endTime));
|
|
1333
1370
|
return url.toString();
|
|
1334
1371
|
}
|
|
@@ -1341,14 +1378,14 @@ function buildOpenAiUsageUrl(startTime, endTime) {
|
|
|
1341
1378
|
url.searchParams.append("group_by", "user_id");
|
|
1342
1379
|
url.searchParams.append("group_by", "api_key_id");
|
|
1343
1380
|
url.searchParams.append("group_by", "model");
|
|
1344
|
-
if (endTime)
|
|
1381
|
+
if (endTime !== undefined)
|
|
1345
1382
|
url.searchParams.set("end_time", String(endTime));
|
|
1346
1383
|
return url.toString();
|
|
1347
1384
|
}
|
|
1348
1385
|
function buildAnthropicCostUrl(startTime, endTime) {
|
|
1349
1386
|
const url = new URL("https://api.anthropic.com/v1/organizations/cost_report");
|
|
1350
1387
|
url.searchParams.set("starting_at", new Date(startTime * 1000).toISOString());
|
|
1351
|
-
if (endTime)
|
|
1388
|
+
if (endTime !== undefined)
|
|
1352
1389
|
url.searchParams.set("ending_at", new Date(endTime * 1000).toISOString());
|
|
1353
1390
|
url.searchParams.set("bucket_width", "1d");
|
|
1354
1391
|
url.searchParams.append("group_by[]", "workspace_id");
|