praxis-agent 0.62.5 → 0.62.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -92,7 +92,7 @@ For Anthropic Messages:
92
92
  ```sh
93
93
  export PRAXIS_PROVIDER="anthropic"
94
94
  export PRAXIS_API_KEY="your-api-key"
95
- export PRAXIS_MODEL="claude-sonnet-4-20250514"
95
+ export PRAXIS_MODEL="claude-sonnet-4-6"
96
96
 
97
97
  cd /path/to/project
98
98
  praxis
@@ -100,7 +100,7 @@ praxis
100
100
 
101
101
  Anthropic models use a 200,000-token context window by default, including
102
102
  unknown model IDs. Add the exact terminal `[1m]` suffix (for example,
103
- `claude-sonnet-4-20250514[1m]`) to request a 1,000,000-token context window;
103
+ `claude-sonnet-4-6[1m]`) to request a 1,000,000-token context window;
104
104
  Praxis keeps that selected model public, removes the suffix on the wire, and
105
105
  adds the `context-1m-2025-08-07` Anthropic beta once. An explicit
106
106
  `PRAXIS_CONTEXT_WINDOW_TOKENS` value overrides either inferred window.
@@ -77,6 +77,7 @@ const modelUsageCounterFields = [
77
77
  'outputTokens',
78
78
  'cacheReadInputTokens',
79
79
  'cacheCreationInputTokens',
80
+ 'cacheCreationInputTokens1h',
80
81
  'webSearchRequests',
81
82
  ];
82
83
  const modelUsageMetadataFields = ['contextWindow', 'maxOutputTokens'];
@@ -90,6 +91,10 @@ function assertValidModelUsageEntry(model, usage) {
90
91
  throw new Error(`Model usage for "${model}" has an invalid ${field} counter`);
91
92
  }
92
93
  }
94
+ if ((usage.cacheCreationInputTokens1h ?? 0) >
95
+ (usage.cacheCreationInputTokens ?? 0)) {
96
+ throw new Error(`Model usage for "${model}" has an invalid cacheCreationInputTokens1h counter`);
97
+ }
93
98
  for (const field of modelUsageMetadataFields) {
94
99
  const value = usage[field];
95
100
  if (value !== undefined && (!Number.isSafeInteger(value) || value < 1)) {
@@ -120,14 +125,20 @@ function addUsageChecked(model, left, right) {
120
125
  const outputTokens = left.outputTokens + right.outputTokens;
121
126
  const cacheReadInputTokens = (left.cacheReadInputTokens ?? 0) + (right.cacheReadInputTokens ?? 0);
122
127
  const cacheCreationInputTokens = (left.cacheCreationInputTokens ?? 0) + (right.cacheCreationInputTokens ?? 0);
128
+ const cacheCreationInputTokens1h = (left.cacheCreationInputTokens1h ?? 0) +
129
+ (right.cacheCreationInputTokens1h ?? 0);
123
130
  const webSearchRequests = (left.webSearchRequests ?? 0) + (right.webSearchRequests ?? 0);
124
131
  if (!Number.isSafeInteger(inputTokens) ||
125
132
  !Number.isSafeInteger(outputTokens) ||
126
133
  !Number.isSafeInteger(cacheReadInputTokens) ||
127
134
  !Number.isSafeInteger(cacheCreationInputTokens) ||
135
+ !Number.isSafeInteger(cacheCreationInputTokens1h) ||
128
136
  !Number.isSafeInteger(webSearchRequests)) {
129
137
  throw new Error('Model usage total overflow');
130
138
  }
139
+ if (cacheCreationInputTokens1h > cacheCreationInputTokens) {
140
+ throw new Error(`Model usage${model === undefined ? '' : ` for "${model}"`} has an invalid cacheCreationInputTokens1h counter`);
141
+ }
131
142
  // Aggregates without a model stay counter-only; per-model rows merge their
132
143
  // capability metadata with conflict rejection.
133
144
  const metadata = model === undefined ? {} : mergeModelUsageMetadata(model, left, right);
@@ -136,6 +147,7 @@ function addUsageChecked(model, left, right) {
136
147
  outputTokens,
137
148
  ...(cacheReadInputTokens === 0 ? {} : { cacheReadInputTokens }),
138
149
  ...(cacheCreationInputTokens === 0 ? {} : { cacheCreationInputTokens }),
150
+ ...(cacheCreationInputTokens1h === 0 ? {} : { cacheCreationInputTokens1h }),
139
151
  ...(webSearchRequests === 0 ? {} : { webSearchRequests }),
140
152
  ...metadata,
141
153
  };
@@ -147,6 +159,10 @@ function assertValidResultUsage(usage) {
147
159
  throw new Error(`Model usage total has an invalid ${field} counter`);
148
160
  }
149
161
  }
162
+ if ((usage.cacheCreationInputTokens1h ?? 0) >
163
+ (usage.cacheCreationInputTokens ?? 0)) {
164
+ throw new Error('Model usage total has an invalid cacheCreationInputTokens1h counter');
165
+ }
150
166
  }
151
167
  function addApiDuration(value, total, field) {
152
168
  if (!Number.isFinite(value) || value < 0) {
@@ -14,7 +14,7 @@ import { projectNativeSessionEntries } from './native-session-projection.js';
14
14
  import { classifyClaudeInterruption, } from '../native/interruption.js';
15
15
  import { findUnresolvedClaudeToolCalls, getClaudeContentBlocks, } from '../native/tool-links.js';
16
16
  import { createClaudeAgentSettingEntry, createClaudeHookAttachmentEntries, createClaudeRuleAttachmentEntry, translateProviderEvents, } from '../native/translation.js';
17
- import { AgentRunCancelledError, AgentRuntime, MALFORMED_TOOL_INPUT_MESSAGE, ModelProviderError, } from '../core/runtime.js';
17
+ import { AgentBudgetExceededError, AgentRunCancelledError, AgentRuntime, MALFORMED_TOOL_INPUT_MESSAGE, ModelProviderError, } from '../core/runtime.js';
18
18
  import { resolveToolSchedulingPolicy } from '../core/tool-scheduling-policy.js';
19
19
  import { BackgroundTaskRuntime, } from './background-task-runtime.js';
20
20
  import { backgroundAgentNotificationMarkers, } from './background-agent-manager.js';
@@ -115,14 +115,30 @@ const emptyToolRegistry = {
115
115
  execute: async () => ({ content: '', isError: false }),
116
116
  };
117
117
  function mergeUsage(left, right) {
118
+ for (const usage of [left, right]) {
119
+ const oneHour = usage.cacheCreationInputTokens1h;
120
+ if (oneHour !== undefined &&
121
+ (!Number.isSafeInteger(oneHour) ||
122
+ oneHour < 0 ||
123
+ oneHour > (usage.cacheCreationInputTokens ?? 0))) {
124
+ throw new Error('Model usage has an invalid cacheCreationInputTokens1h counter');
125
+ }
126
+ }
118
127
  const cacheReadInputTokens = (left.cacheReadInputTokens ?? 0) + (right.cacheReadInputTokens ?? 0);
119
128
  const cacheCreationInputTokens = (left.cacheCreationInputTokens ?? 0) + (right.cacheCreationInputTokens ?? 0);
129
+ const cacheCreationInputTokens1h = (left.cacheCreationInputTokens1h ?? 0) +
130
+ (right.cacheCreationInputTokens1h ?? 0);
120
131
  const webSearchRequests = (left.webSearchRequests ?? 0) + (right.webSearchRequests ?? 0);
132
+ if (!Number.isSafeInteger(cacheCreationInputTokens1h) ||
133
+ cacheCreationInputTokens1h > cacheCreationInputTokens) {
134
+ throw new Error('Model usage has an invalid cacheCreationInputTokens1h counter');
135
+ }
121
136
  return {
122
137
  inputTokens: left.inputTokens + right.inputTokens,
123
138
  outputTokens: left.outputTokens + right.outputTokens,
124
139
  ...(cacheReadInputTokens === 0 ? {} : { cacheReadInputTokens }),
125
140
  ...(cacheCreationInputTokens === 0 ? {} : { cacheCreationInputTokens }),
141
+ ...(cacheCreationInputTokens1h === 0 ? {} : { cacheCreationInputTokens1h }),
126
142
  ...(webSearchRequests === 0 ? {} : { webSearchRequests }),
127
143
  };
128
144
  }
@@ -131,6 +147,7 @@ const sessionUsageCounterFields = [
131
147
  'outputTokens',
132
148
  'cacheReadInputTokens',
133
149
  'cacheCreationInputTokens',
150
+ 'cacheCreationInputTokens1h',
134
151
  'webSearchRequests',
135
152
  ];
136
153
  const sessionUsageMetadataFields = ['contextWindow', 'maxOutputTokens'];
@@ -144,6 +161,10 @@ function assertValidSessionUsageEntry(model, usage) {
144
161
  throw new Error(`Model usage for "${model}" has an invalid ${field} counter`);
145
162
  }
146
163
  }
164
+ if ((usage.cacheCreationInputTokens1h ?? 0) >
165
+ (usage.cacheCreationInputTokens ?? 0)) {
166
+ throw new Error(`Model usage for "${model}" has an invalid cacheCreationInputTokens1h counter`);
167
+ }
147
168
  for (const field of sessionUsageMetadataFields) {
148
169
  const value = usage[field];
149
170
  if (value !== undefined && (!Number.isSafeInteger(value) || value < 1)) {
@@ -174,20 +195,27 @@ function addSessionUsageChecked(model, left, right) {
174
195
  const outputTokens = left.outputTokens + right.outputTokens;
175
196
  const cacheReadInputTokens = (left.cacheReadInputTokens ?? 0) + (right.cacheReadInputTokens ?? 0);
176
197
  const cacheCreationInputTokens = (left.cacheCreationInputTokens ?? 0) + (right.cacheCreationInputTokens ?? 0);
198
+ const cacheCreationInputTokens1h = (left.cacheCreationInputTokens1h ?? 0) +
199
+ (right.cacheCreationInputTokens1h ?? 0);
177
200
  const webSearchRequests = (left.webSearchRequests ?? 0) + (right.webSearchRequests ?? 0);
178
201
  if (!Number.isSafeInteger(inputTokens) ||
179
202
  !Number.isSafeInteger(outputTokens) ||
180
203
  !Number.isSafeInteger(cacheReadInputTokens) ||
181
204
  !Number.isSafeInteger(cacheCreationInputTokens) ||
205
+ !Number.isSafeInteger(cacheCreationInputTokens1h) ||
182
206
  !Number.isSafeInteger(webSearchRequests)) {
183
207
  throw new Error('Model usage total overflow');
184
208
  }
209
+ if (cacheCreationInputTokens1h > cacheCreationInputTokens) {
210
+ throw new Error(`Model usage for "${model}" has an invalid cacheCreationInputTokens1h counter`);
211
+ }
185
212
  const metadata = mergeSessionUsageMetadata(model, left, right);
186
213
  return {
187
214
  inputTokens,
188
215
  outputTokens,
189
216
  ...(cacheReadInputTokens === 0 ? {} : { cacheReadInputTokens }),
190
217
  ...(cacheCreationInputTokens === 0 ? {} : { cacheCreationInputTokens }),
218
+ ...(cacheCreationInputTokens1h === 0 ? {} : { cacheCreationInputTokens1h }),
191
219
  ...(webSearchRequests === 0 ? {} : { webSearchRequests }),
192
220
  ...metadata,
193
221
  };
@@ -228,6 +256,7 @@ function hasNonZeroUsage(usage) {
228
256
  usage.outputTokens > 0 ||
229
257
  (usage.cacheReadInputTokens ?? 0) > 0 ||
230
258
  (usage.cacheCreationInputTokens ?? 0) > 0 ||
259
+ (usage.cacheCreationInputTokens1h ?? 0) > 0 ||
231
260
  (usage.webSearchRequests ?? 0) > 0);
232
261
  }
233
262
  function requireUsageCounter(value, field) {
@@ -244,6 +273,12 @@ function requireManualCompactUsage(usage) {
244
273
  if (usage.cacheCreationInputTokens !== undefined) {
245
274
  requireUsageCounter(usage.cacheCreationInputTokens, 'usage.cacheCreationInputTokens');
246
275
  }
276
+ if (usage.cacheCreationInputTokens1h !== undefined) {
277
+ requireUsageCounter(usage.cacheCreationInputTokens1h, 'usage.cacheCreationInputTokens1h');
278
+ if (usage.cacheCreationInputTokens1h > (usage.cacheCreationInputTokens ?? 0)) {
279
+ throw new TypeError('usage.cacheCreationInputTokens1h must not exceed usage.cacheCreationInputTokens');
280
+ }
281
+ }
247
282
  if (usage.webSearchRequests !== undefined) {
248
283
  requireUsageCounter(usage.webSearchRequests, 'usage.webSearchRequests');
249
284
  }
@@ -3935,9 +3970,23 @@ export class ClaudeSessionService {
3935
3970
  permissionUpdates: this.sessionPermissionUpdates.get(sessionId) ?? [],
3936
3971
  onPermissionUpdates: (updates) => this.applyPermissionUpdates(sessionId, updates),
3937
3972
  };
3938
- const attemptMainTurn = () => signal
3939
- ? runtime.run({ ...runtimeRequest, signal })
3940
- : runtime.run(runtimeRequest);
3973
+ const attemptMainTurn = async () => {
3974
+ try {
3975
+ return signal
3976
+ ? await runtime.run({ ...runtimeRequest, signal })
3977
+ : await runtime.run(runtimeRequest);
3978
+ }
3979
+ catch (error) {
3980
+ if (error instanceof AgentBudgetExceededError) {
3981
+ turnAccounting.complete({
3982
+ kind: 'runtime',
3983
+ recovery: recoveryResults,
3984
+ result: error.result,
3985
+ });
3986
+ }
3987
+ throw error;
3988
+ }
3989
+ };
3941
3990
  const surfaceExhaustedRecovery = (error) => {
3942
3991
  this.options.eventSink?.({
3943
3992
  type: 'failed',
@@ -3975,6 +4024,9 @@ export class ClaudeSessionService {
3975
4024
  retryError instanceof AgentRunCancelledError) {
3976
4025
  throw new AgentRunCancelledError();
3977
4026
  }
4027
+ if (retryError instanceof AgentBudgetExceededError) {
4028
+ throw retryError;
4029
+ }
3978
4030
  surfaceExhaustedRecovery(error);
3979
4031
  throw error;
3980
4032
  }
@@ -33,6 +33,7 @@ function mergeSubagentUsage(left, right) {
33
33
  'outputTokens',
34
34
  'cacheReadInputTokens',
35
35
  'cacheCreationInputTokens',
36
+ 'cacheCreationInputTokens1h',
36
37
  'webSearchRequests',
37
38
  ]) {
38
39
  const value = usage[field];
@@ -46,6 +47,10 @@ function mergeSubagentUsage(left, right) {
46
47
  throw new Error(`Subagent usage has an invalid ${field}`);
47
48
  }
48
49
  }
50
+ if ((usage.cacheCreationInputTokens1h ?? 0) >
51
+ (usage.cacheCreationInputTokens ?? 0)) {
52
+ throw new Error('Subagent usage has an invalid cacheCreationInputTokens1h counter');
53
+ }
49
54
  }
50
55
  const counters = {
51
56
  inputTokens: left.inputTokens + right.inputTokens,
@@ -53,11 +58,16 @@ function mergeSubagentUsage(left, right) {
53
58
  cacheReadInputTokens: (left.cacheReadInputTokens ?? 0) + (right.cacheReadInputTokens ?? 0),
54
59
  cacheCreationInputTokens: (left.cacheCreationInputTokens ?? 0) +
55
60
  (right.cacheCreationInputTokens ?? 0),
61
+ cacheCreationInputTokens1h: (left.cacheCreationInputTokens1h ?? 0) +
62
+ (right.cacheCreationInputTokens1h ?? 0),
56
63
  webSearchRequests: (left.webSearchRequests ?? 0) + (right.webSearchRequests ?? 0),
57
64
  };
58
65
  if (Object.values(counters).some((value) => !Number.isSafeInteger(value))) {
59
66
  throw new Error('Subagent usage total overflow');
60
67
  }
68
+ if (counters.cacheCreationInputTokens1h > counters.cacheCreationInputTokens) {
69
+ throw new Error('Subagent usage has an invalid cacheCreationInputTokens1h counter');
70
+ }
61
71
  const metadata = (field) => {
62
72
  const leftValue = left[field];
63
73
  const rightValue = right[field];
@@ -79,6 +89,11 @@ function mergeSubagentUsage(left, right) {
79
89
  ...(counters.cacheCreationInputTokens === 0
80
90
  ? {}
81
91
  : { cacheCreationInputTokens: counters.cacheCreationInputTokens }),
92
+ ...(counters.cacheCreationInputTokens1h === 0
93
+ ? {}
94
+ : {
95
+ cacheCreationInputTokens1h: counters.cacheCreationInputTokens1h,
96
+ }),
82
97
  ...(counters.webSearchRequests === 0
83
98
  ? {}
84
99
  : { webSearchRequests: counters.webSearchRequests }),
@@ -5,6 +5,7 @@ const counterFields = [
5
5
  'outputTokens',
6
6
  'cacheReadInputTokens',
7
7
  'cacheCreationInputTokens',
8
+ 'cacheCreationInputTokens1h',
8
9
  'webSearchRequests',
9
10
  ];
10
11
  const metadataFields = ['contextWindow', 'maxOutputTokens'];
@@ -25,19 +26,26 @@ function mergeUsage(left, right) {
25
26
  }
26
27
  const cacheReadInputTokens = (left.cacheReadInputTokens ?? 0) + (right.cacheReadInputTokens ?? 0);
27
28
  const cacheCreationInputTokens = (left.cacheCreationInputTokens ?? 0) + (right.cacheCreationInputTokens ?? 0);
29
+ const cacheCreationInputTokens1h = (left.cacheCreationInputTokens1h ?? 0) +
30
+ (right.cacheCreationInputTokens1h ?? 0);
28
31
  const webSearchRequests = (left.webSearchRequests ?? 0) + (right.webSearchRequests ?? 0);
29
32
  if (!Number.isSafeInteger(left.inputTokens + right.inputTokens) ||
30
33
  !Number.isSafeInteger(left.outputTokens + right.outputTokens) ||
31
34
  !Number.isSafeInteger(cacheReadInputTokens) ||
32
35
  !Number.isSafeInteger(cacheCreationInputTokens) ||
36
+ !Number.isSafeInteger(cacheCreationInputTokens1h) ||
33
37
  !Number.isSafeInteger(webSearchRequests)) {
34
38
  throw new Error('Model usage total overflow');
35
39
  }
40
+ if (cacheCreationInputTokens1h > cacheCreationInputTokens) {
41
+ throw new Error('Model usage has an invalid cacheCreationInputTokens1h counter');
42
+ }
36
43
  return {
37
44
  inputTokens: left.inputTokens + right.inputTokens,
38
45
  outputTokens: left.outputTokens + right.outputTokens,
39
46
  ...(cacheReadInputTokens === 0 ? {} : { cacheReadInputTokens }),
40
47
  ...(cacheCreationInputTokens === 0 ? {} : { cacheCreationInputTokens }),
48
+ ...(cacheCreationInputTokens1h === 0 ? {} : { cacheCreationInputTokens1h }),
41
49
  ...(webSearchRequests === 0 ? {} : { webSearchRequests }),
42
50
  };
43
51
  }
@@ -51,6 +59,10 @@ function assertValidSessionUsageEntry(model, usage) {
51
59
  throw new Error(`Model usage for "${model}" has an invalid ${field} counter`);
52
60
  }
53
61
  }
62
+ if ((usage.cacheCreationInputTokens1h ?? 0) >
63
+ (usage.cacheCreationInputTokens ?? 0)) {
64
+ throw new Error(`Model usage for "${model}" has an invalid cacheCreationInputTokens1h counter`);
65
+ }
54
66
  for (const field of metadataFields) {
55
67
  const value = usage[field];
56
68
  if (value !== undefined && (!Number.isSafeInteger(value) || value < 1)) {
@@ -81,20 +93,27 @@ function addSessionUsageChecked(model, left, right) {
81
93
  const outputTokens = left.outputTokens + right.outputTokens;
82
94
  const cacheReadInputTokens = (left.cacheReadInputTokens ?? 0) + (right.cacheReadInputTokens ?? 0);
83
95
  const cacheCreationInputTokens = (left.cacheCreationInputTokens ?? 0) + (right.cacheCreationInputTokens ?? 0);
96
+ const cacheCreationInputTokens1h = (left.cacheCreationInputTokens1h ?? 0) +
97
+ (right.cacheCreationInputTokens1h ?? 0);
84
98
  const webSearchRequests = (left.webSearchRequests ?? 0) + (right.webSearchRequests ?? 0);
85
99
  if (!Number.isSafeInteger(inputTokens) ||
86
100
  !Number.isSafeInteger(outputTokens) ||
87
101
  !Number.isSafeInteger(cacheReadInputTokens) ||
88
102
  !Number.isSafeInteger(cacheCreationInputTokens) ||
103
+ !Number.isSafeInteger(cacheCreationInputTokens1h) ||
89
104
  !Number.isSafeInteger(webSearchRequests)) {
90
105
  throw new Error('Model usage total overflow');
91
106
  }
107
+ if (cacheCreationInputTokens1h > cacheCreationInputTokens) {
108
+ throw new Error(`Model usage for "${model}" has an invalid cacheCreationInputTokens1h counter`);
109
+ }
92
110
  const metadata = mergeSessionUsageMetadata(model, left, right);
93
111
  return {
94
112
  inputTokens,
95
113
  outputTokens,
96
114
  ...(cacheReadInputTokens === 0 ? {} : { cacheReadInputTokens }),
97
115
  ...(cacheCreationInputTokens === 0 ? {} : { cacheCreationInputTokens }),
116
+ ...(cacheCreationInputTokens1h === 0 ? {} : { cacheCreationInputTokens1h }),
98
117
  ...(webSearchRequests === 0 ? {} : { webSearchRequests }),
99
118
  ...metadata,
100
119
  };
@@ -119,6 +138,7 @@ function hasNonZeroUsage(usage) {
119
138
  usage.outputTokens > 0 ||
120
139
  (usage.cacheReadInputTokens ?? 0) > 0 ||
121
140
  (usage.cacheCreationInputTokens ?? 0) > 0 ||
141
+ (usage.cacheCreationInputTokens1h ?? 0) > 0 ||
122
142
  (usage.webSearchRequests ?? 0) > 0);
123
143
  }
124
144
  function validateMetricUsage(usage) {
@@ -134,6 +154,10 @@ function validateMetricUsage(usage) {
134
154
  throw new TypeError(`usage.${field} must be a nonnegative safe integer`);
135
155
  }
136
156
  }
157
+ if ((usage.cacheCreationInputTokens1h ?? 0) >
158
+ (usage.cacheCreationInputTokens ?? 0)) {
159
+ throw new TypeError('usage.cacheCreationInputTokens1h must not exceed usage.cacheCreationInputTokens');
160
+ }
137
161
  }
138
162
  function requireMetric(value, field) {
139
163
  if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
@@ -85,6 +85,7 @@ const workflowUsageCounterFields = [
85
85
  'outputTokens',
86
86
  'cacheReadInputTokens',
87
87
  'cacheCreationInputTokens',
88
+ 'cacheCreationInputTokens1h',
88
89
  'webSearchRequests',
89
90
  ];
90
91
  const workflowUsageMetadataFields = [
@@ -98,6 +99,10 @@ function assertValidWorkflowUsage(usage) {
98
99
  throw new Error(`Workflow agent usage has an invalid ${field} counter`);
99
100
  }
100
101
  }
102
+ if ((usage.cacheCreationInputTokens1h ?? 0) >
103
+ (usage.cacheCreationInputTokens ?? 0)) {
104
+ throw new Error('Workflow agent usage has an invalid cacheCreationInputTokens1h counter');
105
+ }
101
106
  }
102
107
  function addWorkflowApiDuration(value, total, field) {
103
108
  if (!Number.isFinite(value) || value < 0) {
@@ -115,14 +120,20 @@ function addWorkflowUsageChecked(model, left, right) {
115
120
  const outputTokens = left.outputTokens + right.outputTokens;
116
121
  const cacheReadInputTokens = (left.cacheReadInputTokens ?? 0) + (right.cacheReadInputTokens ?? 0);
117
122
  const cacheCreationInputTokens = (left.cacheCreationInputTokens ?? 0) + (right.cacheCreationInputTokens ?? 0);
123
+ const cacheCreationInputTokens1h = (left.cacheCreationInputTokens1h ?? 0) +
124
+ (right.cacheCreationInputTokens1h ?? 0);
118
125
  const webSearchRequests = (left.webSearchRequests ?? 0) + (right.webSearchRequests ?? 0);
119
126
  if (!Number.isSafeInteger(inputTokens) ||
120
127
  !Number.isSafeInteger(outputTokens) ||
121
128
  !Number.isSafeInteger(cacheReadInputTokens) ||
122
129
  !Number.isSafeInteger(cacheCreationInputTokens) ||
130
+ !Number.isSafeInteger(cacheCreationInputTokens1h) ||
123
131
  !Number.isSafeInteger(webSearchRequests)) {
124
132
  throw new Error('Workflow model usage total overflow');
125
133
  }
134
+ if (cacheCreationInputTokens1h > cacheCreationInputTokens) {
135
+ throw new Error(`Workflow model usage${model === undefined ? '' : ` for "${model}"`} has an invalid cacheCreationInputTokens1h counter`);
136
+ }
126
137
  // Aggregates without a model stay counter-only; per-model rows merge their
127
138
  // capability metadata with conflict rejection.
128
139
  const metadata = model === undefined ? {} : mergeWorkflowUsageMetadata(model, left, right);
@@ -131,6 +142,7 @@ function addWorkflowUsageChecked(model, left, right) {
131
142
  outputTokens,
132
143
  ...(cacheReadInputTokens === 0 ? {} : { cacheReadInputTokens }),
133
144
  ...(cacheCreationInputTokens === 0 ? {} : { cacheCreationInputTokens }),
145
+ ...(cacheCreationInputTokens1h === 0 ? {} : { cacheCreationInputTokens1h }),
134
146
  ...(webSearchRequests === 0 ? {} : { webSearchRequests }),
135
147
  ...metadata,
136
148
  };
@@ -1,4 +1,5 @@
1
1
  import { randomUUID } from 'node:crypto';
2
+ import { cacheCreationInputTokenSplit } from '../core/usage.js';
2
3
  export function projectProtocolTimings(startedAt, requestAt, outputAt) {
3
4
  return {
4
5
  ...(requestAt === undefined
@@ -13,6 +14,7 @@ export function projectProtocolTimings(startedAt, requestAt, outputAt) {
13
14
  };
14
15
  }
15
16
  function protocolUsage(usage) {
17
+ const cacheCreation = cacheCreationInputTokenSplit(usage);
16
18
  return {
17
19
  input_tokens: usage.inputTokens,
18
20
  cache_creation_input_tokens: usage.cacheCreationInputTokens ?? 0,
@@ -24,8 +26,8 @@ function protocolUsage(usage) {
24
26
  },
25
27
  service_tier: 'standard',
26
28
  cache_creation: {
27
- ephemeral_1h_input_tokens: 0,
28
- ephemeral_5m_input_tokens: 0,
29
+ ephemeral_1h_input_tokens: cacheCreation.oneHour,
30
+ ephemeral_5m_input_tokens: cacheCreation.fiveMinute,
29
31
  },
30
32
  inference_geo: '',
31
33
  iterations: [],
@@ -74,6 +74,8 @@ export interface ModelUsage {
74
74
  outputTokens: number;
75
75
  cacheReadInputTokens?: number;
76
76
  cacheCreationInputTokens?: number;
77
+ /** One-hour cache writes included in cacheCreationInputTokens. */
78
+ cacheCreationInputTokens1h?: number;
77
79
  webSearchRequests?: number;
78
80
  /** Positive safe integer when the completing model's context window is known. */
79
81
  contextWindow?: number;
@@ -563,6 +565,11 @@ export declare class AgentRunCancelledError extends Error {
563
565
  readonly name = "AgentRunCancelledError";
564
566
  constructor();
565
567
  }
568
+ export declare class AgentBudgetExceededError extends Error {
569
+ readonly name = "AgentBudgetExceededError";
570
+ readonly result: AgentRunResult;
571
+ constructor(message: string, result: AgentRunResult);
572
+ }
566
573
  export type RuntimeEventSink = (event: RuntimeEvent) => void;
567
574
  export type ProviderErrorKind = 'authentication_failed' | 'billing_error' | 'rate_limit' | 'invalid_request' | 'server_error' | 'timeout' | 'overloaded' | 'api_error' | 'prompt_too_long' | 'transport_error' | 'cancelled' | 'unknown' | 'max_output_tokens';
568
575
  export declare function modelProviderErrorKind(error: ModelProviderError): ProviderErrorKind;
@@ -55,6 +55,14 @@ export class AgentRunCancelledError extends Error {
55
55
  super('Agent run cancelled');
56
56
  }
57
57
  }
58
+ export class AgentBudgetExceededError extends Error {
59
+ name = 'AgentBudgetExceededError';
60
+ result;
61
+ constructor(message, result) {
62
+ super(message);
63
+ this.result = result;
64
+ }
65
+ }
58
66
  const emptyUsage = () => ({ inputTokens: 0, outputTokens: 0 });
59
67
  export function modelProviderErrorKind(error) {
60
68
  if (error.kind !== undefined)
@@ -76,14 +84,30 @@ export function modelProviderErrorKind(error) {
76
84
  const unsupportedImageResult = 'Provider does not support image tool results';
77
85
  const unsupportedDocumentResult = 'Provider does not support document tool results';
78
86
  function addUsage(left, right) {
87
+ for (const usage of [left, right]) {
88
+ const oneHour = usage.cacheCreationInputTokens1h;
89
+ if (oneHour !== undefined &&
90
+ (!Number.isSafeInteger(oneHour) ||
91
+ oneHour < 0 ||
92
+ oneHour > (usage.cacheCreationInputTokens ?? 0))) {
93
+ throw new Error('Model usage has an invalid cacheCreationInputTokens1h counter');
94
+ }
95
+ }
79
96
  const cacheReadInputTokens = (left.cacheReadInputTokens ?? 0) + (right.cacheReadInputTokens ?? 0);
80
97
  const cacheCreationInputTokens = (left.cacheCreationInputTokens ?? 0) + (right.cacheCreationInputTokens ?? 0);
98
+ const cacheCreationInputTokens1h = (left.cacheCreationInputTokens1h ?? 0) +
99
+ (right.cacheCreationInputTokens1h ?? 0);
81
100
  const webSearchRequests = (left.webSearchRequests ?? 0) + (right.webSearchRequests ?? 0);
101
+ if (!Number.isSafeInteger(cacheCreationInputTokens1h) ||
102
+ cacheCreationInputTokens1h > cacheCreationInputTokens) {
103
+ throw new Error('Model usage has an invalid cacheCreationInputTokens1h counter');
104
+ }
82
105
  return {
83
106
  inputTokens: left.inputTokens + right.inputTokens,
84
107
  outputTokens: left.outputTokens + right.outputTokens,
85
108
  ...(cacheReadInputTokens === 0 ? {} : { cacheReadInputTokens }),
86
109
  ...(cacheCreationInputTokens === 0 ? {} : { cacheCreationInputTokens }),
110
+ ...(cacheCreationInputTokens1h === 0 ? {} : { cacheCreationInputTokens1h }),
87
111
  ...(webSearchRequests === 0 ? {} : { webSearchRequests }),
88
112
  };
89
113
  }
@@ -126,6 +150,7 @@ const modelUsageCounterFields = [
126
150
  'outputTokens',
127
151
  'cacheReadInputTokens',
128
152
  'cacheCreationInputTokens',
153
+ 'cacheCreationInputTokens1h',
129
154
  'webSearchRequests',
130
155
  ];
131
156
  const modelUsageMetadataFields = ['contextWindow', 'maxOutputTokens'];
@@ -134,6 +159,7 @@ function hasNonZeroModelUsage(usage) {
134
159
  usage.outputTokens > 0 ||
135
160
  (usage.cacheReadInputTokens ?? 0) > 0 ||
136
161
  (usage.cacheCreationInputTokens ?? 0) > 0 ||
162
+ (usage.cacheCreationInputTokens1h ?? 0) > 0 ||
137
163
  (usage.webSearchRequests ?? 0) > 0);
138
164
  }
139
165
  function assertValidModelUsageEntry(model, usage) {
@@ -146,6 +172,10 @@ function assertValidModelUsageEntry(model, usage) {
146
172
  throw new Error(`Model usage for "${model}" has an invalid ${field} counter`);
147
173
  }
148
174
  }
175
+ if ((usage.cacheCreationInputTokens1h ?? 0) >
176
+ (usage.cacheCreationInputTokens ?? 0)) {
177
+ throw new Error(`Model usage for "${model}" has an invalid cacheCreationInputTokens1h counter`);
178
+ }
149
179
  for (const field of modelUsageMetadataFields) {
150
180
  const value = usage[field];
151
181
  if (value !== undefined && (!Number.isSafeInteger(value) || value < 1)) {
@@ -176,20 +206,27 @@ function addUsageChecked(model, left, right) {
176
206
  const outputTokens = left.outputTokens + right.outputTokens;
177
207
  const cacheReadInputTokens = (left.cacheReadInputTokens ?? 0) + (right.cacheReadInputTokens ?? 0);
178
208
  const cacheCreationInputTokens = (left.cacheCreationInputTokens ?? 0) + (right.cacheCreationInputTokens ?? 0);
209
+ const cacheCreationInputTokens1h = (left.cacheCreationInputTokens1h ?? 0) +
210
+ (right.cacheCreationInputTokens1h ?? 0);
179
211
  const webSearchRequests = (left.webSearchRequests ?? 0) + (right.webSearchRequests ?? 0);
180
212
  if (!Number.isSafeInteger(inputTokens) ||
181
213
  !Number.isSafeInteger(outputTokens) ||
182
214
  !Number.isSafeInteger(cacheReadInputTokens) ||
183
215
  !Number.isSafeInteger(cacheCreationInputTokens) ||
216
+ !Number.isSafeInteger(cacheCreationInputTokens1h) ||
184
217
  !Number.isSafeInteger(webSearchRequests)) {
185
218
  throw new Error('Model usage total overflow');
186
219
  }
220
+ if (cacheCreationInputTokens1h > cacheCreationInputTokens) {
221
+ throw new Error(`Model usage for "${model}" has an invalid cacheCreationInputTokens1h counter`);
222
+ }
187
223
  const metadata = mergeModelUsageMetadata(model, left, right);
188
224
  return {
189
225
  inputTokens,
190
226
  outputTokens,
191
227
  ...(cacheReadInputTokens === 0 ? {} : { cacheReadInputTokens }),
192
228
  ...(cacheCreationInputTokens === 0 ? {} : { cacheCreationInputTokens }),
229
+ ...(cacheCreationInputTokens1h === 0 ? {} : { cacheCreationInputTokens1h }),
193
230
  ...(webSearchRequests === 0 ? {} : { webSearchRequests }),
194
231
  ...metadata,
195
232
  };
@@ -337,6 +374,30 @@ export class AgentRuntime {
337
374
  let linesRemoved = 0;
338
375
  let activeAttemptHasPresentation = false;
339
376
  let activeAttemptDiscarded = false;
377
+ const materializeResult = (text) => {
378
+ const modelUsage = modelUsageByModel.size === 0
379
+ ? undefined
380
+ : Object.fromEntries(modelUsageByModel);
381
+ return {
382
+ text,
383
+ usage,
384
+ ...(modelUsage === undefined ? {} : { modelUsage }),
385
+ ...(sawExternallyMeteredSummary
386
+ ? {
387
+ unrecordedModelUsage: Object.fromEntries(unrecordedModelUsageByModel),
388
+ unrecordedDurationApiMs,
389
+ unrecordedDurationApiWithoutRetriesMs,
390
+ }
391
+ : {}),
392
+ ...(durationApiMs === 0 ? {} : { durationApiMs }),
393
+ ...(durationApiMs === 0 && durationApiWithoutRetriesMs === 0
394
+ ? {}
395
+ : { durationApiWithoutRetriesMs }),
396
+ ...(durationToolMs === 0 ? {} : { durationToolMs }),
397
+ ...(linesAdded === 0 ? {} : { linesAdded }),
398
+ ...(linesRemoved === 0 ? {} : { linesRemoved }),
399
+ };
400
+ };
340
401
  const maxModelTurns = request.maxModelTurns ?? this.options.maxModelTurns;
341
402
  if (maxModelTurns !== undefined &&
342
403
  (!Number.isSafeInteger(maxModelTurns) || maxModelTurns <= 0)) {
@@ -399,7 +460,7 @@ export class AgentRuntime {
399
460
  if (this.options.maxBudgetUsd !== undefined &&
400
461
  spent !== undefined &&
401
462
  spent >= this.options.maxBudgetUsd) {
402
- throw new Error(`Maximum budget of $${this.options.maxBudgetUsd.toFixed(6)} exceeded`);
463
+ throw new AgentBudgetExceededError(`Maximum budget of $${this.options.maxBudgetUsd.toFixed(6)} exceeded`, materializeResult(''));
403
464
  }
404
465
  modelTurns += 1;
405
466
  this.emit({ type: 'state', state: 'awaiting-model' });
@@ -698,28 +759,7 @@ export class AgentRuntime {
698
759
  continue;
699
760
  }
700
761
  this.emit({ type: 'state', state: 'completed' });
701
- const modelUsage = modelUsageByModel.size === 0
702
- ? undefined
703
- : Object.fromEntries(modelUsageByModel);
704
- return {
705
- text,
706
- usage,
707
- ...(modelUsage === undefined ? {} : { modelUsage }),
708
- ...(sawExternallyMeteredSummary
709
- ? {
710
- unrecordedModelUsage: Object.fromEntries(unrecordedModelUsageByModel),
711
- unrecordedDurationApiMs,
712
- unrecordedDurationApiWithoutRetriesMs,
713
- }
714
- : {}),
715
- ...(durationApiMs === 0 ? {} : { durationApiMs }),
716
- ...(durationApiMs === 0 && durationApiWithoutRetriesMs === 0
717
- ? {}
718
- : { durationApiWithoutRetriesMs }),
719
- ...(durationToolMs === 0 ? {} : { durationToolMs }),
720
- ...(linesAdded === 0 ? {} : { linesAdded }),
721
- ...(linesRemoved === 0 ? {} : { linesRemoved }),
722
- };
762
+ return materializeResult(text);
723
763
  }
724
764
  const scheduledToolResults = await toolScheduler.settle();
725
765
  const followUpUserMessages = [];
@@ -4,6 +4,9 @@ export interface ModelPricing {
4
4
  outputPerMillionUsd: number;
5
5
  cacheReadInputPerMillionUsd?: number;
6
6
  cacheCreationInputPerMillionUsd?: number;
7
+ /** Falls back to cacheCreationInputPerMillionUsd when omitted. */
8
+ cacheCreationInputPerMillionUsd1h?: number;
9
+ webSearchPerRequestUsd?: number;
7
10
  }
8
11
  export type ModelPricingTable = Readonly<Record<string, ModelPricing>>;
9
12
  export type ModelPricingSource = 'builtin' | 'environment' | 'unknown';
@@ -18,9 +21,15 @@ export declare class ModelPricingRegistry {
18
21
  private readonly table;
19
22
  private readonly environmentModels;
20
23
  constructor(overrides?: ModelPricingTable);
24
+ private resolveKey;
21
25
  resolve(model: string): ModelPricing | undefined;
22
26
  diagnose(model: string): ModelPricingDiagnosis;
23
27
  static fromEnvironment(value: string | undefined): ModelPricingRegistry;
24
28
  }
29
+ export declare function cacheCreationInputTokenSplit(usage: ModelUsage): {
30
+ total: number;
31
+ fiveMinute: number;
32
+ oneHour: number;
33
+ };
25
34
  export declare function usageCostUsd(usage: ModelUsage, pricing: ModelPricing): number;
26
35
  //# sourceMappingURL=usage.d.ts.map
@@ -1,28 +1,67 @@
1
+ function anthropicPricing(inputPerMillionUsd, outputPerMillionUsd, cacheReadInputPerMillionUsd, cacheCreationInputPerMillionUsd) {
2
+ return {
3
+ inputPerMillionUsd,
4
+ outputPerMillionUsd,
5
+ cacheReadInputPerMillionUsd,
6
+ cacheCreationInputPerMillionUsd,
7
+ cacheCreationInputPerMillionUsd1h: inputPerMillionUsd * 2,
8
+ webSearchPerRequestUsd: 0.01,
9
+ };
10
+ }
1
11
  const BUILTIN_PRICING = {
2
12
  'claude-3-5-sonnet-20241022': {
3
13
  inputPerMillionUsd: 3,
4
14
  outputPerMillionUsd: 15,
5
15
  cacheReadInputPerMillionUsd: 0.3,
6
16
  cacheCreationInputPerMillionUsd: 3.75,
17
+ cacheCreationInputPerMillionUsd1h: 6,
18
+ webSearchPerRequestUsd: 0.01,
7
19
  },
8
20
  'claude-3-7-sonnet-20250219': {
9
21
  inputPerMillionUsd: 3,
10
22
  outputPerMillionUsd: 15,
11
23
  cacheReadInputPerMillionUsd: 0.3,
12
24
  cacheCreationInputPerMillionUsd: 3.75,
25
+ cacheCreationInputPerMillionUsd1h: 6,
26
+ webSearchPerRequestUsd: 0.01,
13
27
  },
14
28
  'claude-sonnet-4-20250514': {
15
29
  inputPerMillionUsd: 3,
16
30
  outputPerMillionUsd: 15,
17
31
  cacheReadInputPerMillionUsd: 0.3,
18
32
  cacheCreationInputPerMillionUsd: 3.75,
33
+ cacheCreationInputPerMillionUsd1h: 6,
34
+ webSearchPerRequestUsd: 0.01,
19
35
  },
20
36
  'claude-opus-4-20250514': {
21
37
  inputPerMillionUsd: 15,
22
38
  outputPerMillionUsd: 75,
23
39
  cacheReadInputPerMillionUsd: 1.5,
24
40
  cacheCreationInputPerMillionUsd: 18.75,
41
+ cacheCreationInputPerMillionUsd1h: 30,
42
+ webSearchPerRequestUsd: 0.01,
25
43
  },
44
+ 'claude-fable-5-1': anthropicPricing(10, 50, 0.25, 12.5),
45
+ 'claude-opus-5': anthropicPricing(5, 25, 0.5, 6.25),
46
+ 'claude-opus-4-8': anthropicPricing(5, 25, 0.5, 6.25),
47
+ 'claude-opus-4-7': anthropicPricing(5, 25, 0.5, 6.25),
48
+ 'claude-opus-4-6': anthropicPricing(5, 25, 0.5, 6.25),
49
+ 'claude-opus-4-5': anthropicPricing(5, 25, 0.5, 6.25),
50
+ 'claude-opus-4-5-20251101': anthropicPricing(5, 25, 0.5, 6.25),
51
+ 'claude-sonnet-5': anthropicPricing(2, 10, 0.2, 2.5),
52
+ 'claude-sonnet-4-6': anthropicPricing(3, 15, 0.3, 3.75),
53
+ 'claude-sonnet-4-5': anthropicPricing(3, 15, 0.3, 3.75),
54
+ 'claude-sonnet-4-5-20250929': anthropicPricing(3, 15, 0.3, 3.75),
55
+ 'claude-haiku-4-5': anthropicPricing(1, 5, 0.1, 1.25),
56
+ 'claude-haiku-4-5-20251001': anthropicPricing(1, 5, 0.1, 1.25),
57
+ 'claude-opus-4': anthropicPricing(15, 75, 1.5, 18.75),
58
+ 'claude-opus-4-1': anthropicPricing(15, 75, 1.5, 18.75),
59
+ 'claude-opus-4-1-20250805': anthropicPricing(15, 75, 1.5, 18.75),
60
+ 'claude-sonnet-4': anthropicPricing(3, 15, 0.3, 3.75),
61
+ 'claude-3-5-sonnet-20240620': anthropicPricing(3, 15, 0.3, 3.75),
62
+ 'claude-3-5-sonnet-latest': anthropicPricing(3, 15, 0.3, 3.75),
63
+ 'claude-3-5-haiku-20241022': anthropicPricing(0.8, 4, 0.08, 1),
64
+ 'claude-3-5-haiku-latest': anthropicPricing(0.8, 4, 0.08, 1),
26
65
  'gpt-4o': {
27
66
  inputPerMillionUsd: 5,
28
67
  outputPerMillionUsd: 15,
@@ -47,12 +86,20 @@ function parsePricing(value, label) {
47
86
  }
48
87
  const cacheRead = record.cacheReadInputPerMillionUsd;
49
88
  const cacheCreation = record.cacheCreationInputPerMillionUsd;
89
+ const cacheCreation1h = record.cacheCreationInputPerMillionUsd1h;
90
+ const webSearch = record.webSearchPerRequestUsd;
50
91
  if (cacheRead !== undefined && !validRate(cacheRead)) {
51
92
  throw new Error(`${label}.cacheReadInputPerMillionUsd must be non-negative`);
52
93
  }
53
94
  if (cacheCreation !== undefined && !validRate(cacheCreation)) {
54
95
  throw new Error(`${label}.cacheCreationInputPerMillionUsd must be non-negative`);
55
96
  }
97
+ if (cacheCreation1h !== undefined && !validRate(cacheCreation1h)) {
98
+ throw new Error(`${label}.cacheCreationInputPerMillionUsd1h must be non-negative`);
99
+ }
100
+ if (webSearch !== undefined && !validRate(webSearch)) {
101
+ throw new Error(`${label}.webSearchPerRequestUsd must be non-negative`);
102
+ }
56
103
  return {
57
104
  inputPerMillionUsd: input,
58
105
  outputPerMillionUsd: output,
@@ -62,6 +109,10 @@ function parsePricing(value, label) {
62
109
  ...(cacheCreation === undefined
63
110
  ? {}
64
111
  : { cacheCreationInputPerMillionUsd: cacheCreation }),
112
+ ...(cacheCreation1h === undefined
113
+ ? {}
114
+ : { cacheCreationInputPerMillionUsd1h: cacheCreation1h }),
115
+ ...(webSearch === undefined ? {} : { webSearchPerRequestUsd: webSearch }),
65
116
  };
66
117
  }
67
118
  export class ModelPricingRegistry {
@@ -71,12 +122,36 @@ export class ModelPricingRegistry {
71
122
  this.table = { ...BUILTIN_PRICING, ...overrides };
72
123
  this.environmentModels = new Set(Object.keys(overrides));
73
124
  }
125
+ resolveKey(model) {
126
+ if (Object.prototype.hasOwnProperty.call(this.table, model)) {
127
+ return model;
128
+ }
129
+ const suffix = '[1m]';
130
+ if (!model.endsWith(suffix))
131
+ return undefined;
132
+ const base = model.slice(0, -suffix.length);
133
+ if (base.length === 0 || base.endsWith(suffix))
134
+ return undefined;
135
+ return Object.prototype.hasOwnProperty.call(this.table, base)
136
+ ? base
137
+ : undefined;
138
+ }
74
139
  resolve(model) {
75
- return this.table[model];
140
+ const key = this.resolveKey(model);
141
+ return key === undefined ? undefined : this.table[key];
76
142
  }
77
143
  diagnose(model) {
78
- const pricing = this.resolve(model);
79
- if (!pricing) {
144
+ const key = this.resolveKey(model);
145
+ if (key === undefined) {
146
+ return {
147
+ model,
148
+ source: 'unknown',
149
+ policy: 'fail-closed',
150
+ budgetBehavior: 'reject-before-provider',
151
+ };
152
+ }
153
+ const pricing = this.table[key];
154
+ if (pricing === undefined) {
80
155
  return {
81
156
  model,
82
157
  source: 'unknown',
@@ -86,7 +161,7 @@ export class ModelPricingRegistry {
86
161
  }
87
162
  return {
88
163
  model,
89
- source: this.environmentModels.has(model) ? 'environment' : 'builtin',
164
+ source: this.environmentModels.has(key) ? 'environment' : 'builtin',
90
165
  pricing,
91
166
  policy: 'fail-closed',
92
167
  budgetBehavior: 'enforce',
@@ -115,19 +190,36 @@ export class ModelPricingRegistry {
115
190
  return new ModelPricingRegistry(overrides);
116
191
  }
117
192
  }
193
+ export function cacheCreationInputTokenSplit(usage) {
194
+ const total = usage.cacheCreationInputTokens ?? 0;
195
+ const oneHour = usage.cacheCreationInputTokens1h ?? 0;
196
+ if (!Number.isSafeInteger(total) ||
197
+ total < 0 ||
198
+ !Number.isSafeInteger(oneHour) ||
199
+ oneHour < 0 ||
200
+ oneHour > total) {
201
+ throw new Error('cacheCreationInputTokens1h must be a nonnegative safe integer no greater than cacheCreationInputTokens');
202
+ }
203
+ return { total, fiveMinute: total - oneHour, oneHour };
204
+ }
118
205
  function regularInputTokens(usage) {
119
- return Math.max(0, usage.inputTokens -
120
- (usage.cacheReadInputTokens ?? 0) -
121
- (usage.cacheCreationInputTokens ?? 0));
206
+ const { total } = cacheCreationInputTokenSplit(usage);
207
+ return Math.max(0, usage.inputTokens - (usage.cacheReadInputTokens ?? 0) - total);
122
208
  }
123
209
  export function usageCostUsd(usage, pricing) {
210
+ const cacheCreation = cacheCreationInputTokenSplit(usage);
124
211
  const regularInput = regularInputTokens(usage);
125
212
  const input = regularInput * pricing.inputPerMillionUsd;
126
213
  const output = usage.outputTokens * pricing.outputPerMillionUsd;
127
214
  const cacheRead = (usage.cacheReadInputTokens ?? 0) *
128
215
  (pricing.cacheReadInputPerMillionUsd ?? pricing.inputPerMillionUsd);
129
- const cacheCreation = (usage.cacheCreationInputTokens ?? 0) *
130
- (pricing.cacheCreationInputPerMillionUsd ?? pricing.inputPerMillionUsd);
131
- return (input + output + cacheRead + cacheCreation) / 1_000_000;
216
+ const cacheCreationCost = cacheCreation.fiveMinute *
217
+ (pricing.cacheCreationInputPerMillionUsd ?? pricing.inputPerMillionUsd) +
218
+ cacheCreation.oneHour *
219
+ (pricing.cacheCreationInputPerMillionUsd1h ??
220
+ pricing.cacheCreationInputPerMillionUsd ??
221
+ pricing.inputPerMillionUsd);
222
+ return ((input + output + cacheRead + cacheCreationCost) / 1_000_000 +
223
+ (usage.webSearchRequests ?? 0) * (pricing.webSearchPerRequestUsd ?? 0));
132
224
  }
133
225
  //# sourceMappingURL=usage.js.map
@@ -8,6 +8,7 @@ export interface PersistedSubagentRunResult {
8
8
  outputTokens: number;
9
9
  cacheReadInputTokens?: number;
10
10
  cacheCreationInputTokens?: number;
11
+ cacheCreationInputTokens1h?: number;
11
12
  webSearchRequests?: number;
12
13
  contextWindow?: number;
13
14
  maxOutputTokens?: number;
@@ -38,10 +38,14 @@ function isUsage(value) {
38
38
  for (const field of [
39
39
  'cacheReadInputTokens',
40
40
  'cacheCreationInputTokens',
41
+ 'cacheCreationInputTokens1h',
41
42
  'webSearchRequests',
42
43
  ])
43
44
  if (usage[field] !== undefined && !isNonnegativeInteger(usage[field]))
44
45
  return false;
46
+ if ((usage.cacheCreationInputTokens1h ?? 0) >
47
+ (usage.cacheCreationInputTokens ?? 0))
48
+ return false;
45
49
  for (const field of ['contextWindow', 'maxOutputTokens'])
46
50
  if (usage[field] !== undefined &&
47
51
  (!Number.isSafeInteger(usage[field]) || Number(usage[field]) < 1))
@@ -16,6 +16,28 @@ function readNonNegativeTokenCount(usage, field, required) {
16
16
  }
17
17
  return value;
18
18
  }
19
+ function readCacheCreationInputTokens1h(usage) {
20
+ const detail = usage.cache_creation;
21
+ if (detail === undefined)
22
+ return undefined;
23
+ if (!isRecord(detail)) {
24
+ throw new ModelProviderError('Provider returned an invalid cache_creation usage object', { retryable: false });
25
+ }
26
+ const total = readNonNegativeTokenCount(usage, 'cache_creation_input_tokens', true);
27
+ if (total === undefined) {
28
+ throw new ModelProviderError('Provider returned cache_creation usage without cache_creation_input_tokens', { retryable: false });
29
+ }
30
+ const fiveMinute = readNonNegativeTokenCount(detail, 'ephemeral_5m_input_tokens', true);
31
+ const oneHour = readNonNegativeTokenCount(detail, 'ephemeral_1h_input_tokens', true);
32
+ if (fiveMinute === undefined || oneHour === undefined) {
33
+ throw new ModelProviderError('Provider returned an incomplete cache_creation usage object', { retryable: false });
34
+ }
35
+ const sum = fiveMinute + oneHour;
36
+ if (!Number.isSafeInteger(sum) || sum !== total) {
37
+ throw new ModelProviderError('Provider returned cache_creation usage whose TTL counters do not sum to cache_creation_input_tokens', { retryable: false });
38
+ }
39
+ return oneHour;
40
+ }
19
41
  function webSearchLinks(value) {
20
42
  if (!Array.isArray(value))
21
43
  return [];
@@ -207,6 +229,10 @@ function parseSseEvent(data, state, maxToolArgumentsBytes, maxToolCallsPerRespon
207
229
  typeof usage.cache_creation_input_tokens === 'number'
208
230
  ? usage.cache_creation_input_tokens
209
231
  : 0;
232
+ const cacheCreationInputTokens1h = readCacheCreationInputTokens1h(usage);
233
+ if (cacheCreationInputTokens1h !== undefined) {
234
+ state.cacheCreationInputTokens1h = cacheCreationInputTokens1h;
235
+ }
210
236
  state.cacheReadInputTokens =
211
237
  typeof usage.cache_read_input_tokens === 'number'
212
238
  ? usage.cache_read_input_tokens
@@ -466,6 +492,10 @@ function parseSseEvent(data, state, maxToolArgumentsBytes, maxToolCallsPerRespon
466
492
  ...(state.cacheCreationInputTokens === 0
467
493
  ? {}
468
494
  : { cacheCreationInputTokens: state.cacheCreationInputTokens }),
495
+ ...(state.cacheCreationInputTokens1h === undefined ||
496
+ state.cacheCreationInputTokens1h === 0
497
+ ? {}
498
+ : { cacheCreationInputTokens1h: state.cacheCreationInputTokens1h }),
469
499
  ...(state.webSearchRequests === 0
470
500
  ? {}
471
501
  : { webSearchRequests: state.webSearchRequests }),
@@ -920,6 +950,8 @@ export class AnthropicCompatibleProvider {
920
950
  const outputTokens = readNonNegativeTokenCount(payload.usage, 'output_tokens', true);
921
951
  const cacheReadInputTokens = readNonNegativeTokenCount(payload.usage, 'cache_read_input_tokens', false);
922
952
  const cacheCreationInputTokens = readNonNegativeTokenCount(payload.usage, 'cache_creation_input_tokens', false);
953
+ const cacheCreationInputTokens1h = readCacheCreationInputTokens1h(payload.usage);
954
+ const normalizedCacheCreationInputTokens = cacheCreationInputTokens ?? 0;
923
955
  const usage = {
924
956
  input_tokens: inputTokens,
925
957
  output_tokens: outputTokens,
@@ -929,6 +961,15 @@ export class AnthropicCompatibleProvider {
929
961
  ...(cacheCreationInputTokens === undefined
930
962
  ? {}
931
963
  : { cache_creation_input_tokens: cacheCreationInputTokens }),
964
+ ...(cacheCreationInputTokens1h === undefined
965
+ ? {}
966
+ : {
967
+ cache_creation: {
968
+ ephemeral_5m_input_tokens: normalizedCacheCreationInputTokens -
969
+ cacheCreationInputTokens1h,
970
+ ephemeral_1h_input_tokens: cacheCreationInputTokens1h,
971
+ },
972
+ }),
932
973
  ...(payload.usage.server_tool_use === undefined
933
974
  ? {}
934
975
  : { server_tool_use: payload.usage.server_tool_use }),
@@ -0,0 +1,4 @@
1
+ export type AnthropicModelAliasOverrides = Readonly<Partial<Record<'sonnet' | 'opus' | 'haiku', string>>>;
2
+ export declare function anthropicModelAliasOverridesFromEnvironment(environment: Readonly<Record<string, string | undefined>>): AnthropicModelAliasOverrides;
3
+ export declare function resolveAnthropicModelAlias(model: string, overrides?: AnthropicModelAliasOverrides): string;
4
+ //# sourceMappingURL=anthropic-model-alias.d.ts.map
@@ -0,0 +1,37 @@
1
+ const DEFAULTS = {
2
+ sonnet: 'claude-sonnet-5',
3
+ opus: 'claude-opus-5',
4
+ haiku: 'claude-haiku-4-5-20251001',
5
+ };
6
+ const ENVIRONMENT_KEYS = {
7
+ sonnet: 'ANTHROPIC_DEFAULT_SONNET_MODEL',
8
+ opus: 'ANTHROPIC_DEFAULT_OPUS_MODEL',
9
+ haiku: 'ANTHROPIC_DEFAULT_HAIKU_MODEL',
10
+ };
11
+ export function anthropicModelAliasOverridesFromEnvironment(environment) {
12
+ const overrides = {};
13
+ for (const family of ['sonnet', 'opus', 'haiku']) {
14
+ const value = environment[ENVIRONMENT_KEYS[family]];
15
+ if (value === undefined || value.trim().length === 0)
16
+ continue;
17
+ if (value.length > 256) {
18
+ throw new Error(`${ENVIRONMENT_KEYS[family]} must be at most 256 characters`);
19
+ }
20
+ overrides[family] = value;
21
+ }
22
+ return overrides;
23
+ }
24
+ export function resolveAnthropicModelAlias(model, overrides = {}) {
25
+ const longContext = model.endsWith('[1m]');
26
+ const family = longContext ? model.slice(0, -'[1m]'.length) : model;
27
+ if (family !== 'sonnet' && family !== 'opus' && family !== 'haiku') {
28
+ return model;
29
+ }
30
+ if (longContext && family === 'haiku')
31
+ return model;
32
+ const resolved = overrides[family] ?? DEFAULTS[family];
33
+ if (!longContext || resolved.endsWith('[1m]'))
34
+ return resolved;
35
+ return `${resolved}[1m]`;
36
+ }
37
+ //# sourceMappingURL=anthropic-model-alias.js.map
@@ -1,6 +1,7 @@
1
1
  import type { ModelProvider, ModelThinkingConfig } from '../core/runtime.js';
2
2
  import { type CodexOAuthVault } from './codex-oauth.js';
3
3
  import { type ProviderProtocol, type ProviderTarget } from './provider-settings.js';
4
+ import { type AnthropicModelAliasOverrides } from './anthropic-model-alias.js';
4
5
  import type { ProviderCredentialSourceMetadata, ProviderCredentialReader, ResolvedProviderCredential } from './provider-auth.js';
5
6
  import { parseProviderEnvironment, type ContextEnvironment } from './environment.js';
6
7
  import { type AnthropicPromptCachePolicy } from './anthropic-prompt-cache.js';
@@ -22,6 +23,7 @@ export interface ProviderRegistryOptions {
22
23
  }) => AnthropicPromptCachePolicy;
23
24
  fetchImplementation?: typeof fetch;
24
25
  providerEnvironment?: ReturnType<typeof parseProviderEnvironment>;
26
+ anthropicModelAliasOverrides?: AnthropicModelAliasOverrides;
25
27
  vault?: CodexOAuthVault;
26
28
  }
27
29
  export interface ResolveProviderRegistryOptions {
@@ -7,6 +7,7 @@ import { NonStreamingFallbackModelProvider } from './non-streaming-fallback-prov
7
7
  import { CodexOAuthCredentialManager, } from './codex-oauth.js';
8
8
  import { resolveProviderTarget, } from './provider-settings.js';
9
9
  import { resolveAnthropicModelSpec } from './anthropic-model-spec.js';
10
+ import { anthropicModelAliasOverridesFromEnvironment, resolveAnthropicModelAlias, } from './anthropic-model-alias.js';
10
11
  import { ProviderAuthenticationError, resolveProviderCredential, } from './provider-auth.js';
11
12
  import { parseContextEnvironment, parseProviderEnvironment, } from './environment.js';
12
13
  import { createAnthropicPromptCachePolicyResolver, } from './anthropic-prompt-cache.js';
@@ -55,6 +56,10 @@ export async function resolveProviderRegistry(options) {
55
56
  PRAXIS_BASE_URL: target.baseUrl,
56
57
  };
57
58
  const providerEnvironment = parseProviderEnvironment(controlsEnvironment);
59
+ const anthropicModelAliasOverrides = target.providerId === 'anthropic' &&
60
+ target.protocol === 'anthropic-messages'
61
+ ? anthropicModelAliasOverridesFromEnvironment(environment)
62
+ : undefined;
58
63
  const promptCacheResolver = target.protocol === 'anthropic-messages'
59
64
  ? createAnthropicPromptCachePolicyResolver(controlsEnvironment)
60
65
  : undefined;
@@ -74,6 +79,9 @@ export async function resolveProviderRegistry(options) {
74
79
  ...(promptCacheResolver === undefined
75
80
  ? {}
76
81
  : { anthropicPromptCacheResolver: promptCacheResolver }),
82
+ ...(anthropicModelAliasOverrides === undefined
83
+ ? {}
84
+ : { anthropicModelAliasOverrides }),
77
85
  ...(options.fetchImplementation === undefined
78
86
  ? {}
79
87
  : { fetchImplementation: options.fetchImplementation }),
@@ -91,7 +99,7 @@ class NativeProviderRegistry {
91
99
  codexManager;
92
100
  constructor(options) {
93
101
  this.options = options;
94
- this.target = options.target;
102
+ this.target = this.resolveTarget(options.target);
95
103
  this.credentialSource = options.credential.source;
96
104
  if (options.target.protocol === 'codex-subscription') {
97
105
  if (options.credential.type !== 'oauth') {
@@ -106,7 +114,7 @@ class NativeProviderRegistry {
106
114
  }
107
115
  }
108
116
  create(modelId = this.target.modelId) {
109
- const target = { ...this.target, modelId };
117
+ const target = this.resolveTarget({ ...this.target, modelId });
110
118
  if (target.protocol === 'codex-subscription') {
111
119
  if (!this.codexManager)
112
120
  throw new ProviderAuthenticationError('invalid_credential', 'Provider authentication failed: Codex subscription credentials are unavailable');
@@ -220,6 +228,15 @@ class NativeProviderRegistry {
220
228
  }
221
229
  throw new ProviderRegistryError('unsupported_provider', `Unsupported provider protocol: ${target.protocol}`);
222
230
  }
231
+ resolveTarget(target) {
232
+ if (target.providerId !== 'anthropic' ||
233
+ target.protocol !== 'anthropic-messages')
234
+ return target;
235
+ return {
236
+ ...target,
237
+ modelId: resolveAnthropicModelAlias(target.modelId, this.options.anthropicModelAliasOverrides),
238
+ };
239
+ }
223
240
  withDeadline(provider) {
224
241
  const environment = this.options.providerEnvironment;
225
242
  return environment === undefined
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "praxis-agent",
3
- "version": "0.62.5",
3
+ "version": "0.62.7",
4
4
  "description": "Local-first, single-user general agent for the command line.",
5
5
  "license": "MIT",
6
6
  "author": "wuqisen",