@nathapp/nax 0.46.0 → 0.46.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,268 +1,16 @@
1
1
  /**
2
- * Cost Tracking
2
+ * Cost Tracking — re-exports from the shared src/agents/cost/ module.
3
3
  *
4
- * Token-based cost estimation for AI coding agents.
5
- * Parses agent output for token usage and calculates costs.
4
+ * Kept for zero-breakage backward compatibility.
5
+ * Import directly from src/agents/cost for new code.
6
6
  */
7
7
 
8
- import type { ModelTier } from "../../config/schema";
9
-
10
- /** Cost rates per 1M tokens (USD) */
11
- export interface ModelCostRates {
12
- inputPer1M: number;
13
- outputPer1M: number;
14
- }
15
-
16
- /** Token usage data */
17
- export interface TokenUsage {
18
- inputTokens: number;
19
- outputTokens: number;
20
- }
21
-
22
- /** Cost estimate with confidence indicator */
23
- export interface CostEstimate {
24
- cost: number;
25
- confidence: "exact" | "estimated" | "fallback";
26
- }
27
-
28
- /** Model tier cost rates (as of 2025-01) */
29
- export const COST_RATES: Record<ModelTier, ModelCostRates> = {
30
- fast: {
31
- // Haiku 4.5
32
- inputPer1M: 0.8,
33
- outputPer1M: 4.0,
34
- },
35
- balanced: {
36
- // Sonnet 4.5
37
- inputPer1M: 3.0,
38
- outputPer1M: 15.0,
39
- },
40
- powerful: {
41
- // Opus 4
42
- inputPer1M: 15.0,
43
- outputPer1M: 75.0,
44
- },
45
- };
46
-
47
- /**
48
- * Token usage with confidence indicator.
49
- */
50
- export interface TokenUsageWithConfidence {
51
- inputTokens: number;
52
- outputTokens: number;
53
- confidence: "exact" | "estimated";
54
- }
55
-
56
- /**
57
- * Parse Claude Code output for token usage.
58
- *
59
- * Supports multiple formats with varying confidence levels:
60
- * - JSON structured output → "exact" confidence
61
- * - Markdown/plain text patterns → "estimated" confidence
62
- *
63
- * Uses specific regex patterns to reduce false positives.
64
- *
65
- * @param output - Agent stdout + stderr combined
66
- * @returns Token usage with confidence indicator, or null if tokens cannot be parsed
67
- *
68
- * @example
69
- * ```ts
70
- * // JSON format (exact)
71
- * const usage1 = parseTokenUsage('{"usage": {"input_tokens": 1234, "output_tokens": 5678}}');
72
- * // { inputTokens: 1234, outputTokens: 5678, confidence: 'exact' }
73
- *
74
- * // Markdown format (estimated)
75
- * const usage2 = parseTokenUsage('Input tokens: 1234\nOutput tokens: 5678');
76
- * // { inputTokens: 1234, outputTokens: 5678, confidence: 'estimated' }
77
- *
78
- * // Unparseable
79
- * const usage3 = parseTokenUsage('No token data here');
80
- * // null
81
- * ```
82
- */
83
- export function parseTokenUsage(output: string): TokenUsageWithConfidence | null {
84
- // Try JSON format first (most reliable) - confidence: exact
85
- try {
86
- const jsonMatch = output.match(
87
- /\{[^}]*"usage"\s*:\s*\{[^}]*"input_tokens"\s*:\s*(\d+)[^}]*"output_tokens"\s*:\s*(\d+)[^}]*\}[^}]*\}/,
88
- );
89
- if (jsonMatch) {
90
- return {
91
- inputTokens: Number.parseInt(jsonMatch[1], 10),
92
- outputTokens: Number.parseInt(jsonMatch[2], 10),
93
- confidence: "exact",
94
- };
95
- }
96
-
97
- // Try parsing as full JSON object
98
- const lines = output.split("\n");
99
- for (const line of lines) {
100
- if (line.trim().startsWith("{")) {
101
- try {
102
- const parsed = JSON.parse(line);
103
- if (parsed.usage?.input_tokens && parsed.usage?.output_tokens) {
104
- return {
105
- inputTokens: parsed.usage.input_tokens,
106
- outputTokens: parsed.usage.output_tokens,
107
- confidence: "exact",
108
- };
109
- }
110
- } catch {
111
- // Not valid JSON, continue
112
- }
113
- }
114
- }
115
- } catch {
116
- // JSON parsing failed, try regex patterns
117
- }
118
-
119
- // Try specific markdown-style patterns (more specific to reduce false positives)
120
- // Match "Input tokens: 1234" or "input_tokens: 1234" or "INPUT TOKENS: 1234"
121
- // Use word boundary at start, require colon or space after keyword, then digits
122
- // confidence: estimated (regex-based)
123
- const inputMatch = output.match(/\b(?:input|input_tokens)\s*:\s*(\d{2,})|(?:input)\s+(?:tokens?)\s*:\s*(\d{2,})/i);
124
- const outputMatch = output.match(
125
- /\b(?:output|output_tokens)\s*:\s*(\d{2,})|(?:output)\s+(?:tokens?)\s*:\s*(\d{2,})/i,
126
- );
127
-
128
- if (inputMatch && outputMatch) {
129
- // Extract token counts (may be in capture group 1 or 2)
130
- const inputTokens = Number.parseInt(inputMatch[1] || inputMatch[2], 10);
131
- const outputTokens = Number.parseInt(outputMatch[1] || outputMatch[2], 10);
132
-
133
- // Sanity check: reject if tokens seem unreasonably large (> 1M each)
134
- if (inputTokens > 1_000_000 || outputTokens > 1_000_000) {
135
- return null;
136
- }
137
-
138
- return {
139
- inputTokens,
140
- outputTokens,
141
- confidence: "estimated",
142
- };
143
- }
144
-
145
- return null;
146
- }
147
-
148
- /**
149
- * Estimate cost in USD based on token usage.
150
- *
151
- * Calculates total cost using tier-specific rates per 1M tokens.
152
- *
153
- * @param modelTier - Model tier (fast/balanced/powerful)
154
- * @param inputTokens - Number of input tokens consumed
155
- * @param outputTokens - Number of output tokens generated
156
- * @returns Total cost in USD
157
- *
158
- * @example
159
- * ```ts
160
- * const cost = estimateCost("balanced", 10000, 5000);
161
- * // Sonnet 4.5: (10000/1M * $3.00) + (5000/1M * $15.00) = $0.105
162
- * ```
163
- */
164
- export function estimateCost(
165
- modelTier: ModelTier,
166
- inputTokens: number,
167
- outputTokens: number,
168
- customRates?: ModelCostRates,
169
- ): number {
170
- const rates = customRates ?? COST_RATES[modelTier];
171
- const inputCost = (inputTokens / 1_000_000) * rates.inputPer1M;
172
- const outputCost = (outputTokens / 1_000_000) * rates.outputPer1M;
173
- return inputCost + outputCost;
174
- }
175
-
176
- /**
177
- * Estimate cost from agent output by parsing token usage.
178
- *
179
- * Attempts to extract token counts from stdout/stderr, then calculates cost.
180
- * Returns null if tokens cannot be parsed (caller should use fallback estimation).
181
- *
182
- * @param modelTier - Model tier for cost calculation
183
- * @param output - Agent stdout + stderr combined
184
- * @returns Cost estimate with confidence indicator, or null if unparseable
185
- *
186
- * @example
187
- * ```ts
188
- * const estimate = estimateCostFromOutput("balanced", agentOutput);
189
- * if (estimate) {
190
- * console.log(`Cost: $${estimate.cost.toFixed(4)} (${estimate.confidence})`);
191
- * } else {
192
- * // Fall back to duration-based estimation
193
- * }
194
- * ```
195
- */
196
- export function estimateCostFromOutput(modelTier: ModelTier, output: string): CostEstimate | null {
197
- const usage = parseTokenUsage(output);
198
- if (!usage) {
199
- return null;
200
- }
201
- const cost = estimateCost(modelTier, usage.inputTokens, usage.outputTokens);
202
- return {
203
- cost,
204
- confidence: usage.confidence,
205
- };
206
- }
207
-
208
- /**
209
- * Fallback cost estimation based on runtime duration.
210
- *
211
- * Used when token usage cannot be parsed from agent output.
212
- * Provides conservative estimates using per-minute rates.
213
- *
214
- * @param modelTier - Model tier for cost calculation
215
- * @param durationMs - Agent runtime in milliseconds
216
- * @returns Cost estimate with 'fallback' confidence
217
- *
218
- * @example
219
- * ```ts
220
- * const estimate = estimateCostByDuration("balanced", 120000); // 2 minutes
221
- * // { cost: 0.10, confidence: 'fallback' }
222
- * // Sonnet: 2 min * $0.05/min = $0.10
223
- * ```
224
- */
225
- export function estimateCostByDuration(modelTier: ModelTier, durationMs: number): CostEstimate {
226
- const costPerMinute: Record<ModelTier, number> = {
227
- fast: 0.01,
228
- balanced: 0.05,
229
- powerful: 0.15,
230
- };
231
- const minutes = durationMs / 60000;
232
- const cost = minutes * costPerMinute[modelTier];
233
- return {
234
- cost,
235
- confidence: "fallback",
236
- };
237
- }
238
-
239
- /**
240
- * Format cost estimate with confidence indicator for display.
241
- *
242
- * @param estimate - Cost estimate with confidence level
243
- * @returns Formatted cost string with confidence indicator
244
- *
245
- * @example
246
- * ```ts
247
- * formatCostWithConfidence({ cost: 0.12, confidence: 'exact' });
248
- * // "$0.12"
249
- *
250
- * formatCostWithConfidence({ cost: 0.15, confidence: 'estimated' });
251
- * // "~$0.15"
252
- *
253
- * formatCostWithConfidence({ cost: 0.05, confidence: 'fallback' });
254
- * // "~$0.05 (duration-based)"
255
- * ```
256
- */
257
- export function formatCostWithConfidence(estimate: CostEstimate): string {
258
- const formattedCost = `$${estimate.cost.toFixed(2)}`;
259
-
260
- switch (estimate.confidence) {
261
- case "exact":
262
- return formattedCost;
263
- case "estimated":
264
- return `~${formattedCost}`;
265
- case "fallback":
266
- return `~${formattedCost} (duration-based)`;
267
- }
268
- }
8
+ export type { ModelCostRates, TokenUsage, CostEstimate, TokenUsageWithConfidence } from "../cost";
9
+ export {
10
+ COST_RATES,
11
+ parseTokenUsage,
12
+ estimateCost,
13
+ estimateCostFromOutput,
14
+ estimateCostByDuration,
15
+ formatCostWithConfidence,
16
+ } from "../cost";
@@ -4,6 +4,8 @@
4
4
  * Handles building commands, preparing environment, and process execution.
5
5
  */
6
6
 
7
+ import { homedir } from "node:os";
8
+ import { isAbsolute } from "node:path";
7
9
  import { resolvePermissions } from "../../config/permissions";
8
10
  import type { PidRegistry } from "../../execution/pid-registry";
9
11
  import { withProcessTimeout } from "../../execution/timeout-handler";
@@ -65,13 +67,22 @@ export function buildCommand(binary: string, options: AgentRunOptions): string[]
65
67
  export function buildAllowedEnv(options: AgentRunOptions): Record<string, string | undefined> {
66
68
  const allowed: Record<string, string | undefined> = {};
67
69
 
68
- const essentialVars = ["PATH", "HOME", "TMPDIR", "NODE_ENV", "USER", "LOGNAME"];
70
+ const essentialVars = ["PATH", "TMPDIR", "NODE_ENV", "USER", "LOGNAME"];
69
71
  for (const varName of essentialVars) {
70
72
  if (process.env[varName]) {
71
73
  allowed[varName] = process.env[varName];
72
74
  }
73
75
  }
74
76
 
77
+ // Sanitize HOME — must be absolute path. Unexpanded "~" causes literal ~/dir in cwd.
78
+ const rawHome = process.env.HOME ?? "";
79
+ const safeHome = rawHome && isAbsolute(rawHome) ? rawHome : homedir();
80
+ if (rawHome !== safeHome) {
81
+ const logger = getLogger();
82
+ logger.warn("env", `HOME env is not absolute ("${rawHome}"), falling back to os.homedir(): ${safeHome}`);
83
+ }
84
+ allowed.HOME = safeHome;
85
+
75
86
  const apiKeyVars = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY"];
76
87
  for (const varName of apiKeyVars) {
77
88
  if (process.env[varName]) {
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Cost calculation functions for all agent adapters.
3
+ */
4
+
5
+ import type { ModelTier } from "../../config/schema";
6
+ import { parseTokenUsage } from "./parse";
7
+ import { COST_RATES, MODEL_PRICING } from "./pricing";
8
+ import type { CostEstimate, ModelCostRates, SessionTokenUsage } from "./types";
9
+
10
+ /**
11
+ * Estimate cost in USD based on token usage and model tier.
12
+ *
13
+ * @param modelTier - Model tier (fast/balanced/powerful)
14
+ * @param inputTokens - Number of input tokens consumed
15
+ * @param outputTokens - Number of output tokens generated
16
+ * @param customRates - Optional custom rates (overrides tier defaults)
17
+ * @returns Total cost in USD
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * const cost = estimateCost("balanced", 10000, 5000);
22
+ * // Sonnet 4.5: (10000/1M * $3.00) + (5000/1M * $15.00) = $0.105
23
+ * ```
24
+ */
25
+ export function estimateCost(
26
+ modelTier: ModelTier,
27
+ inputTokens: number,
28
+ outputTokens: number,
29
+ customRates?: ModelCostRates,
30
+ ): number {
31
+ const rates = customRates ?? COST_RATES[modelTier];
32
+ const inputCost = (inputTokens / 1_000_000) * rates.inputPer1M;
33
+ const outputCost = (outputTokens / 1_000_000) * rates.outputPer1M;
34
+ return inputCost + outputCost;
35
+ }
36
+
37
+ /**
38
+ * Estimate cost from agent output by parsing token usage.
39
+ *
40
+ * Attempts to extract token counts from stdout/stderr, then calculates cost.
41
+ * Returns null if tokens cannot be parsed.
42
+ *
43
+ * @param modelTier - Model tier for cost calculation
44
+ * @param output - Agent stdout + stderr combined
45
+ * @returns Cost estimate with confidence indicator, or null if unparseable
46
+ */
47
+ export function estimateCostFromOutput(modelTier: ModelTier, output: string): CostEstimate | null {
48
+ const usage = parseTokenUsage(output);
49
+ if (!usage) {
50
+ return null;
51
+ }
52
+ const cost = estimateCost(modelTier, usage.inputTokens, usage.outputTokens);
53
+ return {
54
+ cost,
55
+ confidence: usage.confidence,
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Fallback cost estimation based on runtime duration.
61
+ *
62
+ * Used when token usage cannot be parsed from agent output.
63
+ * Provides conservative estimates using per-minute rates.
64
+ *
65
+ * @param modelTier - Model tier for cost calculation
66
+ * @param durationMs - Agent runtime in milliseconds
67
+ * @returns Cost estimate with 'fallback' confidence
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * const estimate = estimateCostByDuration("balanced", 120000); // 2 minutes
72
+ * // { cost: 0.10, confidence: 'fallback' }
73
+ * // Sonnet: 2 min * $0.05/min = $0.10
74
+ * ```
75
+ */
76
+ export function estimateCostByDuration(modelTier: ModelTier, durationMs: number): CostEstimate {
77
+ const costPerMinute: Record<ModelTier, number> = {
78
+ fast: 0.01,
79
+ balanced: 0.05,
80
+ powerful: 0.15,
81
+ };
82
+ const minutes = durationMs / 60000;
83
+ const cost = minutes * costPerMinute[modelTier];
84
+ return {
85
+ cost,
86
+ confidence: "fallback",
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Format cost estimate with confidence indicator for display.
92
+ *
93
+ * @param estimate - Cost estimate with confidence level
94
+ * @returns Formatted cost string with confidence indicator
95
+ *
96
+ * @example
97
+ * ```ts
98
+ * formatCostWithConfidence({ cost: 0.12, confidence: 'exact' });
99
+ * // "$0.12"
100
+ *
101
+ * formatCostWithConfidence({ cost: 0.15, confidence: 'estimated' });
102
+ * // "~$0.15"
103
+ *
104
+ * formatCostWithConfidence({ cost: 0.05, confidence: 'fallback' });
105
+ * // "~$0.05 (duration-based)"
106
+ * ```
107
+ */
108
+ export function formatCostWithConfidence(estimate: CostEstimate): string {
109
+ const formattedCost = `$${estimate.cost.toFixed(2)}`;
110
+
111
+ switch (estimate.confidence) {
112
+ case "exact":
113
+ return formattedCost;
114
+ case "estimated":
115
+ return `~${formattedCost}`;
116
+ case "fallback":
117
+ return `~${formattedCost} (duration-based)`;
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Calculate USD cost from ACP session token counts using per-model pricing.
123
+ *
124
+ * @param usage - Token counts from cumulative_token_usage
125
+ * @param model - Model identifier (e.g., 'claude-sonnet-4', 'claude-haiku-4-5')
126
+ * @returns Estimated cost in USD
127
+ */
128
+ export function estimateCostFromTokenUsage(usage: SessionTokenUsage, model: string): number {
129
+ const pricing = MODEL_PRICING[model];
130
+
131
+ if (!pricing) {
132
+ // Fallback: use average rate for unknown models
133
+ const fallbackInputRate = 3 / 1_000_000;
134
+ const fallbackOutputRate = 15 / 1_000_000;
135
+ const inputCost = (usage.input_tokens ?? 0) * fallbackInputRate;
136
+ const outputCost = (usage.output_tokens ?? 0) * fallbackOutputRate;
137
+ const cacheReadCost = (usage.cache_read_input_tokens ?? 0) * (0.5 / 1_000_000);
138
+ const cacheCreationCost = (usage.cache_creation_input_tokens ?? 0) * (2 / 1_000_000);
139
+ return inputCost + outputCost + cacheReadCost + cacheCreationCost;
140
+ }
141
+
142
+ // Convert $/1M rates to $/token
143
+ const inputRate = pricing.input / 1_000_000;
144
+ const outputRate = pricing.output / 1_000_000;
145
+ const cacheReadRate = (pricing.cacheRead ?? pricing.input * 0.1) / 1_000_000;
146
+ const cacheCreationRate = (pricing.cacheCreation ?? pricing.input * 0.33) / 1_000_000;
147
+
148
+ const inputCost = (usage.input_tokens ?? 0) * inputRate;
149
+ const outputCost = (usage.output_tokens ?? 0) * outputRate;
150
+ const cacheReadCost = (usage.cache_read_input_tokens ?? 0) * cacheReadRate;
151
+ const cacheCreationCost = (usage.cache_creation_input_tokens ?? 0) * cacheCreationRate;
152
+
153
+ return inputCost + outputCost + cacheReadCost + cacheCreationCost;
154
+ }
@@ -0,0 +1,10 @@
1
+ export type { ModelCostRates, TokenUsage, CostEstimate, TokenUsageWithConfidence, SessionTokenUsage } from "./types";
2
+ export { COST_RATES, MODEL_PRICING } from "./pricing";
3
+ export { parseTokenUsage } from "./parse";
4
+ export {
5
+ estimateCost,
6
+ estimateCostFromOutput,
7
+ estimateCostByDuration,
8
+ formatCostWithConfidence,
9
+ estimateCostFromTokenUsage,
10
+ } from "./calculate";
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Token usage parsing from raw agent output strings.
3
+ */
4
+
5
+ import type { TokenUsageWithConfidence } from "./types";
6
+
7
+ /**
8
+ * Parse Claude Code output for token usage.
9
+ *
10
+ * Supports multiple formats with varying confidence levels:
11
+ * - JSON structured output → "exact" confidence
12
+ * - Markdown/plain text patterns → "estimated" confidence
13
+ *
14
+ * Uses specific regex patterns to reduce false positives.
15
+ *
16
+ * @param output - Agent stdout + stderr combined
17
+ * @returns Token usage with confidence indicator, or null if tokens cannot be parsed
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * // JSON format (exact)
22
+ * const usage1 = parseTokenUsage('{"usage": {"input_tokens": 1234, "output_tokens": 5678}}');
23
+ * // { inputTokens: 1234, outputTokens: 5678, confidence: 'exact' }
24
+ *
25
+ * // Markdown format (estimated)
26
+ * const usage2 = parseTokenUsage('Input tokens: 1234\nOutput tokens: 5678');
27
+ * // { inputTokens: 1234, outputTokens: 5678, confidence: 'estimated' }
28
+ *
29
+ * // Unparseable
30
+ * const usage3 = parseTokenUsage('No token data here');
31
+ * // null
32
+ * ```
33
+ */
34
+ export function parseTokenUsage(output: string): TokenUsageWithConfidence | null {
35
+ // Try JSON format first (most reliable) - confidence: exact
36
+ try {
37
+ const jsonMatch = output.match(
38
+ /\{[^}]*"usage"\s*:\s*\{[^}]*"input_tokens"\s*:\s*(\d+)[^}]*"output_tokens"\s*:\s*(\d+)[^}]*\}[^}]*\}/,
39
+ );
40
+ if (jsonMatch) {
41
+ return {
42
+ inputTokens: Number.parseInt(jsonMatch[1], 10),
43
+ outputTokens: Number.parseInt(jsonMatch[2], 10),
44
+ confidence: "exact",
45
+ };
46
+ }
47
+
48
+ // Try parsing as full JSON object
49
+ const lines = output.split("\n");
50
+ for (const line of lines) {
51
+ if (line.trim().startsWith("{")) {
52
+ try {
53
+ const parsed = JSON.parse(line);
54
+ if (parsed.usage?.input_tokens && parsed.usage?.output_tokens) {
55
+ return {
56
+ inputTokens: parsed.usage.input_tokens,
57
+ outputTokens: parsed.usage.output_tokens,
58
+ confidence: "exact",
59
+ };
60
+ }
61
+ } catch {
62
+ // Not valid JSON, continue
63
+ }
64
+ }
65
+ }
66
+ } catch {
67
+ // JSON parsing failed, try regex patterns
68
+ }
69
+
70
+ // Try specific markdown-style patterns (more specific to reduce false positives)
71
+ // Match "Input tokens: 1234" or "input_tokens: 1234" or "INPUT TOKENS: 1234"
72
+ // Use word boundary at start, require colon or space after keyword, then digits
73
+ // confidence: estimated (regex-based)
74
+ const inputMatch = output.match(/\b(?:input|input_tokens)\s*:\s*(\d{2,})|(?:input)\s+(?:tokens?)\s*:\s*(\d{2,})/i);
75
+ const outputMatch = output.match(
76
+ /\b(?:output|output_tokens)\s*:\s*(\d{2,})|(?:output)\s+(?:tokens?)\s*:\s*(\d{2,})/i,
77
+ );
78
+
79
+ if (inputMatch && outputMatch) {
80
+ // Extract token counts (may be in capture group 1 or 2)
81
+ const inputTokens = Number.parseInt(inputMatch[1] || inputMatch[2], 10);
82
+ const outputTokens = Number.parseInt(outputMatch[1] || outputMatch[2], 10);
83
+
84
+ // Sanity check: reject if tokens seem unreasonably large (> 1M each)
85
+ if (inputTokens > 1_000_000 || outputTokens > 1_000_000) {
86
+ return null;
87
+ }
88
+
89
+ return {
90
+ inputTokens,
91
+ outputTokens,
92
+ confidence: "estimated",
93
+ };
94
+ }
95
+
96
+ return null;
97
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Cost rate tables for all supported model tiers and specific models.
3
+ */
4
+
5
+ import type { ModelTier } from "../../config/schema";
6
+ import type { ModelCostRates } from "./types";
7
+
8
+ /** Model tier cost rates (as of 2025-01) */
9
+ export const COST_RATES: Record<ModelTier, ModelCostRates> = {
10
+ fast: {
11
+ // Haiku 4.5
12
+ inputPer1M: 0.8,
13
+ outputPer1M: 4.0,
14
+ },
15
+ balanced: {
16
+ // Sonnet 4.5
17
+ inputPer1M: 3.0,
18
+ outputPer1M: 15.0,
19
+ },
20
+ powerful: {
21
+ // Opus 4
22
+ inputPer1M: 15.0,
23
+ outputPer1M: 75.0,
24
+ },
25
+ };
26
+
27
+ /** Per-model pricing in $/1M tokens: { input, output } */
28
+ export const MODEL_PRICING: Record<
29
+ string,
30
+ { input: number; output: number; cacheRead?: number; cacheCreation?: number }
31
+ > = {
32
+ // Anthropic Claude models (short aliases)
33
+ sonnet: { input: 3, output: 15 },
34
+ haiku: { input: 0.8, output: 4.0, cacheRead: 0.1, cacheCreation: 1.0 },
35
+ opus: { input: 15, output: 75 },
36
+
37
+ // Anthropic Claude models (full names)
38
+ "claude-sonnet-4": { input: 3, output: 15 },
39
+ "claude-sonnet-4-5": { input: 3, output: 15 },
40
+ "claude-sonnet-4-6": { input: 3, output: 15 },
41
+ "claude-haiku": { input: 0.8, output: 4.0, cacheRead: 0.1, cacheCreation: 1.0 },
42
+ "claude-haiku-4-5": { input: 0.8, output: 4.0, cacheRead: 0.1, cacheCreation: 1.0 },
43
+ "claude-opus": { input: 15, output: 75 },
44
+ "claude-opus-4": { input: 15, output: 75 },
45
+ "claude-opus-4-6": { input: 15, output: 75 },
46
+
47
+ // OpenAI models
48
+ "gpt-4.1": { input: 10, output: 30 },
49
+ "gpt-4": { input: 30, output: 60 },
50
+ "gpt-3.5-turbo": { input: 0.5, output: 1.5 },
51
+
52
+ // Google Gemini
53
+ "gemini-2.5-pro": { input: 0.075, output: 0.3 },
54
+ "gemini-2-pro": { input: 0.075, output: 0.3 },
55
+
56
+ // OpenAI Codex
57
+ codex: { input: 0.02, output: 0.06 },
58
+ "code-davinci-002": { input: 0.02, output: 0.06 },
59
+ };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Cost tracking types — shared across all agent adapters.
3
+ */
4
+
5
+ import type { ModelTier } from "../../config/schema";
6
+
7
+ export type { ModelTier };
8
+
9
+ /** Cost rates per 1M tokens (USD) */
10
+ export interface ModelCostRates {
11
+ inputPer1M: number;
12
+ outputPer1M: number;
13
+ }
14
+
15
+ /** Token usage data (camelCase — nax-internal representation) */
16
+ export interface TokenUsage {
17
+ inputTokens: number;
18
+ outputTokens: number;
19
+ }
20
+
21
+ /** Cost estimate with confidence indicator */
22
+ export interface CostEstimate {
23
+ cost: number;
24
+ confidence: "exact" | "estimated" | "fallback";
25
+ }
26
+
27
+ /** Token usage with confidence indicator */
28
+ export interface TokenUsageWithConfidence {
29
+ inputTokens: number;
30
+ outputTokens: number;
31
+ confidence: "exact" | "estimated";
32
+ }
33
+
34
+ /**
35
+ * Token usage from an ACP session's cumulative_token_usage field.
36
+ * Uses snake_case to match the ACP wire format.
37
+ */
38
+ export interface SessionTokenUsage {
39
+ input_tokens: number;
40
+ output_tokens: number;
41
+ /** Cache read tokens — billed at a reduced rate */
42
+ cache_read_input_tokens?: number;
43
+ /** Cache creation tokens — billed at a higher creation rate */
44
+ cache_creation_input_tokens?: number;
45
+ }