@compaction/cli 0.1.0
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/LICENSE +663 -0
- package/README.md +319 -0
- package/dist/cli/commands/adapter.d.ts +2 -0
- package/dist/cli/commands/adapter.js +49 -0
- package/dist/cli/commands/aggregate.d.ts +2 -0
- package/dist/cli/commands/aggregate.js +60 -0
- package/dist/cli/commands/analyze.d.ts +2 -0
- package/dist/cli/commands/analyze.js +55 -0
- package/dist/cli/commands/apply-context.d.ts +12 -0
- package/dist/cli/commands/apply-context.js +80 -0
- package/dist/cli/commands/apply.d.ts +2 -0
- package/dist/cli/commands/apply.js +65 -0
- package/dist/cli/commands/approve.d.ts +2 -0
- package/dist/cli/commands/approve.js +55 -0
- package/dist/cli/commands/audit.d.ts +2 -0
- package/dist/cli/commands/audit.js +63 -0
- package/dist/cli/commands/billing-delta.d.ts +2 -0
- package/dist/cli/commands/billing-delta.js +246 -0
- package/dist/cli/commands/capture-claude-code.d.ts +19 -0
- package/dist/cli/commands/capture-claude-code.js +198 -0
- package/dist/cli/commands/capture-provider-usage.d.ts +35 -0
- package/dist/cli/commands/capture-provider-usage.js +113 -0
- package/dist/cli/commands/capture.d.ts +2 -0
- package/dist/cli/commands/capture.js +76 -0
- package/dist/cli/commands/compact.d.ts +2 -0
- package/dist/cli/commands/compact.js +206 -0
- package/dist/cli/commands/eval.d.ts +2 -0
- package/dist/cli/commands/eval.js +66 -0
- package/dist/cli/commands/export-session-seed.d.ts +11 -0
- package/dist/cli/commands/export-session-seed.js +42 -0
- package/dist/cli/commands/feedback.d.ts +2 -0
- package/dist/cli/commands/feedback.js +233 -0
- package/dist/cli/commands/import.d.ts +2 -0
- package/dist/cli/commands/import.js +60 -0
- package/dist/cli/commands/init.d.ts +2 -0
- package/dist/cli/commands/init.js +216 -0
- package/dist/cli/commands/integrations.d.ts +2 -0
- package/dist/cli/commands/integrations.js +29 -0
- package/dist/cli/commands/launch-treatment-session.d.ts +14 -0
- package/dist/cli/commands/launch-treatment-session.js +50 -0
- package/dist/cli/commands/optimize.d.ts +2 -0
- package/dist/cli/commands/optimize.js +33 -0
- package/dist/cli/commands/recommend.d.ts +4 -0
- package/dist/cli/commands/recommend.js +66 -0
- package/dist/cli/commands/review.d.ts +2 -0
- package/dist/cli/commands/review.js +60 -0
- package/dist/cli/commands/run.d.ts +2 -0
- package/dist/cli/commands/run.js +49 -0
- package/dist/cli/commands/spend.d.ts +2 -0
- package/dist/cli/commands/spend.js +24 -0
- package/dist/cli/commands/summary.d.ts +2 -0
- package/dist/cli/commands/summary.js +19 -0
- package/dist/cli/engine-degrade.d.ts +40 -0
- package/dist/cli/engine-degrade.js +59 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +59 -0
- package/dist/core/adapter-validation.d.ts +32 -0
- package/dist/core/adapter-validation.js +185 -0
- package/dist/core/adapters/claude-code-adapter.d.ts +51 -0
- package/dist/core/adapters/claude-code-adapter.js +539 -0
- package/dist/core/adapters/claude-code-discovery.d.ts +40 -0
- package/dist/core/adapters/claude-code-discovery.js +100 -0
- package/dist/core/aggregate-format.d.ts +62 -0
- package/dist/core/aggregate-format.js +349 -0
- package/dist/core/api-client/client.d.ts +43 -0
- package/dist/core/api-client/client.js +143 -0
- package/dist/core/api-client/config.d.ts +36 -0
- package/dist/core/api-client/config.js +44 -0
- package/dist/core/api-client/index.d.ts +13 -0
- package/dist/core/api-client/index.js +14 -0
- package/dist/core/api-client/payload.d.ts +93 -0
- package/dist/core/api-client/payload.js +180 -0
- package/dist/core/api-client/types.d.ts +95 -0
- package/dist/core/api-client/types.js +21 -0
- package/dist/core/artifact-writer.d.ts +2 -0
- package/dist/core/artifact-writer.js +14 -0
- package/dist/core/audit-report.d.ts +91 -0
- package/dist/core/audit-report.js +422 -0
- package/dist/core/billing-delta/billing-delta-record.d.ts +290 -0
- package/dist/core/billing-delta/billing-delta-record.js +394 -0
- package/dist/core/capture-adapter.d.ts +74 -0
- package/dist/core/capture-adapter.js +2 -0
- package/dist/core/command-runner.d.ts +29 -0
- package/dist/core/command-runner.js +96 -0
- package/dist/core/content-hash.d.ts +1 -0
- package/dist/core/content-hash.js +5 -0
- package/dist/core/cost-calculator.d.ts +6 -0
- package/dist/core/cost-calculator.js +20 -0
- package/dist/core/feedback-bundle.d.ts +159 -0
- package/dist/core/feedback-bundle.js +406 -0
- package/dist/core/openai-agents-capture.d.ts +66 -0
- package/dist/core/openai-agents-capture.js +475 -0
- package/dist/core/policy-types.d.ts +85 -0
- package/dist/core/policy-types.js +29 -0
- package/dist/core/pricing.d.ts +23 -0
- package/dist/core/pricing.js +55 -0
- package/dist/core/provider-usage/credential-redaction.d.ts +23 -0
- package/dist/core/provider-usage/credential-redaction.js +60 -0
- package/dist/core/provider-usage/provider-usage-adapter.d.ts +65 -0
- package/dist/core/provider-usage/provider-usage-adapter.js +147 -0
- package/dist/core/provider-usage/provider-usage-client.d.ts +89 -0
- package/dist/core/provider-usage/provider-usage-client.js +96 -0
- package/dist/core/report-generator.d.ts +7 -0
- package/dist/core/report-generator.js +275 -0
- package/dist/core/run-aggregator.d.ts +50 -0
- package/dist/core/run-aggregator.js +251 -0
- package/dist/core/run-labels.d.ts +52 -0
- package/dist/core/run-labels.js +80 -0
- package/dist/core/run-trace-converter.d.ts +3 -0
- package/dist/core/run-trace-converter.js +94 -0
- package/dist/core/safety-report.d.ts +52 -0
- package/dist/core/safety-report.js +440 -0
- package/dist/core/savings.d.ts +22 -0
- package/dist/core/savings.js +36 -0
- package/dist/core/session-aggregate.d.ts +211 -0
- package/dist/core/session-aggregate.js +490 -0
- package/dist/core/skill-injection-detector.d.ts +51 -0
- package/dist/core/skill-injection-detector.js +146 -0
- package/dist/core/spend-attribution.d.ts +169 -0
- package/dist/core/spend-attribution.js +611 -0
- package/dist/core/standalone-capsule-provenance.d.ts +4 -0
- package/dist/core/standalone-capsule-provenance.js +62 -0
- package/dist/core/state-capsule.d.ts +3 -0
- package/dist/core/state-capsule.js +147 -0
- package/dist/core/token-accounting.d.ts +62 -0
- package/dist/core/token-accounting.js +81 -0
- package/dist/core/token-estimator.d.ts +3 -0
- package/dist/core/token-estimator.js +18 -0
- package/dist/core/trace-adapters.d.ts +53 -0
- package/dist/core/trace-adapters.js +714 -0
- package/dist/core/trace-fingerprint.d.ts +79 -0
- package/dist/core/trace-fingerprint.js +102 -0
- package/dist/core/trace-intake.d.ts +41 -0
- package/dist/core/trace-intake.js +164 -0
- package/dist/core/trace-parser.d.ts +203 -0
- package/dist/core/trace-parser.js +143 -0
- package/dist/core/types.d.ts +238 -0
- package/dist/core/types.js +2 -0
- package/dist/core/usage-metadata.d.ts +51 -0
- package/dist/core/usage-metadata.js +122 -0
- package/dist/core/waste-detector.d.ts +8 -0
- package/dist/core/waste-detector.js +58 -0
- package/package.json +90 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Billing-delta measurement record — operator-run A/B protocol (local-first).
|
|
3
|
+
*
|
|
4
|
+
* Implements Alternative 1 / §3 of docs/design/billing-delta-loop.md (the accepted
|
|
5
|
+
* methodology direction): a LOCAL, OPERATOR-RUN measurement record. The OPERATOR
|
|
6
|
+
* performs the real workflow runs themselves (control arm = original context;
|
|
7
|
+
* treatment arm = apply-context-approved compacted context), reads the billed costs
|
|
8
|
+
* from THEIR OWN provider console / billing export, and ENTERS them. This module
|
|
9
|
+
* records runs with provenance, computes per-arm statistics and the delta of means,
|
|
10
|
+
* applies the accepted methodology's honesty rubric, and emits a caveated verdict.
|
|
11
|
+
*
|
|
12
|
+
* HARD HONESTY RAILS (see the accepted doc §2.4, §1(a)-(e), Risks):
|
|
13
|
+
* - This module makes NO provider call, reads NO credential, has NO network access.
|
|
14
|
+
* - Operator-entered figures are labeled `operator_entered: true`. The tool CANNOT
|
|
15
|
+
* verify them against the provider.
|
|
16
|
+
* - The tool NEVER emits the phrase "billing-confirmed savings" as a claim and NEVER
|
|
17
|
+
* sets `billing_confirmed: true`. This is NOT a Tier-3 claim. `ValueProof` is
|
|
18
|
+
* untouched (its `billing_confirmed_savings.claimed` stays false).
|
|
19
|
+
* - The strongest verdict is a "measured, caveated delta on N runs of ONE workflow
|
|
20
|
+
* under recorded conditions" — explicitly NOT a general savings claim.
|
|
21
|
+
* - Aggregate cost figures only (LOW sensitivity). NO prompt/completion content is
|
|
22
|
+
* stored anywhere in the record.
|
|
23
|
+
*/
|
|
24
|
+
import { contentHash } from "../content-hash.js";
|
|
25
|
+
import { getModelPricing, isKnownModel } from "../pricing.js";
|
|
26
|
+
export const BILLING_DELTA_RECORD_ROOT = ".compaction/billing-delta";
|
|
27
|
+
export const BILLING_DELTA_RECORD_SCHEMA = "billing-delta.measurement-record.v1";
|
|
28
|
+
export const BILLING_DELTA_REPORT_SCHEMA = "billing-delta.report.v1";
|
|
29
|
+
export const BILLING_DELTA_PRIVACY_POSTURE = {
|
|
30
|
+
sensitivity: "low",
|
|
31
|
+
aggregate_cost_only: true,
|
|
32
|
+
operator_entered_figures: true,
|
|
33
|
+
local_only: true
|
|
34
|
+
};
|
|
35
|
+
/** The documented decision criterion. Named in every report. */
|
|
36
|
+
export const BILLING_DELTA_CRITERION = "delta-of-means interval = [mean_delta - 2*SE, mean_delta + 2*SE], SE = sqrt(sd_control^2/n_control + sd_treatment^2/n_treatment); " +
|
|
37
|
+
"verdict measured_caveated_delta only when N>=3 per arm AND the interval excludes zero (conservative ~2-standard-error rule, sample SD, no distributional claim beyond it)";
|
|
38
|
+
/** The standard-error multiplier used to form the conservative interval. Documented and fixed (no over-engineered statistics). */
|
|
39
|
+
export const BILLING_DELTA_INTERVAL_K = 2;
|
|
40
|
+
/** Verbatim-ish anecdote note required by the accepted doc (§2.4: "a single A/B run is an anecdote, not proof"). */
|
|
41
|
+
export const BILLING_DELTA_ANECDOTE_NOTE = "A single A/B pair is an anecdote, not proof: N=1 per arm cannot distinguish a compaction effect from run-to-run noise.";
|
|
42
|
+
/**
|
|
43
|
+
* Claim-level labels for the token-delta section. Token-level, provider-reported — never a
|
|
44
|
+
* billing claim. The label is selected by the ACTUAL sign/status of the token delta so it
|
|
45
|
+
* never calls a non-reduction a "reduction" (an overclaim by mislabel): a negative input
|
|
46
|
+
* delta means the treatment context GREW (an INCREASE), zero means NO CHANGE, and the
|
|
47
|
+
* insufficient/suppressed statuses have no computed delta at all. Every variant preserves the
|
|
48
|
+
* "NOT billing-confirmed savings" disclaimer and makes no savings claim.
|
|
49
|
+
*/
|
|
50
|
+
export const BILLING_DELTA_TOKEN_DELTA_LABELS = {
|
|
51
|
+
/** Genuine reduction: treatment used fewer input tokens than control (positive input delta). */
|
|
52
|
+
reduction: "provider-reported token reduction (workflow-scoped) — NOT billing-confirmed savings",
|
|
53
|
+
/** Increase: treatment used MORE input tokens than control (negative input delta). Not a saving. */
|
|
54
|
+
increase: "provider-reported token INCREASE (workflow-scoped) — NOT a saving; NOT billing-confirmed savings",
|
|
55
|
+
/** No change: the input token delta is exactly zero. */
|
|
56
|
+
no_change: "provider-reported token delta: no change (workflow-scoped) — NOT billing-confirmed savings",
|
|
57
|
+
/** Not computed: insufficient token data or deltas suppressed by a failed task. */
|
|
58
|
+
not_computed: "provider-reported token delta not computed (workflow-scoped) — NOT billing-confirmed savings"
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Select the sign/status-accurate claim label. Reduction is used ONLY when the input delta is
|
|
62
|
+
* genuinely positive (treatment used fewer tokens); a negative delta is an increase, zero is no
|
|
63
|
+
* change, and a null delta (insufficient/suppressed status) is "not computed". This never
|
|
64
|
+
* strengthens a claim — it only makes the existing label honest for the actual sign/status.
|
|
65
|
+
*/
|
|
66
|
+
export function selectTokenDeltaLabel(status, inputTokenDelta) {
|
|
67
|
+
if (status !== "computed" || inputTokenDelta === null) {
|
|
68
|
+
return BILLING_DELTA_TOKEN_DELTA_LABELS.not_computed;
|
|
69
|
+
}
|
|
70
|
+
if (inputTokenDelta > 0)
|
|
71
|
+
return BILLING_DELTA_TOKEN_DELTA_LABELS.reduction;
|
|
72
|
+
if (inputTokenDelta < 0)
|
|
73
|
+
return BILLING_DELTA_TOKEN_DELTA_LABELS.increase;
|
|
74
|
+
return BILLING_DELTA_TOKEN_DELTA_LABELS.no_change;
|
|
75
|
+
}
|
|
76
|
+
function deriveMeasurementId(workflowId, generatedAt) {
|
|
77
|
+
const seed = `${workflowId}::${generatedAt}`;
|
|
78
|
+
return `bdm_${contentHash(seed).slice(0, 16)}`;
|
|
79
|
+
}
|
|
80
|
+
export function buildTreatmentApprovalLink(path, fileContent) {
|
|
81
|
+
return {
|
|
82
|
+
apply_approval_record_path: path,
|
|
83
|
+
apply_approval_record_sha256: contentHash(fileContent)
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
export function initMeasurementRecord(input) {
|
|
87
|
+
const measurement_id = input.measurementId ?? deriveMeasurementId(input.workflowId, input.generatedAt);
|
|
88
|
+
let apply_approval_link;
|
|
89
|
+
if (input.applyApprovalRecordPath && input.applyApprovalRecordContent !== undefined) {
|
|
90
|
+
apply_approval_link = buildTreatmentApprovalLink(input.applyApprovalRecordPath, input.applyApprovalRecordContent);
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
schema: BILLING_DELTA_RECORD_SCHEMA,
|
|
94
|
+
measurement_id,
|
|
95
|
+
created_at: input.generatedAt,
|
|
96
|
+
workflow_id: input.workflowId,
|
|
97
|
+
workflow_description: input.workflowDescription,
|
|
98
|
+
fixed_conditions: {
|
|
99
|
+
model: input.model,
|
|
100
|
+
model_version: input.modelVersion ?? "unknown",
|
|
101
|
+
input_description: input.inputDescription,
|
|
102
|
+
window: input.window
|
|
103
|
+
},
|
|
104
|
+
...(apply_approval_link ? { apply_approval_link } : {}),
|
|
105
|
+
runs: [],
|
|
106
|
+
privacy_posture: BILLING_DELTA_PRIVACY_POSTURE,
|
|
107
|
+
operator_entered: true,
|
|
108
|
+
billing_confirmed: false,
|
|
109
|
+
tool_makes_provider_call: false
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function deriveRunId(record, input) {
|
|
113
|
+
const seed = `${record.measurement_id}::${input.arm}::${input.runTimestamp}::${record.runs.length}`;
|
|
114
|
+
return `run_${contentHash(seed).slice(0, 12)}`;
|
|
115
|
+
}
|
|
116
|
+
export function addRun(record, input) {
|
|
117
|
+
if (!Number.isFinite(input.billedCost) || input.billedCost < 0) {
|
|
118
|
+
throw new Error("billed cost must be a finite, non-negative number");
|
|
119
|
+
}
|
|
120
|
+
if (!input.currency || input.currency.trim() === "") {
|
|
121
|
+
throw new Error("currency is required");
|
|
122
|
+
}
|
|
123
|
+
let treatment_approval_link;
|
|
124
|
+
if (input.treatmentApprovalRecordPath && input.treatmentApprovalRecordContent !== undefined) {
|
|
125
|
+
treatment_approval_link = buildTreatmentApprovalLink(input.treatmentApprovalRecordPath, input.treatmentApprovalRecordContent);
|
|
126
|
+
}
|
|
127
|
+
const run = {
|
|
128
|
+
run_id: input.runId ?? deriveRunId(record, input),
|
|
129
|
+
arm: input.arm,
|
|
130
|
+
billed_cost: input.billedCost,
|
|
131
|
+
currency: input.currency,
|
|
132
|
+
operator_entered: true,
|
|
133
|
+
...(input.providerReportedUsage ? { provider_reported_usage: input.providerReportedUsage } : {}),
|
|
134
|
+
run_timestamp: input.runTimestamp,
|
|
135
|
+
...(input.notes ? { notes: input.notes } : {}),
|
|
136
|
+
...(treatment_approval_link ? { treatment_approval_link } : {})
|
|
137
|
+
};
|
|
138
|
+
// Backfill the machine-readable privacy posture when rewriting a record created
|
|
139
|
+
// before this field existed, so absence reliably means "written pre-field" and an
|
|
140
|
+
// operator continuing an old measurement does not keep persisting a posture-less file.
|
|
141
|
+
// The posture is a structural property of the substrate (local, operator-entered,
|
|
142
|
+
// aggregate-only), not a per-record choice, so backfilling it is a restatement.
|
|
143
|
+
return {
|
|
144
|
+
...record,
|
|
145
|
+
privacy_posture: record.privacy_posture ?? BILLING_DELTA_PRIVACY_POSTURE,
|
|
146
|
+
runs: [...record.runs, run]
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function mean(values) {
|
|
150
|
+
return values.reduce((sum, v) => sum + v, 0) / values.length;
|
|
151
|
+
}
|
|
152
|
+
function sampleStandardDeviation(values) {
|
|
153
|
+
if (values.length < 2)
|
|
154
|
+
return null;
|
|
155
|
+
const m = mean(values);
|
|
156
|
+
const variance = values.reduce((sum, v) => sum + (v - m) ** 2, 0) / (values.length - 1);
|
|
157
|
+
return Math.sqrt(variance);
|
|
158
|
+
}
|
|
159
|
+
function armStatistics(arm, runs) {
|
|
160
|
+
const costs = runs.filter((r) => r.arm === arm).map((r) => r.billed_cost);
|
|
161
|
+
if (costs.length === 0) {
|
|
162
|
+
return { arm, n: 0, mean: null, min: null, max: null, sample_sd: null };
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
arm,
|
|
166
|
+
n: costs.length,
|
|
167
|
+
mean: mean(costs),
|
|
168
|
+
min: Math.min(...costs),
|
|
169
|
+
max: Math.max(...costs),
|
|
170
|
+
sample_sd: sampleStandardDeviation(costs)
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function collectConfounders(runs) {
|
|
174
|
+
return runs
|
|
175
|
+
.filter((r) => typeof r.notes === "string" && r.notes.trim() !== "")
|
|
176
|
+
.map((r) => `[${r.arm} ${r.run_id}] ${r.notes}`);
|
|
177
|
+
}
|
|
178
|
+
function collectTreatmentLinks(runs) {
|
|
179
|
+
return runs
|
|
180
|
+
.filter((r) => r.arm === "treatment" && r.treatment_approval_link)
|
|
181
|
+
.map((r) => r.treatment_approval_link);
|
|
182
|
+
}
|
|
183
|
+
const BASE_CAVEATS = (workflowId, nControl, nTreatment) => [
|
|
184
|
+
`Measured, caveated delta on N runs of ONE workflow (workflow_id=${workflowId}; ` +
|
|
185
|
+
`n_control=${nControl}, n_treatment=${nTreatment}) under the recorded fixed conditions only — ` +
|
|
186
|
+
"NOT a billing-confirmed general savings claim.",
|
|
187
|
+
"Operator-entered billing figures, not provider-verified by this tool. The tool made no provider call, " +
|
|
188
|
+
"read no credential, and has no network access.",
|
|
189
|
+
"Run-to-run nondeterminism, prompt-caching, model/version drift, pricing changes, retries, and per-run " +
|
|
190
|
+
"billing attribution are confounders; control or record them. A delta interval that includes zero is not a saving.",
|
|
191
|
+
"This is NOT 'billing-confirmed savings' (Tier 3) and does NOT flip ValueProof.billing_confirmed_savings.claimed."
|
|
192
|
+
];
|
|
193
|
+
/** Parse the task-quality flag from a run's notes ("task: ok" | "task: failed"); "unknown" otherwise. */
|
|
194
|
+
function taskFlagFromNotes(notes) {
|
|
195
|
+
const m = /task:\s*(ok|failed)/i.exec(notes ?? "");
|
|
196
|
+
return m ? m[1].toLowerCase() : "unknown";
|
|
197
|
+
}
|
|
198
|
+
/** Resolve a model name against the price table, tolerating dated variants (e.g. -20251001). */
|
|
199
|
+
function resolvePricedModel(model) {
|
|
200
|
+
if (isKnownModel(model))
|
|
201
|
+
return model;
|
|
202
|
+
const undated = model.replace(/-\d{8}$/, "");
|
|
203
|
+
if (undated !== model && isKnownModel(undated))
|
|
204
|
+
return undated;
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
function tokenArmStatistics(arm, runs) {
|
|
208
|
+
const withTokens = runs.filter((r) => r.arm === arm && typeof r.provider_reported_usage?.input_tokens === "number");
|
|
209
|
+
const inputs = withTokens.map((r) => r.provider_reported_usage.input_tokens);
|
|
210
|
+
const outputs = withTokens
|
|
211
|
+
.map((r) => r.provider_reported_usage?.output_tokens)
|
|
212
|
+
.filter((v) => typeof v === "number");
|
|
213
|
+
const distinct = [...new Set(inputs)].sort((a, b) => a - b);
|
|
214
|
+
return {
|
|
215
|
+
arm,
|
|
216
|
+
n_with_tokens: withTokens.length,
|
|
217
|
+
input_tokens_distinct: distinct,
|
|
218
|
+
input_deterministic: withTokens.length > 0 && distinct.length === 1,
|
|
219
|
+
mean_input_tokens: inputs.length > 0 ? inputs.reduce((a, b) => a + b, 0) / inputs.length : null,
|
|
220
|
+
mean_output_tokens: outputs.length > 0 ? outputs.reduce((a, b) => a + b, 0) / outputs.length : null,
|
|
221
|
+
output_min: outputs.length > 0 ? Math.min(...outputs) : null,
|
|
222
|
+
output_max: outputs.length > 0 ? Math.max(...outputs) : null
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Compute the provider-reported token-delta section. Input tokens are deterministic for a
|
|
227
|
+
* fixed context+prompt, so one tokened run per arm pins the input delta exactly; the
|
|
228
|
+
* billed-cost verdict and its N>=3 criterion are unaffected by this section.
|
|
229
|
+
*/
|
|
230
|
+
export function computeTokenDelta(record) {
|
|
231
|
+
const control = tokenArmStatistics("control", record.runs);
|
|
232
|
+
const treatment = tokenArmStatistics("treatment", record.runs);
|
|
233
|
+
const task_quality = {
|
|
234
|
+
control: record.runs.filter((r) => r.arm === "control").map((r) => taskFlagFromNotes(r.notes)),
|
|
235
|
+
treatment: record.runs.filter((r) => r.arm === "treatment").map((r) => taskFlagFromNotes(r.notes)),
|
|
236
|
+
any_failed: record.runs.some((r) => taskFlagFromNotes(r.notes) === "failed")
|
|
237
|
+
};
|
|
238
|
+
const notes = [];
|
|
239
|
+
const base = {
|
|
240
|
+
control,
|
|
241
|
+
treatment,
|
|
242
|
+
task_quality,
|
|
243
|
+
billing_confirmed: false
|
|
244
|
+
};
|
|
245
|
+
if (task_quality.any_failed) {
|
|
246
|
+
notes.push("Token deltas SUPPRESSED: at least one run recorded 'task: failed'. A cheaper run that " +
|
|
247
|
+
"failed the task is not a saving; fix task quality before reading token deltas.");
|
|
248
|
+
return {
|
|
249
|
+
...base,
|
|
250
|
+
claim_label: selectTokenDeltaLabel("suppressed_task_failure", null),
|
|
251
|
+
status: "suppressed_task_failure",
|
|
252
|
+
input_token_delta: null,
|
|
253
|
+
input_token_delta_pct: null,
|
|
254
|
+
output_token_delta: null,
|
|
255
|
+
estimated_cost_delta: null,
|
|
256
|
+
notes
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
if (control.n_with_tokens === 0 || treatment.n_with_tokens === 0) {
|
|
260
|
+
notes.push(`Insufficient token data: provider-reported input_tokens present on ` +
|
|
261
|
+
`${control.n_with_tokens} control and ${treatment.n_with_tokens} treatment run(s); ` +
|
|
262
|
+
"need at least one tokened run per arm (input tokens are deterministic per arm, so one pins the input delta exactly).");
|
|
263
|
+
return {
|
|
264
|
+
...base,
|
|
265
|
+
claim_label: selectTokenDeltaLabel("insufficient_token_data", null),
|
|
266
|
+
status: "insufficient_token_data",
|
|
267
|
+
input_token_delta: null,
|
|
268
|
+
input_token_delta_pct: null,
|
|
269
|
+
output_token_delta: null,
|
|
270
|
+
estimated_cost_delta: null,
|
|
271
|
+
notes
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
const input_token_delta = control.mean_input_tokens - treatment.mean_input_tokens;
|
|
275
|
+
const input_token_delta_pct = control.mean_input_tokens > 0
|
|
276
|
+
? input_token_delta / control.mean_input_tokens
|
|
277
|
+
: null;
|
|
278
|
+
const output_token_delta = control.mean_output_tokens !== null && treatment.mean_output_tokens !== null
|
|
279
|
+
? control.mean_output_tokens - treatment.mean_output_tokens
|
|
280
|
+
: null;
|
|
281
|
+
if (!control.input_deterministic || !treatment.input_deterministic) {
|
|
282
|
+
notes.push("Input tokens were NOT identical across runs within an arm — fixed conditions may have drifted; " +
|
|
283
|
+
"the input delta below uses arm means and should be read with that caveat.");
|
|
284
|
+
}
|
|
285
|
+
notes.push("Output tokens vary run to run; the output delta is a mean difference — read with the per-arm min/max spread.");
|
|
286
|
+
let estimated_cost_delta = null;
|
|
287
|
+
const pricedModel = resolvePricedModel(record.fixed_conditions.model);
|
|
288
|
+
if (pricedModel !== null) {
|
|
289
|
+
const pricing = getModelPricing(pricedModel);
|
|
290
|
+
const amount = (input_token_delta * pricing.inputPerMillionUsd) / 1_000_000 +
|
|
291
|
+
((output_token_delta ?? 0) * pricing.outputPerMillionUsd) / 1_000_000;
|
|
292
|
+
// When the output delta is unavailable but the model has material output pricing,
|
|
293
|
+
// the cost estimate prices the output component as zero. Label it input-only/incomplete
|
|
294
|
+
// so a partial figure is not read as a complete cost delta.
|
|
295
|
+
const outputUnpriced = output_token_delta === null && pricing.outputPerMillionUsd > 0;
|
|
296
|
+
estimated_cost_delta = {
|
|
297
|
+
amount_usd: amount,
|
|
298
|
+
cost_source: "price_table_estimate",
|
|
299
|
+
note: outputUnpriced
|
|
300
|
+
? `Price-table ESTIMATE for ${pricedModel} — INPUT-ONLY/INCOMPLETE: output-token delta is ` +
|
|
301
|
+
"unavailable (one arm lacks provider-reported output tokens), so the output cost component " +
|
|
302
|
+
"is priced as zero. Read as a lower bound on the input side only, never a complete or " +
|
|
303
|
+
"billing-confirmed figure."
|
|
304
|
+
: `Price-table ESTIMATE for ${pricedModel} applied to the token deltas — an estimate, ` +
|
|
305
|
+
"never a billed or billing-confirmed figure."
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
notes.push(`No price-table entry for model '${record.fixed_conditions.model}'; estimated cost delta omitted ` +
|
|
310
|
+
"(token deltas stand on their own).");
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
...base,
|
|
314
|
+
claim_label: selectTokenDeltaLabel("computed", input_token_delta),
|
|
315
|
+
status: "computed",
|
|
316
|
+
input_token_delta,
|
|
317
|
+
input_token_delta_pct,
|
|
318
|
+
output_token_delta,
|
|
319
|
+
estimated_cost_delta,
|
|
320
|
+
notes
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
export function computeReport(record, generatedAt) {
|
|
324
|
+
const control = armStatistics("control", record.runs);
|
|
325
|
+
const treatment = armStatistics("treatment", record.runs);
|
|
326
|
+
const currency = record.runs.length > 0 ? record.runs[0].currency : null;
|
|
327
|
+
let mean_delta = null;
|
|
328
|
+
if (control.mean !== null && treatment.mean !== null) {
|
|
329
|
+
mean_delta = control.mean - treatment.mean;
|
|
330
|
+
}
|
|
331
|
+
let delta_standard_error = null;
|
|
332
|
+
let delta_interval = null;
|
|
333
|
+
let delta_interval_excludes_zero = false;
|
|
334
|
+
if (mean_delta !== null &&
|
|
335
|
+
control.sample_sd !== null &&
|
|
336
|
+
treatment.sample_sd !== null &&
|
|
337
|
+
control.n >= 2 &&
|
|
338
|
+
treatment.n >= 2) {
|
|
339
|
+
delta_standard_error = Math.sqrt(control.sample_sd ** 2 / control.n + treatment.sample_sd ** 2 / treatment.n);
|
|
340
|
+
const margin = BILLING_DELTA_INTERVAL_K * delta_standard_error;
|
|
341
|
+
delta_interval = { lower: mean_delta - margin, upper: mean_delta + margin };
|
|
342
|
+
delta_interval_excludes_zero =
|
|
343
|
+
(delta_interval.lower > 0 && delta_interval.upper > 0) ||
|
|
344
|
+
(delta_interval.lower < 0 && delta_interval.upper < 0);
|
|
345
|
+
}
|
|
346
|
+
const verdict_notes = [];
|
|
347
|
+
let verdict;
|
|
348
|
+
if (control.n < 3 || treatment.n < 3) {
|
|
349
|
+
verdict = "insufficient_data";
|
|
350
|
+
verdict_notes.push(BILLING_DELTA_ANECDOTE_NOTE);
|
|
351
|
+
verdict_notes.push(`Need N>=3 per arm (have n_control=${control.n}, n_treatment=${treatment.n}). ` +
|
|
352
|
+
"A single pair is an anecdote, not proof; fewer than 3 runs per arm is insufficient data.");
|
|
353
|
+
}
|
|
354
|
+
else if (!delta_interval_excludes_zero) {
|
|
355
|
+
verdict = "inconclusive";
|
|
356
|
+
verdict_notes.push("The delta-of-means interval includes zero: the measured difference is not distinguishable from " +
|
|
357
|
+
"run-to-run noise at the conservative ~2-standard-error margin. Not a saving.");
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
verdict = "measured_caveated_delta";
|
|
361
|
+
const direction = mean_delta > 0 ? "lower (a saving)" : "higher (a cost increase)";
|
|
362
|
+
verdict_notes.push(`The delta-of-means interval excludes zero: treatment is measured ${direction} than control under ` +
|
|
363
|
+
"the recorded conditions. This is a measured, caveated delta on this workflow only — not a general claim.");
|
|
364
|
+
}
|
|
365
|
+
return {
|
|
366
|
+
schema: BILLING_DELTA_REPORT_SCHEMA,
|
|
367
|
+
measurement_id: record.measurement_id,
|
|
368
|
+
workflow_id: record.workflow_id,
|
|
369
|
+
generated_at: generatedAt,
|
|
370
|
+
currency,
|
|
371
|
+
fixed_conditions: record.fixed_conditions,
|
|
372
|
+
control,
|
|
373
|
+
treatment,
|
|
374
|
+
mean_delta,
|
|
375
|
+
delta_standard_error,
|
|
376
|
+
delta_interval,
|
|
377
|
+
delta_interval_excludes_zero,
|
|
378
|
+
criterion: BILLING_DELTA_CRITERION,
|
|
379
|
+
verdict,
|
|
380
|
+
verdict_notes,
|
|
381
|
+
claim_scope: `workflow_id=${record.workflow_id} under fixed conditions ` +
|
|
382
|
+
`(model=${record.fixed_conditions.model}, model_version=${record.fixed_conditions.model_version}, ` +
|
|
383
|
+
`window=${record.fixed_conditions.window}); n_control=${control.n}, n_treatment=${treatment.n}. ` +
|
|
384
|
+
"The measured workflow and conditions ONLY.",
|
|
385
|
+
not_a_general_claim: true,
|
|
386
|
+
billing_confirmed: false,
|
|
387
|
+
operator_entered: true,
|
|
388
|
+
confounders: collectConfounders(record.runs),
|
|
389
|
+
caveats: BASE_CAVEATS(record.workflow_id, control.n, treatment.n),
|
|
390
|
+
treatment_provenance_links: collectTreatmentLinks(record.runs),
|
|
391
|
+
token_delta: computeTokenDelta(record)
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
//# sourceMappingURL=billing-delta-record.js.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { AgentTrace } from "./types.js";
|
|
2
|
+
import type { UsageMetadata } from "./usage-metadata.js";
|
|
3
|
+
import type { TraceFingerprint } from "./trace-fingerprint.js";
|
|
4
|
+
export interface CaptureAdapter {
|
|
5
|
+
/** Stable identifier for this adapter. Used in provenance and logging. */
|
|
6
|
+
readonly id: string;
|
|
7
|
+
/**
|
|
8
|
+
* Normalize a raw input (session file path, event stream, export file, etc.)
|
|
9
|
+
* into a CapturedRun containing a normalized AgentTrace, UsageMetadata,
|
|
10
|
+
* and provenance information.
|
|
11
|
+
*
|
|
12
|
+
* The adapter is responsible for:
|
|
13
|
+
* - Setting source: "real_captured" only when the trace comes from an agent
|
|
14
|
+
* runtime session with identifiable workflow context (see real_captured semantics).
|
|
15
|
+
* - Attaching UsageMetadata at normalization time, not re-deriving it downstream.
|
|
16
|
+
* - Setting provenance.captureAdapter to this adapter's id.
|
|
17
|
+
*/
|
|
18
|
+
normalize(input: CaptureAdapterInput): Promise<CapturedRun>;
|
|
19
|
+
}
|
|
20
|
+
export interface CaptureAdapterInput {
|
|
21
|
+
/** Path to the source artifact (session JSONL file, export file, stdout dump). */
|
|
22
|
+
sourcePath: string;
|
|
23
|
+
/** Human-readable label for CLI output. */
|
|
24
|
+
label?: string;
|
|
25
|
+
/** Additional adapter-specific options. */
|
|
26
|
+
options?: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
export interface CapturedRun {
|
|
29
|
+
/** The normalized agent trace. */
|
|
30
|
+
trace: AgentTrace;
|
|
31
|
+
/** Usage metadata attached at capture time. Never re-derived downstream. */
|
|
32
|
+
usage: UsageMetadata;
|
|
33
|
+
/** Provenance record for the capture. */
|
|
34
|
+
provenance: CaptureProvenance;
|
|
35
|
+
}
|
|
36
|
+
export interface CaptureSubagentProvenance {
|
|
37
|
+
/** Unique agent identifier from the subagent JSONL entries. */
|
|
38
|
+
agentId: string;
|
|
39
|
+
/** Total user + assistant entries processed from this subagent. */
|
|
40
|
+
entryCount: number;
|
|
41
|
+
/** Agent type from meta.json, if present. */
|
|
42
|
+
agentType?: string;
|
|
43
|
+
/** Human-readable description from meta.json, if present. */
|
|
44
|
+
description?: string;
|
|
45
|
+
/** Tool use ID linking this subagent to the parent session tool call, from meta.json if present. */
|
|
46
|
+
toolUseId?: string;
|
|
47
|
+
}
|
|
48
|
+
export interface CaptureProvenance {
|
|
49
|
+
/** The adapter that produced this run. */
|
|
50
|
+
captureAdapter: string;
|
|
51
|
+
/** Stable source path or identifier. */
|
|
52
|
+
sourcePath: string;
|
|
53
|
+
/** ISO 8601 timestamp of when the capture was performed. */
|
|
54
|
+
capturedAt: string;
|
|
55
|
+
/** Claude Code session ID, OpenAI Agents run ID, or equivalent. Null if unknown. */
|
|
56
|
+
sessionId: string | null;
|
|
57
|
+
/** Any warnings or limitations the adapter encountered. */
|
|
58
|
+
warnings: string[];
|
|
59
|
+
/** Any limitations that apply to the captured run. */
|
|
60
|
+
limitations: string[];
|
|
61
|
+
/** Provenance records for each included subagent, when --include-subagents is used. */
|
|
62
|
+
subagents?: CaptureSubagentProvenance[];
|
|
63
|
+
/**
|
|
64
|
+
* Non-sensitive per-run distinctness fingerprint (a one-way SHA-256 DIGEST over the
|
|
65
|
+
* canonical normalized trace content — NEVER raw messages/prompts/tool-output).
|
|
66
|
+
*
|
|
67
|
+
* Lets future dogfood records cite a verifiable per-run distinctness proof so that
|
|
68
|
+
* genuinely-different captured sessions are distinguishable from re-captures of the
|
|
69
|
+
* same session (preventing real_captured N over-counting). When an adapter cannot
|
|
70
|
+
* produce a fingerprint, this is omitted and distinctness must be reported as
|
|
71
|
+
* `not_verified` — never counted as distinct. See `src/core/trace-fingerprint.ts`.
|
|
72
|
+
*/
|
|
73
|
+
traceFingerprint?: TraceFingerprint;
|
|
74
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface ParsedRunCommand {
|
|
2
|
+
executable: string;
|
|
3
|
+
args: string[];
|
|
4
|
+
}
|
|
5
|
+
export interface LocalCommandRun {
|
|
6
|
+
runId: string;
|
|
7
|
+
command: ParsedRunCommand;
|
|
8
|
+
stdout: string;
|
|
9
|
+
stderr: string;
|
|
10
|
+
rawOutput: string;
|
|
11
|
+
exitCode: number;
|
|
12
|
+
signal: NodeJS.Signals | null;
|
|
13
|
+
pid: number | null;
|
|
14
|
+
startedAt: string;
|
|
15
|
+
endedAt: string;
|
|
16
|
+
durationMs: number;
|
|
17
|
+
}
|
|
18
|
+
export interface PersistedLocalCommandRun {
|
|
19
|
+
run: LocalCommandRun;
|
|
20
|
+
outputDirectory: string;
|
|
21
|
+
stdoutPath: string;
|
|
22
|
+
stderrPath: string;
|
|
23
|
+
rawOutputPath: string;
|
|
24
|
+
metadataPath: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function parseRunCommand(commandParts: string[]): ParsedRunCommand;
|
|
27
|
+
export declare function createRunId(now?: Date): string;
|
|
28
|
+
export declare function executeLocalCommand(command: ParsedRunCommand, runId?: string): Promise<LocalCommandRun>;
|
|
29
|
+
export declare function persistLocalCommandRun(run: LocalCommandRun, runsRoot?: string): Promise<PersistedLocalCommandRun>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
export function parseRunCommand(commandParts) {
|
|
6
|
+
const [executable, ...args] = commandParts;
|
|
7
|
+
if (!executable) {
|
|
8
|
+
throw new Error("Usage: compaction run -- <command> [args...]");
|
|
9
|
+
}
|
|
10
|
+
return { executable, args };
|
|
11
|
+
}
|
|
12
|
+
export function createRunId(now = new Date()) {
|
|
13
|
+
const timestamp = now.toISOString().replace(/[:.]/g, "-");
|
|
14
|
+
return `run-${timestamp}-${randomUUID().slice(0, 8)}`;
|
|
15
|
+
}
|
|
16
|
+
export async function executeLocalCommand(command, runId = createRunId()) {
|
|
17
|
+
const startMs = Date.now();
|
|
18
|
+
const startedAt = new Date(startMs).toISOString();
|
|
19
|
+
let stdout = "";
|
|
20
|
+
let stderr = "";
|
|
21
|
+
let rawOutput = "";
|
|
22
|
+
return new Promise((resolve) => {
|
|
23
|
+
const child = spawn(command.executable, command.args, {
|
|
24
|
+
shell: false,
|
|
25
|
+
windowsHide: true
|
|
26
|
+
});
|
|
27
|
+
const pid = child.pid ?? null;
|
|
28
|
+
let spawnError;
|
|
29
|
+
child.stdout.setEncoding("utf8");
|
|
30
|
+
child.stderr.setEncoding("utf8");
|
|
31
|
+
child.stdout.on("data", (chunk) => {
|
|
32
|
+
stdout += chunk;
|
|
33
|
+
rawOutput += chunk;
|
|
34
|
+
});
|
|
35
|
+
child.stderr.on("data", (chunk) => {
|
|
36
|
+
stderr += chunk;
|
|
37
|
+
rawOutput += chunk;
|
|
38
|
+
});
|
|
39
|
+
child.on("error", (error) => {
|
|
40
|
+
spawnError = error;
|
|
41
|
+
const errorMessage = stderr.length === 0 ? error.message : `\n${error.message}`;
|
|
42
|
+
stderr += errorMessage;
|
|
43
|
+
rawOutput += errorMessage;
|
|
44
|
+
});
|
|
45
|
+
child.on("close", (exitCode, signal) => {
|
|
46
|
+
const endMs = Date.now();
|
|
47
|
+
const resolvedExitCode = spawnError ? 127 : exitCode ?? 1;
|
|
48
|
+
resolve({
|
|
49
|
+
runId,
|
|
50
|
+
command,
|
|
51
|
+
stdout,
|
|
52
|
+
stderr,
|
|
53
|
+
rawOutput,
|
|
54
|
+
exitCode: resolvedExitCode,
|
|
55
|
+
signal,
|
|
56
|
+
pid,
|
|
57
|
+
startedAt,
|
|
58
|
+
endedAt: new Date(endMs).toISOString(),
|
|
59
|
+
durationMs: Math.max(0, endMs - startMs)
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
export async function persistLocalCommandRun(run, runsRoot = path.join(".compaction", "runs")) {
|
|
65
|
+
const outputDirectory = path.join(runsRoot, run.runId);
|
|
66
|
+
const stdoutPath = path.join(outputDirectory, "stdout.txt");
|
|
67
|
+
const stderrPath = path.join(outputDirectory, "stderr.txt");
|
|
68
|
+
const rawOutputPath = path.join(outputDirectory, "raw-output.txt");
|
|
69
|
+
const metadataPath = path.join(outputDirectory, "metadata.json");
|
|
70
|
+
await mkdir(outputDirectory, { recursive: true });
|
|
71
|
+
await writeFile(stdoutPath, run.stdout, "utf8");
|
|
72
|
+
await writeFile(stderrPath, run.stderr, "utf8");
|
|
73
|
+
await writeFile(rawOutputPath, run.rawOutput, "utf8");
|
|
74
|
+
await writeFile(metadataPath, `${JSON.stringify({
|
|
75
|
+
runId: run.runId,
|
|
76
|
+
command: run.command,
|
|
77
|
+
exitCode: run.exitCode,
|
|
78
|
+
signal: run.signal,
|
|
79
|
+
pid: run.pid,
|
|
80
|
+
startedAt: run.startedAt,
|
|
81
|
+
endedAt: run.endedAt,
|
|
82
|
+
durationMs: run.durationMs,
|
|
83
|
+
stdoutBytes: Buffer.byteLength(run.stdout, "utf8"),
|
|
84
|
+
stderrBytes: Buffer.byteLength(run.stderr, "utf8"),
|
|
85
|
+
rawOutputBytes: Buffer.byteLength(run.rawOutput, "utf8")
|
|
86
|
+
}, null, 2)}\n`, "utf8");
|
|
87
|
+
return {
|
|
88
|
+
run,
|
|
89
|
+
outputDirectory,
|
|
90
|
+
stdoutPath,
|
|
91
|
+
stderrPath,
|
|
92
|
+
rawOutputPath,
|
|
93
|
+
metadataPath
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=command-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function contentHash(content: string): string;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CostEstimate, TokenEstimate } from "./types.js";
|
|
2
|
+
export interface CacheTokenCounts {
|
|
3
|
+
cacheReadTokens?: number;
|
|
4
|
+
cacheCreationTokens?: number;
|
|
5
|
+
}
|
|
6
|
+
export declare function calculateCost(model: string, estimate: TokenEstimate, cache?: CacheTokenCounts): CostEstimate;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getCacheCreationPricePerMillion, getCacheReadPricePerMillion, getModelPricing } from "./pricing.js";
|
|
2
|
+
const TOKENS_PER_MILLION = 1_000_000;
|
|
3
|
+
export function calculateCost(model, estimate, cache) {
|
|
4
|
+
const pricing = getModelPricing(model);
|
|
5
|
+
const inputCostUsd = (estimate.inputTokens / TOKENS_PER_MILLION) * pricing.inputPerMillionUsd;
|
|
6
|
+
const outputCostUsd = (estimate.outputTokens / TOKENS_PER_MILLION) * pricing.outputPerMillionUsd;
|
|
7
|
+
const cacheReadTokens = cache?.cacheReadTokens ?? 0;
|
|
8
|
+
const cacheCreationTokens = cache?.cacheCreationTokens ?? 0;
|
|
9
|
+
const cacheReadCostUsd = (cacheReadTokens / TOKENS_PER_MILLION) * getCacheReadPricePerMillion(model);
|
|
10
|
+
const cacheCreationCostUsd = (cacheCreationTokens / TOKENS_PER_MILLION) * getCacheCreationPricePerMillion(model);
|
|
11
|
+
return {
|
|
12
|
+
model,
|
|
13
|
+
inputCostUsd,
|
|
14
|
+
outputCostUsd,
|
|
15
|
+
cacheReadCostUsd: cacheReadTokens > 0 ? cacheReadCostUsd : undefined,
|
|
16
|
+
cacheCreationCostUsd: cacheCreationTokens > 0 ? cacheCreationCostUsd : undefined,
|
|
17
|
+
totalCostUsd: inputCostUsd + outputCostUsd + cacheReadCostUsd + cacheCreationCostUsd
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=cost-calculator.js.map
|