@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.
Files changed (143) hide show
  1. package/LICENSE +663 -0
  2. package/README.md +319 -0
  3. package/dist/cli/commands/adapter.d.ts +2 -0
  4. package/dist/cli/commands/adapter.js +49 -0
  5. package/dist/cli/commands/aggregate.d.ts +2 -0
  6. package/dist/cli/commands/aggregate.js +60 -0
  7. package/dist/cli/commands/analyze.d.ts +2 -0
  8. package/dist/cli/commands/analyze.js +55 -0
  9. package/dist/cli/commands/apply-context.d.ts +12 -0
  10. package/dist/cli/commands/apply-context.js +80 -0
  11. package/dist/cli/commands/apply.d.ts +2 -0
  12. package/dist/cli/commands/apply.js +65 -0
  13. package/dist/cli/commands/approve.d.ts +2 -0
  14. package/dist/cli/commands/approve.js +55 -0
  15. package/dist/cli/commands/audit.d.ts +2 -0
  16. package/dist/cli/commands/audit.js +63 -0
  17. package/dist/cli/commands/billing-delta.d.ts +2 -0
  18. package/dist/cli/commands/billing-delta.js +246 -0
  19. package/dist/cli/commands/capture-claude-code.d.ts +19 -0
  20. package/dist/cli/commands/capture-claude-code.js +198 -0
  21. package/dist/cli/commands/capture-provider-usage.d.ts +35 -0
  22. package/dist/cli/commands/capture-provider-usage.js +113 -0
  23. package/dist/cli/commands/capture.d.ts +2 -0
  24. package/dist/cli/commands/capture.js +76 -0
  25. package/dist/cli/commands/compact.d.ts +2 -0
  26. package/dist/cli/commands/compact.js +206 -0
  27. package/dist/cli/commands/eval.d.ts +2 -0
  28. package/dist/cli/commands/eval.js +66 -0
  29. package/dist/cli/commands/export-session-seed.d.ts +11 -0
  30. package/dist/cli/commands/export-session-seed.js +42 -0
  31. package/dist/cli/commands/feedback.d.ts +2 -0
  32. package/dist/cli/commands/feedback.js +233 -0
  33. package/dist/cli/commands/import.d.ts +2 -0
  34. package/dist/cli/commands/import.js +60 -0
  35. package/dist/cli/commands/init.d.ts +2 -0
  36. package/dist/cli/commands/init.js +216 -0
  37. package/dist/cli/commands/integrations.d.ts +2 -0
  38. package/dist/cli/commands/integrations.js +29 -0
  39. package/dist/cli/commands/launch-treatment-session.d.ts +14 -0
  40. package/dist/cli/commands/launch-treatment-session.js +50 -0
  41. package/dist/cli/commands/optimize.d.ts +2 -0
  42. package/dist/cli/commands/optimize.js +33 -0
  43. package/dist/cli/commands/recommend.d.ts +4 -0
  44. package/dist/cli/commands/recommend.js +66 -0
  45. package/dist/cli/commands/review.d.ts +2 -0
  46. package/dist/cli/commands/review.js +60 -0
  47. package/dist/cli/commands/run.d.ts +2 -0
  48. package/dist/cli/commands/run.js +49 -0
  49. package/dist/cli/commands/spend.d.ts +2 -0
  50. package/dist/cli/commands/spend.js +24 -0
  51. package/dist/cli/commands/summary.d.ts +2 -0
  52. package/dist/cli/commands/summary.js +19 -0
  53. package/dist/cli/engine-degrade.d.ts +40 -0
  54. package/dist/cli/engine-degrade.js +59 -0
  55. package/dist/cli/index.d.ts +2 -0
  56. package/dist/cli/index.js +59 -0
  57. package/dist/core/adapter-validation.d.ts +32 -0
  58. package/dist/core/adapter-validation.js +185 -0
  59. package/dist/core/adapters/claude-code-adapter.d.ts +51 -0
  60. package/dist/core/adapters/claude-code-adapter.js +539 -0
  61. package/dist/core/adapters/claude-code-discovery.d.ts +40 -0
  62. package/dist/core/adapters/claude-code-discovery.js +100 -0
  63. package/dist/core/aggregate-format.d.ts +62 -0
  64. package/dist/core/aggregate-format.js +349 -0
  65. package/dist/core/api-client/client.d.ts +43 -0
  66. package/dist/core/api-client/client.js +143 -0
  67. package/dist/core/api-client/config.d.ts +36 -0
  68. package/dist/core/api-client/config.js +44 -0
  69. package/dist/core/api-client/index.d.ts +13 -0
  70. package/dist/core/api-client/index.js +14 -0
  71. package/dist/core/api-client/payload.d.ts +93 -0
  72. package/dist/core/api-client/payload.js +180 -0
  73. package/dist/core/api-client/types.d.ts +95 -0
  74. package/dist/core/api-client/types.js +21 -0
  75. package/dist/core/artifact-writer.d.ts +2 -0
  76. package/dist/core/artifact-writer.js +14 -0
  77. package/dist/core/audit-report.d.ts +91 -0
  78. package/dist/core/audit-report.js +422 -0
  79. package/dist/core/billing-delta/billing-delta-record.d.ts +290 -0
  80. package/dist/core/billing-delta/billing-delta-record.js +394 -0
  81. package/dist/core/capture-adapter.d.ts +74 -0
  82. package/dist/core/capture-adapter.js +2 -0
  83. package/dist/core/command-runner.d.ts +29 -0
  84. package/dist/core/command-runner.js +96 -0
  85. package/dist/core/content-hash.d.ts +1 -0
  86. package/dist/core/content-hash.js +5 -0
  87. package/dist/core/cost-calculator.d.ts +6 -0
  88. package/dist/core/cost-calculator.js +20 -0
  89. package/dist/core/feedback-bundle.d.ts +159 -0
  90. package/dist/core/feedback-bundle.js +406 -0
  91. package/dist/core/openai-agents-capture.d.ts +66 -0
  92. package/dist/core/openai-agents-capture.js +475 -0
  93. package/dist/core/policy-types.d.ts +85 -0
  94. package/dist/core/policy-types.js +29 -0
  95. package/dist/core/pricing.d.ts +23 -0
  96. package/dist/core/pricing.js +55 -0
  97. package/dist/core/provider-usage/credential-redaction.d.ts +23 -0
  98. package/dist/core/provider-usage/credential-redaction.js +60 -0
  99. package/dist/core/provider-usage/provider-usage-adapter.d.ts +65 -0
  100. package/dist/core/provider-usage/provider-usage-adapter.js +147 -0
  101. package/dist/core/provider-usage/provider-usage-client.d.ts +89 -0
  102. package/dist/core/provider-usage/provider-usage-client.js +96 -0
  103. package/dist/core/report-generator.d.ts +7 -0
  104. package/dist/core/report-generator.js +275 -0
  105. package/dist/core/run-aggregator.d.ts +50 -0
  106. package/dist/core/run-aggregator.js +251 -0
  107. package/dist/core/run-labels.d.ts +52 -0
  108. package/dist/core/run-labels.js +80 -0
  109. package/dist/core/run-trace-converter.d.ts +3 -0
  110. package/dist/core/run-trace-converter.js +94 -0
  111. package/dist/core/safety-report.d.ts +52 -0
  112. package/dist/core/safety-report.js +440 -0
  113. package/dist/core/savings.d.ts +22 -0
  114. package/dist/core/savings.js +36 -0
  115. package/dist/core/session-aggregate.d.ts +211 -0
  116. package/dist/core/session-aggregate.js +490 -0
  117. package/dist/core/skill-injection-detector.d.ts +51 -0
  118. package/dist/core/skill-injection-detector.js +146 -0
  119. package/dist/core/spend-attribution.d.ts +169 -0
  120. package/dist/core/spend-attribution.js +611 -0
  121. package/dist/core/standalone-capsule-provenance.d.ts +4 -0
  122. package/dist/core/standalone-capsule-provenance.js +62 -0
  123. package/dist/core/state-capsule.d.ts +3 -0
  124. package/dist/core/state-capsule.js +147 -0
  125. package/dist/core/token-accounting.d.ts +62 -0
  126. package/dist/core/token-accounting.js +81 -0
  127. package/dist/core/token-estimator.d.ts +3 -0
  128. package/dist/core/token-estimator.js +18 -0
  129. package/dist/core/trace-adapters.d.ts +53 -0
  130. package/dist/core/trace-adapters.js +714 -0
  131. package/dist/core/trace-fingerprint.d.ts +79 -0
  132. package/dist/core/trace-fingerprint.js +102 -0
  133. package/dist/core/trace-intake.d.ts +41 -0
  134. package/dist/core/trace-intake.js +164 -0
  135. package/dist/core/trace-parser.d.ts +203 -0
  136. package/dist/core/trace-parser.js +143 -0
  137. package/dist/core/types.d.ts +238 -0
  138. package/dist/core/types.js +2 -0
  139. package/dist/core/usage-metadata.d.ts +51 -0
  140. package/dist/core/usage-metadata.js +122 -0
  141. package/dist/core/waste-detector.d.ts +8 -0
  142. package/dist/core/waste-detector.js +58 -0
  143. package/package.json +90 -0
@@ -0,0 +1,62 @@
1
+ import { access, readFile } from "node:fs/promises";
2
+ async function fileExists(filePath) {
3
+ try {
4
+ await access(filePath);
5
+ return true;
6
+ }
7
+ catch {
8
+ return false;
9
+ }
10
+ }
11
+ function hasCompleteEntry(entry) {
12
+ return (typeof entry.source_trace_id === "string" &&
13
+ entry.source_trace_id.length > 0 &&
14
+ typeof entry.source_message_id === "string" &&
15
+ entry.source_message_id.length > 0 &&
16
+ typeof entry.source_pointer === "string" &&
17
+ entry.source_pointer.length > 0 &&
18
+ typeof entry.source_hash === "string" &&
19
+ entry.source_hash.length > 0 &&
20
+ entry.source_hash_algorithm === "sha256" &&
21
+ entry.source_recoverable === true &&
22
+ typeof entry.source_recovery_path === "string" &&
23
+ entry.source_recovery_path.length > 0);
24
+ }
25
+ export function statusFromStateCapsule(capsule) {
26
+ const entries = capsule.provenance_entries ?? [];
27
+ const topLevelComplete = hasCompleteEntry({
28
+ source_trace_id: capsule.source_trace_id,
29
+ source_message_id: capsule.source_message_id,
30
+ source_pointer: capsule.source_pointer,
31
+ source_hash: capsule.source_hash,
32
+ source_hash_algorithm: capsule.source_hash_algorithm,
33
+ source_recoverable: capsule.source_recoverable ?? "unknown",
34
+ source_recovery_path: capsule.source_recovery_path,
35
+ original_payload_token_count: capsule.original_payload_token_count,
36
+ capsule_token_count: capsule.capsule_token_count
37
+ });
38
+ if (topLevelComplete && entries.length > 0 && entries.every(hasCompleteEntry)) {
39
+ return "complete";
40
+ }
41
+ if (topLevelComplete || entries.length > 0 || capsule.source_recoverable === true || capsule.source_recoverable === "unknown") {
42
+ return "partial";
43
+ }
44
+ return "missing";
45
+ }
46
+ export async function readStandaloneCapsuleProvenanceStatus(capsulePath) {
47
+ if (!capsulePath)
48
+ return "missing";
49
+ if (!(await fileExists(capsulePath)))
50
+ return "missing";
51
+ try {
52
+ const capsule = JSON.parse(await readFile(capsulePath, "utf8"));
53
+ if (typeof capsule !== "object" || capsule === null || Array.isArray(capsule)) {
54
+ return "unknown";
55
+ }
56
+ return statusFromStateCapsule(capsule);
57
+ }
58
+ catch {
59
+ return "unknown";
60
+ }
61
+ }
62
+ //# sourceMappingURL=standalone-capsule-provenance.js.map
@@ -0,0 +1,3 @@
1
+ import type { AgentTrace, StateCapsule, WasteFinding } from "./types.js";
2
+ import type { PolicyMiddlewareStateCapsule } from "./policy-types.js";
3
+ export declare function createStateCapsule(trace: AgentTrace, findings?: WasteFinding[], policyCapsules?: PolicyMiddlewareStateCapsule[]): StateCapsule;
@@ -0,0 +1,147 @@
1
+ import { createHash } from "node:crypto";
2
+ import { estimateTextTokens } from "./token-estimator.js";
3
+ function firstMessageByRole(trace, role) {
4
+ return trace.messages.find((message) => message.role === role);
5
+ }
6
+ function lastMessageByRole(trace, role) {
7
+ return [...trace.messages].reverse().find((message) => message.role === role);
8
+ }
9
+ function compactSentence(content) {
10
+ const normalized = content.trim().replace(/\s+/g, " ");
11
+ return normalized.length > 220 ? `${normalized.slice(0, 217)}...` : normalized;
12
+ }
13
+ function sourcePreview(content) {
14
+ const normalized = content.trim().replace(/\s+/g, " ");
15
+ return normalized.length > 160 ? `${normalized.slice(0, 157)}...` : normalized;
16
+ }
17
+ function contentHash(content) {
18
+ return createHash("sha256").update(content).digest("hex");
19
+ }
20
+ function findVerificationMessages(trace) {
21
+ return trace.messages.filter((message) => {
22
+ if (message.role !== "tool") {
23
+ return false;
24
+ }
25
+ const text = message.content.toLowerCase();
26
+ return message.toolName === "test" || text.includes(" passed") || text.includes(" failed") || text.includes("timeout");
27
+ });
28
+ }
29
+ function sourcePointerText(traceId, messageId, messageIndex, sourceHash) {
30
+ return `trace=${traceId} message=${messageId} index=${messageIndex} sha256=${sourceHash}`;
31
+ }
32
+ function provenanceFromFinding(trace, finding) {
33
+ const sourceMessageId = finding.messageIds[0];
34
+ const sourceIndex = trace.messages.findIndex((message) => message.id === sourceMessageId);
35
+ const sourceMessage = sourceIndex >= 0 ? trace.messages[sourceIndex] : undefined;
36
+ if (!sourceMessage) {
37
+ return {
38
+ source_trace_id: trace.id,
39
+ source_message_id: sourceMessageId,
40
+ source_pointer: `trace=${trace.id} message=${sourceMessageId}`,
41
+ source_recoverable: "unknown",
42
+ original_payload_token_count: finding.estimatedTokens
43
+ };
44
+ }
45
+ const hash = contentHash(sourceMessage.content);
46
+ return {
47
+ source_trace_id: trace.id,
48
+ source_message_id: sourceMessage.id,
49
+ source_pointer: sourcePointerText(trace.id, sourceMessage.id, sourceIndex, hash),
50
+ source_hash: hash,
51
+ source_hash_algorithm: "sha256",
52
+ source_excerpt_preview: sourcePreview(sourceMessage.content),
53
+ source_recoverable: true,
54
+ source_recovery_path: `messages[${sourceIndex}]`,
55
+ original_payload_token_count: estimateTextTokens(sourceMessage.content),
56
+ compacted_message_ids: finding.messageIds.slice(1)
57
+ };
58
+ }
59
+ function provenanceFromPolicyCapsule(capsule) {
60
+ return {
61
+ source_trace_id: capsule.sourcePointer.traceId,
62
+ source_message_id: capsule.sourcePointer.messageId,
63
+ source_pointer: sourcePointerText(capsule.sourcePointer.traceId, capsule.sourcePointer.messageId, capsule.sourcePointer.messageIndex, capsule.sourcePointer.contentSha256),
64
+ source_hash: capsule.sourcePointer.contentSha256,
65
+ source_hash_algorithm: "sha256",
66
+ source_excerpt_preview: capsule.sourceExcerptPreview,
67
+ source_recoverable: true,
68
+ source_recovery_path: `messages[${capsule.sourcePointer.messageIndex}]`,
69
+ original_payload_token_count: capsule.originalPayloadTokenCount,
70
+ capsule_token_count: estimateTextTokens(capsule.text),
71
+ compacted_message_ids: capsule.compactedMessageIds,
72
+ replacement_mode: capsule.replacementMode
73
+ };
74
+ }
75
+ function firstDefinedProvenance(entries) {
76
+ const first = entries[0];
77
+ if (!first) {
78
+ return {
79
+ source_recoverable: "unknown",
80
+ unsupported_additions_check: "No source provenance was available to check for unsupported additions.",
81
+ provenance_limitations: ["No compacted source pointer was available; provenance is marked unknown rather than invented."]
82
+ };
83
+ }
84
+ return {
85
+ source_trace_id: first.source_trace_id,
86
+ source_message_id: first.source_message_id,
87
+ source_pointer: first.source_pointer,
88
+ source_hash: first.source_hash,
89
+ source_hash_algorithm: first.source_hash_algorithm,
90
+ source_excerpt_preview: first.source_excerpt_preview,
91
+ source_recoverable: first.source_recoverable,
92
+ source_recovery_path: first.source_recovery_path,
93
+ original_payload_token_count: first.original_payload_token_count,
94
+ capsule_token_count: first.capsule_token_count,
95
+ unsupported_additions_check: first.source_recoverable === true
96
+ ? "State capsule facts are limited to deterministic trace-derived facts and source-pointer metadata."
97
+ : "Source recoverability is unknown; unsupported additions cannot be fully checked.",
98
+ provenance_limitations: [
99
+ "Short previews are intentionally truncated and are not a substitute for reviewing the source pointer locally.",
100
+ "State Capsule v0 records deterministic provenance; it does not perform semantic replay."
101
+ ]
102
+ };
103
+ }
104
+ export function createStateCapsule(trace, findings = [], policyCapsules = []) {
105
+ const retainedFacts = [];
106
+ const openQuestions = [];
107
+ const safetyNotes = [];
108
+ const userGoal = firstMessageByRole(trace, "user");
109
+ if (userGoal) {
110
+ retainedFacts.push(`User goal: ${compactSentence(userGoal.content)}`);
111
+ }
112
+ for (const message of findVerificationMessages(trace)) {
113
+ retainedFacts.push(`Verification evidence from ${message.id}: ${compactSentence(message.content)}`);
114
+ }
115
+ const finalAssistantMessage = lastMessageByRole(trace, "assistant");
116
+ if (finalAssistantMessage) {
117
+ retainedFacts.push(`Latest assistant summary: ${compactSentence(finalAssistantMessage.content)}`);
118
+ }
119
+ const repeatedFindings = findings.filter((finding) => finding.category === "repeated_tool_output");
120
+ for (const finding of repeatedFindings) {
121
+ retainedFacts.push(`Repeated output preserved by pointer: ${finding.summary}`);
122
+ }
123
+ if (retainedFacts.length === 0) {
124
+ openQuestions.push("No durable facts were identified by the conservative state capsule heuristic.");
125
+ }
126
+ if (findings.length === 0 && policyCapsules.length === 0) {
127
+ safetyNotes.push("No waste findings were detected; the capsule is informational only.");
128
+ }
129
+ else {
130
+ safetyNotes.push("State capsule was generated from local trace content only; no model provider was called.");
131
+ safetyNotes.push("Review source pointers before relying on compacted context for critical decisions.");
132
+ }
133
+ const provenanceEntries = policyCapsules.length > 0 ? policyCapsules.map(provenanceFromPolicyCapsule) : repeatedFindings.map((finding) => provenanceFromFinding(trace, finding));
134
+ const capsuleTokens = estimateTextTokens([...retainedFacts, ...openQuestions, ...safetyNotes].join("\n"));
135
+ const entriesWithCapsuleCounts = provenanceEntries.map((entry) => ({ ...entry, capsule_token_count: entry.capsule_token_count ?? capsuleTokens }));
136
+ return {
137
+ traceId: trace.id,
138
+ ...firstDefinedProvenance(entriesWithCapsuleCounts),
139
+ provenance_entries: entriesWithCapsuleCounts,
140
+ preserved_commitments: retainedFacts,
141
+ omitted_or_unknown_commitments: openQuestions.length > 0 ? openQuestions : ["No additional commitments were inferred beyond the retained trace-derived facts."],
142
+ retainedFacts,
143
+ openQuestions,
144
+ safetyNotes
145
+ };
146
+ }
147
+ //# sourceMappingURL=state-capsule.js.map
@@ -0,0 +1,62 @@
1
+ import type { PolicyMiddlewareResult } from "../engine/policy-middleware.js";
2
+ import type { AgentTrace } from "./types.js";
3
+ /**
4
+ * Token & cost accounting surface (Strong-MVP Track D).
5
+ *
6
+ * Produces ONE honestly-labeled before/after token + cost summary that a developer can read to
7
+ * answer: tokens used, input vs output, how much was reduced, estimated $ saved, and — critically —
8
+ * whether each figure was MEASURED (provider-reported) or ESTIMATED (local chars/4).
9
+ *
10
+ * Label discipline (mvp-capability-matrix.md rows 2/3/10, README evidence ladder):
11
+ * - `provider-reported` is used ONLY when the trace/run carries provider usage metadata.
12
+ * - otherwise every figure is `local estimate` (chars/4 trace tokens + price-table cost).
13
+ * - NEVER `billing-confirmed`. Cost is a price-table estimate, not a billed figure.
14
+ *
15
+ * Output tokens BEFORE come from the trace (assistant messages). Output tokens AFTER are equal to
16
+ * the before value for the supported policies (compaction targets non-assistant input spans —
17
+ * repeated tool output / skill injections — so assistant output is unchanged); this is stated
18
+ * explicitly rather than implied. When provider usage data is absent, output figures are labeled
19
+ * local estimates like the rest.
20
+ */
21
+ export type TokenFigureSource = "provider_reported" | "local_estimate";
22
+ export interface TokenAccountingFigure {
23
+ value: number;
24
+ /** Honest source label for THIS figure. */
25
+ source: TokenFigureSource;
26
+ }
27
+ export interface TokenAccounting {
28
+ model: string;
29
+ /** Per-figure honest source label (provider_reported only when usage metadata is present). */
30
+ measured: boolean;
31
+ input_tokens_before: TokenAccountingFigure;
32
+ input_tokens_after: TokenAccountingFigure;
33
+ output_tokens_before: TokenAccountingFigure;
34
+ output_tokens_after: TokenAccountingFigure;
35
+ input_tokens_saved: number;
36
+ percent_input_reduction: number;
37
+ estimated_cost_before_usd: number;
38
+ estimated_cost_after_usd: number;
39
+ estimated_saving_per_run_usd: number;
40
+ /** Visible pricing assumptions for the cost figures (price-table, NOT billed). */
41
+ pricing_assumptions: string[];
42
+ /** Whether the required token/cost evidence is present (Track C gate input). */
43
+ evidence_present: boolean;
44
+ limitations: string[];
45
+ }
46
+ /**
47
+ * Build the token/cost accounting summary from an in-memory policy result.
48
+ *
49
+ * HONESTY: the numeric token figures here come from `policyResult.tokenEstimateBefore/After`,
50
+ * which are ALWAYS the LOCAL chars/4 estimator (`estimateTraceTokens`) over the trace messages —
51
+ * regardless of `trace.source`. `AgentTrace` carries no provider usage metadata (real provider
52
+ * usage lives in `UsageMetadata`, NOT in the trace), and a `provider_usage` capture has no messages
53
+ * to estimate from, so labeling its figures `provider_reported` would emit a FALSE
54
+ * `0 (provider-reported)` over an empty local estimate. A figure is therefore labeled
55
+ * `provider_reported` ONLY when it genuinely derives from provider usage metadata — which the
56
+ * current `AgentTrace` shape never carries — so every figure here is a `local_estimate`. When a
57
+ * provider-usage path is added that attaches real usage numbers, source those figures from it and
58
+ * flip the label per-figure; until then, never claim provider-reported over a local estimate.
59
+ */
60
+ export declare function buildTokenAccounting(trace: AgentTrace, policyResult: PolicyMiddlewareResult): TokenAccounting;
61
+ /** One-line-per-figure human rendering for stdout / markdown (each line carries its source label). */
62
+ export declare function describeTokenAccounting(acc: TokenAccounting): string[];
@@ -0,0 +1,81 @@
1
+ import { calculateCost } from "./cost-calculator.js";
2
+ function pct(before, after) {
3
+ if (before <= 0) {
4
+ return 0;
5
+ }
6
+ return Number((((before - after) / before) * 100).toFixed(2));
7
+ }
8
+ /**
9
+ * Build the token/cost accounting summary from an in-memory policy result.
10
+ *
11
+ * HONESTY: the numeric token figures here come from `policyResult.tokenEstimateBefore/After`,
12
+ * which are ALWAYS the LOCAL chars/4 estimator (`estimateTraceTokens`) over the trace messages —
13
+ * regardless of `trace.source`. `AgentTrace` carries no provider usage metadata (real provider
14
+ * usage lives in `UsageMetadata`, NOT in the trace), and a `provider_usage` capture has no messages
15
+ * to estimate from, so labeling its figures `provider_reported` would emit a FALSE
16
+ * `0 (provider-reported)` over an empty local estimate. A figure is therefore labeled
17
+ * `provider_reported` ONLY when it genuinely derives from provider usage metadata — which the
18
+ * current `AgentTrace` shape never carries — so every figure here is a `local_estimate`. When a
19
+ * provider-usage path is added that attaches real usage numbers, source those figures from it and
20
+ * flip the label per-figure; until then, never claim provider-reported over a local estimate.
21
+ */
22
+ export function buildTokenAccounting(trace, policyResult) {
23
+ // The figures below come from the local chars/4 estimator (`estimateTraceTokens` via
24
+ // `policyResult.tokenEstimateBefore/After`), NOT from provider usage metadata — which the
25
+ // `AgentTrace` shape does not carry. So these are local estimates even on a `provider_usage`
26
+ // trace (whose capture has no messages to estimate from). Fail honest: label them local_estimate.
27
+ const measured = false;
28
+ const source = "local_estimate";
29
+ const inputBefore = policyResult.tokenEstimateBefore.inputTokens;
30
+ const inputAfter = policyResult.tokenEstimateAfter.inputTokens;
31
+ const outputBefore = policyResult.tokenEstimateBefore.outputTokens;
32
+ // Supported policies do not touch assistant output spans, so output-after equals output-before.
33
+ const outputAfter = policyResult.tokenEstimateAfter.outputTokens;
34
+ const costBefore = calculateCost(trace.model, policyResult.tokenEstimateBefore).totalCostUsd;
35
+ const costAfter = calculateCost(trace.model, policyResult.tokenEstimateAfter).totalCostUsd;
36
+ const evidencePresent = Number.isFinite(inputBefore) && Number.isFinite(inputAfter) && inputBefore >= 0;
37
+ return {
38
+ model: trace.model,
39
+ measured,
40
+ input_tokens_before: { value: inputBefore, source },
41
+ input_tokens_after: { value: inputAfter, source },
42
+ output_tokens_before: { value: outputBefore, source },
43
+ output_tokens_after: { value: outputAfter, source },
44
+ input_tokens_saved: Math.max(0, inputBefore - inputAfter),
45
+ percent_input_reduction: pct(inputBefore, inputAfter),
46
+ estimated_cost_before_usd: Number(costBefore.toFixed(6)),
47
+ estimated_cost_after_usd: Number(costAfter.toFixed(6)),
48
+ estimated_saving_per_run_usd: Number(Math.max(0, costBefore - costAfter).toFixed(6)),
49
+ pricing_assumptions: [
50
+ `price-table estimate for ${trace.model} (NOT billing-confirmed, NOT a billed figure)`,
51
+ measured
52
+ ? "token counts are provider-reported; cost is a local price-table estimate over those counts"
53
+ : "token counts are local estimates (chars/4); cost is a local price-table estimate over those counts"
54
+ ],
55
+ evidence_present: evidencePresent,
56
+ limitations: [
57
+ measured
58
+ ? "Input/output token counts are provider-reported; cost remains a price-table ESTIMATE, never billing-confirmed."
59
+ : "Input/output token counts are LOCAL ESTIMATES (chars/4); cost is a price-table ESTIMATE, never provider-reported or billing-confirmed.",
60
+ "Output tokens after compaction equal output tokens before: the supported policies compact only non-assistant input spans (repeated tool output / skill injections), so assistant output is unchanged.",
61
+ "No realized/applied savings is claimed; these are per-run estimates only."
62
+ ]
63
+ };
64
+ }
65
+ /** One-line-per-figure human rendering for stdout / markdown (each line carries its source label). */
66
+ export function describeTokenAccounting(acc) {
67
+ const label = (f) => `${f.value} (${f.source === "provider_reported" ? "provider-reported" : "local estimate (chars/4)"})`;
68
+ return [
69
+ `model: ${acc.model}`,
70
+ `input tokens before: ${label(acc.input_tokens_before)}`,
71
+ `input tokens after: ${label(acc.input_tokens_after)}`,
72
+ `output tokens before: ${label(acc.output_tokens_before)}`,
73
+ `output tokens after: ${label(acc.output_tokens_after)}`,
74
+ `input tokens saved: ${acc.input_tokens_saved} (${acc.percent_input_reduction}% input reduction)`,
75
+ `estimated cost before: $${acc.estimated_cost_before_usd.toFixed(6)} (price-table estimate, NOT billed)`,
76
+ `estimated cost after: $${acc.estimated_cost_after_usd.toFixed(6)} (price-table estimate, NOT billed)`,
77
+ `estimated saving per run: $${acc.estimated_saving_per_run_usd.toFixed(6)} (estimate, NOT billing-confirmed, NOT realized)`,
78
+ ...acc.pricing_assumptions.map((a) => `pricing assumption: ${a}`)
79
+ ];
80
+ }
81
+ //# sourceMappingURL=token-accounting.js.map
@@ -0,0 +1,3 @@
1
+ import type { AgentTrace, TokenEstimate } from "./types.js";
2
+ export declare function estimateTextTokens(text: string): number;
3
+ export declare function estimateTraceTokens(trace: AgentTrace): TokenEstimate;
@@ -0,0 +1,18 @@
1
+ const APPROX_CHARS_PER_TOKEN = 4;
2
+ export function estimateTextTokens(text) {
3
+ return Math.max(1, Math.ceil(text.length / APPROX_CHARS_PER_TOKEN));
4
+ }
5
+ export function estimateTraceTokens(trace) {
6
+ const inputTokens = trace.messages
7
+ .filter((message) => message.role !== "assistant")
8
+ .reduce((sum, message) => sum + estimateTextTokens(message.content), 0);
9
+ const outputTokens = trace.messages
10
+ .filter((message) => message.role === "assistant")
11
+ .reduce((sum, message) => sum + estimateTextTokens(message.content), 0);
12
+ return {
13
+ inputTokens,
14
+ outputTokens,
15
+ totalTokens: inputTokens + outputTokens
16
+ };
17
+ }
18
+ //# sourceMappingURL=token-estimator.js.map
@@ -0,0 +1,53 @@
1
+ import type { AgentTrace } from "./types.js";
2
+ export type TraceAdapterSource = "agent-trace" | "messages" | "codex-exec-jsonl" | "unknown";
3
+ export type TraceAdapterStatus = "pass" | "warn" | "fail";
4
+ export interface TraceAdapterOptions {
5
+ rawContent: string;
6
+ operatorExport?: boolean;
7
+ }
8
+ export interface TraceAdapterResult {
9
+ status: TraceAdapterStatus;
10
+ trace: AgentTrace | null;
11
+ warnings: string[];
12
+ failures: string[];
13
+ normalization_steps: string[];
14
+ skipped_fields: string[];
15
+ source_metadata: Record<string, unknown>;
16
+ }
17
+ export interface TraceAdapter {
18
+ id: TraceAdapterSource;
19
+ displayName: string;
20
+ description: string;
21
+ supportedSource: TraceAdapterSource;
22
+ limitations: string[];
23
+ canHandle(input: unknown): boolean;
24
+ normalize(input: unknown, options: TraceAdapterOptions): TraceAdapterResult;
25
+ }
26
+ export interface TraceAdapterDetectionResult {
27
+ status: TraceAdapterStatus;
28
+ requested_source: TraceAdapterSource;
29
+ adapter_id: TraceAdapterSource | null;
30
+ supported_format_detected: boolean;
31
+ warnings: string[];
32
+ failures: string[];
33
+ normalization_steps: string[];
34
+ skipped_fields: string[];
35
+ source_metadata: Record<string, unknown>;
36
+ }
37
+ export declare const agentTraceAdapter: TraceAdapter;
38
+ export declare const messagesAdapter: TraceAdapter;
39
+ export declare const codexExecJsonlAdapter: TraceAdapter;
40
+ export declare const unknownAdapter: TraceAdapter;
41
+ export declare const traceAdapters: TraceAdapter[];
42
+ export interface TraceAdapterRawParseResult {
43
+ input: unknown | null;
44
+ failures: string[];
45
+ normalization_steps: string[];
46
+ source_metadata: Record<string, unknown>;
47
+ }
48
+ export declare function parseTraceAdapterRawContent(rawContent: string, source: TraceAdapterSource): TraceAdapterRawParseResult;
49
+ export declare function listTraceAdapters(): TraceAdapter[];
50
+ export declare function listTraceAdapterSources(): TraceAdapterSource[];
51
+ export declare function assertTraceAdapterSource(source: string): asserts source is TraceAdapterSource;
52
+ export declare function getTraceAdapter(source: TraceAdapterSource): TraceAdapter;
53
+ export declare function detectTraceAdapter(input: unknown, requestedSource?: TraceAdapterSource): TraceAdapterDetectionResult;