mcp-rubber-duck 1.5.2 → 1.6.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 (47) hide show
  1. package/.claude/agents/pricing-updater.md +111 -0
  2. package/.claude/commands/update-pricing.md +22 -0
  3. package/CHANGELOG.md +7 -0
  4. package/dist/config/types.d.ts +72 -0
  5. package/dist/config/types.d.ts.map +1 -1
  6. package/dist/config/types.js +8 -0
  7. package/dist/config/types.js.map +1 -1
  8. package/dist/data/default-pricing.d.ts +18 -0
  9. package/dist/data/default-pricing.d.ts.map +1 -0
  10. package/dist/data/default-pricing.js +307 -0
  11. package/dist/data/default-pricing.js.map +1 -0
  12. package/dist/providers/enhanced-manager.d.ts +2 -1
  13. package/dist/providers/enhanced-manager.d.ts.map +1 -1
  14. package/dist/providers/enhanced-manager.js +20 -2
  15. package/dist/providers/enhanced-manager.js.map +1 -1
  16. package/dist/providers/manager.d.ts +3 -1
  17. package/dist/providers/manager.d.ts.map +1 -1
  18. package/dist/providers/manager.js +12 -1
  19. package/dist/providers/manager.js.map +1 -1
  20. package/dist/server.d.ts +2 -0
  21. package/dist/server.d.ts.map +1 -1
  22. package/dist/server.js +35 -4
  23. package/dist/server.js.map +1 -1
  24. package/dist/services/pricing.d.ts +56 -0
  25. package/dist/services/pricing.d.ts.map +1 -0
  26. package/dist/services/pricing.js +124 -0
  27. package/dist/services/pricing.js.map +1 -0
  28. package/dist/services/usage.d.ts +48 -0
  29. package/dist/services/usage.d.ts.map +1 -0
  30. package/dist/services/usage.js +243 -0
  31. package/dist/services/usage.js.map +1 -0
  32. package/dist/tools/get-usage-stats.d.ts +8 -0
  33. package/dist/tools/get-usage-stats.d.ts.map +1 -0
  34. package/dist/tools/get-usage-stats.js +92 -0
  35. package/dist/tools/get-usage-stats.js.map +1 -0
  36. package/package.json +1 -1
  37. package/src/config/types.ts +51 -0
  38. package/src/data/default-pricing.ts +368 -0
  39. package/src/providers/enhanced-manager.ts +41 -4
  40. package/src/providers/manager.ts +22 -1
  41. package/src/server.ts +42 -4
  42. package/src/services/pricing.ts +155 -0
  43. package/src/services/usage.ts +293 -0
  44. package/src/tools/get-usage-stats.ts +109 -0
  45. package/tests/pricing.test.ts +335 -0
  46. package/tests/tools/get-usage-stats.test.ts +236 -0
  47. package/tests/usage.test.ts +661 -0
@@ -0,0 +1,368 @@
1
+ import { PricingConfig } from '../config/types.js';
2
+
3
+ /**
4
+ * Default pricing data for common LLM providers.
5
+ * Prices are in USD per million tokens.
6
+ * Last updated: 2026-01-08
7
+ *
8
+ * To update pricing:
9
+ * 1. Research current pricing from provider websites
10
+ * 2. Update the values below
11
+ * 3. Update the lastUpdated field
12
+ * 4. Release a new version
13
+ *
14
+ * Users can override these defaults in their config.json file.
15
+ */
16
+ export const DEFAULT_PRICING_VERSION = 2;
17
+ export const DEFAULT_PRICING_LAST_UPDATED = '2026-01-08';
18
+
19
+ export const DEFAULT_PRICING: PricingConfig = {
20
+ openai: {
21
+ // GPT-5 models (released 2025)
22
+ 'gpt-5': { inputPricePerMillion: 1.25, outputPricePerMillion: 10 },
23
+ 'gpt-5.1': { inputPricePerMillion: 1.25, outputPricePerMillion: 10 },
24
+ 'gpt-5.2': { inputPricePerMillion: 1.25, outputPricePerMillion: 10 },
25
+ 'gpt-5-mini': { inputPricePerMillion: 0.25, outputPricePerMillion: 2 },
26
+ 'gpt-5-nano': { inputPricePerMillion: 0.05, outputPricePerMillion: 0.4 },
27
+
28
+ // GPT-4.1 models
29
+ 'gpt-4.1': { inputPricePerMillion: 2, outputPricePerMillion: 8 },
30
+ 'gpt-4.1-mini': { inputPricePerMillion: 0.4, outputPricePerMillion: 1.6 },
31
+ 'gpt-4.1-nano': { inputPricePerMillion: 0.1, outputPricePerMillion: 0.4 },
32
+
33
+ // GPT-4o models
34
+ 'gpt-4o': { inputPricePerMillion: 2.5, outputPricePerMillion: 10 },
35
+ 'gpt-4o-2024-11-20': { inputPricePerMillion: 2.5, outputPricePerMillion: 10 },
36
+ 'gpt-4o-2024-08-06': { inputPricePerMillion: 2.5, outputPricePerMillion: 10 },
37
+ 'gpt-4o-2024-05-13': { inputPricePerMillion: 5, outputPricePerMillion: 15 },
38
+ 'gpt-4o-mini': { inputPricePerMillion: 0.15, outputPricePerMillion: 0.6 },
39
+ 'gpt-4o-mini-2024-07-18': { inputPricePerMillion: 0.15, outputPricePerMillion: 0.6 },
40
+
41
+ // GPT-4 Turbo
42
+ 'gpt-4-turbo': { inputPricePerMillion: 10, outputPricePerMillion: 30 },
43
+ 'gpt-4-turbo-2024-04-09': { inputPricePerMillion: 10, outputPricePerMillion: 30 },
44
+ 'gpt-4-turbo-preview': { inputPricePerMillion: 10, outputPricePerMillion: 30 },
45
+
46
+ // GPT-4
47
+ 'gpt-4': { inputPricePerMillion: 30, outputPricePerMillion: 60 },
48
+ 'gpt-4-0613': { inputPricePerMillion: 30, outputPricePerMillion: 60 },
49
+
50
+ // GPT-3.5 Turbo
51
+ 'gpt-3.5-turbo': { inputPricePerMillion: 0.5, outputPricePerMillion: 1.5 },
52
+ 'gpt-3.5-turbo-0125': { inputPricePerMillion: 0.5, outputPricePerMillion: 1.5 },
53
+
54
+ // o4 reasoning models
55
+ 'o4-mini': { inputPricePerMillion: 1.1, outputPricePerMillion: 4.4 },
56
+
57
+ // o3 reasoning models
58
+ 'o3': { inputPricePerMillion: 2, outputPricePerMillion: 8 },
59
+ 'o3-mini': { inputPricePerMillion: 1.1, outputPricePerMillion: 4.4 },
60
+
61
+ // o1 reasoning models
62
+ 'o1': { inputPricePerMillion: 15, outputPricePerMillion: 60 },
63
+ 'o1-2024-12-17': { inputPricePerMillion: 15, outputPricePerMillion: 60 },
64
+ 'o1-preview': { inputPricePerMillion: 15, outputPricePerMillion: 60 },
65
+ 'o1-preview-2024-09-12': { inputPricePerMillion: 15, outputPricePerMillion: 60 },
66
+ 'o1-mini': { inputPricePerMillion: 3, outputPricePerMillion: 12 },
67
+ 'o1-mini-2024-09-12': { inputPricePerMillion: 3, outputPricePerMillion: 12 },
68
+ },
69
+
70
+ anthropic: {
71
+ // Claude 4.5 models (current generation)
72
+ 'claude-opus-4-5-20251101': { inputPricePerMillion: 5, outputPricePerMillion: 25 },
73
+ 'claude-opus-4-5': { inputPricePerMillion: 5, outputPricePerMillion: 25 },
74
+ 'claude-sonnet-4-5-20250929': { inputPricePerMillion: 3, outputPricePerMillion: 15 },
75
+ 'claude-sonnet-4-5': { inputPricePerMillion: 3, outputPricePerMillion: 15 },
76
+ 'claude-haiku-4-5': { inputPricePerMillion: 1, outputPricePerMillion: 5 },
77
+
78
+ // Claude 4.1 models
79
+ 'claude-opus-4-1': { inputPricePerMillion: 15, outputPricePerMillion: 75 },
80
+
81
+ // Claude 4 models
82
+ 'claude-opus-4-20250514': { inputPricePerMillion: 15, outputPricePerMillion: 75 },
83
+ 'claude-opus-4': { inputPricePerMillion: 15, outputPricePerMillion: 75 },
84
+ 'claude-sonnet-4-20250514': { inputPricePerMillion: 3, outputPricePerMillion: 15 },
85
+ 'claude-sonnet-4': { inputPricePerMillion: 3, outputPricePerMillion: 15 },
86
+
87
+ // Claude 3.7 models (deprecated but kept for compatibility)
88
+ 'claude-3-7-sonnet-20250219': { inputPricePerMillion: 3, outputPricePerMillion: 15 },
89
+ 'claude-sonnet-3-7': { inputPricePerMillion: 3, outputPricePerMillion: 15 },
90
+
91
+ // Claude 3.5 models
92
+ 'claude-3-5-sonnet-20241022': { inputPricePerMillion: 3, outputPricePerMillion: 15 },
93
+ 'claude-3-5-haiku-20241022': { inputPricePerMillion: 0.8, outputPricePerMillion: 4 },
94
+ 'claude-haiku-3-5': { inputPricePerMillion: 0.8, outputPricePerMillion: 4 },
95
+
96
+ // Claude 3 models (deprecated but kept for compatibility)
97
+ 'claude-3-opus-20240229': { inputPricePerMillion: 15, outputPricePerMillion: 75 },
98
+ 'claude-opus-3': { inputPricePerMillion: 15, outputPricePerMillion: 75 },
99
+ 'claude-3-sonnet-20240229': { inputPricePerMillion: 3, outputPricePerMillion: 15 },
100
+ 'claude-3-haiku-20240307': { inputPricePerMillion: 0.25, outputPricePerMillion: 1.25 },
101
+ 'claude-haiku-3': { inputPricePerMillion: 0.25, outputPricePerMillion: 1.25 },
102
+ },
103
+
104
+ google: {
105
+ // Gemini 3.0 (preview)
106
+ 'gemini-3-pro-preview': { inputPricePerMillion: 2, outputPricePerMillion: 12 },
107
+ 'gemini-3-flash-preview': { inputPricePerMillion: 0.5, outputPricePerMillion: 3 },
108
+ 'gemini-3-pro-image-preview': { inputPricePerMillion: 2, outputPricePerMillion: 120 },
109
+
110
+ // Gemini 2.5
111
+ 'gemini-2.5-pro': { inputPricePerMillion: 1.25, outputPricePerMillion: 10 },
112
+ 'gemini-2.5-pro-latest': { inputPricePerMillion: 1.25, outputPricePerMillion: 10 },
113
+ 'gemini-2.5-flash': { inputPricePerMillion: 0.3, outputPricePerMillion: 2.5 },
114
+ 'gemini-2.5-flash-latest': { inputPricePerMillion: 0.3, outputPricePerMillion: 2.5 },
115
+ 'gemini-2.5-flash-lite': { inputPricePerMillion: 0.1, outputPricePerMillion: 0.4 },
116
+ 'gemini-2.5-flash-preview-09-2025': { inputPricePerMillion: 0.3, outputPricePerMillion: 2.5 },
117
+ 'gemini-2.5-flash-lite-preview-09-2025': { inputPricePerMillion: 0.1, outputPricePerMillion: 0.4 },
118
+ 'gemini-2.5-flash-native-audio-preview-12-2025': {
119
+ inputPricePerMillion: 0.5,
120
+ outputPricePerMillion: 2,
121
+ },
122
+ 'gemini-2.5-computer-use-preview-10-2025': {
123
+ inputPricePerMillion: 1.25,
124
+ outputPricePerMillion: 10,
125
+ },
126
+
127
+ // Gemini 2.0
128
+ 'gemini-2.0-flash': { inputPricePerMillion: 0.1, outputPricePerMillion: 0.4 },
129
+ 'gemini-2.0-flash-lite': { inputPricePerMillion: 0.075, outputPricePerMillion: 0.3 },
130
+ 'gemini-2.0-flash-exp': { inputPricePerMillion: 0, outputPricePerMillion: 0 }, // Free during preview
131
+
132
+ // Gemini 1.5
133
+ 'gemini-1.5-pro': { inputPricePerMillion: 1.25, outputPricePerMillion: 5 },
134
+ 'gemini-1.5-pro-latest': { inputPricePerMillion: 1.25, outputPricePerMillion: 5 },
135
+ 'gemini-1.5-flash': { inputPricePerMillion: 0.075, outputPricePerMillion: 0.3 },
136
+ 'gemini-1.5-flash-latest': { inputPricePerMillion: 0.075, outputPricePerMillion: 0.3 },
137
+ 'gemini-1.5-flash-8b': { inputPricePerMillion: 0.0375, outputPricePerMillion: 0.15 },
138
+
139
+ // Gemini 1.0
140
+ 'gemini-1.0-pro': { inputPricePerMillion: 0.5, outputPricePerMillion: 1.5 },
141
+ 'gemini-pro': { inputPricePerMillion: 0.5, outputPricePerMillion: 1.5 },
142
+
143
+ // Embeddings
144
+ 'gemini-embedding-001': { inputPricePerMillion: 0.15, outputPricePerMillion: 0 },
145
+
146
+ // Robotics (preview)
147
+ 'gemini-robotics-er-1.5-preview': { inputPricePerMillion: 0.3, outputPricePerMillion: 2.5 },
148
+
149
+ // Gemma (free)
150
+ 'gemma-3': { inputPricePerMillion: 0, outputPricePerMillion: 0 },
151
+ 'gemma-3n': { inputPricePerMillion: 0, outputPricePerMillion: 0 },
152
+ },
153
+
154
+ groq: {
155
+ // Llama 4
156
+ 'llama-4-scout-17b-128k': { inputPricePerMillion: 0.11, outputPricePerMillion: 0.34 },
157
+ 'llama-4-maverick-17b-128k': { inputPricePerMillion: 0.2, outputPricePerMillion: 0.6 },
158
+ 'llama-guard-4-12b': { inputPricePerMillion: 0.2, outputPricePerMillion: 0.2 },
159
+
160
+ // Llama 3.3
161
+ 'llama-3.3-70b-versatile': { inputPricePerMillion: 0.59, outputPricePerMillion: 0.79 },
162
+ 'llama-3.3-70b-specdec': { inputPricePerMillion: 0.59, outputPricePerMillion: 0.99 },
163
+
164
+ // Llama 3.1
165
+ 'llama-3.1-70b-versatile': { inputPricePerMillion: 0.59, outputPricePerMillion: 0.79 },
166
+ 'llama-3.1-8b-instant': { inputPricePerMillion: 0.05, outputPricePerMillion: 0.08 },
167
+
168
+ // Llama 3.2
169
+ 'llama-3.2-90b-vision-preview': { inputPricePerMillion: 0.9, outputPricePerMillion: 0.9 },
170
+ 'llama-3.2-11b-vision-preview': { inputPricePerMillion: 0.18, outputPricePerMillion: 0.18 },
171
+ 'llama-3.2-3b-preview': { inputPricePerMillion: 0.06, outputPricePerMillion: 0.06 },
172
+ 'llama-3.2-1b-preview': { inputPricePerMillion: 0.04, outputPricePerMillion: 0.04 },
173
+
174
+ // GPT-OSS models
175
+ 'gpt-oss-120b-128k': { inputPricePerMillion: 0.15, outputPricePerMillion: 0.6 },
176
+ 'gpt-oss-20b-128k': { inputPricePerMillion: 0.075, outputPricePerMillion: 0.3 },
177
+ 'gpt-oss-safeguard-20b': { inputPricePerMillion: 0.075, outputPricePerMillion: 0.3 },
178
+
179
+ // Qwen
180
+ 'qwen3-32b-131k': { inputPricePerMillion: 0.29, outputPricePerMillion: 0.59 },
181
+
182
+ // Kimi
183
+ 'kimi-k2-0905-256k': { inputPricePerMillion: 1, outputPricePerMillion: 3 },
184
+
185
+ // Mixtral
186
+ 'mixtral-8x7b-32768': { inputPricePerMillion: 0.24, outputPricePerMillion: 0.24 },
187
+
188
+ // Gemma
189
+ 'gemma2-9b-it': { inputPricePerMillion: 0.2, outputPricePerMillion: 0.2 },
190
+ 'gemma-7b-it': { inputPricePerMillion: 0.07, outputPricePerMillion: 0.07 },
191
+ },
192
+
193
+ deepseek: {
194
+ // DeepSeek V3.2 (current models)
195
+ 'deepseek-chat': { inputPricePerMillion: 0.28, outputPricePerMillion: 0.42 },
196
+ 'deepseek-reasoner': { inputPricePerMillion: 0.28, outputPricePerMillion: 0.42 },
197
+ // Legacy aliases kept for compatibility
198
+ 'deepseek-v3': { inputPricePerMillion: 0.28, outputPricePerMillion: 0.42 },
199
+ 'deepseek-r1': { inputPricePerMillion: 0.28, outputPricePerMillion: 0.42 },
200
+ },
201
+
202
+ mistral: {
203
+ // Mistral Large
204
+ 'mistral-large-latest': { inputPricePerMillion: 2, outputPricePerMillion: 6 },
205
+ 'mistral-large-2411': { inputPricePerMillion: 2, outputPricePerMillion: 6 },
206
+
207
+ // Mistral Medium 3
208
+ 'mistral-medium-latest': { inputPricePerMillion: 0.4, outputPricePerMillion: 2 },
209
+ 'mistral-medium-3': { inputPricePerMillion: 0.4, outputPricePerMillion: 2 },
210
+
211
+ // Mistral Small 3
212
+ 'mistral-small-latest': { inputPricePerMillion: 0.1, outputPricePerMillion: 0.3 },
213
+ 'mistral-small-2409': { inputPricePerMillion: 0.1, outputPricePerMillion: 0.3 },
214
+ 'mistral-small-3': { inputPricePerMillion: 0.1, outputPricePerMillion: 0.3 },
215
+
216
+ // Codestral
217
+ 'codestral-latest': { inputPricePerMillion: 0.3, outputPricePerMillion: 0.9 },
218
+ 'codestral-2501': { inputPricePerMillion: 0.3, outputPricePerMillion: 0.9 },
219
+
220
+ // Devstral 2
221
+ 'devstral-2': { inputPricePerMillion: 0.4, outputPricePerMillion: 2 },
222
+ 'devstral-small-2': { inputPricePerMillion: 0.1, outputPricePerMillion: 0.3 },
223
+
224
+ // Ministral
225
+ 'ministral-8b-latest': { inputPricePerMillion: 0.1, outputPricePerMillion: 0.1 },
226
+ 'ministral-3b-latest': { inputPricePerMillion: 0.04, outputPricePerMillion: 0.04 },
227
+
228
+ // Pixtral
229
+ 'pixtral-large-latest': { inputPricePerMillion: 2, outputPricePerMillion: 6 },
230
+
231
+ // Open models
232
+ 'open-mistral-nemo': { inputPricePerMillion: 0.15, outputPricePerMillion: 0.15 },
233
+ },
234
+
235
+ together: {
236
+ // Llama 4
237
+ 'meta-llama/Llama-4-Maverick': { inputPricePerMillion: 0.27, outputPricePerMillion: 0.85 },
238
+ 'meta-llama/Llama-4-Scout': { inputPricePerMillion: 0.18, outputPricePerMillion: 0.59 },
239
+
240
+ // Llama 3.3
241
+ 'meta-llama/Llama-3.3-70B-Instruct-Turbo': {
242
+ inputPricePerMillion: 0.88,
243
+ outputPricePerMillion: 0.88,
244
+ },
245
+
246
+ // Llama 3.2
247
+ 'meta-llama/Llama-3.2-3B-Instruct-Turbo': {
248
+ inputPricePerMillion: 0.06,
249
+ outputPricePerMillion: 0.06,
250
+ },
251
+
252
+ // Llama 3.1
253
+ 'meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo': {
254
+ inputPricePerMillion: 3.5,
255
+ outputPricePerMillion: 3.5,
256
+ },
257
+ 'meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo': {
258
+ inputPricePerMillion: 0.88,
259
+ outputPricePerMillion: 0.88,
260
+ },
261
+ 'meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo': {
262
+ inputPricePerMillion: 0.18,
263
+ outputPricePerMillion: 0.18,
264
+ },
265
+
266
+ // Llama 3
267
+ 'meta-llama/Llama-3-8B-Instruct-Lite': { inputPricePerMillion: 0.1, outputPricePerMillion: 0.1 },
268
+ 'meta-llama/Llama-3-70B-Instruct-Turbo': {
269
+ inputPricePerMillion: 0.88,
270
+ outputPricePerMillion: 0.88,
271
+ },
272
+
273
+ // DeepSeek
274
+ 'deepseek-ai/DeepSeek-R1': { inputPricePerMillion: 3, outputPricePerMillion: 7 },
275
+ 'deepseek-ai/DeepSeek-R1-Distill-Qwen-14B': {
276
+ inputPricePerMillion: 0.18,
277
+ outputPricePerMillion: 0.18,
278
+ },
279
+ 'deepseek-ai/DeepSeek-R1-Distill-Llama-70B': {
280
+ inputPricePerMillion: 2,
281
+ outputPricePerMillion: 2,
282
+ },
283
+ 'deepseek-ai/DeepSeek-R1-0528-tput': {
284
+ inputPricePerMillion: 0.55,
285
+ outputPricePerMillion: 2.19,
286
+ },
287
+ 'deepseek-ai/DeepSeek-V3-1': { inputPricePerMillion: 0.6, outputPricePerMillion: 1.7 },
288
+ 'deepseek-ai/DeepSeek-V3': { inputPricePerMillion: 1.25, outputPricePerMillion: 1.25 },
289
+
290
+ // GPT-OSS
291
+ 'gpt-oss-120B': { inputPricePerMillion: 0.15, outputPricePerMillion: 0.6 },
292
+ 'gpt-oss-20B': { inputPricePerMillion: 0.05, outputPricePerMillion: 0.2 },
293
+
294
+ // Qwen 3
295
+ 'Qwen/Qwen3-Next-80B-A3B-Instruct': { inputPricePerMillion: 0.15, outputPricePerMillion: 1.5 },
296
+ 'Qwen/Qwen3-Next-80B-A3B-Thinking': { inputPricePerMillion: 0.15, outputPricePerMillion: 1.5 },
297
+ 'Qwen/Qwen3-VL-32B-Instruct': { inputPricePerMillion: 0.5, outputPricePerMillion: 1.5 },
298
+ 'Qwen/Qwen3-Coder-480B-A35B-Instruct': { inputPricePerMillion: 2, outputPricePerMillion: 2 },
299
+ 'Qwen/Qwen3-235B-A22B-Instruct-FP8': { inputPricePerMillion: 0.2, outputPricePerMillion: 0.6 },
300
+ 'Qwen/Qwen3-235B-A22B-Thinking-FP8': { inputPricePerMillion: 0.65, outputPricePerMillion: 3 },
301
+ 'Qwen/Qwen3-235B-A22B-FP8-Throughput': { inputPricePerMillion: 0.2, outputPricePerMillion: 0.6 },
302
+
303
+ // Qwen 2.5
304
+ 'Qwen/Qwen2.5-72B-Instruct-Turbo': { inputPricePerMillion: 1.2, outputPricePerMillion: 1.2 },
305
+ 'Qwen/Qwen2.5-VL-72B-Instruct': { inputPricePerMillion: 1.95, outputPricePerMillion: 8 },
306
+ 'Qwen/Qwen2.5-Coder-32B-Instruct': { inputPricePerMillion: 0.8, outputPricePerMillion: 0.8 },
307
+ 'Qwen/Qwen2.5-7B-Instruct-Turbo': { inputPricePerMillion: 0.3, outputPricePerMillion: 0.3 },
308
+ 'Qwen/QwQ-32B': { inputPricePerMillion: 1.2, outputPricePerMillion: 1.2 },
309
+
310
+ // Kimi
311
+ 'Kimi/K2-Instruct': { inputPricePerMillion: 1, outputPricePerMillion: 3 },
312
+ 'Kimi/K2-Thinking': { inputPricePerMillion: 1.2, outputPricePerMillion: 4 },
313
+ 'Kimi/K2-0905': { inputPricePerMillion: 1, outputPricePerMillion: 3 },
314
+
315
+ // GLM
316
+ 'THUDM/GLM-4.6': { inputPricePerMillion: 0.6, outputPricePerMillion: 2.2 },
317
+ 'THUDM/GLM-4.5-Air': { inputPricePerMillion: 0.2, outputPricePerMillion: 1.1 },
318
+
319
+ // Mistral
320
+ 'mistralai/Mistral-7B-Instruct-v0.2': { inputPricePerMillion: 0.2, outputPricePerMillion: 0.2 },
321
+ 'mistralai/Mistral-Small-3': { inputPricePerMillion: 0.8, outputPricePerMillion: 0.8 },
322
+ 'mistralai/Mixtral-8x7B-Instruct-v0.1': { inputPricePerMillion: 0.6, outputPricePerMillion: 0.6 },
323
+
324
+ // Cogito
325
+ 'Cogito/cogito-v2-109B-MoE': { inputPricePerMillion: 0.18, outputPricePerMillion: 0.59 },
326
+ 'Cogito/cogito-v2-405B': { inputPricePerMillion: 3.5, outputPricePerMillion: 3.5 },
327
+ 'Cogito/cogito-v2-671B-MoE': { inputPricePerMillion: 1.25, outputPricePerMillion: 1.25 },
328
+ 'Cogito/cogito-v2-70B': { inputPricePerMillion: 0.88, outputPricePerMillion: 0.88 },
329
+
330
+ // Arcee
331
+ 'arcee-ai/AFM-4.5B': { inputPricePerMillion: 0.1, outputPricePerMillion: 0.4 },
332
+ 'arcee-ai/Coder-Large': { inputPricePerMillion: 0.5, outputPricePerMillion: 0.8 },
333
+ 'arcee-ai/Maestro': { inputPricePerMillion: 0.9, outputPricePerMillion: 3.3 },
334
+ 'arcee-ai/Virtuoso-Large': { inputPricePerMillion: 0.75, outputPricePerMillion: 1.2 },
335
+
336
+ // Refuel
337
+ 'refuel-ai/Refuel-LLM-2': { inputPricePerMillion: 0.6, outputPricePerMillion: 0.6 },
338
+ 'refuel-ai/Refuel-LLM-2-Small': { inputPricePerMillion: 0.2, outputPricePerMillion: 0.2 },
339
+
340
+ // Typhoon
341
+ 'scb10x/Typhoon-2-70B-Instruct': { inputPricePerMillion: 0.88, outputPricePerMillion: 0.88 },
342
+
343
+ // Marin
344
+ 'marin-ai/Marin-8B-Instruct': { inputPricePerMillion: 0.18, outputPricePerMillion: 0.18 },
345
+
346
+ // Gemma
347
+ 'google/gemma-3n-E4B-it': { inputPricePerMillion: 0.02, outputPricePerMillion: 0.04 },
348
+ },
349
+
350
+ // Local models typically have no per-token cost
351
+ ollama: {
352
+ // All local models are free
353
+ 'llama3.2': { inputPricePerMillion: 0, outputPricePerMillion: 0 },
354
+ 'llama3.1': { inputPricePerMillion: 0, outputPricePerMillion: 0 },
355
+ 'llama3': { inputPricePerMillion: 0, outputPricePerMillion: 0 },
356
+ 'llama4': { inputPricePerMillion: 0, outputPricePerMillion: 0 },
357
+ 'mistral': { inputPricePerMillion: 0, outputPricePerMillion: 0 },
358
+ 'codellama': { inputPricePerMillion: 0, outputPricePerMillion: 0 },
359
+ 'phi3': { inputPricePerMillion: 0, outputPricePerMillion: 0 },
360
+ 'phi4': { inputPricePerMillion: 0, outputPricePerMillion: 0 },
361
+ 'gemma2': { inputPricePerMillion: 0, outputPricePerMillion: 0 },
362
+ 'gemma3': { inputPricePerMillion: 0, outputPricePerMillion: 0 },
363
+ 'qwen2.5': { inputPricePerMillion: 0, outputPricePerMillion: 0 },
364
+ 'qwen3': { inputPricePerMillion: 0, outputPricePerMillion: 0 },
365
+ 'deepseek-r1': { inputPricePerMillion: 0, outputPricePerMillion: 0 },
366
+ 'deepseek-v3': { inputPricePerMillion: 0, outputPricePerMillion: 0 },
367
+ },
368
+ };
@@ -2,6 +2,7 @@ import { EnhancedDuckProvider } from './duck-provider-enhanced.js';
2
2
  import { ProviderManager } from './manager.js';
3
3
  import { ConfigManager } from '../config/config.js';
4
4
  import { FunctionBridge } from '../services/function-bridge.js';
5
+ import { UsageService } from '../services/usage.js';
5
6
  import { DuckResponse } from '../config/types.js';
6
7
  import { ChatOptions, MCPResult } from './types.js';
7
8
  import { logger } from '../utils/logger.js';
@@ -11,12 +12,12 @@ export class EnhancedProviderManager extends ProviderManager {
11
12
  private functionBridge?: FunctionBridge;
12
13
  private mcpEnabled: boolean = false;
13
14
 
14
- constructor(configManager: ConfigManager, functionBridge?: FunctionBridge) {
15
- super(configManager);
15
+ constructor(configManager: ConfigManager, functionBridge?: FunctionBridge, usageService?: UsageService) {
16
+ super(configManager, usageService);
16
17
  this.functionBridge = functionBridge;
17
- this.mcpEnabled = !!functionBridge &&
18
+ this.mcpEnabled = !!functionBridge &&
18
19
  (configManager.getConfig().mcp_bridge?.enabled || false);
19
-
20
+
20
21
  if (this.mcpEnabled) {
21
22
  this.initializeEnhancedProviders();
22
23
  }
@@ -91,6 +92,7 @@ export class EnhancedProviderManager extends ProviderManager {
91
92
 
92
93
  const provider = this.getEnhancedProvider(providerName);
93
94
  const startTime = Date.now();
95
+ const modelToUse = options?.model || provider.getInfo().model;
94
96
 
95
97
  try {
96
98
  const response = await provider.chat({
@@ -98,6 +100,18 @@ export class EnhancedProviderManager extends ProviderManager {
98
100
  ...options,
99
101
  });
100
102
 
103
+ // Record usage
104
+ if (this.usageService && response.usage) {
105
+ this.usageService.recordUsage(
106
+ provider.name,
107
+ response.model,
108
+ response.usage.promptTokens,
109
+ response.usage.completionTokens,
110
+ false,
111
+ false
112
+ );
113
+ }
114
+
101
115
  return {
102
116
  provider: provider.name,
103
117
  nickname: provider.nickname,
@@ -117,6 +131,11 @@ export class EnhancedProviderManager extends ProviderManager {
117
131
  mcpResults: response.mcpResults,
118
132
  };
119
133
  } catch (error: unknown) {
134
+ // Record error
135
+ if (this.usageService) {
136
+ this.usageService.recordUsage(provider.name, modelToUse, 0, 0, false, true);
137
+ }
138
+
120
139
  // Try failover if enabled
121
140
  if (this.configManager.getConfig().enable_failover && providerName === undefined) {
122
141
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -208,6 +227,7 @@ export class EnhancedProviderManager extends ProviderManager {
208
227
 
209
228
  const provider = this.getEnhancedProvider(providerName);
210
229
  const startTime = Date.now();
230
+ const modelToUse = options?.model || provider.getInfo().model;
211
231
 
212
232
  try {
213
233
  const response = await provider.retryWithApproval(
@@ -219,6 +239,18 @@ export class EnhancedProviderManager extends ProviderManager {
219
239
  }
220
240
  );
221
241
 
242
+ // Record usage
243
+ if (this.usageService && response.usage) {
244
+ this.usageService.recordUsage(
245
+ provider.name,
246
+ response.model,
247
+ response.usage.promptTokens,
248
+ response.usage.completionTokens,
249
+ false,
250
+ false
251
+ );
252
+ }
253
+
222
254
  return {
223
255
  provider: provider.name,
224
256
  nickname: provider.nickname,
@@ -238,6 +270,11 @@ export class EnhancedProviderManager extends ProviderManager {
238
270
  mcpResults: response.mcpResults,
239
271
  };
240
272
  } catch (error: unknown) {
273
+ // Record error
274
+ if (this.usageService) {
275
+ this.usageService.recordUsage(provider.name, modelToUse, 0, 0, false, true);
276
+ }
277
+
241
278
  const errorMessage = error instanceof Error ? error.message : String(error);
242
279
  throw new Error(`Failed to retry with approval: ${errorMessage}`);
243
280
  }
@@ -2,6 +2,7 @@ import { DuckProvider } from './provider.js';
2
2
  import { ConfigManager } from '../config/config.js';
3
3
  import { ProviderHealth, DuckResponse } from '../config/types.js';
4
4
  import { ChatOptions, ModelInfo } from './types.js';
5
+ import { UsageService } from '../services/usage.js';
5
6
  import { logger } from '../utils/logger.js';
6
7
  import { getRandomDuckMessage } from '../utils/ascii-art.js';
7
8
 
@@ -9,10 +10,12 @@ export class ProviderManager {
9
10
  private providers: Map<string, DuckProvider> = new Map();
10
11
  private healthStatus: Map<string, ProviderHealth> = new Map();
11
12
  protected configManager: ConfigManager;
13
+ protected usageService?: UsageService;
12
14
  private defaultProvider?: string;
13
15
 
14
- constructor(configManager: ConfigManager) {
16
+ constructor(configManager: ConfigManager, usageService?: UsageService) {
15
17
  this.configManager = configManager;
18
+ this.usageService = usageService;
16
19
  this.initializeProviders();
17
20
  }
18
21
 
@@ -91,6 +94,7 @@ export class ProviderManager {
91
94
  ): Promise<DuckResponse> {
92
95
  const provider = this.getProvider(providerName);
93
96
  const startTime = Date.now();
97
+ const modelToUse = options?.model || provider.getInfo().model;
94
98
 
95
99
  try {
96
100
  const response = await provider.chat({
@@ -98,6 +102,18 @@ export class ProviderManager {
98
102
  ...options,
99
103
  });
100
104
 
105
+ // Record usage
106
+ if (this.usageService && response.usage) {
107
+ this.usageService.recordUsage(
108
+ provider.name,
109
+ response.model,
110
+ response.usage.promptTokens,
111
+ response.usage.completionTokens,
112
+ false,
113
+ false
114
+ );
115
+ }
116
+
101
117
  return {
102
118
  provider: provider.name,
103
119
  nickname: provider.nickname,
@@ -115,6 +131,11 @@ export class ProviderManager {
115
131
  cached: false,
116
132
  };
117
133
  } catch (error: unknown) {
134
+ // Record error
135
+ if (this.usageService) {
136
+ this.usageService.recordUsage(provider.name, modelToUse, 0, 0, false, true);
137
+ }
138
+
118
139
  // Try failover if enabled
119
140
  if (this.configManager.getConfig().enable_failover && providerName === undefined) {
120
141
  const errorMessage = error instanceof Error ? error.message : String(error);
package/src/server.ts CHANGED
@@ -13,6 +13,8 @@ import { ConversationManager } from './services/conversation.js';
13
13
  import { ResponseCache } from './services/cache.js';
14
14
  import { HealthMonitor } from './services/health.js';
15
15
  import { MCPClientManager } from './services/mcp-client-manager.js';
16
+ import { PricingService } from './services/pricing.js';
17
+ import { UsageService } from './services/usage.js';
16
18
  import { DuckResponse } from './config/types.js';
17
19
  import { ApprovalService } from './services/approval.js';
18
20
  import { FunctionBridge } from './services/function-bridge.js';
@@ -37,9 +39,14 @@ import { getPendingApprovalsTool } from './tools/get-pending-approvals.js';
37
39
  import { approveMCPRequestTool } from './tools/approve-mcp-request.js';
38
40
  import { mcpStatusTool } from './tools/mcp-status.js';
39
41
 
42
+ // Import usage stats tool
43
+ import { getUsageStatsTool } from './tools/get-usage-stats.js';
44
+
40
45
  export class RubberDuckServer {
41
46
  private server: Server;
42
47
  private configManager: ConfigManager;
48
+ private pricingService: PricingService;
49
+ private usageService: UsageService;
43
50
  private providerManager: ProviderManager;
44
51
  private enhancedProviderManager?: EnhancedProviderManager;
45
52
  private conversationManager: ConversationManager;
@@ -67,9 +74,16 @@ export class RubberDuckServer {
67
74
 
68
75
  // Initialize managers
69
76
  this.configManager = new ConfigManager();
70
- this.providerManager = new ProviderManager(this.configManager);
77
+ const config = this.configManager.getConfig();
78
+
79
+ // Initialize pricing and usage services
80
+ this.pricingService = new PricingService(config.pricing);
81
+ this.usageService = new UsageService(this.pricingService);
82
+
83
+ // Initialize provider manager with usage tracking
84
+ this.providerManager = new ProviderManager(this.configManager, this.usageService);
71
85
  this.conversationManager = new ConversationManager();
72
- this.cache = new ResponseCache(this.configManager.getConfig().cache_ttl);
86
+ this.cache = new ResponseCache(config.cache_ttl);
73
87
  this.healthMonitor = new HealthMonitor(this.providerManager);
74
88
 
75
89
  // Initialize MCP bridge if enabled
@@ -105,10 +119,11 @@ export class RubberDuckServer {
105
119
  mcpConfig.trusted_tools_by_server || {}
106
120
  );
107
121
 
108
- // Initialize enhanced provider manager
122
+ // Initialize enhanced provider manager with usage tracking
109
123
  this.enhancedProviderManager = new EnhancedProviderManager(
110
124
  this.configManager,
111
- this.functionBridge
125
+ this.functionBridge,
126
+ this.usageService
112
127
  );
113
128
 
114
129
  this.mcpEnabled = true;
@@ -178,6 +193,10 @@ export class RubberDuckServer {
178
193
  case 'duck_debate':
179
194
  return await duckDebateTool(this.providerManager, args || {});
180
195
 
196
+ // Usage stats tool
197
+ case 'get_usage_stats':
198
+ return getUsageStatsTool(this.usageService, args || {});
199
+
181
200
  // MCP-specific tools
182
201
  case 'get_pending_approvals':
183
202
  if (!this.approvalService) {
@@ -641,6 +660,22 @@ export class RubberDuckServer {
641
660
  required: ['prompt', 'format'],
642
661
  },
643
662
  },
663
+ {
664
+ name: 'get_usage_stats',
665
+ description:
666
+ 'Get usage statistics for a time period. Shows token counts and costs (when pricing configured).',
667
+ inputSchema: {
668
+ type: 'object',
669
+ properties: {
670
+ period: {
671
+ type: 'string',
672
+ enum: ['today', '7d', '30d', 'all'],
673
+ default: 'today',
674
+ description: 'Time period for stats',
675
+ },
676
+ },
677
+ },
678
+ },
644
679
  ];
645
680
 
646
681
  // Add MCP-specific tools if enabled
@@ -732,6 +767,9 @@ export class RubberDuckServer {
732
767
  }
733
768
 
734
769
  async stop() {
770
+ // Cleanup usage service (flush pending writes)
771
+ this.usageService.shutdown();
772
+
735
773
  // Cleanup MCP resources
736
774
  if (this.approvalService) {
737
775
  this.approvalService.shutdown();