@drawbridge/drawbridge-utils 0.0.64 → 0.0.66
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/dist/ai.cjs +45 -49
- package/dist/ai.d.cts +7 -161
- package/dist/ai.d.ts +7 -161
- package/dist/ai.js +44 -42
- package/dist/billing.cjs +373 -0
- package/dist/billing.d.cts +300 -0
- package/dist/billing.d.ts +300 -0
- package/dist/billing.js +343 -0
- package/dist/index.cjs +61 -59
- package/dist/index.d.cts +2 -122
- package/dist/index.d.ts +2 -122
- package/dist/index.js +61 -59
- package/dist/phone.cjs +48 -0
- package/dist/phone.d.cts +45 -0
- package/dist/phone.d.ts +45 -0
- package/dist/phone.js +23 -0
- package/dist/plans.d.cts +1 -0
- package/dist/plans.d.ts +1 -0
- package/dist/usage.cjs +91 -0
- package/dist/usage.d.cts +129 -0
- package/dist/usage.d.ts +129 -0
- package/dist/usage.js +65 -0
- package/package.json +12 -1
package/dist/ai.cjs
CHANGED
|
@@ -20,13 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
var ai_exports = {};
|
|
21
21
|
__export(ai_exports, {
|
|
22
22
|
MARKUP: () => MARKUP,
|
|
23
|
-
|
|
24
|
-
costForRequest: () => costForRequest,
|
|
25
|
-
google: () => google,
|
|
26
|
-
priceForRequest: () => priceForRequest,
|
|
27
|
-
pricing: () => pricing,
|
|
28
|
-
toolCost: () => toolCost,
|
|
29
|
-
toolPricing: () => toolPricing
|
|
23
|
+
google: () => google
|
|
30
24
|
});
|
|
31
25
|
module.exports = __toCommonJS(ai_exports);
|
|
32
26
|
var import_genai = require("@google/genai");
|
|
@@ -155,19 +149,10 @@ var debit = async ({
|
|
|
155
149
|
});
|
|
156
150
|
};
|
|
157
151
|
|
|
158
|
-
// lib/
|
|
159
|
-
var
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
var googleCircuit = circuit({
|
|
163
|
-
name: "google-ai",
|
|
164
|
-
threshold: 3,
|
|
165
|
-
timeout: 6e4
|
|
166
|
-
});
|
|
167
|
-
var models = {
|
|
168
|
-
image: "gemini-2.5-flash-image",
|
|
169
|
-
text: "gemini-2.5-flash"
|
|
170
|
-
};
|
|
152
|
+
// lib/billing.js
|
|
153
|
+
var import_drawbridge_telemetry2 = require("@drawbridge/drawbridge-telemetry");
|
|
154
|
+
var logger = (0, import_drawbridge_telemetry2.createLogger)();
|
|
155
|
+
var MARKUP = 1.3;
|
|
171
156
|
var cost = {
|
|
172
157
|
"gemini-2.5-flash": {
|
|
173
158
|
cached: 3,
|
|
@@ -180,7 +165,6 @@ var cost = {
|
|
|
180
165
|
output: 3e3
|
|
181
166
|
}
|
|
182
167
|
};
|
|
183
|
-
var MARKUP = 1.3;
|
|
184
168
|
var toolCost = {
|
|
185
169
|
search: 3.5
|
|
186
170
|
};
|
|
@@ -221,25 +205,7 @@ var costForRequest = (model, usage, tools) => {
|
|
|
221
205
|
}
|
|
222
206
|
return sumCents(rates, usage) + Math.ceil(sumToolCents(toolCost, tools));
|
|
223
207
|
};
|
|
224
|
-
var
|
|
225
|
-
cached: Number(metadata == null ? void 0 : metadata.cachedContentTokenCount) || 0,
|
|
226
|
-
input: Number(metadata == null ? void 0 : metadata.promptTokenCount) || 0,
|
|
227
|
-
output: Number(metadata == null ? void 0 : metadata.candidatesTokenCount) || 0,
|
|
228
|
-
thinking: Number(metadata == null ? void 0 : metadata.thoughtsTokenCount) || 0
|
|
229
|
-
});
|
|
230
|
-
var toolsFromResponse = (response) => {
|
|
231
|
-
var _a, _b;
|
|
232
|
-
return {
|
|
233
|
-
search: Boolean((_b = (_a = response == null ? void 0 : response.candidates) == null ? void 0 : _a[0]) == null ? void 0 : _b.groundingMetadata)
|
|
234
|
-
};
|
|
235
|
-
};
|
|
236
|
-
var billRequest = async ({
|
|
237
|
-
db,
|
|
238
|
-
user,
|
|
239
|
-
model,
|
|
240
|
-
usage,
|
|
241
|
-
tools
|
|
242
|
-
}) => {
|
|
208
|
+
var billRequest = async ({ db, user, model, usage, tools }) => {
|
|
243
209
|
const gross = priceForRequest(model, usage, tools);
|
|
244
210
|
const cogs = costForRequest(model, usage, tools);
|
|
245
211
|
await debit({
|
|
@@ -266,6 +232,42 @@ var billRequest = async ({
|
|
|
266
232
|
}
|
|
267
233
|
});
|
|
268
234
|
};
|
|
235
|
+
var ai = {
|
|
236
|
+
// Retail cents for a request (what the user pays).
|
|
237
|
+
price: priceForRequest,
|
|
238
|
+
// Wholesale cents for a request (what we pay the provider).
|
|
239
|
+
cost: costForRequest,
|
|
240
|
+
// Deferred bill() the caller fires on success, so the debit lands only when
|
|
241
|
+
// the request succeeds and bundles into that request's Sentry trace. No user
|
|
242
|
+
// → a no-op.
|
|
243
|
+
bill: ({ db, user, model, usage, tools }) => (user == null ? void 0 : user.id) ? () => billRequest({ db, user, model, usage, tools }) : () => Promise.resolve()
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// lib/ai.js
|
|
247
|
+
var google_client = new import_genai.GoogleGenAI({
|
|
248
|
+
apiKey: process.env.GOOGLE_GEMINI_API_KEY
|
|
249
|
+
});
|
|
250
|
+
var googleCircuit = circuit({
|
|
251
|
+
name: "google-ai",
|
|
252
|
+
threshold: 3,
|
|
253
|
+
timeout: 6e4
|
|
254
|
+
});
|
|
255
|
+
var models = {
|
|
256
|
+
image: "gemini-2.5-flash-image",
|
|
257
|
+
text: "gemini-2.5-flash"
|
|
258
|
+
};
|
|
259
|
+
var tokensFromMetadata = (metadata) => ({
|
|
260
|
+
cached: Number(metadata == null ? void 0 : metadata.cachedContentTokenCount) || 0,
|
|
261
|
+
input: Number(metadata == null ? void 0 : metadata.promptTokenCount) || 0,
|
|
262
|
+
output: Number(metadata == null ? void 0 : metadata.candidatesTokenCount) || 0,
|
|
263
|
+
thinking: Number(metadata == null ? void 0 : metadata.thoughtsTokenCount) || 0
|
|
264
|
+
});
|
|
265
|
+
var toolsFromResponse = (response) => {
|
|
266
|
+
var _a, _b;
|
|
267
|
+
return {
|
|
268
|
+
search: Boolean((_b = (_a = response == null ? void 0 : response.candidates) == null ? void 0 : _a[0]) == null ? void 0 : _b.groundingMetadata)
|
|
269
|
+
};
|
|
270
|
+
};
|
|
269
271
|
var google = {
|
|
270
272
|
image: async ({
|
|
271
273
|
config = {},
|
|
@@ -297,7 +299,7 @@ var google = {
|
|
|
297
299
|
}
|
|
298
300
|
const usage = tokensFromMetadata(response == null ? void 0 : response.usageMetadata);
|
|
299
301
|
const tools = toolsFromResponse(response);
|
|
300
|
-
const bill =
|
|
302
|
+
const bill = ai.bill({ db, user, model, usage, tools });
|
|
301
303
|
return { content, bill };
|
|
302
304
|
},
|
|
303
305
|
text: async ({
|
|
@@ -325,18 +327,12 @@ var google = {
|
|
|
325
327
|
}
|
|
326
328
|
const usage = tokensFromMetadata(response == null ? void 0 : response.usageMetadata);
|
|
327
329
|
const tools = toolsFromResponse(response);
|
|
328
|
-
const bill =
|
|
330
|
+
const bill = ai.bill({ db, user, model, usage, tools });
|
|
329
331
|
return { content, bill };
|
|
330
332
|
}
|
|
331
333
|
};
|
|
332
334
|
// Annotate the CommonJS export names for ESM import in node:
|
|
333
335
|
0 && (module.exports = {
|
|
334
336
|
MARKUP,
|
|
335
|
-
|
|
336
|
-
costForRequest,
|
|
337
|
-
google,
|
|
338
|
-
priceForRequest,
|
|
339
|
-
pricing,
|
|
340
|
-
toolCost,
|
|
341
|
-
toolPricing
|
|
337
|
+
google
|
|
342
338
|
});
|
package/dist/ai.d.cts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { GoogleGenAI } from '@google/genai';
|
|
2
2
|
import { circuit } from './circuit.cjs';
|
|
3
|
-
import {
|
|
3
|
+
import { ai } from './billing.cjs';
|
|
4
|
+
export { MARKUP } from './billing.cjs';
|
|
5
|
+
import './transactions.cjs';
|
|
4
6
|
import '@drawbridge/drawbridge-telemetry';
|
|
7
|
+
import './usage.cjs';
|
|
5
8
|
|
|
6
9
|
// AI request entry point. Wraps the Google GenAI client with a per-process
|
|
7
10
|
// circuit breaker, owns the per-model pricing table, and atomically debits
|
|
@@ -37,117 +40,6 @@ const models = {
|
|
|
37
40
|
text : 'gemini-2.5-flash'
|
|
38
41
|
};
|
|
39
42
|
|
|
40
|
-
// Raw per-model provider rates in cents per 1,000,000 tokens. This is what
|
|
41
|
-
// Drawbridge pays the provider — the wholesale cost. Sync from LiteLLM:
|
|
42
|
-
// https://github.com/BerriAI/litellm/blob/main/litellm/model_prices_and_context_window_backup.json
|
|
43
|
-
// Never apply markup here; the MARKUP constant below derives the retail
|
|
44
|
-
// `pricing` table.
|
|
45
|
-
const cost = {
|
|
46
|
-
'gemini-2.5-flash' : {
|
|
47
|
-
cached : 3,
|
|
48
|
-
input : 30,
|
|
49
|
-
output : 250
|
|
50
|
-
},
|
|
51
|
-
'gemini-2.5-flash-image' : {
|
|
52
|
-
cached : 3,
|
|
53
|
-
input : 30,
|
|
54
|
-
output : 3000
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
// Markup multiplier applied to every model's cost to produce the retail
|
|
59
|
-
// price. 1.3 = 30% margin. Change one number, all pricing updates. Mirrors
|
|
60
|
-
// the per-customer markup Stripe rate cards used to provide; keeping it
|
|
61
|
-
// global (rather than per-model or per-plan) for simplicity — easy to
|
|
62
|
-
// refactor later if tier-based pricing becomes a real need.
|
|
63
|
-
const MARKUP = 1.3;
|
|
64
|
-
|
|
65
|
-
// Flat per-request fees Google charges on top of tokens when specific tools
|
|
66
|
-
// fire. Search grounding is $35/1k requests → 3.5 cents/request. Billed only
|
|
67
|
-
// when the response actually used the tool (response.candidates[0].
|
|
68
|
-
// groundingMetadata is populated) — enabling the tool in config without it
|
|
69
|
-
// being invoked is free. Add more tools here as Google introduces them.
|
|
70
|
-
const toolCost = {
|
|
71
|
-
search : 3.5
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const toolPricing = Object.fromEntries(
|
|
75
|
-
Object.entries( toolCost ).map( ( [ tool, value ] ) => [
|
|
76
|
-
tool,
|
|
77
|
-
Math.ceil( value * MARKUP )
|
|
78
|
-
] )
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
// Derived retail pricing in cents per 1,000,000 tokens — what users pay.
|
|
82
|
-
// Rounded so the per-1M rate is a whole-cent value; per-request rounding
|
|
83
|
-
// happens in priceForRequest via Math.ceil so we never under-bill.
|
|
84
|
-
const pricing = Object.fromEntries(
|
|
85
|
-
Object.entries( cost ).map( ( [ model, rates ] ) => [
|
|
86
|
-
model,
|
|
87
|
-
{
|
|
88
|
-
cached : Math.round( rates.cached * MARKUP ),
|
|
89
|
-
input : Math.round( rates.input * MARKUP ),
|
|
90
|
-
output : Math.round( rates.output * MARKUP )
|
|
91
|
-
}
|
|
92
|
-
] )
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
// Gemini 2.5 emits internal reasoning ("thinking") tokens alongside the
|
|
96
|
-
// visible response. Google bills them at the standard output rate, so they
|
|
97
|
-
// roll into the same multiplier here even though they live as a distinct
|
|
98
|
-
// field on the tokens object for visibility / margin analysis.
|
|
99
|
-
const sumCents = ( rates, tokens ) => Math.ceil(
|
|
100
|
-
(
|
|
101
|
-
Number( tokens?.cached || 0 ) * rates.cached +
|
|
102
|
-
Number( tokens?.input || 0 ) * rates.input +
|
|
103
|
-
(
|
|
104
|
-
Number( tokens?.output || 0 ) +
|
|
105
|
-
Number( tokens?.thinking || 0 )
|
|
106
|
-
) * rates.output
|
|
107
|
-
) / 1_000_000
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
// Sum the per-request flat fees for the tools that actually fired. `tools`
|
|
111
|
-
// is the same { search: true } shape stamped on the transaction row, so the
|
|
112
|
-
// row's `tools` object and the amount it contributes are recomputable from
|
|
113
|
-
// the same source of truth.
|
|
114
|
-
const sumToolCents = ( table, tools ) => Object.entries( tools || {} ).reduce(
|
|
115
|
-
( total, [ name, used ] ) => total + ( used && table[ name ] ? table[ name ] : 0 ),
|
|
116
|
-
0
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
// What we charge the user, rounded up so we never under-bill. Throws on an
|
|
120
|
-
// unknown model so a typo doesn't silently fall back to a wrong price.
|
|
121
|
-
const priceForRequest = ( model, usage, tools ) => {
|
|
122
|
-
|
|
123
|
-
const rates = pricing[ model ];
|
|
124
|
-
|
|
125
|
-
if( !rates ){
|
|
126
|
-
|
|
127
|
-
throw new Error( `Unknown model for pricing: ${ model }` );
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return sumCents( rates, usage ) + Math.ceil( sumToolCents( toolPricing, tools ) );
|
|
132
|
-
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
// What we pay the provider. Recorded on each transaction row so margin is
|
|
136
|
-
// computable per request without backfilling from git history.
|
|
137
|
-
const costForRequest = ( model, usage, tools ) => {
|
|
138
|
-
|
|
139
|
-
const rates = cost[ model ];
|
|
140
|
-
|
|
141
|
-
if( !rates ){
|
|
142
|
-
|
|
143
|
-
throw new Error( `Unknown model for cost: ${ model }` );
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return sumCents( rates, usage ) + Math.ceil( sumToolCents( toolCost, tools ) );
|
|
148
|
-
|
|
149
|
-
};
|
|
150
|
-
|
|
151
43
|
// Map Google's usageMetadata shape into the pricing.tokens shape.
|
|
152
44
|
// `thinking` is Gemini 2.5's internal reasoning token count — billed at the
|
|
153
45
|
// same rate as `output` but tracked separately so the transaction row shows
|
|
@@ -167,48 +59,6 @@ const toolsFromResponse = ( response ) => ({
|
|
|
167
59
|
search : Boolean( response?.candidates?.[ 0 ]?.groundingMetadata )
|
|
168
60
|
});
|
|
169
61
|
|
|
170
|
-
// Compute gross + cost from response usage and atomically debit the user.
|
|
171
|
-
// Records the full income-statement line on the transaction row's `ai`
|
|
172
|
-
// sub-doc: wholesale + retail per-1M rate snapshots, plus rolled-up cost,
|
|
173
|
-
// net, and gross totals. Margin per request is `ai.net`; margin per query
|
|
174
|
-
// is a single $sum: '$ai.net'.
|
|
175
|
-
const billRequest = async ({
|
|
176
|
-
db,
|
|
177
|
-
user,
|
|
178
|
-
model,
|
|
179
|
-
usage,
|
|
180
|
-
tools
|
|
181
|
-
}) => {
|
|
182
|
-
|
|
183
|
-
const gross = priceForRequest( model, usage, tools );
|
|
184
|
-
const cogs = costForRequest( model, usage, tools );
|
|
185
|
-
|
|
186
|
-
await debit({
|
|
187
|
-
db,
|
|
188
|
-
user,
|
|
189
|
-
amount : gross,
|
|
190
|
-
type : 'ai',
|
|
191
|
-
category : 'usage',
|
|
192
|
-
source : 'user',
|
|
193
|
-
ai : {
|
|
194
|
-
name : 'google',
|
|
195
|
-
model,
|
|
196
|
-
tokens : usage,
|
|
197
|
-
tools,
|
|
198
|
-
rates : {
|
|
199
|
-
wholesale : cost[ model ],
|
|
200
|
-
retail : pricing[ model ]
|
|
201
|
-
},
|
|
202
|
-
totals : {
|
|
203
|
-
cost : cogs,
|
|
204
|
-
net : gross - cogs,
|
|
205
|
-
gross
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
};
|
|
211
|
-
|
|
212
62
|
const google = {
|
|
213
63
|
|
|
214
64
|
image : async (
|
|
@@ -254,9 +104,7 @@ const google = {
|
|
|
254
104
|
const usage = tokensFromMetadata( response?.usageMetadata );
|
|
255
105
|
const tools = toolsFromResponse( response );
|
|
256
106
|
|
|
257
|
-
const bill = user
|
|
258
|
-
? () => billRequest({ db, user, model, usage, tools })
|
|
259
|
-
: () => Promise.resolve();
|
|
107
|
+
const bill = ai.bill({ db, user, model, usage, tools });
|
|
260
108
|
|
|
261
109
|
return { content, bill };
|
|
262
110
|
|
|
@@ -310,9 +158,7 @@ const google = {
|
|
|
310
158
|
const usage = tokensFromMetadata( response?.usageMetadata );
|
|
311
159
|
const tools = toolsFromResponse( response );
|
|
312
160
|
|
|
313
|
-
const bill = user
|
|
314
|
-
? () => billRequest({ db, user, model, usage, tools })
|
|
315
|
-
: () => Promise.resolve();
|
|
161
|
+
const bill = ai.bill({ db, user, model, usage, tools });
|
|
316
162
|
|
|
317
163
|
return { content, bill };
|
|
318
164
|
|
|
@@ -320,4 +166,4 @@ const google = {
|
|
|
320
166
|
|
|
321
167
|
};
|
|
322
168
|
|
|
323
|
-
export {
|
|
169
|
+
export { google };
|
package/dist/ai.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { GoogleGenAI } from '@google/genai';
|
|
2
2
|
import { circuit } from './circuit.js';
|
|
3
|
-
import {
|
|
3
|
+
import { ai } from './billing.js';
|
|
4
|
+
export { MARKUP } from './billing.js';
|
|
5
|
+
import './transactions.js';
|
|
4
6
|
import '@drawbridge/drawbridge-telemetry';
|
|
7
|
+
import './usage.js';
|
|
5
8
|
|
|
6
9
|
// AI request entry point. Wraps the Google GenAI client with a per-process
|
|
7
10
|
// circuit breaker, owns the per-model pricing table, and atomically debits
|
|
@@ -37,117 +40,6 @@ const models = {
|
|
|
37
40
|
text : 'gemini-2.5-flash'
|
|
38
41
|
};
|
|
39
42
|
|
|
40
|
-
// Raw per-model provider rates in cents per 1,000,000 tokens. This is what
|
|
41
|
-
// Drawbridge pays the provider — the wholesale cost. Sync from LiteLLM:
|
|
42
|
-
// https://github.com/BerriAI/litellm/blob/main/litellm/model_prices_and_context_window_backup.json
|
|
43
|
-
// Never apply markup here; the MARKUP constant below derives the retail
|
|
44
|
-
// `pricing` table.
|
|
45
|
-
const cost = {
|
|
46
|
-
'gemini-2.5-flash' : {
|
|
47
|
-
cached : 3,
|
|
48
|
-
input : 30,
|
|
49
|
-
output : 250
|
|
50
|
-
},
|
|
51
|
-
'gemini-2.5-flash-image' : {
|
|
52
|
-
cached : 3,
|
|
53
|
-
input : 30,
|
|
54
|
-
output : 3000
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
// Markup multiplier applied to every model's cost to produce the retail
|
|
59
|
-
// price. 1.3 = 30% margin. Change one number, all pricing updates. Mirrors
|
|
60
|
-
// the per-customer markup Stripe rate cards used to provide; keeping it
|
|
61
|
-
// global (rather than per-model or per-plan) for simplicity — easy to
|
|
62
|
-
// refactor later if tier-based pricing becomes a real need.
|
|
63
|
-
const MARKUP = 1.3;
|
|
64
|
-
|
|
65
|
-
// Flat per-request fees Google charges on top of tokens when specific tools
|
|
66
|
-
// fire. Search grounding is $35/1k requests → 3.5 cents/request. Billed only
|
|
67
|
-
// when the response actually used the tool (response.candidates[0].
|
|
68
|
-
// groundingMetadata is populated) — enabling the tool in config without it
|
|
69
|
-
// being invoked is free. Add more tools here as Google introduces them.
|
|
70
|
-
const toolCost = {
|
|
71
|
-
search : 3.5
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const toolPricing = Object.fromEntries(
|
|
75
|
-
Object.entries( toolCost ).map( ( [ tool, value ] ) => [
|
|
76
|
-
tool,
|
|
77
|
-
Math.ceil( value * MARKUP )
|
|
78
|
-
] )
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
// Derived retail pricing in cents per 1,000,000 tokens — what users pay.
|
|
82
|
-
// Rounded so the per-1M rate is a whole-cent value; per-request rounding
|
|
83
|
-
// happens in priceForRequest via Math.ceil so we never under-bill.
|
|
84
|
-
const pricing = Object.fromEntries(
|
|
85
|
-
Object.entries( cost ).map( ( [ model, rates ] ) => [
|
|
86
|
-
model,
|
|
87
|
-
{
|
|
88
|
-
cached : Math.round( rates.cached * MARKUP ),
|
|
89
|
-
input : Math.round( rates.input * MARKUP ),
|
|
90
|
-
output : Math.round( rates.output * MARKUP )
|
|
91
|
-
}
|
|
92
|
-
] )
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
// Gemini 2.5 emits internal reasoning ("thinking") tokens alongside the
|
|
96
|
-
// visible response. Google bills them at the standard output rate, so they
|
|
97
|
-
// roll into the same multiplier here even though they live as a distinct
|
|
98
|
-
// field on the tokens object for visibility / margin analysis.
|
|
99
|
-
const sumCents = ( rates, tokens ) => Math.ceil(
|
|
100
|
-
(
|
|
101
|
-
Number( tokens?.cached || 0 ) * rates.cached +
|
|
102
|
-
Number( tokens?.input || 0 ) * rates.input +
|
|
103
|
-
(
|
|
104
|
-
Number( tokens?.output || 0 ) +
|
|
105
|
-
Number( tokens?.thinking || 0 )
|
|
106
|
-
) * rates.output
|
|
107
|
-
) / 1_000_000
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
// Sum the per-request flat fees for the tools that actually fired. `tools`
|
|
111
|
-
// is the same { search: true } shape stamped on the transaction row, so the
|
|
112
|
-
// row's `tools` object and the amount it contributes are recomputable from
|
|
113
|
-
// the same source of truth.
|
|
114
|
-
const sumToolCents = ( table, tools ) => Object.entries( tools || {} ).reduce(
|
|
115
|
-
( total, [ name, used ] ) => total + ( used && table[ name ] ? table[ name ] : 0 ),
|
|
116
|
-
0
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
// What we charge the user, rounded up so we never under-bill. Throws on an
|
|
120
|
-
// unknown model so a typo doesn't silently fall back to a wrong price.
|
|
121
|
-
const priceForRequest = ( model, usage, tools ) => {
|
|
122
|
-
|
|
123
|
-
const rates = pricing[ model ];
|
|
124
|
-
|
|
125
|
-
if( !rates ){
|
|
126
|
-
|
|
127
|
-
throw new Error( `Unknown model for pricing: ${ model }` );
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return sumCents( rates, usage ) + Math.ceil( sumToolCents( toolPricing, tools ) );
|
|
132
|
-
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
// What we pay the provider. Recorded on each transaction row so margin is
|
|
136
|
-
// computable per request without backfilling from git history.
|
|
137
|
-
const costForRequest = ( model, usage, tools ) => {
|
|
138
|
-
|
|
139
|
-
const rates = cost[ model ];
|
|
140
|
-
|
|
141
|
-
if( !rates ){
|
|
142
|
-
|
|
143
|
-
throw new Error( `Unknown model for cost: ${ model }` );
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return sumCents( rates, usage ) + Math.ceil( sumToolCents( toolCost, tools ) );
|
|
148
|
-
|
|
149
|
-
};
|
|
150
|
-
|
|
151
43
|
// Map Google's usageMetadata shape into the pricing.tokens shape.
|
|
152
44
|
// `thinking` is Gemini 2.5's internal reasoning token count — billed at the
|
|
153
45
|
// same rate as `output` but tracked separately so the transaction row shows
|
|
@@ -167,48 +59,6 @@ const toolsFromResponse = ( response ) => ({
|
|
|
167
59
|
search : Boolean( response?.candidates?.[ 0 ]?.groundingMetadata )
|
|
168
60
|
});
|
|
169
61
|
|
|
170
|
-
// Compute gross + cost from response usage and atomically debit the user.
|
|
171
|
-
// Records the full income-statement line on the transaction row's `ai`
|
|
172
|
-
// sub-doc: wholesale + retail per-1M rate snapshots, plus rolled-up cost,
|
|
173
|
-
// net, and gross totals. Margin per request is `ai.net`; margin per query
|
|
174
|
-
// is a single $sum: '$ai.net'.
|
|
175
|
-
const billRequest = async ({
|
|
176
|
-
db,
|
|
177
|
-
user,
|
|
178
|
-
model,
|
|
179
|
-
usage,
|
|
180
|
-
tools
|
|
181
|
-
}) => {
|
|
182
|
-
|
|
183
|
-
const gross = priceForRequest( model, usage, tools );
|
|
184
|
-
const cogs = costForRequest( model, usage, tools );
|
|
185
|
-
|
|
186
|
-
await debit({
|
|
187
|
-
db,
|
|
188
|
-
user,
|
|
189
|
-
amount : gross,
|
|
190
|
-
type : 'ai',
|
|
191
|
-
category : 'usage',
|
|
192
|
-
source : 'user',
|
|
193
|
-
ai : {
|
|
194
|
-
name : 'google',
|
|
195
|
-
model,
|
|
196
|
-
tokens : usage,
|
|
197
|
-
tools,
|
|
198
|
-
rates : {
|
|
199
|
-
wholesale : cost[ model ],
|
|
200
|
-
retail : pricing[ model ]
|
|
201
|
-
},
|
|
202
|
-
totals : {
|
|
203
|
-
cost : cogs,
|
|
204
|
-
net : gross - cogs,
|
|
205
|
-
gross
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
};
|
|
211
|
-
|
|
212
62
|
const google = {
|
|
213
63
|
|
|
214
64
|
image : async (
|
|
@@ -254,9 +104,7 @@ const google = {
|
|
|
254
104
|
const usage = tokensFromMetadata( response?.usageMetadata );
|
|
255
105
|
const tools = toolsFromResponse( response );
|
|
256
106
|
|
|
257
|
-
const bill = user
|
|
258
|
-
? () => billRequest({ db, user, model, usage, tools })
|
|
259
|
-
: () => Promise.resolve();
|
|
107
|
+
const bill = ai.bill({ db, user, model, usage, tools });
|
|
260
108
|
|
|
261
109
|
return { content, bill };
|
|
262
110
|
|
|
@@ -310,9 +158,7 @@ const google = {
|
|
|
310
158
|
const usage = tokensFromMetadata( response?.usageMetadata );
|
|
311
159
|
const tools = toolsFromResponse( response );
|
|
312
160
|
|
|
313
|
-
const bill = user
|
|
314
|
-
? () => billRequest({ db, user, model, usage, tools })
|
|
315
|
-
: () => Promise.resolve();
|
|
161
|
+
const bill = ai.bill({ db, user, model, usage, tools });
|
|
316
162
|
|
|
317
163
|
return { content, bill };
|
|
318
164
|
|
|
@@ -320,4 +166,4 @@ const google = {
|
|
|
320
166
|
|
|
321
167
|
};
|
|
322
168
|
|
|
323
|
-
export {
|
|
169
|
+
export { google };
|
package/dist/ai.js
CHANGED
|
@@ -125,19 +125,10 @@ var debit = async ({
|
|
|
125
125
|
});
|
|
126
126
|
};
|
|
127
127
|
|
|
128
|
-
// lib/
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
var googleCircuit = circuit({
|
|
133
|
-
name: "google-ai",
|
|
134
|
-
threshold: 3,
|
|
135
|
-
timeout: 6e4
|
|
136
|
-
});
|
|
137
|
-
var models = {
|
|
138
|
-
image: "gemini-2.5-flash-image",
|
|
139
|
-
text: "gemini-2.5-flash"
|
|
140
|
-
};
|
|
128
|
+
// lib/billing.js
|
|
129
|
+
import { createLogger } from "@drawbridge/drawbridge-telemetry";
|
|
130
|
+
var logger = createLogger();
|
|
131
|
+
var MARKUP = 1.3;
|
|
141
132
|
var cost = {
|
|
142
133
|
"gemini-2.5-flash": {
|
|
143
134
|
cached: 3,
|
|
@@ -150,7 +141,6 @@ var cost = {
|
|
|
150
141
|
output: 3e3
|
|
151
142
|
}
|
|
152
143
|
};
|
|
153
|
-
var MARKUP = 1.3;
|
|
154
144
|
var toolCost = {
|
|
155
145
|
search: 3.5
|
|
156
146
|
};
|
|
@@ -191,25 +181,7 @@ var costForRequest = (model, usage, tools) => {
|
|
|
191
181
|
}
|
|
192
182
|
return sumCents(rates, usage) + Math.ceil(sumToolCents(toolCost, tools));
|
|
193
183
|
};
|
|
194
|
-
var
|
|
195
|
-
cached: Number(metadata == null ? void 0 : metadata.cachedContentTokenCount) || 0,
|
|
196
|
-
input: Number(metadata == null ? void 0 : metadata.promptTokenCount) || 0,
|
|
197
|
-
output: Number(metadata == null ? void 0 : metadata.candidatesTokenCount) || 0,
|
|
198
|
-
thinking: Number(metadata == null ? void 0 : metadata.thoughtsTokenCount) || 0
|
|
199
|
-
});
|
|
200
|
-
var toolsFromResponse = (response) => {
|
|
201
|
-
var _a, _b;
|
|
202
|
-
return {
|
|
203
|
-
search: Boolean((_b = (_a = response == null ? void 0 : response.candidates) == null ? void 0 : _a[0]) == null ? void 0 : _b.groundingMetadata)
|
|
204
|
-
};
|
|
205
|
-
};
|
|
206
|
-
var billRequest = async ({
|
|
207
|
-
db,
|
|
208
|
-
user,
|
|
209
|
-
model,
|
|
210
|
-
usage,
|
|
211
|
-
tools
|
|
212
|
-
}) => {
|
|
184
|
+
var billRequest = async ({ db, user, model, usage, tools }) => {
|
|
213
185
|
const gross = priceForRequest(model, usage, tools);
|
|
214
186
|
const cogs = costForRequest(model, usage, tools);
|
|
215
187
|
await debit({
|
|
@@ -236,6 +208,42 @@ var billRequest = async ({
|
|
|
236
208
|
}
|
|
237
209
|
});
|
|
238
210
|
};
|
|
211
|
+
var ai = {
|
|
212
|
+
// Retail cents for a request (what the user pays).
|
|
213
|
+
price: priceForRequest,
|
|
214
|
+
// Wholesale cents for a request (what we pay the provider).
|
|
215
|
+
cost: costForRequest,
|
|
216
|
+
// Deferred bill() the caller fires on success, so the debit lands only when
|
|
217
|
+
// the request succeeds and bundles into that request's Sentry trace. No user
|
|
218
|
+
// → a no-op.
|
|
219
|
+
bill: ({ db, user, model, usage, tools }) => (user == null ? void 0 : user.id) ? () => billRequest({ db, user, model, usage, tools }) : () => Promise.resolve()
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// lib/ai.js
|
|
223
|
+
var google_client = new GoogleGenAI({
|
|
224
|
+
apiKey: process.env.GOOGLE_GEMINI_API_KEY
|
|
225
|
+
});
|
|
226
|
+
var googleCircuit = circuit({
|
|
227
|
+
name: "google-ai",
|
|
228
|
+
threshold: 3,
|
|
229
|
+
timeout: 6e4
|
|
230
|
+
});
|
|
231
|
+
var models = {
|
|
232
|
+
image: "gemini-2.5-flash-image",
|
|
233
|
+
text: "gemini-2.5-flash"
|
|
234
|
+
};
|
|
235
|
+
var tokensFromMetadata = (metadata) => ({
|
|
236
|
+
cached: Number(metadata == null ? void 0 : metadata.cachedContentTokenCount) || 0,
|
|
237
|
+
input: Number(metadata == null ? void 0 : metadata.promptTokenCount) || 0,
|
|
238
|
+
output: Number(metadata == null ? void 0 : metadata.candidatesTokenCount) || 0,
|
|
239
|
+
thinking: Number(metadata == null ? void 0 : metadata.thoughtsTokenCount) || 0
|
|
240
|
+
});
|
|
241
|
+
var toolsFromResponse = (response) => {
|
|
242
|
+
var _a, _b;
|
|
243
|
+
return {
|
|
244
|
+
search: Boolean((_b = (_a = response == null ? void 0 : response.candidates) == null ? void 0 : _a[0]) == null ? void 0 : _b.groundingMetadata)
|
|
245
|
+
};
|
|
246
|
+
};
|
|
239
247
|
var google = {
|
|
240
248
|
image: async ({
|
|
241
249
|
config = {},
|
|
@@ -267,7 +275,7 @@ var google = {
|
|
|
267
275
|
}
|
|
268
276
|
const usage = tokensFromMetadata(response == null ? void 0 : response.usageMetadata);
|
|
269
277
|
const tools = toolsFromResponse(response);
|
|
270
|
-
const bill =
|
|
278
|
+
const bill = ai.bill({ db, user, model, usage, tools });
|
|
271
279
|
return { content, bill };
|
|
272
280
|
},
|
|
273
281
|
text: async ({
|
|
@@ -295,17 +303,11 @@ var google = {
|
|
|
295
303
|
}
|
|
296
304
|
const usage = tokensFromMetadata(response == null ? void 0 : response.usageMetadata);
|
|
297
305
|
const tools = toolsFromResponse(response);
|
|
298
|
-
const bill =
|
|
306
|
+
const bill = ai.bill({ db, user, model, usage, tools });
|
|
299
307
|
return { content, bill };
|
|
300
308
|
}
|
|
301
309
|
};
|
|
302
310
|
export {
|
|
303
311
|
MARKUP,
|
|
304
|
-
|
|
305
|
-
costForRequest,
|
|
306
|
-
google,
|
|
307
|
-
priceForRequest,
|
|
308
|
-
pricing,
|
|
309
|
-
toolCost,
|
|
310
|
-
toolPricing
|
|
312
|
+
google
|
|
311
313
|
};
|