@drawbridge/drawbridge-utils 0.0.33 → 0.0.36
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 +44 -13
- package/dist/ai.d.cts +72 -12
- package/dist/ai.d.ts +72 -12
- package/dist/ai.js +41 -12
- package/package.json +2 -1
package/dist/ai.cjs
CHANGED
|
@@ -24,10 +24,13 @@ __export(ai_exports, {
|
|
|
24
24
|
costForRequest: () => costForRequest,
|
|
25
25
|
google: () => google,
|
|
26
26
|
priceForRequest: () => priceForRequest,
|
|
27
|
-
pricing: () => pricing
|
|
27
|
+
pricing: () => pricing,
|
|
28
|
+
toolCost: () => toolCost,
|
|
29
|
+
toolPricing: () => toolPricing
|
|
28
30
|
});
|
|
29
31
|
module.exports = __toCommonJS(ai_exports);
|
|
30
32
|
var import_genai = require("@google/genai");
|
|
33
|
+
var import_drawbridge_telemetry = require("@drawbridge/drawbridge-telemetry");
|
|
31
34
|
|
|
32
35
|
// circuit.js
|
|
33
36
|
var CLOSED = "CLOSED";
|
|
@@ -176,6 +179,15 @@ var cost = {
|
|
|
176
179
|
}
|
|
177
180
|
};
|
|
178
181
|
var MARKUP = 1.3;
|
|
182
|
+
var toolCost = {
|
|
183
|
+
search: 3.5
|
|
184
|
+
};
|
|
185
|
+
var toolPricing = Object.fromEntries(
|
|
186
|
+
Object.entries(toolCost).map(([tool, value]) => [
|
|
187
|
+
tool,
|
|
188
|
+
Math.ceil(value * MARKUP)
|
|
189
|
+
])
|
|
190
|
+
);
|
|
179
191
|
var pricing = Object.fromEntries(
|
|
180
192
|
Object.entries(cost).map(([model, rates]) => [
|
|
181
193
|
model,
|
|
@@ -187,35 +199,48 @@ var pricing = Object.fromEntries(
|
|
|
187
199
|
])
|
|
188
200
|
);
|
|
189
201
|
var sumCents = (rates, tokens) => Math.ceil(
|
|
190
|
-
(Number((tokens == null ? void 0 : tokens.cached) || 0) * rates.cached + Number((tokens == null ? void 0 : tokens.input) || 0) * rates.input + Number((tokens == null ? void 0 : tokens.output) || 0) * rates.output) / 1e6
|
|
202
|
+
(Number((tokens == null ? void 0 : tokens.cached) || 0) * rates.cached + Number((tokens == null ? void 0 : tokens.input) || 0) * rates.input + (Number((tokens == null ? void 0 : tokens.output) || 0) + Number((tokens == null ? void 0 : tokens.thinking) || 0)) * rates.output) / 1e6
|
|
191
203
|
);
|
|
192
|
-
var
|
|
204
|
+
var sumToolCents = (table, tools) => Object.entries(tools || {}).reduce(
|
|
205
|
+
(total, [name, used]) => total + (used && table[name] ? table[name] : 0),
|
|
206
|
+
0
|
|
207
|
+
);
|
|
208
|
+
var priceForRequest = (model, usage, tools) => {
|
|
193
209
|
const rates = pricing[model];
|
|
194
210
|
if (!rates) {
|
|
195
211
|
throw new Error(`Unknown model for pricing: ${model}`);
|
|
196
212
|
}
|
|
197
|
-
return sumCents(rates, usage);
|
|
213
|
+
return sumCents(rates, usage) + Math.ceil(sumToolCents(toolPricing, tools));
|
|
198
214
|
};
|
|
199
|
-
var costForRequest = (model, usage) => {
|
|
215
|
+
var costForRequest = (model, usage, tools) => {
|
|
200
216
|
const rates = cost[model];
|
|
201
217
|
if (!rates) {
|
|
202
218
|
throw new Error(`Unknown model for cost: ${model}`);
|
|
203
219
|
}
|
|
204
|
-
return sumCents(rates, usage);
|
|
220
|
+
return sumCents(rates, usage) + Math.ceil(sumToolCents(toolCost, tools));
|
|
205
221
|
};
|
|
206
222
|
var tokensFromMetadata = (metadata) => ({
|
|
207
223
|
cached: Number(metadata == null ? void 0 : metadata.cachedContentTokenCount) || 0,
|
|
208
224
|
input: Number(metadata == null ? void 0 : metadata.promptTokenCount) || 0,
|
|
209
|
-
output: Number(metadata == null ? void 0 : metadata.candidatesTokenCount) || 0
|
|
225
|
+
output: Number(metadata == null ? void 0 : metadata.candidatesTokenCount) || 0,
|
|
226
|
+
thinking: Number(metadata == null ? void 0 : metadata.thoughtsTokenCount) || 0
|
|
210
227
|
});
|
|
228
|
+
var toolsFromResponse = (response) => {
|
|
229
|
+
var _a, _b;
|
|
230
|
+
return {
|
|
231
|
+
search: Boolean((_b = (_a = response == null ? void 0 : response.candidates) == null ? void 0 : _a[0]) == null ? void 0 : _b.groundingMetadata)
|
|
232
|
+
};
|
|
233
|
+
};
|
|
211
234
|
var billRequest = async ({
|
|
212
235
|
db,
|
|
213
236
|
user,
|
|
214
237
|
model,
|
|
215
|
-
usage
|
|
238
|
+
usage,
|
|
239
|
+
tools
|
|
216
240
|
}) => {
|
|
217
|
-
const gross = priceForRequest(model, usage);
|
|
218
|
-
const cogs = costForRequest(model, usage);
|
|
241
|
+
const gross = priceForRequest(model, usage, tools);
|
|
242
|
+
const cogs = costForRequest(model, usage, tools);
|
|
243
|
+
const id = (0, import_drawbridge_telemetry.currentTraceId)();
|
|
219
244
|
await debit({
|
|
220
245
|
db,
|
|
221
246
|
user,
|
|
@@ -223,10 +248,12 @@ var billRequest = async ({
|
|
|
223
248
|
type: "ai",
|
|
224
249
|
category: "usage",
|
|
225
250
|
source: "user",
|
|
251
|
+
...id && { trace: { id } },
|
|
226
252
|
ai: {
|
|
227
253
|
name: "google",
|
|
228
254
|
model,
|
|
229
255
|
tokens: usage,
|
|
256
|
+
tools,
|
|
230
257
|
rates: {
|
|
231
258
|
wholesale: cost[model],
|
|
232
259
|
retail: pricing[model]
|
|
@@ -271,7 +298,8 @@ var google = {
|
|
|
271
298
|
db,
|
|
272
299
|
user,
|
|
273
300
|
model,
|
|
274
|
-
usage: tokensFromMetadata(response == null ? void 0 : response.usageMetadata)
|
|
301
|
+
usage: tokensFromMetadata(response == null ? void 0 : response.usageMetadata),
|
|
302
|
+
tools: toolsFromResponse(response)
|
|
275
303
|
});
|
|
276
304
|
}
|
|
277
305
|
return { content };
|
|
@@ -301,7 +329,8 @@ var google = {
|
|
|
301
329
|
db,
|
|
302
330
|
user,
|
|
303
331
|
model,
|
|
304
|
-
usage: tokensFromMetadata(response == null ? void 0 : response.usageMetadata)
|
|
332
|
+
usage: tokensFromMetadata(response == null ? void 0 : response.usageMetadata),
|
|
333
|
+
tools: toolsFromResponse(response)
|
|
305
334
|
});
|
|
306
335
|
}
|
|
307
336
|
return { content };
|
|
@@ -314,5 +343,7 @@ var google = {
|
|
|
314
343
|
costForRequest,
|
|
315
344
|
google,
|
|
316
345
|
priceForRequest,
|
|
317
|
-
pricing
|
|
346
|
+
pricing,
|
|
347
|
+
toolCost,
|
|
348
|
+
toolPricing
|
|
318
349
|
});
|
package/dist/ai.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { GoogleGenAI } from '@google/genai';
|
|
2
|
+
import { currentTraceId } from '@drawbridge/drawbridge-telemetry';
|
|
2
3
|
import { circuit } from './circuit.cjs';
|
|
3
4
|
import { debit } from './transactions.cjs';
|
|
4
5
|
|
|
@@ -61,6 +62,22 @@ const cost = {
|
|
|
61
62
|
// refactor later if tier-based pricing becomes a real need.
|
|
62
63
|
const MARKUP = 1.3;
|
|
63
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
|
+
|
|
64
81
|
// Derived retail pricing in cents per 1,000,000 tokens — what users pay.
|
|
65
82
|
// Rounded so the per-1M rate is a whole-cent value; per-request rounding
|
|
66
83
|
// happens in priceForRequest via Math.ceil so we never under-bill.
|
|
@@ -75,17 +92,33 @@ const pricing = Object.fromEntries(
|
|
|
75
92
|
] )
|
|
76
93
|
);
|
|
77
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.
|
|
78
99
|
const sumCents = ( rates, tokens ) => Math.ceil(
|
|
79
100
|
(
|
|
80
101
|
Number( tokens?.cached || 0 ) * rates.cached +
|
|
81
102
|
Number( tokens?.input || 0 ) * rates.input +
|
|
82
|
-
|
|
103
|
+
(
|
|
104
|
+
Number( tokens?.output || 0 ) +
|
|
105
|
+
Number( tokens?.thinking || 0 )
|
|
106
|
+
) * rates.output
|
|
83
107
|
) / 1_000_000
|
|
84
108
|
);
|
|
85
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
|
+
|
|
86
119
|
// What we charge the user, rounded up so we never under-bill. Throws on an
|
|
87
120
|
// unknown model so a typo doesn't silently fall back to a wrong price.
|
|
88
|
-
const priceForRequest = ( model, usage ) => {
|
|
121
|
+
const priceForRequest = ( model, usage, tools ) => {
|
|
89
122
|
|
|
90
123
|
const rates = pricing[ model ];
|
|
91
124
|
|
|
@@ -95,13 +128,13 @@ const priceForRequest = ( model, usage ) => {
|
|
|
95
128
|
|
|
96
129
|
}
|
|
97
130
|
|
|
98
|
-
return sumCents( rates, usage );
|
|
131
|
+
return sumCents( rates, usage ) + Math.ceil( sumToolCents( toolPricing, tools ) );
|
|
99
132
|
|
|
100
133
|
};
|
|
101
134
|
|
|
102
135
|
// What we pay the provider. Recorded on each transaction row so margin is
|
|
103
136
|
// computable per request without backfilling from git history.
|
|
104
|
-
const costForRequest = ( model, usage ) => {
|
|
137
|
+
const costForRequest = ( model, usage, tools ) => {
|
|
105
138
|
|
|
106
139
|
const rates = cost[ model ];
|
|
107
140
|
|
|
@@ -111,15 +144,27 @@ const costForRequest = ( model, usage ) => {
|
|
|
111
144
|
|
|
112
145
|
}
|
|
113
146
|
|
|
114
|
-
return sumCents( rates, usage );
|
|
147
|
+
return sumCents( rates, usage ) + Math.ceil( sumToolCents( toolCost, tools ) );
|
|
115
148
|
|
|
116
149
|
};
|
|
117
150
|
|
|
118
151
|
// Map Google's usageMetadata shape into the pricing.tokens shape.
|
|
152
|
+
// `thinking` is Gemini 2.5's internal reasoning token count — billed at the
|
|
153
|
+
// same rate as `output` but tracked separately so the transaction row shows
|
|
154
|
+
// the visible-vs-reasoning split.
|
|
119
155
|
const tokensFromMetadata = ( metadata ) => ({
|
|
120
156
|
cached : Number( metadata?.cachedContentTokenCount ) || 0,
|
|
121
157
|
input : Number( metadata?.promptTokenCount ) || 0,
|
|
122
|
-
output : Number( metadata?.candidatesTokenCount ) || 0
|
|
158
|
+
output : Number( metadata?.candidatesTokenCount ) || 0,
|
|
159
|
+
thinking : Number( metadata?.thoughtsTokenCount ) || 0
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Derive which billable tools actually fired from the response. Google bills
|
|
163
|
+
// per-request when the tool was invoked, not when it was merely enabled in
|
|
164
|
+
// config — `groundingMetadata` is only present on candidates when search was
|
|
165
|
+
// used. Add more tools here as Google adds them.
|
|
166
|
+
const toolsFromResponse = ( response ) => ({
|
|
167
|
+
search : Boolean( response?.candidates?.[ 0 ]?.groundingMetadata )
|
|
123
168
|
});
|
|
124
169
|
|
|
125
170
|
// Compute gross + cost from response usage and atomically debit the user.
|
|
@@ -131,11 +176,22 @@ const billRequest = async ({
|
|
|
131
176
|
db,
|
|
132
177
|
user,
|
|
133
178
|
model,
|
|
134
|
-
usage
|
|
179
|
+
usage,
|
|
180
|
+
tools
|
|
135
181
|
}) => {
|
|
136
182
|
|
|
137
|
-
const gross = priceForRequest( model, usage );
|
|
138
|
-
const cogs = costForRequest( model, usage );
|
|
183
|
+
const gross = priceForRequest( model, usage, tools );
|
|
184
|
+
const cogs = costForRequest( model, usage, tools );
|
|
185
|
+
|
|
186
|
+
// Stamp the current trace ID so multiple AI calls inside one HTTP request
|
|
187
|
+
// (or one BullMQ job) share a grouping key — the UI can collapse rows
|
|
188
|
+
// per trace.id to show "this preview cost $0.12 across 5 calls" without
|
|
189
|
+
// the user needing to mentally fold them together. currentTraceId()
|
|
190
|
+
// returns undefined when called outside any traced scope, in which case
|
|
191
|
+
// the field is omitted from the row (additionalProperties:false would
|
|
192
|
+
// reject `trace: undefined`). Nested as `trace.id` to match the `ai` /
|
|
193
|
+
// `balance` sub-object pattern already on this document.
|
|
194
|
+
const id = currentTraceId();
|
|
139
195
|
|
|
140
196
|
await debit({
|
|
141
197
|
db,
|
|
@@ -144,10 +200,12 @@ const billRequest = async ({
|
|
|
144
200
|
type : 'ai',
|
|
145
201
|
category : 'usage',
|
|
146
202
|
source : 'user',
|
|
203
|
+
...( id && { trace : { id } }),
|
|
147
204
|
ai : {
|
|
148
205
|
name : 'google',
|
|
149
206
|
model,
|
|
150
207
|
tokens : usage,
|
|
208
|
+
tools,
|
|
151
209
|
rates : {
|
|
152
210
|
wholesale : cost[ model ],
|
|
153
211
|
retail : pricing[ model ]
|
|
@@ -206,7 +264,8 @@ const google = {
|
|
|
206
264
|
db,
|
|
207
265
|
user,
|
|
208
266
|
model,
|
|
209
|
-
usage : tokensFromMetadata( response?.usageMetadata )
|
|
267
|
+
usage : tokensFromMetadata( response?.usageMetadata ),
|
|
268
|
+
tools : toolsFromResponse( response )
|
|
210
269
|
});
|
|
211
270
|
|
|
212
271
|
}
|
|
@@ -251,7 +310,8 @@ const google = {
|
|
|
251
310
|
db,
|
|
252
311
|
user,
|
|
253
312
|
model,
|
|
254
|
-
usage : tokensFromMetadata( response?.usageMetadata )
|
|
313
|
+
usage : tokensFromMetadata( response?.usageMetadata ),
|
|
314
|
+
tools : toolsFromResponse( response )
|
|
255
315
|
});
|
|
256
316
|
|
|
257
317
|
}
|
|
@@ -262,4 +322,4 @@ const google = {
|
|
|
262
322
|
|
|
263
323
|
};
|
|
264
324
|
|
|
265
|
-
export { MARKUP, cost, costForRequest, google, priceForRequest, pricing };
|
|
325
|
+
export { MARKUP, cost, costForRequest, google, priceForRequest, pricing, toolCost, toolPricing };
|
package/dist/ai.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { GoogleGenAI } from '@google/genai';
|
|
2
|
+
import { currentTraceId } from '@drawbridge/drawbridge-telemetry';
|
|
2
3
|
import { circuit } from './circuit.js';
|
|
3
4
|
import { debit } from './transactions.js';
|
|
4
5
|
|
|
@@ -61,6 +62,22 @@ const cost = {
|
|
|
61
62
|
// refactor later if tier-based pricing becomes a real need.
|
|
62
63
|
const MARKUP = 1.3;
|
|
63
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
|
+
|
|
64
81
|
// Derived retail pricing in cents per 1,000,000 tokens — what users pay.
|
|
65
82
|
// Rounded so the per-1M rate is a whole-cent value; per-request rounding
|
|
66
83
|
// happens in priceForRequest via Math.ceil so we never under-bill.
|
|
@@ -75,17 +92,33 @@ const pricing = Object.fromEntries(
|
|
|
75
92
|
] )
|
|
76
93
|
);
|
|
77
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.
|
|
78
99
|
const sumCents = ( rates, tokens ) => Math.ceil(
|
|
79
100
|
(
|
|
80
101
|
Number( tokens?.cached || 0 ) * rates.cached +
|
|
81
102
|
Number( tokens?.input || 0 ) * rates.input +
|
|
82
|
-
|
|
103
|
+
(
|
|
104
|
+
Number( tokens?.output || 0 ) +
|
|
105
|
+
Number( tokens?.thinking || 0 )
|
|
106
|
+
) * rates.output
|
|
83
107
|
) / 1_000_000
|
|
84
108
|
);
|
|
85
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
|
+
|
|
86
119
|
// What we charge the user, rounded up so we never under-bill. Throws on an
|
|
87
120
|
// unknown model so a typo doesn't silently fall back to a wrong price.
|
|
88
|
-
const priceForRequest = ( model, usage ) => {
|
|
121
|
+
const priceForRequest = ( model, usage, tools ) => {
|
|
89
122
|
|
|
90
123
|
const rates = pricing[ model ];
|
|
91
124
|
|
|
@@ -95,13 +128,13 @@ const priceForRequest = ( model, usage ) => {
|
|
|
95
128
|
|
|
96
129
|
}
|
|
97
130
|
|
|
98
|
-
return sumCents( rates, usage );
|
|
131
|
+
return sumCents( rates, usage ) + Math.ceil( sumToolCents( toolPricing, tools ) );
|
|
99
132
|
|
|
100
133
|
};
|
|
101
134
|
|
|
102
135
|
// What we pay the provider. Recorded on each transaction row so margin is
|
|
103
136
|
// computable per request without backfilling from git history.
|
|
104
|
-
const costForRequest = ( model, usage ) => {
|
|
137
|
+
const costForRequest = ( model, usage, tools ) => {
|
|
105
138
|
|
|
106
139
|
const rates = cost[ model ];
|
|
107
140
|
|
|
@@ -111,15 +144,27 @@ const costForRequest = ( model, usage ) => {
|
|
|
111
144
|
|
|
112
145
|
}
|
|
113
146
|
|
|
114
|
-
return sumCents( rates, usage );
|
|
147
|
+
return sumCents( rates, usage ) + Math.ceil( sumToolCents( toolCost, tools ) );
|
|
115
148
|
|
|
116
149
|
};
|
|
117
150
|
|
|
118
151
|
// Map Google's usageMetadata shape into the pricing.tokens shape.
|
|
152
|
+
// `thinking` is Gemini 2.5's internal reasoning token count — billed at the
|
|
153
|
+
// same rate as `output` but tracked separately so the transaction row shows
|
|
154
|
+
// the visible-vs-reasoning split.
|
|
119
155
|
const tokensFromMetadata = ( metadata ) => ({
|
|
120
156
|
cached : Number( metadata?.cachedContentTokenCount ) || 0,
|
|
121
157
|
input : Number( metadata?.promptTokenCount ) || 0,
|
|
122
|
-
output : Number( metadata?.candidatesTokenCount ) || 0
|
|
158
|
+
output : Number( metadata?.candidatesTokenCount ) || 0,
|
|
159
|
+
thinking : Number( metadata?.thoughtsTokenCount ) || 0
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Derive which billable tools actually fired from the response. Google bills
|
|
163
|
+
// per-request when the tool was invoked, not when it was merely enabled in
|
|
164
|
+
// config — `groundingMetadata` is only present on candidates when search was
|
|
165
|
+
// used. Add more tools here as Google adds them.
|
|
166
|
+
const toolsFromResponse = ( response ) => ({
|
|
167
|
+
search : Boolean( response?.candidates?.[ 0 ]?.groundingMetadata )
|
|
123
168
|
});
|
|
124
169
|
|
|
125
170
|
// Compute gross + cost from response usage and atomically debit the user.
|
|
@@ -131,11 +176,22 @@ const billRequest = async ({
|
|
|
131
176
|
db,
|
|
132
177
|
user,
|
|
133
178
|
model,
|
|
134
|
-
usage
|
|
179
|
+
usage,
|
|
180
|
+
tools
|
|
135
181
|
}) => {
|
|
136
182
|
|
|
137
|
-
const gross = priceForRequest( model, usage );
|
|
138
|
-
const cogs = costForRequest( model, usage );
|
|
183
|
+
const gross = priceForRequest( model, usage, tools );
|
|
184
|
+
const cogs = costForRequest( model, usage, tools );
|
|
185
|
+
|
|
186
|
+
// Stamp the current trace ID so multiple AI calls inside one HTTP request
|
|
187
|
+
// (or one BullMQ job) share a grouping key — the UI can collapse rows
|
|
188
|
+
// per trace.id to show "this preview cost $0.12 across 5 calls" without
|
|
189
|
+
// the user needing to mentally fold them together. currentTraceId()
|
|
190
|
+
// returns undefined when called outside any traced scope, in which case
|
|
191
|
+
// the field is omitted from the row (additionalProperties:false would
|
|
192
|
+
// reject `trace: undefined`). Nested as `trace.id` to match the `ai` /
|
|
193
|
+
// `balance` sub-object pattern already on this document.
|
|
194
|
+
const id = currentTraceId();
|
|
139
195
|
|
|
140
196
|
await debit({
|
|
141
197
|
db,
|
|
@@ -144,10 +200,12 @@ const billRequest = async ({
|
|
|
144
200
|
type : 'ai',
|
|
145
201
|
category : 'usage',
|
|
146
202
|
source : 'user',
|
|
203
|
+
...( id && { trace : { id } }),
|
|
147
204
|
ai : {
|
|
148
205
|
name : 'google',
|
|
149
206
|
model,
|
|
150
207
|
tokens : usage,
|
|
208
|
+
tools,
|
|
151
209
|
rates : {
|
|
152
210
|
wholesale : cost[ model ],
|
|
153
211
|
retail : pricing[ model ]
|
|
@@ -206,7 +264,8 @@ const google = {
|
|
|
206
264
|
db,
|
|
207
265
|
user,
|
|
208
266
|
model,
|
|
209
|
-
usage : tokensFromMetadata( response?.usageMetadata )
|
|
267
|
+
usage : tokensFromMetadata( response?.usageMetadata ),
|
|
268
|
+
tools : toolsFromResponse( response )
|
|
210
269
|
});
|
|
211
270
|
|
|
212
271
|
}
|
|
@@ -251,7 +310,8 @@ const google = {
|
|
|
251
310
|
db,
|
|
252
311
|
user,
|
|
253
312
|
model,
|
|
254
|
-
usage : tokensFromMetadata( response?.usageMetadata )
|
|
313
|
+
usage : tokensFromMetadata( response?.usageMetadata ),
|
|
314
|
+
tools : toolsFromResponse( response )
|
|
255
315
|
});
|
|
256
316
|
|
|
257
317
|
}
|
|
@@ -262,4 +322,4 @@ const google = {
|
|
|
262
322
|
|
|
263
323
|
};
|
|
264
324
|
|
|
265
|
-
export { MARKUP, cost, costForRequest, google, priceForRequest, pricing };
|
|
325
|
+
export { MARKUP, cost, costForRequest, google, priceForRequest, pricing, toolCost, toolPricing };
|
package/dist/ai.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
|
|
8
8
|
// ai.js
|
|
9
9
|
import { GoogleGenAI } from "@google/genai";
|
|
10
|
+
import { currentTraceId } from "@drawbridge/drawbridge-telemetry";
|
|
10
11
|
var google_client = new GoogleGenAI({
|
|
11
12
|
apiKey: process.env.GOOGLE_API_KEY
|
|
12
13
|
});
|
|
@@ -32,6 +33,15 @@ var cost = {
|
|
|
32
33
|
}
|
|
33
34
|
};
|
|
34
35
|
var MARKUP = 1.3;
|
|
36
|
+
var toolCost = {
|
|
37
|
+
search: 3.5
|
|
38
|
+
};
|
|
39
|
+
var toolPricing = Object.fromEntries(
|
|
40
|
+
Object.entries(toolCost).map(([tool, value]) => [
|
|
41
|
+
tool,
|
|
42
|
+
Math.ceil(value * MARKUP)
|
|
43
|
+
])
|
|
44
|
+
);
|
|
35
45
|
var pricing = Object.fromEntries(
|
|
36
46
|
Object.entries(cost).map(([model, rates]) => [
|
|
37
47
|
model,
|
|
@@ -43,35 +53,48 @@ var pricing = Object.fromEntries(
|
|
|
43
53
|
])
|
|
44
54
|
);
|
|
45
55
|
var sumCents = (rates, tokens) => Math.ceil(
|
|
46
|
-
(Number((tokens == null ? void 0 : tokens.cached) || 0) * rates.cached + Number((tokens == null ? void 0 : tokens.input) || 0) * rates.input + Number((tokens == null ? void 0 : tokens.output) || 0) * rates.output) / 1e6
|
|
56
|
+
(Number((tokens == null ? void 0 : tokens.cached) || 0) * rates.cached + Number((tokens == null ? void 0 : tokens.input) || 0) * rates.input + (Number((tokens == null ? void 0 : tokens.output) || 0) + Number((tokens == null ? void 0 : tokens.thinking) || 0)) * rates.output) / 1e6
|
|
47
57
|
);
|
|
48
|
-
var
|
|
58
|
+
var sumToolCents = (table, tools) => Object.entries(tools || {}).reduce(
|
|
59
|
+
(total, [name, used]) => total + (used && table[name] ? table[name] : 0),
|
|
60
|
+
0
|
|
61
|
+
);
|
|
62
|
+
var priceForRequest = (model, usage, tools) => {
|
|
49
63
|
const rates = pricing[model];
|
|
50
64
|
if (!rates) {
|
|
51
65
|
throw new Error(`Unknown model for pricing: ${model}`);
|
|
52
66
|
}
|
|
53
|
-
return sumCents(rates, usage);
|
|
67
|
+
return sumCents(rates, usage) + Math.ceil(sumToolCents(toolPricing, tools));
|
|
54
68
|
};
|
|
55
|
-
var costForRequest = (model, usage) => {
|
|
69
|
+
var costForRequest = (model, usage, tools) => {
|
|
56
70
|
const rates = cost[model];
|
|
57
71
|
if (!rates) {
|
|
58
72
|
throw new Error(`Unknown model for cost: ${model}`);
|
|
59
73
|
}
|
|
60
|
-
return sumCents(rates, usage);
|
|
74
|
+
return sumCents(rates, usage) + Math.ceil(sumToolCents(toolCost, tools));
|
|
61
75
|
};
|
|
62
76
|
var tokensFromMetadata = (metadata) => ({
|
|
63
77
|
cached: Number(metadata == null ? void 0 : metadata.cachedContentTokenCount) || 0,
|
|
64
78
|
input: Number(metadata == null ? void 0 : metadata.promptTokenCount) || 0,
|
|
65
|
-
output: Number(metadata == null ? void 0 : metadata.candidatesTokenCount) || 0
|
|
79
|
+
output: Number(metadata == null ? void 0 : metadata.candidatesTokenCount) || 0,
|
|
80
|
+
thinking: Number(metadata == null ? void 0 : metadata.thoughtsTokenCount) || 0
|
|
66
81
|
});
|
|
82
|
+
var toolsFromResponse = (response) => {
|
|
83
|
+
var _a, _b;
|
|
84
|
+
return {
|
|
85
|
+
search: Boolean((_b = (_a = response == null ? void 0 : response.candidates) == null ? void 0 : _a[0]) == null ? void 0 : _b.groundingMetadata)
|
|
86
|
+
};
|
|
87
|
+
};
|
|
67
88
|
var billRequest = async ({
|
|
68
89
|
db,
|
|
69
90
|
user,
|
|
70
91
|
model,
|
|
71
|
-
usage
|
|
92
|
+
usage,
|
|
93
|
+
tools
|
|
72
94
|
}) => {
|
|
73
|
-
const gross = priceForRequest(model, usage);
|
|
74
|
-
const cogs = costForRequest(model, usage);
|
|
95
|
+
const gross = priceForRequest(model, usage, tools);
|
|
96
|
+
const cogs = costForRequest(model, usage, tools);
|
|
97
|
+
const id = currentTraceId();
|
|
75
98
|
await debit({
|
|
76
99
|
db,
|
|
77
100
|
user,
|
|
@@ -79,10 +102,12 @@ var billRequest = async ({
|
|
|
79
102
|
type: "ai",
|
|
80
103
|
category: "usage",
|
|
81
104
|
source: "user",
|
|
105
|
+
...id && { trace: { id } },
|
|
82
106
|
ai: {
|
|
83
107
|
name: "google",
|
|
84
108
|
model,
|
|
85
109
|
tokens: usage,
|
|
110
|
+
tools,
|
|
86
111
|
rates: {
|
|
87
112
|
wholesale: cost[model],
|
|
88
113
|
retail: pricing[model]
|
|
@@ -127,7 +152,8 @@ var google = {
|
|
|
127
152
|
db,
|
|
128
153
|
user,
|
|
129
154
|
model,
|
|
130
|
-
usage: tokensFromMetadata(response == null ? void 0 : response.usageMetadata)
|
|
155
|
+
usage: tokensFromMetadata(response == null ? void 0 : response.usageMetadata),
|
|
156
|
+
tools: toolsFromResponse(response)
|
|
131
157
|
});
|
|
132
158
|
}
|
|
133
159
|
return { content };
|
|
@@ -157,7 +183,8 @@ var google = {
|
|
|
157
183
|
db,
|
|
158
184
|
user,
|
|
159
185
|
model,
|
|
160
|
-
usage: tokensFromMetadata(response == null ? void 0 : response.usageMetadata)
|
|
186
|
+
usage: tokensFromMetadata(response == null ? void 0 : response.usageMetadata),
|
|
187
|
+
tools: toolsFromResponse(response)
|
|
161
188
|
});
|
|
162
189
|
}
|
|
163
190
|
return { content };
|
|
@@ -169,5 +196,7 @@ export {
|
|
|
169
196
|
costForRequest,
|
|
170
197
|
google,
|
|
171
198
|
priceForRequest,
|
|
172
|
-
pricing
|
|
199
|
+
pricing,
|
|
200
|
+
toolCost,
|
|
201
|
+
toolPricing
|
|
173
202
|
};
|
package/package.json
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"type": "module",
|
|
3
3
|
"dependencies": {
|
|
4
4
|
"@drawbridge/drawbridge-agents": "0.0.10",
|
|
5
|
+
"@drawbridge/drawbridge-telemetry": "0.0.13",
|
|
5
6
|
"@google/genai": "1.30.0",
|
|
6
7
|
"axios": "1.16.0",
|
|
7
8
|
"currency-codes": "2.2.0",
|
|
@@ -103,5 +104,5 @@
|
|
|
103
104
|
"build": "tsup && npm publish"
|
|
104
105
|
},
|
|
105
106
|
"types": "dist/index.d.ts",
|
|
106
|
-
"version": "0.0.
|
|
107
|
+
"version": "0.0.36"
|
|
107
108
|
}
|