@proteinjs/conversation 2.5.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/index.d.ts +2 -1
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +2 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/src/Conversation.d.ts.map +1 -1
  7. package/dist/src/Conversation.js +12 -16
  8. package/dist/src/Conversation.js.map +1 -1
  9. package/dist/src/OpenAi.js +3 -3
  10. package/dist/src/OpenAi.js.map +1 -1
  11. package/dist/src/OpenAiResponses.d.ts +158 -0
  12. package/dist/src/OpenAiResponses.d.ts.map +1 -0
  13. package/dist/src/OpenAiResponses.js +1621 -0
  14. package/dist/src/OpenAiResponses.js.map +1 -0
  15. package/dist/src/OpenAiStreamProcessor.js +4 -4
  16. package/dist/src/OpenAiStreamProcessor.js.map +1 -1
  17. package/dist/src/UsageData.d.ts +39 -4
  18. package/dist/src/UsageData.d.ts.map +1 -1
  19. package/dist/src/UsageData.js +302 -11
  20. package/dist/src/UsageData.js.map +1 -1
  21. package/dist/src/fs/conversation_fs/ConversationFsModule.d.ts.map +1 -1
  22. package/dist/src/fs/conversation_fs/ConversationFsModule.js +1 -0
  23. package/dist/src/fs/conversation_fs/ConversationFsModule.js.map +1 -1
  24. package/dist/src/fs/conversation_fs/FsFunctions.d.ts +26 -0
  25. package/dist/src/fs/conversation_fs/FsFunctions.d.ts.map +1 -1
  26. package/dist/src/fs/conversation_fs/FsFunctions.js +68 -27
  27. package/dist/src/fs/conversation_fs/FsFunctions.js.map +1 -1
  28. package/index.ts +2 -1
  29. package/package.json +4 -4
  30. package/src/Conversation.ts +14 -17
  31. package/src/OpenAi.ts +3 -3
  32. package/src/OpenAiResponses.ts +1869 -0
  33. package/src/OpenAiStreamProcessor.ts +3 -3
  34. package/src/UsageData.ts +376 -13
  35. package/src/fs/conversation_fs/ConversationFsModule.ts +2 -0
  36. package/src/fs/conversation_fs/FsFunctions.ts +32 -2
@@ -90,10 +90,10 @@ export class OpenAiStreamProcessor {
90
90
  this.outputStreamTerminated = true;
91
91
  } else if (chunk.usage) {
92
92
  this.usageDataAccumulator.addTokenUsage({
93
- promptTokens: chunk.usage.prompt_tokens,
93
+ inputTokens: chunk.usage.prompt_tokens,
94
+ cachedInputTokens: chunk.usage.prompt_tokens_details?.cached_tokens ?? 0,
94
95
  reasoningTokens: chunk.usage.completion_tokens_details?.reasoning_tokens ?? 0,
95
- cachedPromptTokens: chunk.usage.prompt_tokens_details?.cached_tokens ?? 0,
96
- completionTokens: chunk.usage.completion_tokens,
96
+ outputTokens: chunk.usage.completion_tokens,
97
97
  totalTokens: chunk.usage.total_tokens,
98
98
  });
99
99
  if (finishedProcessingToolCallStream) {
package/src/UsageData.ts CHANGED
@@ -1,13 +1,30 @@
1
1
  import { TiktokenModel } from 'tiktoken';
2
2
 
3
3
  export type TokenUsage = {
4
- promptTokens: number;
4
+ inputTokens: number;
5
+ cachedInputTokens: number;
5
6
  reasoningTokens: number;
6
- cachedPromptTokens: number;
7
- completionTokens: number;
7
+ outputTokens: number;
8
8
  totalTokens: number;
9
9
  };
10
10
 
11
+ export type ModelApiCost = {
12
+ /** USD per 1M input tokens */
13
+ inputUsdPer1M: number;
14
+ /** USD per 1M cached input tokens (if supported) */
15
+ cachedInputUsdPer1M?: number;
16
+ /** USD per 1M output tokens */
17
+ outputUsdPer1M: number;
18
+ };
19
+
20
+ export type UsageCostUsd = {
21
+ inputUsd: number;
22
+ cachedInputUsd: number;
23
+ reasoningUsd: number;
24
+ outputUsd: number;
25
+ totalUsd: number;
26
+ };
27
+
11
28
  /**
12
29
  * Usage data accumulated throughout the lifecycle of a single call to
13
30
  * `OpenAi.generateResponse` or `OpenAi.generateStreamingResponse`.
@@ -17,8 +34,12 @@ export type UsageData = {
17
34
  model: TiktokenModel;
18
35
  /** The token usage of the initial request sent to the assistant */
19
36
  initialRequestTokenUsage: TokenUsage;
37
+ /** The USD cost of the initial request */
38
+ initialRequestCostUsd: UsageCostUsd;
20
39
  /** The total token usage of all requests sent to the assistant (ie. initial request + all subsequent tool call requests) */
21
40
  totalTokenUsage: TokenUsage;
41
+ /** The total USD cost of all requests sent to the assistant */
42
+ totalCostUsd: UsageCostUsd;
22
43
  /** The number of requests sent to the assistant */
23
44
  totalRequestsToAssistant: number;
24
45
  /** The number of times each tool was called by the assistant */
@@ -39,36 +60,71 @@ export class UsageDataAccumulator {
39
60
  this.usageData = {
40
61
  model,
41
62
  initialRequestTokenUsage: {
42
- promptTokens: 0,
63
+ inputTokens: 0,
43
64
  reasoningTokens: 0,
44
- cachedPromptTokens: 0,
45
- completionTokens: 0,
65
+ cachedInputTokens: 0,
66
+ outputTokens: 0,
46
67
  totalTokens: 0,
47
68
  },
69
+ initialRequestCostUsd: {
70
+ inputUsd: 0,
71
+ cachedInputUsd: 0,
72
+ reasoningUsd: 0,
73
+ outputUsd: 0,
74
+ totalUsd: 0,
75
+ },
48
76
  totalTokenUsage: {
49
- promptTokens: 0,
77
+ inputTokens: 0,
78
+ cachedInputTokens: 0,
50
79
  reasoningTokens: 0,
51
- cachedPromptTokens: 0,
52
- completionTokens: 0,
80
+ outputTokens: 0,
53
81
  totalTokens: 0,
54
82
  },
83
+ totalCostUsd: {
84
+ inputUsd: 0,
85
+ cachedInputUsd: 0,
86
+ reasoningUsd: 0,
87
+ outputUsd: 0,
88
+ totalUsd: 0,
89
+ },
55
90
  totalRequestsToAssistant: 0,
56
91
  callsPerTool: {},
57
92
  totalToolCalls: 0,
58
93
  };
59
94
  }
60
95
 
61
- addTokenUsage(tokenUsage: TokenUsage) {
96
+ addTokenUsage(tokenUsage: TokenUsage, opts?: { serviceTier?: string }) {
62
97
  this.usageData.totalRequestsToAssistant++;
98
+
99
+ const cost = calculateUsageCostUsd(this.usageData.model, tokenUsage, { serviceTier: opts?.serviceTier });
100
+
63
101
  if (!this.processedInitialRequest) {
64
102
  this.usageData.initialRequestTokenUsage = tokenUsage;
103
+ this.usageData.initialRequestCostUsd = cost;
65
104
  this.processedInitialRequest = true;
66
105
  }
106
+
107
+ if (cost) {
108
+ if (!this.usageData.totalCostUsd) {
109
+ this.usageData.totalCostUsd = { ...cost };
110
+ } else {
111
+ this.usageData.totalCostUsd = {
112
+ inputUsd: this.usageData.totalCostUsd.inputUsd + cost.inputUsd,
113
+ cachedInputUsd: this.usageData.totalCostUsd.cachedInputUsd + cost.cachedInputUsd,
114
+ reasoningUsd: this.usageData.totalCostUsd.reasoningUsd + cost.reasoningUsd,
115
+ outputUsd: this.usageData.totalCostUsd.outputUsd + cost.outputUsd,
116
+ totalUsd: this.usageData.totalCostUsd.totalUsd + cost.totalUsd,
117
+ };
118
+ }
119
+
120
+ this.usageData.totalCostUsd = roundUsageCostUsdToCents(this.usageData.totalCostUsd);
121
+ }
122
+
67
123
  this.usageData.totalTokenUsage = {
68
- promptTokens: this.usageData.totalTokenUsage.promptTokens + tokenUsage.promptTokens,
124
+ inputTokens: this.usageData.totalTokenUsage.inputTokens + tokenUsage.inputTokens,
125
+ cachedInputTokens: this.usageData.totalTokenUsage.cachedInputTokens + tokenUsage.cachedInputTokens,
69
126
  reasoningTokens: this.usageData.totalTokenUsage.reasoningTokens + tokenUsage.reasoningTokens,
70
- cachedPromptTokens: this.usageData.totalTokenUsage.promptTokens + tokenUsage.cachedPromptTokens,
71
- completionTokens: this.usageData.totalTokenUsage.completionTokens + tokenUsage.completionTokens,
127
+ outputTokens: this.usageData.totalTokenUsage.outputTokens + tokenUsage.outputTokens,
72
128
  totalTokens: this.usageData.totalTokenUsage.totalTokens + tokenUsage.totalTokens,
73
129
  };
74
130
  }
@@ -82,3 +138,310 @@ export class UsageDataAccumulator {
82
138
  this.usageData.totalToolCalls++;
83
139
  }
84
140
  }
141
+
142
+ /**
143
+ * Aggregate multiple UsageData objects into a single UsageData.
144
+ */
145
+ export function aggregateUsageData(list: UsageData[]): UsageData | undefined {
146
+ if (!Array.isArray(list) || list.length === 0) {
147
+ return undefined;
148
+ }
149
+
150
+ const first = list[0];
151
+
152
+ const out: UsageData = {
153
+ model: first.model,
154
+ initialRequestTokenUsage: { ...first.initialRequestTokenUsage },
155
+ totalTokenUsage: { ...first.totalTokenUsage },
156
+ totalRequestsToAssistant: first.totalRequestsToAssistant,
157
+ callsPerTool: { ...first.callsPerTool },
158
+ totalToolCalls: first.totalToolCalls,
159
+ initialRequestCostUsd: { ...first.initialRequestCostUsd },
160
+ totalCostUsd: { ...first.totalCostUsd },
161
+ };
162
+
163
+ for (const u of list.slice(1)) {
164
+ out.totalTokenUsage.inputTokens += u.totalTokenUsage.inputTokens;
165
+ out.totalTokenUsage.cachedInputTokens += u.totalTokenUsage.cachedInputTokens;
166
+ out.totalTokenUsage.reasoningTokens += u.totalTokenUsage.reasoningTokens;
167
+ out.totalTokenUsage.outputTokens += u.totalTokenUsage.outputTokens;
168
+ out.totalTokenUsage.totalTokens += u.totalTokenUsage.totalTokens;
169
+
170
+ out.totalRequestsToAssistant += u.totalRequestsToAssistant;
171
+ out.totalToolCalls += u.totalToolCalls;
172
+
173
+ for (const [k, v] of Object.entries(u.callsPerTool)) {
174
+ out.callsPerTool[k] = (out.callsPerTool[k] ?? 0) + v;
175
+ }
176
+
177
+ out.totalCostUsd.inputUsd += u.totalCostUsd.inputUsd;
178
+ out.totalCostUsd.cachedInputUsd += u.totalCostUsd.cachedInputUsd;
179
+ out.totalCostUsd.reasoningUsd += u.totalCostUsd.reasoningUsd;
180
+ out.totalCostUsd.outputUsd += u.totalCostUsd.outputUsd;
181
+ out.totalCostUsd.totalUsd += u.totalCostUsd.totalUsd;
182
+
183
+ out.totalCostUsd = roundUsageCostUsdToCents(out.totalCostUsd);
184
+ }
185
+
186
+ return out;
187
+ }
188
+
189
+ /**
190
+ * Standard tier costs (USD per 1M tokens) based on the pricing on OpenAI's website.
191
+ */
192
+ export const MODEL_API_COST_USD_PER_1M_TOKENS_STANDARD: Record<string, ModelApiCost> = {
193
+ 'gpt-5.2': { inputUsdPer1M: 1.75, cachedInputUsdPer1M: 0.175, outputUsdPer1M: 14.0 },
194
+ 'gpt-5.1': { inputUsdPer1M: 1.25, cachedInputUsdPer1M: 0.125, outputUsdPer1M: 10.0 },
195
+ 'gpt-5': { inputUsdPer1M: 1.25, cachedInputUsdPer1M: 0.125, outputUsdPer1M: 10.0 },
196
+ 'gpt-5-mini': { inputUsdPer1M: 0.25, cachedInputUsdPer1M: 0.025, outputUsdPer1M: 2.0 },
197
+ 'gpt-5-nano': { inputUsdPer1M: 0.05, cachedInputUsdPer1M: 0.005, outputUsdPer1M: 0.4 },
198
+
199
+ 'gpt-5.2-chat-latest': { inputUsdPer1M: 1.75, cachedInputUsdPer1M: 0.175, outputUsdPer1M: 14.0 },
200
+ 'gpt-5.1-chat-latest': { inputUsdPer1M: 1.25, cachedInputUsdPer1M: 0.125, outputUsdPer1M: 10.0 },
201
+ 'gpt-5-chat-latest': { inputUsdPer1M: 1.25, cachedInputUsdPer1M: 0.125, outputUsdPer1M: 10.0 },
202
+
203
+ 'gpt-5.2-codex': { inputUsdPer1M: 1.75, cachedInputUsdPer1M: 0.175, outputUsdPer1M: 14.0 },
204
+ 'gpt-5.1-codex-max': { inputUsdPer1M: 1.25, cachedInputUsdPer1M: 0.125, outputUsdPer1M: 10.0 },
205
+ 'gpt-5.1-codex': { inputUsdPer1M: 1.25, cachedInputUsdPer1M: 0.125, outputUsdPer1M: 10.0 },
206
+ 'gpt-5-codex': { inputUsdPer1M: 1.25, cachedInputUsdPer1M: 0.125, outputUsdPer1M: 10.0 },
207
+
208
+ 'gpt-5.2-pro': { inputUsdPer1M: 21.0, outputUsdPer1M: 168.0 },
209
+ 'gpt-5-pro': { inputUsdPer1M: 15.0, outputUsdPer1M: 120.0 },
210
+
211
+ 'gpt-4.1': { inputUsdPer1M: 2.0, cachedInputUsdPer1M: 0.5, outputUsdPer1M: 8.0 },
212
+ 'gpt-4.1-mini': { inputUsdPer1M: 0.4, cachedInputUsdPer1M: 0.1, outputUsdPer1M: 1.6 },
213
+ 'gpt-4.1-nano': { inputUsdPer1M: 0.1, cachedInputUsdPer1M: 0.025, outputUsdPer1M: 0.4 },
214
+
215
+ 'gpt-4o': { inputUsdPer1M: 2.5, cachedInputUsdPer1M: 1.25, outputUsdPer1M: 10.0 },
216
+ 'gpt-4o-2024-05-13': { inputUsdPer1M: 5.0, outputUsdPer1M: 15.0 },
217
+ 'gpt-4o-mini': { inputUsdPer1M: 0.15, cachedInputUsdPer1M: 0.075, outputUsdPer1M: 0.6 },
218
+
219
+ 'gpt-realtime': { inputUsdPer1M: 4.0, cachedInputUsdPer1M: 0.4, outputUsdPer1M: 16.0 },
220
+ 'gpt-realtime-mini': { inputUsdPer1M: 0.6, cachedInputUsdPer1M: 0.06, outputUsdPer1M: 2.4 },
221
+
222
+ 'gpt-4o-realtime-preview': { inputUsdPer1M: 5.0, cachedInputUsdPer1M: 2.5, outputUsdPer1M: 20.0 },
223
+ 'gpt-4o-mini-realtime-preview': { inputUsdPer1M: 0.6, cachedInputUsdPer1M: 0.3, outputUsdPer1M: 2.4 },
224
+
225
+ 'gpt-audio': { inputUsdPer1M: 2.5, outputUsdPer1M: 10.0 },
226
+ 'gpt-audio-mini': { inputUsdPer1M: 0.6, outputUsdPer1M: 2.4 },
227
+ 'gpt-4o-audio-preview': { inputUsdPer1M: 2.5, outputUsdPer1M: 10.0 },
228
+ 'gpt-4o-mini-audio-preview': { inputUsdPer1M: 0.15, outputUsdPer1M: 0.6 },
229
+
230
+ o1: { inputUsdPer1M: 15.0, cachedInputUsdPer1M: 7.5, outputUsdPer1M: 60.0 },
231
+ 'o1-pro': { inputUsdPer1M: 150.0, outputUsdPer1M: 600.0 },
232
+ 'o1-mini': { inputUsdPer1M: 1.1, cachedInputUsdPer1M: 0.55, outputUsdPer1M: 4.4 },
233
+
234
+ o3: { inputUsdPer1M: 2.0, cachedInputUsdPer1M: 0.5, outputUsdPer1M: 8.0 },
235
+ 'o3-pro': { inputUsdPer1M: 20.0, outputUsdPer1M: 80.0 },
236
+ 'o3-mini': { inputUsdPer1M: 1.1, cachedInputUsdPer1M: 0.55, outputUsdPer1M: 4.4 },
237
+ 'o3-deep-research': { inputUsdPer1M: 10.0, cachedInputUsdPer1M: 2.5, outputUsdPer1M: 40.0 },
238
+
239
+ 'o4-mini': { inputUsdPer1M: 1.1, cachedInputUsdPer1M: 0.275, outputUsdPer1M: 4.4 },
240
+ 'o4-mini-deep-research': { inputUsdPer1M: 2.0, cachedInputUsdPer1M: 0.5, outputUsdPer1M: 8.0 },
241
+
242
+ 'gpt-5-search-api': { inputUsdPer1M: 1.25, cachedInputUsdPer1M: 0.125, outputUsdPer1M: 10.0 },
243
+ 'gpt-4o-mini-search-preview': { inputUsdPer1M: 0.15, outputUsdPer1M: 0.6 },
244
+ 'gpt-4o-search-preview': { inputUsdPer1M: 2.5, outputUsdPer1M: 10.0 },
245
+ 'computer-use-preview': { inputUsdPer1M: 3.0, outputUsdPer1M: 12.0 },
246
+
247
+ 'gpt-5.1-codex-mini': { inputUsdPer1M: 0.25, cachedInputUsdPer1M: 0.025, outputUsdPer1M: 2.0 },
248
+ 'codex-mini-latest': { inputUsdPer1M: 1.5, cachedInputUsdPer1M: 0.375, outputUsdPer1M: 6.0 },
249
+ };
250
+
251
+ export const MODEL_API_COST_USD_PER_1M_TOKENS_BATCH: Record<string, ModelApiCost> = {
252
+ 'gpt-5.2': { inputUsdPer1M: 0.875, cachedInputUsdPer1M: 0.0875, outputUsdPer1M: 7.0 },
253
+ 'gpt-5.1': { inputUsdPer1M: 0.625, cachedInputUsdPer1M: 0.0625, outputUsdPer1M: 5.0 },
254
+ 'gpt-5': { inputUsdPer1M: 0.625, cachedInputUsdPer1M: 0.0625, outputUsdPer1M: 5.0 },
255
+ 'gpt-5-mini': { inputUsdPer1M: 0.125, cachedInputUsdPer1M: 0.0125, outputUsdPer1M: 1.0 },
256
+ 'gpt-5-nano': { inputUsdPer1M: 0.025, cachedInputUsdPer1M: 0.0025, outputUsdPer1M: 0.2 },
257
+
258
+ 'gpt-5.2-pro': { inputUsdPer1M: 10.5, outputUsdPer1M: 84.0 },
259
+ 'gpt-5-pro': { inputUsdPer1M: 7.5, outputUsdPer1M: 60.0 },
260
+
261
+ 'gpt-4.1': { inputUsdPer1M: 1.0, outputUsdPer1M: 4.0 },
262
+ 'gpt-4.1-mini': { inputUsdPer1M: 0.2, outputUsdPer1M: 0.8 },
263
+ 'gpt-4.1-nano': { inputUsdPer1M: 0.05, outputUsdPer1M: 0.2 },
264
+
265
+ 'gpt-4o': { inputUsdPer1M: 1.25, outputUsdPer1M: 5.0 },
266
+ 'gpt-4o-2024-05-13': { inputUsdPer1M: 2.5, outputUsdPer1M: 7.5 },
267
+ 'gpt-4o-mini': { inputUsdPer1M: 0.075, outputUsdPer1M: 0.3 },
268
+
269
+ o1: { inputUsdPer1M: 7.5, outputUsdPer1M: 30.0 },
270
+ 'o1-pro': { inputUsdPer1M: 75.0, outputUsdPer1M: 300.0 },
271
+
272
+ 'o3-pro': { inputUsdPer1M: 10.0, outputUsdPer1M: 40.0 },
273
+ o3: { inputUsdPer1M: 1.0, outputUsdPer1M: 4.0 },
274
+ 'o3-deep-research': { inputUsdPer1M: 5.0, outputUsdPer1M: 20.0 },
275
+
276
+ 'o4-mini': { inputUsdPer1M: 0.55, outputUsdPer1M: 2.2 },
277
+ 'o4-mini-deep-research': { inputUsdPer1M: 1.0, outputUsdPer1M: 4.0 },
278
+ 'o3-mini': { inputUsdPer1M: 0.55, outputUsdPer1M: 2.2 },
279
+ 'o1-mini': { inputUsdPer1M: 0.55, outputUsdPer1M: 2.2 },
280
+
281
+ 'computer-use-preview': { inputUsdPer1M: 1.5, outputUsdPer1M: 6.0 },
282
+ };
283
+
284
+ export const MODEL_API_COST_USD_PER_1M_TOKENS_FLEX: Record<string, ModelApiCost> = {
285
+ 'gpt-5.2': { inputUsdPer1M: 0.875, cachedInputUsdPer1M: 0.0875, outputUsdPer1M: 7.0 },
286
+ 'gpt-5.1': { inputUsdPer1M: 0.625, cachedInputUsdPer1M: 0.0625, outputUsdPer1M: 5.0 },
287
+ 'gpt-5': { inputUsdPer1M: 0.625, cachedInputUsdPer1M: 0.0625, outputUsdPer1M: 5.0 },
288
+ 'gpt-5-mini': { inputUsdPer1M: 0.125, cachedInputUsdPer1M: 0.0125, outputUsdPer1M: 1.0 },
289
+ 'gpt-5-nano': { inputUsdPer1M: 0.025, cachedInputUsdPer1M: 0.0025, outputUsdPer1M: 0.2 },
290
+
291
+ o3: { inputUsdPer1M: 1.0, cachedInputUsdPer1M: 0.25, outputUsdPer1M: 4.0 },
292
+ 'o4-mini': { inputUsdPer1M: 0.55, cachedInputUsdPer1M: 0.138, outputUsdPer1M: 2.2 },
293
+ };
294
+
295
+ export const MODEL_API_COST_USD_PER_1M_TOKENS_PRIORITY: Record<string, ModelApiCost> = {
296
+ 'gpt-5.2': { inputUsdPer1M: 3.5, cachedInputUsdPer1M: 0.35, outputUsdPer1M: 28.0 },
297
+ 'gpt-5.1': { inputUsdPer1M: 2.5, cachedInputUsdPer1M: 0.25, outputUsdPer1M: 20.0 },
298
+ 'gpt-5': { inputUsdPer1M: 2.5, cachedInputUsdPer1M: 0.25, outputUsdPer1M: 20.0 },
299
+ 'gpt-5-mini': { inputUsdPer1M: 0.45, cachedInputUsdPer1M: 0.045, outputUsdPer1M: 3.6 },
300
+
301
+ 'gpt-5.2-codex': { inputUsdPer1M: 3.5, cachedInputUsdPer1M: 0.35, outputUsdPer1M: 28.0 },
302
+ 'gpt-5.1-codex-max': { inputUsdPer1M: 2.5, cachedInputUsdPer1M: 0.25, outputUsdPer1M: 20.0 },
303
+ 'gpt-5.1-codex': { inputUsdPer1M: 2.5, cachedInputUsdPer1M: 0.25, outputUsdPer1M: 20.0 },
304
+ 'gpt-5-codex': { inputUsdPer1M: 2.5, cachedInputUsdPer1M: 0.25, outputUsdPer1M: 20.0 },
305
+
306
+ 'gpt-4.1': { inputUsdPer1M: 3.5, cachedInputUsdPer1M: 0.875, outputUsdPer1M: 14.0 },
307
+ 'gpt-4.1-mini': { inputUsdPer1M: 0.7, cachedInputUsdPer1M: 0.175, outputUsdPer1M: 2.8 },
308
+ 'gpt-4.1-nano': { inputUsdPer1M: 0.2, cachedInputUsdPer1M: 0.05, outputUsdPer1M: 0.8 },
309
+
310
+ 'gpt-4o': { inputUsdPer1M: 4.25, cachedInputUsdPer1M: 2.125, outputUsdPer1M: 17.0 },
311
+ 'gpt-4o-2024-05-13': { inputUsdPer1M: 8.75, outputUsdPer1M: 26.25 },
312
+ 'gpt-4o-mini': { inputUsdPer1M: 0.25, cachedInputUsdPer1M: 0.125, outputUsdPer1M: 1.0 },
313
+
314
+ o3: { inputUsdPer1M: 3.5, cachedInputUsdPer1M: 0.875, outputUsdPer1M: 14.0 },
315
+ 'o4-mini': { inputUsdPer1M: 2.0, cachedInputUsdPer1M: 0.5, outputUsdPer1M: 8.0 },
316
+ };
317
+
318
+ const TOKENS_PER_1M = 1_000_000;
319
+
320
+ const normalizeModelIdForPricing = (model: string): string => {
321
+ const raw = String(model ?? '').trim();
322
+ if (!raw) {
323
+ return '';
324
+ }
325
+
326
+ // handle e.g. "openai:gpt-4o" or "openai/gpt-4o"
327
+ const afterColon = raw.includes(':') ? raw.split(':').pop() ?? raw : raw;
328
+ const afterSlash = afterColon.includes('/') ? afterColon.split('/').pop() ?? afterColon : afterColon;
329
+ return afterSlash;
330
+ };
331
+
332
+ type UsagePricingTier = 'standard' | 'batch' | 'flex' | 'priority';
333
+
334
+ const normalizeServiceTierForPricing = (serviceTier?: string): UsagePricingTier => {
335
+ const v = String(serviceTier ?? '')
336
+ .trim()
337
+ .toLowerCase();
338
+ if (v === 'priority') {
339
+ return 'priority';
340
+ }
341
+ if (v === 'flex') {
342
+ return 'flex';
343
+ }
344
+ if (v === 'batch') {
345
+ return 'batch';
346
+ }
347
+ return 'standard';
348
+ };
349
+
350
+ const resolveModelApiCost = (model: string, tier?: UsagePricingTier): ModelApiCost | undefined => {
351
+ const m = normalizeModelIdForPricing(model);
352
+ if (!m) {
353
+ return undefined;
354
+ }
355
+
356
+ const t: UsagePricingTier = tier ?? 'standard';
357
+ const table =
358
+ t === 'priority'
359
+ ? MODEL_API_COST_USD_PER_1M_TOKENS_PRIORITY
360
+ : t === 'flex'
361
+ ? MODEL_API_COST_USD_PER_1M_TOKENS_FLEX
362
+ : t === 'batch'
363
+ ? MODEL_API_COST_USD_PER_1M_TOKENS_BATCH
364
+ : MODEL_API_COST_USD_PER_1M_TOKENS_STANDARD;
365
+
366
+ const direct = table[m];
367
+ if (direct) {
368
+ return direct;
369
+ }
370
+
371
+ // common suffix normalization (only if the base exists)
372
+ if (m.endsWith('-latest')) {
373
+ const base = m.slice(0, -'-latest'.length);
374
+ const baseCost = table[base];
375
+ if (baseCost) {
376
+ return baseCost;
377
+ }
378
+ }
379
+
380
+ return undefined;
381
+ };
382
+
383
+ export const calculateUsageCostUsd = (
384
+ model: string,
385
+ tokenUsage: TokenUsage,
386
+ opts?: { serviceTier?: string }
387
+ ): UsageCostUsd => {
388
+ const tier = normalizeServiceTierForPricing(opts?.serviceTier);
389
+ const pricing = resolveModelApiCost(model, tier);
390
+ if (!pricing) {
391
+ return {
392
+ inputUsd: 0,
393
+ cachedInputUsd: 0,
394
+ reasoningUsd: 0,
395
+ outputUsd: 0,
396
+ totalUsd: 0,
397
+ };
398
+ }
399
+
400
+ const input = Number.isFinite(tokenUsage.inputTokens) ? Number(tokenUsage.inputTokens) : 0;
401
+ const cachedInput = Number.isFinite(tokenUsage.cachedInputTokens) ? Number(tokenUsage.cachedInputTokens) : 0;
402
+ const reasoning = Number.isFinite(tokenUsage.reasoningTokens) ? Number(tokenUsage.reasoningTokens) : 0;
403
+ const output = Number.isFinite(tokenUsage.outputTokens) ? Number(tokenUsage.outputTokens) : 0;
404
+
405
+ const inputTokens = Math.max(0, input);
406
+ const cachedInputTokens = Math.max(0, cachedInput);
407
+ const nonCachedInputTokens = Math.max(0, inputTokens - cachedInputTokens);
408
+ const reasoningTokens = Math.max(0, reasoning);
409
+ const outputTokens = Math.max(0, output);
410
+
411
+ const cachedRate =
412
+ typeof pricing.cachedInputUsdPer1M === 'number' ? pricing.cachedInputUsdPer1M : pricing.inputUsdPer1M;
413
+
414
+ const inputUsd = (nonCachedInputTokens * pricing.inputUsdPer1M + cachedInputTokens * cachedRate) / TOKENS_PER_1M;
415
+ const cachedInputUsd = (cachedInputTokens * cachedRate) / TOKENS_PER_1M;
416
+ const reasoningUsd = (reasoningTokens * pricing.outputUsdPer1M) / TOKENS_PER_1M;
417
+ const outputUsd = (outputTokens * pricing.outputUsdPer1M) / TOKENS_PER_1M;
418
+ const totalUsd = inputUsd + outputUsd;
419
+
420
+ return roundUsageCostUsdToCents({
421
+ inputUsd,
422
+ cachedInputUsd,
423
+ reasoningUsd,
424
+ outputUsd,
425
+ totalUsd,
426
+ });
427
+ };
428
+
429
+ function roundToHundredths(value: number): number {
430
+ return Math.round(value * 100) / 100;
431
+ }
432
+
433
+ function roundUsageCostUsdToCents(cost: UsageCostUsd): UsageCostUsd {
434
+ const inputUsd = roundToHundredths(cost.inputUsd);
435
+ const cachedInputUsd = roundToHundredths(cost.cachedInputUsd);
436
+ const reasoningUsd = roundToHundredths(cost.reasoningUsd);
437
+ const outputUsd = roundToHundredths(cost.outputUsd);
438
+ const totalUsd = roundToHundredths(inputUsd + cachedInputUsd + outputUsd);
439
+
440
+ return {
441
+ inputUsd,
442
+ cachedInputUsd,
443
+ reasoningUsd,
444
+ outputUsd,
445
+ totalUsd,
446
+ };
447
+ }
@@ -4,6 +4,7 @@ import { searchFilesFunctionName } from '../keyword_to_files_index/KeywordToFile
4
4
  import { searchLibrariesFunctionName, searchPackagesFunctionName } from '../package/PackageFunctions';
5
5
  import { ConversationFsModerator } from './ConversationFsModerator';
6
6
  import {
7
+ deleteFilesFunction,
7
8
  fsFunctions,
8
9
  getRecentlyAccessedFilePathsFunction,
9
10
  grepFunction,
@@ -46,6 +47,7 @@ export class ConversationFsModule implements ConversationModule {
46
47
  return [
47
48
  readFilesFunction(this),
48
49
  writeFilesFunction(this),
50
+ deleteFilesFunction(this),
49
51
  getRecentlyAccessedFilePathsFunction(this),
50
52
  grepFunction(this),
51
53
  ...fsFunctions,
@@ -115,6 +115,36 @@ export function writeFilesFunction(fsModule: ConversationFsModule) {
115
115
  };
116
116
  }
117
117
 
118
+ export const deleteFilesFunctionName = 'deleteFiles';
119
+ export function deleteFilesFunction(fsModule: ConversationFsModule) {
120
+ return {
121
+ definition: {
122
+ name: deleteFilesFunctionName,
123
+ description: 'Delete files from the file system',
124
+ parameters: {
125
+ type: 'object',
126
+ properties: {
127
+ paths: {
128
+ type: 'array',
129
+ description: 'Paths to the files',
130
+ items: { type: 'string' },
131
+ },
132
+ },
133
+ required: ['paths'],
134
+ },
135
+ },
136
+ call: async (params: { paths: string[] }) => {
137
+ try {
138
+ const absPaths = await canonicalizePaths(fsModule, params.paths);
139
+ return await Fs.deleteFiles(absPaths);
140
+ } catch (error: any) {
141
+ return error.message;
142
+ }
143
+ },
144
+ instructions: [`To delete files from the local file system, use the ${deleteFilesFunctionName} function`],
145
+ };
146
+ }
147
+
118
148
  export const getRecentlyAccessedFilePathsFunctionName = 'getRecentlyAccessedFilePaths';
119
149
  export function getRecentlyAccessedFilePathsFunction(fsModule: ConversationFsModule) {
120
150
  return {
@@ -131,8 +161,8 @@ export function getRecentlyAccessedFilePathsFunction(fsModule: ConversationFsMod
131
161
  };
132
162
  }
133
163
 
134
- const createFolderFunctionName = 'createFolder';
135
- const createFolderFunction: Function = {
164
+ export const createFolderFunctionName = 'createFolder';
165
+ export const createFolderFunction: Function = {
136
166
  definition: {
137
167
  name: createFolderFunctionName,
138
168
  description: 'Create a folder/directory',