@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,475 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { executeLocalCommand, parseRunCommand } from "./command-runner.js";
|
|
5
|
+
import { CURRENT_AGENT_TRACE_ARTIFACT_VERSION } from "./trace-parser.js";
|
|
6
|
+
import { createUsageMetadata, describeCostMetadata, describeTokenMetadata, missingUsageMetadata } from "./usage-metadata.js";
|
|
7
|
+
const integrationLimitations = [
|
|
8
|
+
"This spike is local-first and captures only local command output that exposes OpenAI Agents SDK-style trace/span JSON events.",
|
|
9
|
+
"It does not scrape ChatGPT app traces and does not use undocumented APIs.",
|
|
10
|
+
"It does not install an OpenAI Agents SDK tracing processor into arbitrary user processes; native SDK processors/export files are future work.",
|
|
11
|
+
"Token and cost metadata are reported only when present in captured events; missing values are not estimated or invented.",
|
|
12
|
+
"No hosted service, dashboard, database, auth, billing, model routing, provider switching, or auto-optimization mode is included."
|
|
13
|
+
];
|
|
14
|
+
function stableHash(value) {
|
|
15
|
+
return createHash("sha256").update(value).digest("hex").slice(0, 12);
|
|
16
|
+
}
|
|
17
|
+
export function createOpenAIAgentsCaptureId(now = new Date()) {
|
|
18
|
+
return `capture-${now.toISOString().replace(/[:.]/g, "-")}-${randomUUID().slice(0, 8)}`;
|
|
19
|
+
}
|
|
20
|
+
function toRecord(value) {
|
|
21
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
22
|
+
}
|
|
23
|
+
function stringValue(value) {
|
|
24
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
25
|
+
}
|
|
26
|
+
function objectText(value) {
|
|
27
|
+
if (typeof value === "string") {
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
const record = toRecord(value);
|
|
31
|
+
if (!record) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const content = record.content;
|
|
35
|
+
if (typeof content === "string") {
|
|
36
|
+
return content;
|
|
37
|
+
}
|
|
38
|
+
const text = record.text;
|
|
39
|
+
return typeof text === "string" ? text : null;
|
|
40
|
+
}
|
|
41
|
+
function eventTimestamp(event, fallback) {
|
|
42
|
+
const timestamp = stringValue(event.timestamp) ?? stringValue(event.started_at) ?? stringValue(event.startedAt) ?? stringValue(event.created_at);
|
|
43
|
+
if (!timestamp) {
|
|
44
|
+
return fallback;
|
|
45
|
+
}
|
|
46
|
+
const parsed = new Date(timestamp);
|
|
47
|
+
return Number.isNaN(parsed.getTime()) ? fallback : parsed.toISOString();
|
|
48
|
+
}
|
|
49
|
+
function parseJsonEvents(rawOutput) {
|
|
50
|
+
return rawOutput
|
|
51
|
+
.split(/\r?\n/)
|
|
52
|
+
.map((line, index) => ({ line: line.trim(), lineNumber: index + 1 }))
|
|
53
|
+
.filter(({ line }) => line.startsWith("{") && line.endsWith("}"))
|
|
54
|
+
.flatMap(({ line, lineNumber }) => {
|
|
55
|
+
try {
|
|
56
|
+
const parsed = JSON.parse(line);
|
|
57
|
+
const record = toRecord(parsed);
|
|
58
|
+
return record ? [{ value: record, lineNumber }] : [];
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function spanData(event) {
|
|
66
|
+
return toRecord(event.span_data) ?? toRecord(event.spanData) ?? event;
|
|
67
|
+
}
|
|
68
|
+
function eventKind(event) {
|
|
69
|
+
const data = spanData(event);
|
|
70
|
+
return stringValue(data.type) ?? stringValue(event.type) ?? stringValue(event.object) ?? "event";
|
|
71
|
+
}
|
|
72
|
+
function extractTraceId(events, captureId) {
|
|
73
|
+
for (const { value } of events) {
|
|
74
|
+
const id = stringValue(value.trace_id) ?? stringValue(value.traceId);
|
|
75
|
+
if (id)
|
|
76
|
+
return id;
|
|
77
|
+
const type = stringValue(value.type) ?? stringValue(value.object);
|
|
78
|
+
const ownId = stringValue(value.id);
|
|
79
|
+
if (ownId && type?.includes("trace"))
|
|
80
|
+
return ownId;
|
|
81
|
+
}
|
|
82
|
+
return `trace_openai_agents_${stableHash(captureId)}`;
|
|
83
|
+
}
|
|
84
|
+
function extractModel(events) {
|
|
85
|
+
for (const { value } of events) {
|
|
86
|
+
const data = spanData(value);
|
|
87
|
+
const model = stringValue(data.model) ?? stringValue(value.model);
|
|
88
|
+
if (model)
|
|
89
|
+
return model;
|
|
90
|
+
}
|
|
91
|
+
return "openai-agents-sdk-unknown-model";
|
|
92
|
+
}
|
|
93
|
+
function numericValue(value) {
|
|
94
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
95
|
+
}
|
|
96
|
+
function usageRecord(event) {
|
|
97
|
+
const data = spanData(event);
|
|
98
|
+
return toRecord(data.usage) ?? toRecord(event.usage);
|
|
99
|
+
}
|
|
100
|
+
function usageNumber(event, keys) {
|
|
101
|
+
const data = spanData(event);
|
|
102
|
+
const usage = usageRecord(event);
|
|
103
|
+
for (const source of [usage, data, event]) {
|
|
104
|
+
if (!source)
|
|
105
|
+
continue;
|
|
106
|
+
for (const key of keys) {
|
|
107
|
+
const value = numericValue(source[key]);
|
|
108
|
+
if (value !== undefined)
|
|
109
|
+
return value;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
function eventCostIsProviderReported(event) {
|
|
115
|
+
const data = spanData(event);
|
|
116
|
+
const usage = usageRecord(event);
|
|
117
|
+
return [usage, data, event].some((source) => source ? ["cost_usd", "costUsd", "total_cost_usd", "totalCostUsd", "cost"].some((key) => numericValue(source[key]) !== undefined) : false);
|
|
118
|
+
}
|
|
119
|
+
function stringMetadataValue(event, keys) {
|
|
120
|
+
const data = spanData(event);
|
|
121
|
+
const usage = usageRecord(event);
|
|
122
|
+
for (const source of [usage, data, event]) {
|
|
123
|
+
if (!source)
|
|
124
|
+
continue;
|
|
125
|
+
for (const key of keys) {
|
|
126
|
+
const value = stringValue(source[key]);
|
|
127
|
+
if (value)
|
|
128
|
+
return value;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
function aggregateUsageMetadata(events, fallbackModel) {
|
|
134
|
+
if (events.length === 0) {
|
|
135
|
+
return missingUsageMetadata({ model: fallbackModel, provider: "openai-agents", limitations: ["No provider events were captured, so token metadata is unknown."] });
|
|
136
|
+
}
|
|
137
|
+
let inputTokens = 0;
|
|
138
|
+
let outputTokens = 0;
|
|
139
|
+
let totalTokens = 0;
|
|
140
|
+
let hasInput = false;
|
|
141
|
+
let hasOutput = false;
|
|
142
|
+
let hasTotal = false;
|
|
143
|
+
let providerReportedCost = false;
|
|
144
|
+
let syntheticDemo = false;
|
|
145
|
+
let model;
|
|
146
|
+
let provider;
|
|
147
|
+
let currency;
|
|
148
|
+
for (const { value } of events) {
|
|
149
|
+
const input = usageNumber(value, ["input_tokens", "inputTokens", "prompt_tokens", "promptTokens"]);
|
|
150
|
+
const output = usageNumber(value, ["output_tokens", "outputTokens", "completion_tokens", "completionTokens"]);
|
|
151
|
+
const total = usageNumber(value, ["total_tokens", "totalTokens"]);
|
|
152
|
+
if (input !== undefined) {
|
|
153
|
+
inputTokens += input;
|
|
154
|
+
hasInput = true;
|
|
155
|
+
}
|
|
156
|
+
if (output !== undefined) {
|
|
157
|
+
outputTokens += output;
|
|
158
|
+
hasOutput = true;
|
|
159
|
+
}
|
|
160
|
+
if (total !== undefined) {
|
|
161
|
+
totalTokens += total;
|
|
162
|
+
hasTotal = true;
|
|
163
|
+
}
|
|
164
|
+
providerReportedCost = providerReportedCost || eventCostIsProviderReported(value);
|
|
165
|
+
syntheticDemo = syntheticDemo || eventUsageIsSyntheticDemo(value);
|
|
166
|
+
model = model ?? stringMetadataValue(value, ["model"]);
|
|
167
|
+
provider = provider ?? stringMetadataValue(value, ["provider"]);
|
|
168
|
+
currency = currency ?? stringMetadataValue(value, ["currency"]);
|
|
169
|
+
}
|
|
170
|
+
const hasAnyTokens = hasInput || hasOutput || hasTotal;
|
|
171
|
+
if (!hasAnyTokens) {
|
|
172
|
+
return missingUsageMetadata({ model: model ?? fallbackModel, provider: provider ?? "openai-agents", limitations: ["Captured events did not include provider token usage; values were not invented."] });
|
|
173
|
+
}
|
|
174
|
+
return createUsageMetadata({
|
|
175
|
+
inputTokens: hasInput ? inputTokens : undefined,
|
|
176
|
+
outputTokens: hasOutput ? outputTokens : undefined,
|
|
177
|
+
totalTokens: hasTotal ? totalTokens : undefined,
|
|
178
|
+
providerReportedTokens: true,
|
|
179
|
+
estimatedTokens: false,
|
|
180
|
+
syntheticDemo,
|
|
181
|
+
providerReportedCost,
|
|
182
|
+
currency,
|
|
183
|
+
model: model ?? fallbackModel,
|
|
184
|
+
provider: provider ?? "openai-agents",
|
|
185
|
+
limitations: [
|
|
186
|
+
syntheticDemo
|
|
187
|
+
? "Synthetic-demo token usage was emitted by the safe local demo workflow and is not production billing data."
|
|
188
|
+
: providerReportedCost
|
|
189
|
+
? "Provider-reported token and cost metadata were captured from local events."
|
|
190
|
+
: "Provider-reported tokens were captured; cost remains an estimate unless provider billing data is present."
|
|
191
|
+
]
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
function eventUsageIsSyntheticDemo(event) {
|
|
195
|
+
const data = spanData(event);
|
|
196
|
+
const usage = toRecord(data.usage) ?? toRecord(event.usage);
|
|
197
|
+
return [usage, data, event].some((source) => source
|
|
198
|
+
? source.synthetic_demo === true ||
|
|
199
|
+
source.syntheticDemo === true ||
|
|
200
|
+
source.demo === true ||
|
|
201
|
+
source.metadata_status === "synthetic-demo" ||
|
|
202
|
+
source.metadataStatus === "synthetic-demo" ||
|
|
203
|
+
source.source === "synthetic-demo"
|
|
204
|
+
: false);
|
|
205
|
+
}
|
|
206
|
+
function hasTokenMetadata(event) {
|
|
207
|
+
const data = spanData(event);
|
|
208
|
+
const usage = toRecord(data.usage) ?? toRecord(event.usage);
|
|
209
|
+
return Boolean(usage) || data.input_tokens !== undefined || data.output_tokens !== undefined || data.total_tokens !== undefined;
|
|
210
|
+
}
|
|
211
|
+
function hasCostMetadata(event) {
|
|
212
|
+
const data = spanData(event);
|
|
213
|
+
return eventUsageIsSyntheticDemo(event) || data.cost_usd !== undefined || data.costUsd !== undefined || data.cost !== undefined || event.cost_usd !== undefined;
|
|
214
|
+
}
|
|
215
|
+
function metadataStatus(events, predicate) {
|
|
216
|
+
if (events.length === 0)
|
|
217
|
+
return "unknown";
|
|
218
|
+
const matchingEvents = events.filter(({ value }) => predicate(value));
|
|
219
|
+
const count = matchingEvents.length;
|
|
220
|
+
if (count === 0)
|
|
221
|
+
return "missing";
|
|
222
|
+
if (matchingEvents.every(({ value }) => eventUsageIsSyntheticDemo(value)))
|
|
223
|
+
return "synthetic-demo";
|
|
224
|
+
return count === events.length ? "present" : "partial";
|
|
225
|
+
}
|
|
226
|
+
function addMessage(messages, role, content, timestamp, metadata, toolName) {
|
|
227
|
+
messages.push({
|
|
228
|
+
id: `msg_${(messages.length + 1).toString().padStart(3, "0")}`,
|
|
229
|
+
role,
|
|
230
|
+
content,
|
|
231
|
+
timestamp,
|
|
232
|
+
...(toolName ? { toolName } : {}),
|
|
233
|
+
...(metadata ? { metadata } : {})
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
function appendGenerationMessages(messages, event, timestamp, lineNumber) {
|
|
237
|
+
const data = spanData(event);
|
|
238
|
+
const inputs = Array.isArray(data.input) ? data.input : Array.isArray(data.inputs) ? data.inputs : [];
|
|
239
|
+
const outputs = Array.isArray(data.output) ? data.output : Array.isArray(data.outputs) ? data.outputs : [];
|
|
240
|
+
for (const input of inputs) {
|
|
241
|
+
const text = objectText(input);
|
|
242
|
+
const role = toRecord(input)?.role === "system" ? "system" : "user";
|
|
243
|
+
if (text)
|
|
244
|
+
addMessage(messages, role, text, timestamp, { sourceLine: lineNumber, eventType: "generation_input" });
|
|
245
|
+
}
|
|
246
|
+
for (const output of outputs) {
|
|
247
|
+
const text = objectText(output);
|
|
248
|
+
if (text)
|
|
249
|
+
addMessage(messages, "assistant", text, timestamp, { sourceLine: lineNumber, eventType: "generation_output" });
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function appendFunctionMessages(messages, event, timestamp, lineNumber) {
|
|
253
|
+
const data = spanData(event);
|
|
254
|
+
const name = stringValue(data.name) ?? stringValue(data.tool_name) ?? stringValue(data.toolName) ?? "openai_agents_tool";
|
|
255
|
+
const input = objectText(data.input) ?? objectText(data.arguments);
|
|
256
|
+
const output = objectText(data.output) ?? objectText(data.result);
|
|
257
|
+
if (input) {
|
|
258
|
+
addMessage(messages, "assistant", `Tool call ${name}: ${input}`, timestamp, { sourceLine: lineNumber, eventType: "tool_call" });
|
|
259
|
+
}
|
|
260
|
+
if (output) {
|
|
261
|
+
addMessage(messages, "tool", output, timestamp, { sourceLine: lineNumber, eventType: "tool_output" }, name);
|
|
262
|
+
return 1;
|
|
263
|
+
}
|
|
264
|
+
return 0;
|
|
265
|
+
}
|
|
266
|
+
export function normalizeOpenAIAgentsEvents(params) {
|
|
267
|
+
const generatedAt = params.generatedAt ?? new Date().toISOString();
|
|
268
|
+
const events = parseJsonEvents(params.rawOutput);
|
|
269
|
+
const warnings = [];
|
|
270
|
+
const messages = [];
|
|
271
|
+
let toolOutputsCaptured = 0;
|
|
272
|
+
if (events.length === 0) {
|
|
273
|
+
warnings.push("No OpenAI Agents SDK-style JSON trace/span events were found in command output.");
|
|
274
|
+
}
|
|
275
|
+
addMessage(messages, "system", "OpenAI Agents SDK capture spike normalized local SDK-style trace/span events. ChatGPT app traces are not captured or scraped.", params.commandRun?.startedAt ?? generatedAt, { integration: "openai-agents", captureMode: "command wrapper" });
|
|
276
|
+
for (const event of events) {
|
|
277
|
+
const timestamp = eventTimestamp(event.value, params.commandRun?.startedAt ?? generatedAt);
|
|
278
|
+
const kind = eventKind(event.value);
|
|
279
|
+
if (kind === "generation" || kind === "response" || kind === "llm") {
|
|
280
|
+
appendGenerationMessages(messages, event.value, timestamp, event.lineNumber);
|
|
281
|
+
}
|
|
282
|
+
else if (kind === "function" || kind === "tool" || kind === "tool_call" || kind === "function_call") {
|
|
283
|
+
toolOutputsCaptured += appendFunctionMessages(messages, event.value, timestamp, event.lineNumber);
|
|
284
|
+
}
|
|
285
|
+
else if (kind.includes("trace")) {
|
|
286
|
+
const workflow = stringValue(event.value.workflow_name) ?? stringValue(event.value.name) ?? "OpenAI Agents SDK workflow";
|
|
287
|
+
const sourceProvenance = toRecord(event.value.source_provenance) ?? toRecord(event.value.sourceProvenance);
|
|
288
|
+
addMessage(messages, "system", `Trace started: ${workflow}`, timestamp, {
|
|
289
|
+
sourceLine: event.lineNumber,
|
|
290
|
+
eventType: kind,
|
|
291
|
+
...(sourceProvenance ? { sourceProvenance } : {})
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (messages.length === 1 && params.commandRun) {
|
|
296
|
+
addMessage(messages, "tool", params.commandRun.rawOutput, params.commandRun.endedAt, { eventType: "raw_command_output" }, "command.output");
|
|
297
|
+
toolOutputsCaptured += params.commandRun.rawOutput.length > 0 ? 1 : 0;
|
|
298
|
+
}
|
|
299
|
+
const tokenMetadataStatus = metadataStatus(events, hasTokenMetadata);
|
|
300
|
+
const costMetadataStatus = metadataStatus(events, hasCostMetadata);
|
|
301
|
+
const model = extractModel(events);
|
|
302
|
+
const usageMetadata = aggregateUsageMetadata(events, model);
|
|
303
|
+
const pricingAssumptions = usageMetadata.pricing_assumption ? [usageMetadata.pricing_assumption] : [];
|
|
304
|
+
if (tokenMetadataStatus === "missing")
|
|
305
|
+
warnings.push("Token metadata was missing from captured events and was not invented.");
|
|
306
|
+
if (costMetadataStatus === "missing")
|
|
307
|
+
warnings.push("Cost metadata was missing from captured events and was not invented.");
|
|
308
|
+
return {
|
|
309
|
+
trace: {
|
|
310
|
+
id: extractTraceId(events, params.captureId),
|
|
311
|
+
title: "OpenAI Agents SDK captured trace",
|
|
312
|
+
artifactVersion: CURRENT_AGENT_TRACE_ARTIFACT_VERSION,
|
|
313
|
+
source: "local_command",
|
|
314
|
+
createdAt: params.commandRun?.startedAt ?? generatedAt,
|
|
315
|
+
generatedAt,
|
|
316
|
+
model,
|
|
317
|
+
command: params.commandRun
|
|
318
|
+
? { command: params.commandRun.command.executable, args: params.commandRun.command.args, cwd: process.cwd() }
|
|
319
|
+
: { command: "local-export", args: [], cwd: process.cwd() },
|
|
320
|
+
durationMs: params.commandRun?.durationMs ?? 0,
|
|
321
|
+
exitCode: params.commandRun?.exitCode ?? 0,
|
|
322
|
+
messages
|
|
323
|
+
},
|
|
324
|
+
eventsCaptured: events.length,
|
|
325
|
+
messagesCaptured: messages.filter((message) => ["system", "user", "assistant"].includes(message.role)).length,
|
|
326
|
+
toolOutputsCaptured,
|
|
327
|
+
tokenMetadataStatus,
|
|
328
|
+
costMetadataStatus,
|
|
329
|
+
usageMetadata,
|
|
330
|
+
spendConfidence: usageMetadata.cost_confidence,
|
|
331
|
+
pricingAssumptions,
|
|
332
|
+
warnings,
|
|
333
|
+
limitations: integrationLimitations
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
function formatReportMarkdown(report) {
|
|
337
|
+
const list = (values, empty) => (values.length === 0 ? `- ${empty}` : values.map((value) => `- ${value}`).join("\n"));
|
|
338
|
+
return [
|
|
339
|
+
"# OpenAI Agents SDK Capture Report",
|
|
340
|
+
"",
|
|
341
|
+
"## Summary",
|
|
342
|
+
"",
|
|
343
|
+
`- Status: ${report.status}`,
|
|
344
|
+
`- Capture ID: ${report.capture_id}`,
|
|
345
|
+
`- Integration: OpenAI Agents SDK`,
|
|
346
|
+
`- Output trace ID: ${report.output_trace_id ?? "none"}`,
|
|
347
|
+
"",
|
|
348
|
+
"## Capture Mode",
|
|
349
|
+
"",
|
|
350
|
+
`- ${report.capture_mode}`,
|
|
351
|
+
report.command_run ? `- Command: ${[report.command_run.command, ...report.command_run.args].join(" ")}` : "- Command: none",
|
|
352
|
+
"",
|
|
353
|
+
"## Trace Coverage",
|
|
354
|
+
"",
|
|
355
|
+
`- Events captured: ${report.events_captured}`,
|
|
356
|
+
`- Messages captured: ${report.messages_captured}`,
|
|
357
|
+
`- Tool outputs captured: ${report.tool_outputs_captured}`,
|
|
358
|
+
"",
|
|
359
|
+
"## Token and Cost Metadata",
|
|
360
|
+
"",
|
|
361
|
+
`- Token metadata: ${report.token_metadata_status}`,
|
|
362
|
+
`- Cost metadata: ${report.cost_metadata_status}`,
|
|
363
|
+
`- Spend confidence: ${report.spend_confidence}`,
|
|
364
|
+
...describeTokenMetadata(report.usage_metadata).map((line) => `- ${line}`),
|
|
365
|
+
...describeCostMetadata(report.usage_metadata).map((line) => `- ${line}`),
|
|
366
|
+
"",
|
|
367
|
+
"## Warnings",
|
|
368
|
+
"",
|
|
369
|
+
list(report.warnings, "No warnings."),
|
|
370
|
+
"",
|
|
371
|
+
"## Failures",
|
|
372
|
+
"",
|
|
373
|
+
list(report.failures, "No failures."),
|
|
374
|
+
"",
|
|
375
|
+
"## Recommended Next Command",
|
|
376
|
+
"",
|
|
377
|
+
report.recommended_next_command ? `\`${report.recommended_next_command}\`` : "No next command is recommended until capture succeeds.",
|
|
378
|
+
"",
|
|
379
|
+
"## Limitations",
|
|
380
|
+
"",
|
|
381
|
+
list(report.limitations, "No limitations recorded."),
|
|
382
|
+
""
|
|
383
|
+
].join("\n");
|
|
384
|
+
}
|
|
385
|
+
function terminalSummary(report, tracePath) {
|
|
386
|
+
return [
|
|
387
|
+
"integration: OpenAI Agents SDK",
|
|
388
|
+
`capture mode: ${report.capture_mode}`,
|
|
389
|
+
`status: ${report.status}`,
|
|
390
|
+
`events captured: ${report.events_captured}`,
|
|
391
|
+
`messages captured: ${report.messages_captured}`,
|
|
392
|
+
`tool outputs captured: ${report.tool_outputs_captured}`,
|
|
393
|
+
`token metadata: ${report.token_metadata_status}`,
|
|
394
|
+
`cost metadata: ${report.cost_metadata_status}`,
|
|
395
|
+
`spend confidence: ${report.spend_confidence}`,
|
|
396
|
+
...describeTokenMetadata(report.usage_metadata),
|
|
397
|
+
...describeCostMetadata(report.usage_metadata),
|
|
398
|
+
`normalized trace path: ${tracePath}`,
|
|
399
|
+
`recommended next command: ${report.recommended_next_command ?? "none"}`,
|
|
400
|
+
`limitations: ${report.limitations.join("; ")}`
|
|
401
|
+
].join("\n");
|
|
402
|
+
}
|
|
403
|
+
async function writeArtifacts(outputDirectory, trace, report) {
|
|
404
|
+
await mkdir(outputDirectory, { recursive: true });
|
|
405
|
+
const capturedTracePath = path.join(outputDirectory, "captured-trace.json");
|
|
406
|
+
const captureReportJsonPath = path.join(outputDirectory, "capture-report.json");
|
|
407
|
+
const captureReportMarkdownPath = path.join(outputDirectory, "capture-report.md");
|
|
408
|
+
await writeFile(capturedTracePath, `${JSON.stringify(trace, null, 2)}\n`, "utf8");
|
|
409
|
+
await writeFile(captureReportJsonPath, `${JSON.stringify(report, null, 2)}\n`, "utf8");
|
|
410
|
+
await writeFile(captureReportMarkdownPath, formatReportMarkdown(report), "utf8");
|
|
411
|
+
return { report, trace, outputDirectory, paths: { capturedTracePath, captureReportJsonPath, captureReportMarkdownPath }, terminalSummary: terminalSummary(report, capturedTracePath) };
|
|
412
|
+
}
|
|
413
|
+
export async function captureOpenAIAgentsCommand(commandParts, outRoot) {
|
|
414
|
+
const command = parseRunCommand(commandParts);
|
|
415
|
+
const captureId = createOpenAIAgentsCaptureId();
|
|
416
|
+
const run = await executeLocalCommand(command, captureId);
|
|
417
|
+
const normalized = normalizeOpenAIAgentsEvents({ captureId, commandRun: run, rawOutput: run.rawOutput, generatedAt: run.endedAt });
|
|
418
|
+
const outputDirectory = path.join(outRoot, captureId);
|
|
419
|
+
const capturedTracePath = path.join(outputDirectory, "captured-trace.json");
|
|
420
|
+
const failures = run.exitCode === 0 ? [] : [`Source command exited with code ${run.exitCode}.`];
|
|
421
|
+
const status = failures.length > 0 ? "fail" : normalized.warnings.length > 0 ? "warn" : "pass";
|
|
422
|
+
const report = {
|
|
423
|
+
capture_id: captureId,
|
|
424
|
+
generated_at: run.endedAt,
|
|
425
|
+
integration: "openai-agents",
|
|
426
|
+
capture_mode: "command wrapper",
|
|
427
|
+
status,
|
|
428
|
+
command_run: { command: run.command.executable, args: run.command.args, exit_code: run.exitCode, duration_ms: run.durationMs },
|
|
429
|
+
output_trace_id: normalized.trace.id,
|
|
430
|
+
events_captured: normalized.eventsCaptured,
|
|
431
|
+
messages_captured: normalized.messagesCaptured,
|
|
432
|
+
tool_outputs_captured: normalized.toolOutputsCaptured,
|
|
433
|
+
token_metadata_status: normalized.tokenMetadataStatus,
|
|
434
|
+
cost_metadata_status: normalized.costMetadataStatus,
|
|
435
|
+
usage_metadata: normalized.usageMetadata,
|
|
436
|
+
spend_confidence: normalized.spendConfidence,
|
|
437
|
+
pricing_assumptions: normalized.pricingAssumptions,
|
|
438
|
+
warnings: normalized.warnings,
|
|
439
|
+
failures,
|
|
440
|
+
limitations: normalized.limitations,
|
|
441
|
+
recommended_next_command: `compaction analyze ${capturedTracePath}`
|
|
442
|
+
};
|
|
443
|
+
return writeArtifacts(outputDirectory, normalized.trace, report);
|
|
444
|
+
}
|
|
445
|
+
export async function captureOpenAIAgentsExport(inputPath, outRoot) {
|
|
446
|
+
const captureId = createOpenAIAgentsCaptureId();
|
|
447
|
+
const rawOutput = await readFile(inputPath, "utf8");
|
|
448
|
+
const generatedAt = new Date().toISOString();
|
|
449
|
+
const normalized = normalizeOpenAIAgentsEvents({ captureId, rawOutput, generatedAt });
|
|
450
|
+
const outputDirectory = path.join(outRoot, captureId);
|
|
451
|
+
const capturedTracePath = path.join(outputDirectory, "captured-trace.json");
|
|
452
|
+
const status = normalized.warnings.length > 0 ? "warn" : "pass";
|
|
453
|
+
const report = {
|
|
454
|
+
capture_id: captureId,
|
|
455
|
+
generated_at: generatedAt,
|
|
456
|
+
integration: "openai-agents",
|
|
457
|
+
capture_mode: "local export",
|
|
458
|
+
status,
|
|
459
|
+
output_trace_id: normalized.trace.id,
|
|
460
|
+
events_captured: normalized.eventsCaptured,
|
|
461
|
+
messages_captured: normalized.messagesCaptured,
|
|
462
|
+
tool_outputs_captured: normalized.toolOutputsCaptured,
|
|
463
|
+
token_metadata_status: normalized.tokenMetadataStatus,
|
|
464
|
+
cost_metadata_status: normalized.costMetadataStatus,
|
|
465
|
+
usage_metadata: normalized.usageMetadata,
|
|
466
|
+
spend_confidence: normalized.spendConfidence,
|
|
467
|
+
pricing_assumptions: normalized.pricingAssumptions,
|
|
468
|
+
warnings: normalized.warnings,
|
|
469
|
+
failures: [],
|
|
470
|
+
limitations: normalized.limitations,
|
|
471
|
+
recommended_next_command: `compaction analyze ${capturedTracePath}`
|
|
472
|
+
};
|
|
473
|
+
return writeArtifacts(outputDirectory, normalized.trace, report);
|
|
474
|
+
}
|
|
475
|
+
//# sourceMappingURL=openai-agents-capture.js.map
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { StateCapsule } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* PUBLIC policy types + name constants (open-core Phase 1a seam).
|
|
4
|
+
*
|
|
5
|
+
* This module holds the public-safe surface of the two compaction policies — the policy NAME
|
|
6
|
+
* constants, the supported-policy-name set + its validator, and the structural types of a state
|
|
7
|
+
* capsule / source pointer / replacement mode / skill-injection provenance. It contains NO
|
|
8
|
+
* optimization, scoring, policy-application, or aggregation ALGORITHM. It exists so public modules
|
|
9
|
+
* (state-capsule, safety-report, report-generator, token-accounting, the analyze/summary/spend/
|
|
10
|
+
* feedback/audit public paths) can validate and shape compaction artifacts WITHOUT a runtime import
|
|
11
|
+
* of the proprietary engine modules (`policy-middleware`'s policy-application core,
|
|
12
|
+
* `skill-injection-policy`'s candidate/capsule builder, etc.).
|
|
13
|
+
*
|
|
14
|
+
* The proprietary engine modules re-export these names for backward compatibility, so existing
|
|
15
|
+
* engine consumers are unaffected; the algorithms themselves remain in the engine modules.
|
|
16
|
+
*/
|
|
17
|
+
/** Shipped tool-output policy name. */
|
|
18
|
+
export declare const COMPACTION_POLICY_NAME: "stale_tool_output_to_state_capsule";
|
|
19
|
+
/** Approval-required skill-injection policy name (design §1, §Q1). */
|
|
20
|
+
export declare const SKILL_INJECTION_POLICY_NAME: "repeated_skill_injection_to_state_capsule";
|
|
21
|
+
/**
|
|
22
|
+
* The set of supported compaction policy names. The first is the shipped tool-output policy;
|
|
23
|
+
* the second is the approval-required skill-injection policy (design §1, §Q1). A capsule or
|
|
24
|
+
* applied policy carrying any OTHER name is "unsupported" and fails the safety report.
|
|
25
|
+
*/
|
|
26
|
+
export declare const SUPPORTED_COMPACTION_POLICY_NAMES: readonly ["stale_tool_output_to_state_capsule", "repeated_skill_injection_to_state_capsule"];
|
|
27
|
+
export type SupportedCompactionPolicyName = (typeof SUPPORTED_COMPACTION_POLICY_NAMES)[number];
|
|
28
|
+
export declare function isSupportedCompactionPolicyName(name: string): name is SupportedCompactionPolicyName;
|
|
29
|
+
export type ReplacementMode = "whole_message" | "embedded_payload";
|
|
30
|
+
export interface SourcePointer {
|
|
31
|
+
traceId: string;
|
|
32
|
+
messageId: string;
|
|
33
|
+
messageIndex: number;
|
|
34
|
+
contentSha256: string;
|
|
35
|
+
}
|
|
36
|
+
export interface ReplacedRange {
|
|
37
|
+
start: number;
|
|
38
|
+
end: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Additive, backward-compatible provenance for a compacted skill injection (design §5 / Q2).
|
|
42
|
+
* Recorded per-capsule; not part of the trace schema.
|
|
43
|
+
*/
|
|
44
|
+
export interface SkillInjectionProvenance {
|
|
45
|
+
/** Skill name parsed from the injection's `.claude/skills/<NAME>` path. */
|
|
46
|
+
skillName: string;
|
|
47
|
+
/**
|
|
48
|
+
* Owning agent id of the REPLACED copy. Recovered from the captured message-id prefix
|
|
49
|
+
* (`<agentId>-<uuid>` for subagents) or "root" when the id is a bare UUID (root-agent
|
|
50
|
+
* injections such as start-cycle / complete-cycle / next-cycles).
|
|
51
|
+
*/
|
|
52
|
+
owningAgentId: string;
|
|
53
|
+
/** True when the owning agent id was recovered from a message-id prefix (subagent). */
|
|
54
|
+
owningAgentIdRecoveredFromPrefix: boolean;
|
|
55
|
+
/** Trace position (index) of the replaced later copy (within-context re-injection mode). */
|
|
56
|
+
replacedMessagePosition: number;
|
|
57
|
+
/** Id of the replaced later copy. */
|
|
58
|
+
replacedMessageId: string;
|
|
59
|
+
/** Trace position (index) of the retained first copy this capsule points at. */
|
|
60
|
+
firstCopyPosition: number;
|
|
61
|
+
/** Id of the retained first copy. */
|
|
62
|
+
firstCopyMessageId: string;
|
|
63
|
+
}
|
|
64
|
+
export interface PolicyMiddlewareStateCapsule {
|
|
65
|
+
id: string;
|
|
66
|
+
policyName: SupportedCompactionPolicyName;
|
|
67
|
+
traceId: string;
|
|
68
|
+
sourcePointer: SourcePointer;
|
|
69
|
+
compactedMessageIds: string[];
|
|
70
|
+
replacementMode: ReplacementMode;
|
|
71
|
+
replacedRanges?: ReplacedRange[];
|
|
72
|
+
replacedContentSha256?: string;
|
|
73
|
+
sourceExcerptPreview: string;
|
|
74
|
+
originalPayloadTokenCount: number;
|
|
75
|
+
text: string;
|
|
76
|
+
retainedFacts: StateCapsule["retainedFacts"];
|
|
77
|
+
openQuestions: StateCapsule["openQuestions"];
|
|
78
|
+
safetyNotes: StateCapsule["safetyNotes"];
|
|
79
|
+
/**
|
|
80
|
+
* Additive, backward-compatible provenance for compacted skill injections (design §5/Q2).
|
|
81
|
+
* Present ONLY on `repeated_skill_injection_to_state_capsule` capsules; undefined for the
|
|
82
|
+
* tool-output policy. Not part of the trace schema.
|
|
83
|
+
*/
|
|
84
|
+
skillInjectionProvenance?: SkillInjectionProvenance;
|
|
85
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PUBLIC policy types + name constants (open-core Phase 1a seam).
|
|
3
|
+
*
|
|
4
|
+
* This module holds the public-safe surface of the two compaction policies — the policy NAME
|
|
5
|
+
* constants, the supported-policy-name set + its validator, and the structural types of a state
|
|
6
|
+
* capsule / source pointer / replacement mode / skill-injection provenance. It contains NO
|
|
7
|
+
* optimization, scoring, policy-application, or aggregation ALGORITHM. It exists so public modules
|
|
8
|
+
* (state-capsule, safety-report, report-generator, token-accounting, the analyze/summary/spend/
|
|
9
|
+
* feedback/audit public paths) can validate and shape compaction artifacts WITHOUT a runtime import
|
|
10
|
+
* of the proprietary engine modules (`policy-middleware`'s policy-application core,
|
|
11
|
+
* `skill-injection-policy`'s candidate/capsule builder, etc.).
|
|
12
|
+
*
|
|
13
|
+
* The proprietary engine modules re-export these names for backward compatibility, so existing
|
|
14
|
+
* engine consumers are unaffected; the algorithms themselves remain in the engine modules.
|
|
15
|
+
*/
|
|
16
|
+
/** Shipped tool-output policy name. */
|
|
17
|
+
export const COMPACTION_POLICY_NAME = "stale_tool_output_to_state_capsule";
|
|
18
|
+
/** Approval-required skill-injection policy name (design §1, §Q1). */
|
|
19
|
+
export const SKILL_INJECTION_POLICY_NAME = "repeated_skill_injection_to_state_capsule";
|
|
20
|
+
/**
|
|
21
|
+
* The set of supported compaction policy names. The first is the shipped tool-output policy;
|
|
22
|
+
* the second is the approval-required skill-injection policy (design §1, §Q1). A capsule or
|
|
23
|
+
* applied policy carrying any OTHER name is "unsupported" and fails the safety report.
|
|
24
|
+
*/
|
|
25
|
+
export const SUPPORTED_COMPACTION_POLICY_NAMES = [COMPACTION_POLICY_NAME, SKILL_INJECTION_POLICY_NAME];
|
|
26
|
+
export function isSupportedCompactionPolicyName(name) {
|
|
27
|
+
return SUPPORTED_COMPACTION_POLICY_NAMES.includes(name);
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=policy-types.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface ModelPricing {
|
|
2
|
+
inputPerMillionUsd: number;
|
|
3
|
+
outputPerMillionUsd: number;
|
|
4
|
+
cacheReadPerMillionUsd?: number;
|
|
5
|
+
cacheCreationPerMillionUsd?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare const DEFAULT_MODEL = "placeholder-agent-model";
|
|
8
|
+
export declare const MODEL_PRICING: Record<string, ModelPricing>;
|
|
9
|
+
export declare function hasModelPricing(model: string): boolean;
|
|
10
|
+
export declare function isKnownModel(model: string): boolean;
|
|
11
|
+
export declare function getModelPricing(model: string): ModelPricing;
|
|
12
|
+
/**
|
|
13
|
+
* Returns the effective cache read price per million tokens for a model.
|
|
14
|
+
* Uses the explicit cacheReadPerMillionUsd when present; otherwise derives it
|
|
15
|
+
* as 10% of inputPerMillionUsd (Anthropic convention).
|
|
16
|
+
*/
|
|
17
|
+
export declare function getCacheReadPricePerMillion(model: string): number;
|
|
18
|
+
/**
|
|
19
|
+
* Returns the effective cache creation price per million tokens for a model.
|
|
20
|
+
* Uses the explicit cacheCreationPerMillionUsd when present; otherwise derives it
|
|
21
|
+
* as 125% of inputPerMillionUsd (Anthropic convention).
|
|
22
|
+
*/
|
|
23
|
+
export declare function getCacheCreationPricePerMillion(model: string): number;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export const DEFAULT_MODEL = "placeholder-agent-model";
|
|
2
|
+
// Approximate list prices as of 2026-06. Prices change — treat as estimates only.
|
|
3
|
+
// Add a new entry when a trace carries a model name not listed here.
|
|
4
|
+
export const MODEL_PRICING = {
|
|
5
|
+
[DEFAULT_MODEL]: {
|
|
6
|
+
inputPerMillionUsd: 1,
|
|
7
|
+
outputPerMillionUsd: 3
|
|
8
|
+
// No cache pricing for placeholder model — cache rates derived from inputPerMillionUsd if needed.
|
|
9
|
+
},
|
|
10
|
+
// Anthropic Claude — cache_read ≈ $0.30/M (10% of input), cache_creation ≈ 125% of input
|
|
11
|
+
"claude-opus-4-8": { inputPerMillionUsd: 15, outputPerMillionUsd: 75, cacheReadPerMillionUsd: 1.5, cacheCreationPerMillionUsd: 18.75 },
|
|
12
|
+
"claude-opus-4": { inputPerMillionUsd: 15, outputPerMillionUsd: 75, cacheReadPerMillionUsd: 1.5, cacheCreationPerMillionUsd: 18.75 },
|
|
13
|
+
"claude-sonnet-4-6": { inputPerMillionUsd: 3, outputPerMillionUsd: 15, cacheReadPerMillionUsd: 0.30, cacheCreationPerMillionUsd: 3.75 },
|
|
14
|
+
"claude-sonnet-4-5": { inputPerMillionUsd: 3, outputPerMillionUsd: 15, cacheReadPerMillionUsd: 0.30, cacheCreationPerMillionUsd: 3.75 },
|
|
15
|
+
"claude-sonnet-3-7": { inputPerMillionUsd: 3, outputPerMillionUsd: 15, cacheReadPerMillionUsd: 0.30, cacheCreationPerMillionUsd: 3.75 },
|
|
16
|
+
"claude-haiku-4-5": { inputPerMillionUsd: 0.25, outputPerMillionUsd: 1.25, cacheReadPerMillionUsd: 0.025, cacheCreationPerMillionUsd: 0.3125 },
|
|
17
|
+
"claude-haiku-3-5": { inputPerMillionUsd: 0.8, outputPerMillionUsd: 4, cacheReadPerMillionUsd: 0.08, cacheCreationPerMillionUsd: 1.0 },
|
|
18
|
+
// OpenAI — no cache pricing entries; cache rates derived from input rate when needed
|
|
19
|
+
"gpt-4o": { inputPerMillionUsd: 2.5, outputPerMillionUsd: 10 },
|
|
20
|
+
"gpt-4o-mini": { inputPerMillionUsd: 0.15, outputPerMillionUsd: 0.6 },
|
|
21
|
+
"o1": { inputPerMillionUsd: 15, outputPerMillionUsd: 60 },
|
|
22
|
+
"o3": { inputPerMillionUsd: 10, outputPerMillionUsd: 40 },
|
|
23
|
+
"o3-mini": { inputPerMillionUsd: 1.1, outputPerMillionUsd: 4.4 },
|
|
24
|
+
"o4-mini": { inputPerMillionUsd: 1.1, outputPerMillionUsd: 4.4 }
|
|
25
|
+
};
|
|
26
|
+
export function hasModelPricing(model) {
|
|
27
|
+
return Object.prototype.hasOwnProperty.call(MODEL_PRICING, model);
|
|
28
|
+
}
|
|
29
|
+
// Returns true only when the model is in the price table with real-world pricing,
|
|
30
|
+
// not the placeholder fallback. Use this to label estimates that may be inaccurate.
|
|
31
|
+
export function isKnownModel(model) {
|
|
32
|
+
return hasModelPricing(model) && model !== DEFAULT_MODEL;
|
|
33
|
+
}
|
|
34
|
+
export function getModelPricing(model) {
|
|
35
|
+
return MODEL_PRICING[model] ?? MODEL_PRICING[DEFAULT_MODEL];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Returns the effective cache read price per million tokens for a model.
|
|
39
|
+
* Uses the explicit cacheReadPerMillionUsd when present; otherwise derives it
|
|
40
|
+
* as 10% of inputPerMillionUsd (Anthropic convention).
|
|
41
|
+
*/
|
|
42
|
+
export function getCacheReadPricePerMillion(model) {
|
|
43
|
+
const pricing = getModelPricing(model);
|
|
44
|
+
return pricing.cacheReadPerMillionUsd ?? pricing.inputPerMillionUsd * 0.1;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Returns the effective cache creation price per million tokens for a model.
|
|
48
|
+
* Uses the explicit cacheCreationPerMillionUsd when present; otherwise derives it
|
|
49
|
+
* as 125% of inputPerMillionUsd (Anthropic convention).
|
|
50
|
+
*/
|
|
51
|
+
export function getCacheCreationPricePerMillion(model) {
|
|
52
|
+
const pricing = getModelPricing(model);
|
|
53
|
+
return pricing.cacheCreationPerMillionUsd ?? pricing.inputPerMillionUsd * 1.25;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=pricing.js.map
|