@agent-finops/core 0.8.0 → 0.8.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 +49 -49
- package/dist/agentEconomicsReceipt.d.ts +44 -44
- package/dist/analyze.js +3 -1
- package/dist/cutList.js +1 -1
- package/dist/glance.d.ts +3 -1
- package/dist/glance.js +114 -72
- package/dist/insights.js +3 -1
- package/dist/modelPricing.d.ts +1 -1
- package/dist/modelPricing.js +4 -1
- package/dist/planMath.js +12 -7
- package/dist/providerConnectors.js +17 -1
- package/dist/sourceRegistry.js +90 -52
- package/package.json +1 -1
package/dist/glance.js
CHANGED
|
@@ -45,7 +45,7 @@ export function buildUsageGlance(calls, options = {}) {
|
|
|
45
45
|
: null;
|
|
46
46
|
const limitCalls = sanitizeStringMetadata(options.limitCalls ?? safeCalls)
|
|
47
47
|
.filter((call) => localAgentFormatSupports(call.agent, "rateLimits"));
|
|
48
|
-
const limits = latestLimits(limitCalls, now).map(({ agent, window, observedAt }) => toGlanceLimit(agent, window, observedAt));
|
|
48
|
+
const limits = latestLimits(limitCalls, now).map(({ agent, window, observedAt }) => toGlanceLimit(agent, window, observedAt, now));
|
|
49
49
|
const windowStart = now.getTime() - focusWindowDays * DAY_MS;
|
|
50
50
|
const windowCalls = safeCalls.filter((call) => Date.parse(call.timestamp) >= windowStart);
|
|
51
51
|
const windowSessions = groupSessions(windowCalls);
|
|
@@ -68,7 +68,8 @@ export function buildUsageGlance(calls, options = {}) {
|
|
|
68
68
|
});
|
|
69
69
|
const detectedAgents = (options.detectedAgents ?? uniqueAgents(safeCalls))
|
|
70
70
|
.filter((agent) => localAgentFormatSupports(agent, "glance"));
|
|
71
|
-
const
|
|
71
|
+
const agentsWithCurrentLimits = new Set(limits.filter((limit) => limit.freshness === "current").map((limit) => limit.agent));
|
|
72
|
+
const agentsWithStaleLimits = new Set(limits.filter((limit) => limit.freshness === "stale").map((limit) => limit.agent));
|
|
72
73
|
const limitAgents = uniqueAgents(limitCalls.filter((call) => call.rateLimits));
|
|
73
74
|
const reportedWindows = (agent) => ([...new Set(limits
|
|
74
75
|
.filter((limit) => limit.agent === agent)
|
|
@@ -77,6 +78,7 @@ export function buildUsageGlance(calls, options = {}) {
|
|
|
77
78
|
"Session value is an API-equivalent estimate from transcript token counts, not an invoice or subscription charge.",
|
|
78
79
|
"A detected monthly subscription changes the interpretation, not the token math: the API-equivalent amount is usage priced at list rates, not incremental spend or business outcome value.",
|
|
79
80
|
"Exhaustion time is a pace projection; remaining percentage and reset time are provider-reported only when embedded in a transcript.",
|
|
81
|
+
"A five-hour percentage is current for at most five hours after observation; a weekly percentage is current for at most 24 hours. Older transcript evidence is labeled stale and never drives an action.",
|
|
80
82
|
"Main focus is a local summary of observed human prompts and tool activity, not elapsed time or spend; raw prompts are not returned.",
|
|
81
83
|
"The primary action combines Context Health, Main focus, and reported runway locally. It only provides a copyable handoff prompt and never runs an agent automatically.",
|
|
82
84
|
"Claude Code transcripts do not report plan headroom. Missing limits remain unavailable instead of being inferred.",
|
|
@@ -92,7 +94,9 @@ export function buildUsageGlance(calls, options = {}) {
|
|
|
92
94
|
rateLimitMetadata: supportedFormats.map((descriptor) => ({
|
|
93
95
|
agent: descriptor.id,
|
|
94
96
|
status: descriptor.capabilities.rateLimits
|
|
95
|
-
?
|
|
97
|
+
? agentsWithCurrentLimits.has(descriptor.id)
|
|
98
|
+
? "reported"
|
|
99
|
+
: agentsWithStaleLimits.has(descriptor.id) ? "stale" : "not_seen"
|
|
96
100
|
: "not_reported_by_transcript",
|
|
97
101
|
windowsReported: reportedWindows(descriptor.id)
|
|
98
102
|
})),
|
|
@@ -267,8 +271,11 @@ function latestLimits(calls, now) {
|
|
|
267
271
|
return [...byWindow.values()].sort((left, right) => (order[left.window.kind] - order[right.window.kind] ||
|
|
268
272
|
left.agent.localeCompare(right.agent)));
|
|
269
273
|
}
|
|
270
|
-
function toGlanceLimit(agent, window, observedAt) {
|
|
271
|
-
const
|
|
274
|
+
function toGlanceLimit(agent, window, observedAt, now) {
|
|
275
|
+
const freshness = limitEvidenceFreshness(window, observedAt, now);
|
|
276
|
+
const projection = freshness === "current"
|
|
277
|
+
? projectExhaustion(window, observedAt, now)
|
|
278
|
+
: { at: null, beforeReset: false };
|
|
272
279
|
return {
|
|
273
280
|
agent,
|
|
274
281
|
kind: window.kind,
|
|
@@ -279,12 +286,13 @@ function toGlanceLimit(agent, window, observedAt) {
|
|
|
279
286
|
observedAt,
|
|
280
287
|
resetsAt: window.resetsAt,
|
|
281
288
|
source: "transcript_reported",
|
|
289
|
+
freshness,
|
|
282
290
|
projectedExhaustionAt: projection.at,
|
|
283
291
|
projectedToExhaustBeforeReset: projection.beforeReset,
|
|
284
292
|
projectionConfidence: "estimated"
|
|
285
293
|
};
|
|
286
294
|
}
|
|
287
|
-
function projectExhaustion(window, observedAt) {
|
|
295
|
+
function projectExhaustion(window, observedAt, now) {
|
|
288
296
|
const observedMs = Date.parse(observedAt);
|
|
289
297
|
const resetMs = Date.parse(window.resetsAt);
|
|
290
298
|
const windowStartMs = resetMs - window.windowMinutes * 60_000;
|
|
@@ -296,15 +304,27 @@ function projectExhaustion(window, observedAt) {
|
|
|
296
304
|
window.usedPercent <= 0) {
|
|
297
305
|
return { at: null, beforeReset: false };
|
|
298
306
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
307
|
+
// A current report at the limit is an observed exhausted state, not a
|
|
308
|
+
// forecast. Keep the projection empty so downstream copy cannot describe an
|
|
309
|
+
// already-exhausted window as something that merely "may" exhaust.
|
|
310
|
+
if (window.usedPercent >= 100)
|
|
311
|
+
return { at: null, beforeReset: false };
|
|
302
312
|
const remainingMs = elapsedMs * ((100 - window.usedPercent) / window.usedPercent);
|
|
303
313
|
const exhaustionMs = observedMs + remainingMs;
|
|
304
|
-
return exhaustionMs < resetMs
|
|
314
|
+
return exhaustionMs > now.getTime() && exhaustionMs < resetMs
|
|
305
315
|
? { at: new Date(exhaustionMs).toISOString(), beforeReset: true }
|
|
306
316
|
: { at: null, beforeReset: false };
|
|
307
317
|
}
|
|
318
|
+
function limitEvidenceFreshness(window, observedAt, now) {
|
|
319
|
+
const observedMs = Date.parse(observedAt);
|
|
320
|
+
const ageMs = now.getTime() - observedMs;
|
|
321
|
+
const windowMs = window.windowMinutes * 60_000;
|
|
322
|
+
const maximumAgeMs = Math.min(windowMs, DAY_MS);
|
|
323
|
+
return Number.isFinite(observedMs) && Number.isFinite(windowMs) && windowMs > 0 &&
|
|
324
|
+
ageMs >= 0 && ageMs <= maximumAgeMs
|
|
325
|
+
? "current"
|
|
326
|
+
: "stale";
|
|
327
|
+
}
|
|
308
328
|
function buildMainFocus(sessions, windowDays, now) {
|
|
309
329
|
const candidates = sessions.map((session) => {
|
|
310
330
|
const rawActivity = session.activity ?? fallbackActivity(session);
|
|
@@ -449,8 +469,17 @@ function buildPrimaryAction(input) {
|
|
|
449
469
|
const project = safeActionMetadata(preferredProject, 80);
|
|
450
470
|
const focus = safeActionMetadata(input.focus?.summary, 120);
|
|
451
471
|
const focalFile = safeActionMetadata(input.focus?.file, 100);
|
|
472
|
+
const generatedAtMs = Date.parse(input.generatedAt);
|
|
473
|
+
const exhaustedLimit = input.limits
|
|
474
|
+
.filter((limit) => (limit.freshness === "current" &&
|
|
475
|
+
limit.usedPercent >= 100 &&
|
|
476
|
+
Date.parse(limit.resetsAt) > generatedAtMs))
|
|
477
|
+
.sort((left, right) => Date.parse(left.resetsAt) - Date.parse(right.resetsAt))[0];
|
|
452
478
|
const urgentLimit = input.limits
|
|
453
|
-
.filter((limit) => limit.
|
|
479
|
+
.filter((limit) => (limit.freshness === "current" &&
|
|
480
|
+
limit.projectedToExhaustBeforeReset &&
|
|
481
|
+
limit.projectedExhaustionAt !== null &&
|
|
482
|
+
Date.parse(limit.projectedExhaustionAt) > Date.parse(input.generatedAt)))
|
|
454
483
|
.sort((left, right) => left.remainingPercent - right.remainingPercent)[0];
|
|
455
484
|
const projectSuffix = project ? ` · ${project}` : "";
|
|
456
485
|
let intent;
|
|
@@ -458,68 +487,81 @@ function buildPrimaryAction(input) {
|
|
|
458
487
|
let detail;
|
|
459
488
|
let instruction;
|
|
460
489
|
let confidence = input.sessionHealth.confidence;
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
490
|
+
if (exhaustedLimit) {
|
|
491
|
+
intent = "protect_runway";
|
|
492
|
+
label = `Checkpoint${projectSuffix}`;
|
|
493
|
+
detail = `${limitActionName(exhaustedLimit)} is exhausted until reset`;
|
|
494
|
+
instruction = "Create a concise checkpoint for the observed focus and wait for the provider-reported reset before resuming work that requires this plan window.";
|
|
495
|
+
confidence = "high";
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
switch (input.sessionHealth.recommendation) {
|
|
499
|
+
case "start_fresh":
|
|
500
|
+
intent = "start_fresh";
|
|
501
|
+
label = `Start fresh${projectSuffix}`;
|
|
502
|
+
detail = focus
|
|
503
|
+
? `Carry “${focus}” into a clean session`
|
|
504
|
+
: "Carry only the concrete state you still need";
|
|
505
|
+
instruction = "Start a clean session and continue the observed focus after verifying the current repository state.";
|
|
506
|
+
break;
|
|
507
|
+
case "review_hooks":
|
|
508
|
+
intent = "review_context";
|
|
509
|
+
label = `Review context${projectSuffix}`;
|
|
510
|
+
detail = focus
|
|
511
|
+
? `Protect “${focus}” from unnecessary hook context`
|
|
512
|
+
: "Inspect configured hooks before removing anything";
|
|
513
|
+
instruction = "Review installed hook sources that affect this work. Do not remove or edit configuration without explicit user approval.";
|
|
514
|
+
break;
|
|
515
|
+
case "trim_dead_context":
|
|
516
|
+
intent = "trim_context";
|
|
517
|
+
label = `Trim context${projectSuffix}`;
|
|
518
|
+
detail = focus
|
|
519
|
+
? `Keep only context useful to “${focus}”`
|
|
520
|
+
: "Inspect unused loaded context before changing it";
|
|
521
|
+
instruction = "Identify loaded context that is unrelated to the observed focus. Recommend scoped changes, but do not remove anything without explicit user approval.";
|
|
522
|
+
break;
|
|
523
|
+
default:
|
|
524
|
+
if (urgentLimit) {
|
|
525
|
+
intent = "protect_runway";
|
|
526
|
+
label = `Checkpoint${projectSuffix}`;
|
|
527
|
+
detail = `${limitActionName(urgentLimit)} may exhaust before reset`;
|
|
528
|
+
instruction = "Create a concise checkpoint for the observed focus and prioritize the smallest verifiable next step before the reported plan window may be exhausted.";
|
|
529
|
+
confidence = "medium";
|
|
530
|
+
}
|
|
531
|
+
else if (focus &&
|
|
532
|
+
input.focus?.confidence !== "low" &&
|
|
533
|
+
input.currentSession?.status === "active") {
|
|
534
|
+
intent = "continue_focus";
|
|
535
|
+
label = `Continue${projectSuffix}`;
|
|
536
|
+
detail = focus;
|
|
537
|
+
instruction = "Continue the observed focus with the smallest verifiable next step.";
|
|
538
|
+
confidence = input.focus?.confidence ?? input.sessionHealth.confidence;
|
|
539
|
+
}
|
|
540
|
+
else if (focus && input.focus?.confidence !== "low") {
|
|
541
|
+
intent = "resume_focus";
|
|
542
|
+
label = `Resume${projectSuffix}`;
|
|
543
|
+
detail = focus;
|
|
544
|
+
instruction = "Resume the observed focus after checking what changed since the last local activity.";
|
|
545
|
+
confidence = input.focus?.confidence ?? input.sessionHealth.confidence;
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
intent = "inspect_current_work";
|
|
549
|
+
label = `Inspect current work${projectSuffix}`;
|
|
550
|
+
detail = "Verify the active task before making changes";
|
|
551
|
+
instruction = "Inspect the current repository and ask for the intended task if it cannot be established from local evidence.";
|
|
552
|
+
confidence = "low";
|
|
553
|
+
}
|
|
554
|
+
}
|
|
517
555
|
}
|
|
518
|
-
const runway =
|
|
519
|
-
? `${limitActionName(
|
|
520
|
-
:
|
|
521
|
-
?
|
|
522
|
-
:
|
|
556
|
+
const runway = exhaustedLimit
|
|
557
|
+
? `${limitActionName(exhaustedLimit)}: exhausted (0% remaining); provider-reported reset=${exhaustedLimit.resetsAt}; observed=${exhaustedLimit.observedAt}.`
|
|
558
|
+
: urgentLimit
|
|
559
|
+
? `${limitActionName(urgentLimit)}: ${roundPercent(urgentLimit.remainingPercent)}% remaining; locally projected exhaustion=${urgentLimit.projectedExhaustionAt ?? "unavailable"}; provider-reported reset=${urgentLimit.resetsAt}.`
|
|
560
|
+
: input.limits.some((limit) => limit.freshness === "current")
|
|
561
|
+
? "No transcript-reported plan window is currently projected to exhaust before reset."
|
|
562
|
+
: input.limits.some((limit) => limit.freshness === "stale")
|
|
563
|
+
? "Stale; the last transcript-reported plan window is too old to use as current runway. Refresh the agent limit before acting."
|
|
564
|
+
: "Not available; no plan window was reported in the local transcript.";
|
|
523
565
|
const reportedTotalEvidence = input.currentSession?.reportedTotalTokens === undefined
|
|
524
566
|
? ""
|
|
525
567
|
: `; provider-reported total tokens=${input.currentSession.reportedTotalTokens.toLocaleString("en-US")}; input/output breakdown unavailable`;
|
package/dist/insights.js
CHANGED
|
@@ -178,6 +178,8 @@ function combinedConfidence(confidences) {
|
|
|
178
178
|
return confidences.reduce((lowest, current) => confidenceRank[current] > confidenceRank[lowest] ? current : lowest);
|
|
179
179
|
}
|
|
180
180
|
function formatUsd(value) {
|
|
181
|
+
if (value > 0 && value < 0.01)
|
|
182
|
+
return "<$0.01";
|
|
181
183
|
return `$${value.toFixed(2)}`;
|
|
182
184
|
}
|
|
183
185
|
function formatMultiplier(value) {
|
|
@@ -201,6 +203,6 @@ function stableSuffix(value) {
|
|
|
201
203
|
return (hash >>> 0).toString(36);
|
|
202
204
|
}
|
|
203
205
|
function roundMoney(value) {
|
|
204
|
-
return Math.round(value *
|
|
206
|
+
return Math.round(value * 10_000) / 10_000;
|
|
205
207
|
}
|
|
206
208
|
//# sourceMappingURL=insights.js.map
|
package/dist/modelPricing.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* matched top-down; first match wins. Unknown models return undefined so
|
|
8
8
|
* callers can label the record "missing" instead of inventing a number.
|
|
9
9
|
*/
|
|
10
|
-
export declare const PRICING_TABLE_AS_OF = "2026-08-
|
|
10
|
+
export declare const PRICING_TABLE_AS_OF = "2026-08-14";
|
|
11
11
|
export type TokenUsage = {
|
|
12
12
|
/** Billable, uncached input tokens. */
|
|
13
13
|
inputTokens: number;
|
package/dist/modelPricing.js
CHANGED
|
@@ -7,10 +7,13 @@
|
|
|
7
7
|
* matched top-down; first match wins. Unknown models return undefined so
|
|
8
8
|
* callers can label the record "missing" instead of inventing a number.
|
|
9
9
|
*/
|
|
10
|
-
export const PRICING_TABLE_AS_OF = "2026-08-
|
|
10
|
+
export const PRICING_TABLE_AS_OF = "2026-08-14";
|
|
11
11
|
const pricingRules = [
|
|
12
12
|
// Anthropic
|
|
13
13
|
{ match: /^claude-fable-5/i, inputPerM: 10, outputPerM: 50 },
|
|
14
|
+
{ match: /^claude-mythos-5/i, inputPerM: 10, outputPerM: 50 },
|
|
15
|
+
{ match: /^claude-opus-5/i, inputPerM: 5, outputPerM: 25 },
|
|
16
|
+
{ match: /^claude-sonnet-5/i, inputPerM: 2, outputPerM: 10 },
|
|
14
17
|
{ match: /^claude-opus-4-[5-9]/i, inputPerM: 5, outputPerM: 25 },
|
|
15
18
|
{ match: /^claude-opus-4(-[01])?$/i, inputPerM: 15, outputPerM: 75 },
|
|
16
19
|
{ match: /^claude-sonnet-4/i, inputPerM: 3, outputPerM: 15 },
|
package/dist/planMath.js
CHANGED
|
@@ -54,9 +54,9 @@ export function computePlanChecks(records, detectedPlans = []) {
|
|
|
54
54
|
const savingsVsApi = roundMoney(monthly - detectedKnown.monthlyUsd);
|
|
55
55
|
effectiveSavings = savingsVsApi > 0 ? savingsVsApi : undefined;
|
|
56
56
|
headline =
|
|
57
|
-
`${agent}:
|
|
57
|
+
`${agent}: ~${formatUsd(monthly)}/mo at API rates (${basis}) — compared with ${detectedKnown.name} ` +
|
|
58
58
|
`($${detectedKnown.monthlyUsd}/mo; label detected locally): ~${valueMultiple}× the plan price in API-equivalent usage` +
|
|
59
|
-
(effectiveSavings ? `, a
|
|
59
|
+
(effectiveSavings ? `, a ~${formatUsd(effectiveSavings)}/mo value difference to investigate.` : `.`);
|
|
60
60
|
if (monthly > detectedKnown.coversUpToUsd) {
|
|
61
61
|
const nextTier = subscriptionPlans.find((plan) => plan.agent === agent && plan.coversUpToUsd > detectedKnown.coversUpToUsd);
|
|
62
62
|
// A local limit signal upgrades "might hit limits" to hard evidence.
|
|
@@ -75,7 +75,7 @@ export function computePlanChecks(records, detectedPlans = []) {
|
|
|
75
75
|
// Detected a plan we can't price (e.g. an unrecognized tier): state the
|
|
76
76
|
// fact, then fall back to suggestion math without pretending certainty.
|
|
77
77
|
headline =
|
|
78
|
-
`${agent}:
|
|
78
|
+
`${agent}: ~${formatUsd(monthly)}/mo at API rates (${basis}) — compared with ${detected.planLabel} ` +
|
|
79
79
|
`(label detected locally; price not in our table)` +
|
|
80
80
|
(suggested ? `; reference listed plan: ${suggested.name} ($${suggested.monthlyUsd}/mo).` : `.`);
|
|
81
81
|
}
|
|
@@ -84,13 +84,13 @@ export function computePlanChecks(records, detectedPlans = []) {
|
|
|
84
84
|
valueMultiple = covered ? Math.round((monthly / suggested.monthlyUsd) * 10) / 10 : undefined;
|
|
85
85
|
effectiveSavings = covered ? savings : undefined;
|
|
86
86
|
if (!suggested) {
|
|
87
|
-
headline = `${agent}:
|
|
87
|
+
headline = `${agent}: ~${formatUsd(monthly)}/mo at API rates (${basis}).`;
|
|
88
88
|
}
|
|
89
89
|
else if (covered) {
|
|
90
|
-
headline = `${agent}:
|
|
90
|
+
headline = `${agent}: ~${formatUsd(monthly)}/mo at API rates (${basis}) — ${suggested.name} is a $${suggested.monthlyUsd}/mo reference point. That is ~${valueMultiple}× the plan price in API-equivalent usage, a ~${formatUsd(savings)}/mo value difference to investigate; it does not prove plan coverage.`;
|
|
91
91
|
}
|
|
92
92
|
else {
|
|
93
|
-
headline = `${agent}:
|
|
93
|
+
headline = `${agent}: ~${formatUsd(monthly)}/mo at API rates (${basis}) — below the $${suggested.monthlyUsd}/mo price of ${suggested.name}; compare account benefits and provider-reported charges before changing plans.`;
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
checks.push({
|
|
@@ -108,6 +108,11 @@ export function computePlanChecks(records, detectedPlans = []) {
|
|
|
108
108
|
return checks.sort((left, right) => right.apiEquivalentMonthlyUsd - left.apiEquivalentMonthlyUsd);
|
|
109
109
|
}
|
|
110
110
|
function roundMoney(value) {
|
|
111
|
-
return Math.round(value *
|
|
111
|
+
return Math.round(value * 10_000) / 10_000;
|
|
112
|
+
}
|
|
113
|
+
function formatUsd(value) {
|
|
114
|
+
if (value > 0 && value < 0.01)
|
|
115
|
+
return "<$0.01";
|
|
116
|
+
return `$${value.toFixed(2)}`;
|
|
112
117
|
}
|
|
113
118
|
//# sourceMappingURL=planMath.js.map
|
|
@@ -1591,16 +1591,32 @@ export function createProviderConnection(input) {
|
|
|
1591
1591
|
const source = createProviderConnectorStub(input.provider, "provider_api", input.fetchedAt);
|
|
1592
1592
|
const total = input.totalUsd === null ? "an unavailable financial headline" : formatProviderUsd(input.totalUsd);
|
|
1593
1593
|
const financialEvidence = input.completeness ?? "verified";
|
|
1594
|
+
const fulfilledBySuccessfulSync = new Set(providerConnectionPrerequisiteFields[input.provider] ?? [
|
|
1595
|
+
"approved account/API/export source"
|
|
1596
|
+
]);
|
|
1597
|
+
const fieldsMissing = source.fieldsMissing.filter((field) => !fulfilledBySuccessfulSync.has(field));
|
|
1598
|
+
if (financialEvidence === "missing" || input.totalUsd === null) {
|
|
1599
|
+
fieldsMissing.push("provider financial headline");
|
|
1600
|
+
}
|
|
1594
1601
|
return {
|
|
1595
1602
|
...source,
|
|
1596
1603
|
id: input.sourceId ?? source.id,
|
|
1597
1604
|
validationCoverage: validationCoverageForCompletedProviderSync(input.provider),
|
|
1598
1605
|
financialEvidence,
|
|
1599
1606
|
authReference: input.authReference,
|
|
1600
|
-
|
|
1607
|
+
// A successful request satisfies credentials/setup, not permanent product
|
|
1608
|
+
// coverage gaps such as invoice settlement or unsupported usage families.
|
|
1609
|
+
fieldsMissing: Array.from(new Set(fieldsMissing)),
|
|
1601
1610
|
scope: `${source.scope} Last successful pull produced ${input.verifiedRecordCount} record(s); financial evidence: ${financialEvidence}; financial headline: ${total}.`
|
|
1602
1611
|
};
|
|
1603
1612
|
}
|
|
1613
|
+
const providerConnectionPrerequisiteFields = {
|
|
1614
|
+
openai: ["OpenAI Admin API key reference"],
|
|
1615
|
+
anthropic: ["Anthropic Admin API key reference"],
|
|
1616
|
+
cursor: ["Cursor team Admin API key reference"],
|
|
1617
|
+
"github-copilot": ["GitHub admin token reference and organization or enterprise slug"],
|
|
1618
|
+
codex: ["OpenAI Admin API key reference"]
|
|
1619
|
+
};
|
|
1604
1620
|
function validationCoverageForCompletedProviderSync(provider) {
|
|
1605
1621
|
if (provider === "openai" || provider === "anthropic")
|
|
1606
1622
|
return "live_verified";
|