@agent-finops/core 0.9.5 → 0.9.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/README.md +3 -3
- package/dist/actionPlanner.js +4 -1
- package/dist/activitySnapshot.js +12 -3
- package/dist/analyze.js +59 -18
- package/dist/contextHealth.js +15 -4
- package/dist/cutList.d.ts +23 -0
- package/dist/cutList.js +268 -11
- package/dist/deadContext.js +10 -3
- package/dist/glance.js +10 -6
- package/dist/insights.js +41 -23
- package/dist/localAgentLogs.d.ts +19 -3
- package/dist/localAgentLogs.js +112 -18
- package/dist/modelPricing.d.ts +17 -4
- package/dist/modelPricing.js +114 -22
- package/dist/planMath.js +25 -4
- package/dist/projectIndexStore.d.ts +6 -4
- package/dist/qualitativeIndexCache.d.ts +4 -2
- package/dist/qualitativeIndexCache.js +5 -0
- package/dist/toolInvocations.js +9 -1
- package/dist/untrustedLabel.d.ts +70 -0
- package/dist/untrustedLabel.js +161 -0
- package/package.json +1 -1
package/dist/deadContext.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { loadAgentInventory } from "./agentInventory.js";
|
|
2
|
+
import { safeUntrustedLabel, WITHHELD_ENTITY_LABEL, WITHHELD_FILE_LABEL } from "./untrustedLabel.js";
|
|
2
3
|
import { findPricingRule } from "./modelPricing.js";
|
|
3
4
|
import { loadToolInvocations } from "./toolInvocations.js";
|
|
4
5
|
/**
|
|
@@ -97,15 +98,21 @@ export function computeDeadContext(items, invocations, config) {
|
|
|
97
98
|
}
|
|
98
99
|
dead.push({
|
|
99
100
|
kind: item.kind,
|
|
100
|
-
|
|
101
|
+
// Skill, subagent, slash-command, hook and MCP SERVER names, read off
|
|
102
|
+
// disk. They are printed by name on the readout and in the artifact.
|
|
103
|
+
name: safeUntrustedLabel(item.name, WITHHELD_ENTITY_LABEL),
|
|
101
104
|
scope: item.scope,
|
|
102
105
|
activation: item.activation,
|
|
106
|
+
// The STRUCTURED siblings travel with the name to every surface the name
|
|
107
|
+
// does, including the Apply artifact. Neutralizing the name and leaving
|
|
108
|
+
// the path beside it raw is the same inversion Blocker A was.
|
|
109
|
+
// `host` is the InventoryHost enum, not free text — bounded by the type.
|
|
103
110
|
host: item.host,
|
|
104
111
|
invocationTracking: item.invocationTracking,
|
|
105
112
|
alwaysLoadedTokens: item.alwaysLoadedTokens,
|
|
106
113
|
weightConfidence: item.weightConfidence,
|
|
107
|
-
path: item.path,
|
|
108
|
-
ownerDirs: item.ownerDirs
|
|
114
|
+
path: item.path === undefined ? undefined : safeUntrustedLabel(item.path, WITHHELD_FILE_LABEL),
|
|
115
|
+
ownerDirs: item.ownerDirs?.map((dir) => safeUntrustedLabel(dir, WITHHELD_FILE_LABEL))
|
|
109
116
|
});
|
|
110
117
|
}
|
|
111
118
|
dead.sort((a, b) => b.alwaysLoadedTokens - a.alwaysLoadedTokens);
|
package/dist/glance.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { dedupeCumulativeSessionCalls, sanitizeLocalActivityText } from "./localAgentLogs.js";
|
|
2
|
-
import { canPriceTokenUsageAtScope,
|
|
2
|
+
import { canPriceTokenUsageAtScope, estimateTokenCostsUsd, PRICING_TABLE_AS_OF } from "./modelPricing.js";
|
|
3
3
|
import { subscriptionPlans } from "./planMath.js";
|
|
4
4
|
import { buildContextHealth } from "./contextHealth.js";
|
|
5
5
|
import { localAgentFormatDescriptors, localAgentFormatSupports } from "./localAgentFormats/registry.js";
|
|
@@ -106,7 +106,7 @@ export function buildUsageGlance(calls, options = {}) {
|
|
|
106
106
|
"Claude Code transcripts do not report plan headroom. Missing limits remain unavailable instead of being inferred.",
|
|
107
107
|
"Cursor and GitHub Copilot require their provider connections because their local chat stores are not treated as authoritative billing transcripts.",
|
|
108
108
|
...(qualitativeComplete ? [] : [
|
|
109
|
-
"Main focus, anomaly, and context-change handoff are unavailable because
|
|
109
|
+
"Main focus, anomaly, and context-change handoff are unavailable because some session transcripts have not been read yet; no global driver was inferred from a partly-read subset."
|
|
110
110
|
])
|
|
111
111
|
];
|
|
112
112
|
return {
|
|
@@ -202,10 +202,10 @@ function buildCoverageLimitedPrimaryAction(input) {
|
|
|
202
202
|
kind: "session_handoff",
|
|
203
203
|
intent: "inspect_current_work",
|
|
204
204
|
label: project ? `Refresh evidence · ${project}` : "Refresh evidence",
|
|
205
|
-
detail: `Main focus unavailable ·
|
|
205
|
+
detail: `Main focus unavailable · session transcripts ${status === "partial" ? "only partly read" : "not available"}`,
|
|
206
206
|
...(project ? { project } : {}),
|
|
207
207
|
agentPrompt: [
|
|
208
|
-
"
|
|
208
|
+
"Some coding-agent session transcripts have not been read yet.",
|
|
209
209
|
"Do not infer a global main focus, waste cause, or context change from the selected subset.",
|
|
210
210
|
`Run \`${aibillImproveCommandV0()}\` from the exact project root to refresh the private index, then review the new evidence before editing.`
|
|
211
211
|
].join("\n"),
|
|
@@ -793,9 +793,13 @@ function limitActionName(limit) {
|
|
|
793
793
|
function callCost(call) {
|
|
794
794
|
if (call.usageSupport === "unsupported_token_shape")
|
|
795
795
|
return undefined;
|
|
796
|
-
if (!canPriceTokenUsageAtScope(call.model, call.usage, call.usageScope === "turn" ? "request" : "aggregate"))
|
|
796
|
+
if (!canPriceTokenUsageAtScope(call.model, call.usage, call.usageScope === "turn" ? "request" : "aggregate", call.maxRequestPromptTokens))
|
|
797
797
|
return undefined;
|
|
798
|
-
|
|
798
|
+
// Tier must come from the largest single request, exactly as the report's
|
|
799
|
+
// aggregation does — otherwise the statusline and the receipt disagree on
|
|
800
|
+
// the same session (a cache-heavy Codex session voided here while the
|
|
801
|
+
// receipt prices it, or priced here at 2x).
|
|
802
|
+
return estimateTokenCostsUsd(call.model, [call.usage], [call.maxRequestPromptTokens]);
|
|
799
803
|
}
|
|
800
804
|
function inputSideTokens(call) {
|
|
801
805
|
return call.usage.inputTokens +
|
package/dist/insights.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { hasCallLevelProvenance, hasPricedEvidence, spendComparisonKey, spendInsightSchema } from "./schema.js";
|
|
2
|
+
import { safeUntrustedLabel, safeUntrustedLabels, WITHHELD_ENTITY_LABEL, WITHHELD_OPERATION_LABEL } from "./untrustedLabel.js";
|
|
2
3
|
const confidenceRank = {
|
|
3
4
|
verified: 0,
|
|
4
5
|
estimated: 1,
|
|
@@ -32,7 +33,10 @@ function spikeInsights(records, summary) {
|
|
|
32
33
|
const topProject = topBreakdown(currentRecords, (record) => record.projectId);
|
|
33
34
|
const topModels = breakdown(currentRecords, (record) => record.model).slice(0, 2).map((entry) => entry.key);
|
|
34
35
|
const deltaUsd = roundMoney(anomaly.currentAmountUsd - anomaly.previousAmountUsd);
|
|
35
|
-
|
|
36
|
+
// Ownership lead is an agent/project/client id off the records — it lands
|
|
37
|
+
// mid-sentence in `summary`, so it is neutralized at the interpolation
|
|
38
|
+
// point like every other untrusted fragment.
|
|
39
|
+
const likelyOwner = safeEntity(topAgent?.key ?? topProject?.key ?? topClient?.key ?? "an unassigned owner");
|
|
36
40
|
const cohortSuffix = stableSuffix(anomaly.comparisonKey ?? "legacy");
|
|
37
41
|
const isProviderBilledCost = currentRecords.length > 0 && currentRecords.every((record) => record.usageGranularity === "billing_bucket" &&
|
|
38
42
|
record.costConfidence === "verified");
|
|
@@ -50,14 +54,14 @@ function spikeInsights(records, summary) {
|
|
|
50
54
|
{ label: `Previous cohort ${isProviderBilledCost ? "spend" : "value"}`, value: formatUsd(anomaly.previousAmountUsd) },
|
|
51
55
|
{ label: `Current cohort ${isProviderBilledCost ? "spend" : "value"}`, value: formatUsd(anomaly.currentAmountUsd) },
|
|
52
56
|
{ label: `${evidenceLabel} increase`, value: formatUsd(deltaUsd), detail: `${formatMultiplier(anomaly.multiplier)} day-over-day multiplier` },
|
|
53
|
-
topAgent ? { label: "Ownership lead", value: topAgent.key, detail: `${formatUsd(topAgent.amountUsd)} across ${topAgent.recordCount} cohort records` } : undefined,
|
|
54
|
-
topClient ? { label: "Client concentration", value: topClient.key, detail: `${formatUsd(topClient.amountUsd)} on spike day` } : undefined,
|
|
55
|
-
topModels.length > 0 ? { label: "Dominant models", value: topModels.join(", ") } : undefined
|
|
57
|
+
topAgent ? { label: "Ownership lead", value: safeEntity(topAgent.key), detail: `${formatUsd(topAgent.amountUsd)} across ${topAgent.recordCount} cohort records` } : undefined,
|
|
58
|
+
topClient ? { label: "Client concentration", value: safeEntity(topClient.key), detail: `${formatUsd(topClient.amountUsd)} on spike day` } : undefined,
|
|
59
|
+
topModels.length > 0 ? { label: "Dominant models", value: safeUntrustedLabels(topModels).join(", ") } : undefined
|
|
56
60
|
]),
|
|
57
|
-
affectedClients: keysFrom(currentRecords, (record) => record.clientId),
|
|
58
|
-
affectedProjects: keysFrom(currentRecords, (record) => record.projectId),
|
|
59
|
-
affectedAgents: keysFrom(currentRecords, (record) => record.agentId),
|
|
60
|
-
affectedModels: keysFrom(currentRecords, (record) => record.model),
|
|
61
|
+
affectedClients: safeUntrustedLabels(keysFrom(currentRecords, (record) => record.clientId)),
|
|
62
|
+
affectedProjects: safeUntrustedLabels(keysFrom(currentRecords, (record) => record.projectId)),
|
|
63
|
+
affectedAgents: safeUntrustedLabels(keysFrom(currentRecords, (record) => record.agentId)),
|
|
64
|
+
affectedModels: safeUntrustedLabels(keysFrom(currentRecords, (record) => record.model)),
|
|
61
65
|
estimatedImpactUsd: deltaUsd,
|
|
62
66
|
confidence: anomaly.confidence,
|
|
63
67
|
recommendedAction: `Review and reconcile the provider-cohort records from ${anomaly.key}, confirm the accountable owner, and obtain run-level evidence before diagnosing behavior or changing a policy.`,
|
|
@@ -81,24 +85,27 @@ function agentCostDriverInsights(records, summary) {
|
|
|
81
85
|
const topModel = topBreakdown(agentRecords, (record) => record.model);
|
|
82
86
|
const hasRunLevelEvidence = agentRecords.length > 0 && agentRecords.every(hasCallLevelProvenance);
|
|
83
87
|
return [{
|
|
84
|
-
id
|
|
88
|
+
// The id is a STRUCTURED field beside the neutralized title, and it is
|
|
89
|
+
// rendered (`Canonical candidate ID: ...`). Slug the neutralized form, not
|
|
90
|
+
// the raw key.
|
|
91
|
+
id: `agent-spend-concentration-${slug(safeEntity(topAgent.key))}`,
|
|
85
92
|
kind: "optimization_opportunity",
|
|
86
93
|
severity: "medium",
|
|
87
|
-
title: `${topAgent.key} spend concentration needs owner and budget review`,
|
|
88
|
-
summary: `${topAgent.key} is attached to ${formatPercent(share)} of tracked spend. Concentration alone does not prove abnormal behavior or an avoidable dollar amount${hasRunLevelEvidence ? "." : "; the evidence is aggregate rather than run-level."}`,
|
|
94
|
+
title: `${safeEntity(topAgent.key)} spend concentration needs owner and budget review`,
|
|
95
|
+
summary: `${safeEntity(topAgent.key)} is attached to ${formatPercent(share)} of tracked spend. Concentration alone does not prove abnormal behavior or an avoidable dollar amount${hasRunLevelEvidence ? "." : "; the evidence is aggregate rather than run-level."}`,
|
|
89
96
|
evidence: compactEvidence([
|
|
90
97
|
{ label: "Attributed spend", value: formatUsd(topAgent.amountUsd), detail: `${topAgent.recordCount} ${hasRunLevelEvidence ? "call-level" : "aggregate"} record${topAgent.recordCount === 1 ? "" : "s"}` },
|
|
91
98
|
{ label: "Share of tracked spend", value: formatPercent(share) },
|
|
92
|
-
topModel ? { label: "Dominant model or billing label", value: topModel.key, detail: `${formatUsd(topModel.amountUsd)} in this concentration` } : undefined,
|
|
93
|
-
topOperation ? { label: "Operation label", value: topOperation.key, detail: hasRunLevelEvidence ? "Call-level attribution" : "Not verified as one call or run" } : undefined
|
|
99
|
+
topModel ? { label: "Dominant model or billing label", value: safeEntity(topModel.key), detail: `${formatUsd(topModel.amountUsd)} in this concentration` } : undefined,
|
|
100
|
+
topOperation ? { label: "Operation label", value: safeEntity(topOperation.key), detail: hasRunLevelEvidence ? "Call-level attribution" : "Not verified as one call or run" } : undefined
|
|
94
101
|
]),
|
|
95
|
-
affectedClients: keysFrom(agentRecords, (record) => record.clientId),
|
|
96
|
-
affectedProjects: keysFrom(agentRecords, (record) => record.projectId),
|
|
97
|
-
affectedAgents: [topAgent.key],
|
|
98
|
-
affectedModels: keysFrom(agentRecords, (record) => record.model),
|
|
102
|
+
affectedClients: safeUntrustedLabels(keysFrom(agentRecords, (record) => record.clientId)),
|
|
103
|
+
affectedProjects: safeUntrustedLabels(keysFrom(agentRecords, (record) => record.projectId)),
|
|
104
|
+
affectedAgents: [safeEntity(topAgent.key)],
|
|
105
|
+
affectedModels: safeUntrustedLabels(keysFrom(agentRecords, (record) => record.model)),
|
|
99
106
|
estimatedImpactUsd: 0,
|
|
100
107
|
confidence: topAgent.confidence,
|
|
101
|
-
recommendedAction: `Confirm who owns ${topAgent.key}, reconcile the spend to its approved budget, and collect behavioral evidence before setting a cap or savings target.`,
|
|
108
|
+
recommendedAction: `Confirm who owns ${safeEntity(topAgent.key)}, reconcile the spend to its approved budget, and collect behavioral evidence before setting a cap or savings target.`,
|
|
102
109
|
verificationNeeded: "Confirm the budget owner and expected range; concentration alone is not behavioral evidence."
|
|
103
110
|
}];
|
|
104
111
|
}
|
|
@@ -113,7 +120,7 @@ function contextBloatInsights(records) {
|
|
|
113
120
|
const scopedRecords = topOperation
|
|
114
121
|
? highInputRecords.filter((record) => record.operation === topOperation.key)
|
|
115
122
|
: highInputRecords;
|
|
116
|
-
const operationLabel = topOperation?.key ?? "large-context calls";
|
|
123
|
+
const operationLabel = safeUntrustedLabel(topOperation?.key ?? "large-context calls", WITHHELD_OPERATION_LABEL);
|
|
117
124
|
const totalInputTokens = scopedRecords.reduce((total, record) => total + record.inputTokens, 0);
|
|
118
125
|
const scopedSpend = roundMoney(sumRecords(scopedRecords));
|
|
119
126
|
if (scopedSpend < 20) {
|
|
@@ -131,16 +138,27 @@ function contextBloatInsights(records) {
|
|
|
131
138
|
{ label: "Spend attached to large context", value: formatUsd(scopedSpend) },
|
|
132
139
|
{ label: "Dominant operation", value: operationLabel }
|
|
133
140
|
],
|
|
134
|
-
affectedClients: keysFrom(scopedRecords, (record) => record.clientId),
|
|
135
|
-
affectedProjects: keysFrom(scopedRecords, (record) => record.projectId),
|
|
136
|
-
affectedAgents: keysFrom(scopedRecords, (record) => record.agentId),
|
|
137
|
-
affectedModels: keysFrom(scopedRecords, (record) => record.model),
|
|
141
|
+
affectedClients: safeUntrustedLabels(keysFrom(scopedRecords, (record) => record.clientId)),
|
|
142
|
+
affectedProjects: safeUntrustedLabels(keysFrom(scopedRecords, (record) => record.projectId)),
|
|
143
|
+
affectedAgents: safeUntrustedLabels(keysFrom(scopedRecords, (record) => record.agentId)),
|
|
144
|
+
affectedModels: safeUntrustedLabels(keysFrom(scopedRecords, (record) => record.model)),
|
|
138
145
|
estimatedImpactUsd: 0,
|
|
139
146
|
confidence: combinedConfidence(scopedRecords.map((record) => record.costConfidence)),
|
|
140
147
|
recommendedAction: `Inspect representative ${operationLabel} prompts locally and run a matched before/after with the same acceptance criteria before proposing one reversible context change.`,
|
|
141
148
|
verificationNeeded: "Measure token and quality deltas on matched calls; no savings counterfactual is present yet."
|
|
142
149
|
}];
|
|
143
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* A breakdown key rendered for a HUMAN. The same slot holds a client, a
|
|
153
|
+
* project, an agent, a model or an operation depending on which grouping won,
|
|
154
|
+
* so it takes the dimension-neutral marker.
|
|
155
|
+
*
|
|
156
|
+
* Display only. The raw key is still what `records.filter(...)` matches on —
|
|
157
|
+
* rewriting a matching key would silently empty the cohort behind the finding.
|
|
158
|
+
*/
|
|
159
|
+
function safeEntity(value) {
|
|
160
|
+
return safeUntrustedLabel(value, WITHHELD_ENTITY_LABEL);
|
|
161
|
+
}
|
|
144
162
|
function topBreakdown(records, select) {
|
|
145
163
|
return breakdown(records, select)[0];
|
|
146
164
|
}
|
package/dist/localAgentLogs.d.ts
CHANGED
|
@@ -49,6 +49,16 @@ export type LocalAgentCall = {
|
|
|
49
49
|
latestTurnUsage?: LocalAgentTurnUsage;
|
|
50
50
|
/** Whether `usage` is one model turn or the session's cumulative financial total. */
|
|
51
51
|
usageScope?: "turn" | "session_cumulative";
|
|
52
|
+
/**
|
|
53
|
+
* Largest single-request prompt (effective input, cache reads included)
|
|
54
|
+
* observed within a `session_cumulative` `usage`. Tiered per-request pricing
|
|
55
|
+
* is selected per request, never from a cumulative sum: a cache-heavy session
|
|
56
|
+
* routinely clears a per-request tier threshold in aggregate while no single
|
|
57
|
+
* request did. This evidence lets pricing keep such a total on the base tier
|
|
58
|
+
* (exact) instead of failing closed to "missing". Absent for turn-scoped
|
|
59
|
+
* calls, which are already a single request.
|
|
60
|
+
*/
|
|
61
|
+
maxRequestPromptTokens?: number;
|
|
52
62
|
/**
|
|
53
63
|
* Whether the transcript exposed the input/output components required for
|
|
54
64
|
* pricing. A total-only snapshot is still usage evidence, but pricing it as
|
|
@@ -374,7 +384,7 @@ export type LocalAgentStreamCheckpointAdapter = {
|
|
|
374
384
|
writeStreamCheckpoint: (agent: LocalAgentFormatId, pathHash: string, checkpoint: Readonly<LocalAgentStreamCheckpointRecord>) => Promise<void>;
|
|
375
385
|
deleteStreamCheckpoint: (agent: LocalAgentFormatId, pathHash: string) => Promise<void>;
|
|
376
386
|
};
|
|
377
|
-
export declare const localAgentFinancialParserVersion =
|
|
387
|
+
export declare const localAgentFinancialParserVersion = 3;
|
|
378
388
|
/**
|
|
379
389
|
* Qualitative parser contract version. Bumped to 2 with the checkpointed
|
|
380
390
|
* streaming path (A4b): entries and checkpoints written by the pre-streaming
|
|
@@ -385,9 +395,15 @@ export declare const localAgentFinancialParserVersion = 1;
|
|
|
385
395
|
* cross-file completion evidence (`subagentCompletions`, from both Task tool
|
|
386
396
|
* results and background task-notifications): entries persisted by the
|
|
387
397
|
* collapsing parser must re-parse rather than silently keep merging subagent
|
|
388
|
-
* runs into their parent session.
|
|
398
|
+
* runs into their parent session. Bumped to 5 when Codex reducer checkpoints
|
|
399
|
+
* gained `maxTurnPromptTokens` (largest single request): a checkpoint written
|
|
400
|
+
* by v4 lacks the running per-request maximum, so restoring it could under-read
|
|
401
|
+
* the tier evidence for a cumulative session that crossed the boundary — a
|
|
402
|
+
* version mismatch discards it and re-parses from scratch instead. Bumped to 6
|
|
403
|
+
* with the user-fork inherited-baseline fix: v5 entries and checkpoints treated
|
|
404
|
+
* a fork's replayed parent history as the child's own usage and activity.
|
|
389
405
|
*/
|
|
390
|
-
export declare const localAgentQualitativeParserVersion =
|
|
406
|
+
export declare const localAgentQualitativeParserVersion = 6;
|
|
391
407
|
/**
|
|
392
408
|
* Conservative launch defaults for action-capable qualitative evidence.
|
|
393
409
|
* Callers must still inspect `qualitativeCoverage` before deriving a finding:
|
package/dist/localAgentLogs.js
CHANGED
|
@@ -22,7 +22,15 @@ export function latestObservedWorkingDirectory(calls) {
|
|
|
22
22
|
.sort((left, right) => right.timestamp.localeCompare(left.timestamp))[0]
|
|
23
23
|
?.workingDirectory;
|
|
24
24
|
}
|
|
25
|
-
|
|
25
|
+
// Bumped to 2 when session-cumulative Codex calls gained `maxRequestPromptTokens`
|
|
26
|
+
// (largest single request in the session). Financial caches written by v1 lack
|
|
27
|
+
// this per-request tier evidence, so a >272K-cumulative Codex session would
|
|
28
|
+
// stay voided to "missing" on reuse; a version mismatch re-parses instead.
|
|
29
|
+
// Bumped to 3 when user forks (`forked_from_id`) started resetting their
|
|
30
|
+
// inherited baseline: v2 entries priced a fork's replayed parent history as
|
|
31
|
+
// the child's own usage, so those cached amounts are overstated and must not
|
|
32
|
+
// be reused.
|
|
33
|
+
export const localAgentFinancialParserVersion = 3;
|
|
26
34
|
/**
|
|
27
35
|
* Qualitative parser contract version. Bumped to 2 with the checkpointed
|
|
28
36
|
* streaming path (A4b): entries and checkpoints written by the pre-streaming
|
|
@@ -33,9 +41,15 @@ export const localAgentFinancialParserVersion = 1;
|
|
|
33
41
|
* cross-file completion evidence (`subagentCompletions`, from both Task tool
|
|
34
42
|
* results and background task-notifications): entries persisted by the
|
|
35
43
|
* collapsing parser must re-parse rather than silently keep merging subagent
|
|
36
|
-
* runs into their parent session.
|
|
44
|
+
* runs into their parent session. Bumped to 5 when Codex reducer checkpoints
|
|
45
|
+
* gained `maxTurnPromptTokens` (largest single request): a checkpoint written
|
|
46
|
+
* by v4 lacks the running per-request maximum, so restoring it could under-read
|
|
47
|
+
* the tier evidence for a cumulative session that crossed the boundary — a
|
|
48
|
+
* version mismatch discards it and re-parses from scratch instead. Bumped to 6
|
|
49
|
+
* with the user-fork inherited-baseline fix: v5 entries and checkpoints treated
|
|
50
|
+
* a fork's replayed parent history as the child's own usage and activity.
|
|
37
51
|
*/
|
|
38
|
-
export const localAgentQualitativeParserVersion =
|
|
52
|
+
export const localAgentQualitativeParserVersion = 6;
|
|
39
53
|
/**
|
|
40
54
|
* Conservative launch defaults for action-capable qualitative evidence.
|
|
41
55
|
* Callers must still inspect `qualitativeCoverage` before deriving a finding:
|
|
@@ -384,6 +398,42 @@ function parseCodexTurnUsage(value) {
|
|
|
384
398
|
}
|
|
385
399
|
};
|
|
386
400
|
}
|
|
401
|
+
/**
|
|
402
|
+
* The tier-relevant prompt size of one Codex `last_token_usage` turn: its full
|
|
403
|
+
* request input (Codex `input_tokens` already includes cached input), which is
|
|
404
|
+
* exactly `effectivePromptTokens` of the parsed turn usage. Returned only when
|
|
405
|
+
* the field parses to a non-negative number, so an unreadable turn never lowers
|
|
406
|
+
* a running maximum. Used to prove whether any single request in a cumulative
|
|
407
|
+
* session crossed a per-request tier threshold.
|
|
408
|
+
*/
|
|
409
|
+
function codexTurnPromptTokens(turn) {
|
|
410
|
+
const input = tokenComponentOf(turn.input_tokens);
|
|
411
|
+
return input === undefined ? undefined : input;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Whether a Codex rollout replays another session's history before its own
|
|
415
|
+
* work, so the cumulative counter at its first real task is an inherited
|
|
416
|
+
* baseline rather than this session's usage.
|
|
417
|
+
*
|
|
418
|
+
* TWO kinds of rollout do this, and only the first was recognized before:
|
|
419
|
+
* - subagent rollouts (`thread_source: "subagent"`, or a `source.subagent`);
|
|
420
|
+
* - USER FORKS (`forked_from_id`), which carry `thread_source: "user"` and
|
|
421
|
+
* replay the parent transcript verbatim. Observed: a forked rollout whose
|
|
422
|
+
* first 9,051 of 9,442 token_count events are byte-identical copies of the
|
|
423
|
+
* parent's, all with earlier parent timestamps, carrying 1,204,265,192 of
|
|
424
|
+
* its 1,256,637,395 final cumulative input tokens (95.9%) — parent usage
|
|
425
|
+
* that was being billed again under the child, and again down each fork
|
|
426
|
+
* chain.
|
|
427
|
+
*
|
|
428
|
+
* This is deliberately NOT folded into `isSubagent`: that flag also drives
|
|
429
|
+
* activity attribution and checkpoint identity, where a user fork is a normal
|
|
430
|
+
* user session and must not be relabelled a subagent.
|
|
431
|
+
*/
|
|
432
|
+
function codexHasInheritedHistory(payload) {
|
|
433
|
+
return stringOf(payload.thread_source) === "subagent" ||
|
|
434
|
+
isRecord(payload.source) && "subagent" in payload.source ||
|
|
435
|
+
stringOf(payload.forked_from_id) !== undefined;
|
|
436
|
+
}
|
|
387
437
|
/** Parse one Claude Code transcript (JSONL). Exported for tests. */
|
|
388
438
|
export function parseClaudeCodeTranscript(content, filePath = "", sinceMs, onDiagnostic) {
|
|
389
439
|
const calls = [];
|
|
@@ -634,6 +684,7 @@ function createCodexRolloutParserState() {
|
|
|
634
684
|
fileCounts: new Map(),
|
|
635
685
|
toolCallCount: 0,
|
|
636
686
|
isSubagent: false,
|
|
687
|
+
hasInheritedHistory: false,
|
|
637
688
|
malformedLines: 0
|
|
638
689
|
};
|
|
639
690
|
}
|
|
@@ -666,6 +717,7 @@ function consumeCodexRolloutLine(state, line, onEntry) {
|
|
|
666
717
|
state.rootStartedAtMs = timestampMilliseconds(payload.timestamp ?? entry.timestamp);
|
|
667
718
|
state.isSubagent = stringOf(payload.thread_source) === "subagent" ||
|
|
668
719
|
isRecord(payload.source) && "subagent" in payload.source;
|
|
720
|
+
state.hasInheritedHistory = codexHasInheritedHistory(payload);
|
|
669
721
|
state.parentSessionId = stringOf(payload.parent_thread_id);
|
|
670
722
|
}
|
|
671
723
|
if (entry.type === "turn_context" && payload) {
|
|
@@ -677,7 +729,7 @@ function consumeCodexRolloutLine(state, line, onEntry) {
|
|
|
677
729
|
state.rootCwd = stringOf(payload.cwd);
|
|
678
730
|
}
|
|
679
731
|
}
|
|
680
|
-
if (state.
|
|
732
|
+
if (state.hasInheritedHistory &&
|
|
681
733
|
!state.rootTaskStarted &&
|
|
682
734
|
payload?.type === "task_started" &&
|
|
683
735
|
isRootSpecificTaskStart(payload.started_at, state.rootStartedAtMs)) {
|
|
@@ -688,6 +740,11 @@ function consumeCodexRolloutLine(state, line, onEntry) {
|
|
|
688
740
|
// prompts/files cannot become the child's focus. Restored checkpoint
|
|
689
741
|
// evidence predating the boundary is parent history too and resets with
|
|
690
742
|
// it (the raw session cwd deliberately survives, exactly as rootCwd).
|
|
743
|
+
//
|
|
744
|
+
// Replayed task_started events keep the PARENT's original started_at, so
|
|
745
|
+
// `isRootSpecificTaskStart` rejects them and the first accepted task is
|
|
746
|
+
// this session's own — which is why the boundary lands exactly at the end
|
|
747
|
+
// of the replay.
|
|
691
748
|
state.inheritedUsageBaseline = state.lastTotal;
|
|
692
749
|
state.lastTotal = undefined;
|
|
693
750
|
state.rootTaskStarted = true;
|
|
@@ -699,10 +756,11 @@ function consumeCodexRolloutLine(state, line, onEntry) {
|
|
|
699
756
|
state.toolCallCount = 0;
|
|
700
757
|
state.model = undefined;
|
|
701
758
|
state.lastTurn = undefined;
|
|
759
|
+
state.maxTurnPromptTokens = undefined;
|
|
702
760
|
state.lastRateLimits = undefined;
|
|
703
761
|
state.lastActivityAt = toIso(stringOf(entry.timestamp)) ?? state.startedAt;
|
|
704
762
|
}
|
|
705
|
-
if (payload?.type === "task_started" && (!state.
|
|
763
|
+
if (payload?.type === "task_started" && (!state.hasInheritedHistory || state.rootTaskStarted)) {
|
|
706
764
|
state.pendingTaskTurnId = stringOf(payload.turn_id);
|
|
707
765
|
state.completedTask = undefined;
|
|
708
766
|
}
|
|
@@ -757,6 +815,10 @@ function consumeCodexRolloutLine(state, line, onEntry) {
|
|
|
757
815
|
if (turn) {
|
|
758
816
|
state.lastTurn = turn;
|
|
759
817
|
state.lastActivityAt = eventTimestamp;
|
|
818
|
+
const turnPrompt = codexTurnPromptTokens(turn);
|
|
819
|
+
if (turnPrompt !== undefined) {
|
|
820
|
+
state.maxTurnPromptTokens = Math.max(state.maxTurnPromptTokens ?? 0, turnPrompt);
|
|
821
|
+
}
|
|
760
822
|
}
|
|
761
823
|
const rateLimits = parseCodexRateLimits(payload.rate_limits, eventTimestamp);
|
|
762
824
|
if (rateLimits) {
|
|
@@ -774,7 +836,7 @@ function finishCodexRolloutParse(state, onDiagnostic) {
|
|
|
774
836
|
if (state.malformedLines > 0) {
|
|
775
837
|
onDiagnostic?.({ code: "malformed_jsonl", count: state.malformedLines });
|
|
776
838
|
}
|
|
777
|
-
if (!state.lastTotal || state.
|
|
839
|
+
if (!state.lastTotal || state.hasInheritedHistory && !state.rootTaskStarted)
|
|
778
840
|
return [];
|
|
779
841
|
const parsedUsage = parseCodexCumulativeUsage(state.lastTotal, state.inheritedUsageBaseline);
|
|
780
842
|
const parsedTurn = state.lastTurn
|
|
@@ -834,6 +896,9 @@ function finishCodexRolloutParse(state, onDiagnostic) {
|
|
|
834
896
|
: {}),
|
|
835
897
|
usageScope: "session_cumulative",
|
|
836
898
|
usageSupport,
|
|
899
|
+
...(usageSupport === "complete" && state.maxTurnPromptTokens !== undefined
|
|
900
|
+
? { maxRequestPromptTokens: state.maxTurnPromptTokens }
|
|
901
|
+
: {}),
|
|
837
902
|
...(parsedUsage.reportedTotalTokens !== undefined
|
|
838
903
|
? { reportedTotalTokens: parsedUsage.reportedTotalTokens }
|
|
839
904
|
: {}),
|
|
@@ -1999,6 +2064,7 @@ function serializeCodexReducerState(state) {
|
|
|
1999
2064
|
...(state.rootStartedAtMs !== undefined ? { rootStartedAtMs: state.rootStartedAtMs } : {}),
|
|
2000
2065
|
rootTaskStarted: state.rootTaskStarted,
|
|
2001
2066
|
isSubagent: state.isSubagent,
|
|
2067
|
+
hasInheritedHistory: state.hasInheritedHistory,
|
|
2002
2068
|
...(state.parentSessionId !== undefined ? { parentSessionId: state.parentSessionId } : {}),
|
|
2003
2069
|
...(state.pendingTaskTurnId !== undefined ? { pendingTaskTurnId: state.pendingTaskTurnId } : {}),
|
|
2004
2070
|
...(state.completedTask !== undefined ? { completedTask: state.completedTask } : {}),
|
|
@@ -2006,6 +2072,9 @@ function serializeCodexReducerState(state) {
|
|
|
2006
2072
|
...(state.lastActivityAt !== undefined ? { lastActivityAt: state.lastActivityAt } : {}),
|
|
2007
2073
|
...(state.lastTotal !== undefined ? { lastTotal: state.lastTotal } : {}),
|
|
2008
2074
|
...(state.lastTurn !== undefined ? { lastTurn: state.lastTurn } : {}),
|
|
2075
|
+
...(state.maxTurnPromptTokens !== undefined
|
|
2076
|
+
? { maxTurnPromptTokens: state.maxTurnPromptTokens }
|
|
2077
|
+
: {}),
|
|
2009
2078
|
...(state.inheritedUsageBaseline !== undefined
|
|
2010
2079
|
? { inheritedUsageBaseline: state.inheritedUsageBaseline }
|
|
2011
2080
|
: {}),
|
|
@@ -2050,6 +2119,8 @@ function isPersistedCodexReducerState(value) {
|
|
|
2050
2119
|
return typeof value.rootSessionMetaSeen === "boolean" &&
|
|
2051
2120
|
typeof value.rootTaskStarted === "boolean" &&
|
|
2052
2121
|
typeof value.isSubagent === "boolean" &&
|
|
2122
|
+
(value.hasInheritedHistory === undefined ||
|
|
2123
|
+
typeof value.hasInheritedHistory === "boolean") &&
|
|
2053
2124
|
optionalString(value.model) &&
|
|
2054
2125
|
optionalString(value.sessionId) &&
|
|
2055
2126
|
optionalString(value.sourceVersion) &&
|
|
@@ -2062,6 +2133,7 @@ function isPersistedCodexReducerState(value) {
|
|
|
2062
2133
|
optionalString(value.lastActivityAt) &&
|
|
2063
2134
|
optionalRecord(value.lastTotal) &&
|
|
2064
2135
|
optionalRecord(value.lastTurn) &&
|
|
2136
|
+
optionalFinite(value.maxTurnPromptTokens) &&
|
|
2065
2137
|
optionalRecord(value.inheritedUsageBaseline) &&
|
|
2066
2138
|
validRateLimits &&
|
|
2067
2139
|
validRootCwd &&
|
|
@@ -2084,6 +2156,10 @@ function restoreCodexReducerState(persisted) {
|
|
|
2084
2156
|
state.rootStartedAtMs = persisted.rootStartedAtMs;
|
|
2085
2157
|
state.rootTaskStarted = persisted.rootTaskStarted;
|
|
2086
2158
|
state.isSubagent = persisted.isSubagent;
|
|
2159
|
+
// Pre-fork-fix checkpoints carry no flag; fall back to the subagent bit so a
|
|
2160
|
+
// restored subagent keeps its boundary (a restored user fork re-parses, its
|
|
2161
|
+
// parser version having changed).
|
|
2162
|
+
state.hasInheritedHistory = persisted.hasInheritedHistory ?? persisted.isSubagent;
|
|
2087
2163
|
state.parentSessionId = persisted.parentSessionId;
|
|
2088
2164
|
state.pendingTaskTurnId = persisted.pendingTaskTurnId;
|
|
2089
2165
|
state.completedTask = persisted.completedTask;
|
|
@@ -2091,6 +2167,7 @@ function restoreCodexReducerState(persisted) {
|
|
|
2091
2167
|
state.lastActivityAt = persisted.lastActivityAt;
|
|
2092
2168
|
state.lastTotal = persisted.lastTotal;
|
|
2093
2169
|
state.lastTurn = persisted.lastTurn;
|
|
2170
|
+
state.maxTurnPromptTokens = persisted.maxTurnPromptTokens;
|
|
2094
2171
|
state.inheritedUsageBaseline = persisted.inheritedUsageBaseline;
|
|
2095
2172
|
state.lastRateLimits = persisted.lastRateLimits;
|
|
2096
2173
|
state.restoredRootCwd = persisted.rootCwd;
|
|
@@ -2726,8 +2803,11 @@ async function readCodexFinancialFile(file) {
|
|
|
2726
2803
|
const rootPayload = rootEntry?.type === "session_meta" && isRecord(rootEntry.payload)
|
|
2727
2804
|
? rootEntry.payload
|
|
2728
2805
|
: undefined;
|
|
2729
|
-
|
|
2730
|
-
|
|
2806
|
+
// A rollout that replays another session's history must be read WHOLE:
|
|
2807
|
+
// its inherited-baseline boundary sits mid-file, and a proof-complete tail
|
|
2808
|
+
// would never reach it. User forks need this exactly as subagents do.
|
|
2809
|
+
const hasInheritedHistory = rootPayload !== undefined &&
|
|
2810
|
+
codexHasInheritedHistory(rootPayload);
|
|
2731
2811
|
const entriesReverse = [];
|
|
2732
2812
|
let malformedLines = 0;
|
|
2733
2813
|
let prefilteredLines = 0;
|
|
@@ -2738,7 +2818,7 @@ async function readCodexFinancialFile(file) {
|
|
|
2738
2818
|
let suffixPartsReverse = [];
|
|
2739
2819
|
let stoppedEarly = false;
|
|
2740
2820
|
let bytesSkipped = 0;
|
|
2741
|
-
const proof = createCodexReverseProof(
|
|
2821
|
+
const proof = createCodexReverseProof(hasInheritedHistory, !rootPayload);
|
|
2742
2822
|
while (position > 0 && !stoppedEarly) {
|
|
2743
2823
|
const chunkStart = Math.max(0, position - FINANCIAL_REVERSE_CHUNK_BYTES);
|
|
2744
2824
|
const length = position - chunkStart;
|
|
@@ -2979,9 +3059,9 @@ function skipJsonWhitespace(input, start) {
|
|
|
2979
3059
|
index += 1;
|
|
2980
3060
|
return index;
|
|
2981
3061
|
}
|
|
2982
|
-
function createCodexReverseProof(
|
|
3062
|
+
function createCodexReverseProof(hasInheritedHistory, forceFullScan) {
|
|
2983
3063
|
return {
|
|
2984
|
-
|
|
3064
|
+
hasInheritedHistory,
|
|
2985
3065
|
forceFullScan,
|
|
2986
3066
|
totalSeen: false,
|
|
2987
3067
|
turnSeen: false,
|
|
@@ -2990,7 +3070,7 @@ function createCodexReverseProof(isSubagent, forceFullScan) {
|
|
|
2990
3070
|
};
|
|
2991
3071
|
}
|
|
2992
3072
|
function observeCodexReverseProof(proof, entry) {
|
|
2993
|
-
if (proof.forceFullScan || proof.
|
|
3073
|
+
if (proof.forceFullScan || proof.hasInheritedHistory)
|
|
2994
3074
|
return false;
|
|
2995
3075
|
const payload = isRecord(entry.payload) ? entry.payload : undefined;
|
|
2996
3076
|
const info = payload?.type === "token_count" && isRecord(payload.info)
|
|
@@ -3007,7 +3087,7 @@ function observeCodexReverseProof(proof, entry) {
|
|
|
3007
3087
|
if (entry.type === "turn_context" && stringOf(payload?.model)) {
|
|
3008
3088
|
proof.modelSeen = true;
|
|
3009
3089
|
}
|
|
3010
|
-
return !proof.
|
|
3090
|
+
return !proof.hasInheritedHistory &&
|
|
3011
3091
|
proof.totalSeen &&
|
|
3012
3092
|
proof.turnSeen &&
|
|
3013
3093
|
proof.rateLimitsSeen &&
|
|
@@ -3112,7 +3192,8 @@ function createCodexFinancialStreamState() {
|
|
|
3112
3192
|
return {
|
|
3113
3193
|
rootSessionMetaSeen: false,
|
|
3114
3194
|
rootTaskStarted: false,
|
|
3115
|
-
isSubagent: false
|
|
3195
|
+
isSubagent: false,
|
|
3196
|
+
hasInheritedHistory: false
|
|
3116
3197
|
};
|
|
3117
3198
|
}
|
|
3118
3199
|
function consumeCodexFinancialEntry(state, entry) {
|
|
@@ -3126,12 +3207,13 @@ function consumeCodexFinancialEntry(state, entry) {
|
|
|
3126
3207
|
state.rootStartedAtMs = timestampMilliseconds(payload.timestamp ?? entry.timestamp);
|
|
3127
3208
|
state.isSubagent = stringOf(payload.thread_source) === "subagent" ||
|
|
3128
3209
|
isRecord(payload.source) && "subagent" in payload.source;
|
|
3210
|
+
state.hasInheritedHistory = codexHasInheritedHistory(payload);
|
|
3129
3211
|
}
|
|
3130
3212
|
if (entry.type === "turn_context" && payload) {
|
|
3131
3213
|
state.model = stringOf(payload.model) ?? state.model;
|
|
3132
3214
|
state.rootCwd ??= stringOf(payload.cwd);
|
|
3133
3215
|
}
|
|
3134
|
-
if (state.
|
|
3216
|
+
if (state.hasInheritedHistory &&
|
|
3135
3217
|
!state.rootTaskStarted &&
|
|
3136
3218
|
payload?.type === "task_started" &&
|
|
3137
3219
|
isRootSpecificTaskStart(payload.started_at, state.rootStartedAtMs)) {
|
|
@@ -3140,6 +3222,7 @@ function consumeCodexFinancialEntry(state, entry) {
|
|
|
3140
3222
|
state.rootTaskStarted = true;
|
|
3141
3223
|
state.model = undefined;
|
|
3142
3224
|
state.lastTurn = undefined;
|
|
3225
|
+
state.maxTurnPromptTokens = undefined;
|
|
3143
3226
|
state.lastRateLimits = undefined;
|
|
3144
3227
|
state.lastActivityAt = toIso(stringOf(entry.timestamp)) ?? state.startedAt;
|
|
3145
3228
|
}
|
|
@@ -3162,13 +3245,17 @@ function consumeCodexFinancialEntry(state, entry) {
|
|
|
3162
3245
|
if (turn) {
|
|
3163
3246
|
state.lastTurn = turn;
|
|
3164
3247
|
state.lastActivityAt = eventTimestamp;
|
|
3248
|
+
const turnPrompt = codexTurnPromptTokens(turn);
|
|
3249
|
+
if (turnPrompt !== undefined) {
|
|
3250
|
+
state.maxTurnPromptTokens = Math.max(state.maxTurnPromptTokens ?? 0, turnPrompt);
|
|
3251
|
+
}
|
|
3165
3252
|
}
|
|
3166
3253
|
const rateLimits = parseCodexRateLimits(payload.rate_limits, eventTimestamp);
|
|
3167
3254
|
if (rateLimits)
|
|
3168
3255
|
state.lastRateLimits = rateLimits;
|
|
3169
3256
|
}
|
|
3170
3257
|
function finishCodexFinancialStream(state, onDiagnostic) {
|
|
3171
|
-
if (!state.lastTotal || state.
|
|
3258
|
+
if (!state.lastTotal || state.hasInheritedHistory && !state.rootTaskStarted)
|
|
3172
3259
|
return undefined;
|
|
3173
3260
|
const parsedUsage = parseCodexCumulativeUsage(state.lastTotal, state.inheritedUsageBaseline);
|
|
3174
3261
|
const parsedTurn = state.lastTurn
|
|
@@ -3196,6 +3283,9 @@ function finishCodexFinancialStream(state, onDiagnostic) {
|
|
|
3196
3283
|
: {}),
|
|
3197
3284
|
usageScope: "session_cumulative",
|
|
3198
3285
|
usageSupport,
|
|
3286
|
+
...(usageSupport === "complete" && state.maxTurnPromptTokens !== undefined
|
|
3287
|
+
? { maxRequestPromptTokens: state.maxTurnPromptTokens }
|
|
3288
|
+
: {}),
|
|
3199
3289
|
...(parsedUsage.reportedTotalTokens !== undefined
|
|
3200
3290
|
? { reportedTotalTokens: parsedUsage.reportedTotalTokens }
|
|
3201
3291
|
: {}),
|
|
@@ -3234,10 +3324,14 @@ export function aggregateCallsForFormats(calls, descriptors) {
|
|
|
3234
3324
|
const usageSupported = groupCalls.every((call) => call.usageSupport !== "unsupported_token_shape");
|
|
3235
3325
|
const sourceVersions = [...new Set(groupCalls.flatMap((call) => call.sourceVersion ? [call.sourceVersion] : []))].sort().slice(0, 8);
|
|
3236
3326
|
const tieredPricingEvidenceSupported = !usesPromptTieredPricing(model) ||
|
|
3237
|
-
groupCalls.every((call) => canPriceTokenUsageAtScope(model, call.usage, call.usageScope === "turn" ? "request" : "aggregate") && (agent !== "gemini-cli" || hasCompleteGeminiPromptEvidence(call)));
|
|
3327
|
+
groupCalls.every((call) => canPriceTokenUsageAtScope(model, call.usage, call.usageScope === "turn" ? "request" : "aggregate", call.maxRequestPromptTokens) && (agent !== "gemini-cli" || hasCompleteGeminiPromptEvidence(call)));
|
|
3238
3328
|
const amountUsd = usageSupported && tieredPricingEvidenceSupported && format
|
|
3239
3329
|
? usesPromptTieredPricing(model)
|
|
3240
|
-
? estimateTokenCostsUsd(model, groupCalls.map((call) => call.usage)
|
|
3330
|
+
? estimateTokenCostsUsd(model, groupCalls.map((call) => call.usage),
|
|
3331
|
+
// Fix each cumulative slice's tier from its largest single request
|
|
3332
|
+
// rather than its cache-inflated sum. Turn-scoped slices carry no
|
|
3333
|
+
// such evidence and keep selecting their tier from their own prompt.
|
|
3334
|
+
groupCalls.map((call) => call.maxRequestPromptTokens))
|
|
3241
3335
|
: estimateTokenCostUsd(model, usage)
|
|
3242
3336
|
: undefined;
|
|
3243
3337
|
const priced = usageSupported && typeof amountUsd === "number";
|
package/dist/modelPricing.d.ts
CHANGED
|
@@ -50,8 +50,17 @@ export declare function estimateTokenCostUsd(model: string, usage: TokenUsage):
|
|
|
50
50
|
* models whose entire request moves to a higher rate above a prompt-size
|
|
51
51
|
* threshold; pricing a daily token sum would incorrectly treat many small
|
|
52
52
|
* requests as one large request.
|
|
53
|
+
*
|
|
54
|
+
* `tierPromptTokens[i]`, when provided, fixes the tier of `usages[i]` from
|
|
55
|
+
* request-level evidence instead of the slice's own prompt total. A
|
|
56
|
+
* session-cumulative slice is a sum of many requests whose prompt total
|
|
57
|
+
* routinely clears a per-request threshold on cache reads alone, even though no
|
|
58
|
+
* single request did; supplying the largest single request's prompt keeps such
|
|
59
|
+
* a slice on the base tier (and pricing it there is exact, since the base rate
|
|
60
|
+
* distributes over the sum). Omitting the array preserves single-request
|
|
61
|
+
* behaviour: each slice's own prompt selects its tier.
|
|
53
62
|
*/
|
|
54
|
-
export declare function estimateTokenCostsUsd(model: string, usages: readonly TokenUsage[]): number | undefined;
|
|
63
|
+
export declare function estimateTokenCostsUsd(model: string, usages: readonly TokenUsage[], tierPromptTokens?: readonly (number | undefined)[]): number | undefined;
|
|
55
64
|
/** Whether this model's rate selection depends on each request's prompt size. */
|
|
56
65
|
export declare function usesPromptTieredPricing(model: string): boolean;
|
|
57
66
|
/** Prompt-size threshold for tiered request pricing, when one is published. */
|
|
@@ -60,9 +69,13 @@ export declare function promptTierThreshold(model: string): number | undefined;
|
|
|
60
69
|
* Tiered prices are selected per request, never from a multi-request sum.
|
|
61
70
|
* An aggregate is still unambiguous when its entire non-negative prompt-side
|
|
62
71
|
* total is at or below the threshold; then no constituent request can have
|
|
63
|
-
* crossed it.
|
|
64
|
-
*
|
|
72
|
+
* crossed it. It is also unambiguous when request-level evidence
|
|
73
|
+
* (`maxRequestPromptTokens`, the largest single request the aggregate contains)
|
|
74
|
+
* proves that no constituent request crossed the threshold: every request was
|
|
75
|
+
* base-tier, so the whole sum is base-tier and prices exactly at the base rate.
|
|
76
|
+
* Larger aggregates without such evidence fail closed to keep an unpriceable
|
|
77
|
+
* total honestly "missing" rather than guessing a tier.
|
|
65
78
|
*/
|
|
66
|
-
export declare function canPriceTokenUsageAtScope(model: string, usage: TokenUsage, scope: "request" | "aggregate"): boolean;
|
|
79
|
+
export declare function canPriceTokenUsageAtScope(model: string, usage: TokenUsage, scope: "request" | "aggregate", maxRequestPromptTokens?: number): boolean;
|
|
67
80
|
export {};
|
|
68
81
|
//# sourceMappingURL=modelPricing.d.ts.map
|