vibezcheck 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +219 -0
- package/dist/ai-sdk/index.d.mts +34 -0
- package/dist/ai-sdk/index.d.ts +34 -0
- package/dist/ai-sdk/index.js +982 -0
- package/dist/ai-sdk/index.js.map +1 -0
- package/dist/ai-sdk/index.mjs +944 -0
- package/dist/ai-sdk/index.mjs.map +1 -0
- package/dist/auth/index.d.mts +59 -0
- package/dist/auth/index.d.ts +59 -0
- package/dist/auth/index.js +129 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/index.mjs +90 -0
- package/dist/auth/index.mjs.map +1 -0
- package/dist/billing/index.d.mts +48 -0
- package/dist/billing/index.d.ts +48 -0
- package/dist/billing/index.js +128 -0
- package/dist/billing/index.js.map +1 -0
- package/dist/billing/index.mjs +90 -0
- package/dist/billing/index.mjs.map +1 -0
- package/dist/client-MJ3tl7bz.d.mts +44 -0
- package/dist/client-txrE0D_D.d.ts +44 -0
- package/dist/customers/index.d.mts +49 -0
- package/dist/customers/index.d.ts +49 -0
- package/dist/customers/index.js +158 -0
- package/dist/customers/index.js.map +1 -0
- package/dist/customers/index.mjs +119 -0
- package/dist/customers/index.mjs.map +1 -0
- package/dist/index.d.mts +92 -0
- package/dist/index.d.ts +92 -0
- package/dist/index.js +1458 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1386 -0
- package/dist/index.mjs.map +1 -0
- package/dist/meter/index.d.mts +131 -0
- package/dist/meter/index.d.ts +131 -0
- package/dist/meter/index.js +895 -0
- package/dist/meter/index.js.map +1 -0
- package/dist/meter/index.mjs +843 -0
- package/dist/meter/index.mjs.map +1 -0
- package/dist/pricing/index.d.mts +40 -0
- package/dist/pricing/index.d.ts +40 -0
- package/dist/pricing/index.js +178 -0
- package/dist/pricing/index.js.map +1 -0
- package/dist/pricing/index.mjs +146 -0
- package/dist/pricing/index.mjs.map +1 -0
- package/dist/types-CSrSmsd1.d.mts +159 -0
- package/dist/types-CSrSmsd1.d.ts +159 -0
- package/package.json +131 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1458 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
AnthropicStreamAccumulator: () => AnthropicStreamAccumulator,
|
|
34
|
+
ApiKeyAuth: () => ApiKeyAuth,
|
|
35
|
+
BillingHelper: () => BillingHelper,
|
|
36
|
+
CustomerCache: () => CustomerCache,
|
|
37
|
+
CustomerManager: () => CustomerManager,
|
|
38
|
+
MODEL_PRICING_TABLE: () => MODEL_PRICING_TABLE,
|
|
39
|
+
MeterBatcher: () => MeterBatcher,
|
|
40
|
+
VibezCheckClient: () => VibezCheckClient,
|
|
41
|
+
VibezMeter: () => VibezMeter,
|
|
42
|
+
calculateCost: () => calculateCost,
|
|
43
|
+
calculateUsageCost: () => calculateUsageCost,
|
|
44
|
+
createApiKeyAuth: () => createApiKeyAuth,
|
|
45
|
+
createBillingHelper: () => createBillingHelper,
|
|
46
|
+
createCustomerManager: () => createCustomerManager,
|
|
47
|
+
createMeter: () => createMeter,
|
|
48
|
+
createVibezCheck: () => createVibezCheck,
|
|
49
|
+
detectAndExtractUsage: () => detectAndExtractUsage,
|
|
50
|
+
extractAnthropicResponseUsage: () => extractAnthropicResponseUsage,
|
|
51
|
+
extractAuthToken: () => extractAuthToken,
|
|
52
|
+
extractGeminiResponseUsage: () => extractGeminiResponseUsage,
|
|
53
|
+
extractGenericResponseUsage: () => extractGenericResponseUsage,
|
|
54
|
+
extractOpenAIResponseUsage: () => extractOpenAIResponseUsage,
|
|
55
|
+
getModelPricing: () => getModelPricing,
|
|
56
|
+
inspectOpenAIStreamChunk: () => inspectOpenAIStreamChunk,
|
|
57
|
+
meteredModel: () => meteredModel,
|
|
58
|
+
normalizeModelKey: () => normalizeModelKey,
|
|
59
|
+
registerModelPricing: () => registerModelPricing,
|
|
60
|
+
trackTokens: () => trackTokens,
|
|
61
|
+
vibes: () => vibes,
|
|
62
|
+
vibescheck: () => vibescheck,
|
|
63
|
+
vibez: () => vibez,
|
|
64
|
+
vibezcheck: () => vibezcheck,
|
|
65
|
+
withBilling: () => withBilling,
|
|
66
|
+
wrapAnthropicStream: () => wrapAnthropicStream,
|
|
67
|
+
wrapGeminiStream: () => wrapGeminiStream,
|
|
68
|
+
wrapOpenAIStream: () => wrapOpenAIStream,
|
|
69
|
+
wrapStream: () => wrapStream,
|
|
70
|
+
wrapUniversalStream: () => wrapUniversalStream
|
|
71
|
+
});
|
|
72
|
+
module.exports = __toCommonJS(src_exports);
|
|
73
|
+
var import_stripe5 = __toESM(require("stripe"));
|
|
74
|
+
|
|
75
|
+
// src/meter/client.ts
|
|
76
|
+
var import_stripe = __toESM(require("stripe"));
|
|
77
|
+
|
|
78
|
+
// src/meter/batcher.ts
|
|
79
|
+
var MeterBatcher = class {
|
|
80
|
+
queue = [];
|
|
81
|
+
timer = null;
|
|
82
|
+
isFlushing = false;
|
|
83
|
+
maxBatchSize;
|
|
84
|
+
flushIntervalMs;
|
|
85
|
+
stripeClient;
|
|
86
|
+
eventName;
|
|
87
|
+
onUsageCallback;
|
|
88
|
+
onErrorCallback;
|
|
89
|
+
debug;
|
|
90
|
+
// In-memory ledger for local stats
|
|
91
|
+
totalRequests = 0;
|
|
92
|
+
totalTokens = 0;
|
|
93
|
+
totalInputTokens = 0;
|
|
94
|
+
totalOutputTokens = 0;
|
|
95
|
+
totalReasoningTokens = 0;
|
|
96
|
+
totalCostUSD = 0;
|
|
97
|
+
byModel = {};
|
|
98
|
+
constructor(options = {}) {
|
|
99
|
+
this.stripeClient = options.stripe;
|
|
100
|
+
this.eventName = options.eventName || "token-billing-tokens";
|
|
101
|
+
this.maxBatchSize = options.batching?.maxBatchSize ?? 50;
|
|
102
|
+
this.flushIntervalMs = options.batching?.flushIntervalMs ?? 50;
|
|
103
|
+
this.onUsageCallback = options.onUsage;
|
|
104
|
+
this.onErrorCallback = options.onError;
|
|
105
|
+
this.debug = options.debug ?? false;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Enqueue a usage event for batch dispatching
|
|
109
|
+
*/
|
|
110
|
+
enqueue(event) {
|
|
111
|
+
this.recordInLedger(event);
|
|
112
|
+
if (this.onUsageCallback) {
|
|
113
|
+
try {
|
|
114
|
+
const res = this.onUsageCallback(event);
|
|
115
|
+
if (res instanceof Promise) {
|
|
116
|
+
res.catch((err) => {
|
|
117
|
+
if (this.debug) console.error("[vibezcheck] Error in onUsage callback:", err);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
} catch (err) {
|
|
121
|
+
if (this.debug) console.error("[vibezcheck] Error in onUsage callback:", err);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (!this.stripeClient) {
|
|
125
|
+
if (this.debug) {
|
|
126
|
+
console.log(
|
|
127
|
+
`[vibezcheck:local] \u{1F4CA} ${event.model} | Tokens: ${event.usage.totalTokens} | Cost: $${event.cost.totalUSD.toFixed(6)}`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
this.queue.push(event);
|
|
133
|
+
if (this.queue.length >= this.maxBatchSize) {
|
|
134
|
+
this.flush().catch((err) => {
|
|
135
|
+
if (this.debug) console.error("[vibezcheck] Batch flush error:", err);
|
|
136
|
+
});
|
|
137
|
+
} else if (!this.timer) {
|
|
138
|
+
this.timer = setTimeout(() => {
|
|
139
|
+
this.timer = null;
|
|
140
|
+
this.flush().catch((err) => {
|
|
141
|
+
if (this.debug) console.error("[vibezcheck] Debounce flush error:", err);
|
|
142
|
+
});
|
|
143
|
+
}, this.flushIntervalMs);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Immediately flush all queued events to Stripe
|
|
148
|
+
*/
|
|
149
|
+
async flush() {
|
|
150
|
+
if (this.timer) {
|
|
151
|
+
clearTimeout(this.timer);
|
|
152
|
+
this.timer = null;
|
|
153
|
+
}
|
|
154
|
+
if (this.queue.length === 0 || !this.stripeClient || this.isFlushing) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
this.isFlushing = true;
|
|
158
|
+
const eventsToSend = [...this.queue];
|
|
159
|
+
this.queue = [];
|
|
160
|
+
try {
|
|
161
|
+
await this.sendEventsToStripe(eventsToSend);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
164
|
+
if (this.debug) {
|
|
165
|
+
console.error("[vibezcheck] Failed to send meter events to Stripe:", err);
|
|
166
|
+
}
|
|
167
|
+
if (this.onErrorCallback) {
|
|
168
|
+
this.onErrorCallback(err, eventsToSend);
|
|
169
|
+
}
|
|
170
|
+
} finally {
|
|
171
|
+
this.isFlushing = false;
|
|
172
|
+
if (this.queue.length > 0) {
|
|
173
|
+
this.flush().catch(() => {
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Sends events to Stripe Billing Meter Events API
|
|
180
|
+
*/
|
|
181
|
+
async sendEventsToStripe(events) {
|
|
182
|
+
if (!this.stripeClient) return;
|
|
183
|
+
for (const event of events) {
|
|
184
|
+
const customerId = event.customerId;
|
|
185
|
+
if (!customerId) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const timestamp = event.timestamp || (/* @__PURE__ */ new Date()).toISOString();
|
|
189
|
+
const model = `${event.provider}/${event.model}`;
|
|
190
|
+
if (event.usage.inputTokens > 0) {
|
|
191
|
+
try {
|
|
192
|
+
await this.stripeClient.v2.billing.meterEvents.create({
|
|
193
|
+
event_name: this.eventName,
|
|
194
|
+
timestamp,
|
|
195
|
+
payload: {
|
|
196
|
+
stripe_customer_id: customerId,
|
|
197
|
+
value: event.usage.inputTokens.toString(),
|
|
198
|
+
model,
|
|
199
|
+
token_type: "input",
|
|
200
|
+
cached_tokens: (event.usage.cachedTokens ?? 0).toString(),
|
|
201
|
+
...event.metadata ? event.metadata : {}
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
} catch (e) {
|
|
205
|
+
if (this.debug) console.warn("[vibezcheck] Input meter event error:", e);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (event.usage.outputTokens > 0) {
|
|
209
|
+
try {
|
|
210
|
+
await this.stripeClient.v2.billing.meterEvents.create({
|
|
211
|
+
event_name: this.eventName,
|
|
212
|
+
timestamp,
|
|
213
|
+
payload: {
|
|
214
|
+
stripe_customer_id: customerId,
|
|
215
|
+
value: event.usage.outputTokens.toString(),
|
|
216
|
+
model,
|
|
217
|
+
token_type: "output",
|
|
218
|
+
reasoning_tokens: (event.usage.reasoningTokens ?? 0).toString(),
|
|
219
|
+
visible_tokens: (event.usage.visibleOutputTokens ?? event.usage.outputTokens).toString(),
|
|
220
|
+
...event.metadata ? event.metadata : {}
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
} catch (e) {
|
|
224
|
+
if (this.debug) console.warn("[vibezcheck] Output meter event error:", e);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Updates internal in-memory ledger
|
|
231
|
+
*/
|
|
232
|
+
recordInLedger(event) {
|
|
233
|
+
this.totalRequests += 1;
|
|
234
|
+
this.totalTokens += event.usage.totalTokens;
|
|
235
|
+
this.totalInputTokens += event.usage.inputTokens;
|
|
236
|
+
this.totalOutputTokens += event.usage.outputTokens;
|
|
237
|
+
this.totalReasoningTokens += event.usage.reasoningTokens ?? 0;
|
|
238
|
+
this.totalCostUSD += event.cost.totalUSD;
|
|
239
|
+
const modelKey = event.model;
|
|
240
|
+
if (!this.byModel[modelKey]) {
|
|
241
|
+
this.byModel[modelKey] = { requests: 0, tokens: 0, costUSD: 0 };
|
|
242
|
+
}
|
|
243
|
+
this.byModel[modelKey].requests += 1;
|
|
244
|
+
this.byModel[modelKey].tokens += event.usage.totalTokens;
|
|
245
|
+
this.byModel[modelKey].costUSD += event.cost.totalUSD;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Get in-memory usage summary
|
|
249
|
+
*/
|
|
250
|
+
getSummary() {
|
|
251
|
+
return {
|
|
252
|
+
totalRequests: this.totalRequests,
|
|
253
|
+
totalTokens: this.totalTokens,
|
|
254
|
+
totalInputTokens: this.totalInputTokens,
|
|
255
|
+
totalOutputTokens: this.totalOutputTokens,
|
|
256
|
+
totalReasoningTokens: this.totalReasoningTokens,
|
|
257
|
+
totalCostUSD: Number(this.totalCostUSD.toFixed(6)),
|
|
258
|
+
byModel: { ...this.byModel }
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Reset in-memory ledger
|
|
263
|
+
*/
|
|
264
|
+
resetLedger() {
|
|
265
|
+
this.totalRequests = 0;
|
|
266
|
+
this.totalTokens = 0;
|
|
267
|
+
this.totalInputTokens = 0;
|
|
268
|
+
this.totalOutputTokens = 0;
|
|
269
|
+
this.totalReasoningTokens = 0;
|
|
270
|
+
this.totalCostUSD = 0;
|
|
271
|
+
this.byModel = {};
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// src/meter/extractors/openai.ts
|
|
276
|
+
function extractOpenAIResponseUsage(response) {
|
|
277
|
+
if (!response || typeof response !== "object") return null;
|
|
278
|
+
if ("choices" in response && "usage" in response && response.usage) {
|
|
279
|
+
const rawUsage = response.usage;
|
|
280
|
+
const model = response.model || "gpt-4o";
|
|
281
|
+
const inputTokens = rawUsage.prompt_tokens ?? 0;
|
|
282
|
+
const outputTokens = rawUsage.completion_tokens ?? 0;
|
|
283
|
+
const reasoningTokens = rawUsage.completion_tokens_details?.reasoning_tokens ?? 0;
|
|
284
|
+
const cachedTokens = rawUsage.prompt_tokens_details?.cached_tokens ?? 0;
|
|
285
|
+
return {
|
|
286
|
+
model,
|
|
287
|
+
provider: "openai",
|
|
288
|
+
usage: {
|
|
289
|
+
inputTokens,
|
|
290
|
+
outputTokens,
|
|
291
|
+
totalTokens: inputTokens + outputTokens,
|
|
292
|
+
reasoningTokens: reasoningTokens > 0 ? reasoningTokens : void 0,
|
|
293
|
+
visibleOutputTokens: Math.max(0, outputTokens - reasoningTokens),
|
|
294
|
+
cachedTokens: cachedTokens > 0 ? cachedTokens : void 0
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
if ("data" in response && "usage" in response && response.usage && "model" in response) {
|
|
299
|
+
const rawUsage = response.usage;
|
|
300
|
+
const inputTokens = rawUsage.prompt_tokens ?? 0;
|
|
301
|
+
return {
|
|
302
|
+
model: response.model || "text-embedding-3-small",
|
|
303
|
+
provider: "openai",
|
|
304
|
+
usage: {
|
|
305
|
+
inputTokens,
|
|
306
|
+
outputTokens: 0,
|
|
307
|
+
totalTokens: inputTokens
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
if ("status" in response && "usage" in response && response.usage) {
|
|
312
|
+
const rawUsage = response.usage;
|
|
313
|
+
const model = response.model || "gpt-5.6-sol";
|
|
314
|
+
const inputTokens = rawUsage.input_tokens ?? rawUsage.prompt_tokens ?? 0;
|
|
315
|
+
const outputTokens = rawUsage.output_tokens ?? rawUsage.completion_tokens ?? 0;
|
|
316
|
+
const reasoningTokens = rawUsage.output_token_details?.reasoning_tokens ?? rawUsage.completion_tokens_details?.reasoning_tokens ?? 0;
|
|
317
|
+
const cachedTokens = rawUsage.input_token_details?.cached_tokens ?? rawUsage.prompt_tokens_details?.cached_tokens ?? 0;
|
|
318
|
+
return {
|
|
319
|
+
model,
|
|
320
|
+
provider: "openai",
|
|
321
|
+
usage: {
|
|
322
|
+
inputTokens,
|
|
323
|
+
outputTokens,
|
|
324
|
+
totalTokens: inputTokens + outputTokens,
|
|
325
|
+
reasoningTokens: reasoningTokens > 0 ? reasoningTokens : void 0,
|
|
326
|
+
visibleOutputTokens: Math.max(0, outputTokens - reasoningTokens),
|
|
327
|
+
cachedTokens: cachedTokens > 0 ? cachedTokens : void 0
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
function inspectOpenAIStreamChunk(chunk) {
|
|
334
|
+
if (!chunk || typeof chunk !== "object") return {};
|
|
335
|
+
const model = chunk.model;
|
|
336
|
+
if (chunk.usage) {
|
|
337
|
+
const rawUsage = chunk.usage;
|
|
338
|
+
const inputTokens = rawUsage.prompt_tokens ?? rawUsage.input_tokens ?? 0;
|
|
339
|
+
const outputTokens = rawUsage.completion_tokens ?? rawUsage.output_tokens ?? 0;
|
|
340
|
+
const reasoningTokens = rawUsage.completion_tokens_details?.reasoning_tokens ?? rawUsage.output_token_details?.reasoning_tokens ?? 0;
|
|
341
|
+
const cachedTokens = rawUsage.prompt_tokens_details?.cached_tokens ?? rawUsage.input_token_details?.cached_tokens ?? 0;
|
|
342
|
+
return {
|
|
343
|
+
model,
|
|
344
|
+
usage: {
|
|
345
|
+
inputTokens,
|
|
346
|
+
outputTokens,
|
|
347
|
+
totalTokens: inputTokens + outputTokens,
|
|
348
|
+
reasoningTokens: reasoningTokens > 0 ? reasoningTokens : void 0,
|
|
349
|
+
visibleOutputTokens: Math.max(0, outputTokens - reasoningTokens),
|
|
350
|
+
cachedTokens: cachedTokens > 0 ? cachedTokens : void 0
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
if (chunk.type === "response.completed" || chunk.type === "response.done") {
|
|
355
|
+
if (chunk.response?.usage) {
|
|
356
|
+
const rawUsage = chunk.response.usage;
|
|
357
|
+
const inputTokens = rawUsage.input_tokens ?? 0;
|
|
358
|
+
const outputTokens = rawUsage.output_tokens ?? 0;
|
|
359
|
+
const reasoningTokens = rawUsage.output_token_details?.reasoning_tokens ?? 0;
|
|
360
|
+
const cachedTokens = rawUsage.input_token_details?.cached_tokens ?? 0;
|
|
361
|
+
return {
|
|
362
|
+
model: chunk.response.model || model,
|
|
363
|
+
usage: {
|
|
364
|
+
inputTokens,
|
|
365
|
+
outputTokens,
|
|
366
|
+
totalTokens: inputTokens + outputTokens,
|
|
367
|
+
reasoningTokens: reasoningTokens > 0 ? reasoningTokens : void 0,
|
|
368
|
+
visibleOutputTokens: Math.max(0, outputTokens - reasoningTokens),
|
|
369
|
+
cachedTokens: cachedTokens > 0 ? cachedTokens : void 0
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return { model };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// src/meter/extractors/anthropic.ts
|
|
378
|
+
function extractAnthropicResponseUsage(response) {
|
|
379
|
+
if (!response || typeof response !== "object") return null;
|
|
380
|
+
if (response.type === "message" || "content" in response && "usage" in response) {
|
|
381
|
+
const rawUsage = response.usage || {};
|
|
382
|
+
const model = response.model || "claude-3-7-sonnet";
|
|
383
|
+
const inputTokens = rawUsage.input_tokens ?? 0;
|
|
384
|
+
const outputTokens = rawUsage.output_tokens ?? 0;
|
|
385
|
+
const cachedTokens = rawUsage.cache_read_input_tokens ?? 0;
|
|
386
|
+
const cacheWriteTokens = rawUsage.cache_creation_input_tokens ?? 0;
|
|
387
|
+
let reasoningTokens = void 0;
|
|
388
|
+
if (Array.isArray(response.content)) {
|
|
389
|
+
const thinkingBlocks = response.content.filter((b) => b.type === "thinking");
|
|
390
|
+
if (thinkingBlocks.length > 0) {
|
|
391
|
+
reasoningTokens = rawUsage.thinking_tokens ?? void 0;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return {
|
|
395
|
+
model,
|
|
396
|
+
provider: "anthropic",
|
|
397
|
+
usage: {
|
|
398
|
+
inputTokens,
|
|
399
|
+
outputTokens,
|
|
400
|
+
totalTokens: inputTokens + outputTokens,
|
|
401
|
+
reasoningTokens,
|
|
402
|
+
visibleOutputTokens: reasoningTokens !== void 0 ? Math.max(0, outputTokens - reasoningTokens) : outputTokens,
|
|
403
|
+
cachedTokens: cachedTokens > 0 ? cachedTokens : void 0,
|
|
404
|
+
cacheWriteTokens: cacheWriteTokens > 0 ? cacheWriteTokens : void 0
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
var AnthropicStreamAccumulator = class {
|
|
411
|
+
model = "claude-3-7-sonnet";
|
|
412
|
+
inputTokens = 0;
|
|
413
|
+
outputTokens = 0;
|
|
414
|
+
cachedTokens = 0;
|
|
415
|
+
cacheWriteTokens = 0;
|
|
416
|
+
reasoningTokens = 0;
|
|
417
|
+
processEvent(event) {
|
|
418
|
+
if (!event || typeof event !== "object") return;
|
|
419
|
+
if (event.type === "message_start" && event.message) {
|
|
420
|
+
if (event.message.model) {
|
|
421
|
+
this.model = event.message.model;
|
|
422
|
+
}
|
|
423
|
+
if (event.message.usage) {
|
|
424
|
+
this.inputTokens = event.message.usage.input_tokens ?? 0;
|
|
425
|
+
this.cachedTokens = event.message.usage.cache_read_input_tokens ?? 0;
|
|
426
|
+
this.cacheWriteTokens = event.message.usage.cache_creation_input_tokens ?? 0;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (event.type === "message_delta" && event.usage) {
|
|
430
|
+
this.outputTokens = event.usage.output_tokens ?? 0;
|
|
431
|
+
if (event.usage.thinking_tokens) {
|
|
432
|
+
this.reasoningTokens = event.usage.thinking_tokens;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (event.type === "content_block_start" && event.content_block?.type === "thinking") {
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
getUsage() {
|
|
439
|
+
return {
|
|
440
|
+
model: this.model,
|
|
441
|
+
provider: "anthropic",
|
|
442
|
+
usage: {
|
|
443
|
+
inputTokens: this.inputTokens,
|
|
444
|
+
outputTokens: this.outputTokens,
|
|
445
|
+
totalTokens: this.inputTokens + this.outputTokens,
|
|
446
|
+
reasoningTokens: this.reasoningTokens > 0 ? this.reasoningTokens : void 0,
|
|
447
|
+
visibleOutputTokens: this.reasoningTokens > 0 ? Math.max(0, this.outputTokens - this.reasoningTokens) : this.outputTokens,
|
|
448
|
+
cachedTokens: this.cachedTokens > 0 ? this.cachedTokens : void 0,
|
|
449
|
+
cacheWriteTokens: this.cacheWriteTokens > 0 ? this.cacheWriteTokens : void 0
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
// src/meter/extractors/gemini.ts
|
|
456
|
+
function extractGeminiResponseUsage(response, fallbackModel = "gemini-3.7-flash") {
|
|
457
|
+
if (!response || typeof response !== "object") return null;
|
|
458
|
+
const usageMetadata = response.usageMetadata || response.response?.usageMetadata;
|
|
459
|
+
if (usageMetadata) {
|
|
460
|
+
const inputTokens = usageMetadata.promptTokenCount ?? 0;
|
|
461
|
+
const baseOutputTokens = usageMetadata.candidatesTokenCount ?? 0;
|
|
462
|
+
const thoughtsTokenCount = usageMetadata.thoughtsTokenCount ?? usageMetadata.reasoningTokenCount ?? 0;
|
|
463
|
+
const cachedTokens = usageMetadata.cachedContentTokenCount ?? 0;
|
|
464
|
+
const totalOutput = baseOutputTokens + thoughtsTokenCount;
|
|
465
|
+
const model = response.model || response.response?.model || fallbackModel;
|
|
466
|
+
return {
|
|
467
|
+
model,
|
|
468
|
+
provider: "google",
|
|
469
|
+
usage: {
|
|
470
|
+
inputTokens,
|
|
471
|
+
outputTokens: totalOutput,
|
|
472
|
+
totalTokens: inputTokens + totalOutput,
|
|
473
|
+
reasoningTokens: thoughtsTokenCount > 0 ? thoughtsTokenCount : void 0,
|
|
474
|
+
visibleOutputTokens: baseOutputTokens,
|
|
475
|
+
cachedTokens: cachedTokens > 0 ? cachedTokens : void 0
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// src/meter/extractors/generic.ts
|
|
483
|
+
function extractGenericResponseUsage(response, fallbackModel = "generic-llm", fallbackProvider = "generic") {
|
|
484
|
+
if (!response || typeof response !== "object") return null;
|
|
485
|
+
const usage = response.usage || response.token_usage || response.usageMetadata;
|
|
486
|
+
if (usage) {
|
|
487
|
+
const inputTokens = usage.prompt_tokens ?? usage.input_tokens ?? usage.promptTokenCount ?? usage.prompt_eval_count ?? 0;
|
|
488
|
+
const outputTokens = usage.completion_tokens ?? usage.output_tokens ?? usage.candidatesTokenCount ?? usage.eval_count ?? 0;
|
|
489
|
+
const reasoningTokens = usage.reasoning_tokens ?? usage.thoughtsTokenCount ?? usage.completion_tokens_details?.reasoning_tokens ?? 0;
|
|
490
|
+
const cachedTokens = usage.prompt_tokens_details?.cached_tokens ?? usage.cached_tokens ?? usage.cachedContentTokenCount ?? 0;
|
|
491
|
+
const model = response.model || fallbackModel;
|
|
492
|
+
const provider = response.provider || fallbackProvider;
|
|
493
|
+
return {
|
|
494
|
+
model,
|
|
495
|
+
provider,
|
|
496
|
+
usage: {
|
|
497
|
+
inputTokens,
|
|
498
|
+
outputTokens,
|
|
499
|
+
totalTokens: inputTokens + outputTokens,
|
|
500
|
+
reasoningTokens: reasoningTokens > 0 ? reasoningTokens : void 0,
|
|
501
|
+
visibleOutputTokens: Math.max(0, outputTokens - reasoningTokens),
|
|
502
|
+
cachedTokens: cachedTokens > 0 ? cachedTokens : void 0
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// src/meter/extractors/index.ts
|
|
510
|
+
function detectAndExtractUsage(response, fallbackModel, fallbackProvider) {
|
|
511
|
+
if (!response || typeof response !== "object") return null;
|
|
512
|
+
const openaiResult = extractOpenAIResponseUsage(response);
|
|
513
|
+
if (openaiResult) return openaiResult;
|
|
514
|
+
const anthropicResult = extractAnthropicResponseUsage(response);
|
|
515
|
+
if (anthropicResult) return anthropicResult;
|
|
516
|
+
const geminiResult = extractGeminiResponseUsage(response, fallbackModel);
|
|
517
|
+
if (geminiResult) return geminiResult;
|
|
518
|
+
const genericResult = extractGenericResponseUsage(response, fallbackModel, fallbackProvider);
|
|
519
|
+
if (genericResult) return genericResult;
|
|
520
|
+
return null;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// src/pricing/table.ts
|
|
524
|
+
var MODEL_PRICING_TABLE = {
|
|
525
|
+
// --- OpenAI ---
|
|
526
|
+
"gpt-5.6-sol": { inputPer1M: 4, outputPer1M: 20, cachedInputPer1M: 0.4 },
|
|
527
|
+
"gpt-5.6-terra": { inputPer1M: 2, outputPer1M: 12, cachedInputPer1M: 0.2 },
|
|
528
|
+
"gpt-5.6-luna": { inputPer1M: 0.2, outputPer1M: 1.2, cachedInputPer1M: 0.02 },
|
|
529
|
+
"gpt-5": { inputPer1M: 4, outputPer1M: 20, cachedInputPer1M: 0.4 },
|
|
530
|
+
"gpt-5-mini": { inputPer1M: 0.2, outputPer1M: 1.2, cachedInputPer1M: 0.02 },
|
|
531
|
+
"o1": { inputPer1M: 15, outputPer1M: 60, cachedInputPer1M: 7.5 },
|
|
532
|
+
"o1-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cachedInputPer1M: 0.55 },
|
|
533
|
+
"o3": { inputPer1M: 15, outputPer1M: 60, cachedInputPer1M: 7.5 },
|
|
534
|
+
"o3-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cachedInputPer1M: 0.55 },
|
|
535
|
+
"gpt-4o": { inputPer1M: 2.5, outputPer1M: 10, cachedInputPer1M: 1.25 },
|
|
536
|
+
"gpt-4o-mini": { inputPer1M: 0.15, outputPer1M: 0.6, cachedInputPer1M: 0.075 },
|
|
537
|
+
"gpt-4.1": { inputPer1M: 2, outputPer1M: 8, cachedInputPer1M: 1 },
|
|
538
|
+
"gpt-4.1-nano": { inputPer1M: 0.1, outputPer1M: 0.4, cachedInputPer1M: 0.05 },
|
|
539
|
+
"text-embedding-3-small": { inputPer1M: 0.02, outputPer1M: 0 },
|
|
540
|
+
"text-embedding-3-large": { inputPer1M: 0.13, outputPer1M: 0 },
|
|
541
|
+
// --- Anthropic ---
|
|
542
|
+
"claude-3-7-sonnet": { inputPer1M: 0.59, outputPer1M: 2.93, cachedInputPer1M: 0.3 },
|
|
543
|
+
"claude-sonnet-5": { inputPer1M: 2, outputPer1M: 10, cachedInputPer1M: 0.3 },
|
|
544
|
+
"claude-3-5-sonnet": { inputPer1M: 3, outputPer1M: 15, cachedInputPer1M: 0.3 },
|
|
545
|
+
"claude-3-5-haiku": { inputPer1M: 0.8, outputPer1M: 4, cachedInputPer1M: 0.08 },
|
|
546
|
+
"haiku-4.5": { inputPer1M: 1, outputPer1M: 5, cachedInputPer1M: 0.1 },
|
|
547
|
+
"claude-opus-5": { inputPer1M: 5, outputPer1M: 25, cachedInputPer1M: 1.5 },
|
|
548
|
+
"claude-3-opus": { inputPer1M: 15, outputPer1M: 75, cachedInputPer1M: 1.5 },
|
|
549
|
+
// --- Google Gemini ---
|
|
550
|
+
"gemini-3.7-flash": { inputPer1M: 0.75, outputPer1M: 3.75, cachedInputPer1M: 0.18 },
|
|
551
|
+
"gemini-3.1-pro": { inputPer1M: 2, outputPer1M: 12, cachedInputPer1M: 0.5 },
|
|
552
|
+
"gemini-3.5-flash": { inputPer1M: 1.5, outputPer1M: 9, cachedInputPer1M: 0.38 },
|
|
553
|
+
"gemini-3.1-flash-lite": { inputPer1M: 0.25, outputPer1M: 1.5, cachedInputPer1M: 0.06 },
|
|
554
|
+
"gemini-2.0-flash": { inputPer1M: 0.1, outputPer1M: 0.4, cachedInputPer1M: 0.025 },
|
|
555
|
+
"gemini-1.5-pro": { inputPer1M: 1.25, outputPer1M: 5, cachedInputPer1M: 0.3125 },
|
|
556
|
+
"gemini-1.5-flash": { inputPer1M: 0.075, outputPer1M: 0.3, cachedInputPer1M: 0.01875 },
|
|
557
|
+
// --- xAI Grok ---
|
|
558
|
+
"grok-4.6": { inputPer1M: 3, outputPer1M: 15 },
|
|
559
|
+
"grok-2": { inputPer1M: 2, outputPer1M: 10 },
|
|
560
|
+
"grok-2-vision": { inputPer1M: 2, outputPer1M: 10 },
|
|
561
|
+
"grok-beta": { inputPer1M: 5, outputPer1M: 15 },
|
|
562
|
+
// --- Mistral ---
|
|
563
|
+
"mistral-large-3": { inputPer1M: 2, outputPer1M: 6 },
|
|
564
|
+
"mistral-large-latest": { inputPer1M: 2, outputPer1M: 6 },
|
|
565
|
+
"codestral-latest": { inputPer1M: 0.3, outputPer1M: 0.9 },
|
|
566
|
+
"mistral-small-latest": { inputPer1M: 0.2, outputPer1M: 0.6 },
|
|
567
|
+
"ministral-8b-latest": { inputPer1M: 0.1, outputPer1M: 0.1 },
|
|
568
|
+
// --- Groq LPUs ---
|
|
569
|
+
"llama-3.3-70b-versatile": { inputPer1M: 0.59, outputPer1M: 0.79 },
|
|
570
|
+
"llama-3.1-8b-instant": { inputPer1M: 0.05, outputPer1M: 0.08 },
|
|
571
|
+
"deepseek-r1-distill-llama-70b": { inputPer1M: 0.75, outputPer1M: 0.99 },
|
|
572
|
+
"qwen-2.5-32b": { inputPer1M: 0.29, outputPer1M: 0.39 },
|
|
573
|
+
// --- DeepSeek ---
|
|
574
|
+
"deepseek-v4-pro": { inputPer1M: 0.66, outputPer1M: 1.98, cachedInputPer1M: 0.15 },
|
|
575
|
+
"deepseek-v4-flash": { inputPer1M: 0.22, outputPer1M: 0.66, cachedInputPer1M: 0.05 },
|
|
576
|
+
"deepseek-chat": { inputPer1M: 0.22, outputPer1M: 0.66, cachedInputPer1M: 0.05 },
|
|
577
|
+
"deepseek-reasoner": { inputPer1M: 0.66, outputPer1M: 1.98, cachedInputPer1M: 0.15 },
|
|
578
|
+
// --- Cohere ---
|
|
579
|
+
"command-r-plus": { inputPer1M: 2.5, outputPer1M: 10 },
|
|
580
|
+
"command-r": { inputPer1M: 0.15, outputPer1M: 0.6 }
|
|
581
|
+
};
|
|
582
|
+
var customPricingRegistry = {};
|
|
583
|
+
function normalizeModelKey(rawModel) {
|
|
584
|
+
if (!rawModel) return "unknown";
|
|
585
|
+
let model = rawModel.toLowerCase().trim();
|
|
586
|
+
if (model.includes("/")) {
|
|
587
|
+
model = model.split("/")[1] || model;
|
|
588
|
+
}
|
|
589
|
+
model = model.replace(/-\d{8}$/, "");
|
|
590
|
+
model = model.replace(/-\d{4}-\d{2}-\d{2}$/, "");
|
|
591
|
+
return model;
|
|
592
|
+
}
|
|
593
|
+
function getModelPricing(modelName) {
|
|
594
|
+
const normalized = normalizeModelKey(modelName);
|
|
595
|
+
if (customPricingRegistry[normalized]) {
|
|
596
|
+
return customPricingRegistry[normalized];
|
|
597
|
+
}
|
|
598
|
+
if (customPricingRegistry[modelName]) {
|
|
599
|
+
return customPricingRegistry[modelName];
|
|
600
|
+
}
|
|
601
|
+
if (MODEL_PRICING_TABLE[normalized]) {
|
|
602
|
+
return MODEL_PRICING_TABLE[normalized];
|
|
603
|
+
}
|
|
604
|
+
if (MODEL_PRICING_TABLE[modelName]) {
|
|
605
|
+
return MODEL_PRICING_TABLE[modelName];
|
|
606
|
+
}
|
|
607
|
+
return {
|
|
608
|
+
inputPer1M: 1,
|
|
609
|
+
outputPer1M: 3,
|
|
610
|
+
cachedInputPer1M: 0.5
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
function registerModelPricing(modelName, rates) {
|
|
614
|
+
const normalized = normalizeModelKey(modelName);
|
|
615
|
+
customPricingRegistry[normalized] = rates;
|
|
616
|
+
customPricingRegistry[modelName] = rates;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// src/pricing/calculator.ts
|
|
620
|
+
function calculateCost(params) {
|
|
621
|
+
const rates = getModelPricing(params.model);
|
|
622
|
+
const inputTokens = params.inputTokens ?? 0;
|
|
623
|
+
const outputTokens = params.outputTokens ?? 0;
|
|
624
|
+
const reasoningTokens = params.reasoningTokens ?? 0;
|
|
625
|
+
const cachedTokens = params.cachedTokens ?? 0;
|
|
626
|
+
const regularInputTokens = Math.max(0, inputTokens - cachedTokens);
|
|
627
|
+
const regularInputCost = regularInputTokens / 1e6 * rates.inputPer1M;
|
|
628
|
+
const cachedRate = rates.cachedInputPer1M ?? rates.inputPer1M * 0.5;
|
|
629
|
+
const cachedInputCost = cachedTokens / 1e6 * cachedRate;
|
|
630
|
+
const inputCostUSD = regularInputCost + cachedInputCost;
|
|
631
|
+
const outputCostUSD = outputTokens / 1e6 * rates.outputPer1M;
|
|
632
|
+
const reasoningRate = rates.reasoningPer1M ?? rates.outputPer1M;
|
|
633
|
+
const reasoningCostUSD = reasoningTokens / 1e6 * reasoningRate;
|
|
634
|
+
const standardCacheCost = cachedTokens / 1e6 * rates.inputPer1M;
|
|
635
|
+
const cachedDiscountUSD = Math.max(0, standardCacheCost - cachedInputCost);
|
|
636
|
+
const totalUSD = inputCostUSD + outputCostUSD;
|
|
637
|
+
const markup = params.markupMultiplier ?? 1;
|
|
638
|
+
const retailUSD = markup !== 1 ? totalUSD * markup : void 0;
|
|
639
|
+
return {
|
|
640
|
+
inputCostUSD: Number(inputCostUSD.toFixed(8)),
|
|
641
|
+
outputCostUSD: Number(outputCostUSD.toFixed(8)),
|
|
642
|
+
reasoningCostUSD: reasoningTokens > 0 ? Number(reasoningCostUSD.toFixed(8)) : void 0,
|
|
643
|
+
cachedDiscountUSD: cachedTokens > 0 ? Number(cachedDiscountUSD.toFixed(8)) : void 0,
|
|
644
|
+
totalUSD: Number(totalUSD.toFixed(8)),
|
|
645
|
+
retailUSD: retailUSD ? Number(retailUSD.toFixed(8)) : void 0,
|
|
646
|
+
currency: rates.currency || "USD"
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
function calculateUsageCost(model, usage, markupMultiplier) {
|
|
650
|
+
return calculateCost({
|
|
651
|
+
model,
|
|
652
|
+
inputTokens: usage.inputTokens,
|
|
653
|
+
outputTokens: usage.outputTokens,
|
|
654
|
+
reasoningTokens: usage.reasoningTokens,
|
|
655
|
+
cachedTokens: usage.cachedTokens,
|
|
656
|
+
cacheWriteTokens: usage.cacheWriteTokens,
|
|
657
|
+
markupMultiplier
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// src/meter/stream.ts
|
|
662
|
+
function wrapOpenAIStream(stream, options, onComplete) {
|
|
663
|
+
let detectedModel = options.model || "gpt-4o";
|
|
664
|
+
let finalUsage = null;
|
|
665
|
+
const wrappedAsyncIterable = {
|
|
666
|
+
async *[Symbol.asyncIterator]() {
|
|
667
|
+
try {
|
|
668
|
+
for await (const chunk of stream) {
|
|
669
|
+
const inspected = inspectOpenAIStreamChunk(chunk);
|
|
670
|
+
if (inspected.model) {
|
|
671
|
+
detectedModel = inspected.model;
|
|
672
|
+
}
|
|
673
|
+
if (inspected.usage) {
|
|
674
|
+
finalUsage = inspected.usage;
|
|
675
|
+
}
|
|
676
|
+
yield chunk;
|
|
677
|
+
}
|
|
678
|
+
} finally {
|
|
679
|
+
if (finalUsage) {
|
|
680
|
+
const cost = calculateUsageCost(detectedModel, finalUsage);
|
|
681
|
+
const customerId = typeof options.customer === "string" ? options.customer : options.customer?.id || options.customerId;
|
|
682
|
+
const event = {
|
|
683
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
684
|
+
model: detectedModel,
|
|
685
|
+
provider: "openai",
|
|
686
|
+
usage: finalUsage,
|
|
687
|
+
cost,
|
|
688
|
+
customerId,
|
|
689
|
+
metadata: options.metadata
|
|
690
|
+
};
|
|
691
|
+
onComplete(event);
|
|
692
|
+
if (options.onUsage) {
|
|
693
|
+
options.onUsage(event);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
return wrappedAsyncIterable;
|
|
700
|
+
}
|
|
701
|
+
function wrapAnthropicStream(stream, options, onComplete) {
|
|
702
|
+
const accumulator = new AnthropicStreamAccumulator();
|
|
703
|
+
const wrappedAsyncIterable = {
|
|
704
|
+
async *[Symbol.asyncIterator]() {
|
|
705
|
+
try {
|
|
706
|
+
for await (const event of stream) {
|
|
707
|
+
accumulator.processEvent(event);
|
|
708
|
+
yield event;
|
|
709
|
+
}
|
|
710
|
+
} finally {
|
|
711
|
+
const extracted = accumulator.getUsage();
|
|
712
|
+
const model = options.model || extracted.model;
|
|
713
|
+
const cost = calculateUsageCost(model, extracted.usage);
|
|
714
|
+
const customerId = typeof options.customer === "string" ? options.customer : options.customer?.id || options.customerId;
|
|
715
|
+
const usageEvent = {
|
|
716
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
717
|
+
model,
|
|
718
|
+
provider: "anthropic",
|
|
719
|
+
usage: extracted.usage,
|
|
720
|
+
cost,
|
|
721
|
+
customerId,
|
|
722
|
+
metadata: options.metadata
|
|
723
|
+
};
|
|
724
|
+
onComplete(usageEvent);
|
|
725
|
+
if (options.onUsage) {
|
|
726
|
+
options.onUsage(usageEvent);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
return wrappedAsyncIterable;
|
|
732
|
+
}
|
|
733
|
+
function wrapGeminiStream(result, options, onComplete) {
|
|
734
|
+
if (!result || !result.stream) return result;
|
|
735
|
+
const originalStream = result.stream;
|
|
736
|
+
const model = options.model || "gemini-3.7-flash";
|
|
737
|
+
let lastChunkWithUsage = null;
|
|
738
|
+
const wrappedStream = (async function* () {
|
|
739
|
+
try {
|
|
740
|
+
for await (const chunk of originalStream) {
|
|
741
|
+
if (chunk.usageMetadata) {
|
|
742
|
+
lastChunkWithUsage = chunk;
|
|
743
|
+
}
|
|
744
|
+
yield chunk;
|
|
745
|
+
}
|
|
746
|
+
} finally {
|
|
747
|
+
if (lastChunkWithUsage) {
|
|
748
|
+
const extracted = extractGeminiResponseUsage(lastChunkWithUsage, model);
|
|
749
|
+
if (extracted) {
|
|
750
|
+
const cost = calculateUsageCost(extracted.model, extracted.usage);
|
|
751
|
+
const customerId = typeof options.customer === "string" ? options.customer : options.customer?.id || options.customerId;
|
|
752
|
+
const event = {
|
|
753
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
754
|
+
model: extracted.model,
|
|
755
|
+
provider: "google",
|
|
756
|
+
usage: extracted.usage,
|
|
757
|
+
cost,
|
|
758
|
+
customerId,
|
|
759
|
+
metadata: options.metadata
|
|
760
|
+
};
|
|
761
|
+
onComplete(event);
|
|
762
|
+
if (options.onUsage) {
|
|
763
|
+
options.onUsage(event);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
})();
|
|
769
|
+
return {
|
|
770
|
+
...result,
|
|
771
|
+
stream: wrappedStream
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
function wrapUniversalStream(stream, options = {}, onComplete) {
|
|
775
|
+
if (!stream || typeof stream !== "object") return stream;
|
|
776
|
+
if ("stream" in stream && "response" in stream) {
|
|
777
|
+
return wrapGeminiStream(stream, options, onComplete);
|
|
778
|
+
}
|
|
779
|
+
if (Symbol.asyncIterator in stream) {
|
|
780
|
+
if (options.provider === "anthropic") {
|
|
781
|
+
return wrapAnthropicStream(stream, options, onComplete);
|
|
782
|
+
}
|
|
783
|
+
return wrapOpenAIStream(stream, options, onComplete);
|
|
784
|
+
}
|
|
785
|
+
return stream;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// src/meter/client.ts
|
|
789
|
+
var VibezMeter = class {
|
|
790
|
+
batcher;
|
|
791
|
+
stripeClient;
|
|
792
|
+
markupMultiplier;
|
|
793
|
+
constructor(options = {}) {
|
|
794
|
+
this.markupMultiplier = options.markupMultiplier;
|
|
795
|
+
if (options.stripe) {
|
|
796
|
+
this.stripeClient = options.stripe;
|
|
797
|
+
} else if (options.apiKey || process.env.STRIPE_SECRET_KEY) {
|
|
798
|
+
const key = options.apiKey || process.env.STRIPE_SECRET_KEY;
|
|
799
|
+
this.stripeClient = new import_stripe.default(key, {
|
|
800
|
+
appInfo: {
|
|
801
|
+
name: "vibezcheck",
|
|
802
|
+
version: "0.1.0",
|
|
803
|
+
url: "https://vibezcheck.xyz"
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
this.batcher = new MeterBatcher({
|
|
808
|
+
...options,
|
|
809
|
+
stripe: this.stripeClient
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Track token usage from a non-streaming response object (OpenAI, Anthropic, Gemini, etc.)
|
|
814
|
+
*/
|
|
815
|
+
trackUsage(response, options = {}) {
|
|
816
|
+
const extracted = detectAndExtractUsage(response, options.model, options.provider);
|
|
817
|
+
if (!extracted) {
|
|
818
|
+
return null;
|
|
819
|
+
}
|
|
820
|
+
const model = options.model || extracted.model;
|
|
821
|
+
const cost = calculateUsageCost(model, extracted.usage, this.markupMultiplier);
|
|
822
|
+
const customerId = typeof options.customer === "string" ? options.customer : options.customer?.id || options.customerId;
|
|
823
|
+
const event = {
|
|
824
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
825
|
+
model,
|
|
826
|
+
provider: extracted.provider,
|
|
827
|
+
usage: extracted.usage,
|
|
828
|
+
cost,
|
|
829
|
+
customerId,
|
|
830
|
+
metadata: options.metadata
|
|
831
|
+
};
|
|
832
|
+
this.batcher.enqueue(event);
|
|
833
|
+
return event;
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Wrap any LLM stream (OpenAI, Anthropic, Gemini) with zero added latency
|
|
837
|
+
*/
|
|
838
|
+
wrapStream(stream, options = {}) {
|
|
839
|
+
return wrapUniversalStream(stream, options, (event) => {
|
|
840
|
+
this.batcher.enqueue(event);
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Directly record token usage manually
|
|
845
|
+
*/
|
|
846
|
+
recordUsage(options) {
|
|
847
|
+
const inputTokens = options.inputTokens ?? 0;
|
|
848
|
+
const outputTokens = options.outputTokens ?? 0;
|
|
849
|
+
const reasoningTokens = options.reasoningTokens;
|
|
850
|
+
const cachedTokens = options.cachedTokens;
|
|
851
|
+
const usage = {
|
|
852
|
+
inputTokens,
|
|
853
|
+
outputTokens,
|
|
854
|
+
totalTokens: inputTokens + outputTokens,
|
|
855
|
+
reasoningTokens,
|
|
856
|
+
visibleOutputTokens: reasoningTokens !== void 0 ? Math.max(0, outputTokens - reasoningTokens) : outputTokens,
|
|
857
|
+
cachedTokens
|
|
858
|
+
};
|
|
859
|
+
const cost = calculateUsageCost(options.model, usage, this.markupMultiplier);
|
|
860
|
+
const customerId = typeof options.customer === "string" ? options.customer : options.customer?.id || options.customerId;
|
|
861
|
+
const event = {
|
|
862
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
863
|
+
model: options.model,
|
|
864
|
+
provider: options.provider || "custom",
|
|
865
|
+
usage,
|
|
866
|
+
cost,
|
|
867
|
+
customerId,
|
|
868
|
+
metadata: options.metadata
|
|
869
|
+
};
|
|
870
|
+
this.batcher.enqueue(event);
|
|
871
|
+
return event;
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Flush pending events to Stripe (vital for Serverless & Edge environments)
|
|
875
|
+
*/
|
|
876
|
+
async flush() {
|
|
877
|
+
await this.batcher.flush();
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Get in-memory aggregated usage statistics
|
|
881
|
+
*/
|
|
882
|
+
getUsageSummary() {
|
|
883
|
+
return this.batcher.getSummary();
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Reset in-memory ledger
|
|
887
|
+
*/
|
|
888
|
+
resetSummary() {
|
|
889
|
+
this.batcher.resetLedger();
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
function createMeter(options = {}) {
|
|
893
|
+
return new VibezMeter(options);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// src/ai-sdk/with-billing.ts
|
|
897
|
+
function withBilling(model, options = {}) {
|
|
898
|
+
if (!model || typeof model !== "object") {
|
|
899
|
+
return model;
|
|
900
|
+
}
|
|
901
|
+
const meter = options.meter || createMeter({
|
|
902
|
+
apiKey: options.stripeApiKey,
|
|
903
|
+
eventName: options.eventName
|
|
904
|
+
});
|
|
905
|
+
const customerId = typeof options.customer === "string" ? options.customer : options.customer?.id || options.customerId;
|
|
906
|
+
const modelId = model.modelId || "unknown-model";
|
|
907
|
+
const provider = model.provider?.replace(/^@ai-sdk\//, "") || "ai-sdk";
|
|
908
|
+
const handleUsage = (rawUsage) => {
|
|
909
|
+
if (!rawUsage) return;
|
|
910
|
+
const inputTokens = rawUsage.promptTokens ?? rawUsage.inputTokens ?? 0;
|
|
911
|
+
const outputTokens = rawUsage.completionTokens ?? rawUsage.outputTokens ?? 0;
|
|
912
|
+
const reasoningTokens = rawUsage.reasoningTokens ?? rawUsage.completionTokensDetails?.reasoningTokens ?? rawUsage.outputTokenDetails?.reasoningTokens ?? 0;
|
|
913
|
+
const cachedTokens = rawUsage.promptTokensDetails?.cachedTokens ?? rawUsage.inputTokenDetails?.cachedTokens ?? 0;
|
|
914
|
+
const usage = {
|
|
915
|
+
inputTokens,
|
|
916
|
+
outputTokens,
|
|
917
|
+
totalTokens: inputTokens + outputTokens,
|
|
918
|
+
reasoningTokens: reasoningTokens > 0 ? reasoningTokens : void 0,
|
|
919
|
+
visibleOutputTokens: Math.max(0, outputTokens - reasoningTokens),
|
|
920
|
+
cachedTokens: cachedTokens > 0 ? cachedTokens : void 0
|
|
921
|
+
};
|
|
922
|
+
const cost = calculateUsageCost(modelId, usage);
|
|
923
|
+
const event = {
|
|
924
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
925
|
+
model: modelId,
|
|
926
|
+
provider,
|
|
927
|
+
usage,
|
|
928
|
+
cost,
|
|
929
|
+
customerId,
|
|
930
|
+
metadata: options.metadata
|
|
931
|
+
};
|
|
932
|
+
meter.recordUsage({
|
|
933
|
+
model: modelId,
|
|
934
|
+
provider,
|
|
935
|
+
inputTokens,
|
|
936
|
+
outputTokens,
|
|
937
|
+
reasoningTokens: usage.reasoningTokens,
|
|
938
|
+
cachedTokens: usage.cachedTokens,
|
|
939
|
+
customerId,
|
|
940
|
+
metadata: options.metadata
|
|
941
|
+
});
|
|
942
|
+
if (options.onUsage) {
|
|
943
|
+
options.onUsage(event);
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
return new Proxy(model, {
|
|
947
|
+
get(target, prop, receiver) {
|
|
948
|
+
const originalValue = Reflect.get(target, prop, receiver);
|
|
949
|
+
if (prop === "doGenerate" && typeof originalValue === "function") {
|
|
950
|
+
return async function(...args) {
|
|
951
|
+
const result = await originalValue.apply(target, args);
|
|
952
|
+
if (result && result.usage) {
|
|
953
|
+
handleUsage(result.usage);
|
|
954
|
+
}
|
|
955
|
+
return result;
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
if (prop === "doStream" && typeof originalValue === "function") {
|
|
959
|
+
return async function(...args) {
|
|
960
|
+
const result = await originalValue.apply(target, args);
|
|
961
|
+
if (!result || !result.stream) {
|
|
962
|
+
return result;
|
|
963
|
+
}
|
|
964
|
+
const originalStream = result.stream;
|
|
965
|
+
if (typeof originalStream.getReader === "function") {
|
|
966
|
+
const reader = originalStream.getReader();
|
|
967
|
+
const transformedStream = new ReadableStream({
|
|
968
|
+
async start(controller) {
|
|
969
|
+
try {
|
|
970
|
+
while (true) {
|
|
971
|
+
const { done, value } = await reader.read();
|
|
972
|
+
if (done) {
|
|
973
|
+
controller.close();
|
|
974
|
+
break;
|
|
975
|
+
}
|
|
976
|
+
if (value && typeof value === "object") {
|
|
977
|
+
if (value.type === "finish" && value.usage) {
|
|
978
|
+
handleUsage(value.usage);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
controller.enqueue(value);
|
|
982
|
+
}
|
|
983
|
+
} catch (err) {
|
|
984
|
+
controller.error(err);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
return {
|
|
989
|
+
...result,
|
|
990
|
+
stream: transformedStream
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
if (Symbol.asyncIterator in originalStream) {
|
|
994
|
+
const wrappedAsyncIterable = {
|
|
995
|
+
async *[Symbol.asyncIterator]() {
|
|
996
|
+
for await (const chunk of originalStream) {
|
|
997
|
+
if (chunk && typeof chunk === "object") {
|
|
998
|
+
if (chunk.type === "finish" && chunk.usage) {
|
|
999
|
+
handleUsage(chunk.usage);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
yield chunk;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
};
|
|
1006
|
+
return {
|
|
1007
|
+
...result,
|
|
1008
|
+
stream: wrappedAsyncIterable
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
return result;
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
return originalValue;
|
|
1015
|
+
}
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
var meteredModel = withBilling;
|
|
1019
|
+
|
|
1020
|
+
// src/customers/manager.ts
|
|
1021
|
+
var import_stripe2 = __toESM(require("stripe"));
|
|
1022
|
+
|
|
1023
|
+
// src/customers/cache.ts
|
|
1024
|
+
var CustomerCache = class {
|
|
1025
|
+
cache = /* @__PURE__ */ new Map();
|
|
1026
|
+
defaultTtlMs;
|
|
1027
|
+
constructor(defaultTtlMs = 1e3 * 60 * 60) {
|
|
1028
|
+
this.defaultTtlMs = defaultTtlMs;
|
|
1029
|
+
}
|
|
1030
|
+
get(key) {
|
|
1031
|
+
const entry = this.cache.get(key);
|
|
1032
|
+
if (!entry) return null;
|
|
1033
|
+
if (Date.now() > entry.expiresAt) {
|
|
1034
|
+
this.cache.delete(key);
|
|
1035
|
+
return null;
|
|
1036
|
+
}
|
|
1037
|
+
return entry.customerId;
|
|
1038
|
+
}
|
|
1039
|
+
set(key, customerId, ttlMs) {
|
|
1040
|
+
const expiresAt = Date.now() + (ttlMs ?? this.defaultTtlMs);
|
|
1041
|
+
this.cache.set(key, { customerId, expiresAt });
|
|
1042
|
+
}
|
|
1043
|
+
delete(key) {
|
|
1044
|
+
this.cache.delete(key);
|
|
1045
|
+
}
|
|
1046
|
+
clear() {
|
|
1047
|
+
this.cache.clear();
|
|
1048
|
+
}
|
|
1049
|
+
};
|
|
1050
|
+
|
|
1051
|
+
// src/customers/manager.ts
|
|
1052
|
+
var CustomerManager = class {
|
|
1053
|
+
stripe;
|
|
1054
|
+
cache;
|
|
1055
|
+
constructor(options = {}) {
|
|
1056
|
+
if (options.stripe) {
|
|
1057
|
+
this.stripe = options.stripe;
|
|
1058
|
+
} else {
|
|
1059
|
+
const apiKey = options.apiKey || process.env.STRIPE_SECRET_KEY;
|
|
1060
|
+
if (!apiKey) {
|
|
1061
|
+
throw new Error("[vibezcheck] Stripe API key required for customer management.");
|
|
1062
|
+
}
|
|
1063
|
+
this.stripe = new import_stripe2.default(apiKey);
|
|
1064
|
+
}
|
|
1065
|
+
this.cache = new CustomerCache(options.cacheTtlMs);
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Retrieves existing Stripe Customer or automatically provisions a new one
|
|
1069
|
+
*/
|
|
1070
|
+
async getOrCreate(params) {
|
|
1071
|
+
const cacheKey = params.userId || params.email;
|
|
1072
|
+
if (cacheKey) {
|
|
1073
|
+
const cachedId = this.cache.get(cacheKey);
|
|
1074
|
+
if (cachedId) {
|
|
1075
|
+
return {
|
|
1076
|
+
id: cachedId,
|
|
1077
|
+
isNew: false,
|
|
1078
|
+
customer: { id: cachedId }
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
if (params.userId) {
|
|
1083
|
+
try {
|
|
1084
|
+
const searchResult = await this.stripe.customers.search({
|
|
1085
|
+
query: `metadata['vibez_user_id']:'${params.userId}'`,
|
|
1086
|
+
limit: 1
|
|
1087
|
+
});
|
|
1088
|
+
if (searchResult.data.length > 0) {
|
|
1089
|
+
const customer = searchResult.data[0];
|
|
1090
|
+
if (cacheKey) this.cache.set(cacheKey, customer.id);
|
|
1091
|
+
if (params.email) this.cache.set(params.email, customer.id);
|
|
1092
|
+
return { id: customer.id, isNew: false, customer };
|
|
1093
|
+
}
|
|
1094
|
+
} catch {
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
if (params.email) {
|
|
1098
|
+
const listResult = await this.stripe.customers.list({
|
|
1099
|
+
email: params.email,
|
|
1100
|
+
limit: 1
|
|
1101
|
+
});
|
|
1102
|
+
if (listResult.data.length > 0) {
|
|
1103
|
+
const customer = listResult.data[0];
|
|
1104
|
+
if (cacheKey) this.cache.set(cacheKey, customer.id);
|
|
1105
|
+
if (params.userId) this.cache.set(params.userId, customer.id);
|
|
1106
|
+
return { id: customer.id, isNew: false, customer };
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
const newCustomer = await this.stripe.customers.create({
|
|
1110
|
+
email: params.email,
|
|
1111
|
+
name: params.name,
|
|
1112
|
+
metadata: {
|
|
1113
|
+
vibez_user_id: params.userId,
|
|
1114
|
+
created_by: "vibezcheck",
|
|
1115
|
+
...params.metadata || {}
|
|
1116
|
+
}
|
|
1117
|
+
});
|
|
1118
|
+
if (cacheKey) this.cache.set(cacheKey, newCustomer.id);
|
|
1119
|
+
if (params.userId) this.cache.set(params.userId, newCustomer.id);
|
|
1120
|
+
if (params.email) this.cache.set(params.email, newCustomer.id);
|
|
1121
|
+
return { id: newCustomer.id, isNew: true, customer: newCustomer };
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Clears in-memory resolution cache
|
|
1125
|
+
*/
|
|
1126
|
+
clearCache() {
|
|
1127
|
+
this.cache.clear();
|
|
1128
|
+
}
|
|
1129
|
+
};
|
|
1130
|
+
function createCustomerManager(options = {}) {
|
|
1131
|
+
return new CustomerManager(options);
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// src/auth/keys.ts
|
|
1135
|
+
var crypto = __toESM(require("crypto"));
|
|
1136
|
+
var import_stripe3 = __toESM(require("stripe"));
|
|
1137
|
+
var ApiKeyAuth = class {
|
|
1138
|
+
stripe;
|
|
1139
|
+
constructor(stripe) {
|
|
1140
|
+
this.stripe = stripe;
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* Hashes a raw API key using SHA-256
|
|
1144
|
+
*/
|
|
1145
|
+
hashKey(rawKey) {
|
|
1146
|
+
return crypto.createHash("sha256").update(rawKey).digest("hex");
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Generates a new secure vz_live_... API key
|
|
1150
|
+
*/
|
|
1151
|
+
async createApiKey(params) {
|
|
1152
|
+
const randomBytes2 = crypto.randomBytes(24).toString("hex");
|
|
1153
|
+
const apiKey = `vz_live_${randomBytes2}`;
|
|
1154
|
+
const keyId = `key_${crypto.randomBytes(8).toString("hex")}`;
|
|
1155
|
+
const keyHash = this.hashKey(apiKey);
|
|
1156
|
+
const record = {
|
|
1157
|
+
keyId,
|
|
1158
|
+
keyHash,
|
|
1159
|
+
customerId: params.customerId,
|
|
1160
|
+
userId: params.userId,
|
|
1161
|
+
name: params.name,
|
|
1162
|
+
scopes: params.scopes,
|
|
1163
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1164
|
+
};
|
|
1165
|
+
if (this.stripe && params.customerId) {
|
|
1166
|
+
try {
|
|
1167
|
+
await this.stripe.customers.update(params.customerId, {
|
|
1168
|
+
metadata: {
|
|
1169
|
+
[`vibez_key_${keyId}`]: JSON.stringify({
|
|
1170
|
+
hash: keyHash,
|
|
1171
|
+
name: params.name,
|
|
1172
|
+
scopes: params.scopes,
|
|
1173
|
+
created: record.createdAt
|
|
1174
|
+
})
|
|
1175
|
+
}
|
|
1176
|
+
});
|
|
1177
|
+
} catch {
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
return { apiKey, keyId, record };
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* Validates a raw key against an expected hash
|
|
1184
|
+
*/
|
|
1185
|
+
verifyKeyHash(rawKey, expectedHash) {
|
|
1186
|
+
const computedHash = this.hashKey(rawKey);
|
|
1187
|
+
return crypto.timingSafeEqual(Buffer.from(computedHash), Buffer.from(expectedHash));
|
|
1188
|
+
}
|
|
1189
|
+
};
|
|
1190
|
+
function createApiKeyAuth(options = {}) {
|
|
1191
|
+
let stripeClient = options.stripe;
|
|
1192
|
+
if (!stripeClient && (options.apiKey || process.env.STRIPE_SECRET_KEY)) {
|
|
1193
|
+
stripeClient = new import_stripe3.default(options.apiKey || process.env.STRIPE_SECRET_KEY);
|
|
1194
|
+
}
|
|
1195
|
+
return new ApiKeyAuth(stripeClient);
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
// src/auth/verify.ts
|
|
1199
|
+
function extractAuthToken(reqOrHeader) {
|
|
1200
|
+
if (!reqOrHeader) return null;
|
|
1201
|
+
let authHeader = null;
|
|
1202
|
+
if (typeof reqOrHeader === "string") {
|
|
1203
|
+
authHeader = reqOrHeader;
|
|
1204
|
+
} else if ("headers" in reqOrHeader && typeof reqOrHeader.headers?.get === "function") {
|
|
1205
|
+
authHeader = reqOrHeader.headers.get("authorization") || reqOrHeader.headers.get("x-api-key");
|
|
1206
|
+
} else if (typeof reqOrHeader.get === "function") {
|
|
1207
|
+
authHeader = reqOrHeader.get("authorization") || reqOrHeader.get("x-api-key");
|
|
1208
|
+
} else if (typeof reqOrHeader === "object") {
|
|
1209
|
+
const raw = reqOrHeader["authorization"] || reqOrHeader["Authorization"] || reqOrHeader["x-api-key"] || reqOrHeader["X-API-Key"];
|
|
1210
|
+
authHeader = Array.isArray(raw) ? raw[0] : raw;
|
|
1211
|
+
}
|
|
1212
|
+
if (!authHeader) return null;
|
|
1213
|
+
if (authHeader.startsWith("Bearer ")) {
|
|
1214
|
+
return authHeader.substring(7).trim();
|
|
1215
|
+
}
|
|
1216
|
+
return authHeader.trim();
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
// src/billing/sessions.ts
|
|
1220
|
+
var import_stripe4 = __toESM(require("stripe"));
|
|
1221
|
+
var BillingHelper = class {
|
|
1222
|
+
stripe;
|
|
1223
|
+
constructor(options = {}) {
|
|
1224
|
+
if (options.stripe) {
|
|
1225
|
+
this.stripe = options.stripe;
|
|
1226
|
+
} else {
|
|
1227
|
+
const apiKey = options.apiKey || process.env.STRIPE_SECRET_KEY;
|
|
1228
|
+
if (!apiKey) {
|
|
1229
|
+
throw new Error("[vibezcheck] Stripe API key required for billing operations.");
|
|
1230
|
+
}
|
|
1231
|
+
this.stripe = new import_stripe4.default(apiKey);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* Creates a Stripe Customer Portal session URL where users can manage cards, view usage & invoices
|
|
1236
|
+
*/
|
|
1237
|
+
async createPortalSession(params) {
|
|
1238
|
+
const session = await this.stripe.billingPortal.sessions.create({
|
|
1239
|
+
customer: params.customerId,
|
|
1240
|
+
return_url: params.returnUrl
|
|
1241
|
+
});
|
|
1242
|
+
return session.url;
|
|
1243
|
+
}
|
|
1244
|
+
/**
|
|
1245
|
+
* Creates a Stripe Checkout session to subscribe a customer to a metered pricing tier
|
|
1246
|
+
*/
|
|
1247
|
+
async createCheckoutSession(params) {
|
|
1248
|
+
const session = await this.stripe.checkout.sessions.create({
|
|
1249
|
+
customer: params.customerId,
|
|
1250
|
+
customer_email: !params.customerId ? params.customerEmail : void 0,
|
|
1251
|
+
line_items: [
|
|
1252
|
+
{
|
|
1253
|
+
price: params.priceId,
|
|
1254
|
+
quantity: 1
|
|
1255
|
+
}
|
|
1256
|
+
],
|
|
1257
|
+
mode: params.mode || "subscription",
|
|
1258
|
+
success_url: `${params.returnUrl}?session_id={CHECKOUT_SESSION_ID}&status=success`,
|
|
1259
|
+
cancel_url: `${params.returnUrl}?status=cancelled`,
|
|
1260
|
+
metadata: params.metadata
|
|
1261
|
+
});
|
|
1262
|
+
if (!session.url) {
|
|
1263
|
+
throw new Error("[vibezcheck] Failed to generate checkout session URL.");
|
|
1264
|
+
}
|
|
1265
|
+
return session.url;
|
|
1266
|
+
}
|
|
1267
|
+
/**
|
|
1268
|
+
* Creates a Checkout session for topping up prepaid credit wallet balance
|
|
1269
|
+
*/
|
|
1270
|
+
async createTopUpSession(params) {
|
|
1271
|
+
const session = await this.stripe.checkout.sessions.create({
|
|
1272
|
+
customer: params.customerId,
|
|
1273
|
+
line_items: [
|
|
1274
|
+
{
|
|
1275
|
+
price_data: {
|
|
1276
|
+
currency: params.currency || "usd",
|
|
1277
|
+
unit_amount: params.amountCents,
|
|
1278
|
+
product_data: {
|
|
1279
|
+
name: "AI Token Credits Top-Up",
|
|
1280
|
+
description: `Add $${(params.amountCents / 100).toFixed(2)} in AI inference credits`
|
|
1281
|
+
}
|
|
1282
|
+
},
|
|
1283
|
+
quantity: 1
|
|
1284
|
+
}
|
|
1285
|
+
],
|
|
1286
|
+
mode: "payment",
|
|
1287
|
+
success_url: `${params.returnUrl}?status=success&amount=${params.amountCents}`,
|
|
1288
|
+
cancel_url: `${params.returnUrl}?status=cancelled`,
|
|
1289
|
+
metadata: {
|
|
1290
|
+
type: "vibezcheck_topup",
|
|
1291
|
+
customerId: params.customerId,
|
|
1292
|
+
amountCents: params.amountCents.toString()
|
|
1293
|
+
}
|
|
1294
|
+
});
|
|
1295
|
+
if (!session.url) {
|
|
1296
|
+
throw new Error("[vibezcheck] Failed to generate top-up checkout URL.");
|
|
1297
|
+
}
|
|
1298
|
+
return session.url;
|
|
1299
|
+
}
|
|
1300
|
+
};
|
|
1301
|
+
function createBillingHelper(options = {}) {
|
|
1302
|
+
return new BillingHelper(options);
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
// src/meter/index.ts
|
|
1306
|
+
var defaultMeter = createMeter();
|
|
1307
|
+
function wrapStream(stream, options) {
|
|
1308
|
+
return defaultMeter.wrapStream(stream, options);
|
|
1309
|
+
}
|
|
1310
|
+
function trackTokens(response, options) {
|
|
1311
|
+
return defaultMeter.trackUsage(response, options);
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
// src/index.ts
|
|
1315
|
+
var VibezCheckClient = class {
|
|
1316
|
+
meter;
|
|
1317
|
+
customers;
|
|
1318
|
+
auth;
|
|
1319
|
+
billing;
|
|
1320
|
+
stripeClient;
|
|
1321
|
+
constructor(config = {}) {
|
|
1322
|
+
const apiKey = config.apiKey || process.env.STRIPE_SECRET_KEY;
|
|
1323
|
+
if (apiKey) {
|
|
1324
|
+
this.stripeClient = config.stripe || new import_stripe5.default(apiKey);
|
|
1325
|
+
}
|
|
1326
|
+
this.meter = new VibezMeter({
|
|
1327
|
+
...config,
|
|
1328
|
+
stripe: this.stripeClient
|
|
1329
|
+
});
|
|
1330
|
+
if (this.stripeClient) {
|
|
1331
|
+
this.customers = new CustomerManager({ stripe: this.stripeClient });
|
|
1332
|
+
this.auth = new ApiKeyAuth(this.stripeClient);
|
|
1333
|
+
this.billing = new BillingHelper({ stripe: this.stripeClient });
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* 1-Line Zero-Latency Stream Wrapper for OpenAI, Anthropic, Gemini, etc.
|
|
1338
|
+
*/
|
|
1339
|
+
wrapStream(stream, options) {
|
|
1340
|
+
return this.meter.wrapStream(stream, options);
|
|
1341
|
+
}
|
|
1342
|
+
/**
|
|
1343
|
+
* Track token usage from a non-streaming response object
|
|
1344
|
+
*/
|
|
1345
|
+
track(response, options) {
|
|
1346
|
+
return this.meter.trackUsage(response, options);
|
|
1347
|
+
}
|
|
1348
|
+
/**
|
|
1349
|
+
* 1-Line Wrapper for Vercel AI SDK LanguageModel
|
|
1350
|
+
*/
|
|
1351
|
+
withBilling(model, options) {
|
|
1352
|
+
return withBilling(model, {
|
|
1353
|
+
...options,
|
|
1354
|
+
meter: this.meter
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1357
|
+
/**
|
|
1358
|
+
* 1-Line Universal Stream Responder for API Routes (Next.js, Express, Hono)
|
|
1359
|
+
*/
|
|
1360
|
+
async stream(params) {
|
|
1361
|
+
const OpenAI = (await import("openai")).default;
|
|
1362
|
+
const openai = new OpenAI();
|
|
1363
|
+
const responseStream = await openai.chat.completions.create({
|
|
1364
|
+
model: params.model,
|
|
1365
|
+
messages: params.messages,
|
|
1366
|
+
stream: true,
|
|
1367
|
+
stream_options: { include_usage: true },
|
|
1368
|
+
temperature: params.temperature
|
|
1369
|
+
});
|
|
1370
|
+
const meteredStream = this.wrapStream(responseStream, {
|
|
1371
|
+
customer: params.customer,
|
|
1372
|
+
model: params.model
|
|
1373
|
+
});
|
|
1374
|
+
const encoder = new TextEncoder();
|
|
1375
|
+
const readable = new ReadableStream({
|
|
1376
|
+
async start(controller) {
|
|
1377
|
+
try {
|
|
1378
|
+
for await (const chunk of meteredStream) {
|
|
1379
|
+
const text = chunk.choices?.[0]?.delta?.content || "";
|
|
1380
|
+
if (text) {
|
|
1381
|
+
controller.enqueue(encoder.encode(text));
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
controller.close();
|
|
1385
|
+
} catch (err) {
|
|
1386
|
+
controller.error(err);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
});
|
|
1390
|
+
return new Response(readable, {
|
|
1391
|
+
headers: {
|
|
1392
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
1393
|
+
"Transfer-Encoding": "chunked"
|
|
1394
|
+
}
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
/**
|
|
1398
|
+
* Flush pending meter events (vital for serverless runtimes)
|
|
1399
|
+
*/
|
|
1400
|
+
async flush() {
|
|
1401
|
+
await this.meter.flush();
|
|
1402
|
+
}
|
|
1403
|
+
/**
|
|
1404
|
+
* In-memory usage statistics
|
|
1405
|
+
*/
|
|
1406
|
+
getUsageSummary() {
|
|
1407
|
+
return this.meter.getUsageSummary();
|
|
1408
|
+
}
|
|
1409
|
+
};
|
|
1410
|
+
function createVibezCheck(config = {}) {
|
|
1411
|
+
return new VibezCheckClient(config);
|
|
1412
|
+
}
|
|
1413
|
+
var vibezcheck = createVibezCheck;
|
|
1414
|
+
var vibescheck = createVibezCheck;
|
|
1415
|
+
var vibez = createVibezCheck();
|
|
1416
|
+
var vibes = vibez;
|
|
1417
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1418
|
+
0 && (module.exports = {
|
|
1419
|
+
AnthropicStreamAccumulator,
|
|
1420
|
+
ApiKeyAuth,
|
|
1421
|
+
BillingHelper,
|
|
1422
|
+
CustomerCache,
|
|
1423
|
+
CustomerManager,
|
|
1424
|
+
MODEL_PRICING_TABLE,
|
|
1425
|
+
MeterBatcher,
|
|
1426
|
+
VibezCheckClient,
|
|
1427
|
+
VibezMeter,
|
|
1428
|
+
calculateCost,
|
|
1429
|
+
calculateUsageCost,
|
|
1430
|
+
createApiKeyAuth,
|
|
1431
|
+
createBillingHelper,
|
|
1432
|
+
createCustomerManager,
|
|
1433
|
+
createMeter,
|
|
1434
|
+
createVibezCheck,
|
|
1435
|
+
detectAndExtractUsage,
|
|
1436
|
+
extractAnthropicResponseUsage,
|
|
1437
|
+
extractAuthToken,
|
|
1438
|
+
extractGeminiResponseUsage,
|
|
1439
|
+
extractGenericResponseUsage,
|
|
1440
|
+
extractOpenAIResponseUsage,
|
|
1441
|
+
getModelPricing,
|
|
1442
|
+
inspectOpenAIStreamChunk,
|
|
1443
|
+
meteredModel,
|
|
1444
|
+
normalizeModelKey,
|
|
1445
|
+
registerModelPricing,
|
|
1446
|
+
trackTokens,
|
|
1447
|
+
vibes,
|
|
1448
|
+
vibescheck,
|
|
1449
|
+
vibez,
|
|
1450
|
+
vibezcheck,
|
|
1451
|
+
withBilling,
|
|
1452
|
+
wrapAnthropicStream,
|
|
1453
|
+
wrapGeminiStream,
|
|
1454
|
+
wrapOpenAIStream,
|
|
1455
|
+
wrapStream,
|
|
1456
|
+
wrapUniversalStream
|
|
1457
|
+
});
|
|
1458
|
+
//# sourceMappingURL=index.js.map
|