@drawbridge/drawbridge-utils 0.0.33 → 0.0.35

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 CHANGED
@@ -24,7 +24,9 @@ __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");
@@ -176,6 +178,15 @@ var cost = {
176
178
  }
177
179
  };
178
180
  var MARKUP = 1.3;
181
+ var toolCost = {
182
+ search: 3.5
183
+ };
184
+ var toolPricing = Object.fromEntries(
185
+ Object.entries(toolCost).map(([tool, value]) => [
186
+ tool,
187
+ Math.ceil(value * MARKUP)
188
+ ])
189
+ );
179
190
  var pricing = Object.fromEntries(
180
191
  Object.entries(cost).map(([model, rates]) => [
181
192
  model,
@@ -187,35 +198,47 @@ var pricing = Object.fromEntries(
187
198
  ])
188
199
  );
189
200
  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
201
+ (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
202
  );
192
- var priceForRequest = (model, usage) => {
203
+ var sumToolCents = (table, tools) => Object.entries(tools || {}).reduce(
204
+ (total, [name, used]) => total + (used && table[name] ? table[name] : 0),
205
+ 0
206
+ );
207
+ var priceForRequest = (model, usage, tools) => {
193
208
  const rates = pricing[model];
194
209
  if (!rates) {
195
210
  throw new Error(`Unknown model for pricing: ${model}`);
196
211
  }
197
- return sumCents(rates, usage);
212
+ return sumCents(rates, usage) + Math.ceil(sumToolCents(toolPricing, tools));
198
213
  };
199
- var costForRequest = (model, usage) => {
214
+ var costForRequest = (model, usage, tools) => {
200
215
  const rates = cost[model];
201
216
  if (!rates) {
202
217
  throw new Error(`Unknown model for cost: ${model}`);
203
218
  }
204
- return sumCents(rates, usage);
219
+ return sumCents(rates, usage) + Math.ceil(sumToolCents(toolCost, tools));
205
220
  };
206
221
  var tokensFromMetadata = (metadata) => ({
207
222
  cached: Number(metadata == null ? void 0 : metadata.cachedContentTokenCount) || 0,
208
223
  input: Number(metadata == null ? void 0 : metadata.promptTokenCount) || 0,
209
- output: Number(metadata == null ? void 0 : metadata.candidatesTokenCount) || 0
224
+ output: Number(metadata == null ? void 0 : metadata.candidatesTokenCount) || 0,
225
+ thinking: Number(metadata == null ? void 0 : metadata.thoughtsTokenCount) || 0
210
226
  });
227
+ var toolsFromResponse = (response) => {
228
+ var _a, _b;
229
+ return {
230
+ search: Boolean((_b = (_a = response == null ? void 0 : response.candidates) == null ? void 0 : _a[0]) == null ? void 0 : _b.groundingMetadata)
231
+ };
232
+ };
211
233
  var billRequest = async ({
212
234
  db,
213
235
  user,
214
236
  model,
215
- usage
237
+ usage,
238
+ tools
216
239
  }) => {
217
- const gross = priceForRequest(model, usage);
218
- const cogs = costForRequest(model, usage);
240
+ const gross = priceForRequest(model, usage, tools);
241
+ const cogs = costForRequest(model, usage, tools);
219
242
  await debit({
220
243
  db,
221
244
  user,
@@ -227,6 +250,7 @@ var billRequest = async ({
227
250
  name: "google",
228
251
  model,
229
252
  tokens: usage,
253
+ tools,
230
254
  rates: {
231
255
  wholesale: cost[model],
232
256
  retail: pricing[model]
@@ -271,7 +295,8 @@ var google = {
271
295
  db,
272
296
  user,
273
297
  model,
274
- usage: tokensFromMetadata(response == null ? void 0 : response.usageMetadata)
298
+ usage: tokensFromMetadata(response == null ? void 0 : response.usageMetadata),
299
+ tools: toolsFromResponse(response)
275
300
  });
276
301
  }
277
302
  return { content };
@@ -301,7 +326,8 @@ var google = {
301
326
  db,
302
327
  user,
303
328
  model,
304
- usage: tokensFromMetadata(response == null ? void 0 : response.usageMetadata)
329
+ usage: tokensFromMetadata(response == null ? void 0 : response.usageMetadata),
330
+ tools: toolsFromResponse(response)
305
331
  });
306
332
  }
307
333
  return { content };
@@ -314,5 +340,7 @@ var google = {
314
340
  costForRequest,
315
341
  google,
316
342
  priceForRequest,
317
- pricing
343
+ pricing,
344
+ toolCost,
345
+ toolPricing
318
346
  });
package/dist/ai.d.cts CHANGED
@@ -61,6 +61,22 @@ const cost = {
61
61
  // refactor later if tier-based pricing becomes a real need.
62
62
  const MARKUP = 1.3;
63
63
 
64
+ // Flat per-request fees Google charges on top of tokens when specific tools
65
+ // fire. Search grounding is $35/1k requests → 3.5 cents/request. Billed only
66
+ // when the response actually used the tool (response.candidates[0].
67
+ // groundingMetadata is populated) — enabling the tool in config without it
68
+ // being invoked is free. Add more tools here as Google introduces them.
69
+ const toolCost = {
70
+ search : 3.5
71
+ };
72
+
73
+ const toolPricing = Object.fromEntries(
74
+ Object.entries( toolCost ).map( ( [ tool, value ] ) => [
75
+ tool,
76
+ Math.ceil( value * MARKUP )
77
+ ] )
78
+ );
79
+
64
80
  // Derived retail pricing in cents per 1,000,000 tokens — what users pay.
65
81
  // Rounded so the per-1M rate is a whole-cent value; per-request rounding
66
82
  // happens in priceForRequest via Math.ceil so we never under-bill.
@@ -75,17 +91,33 @@ const pricing = Object.fromEntries(
75
91
  ] )
76
92
  );
77
93
 
94
+ // Gemini 2.5 emits internal reasoning ("thinking") tokens alongside the
95
+ // visible response. Google bills them at the standard output rate, so they
96
+ // roll into the same multiplier here even though they live as a distinct
97
+ // field on the tokens object for visibility / margin analysis.
78
98
  const sumCents = ( rates, tokens ) => Math.ceil(
79
99
  (
80
100
  Number( tokens?.cached || 0 ) * rates.cached +
81
101
  Number( tokens?.input || 0 ) * rates.input +
82
- Number( tokens?.output || 0 ) * rates.output
102
+ (
103
+ Number( tokens?.output || 0 ) +
104
+ Number( tokens?.thinking || 0 )
105
+ ) * rates.output
83
106
  ) / 1_000_000
84
107
  );
85
108
 
109
+ // Sum the per-request flat fees for the tools that actually fired. `tools`
110
+ // is the same { search: true } shape stamped on the transaction row, so the
111
+ // row's `tools` object and the amount it contributes are recomputable from
112
+ // the same source of truth.
113
+ const sumToolCents = ( table, tools ) => Object.entries( tools || {} ).reduce(
114
+ ( total, [ name, used ] ) => total + ( used && table[ name ] ? table[ name ] : 0 ),
115
+ 0
116
+ );
117
+
86
118
  // What we charge the user, rounded up so we never under-bill. Throws on an
87
119
  // unknown model so a typo doesn't silently fall back to a wrong price.
88
- const priceForRequest = ( model, usage ) => {
120
+ const priceForRequest = ( model, usage, tools ) => {
89
121
 
90
122
  const rates = pricing[ model ];
91
123
 
@@ -95,13 +127,13 @@ const priceForRequest = ( model, usage ) => {
95
127
 
96
128
  }
97
129
 
98
- return sumCents( rates, usage );
130
+ return sumCents( rates, usage ) + Math.ceil( sumToolCents( toolPricing, tools ) );
99
131
 
100
132
  };
101
133
 
102
134
  // What we pay the provider. Recorded on each transaction row so margin is
103
135
  // computable per request without backfilling from git history.
104
- const costForRequest = ( model, usage ) => {
136
+ const costForRequest = ( model, usage, tools ) => {
105
137
 
106
138
  const rates = cost[ model ];
107
139
 
@@ -111,15 +143,27 @@ const costForRequest = ( model, usage ) => {
111
143
 
112
144
  }
113
145
 
114
- return sumCents( rates, usage );
146
+ return sumCents( rates, usage ) + Math.ceil( sumToolCents( toolCost, tools ) );
115
147
 
116
148
  };
117
149
 
118
150
  // Map Google's usageMetadata shape into the pricing.tokens shape.
151
+ // `thinking` is Gemini 2.5's internal reasoning token count — billed at the
152
+ // same rate as `output` but tracked separately so the transaction row shows
153
+ // the visible-vs-reasoning split.
119
154
  const tokensFromMetadata = ( metadata ) => ({
120
155
  cached : Number( metadata?.cachedContentTokenCount ) || 0,
121
156
  input : Number( metadata?.promptTokenCount ) || 0,
122
- output : Number( metadata?.candidatesTokenCount ) || 0
157
+ output : Number( metadata?.candidatesTokenCount ) || 0,
158
+ thinking : Number( metadata?.thoughtsTokenCount ) || 0
159
+ });
160
+
161
+ // Derive which billable tools actually fired from the response. Google bills
162
+ // per-request when the tool was invoked, not when it was merely enabled in
163
+ // config — `groundingMetadata` is only present on candidates when search was
164
+ // used. Add more tools here as Google adds them.
165
+ const toolsFromResponse = ( response ) => ({
166
+ search : Boolean( response?.candidates?.[ 0 ]?.groundingMetadata )
123
167
  });
124
168
 
125
169
  // Compute gross + cost from response usage and atomically debit the user.
@@ -131,11 +175,12 @@ const billRequest = async ({
131
175
  db,
132
176
  user,
133
177
  model,
134
- usage
178
+ usage,
179
+ tools
135
180
  }) => {
136
181
 
137
- const gross = priceForRequest( model, usage );
138
- const cogs = costForRequest( model, usage );
182
+ const gross = priceForRequest( model, usage, tools );
183
+ const cogs = costForRequest( model, usage, tools );
139
184
 
140
185
  await debit({
141
186
  db,
@@ -148,6 +193,7 @@ const billRequest = async ({
148
193
  name : 'google',
149
194
  model,
150
195
  tokens : usage,
196
+ tools,
151
197
  rates : {
152
198
  wholesale : cost[ model ],
153
199
  retail : pricing[ model ]
@@ -206,7 +252,8 @@ const google = {
206
252
  db,
207
253
  user,
208
254
  model,
209
- usage : tokensFromMetadata( response?.usageMetadata )
255
+ usage : tokensFromMetadata( response?.usageMetadata ),
256
+ tools : toolsFromResponse( response )
210
257
  });
211
258
 
212
259
  }
@@ -251,7 +298,8 @@ const google = {
251
298
  db,
252
299
  user,
253
300
  model,
254
- usage : tokensFromMetadata( response?.usageMetadata )
301
+ usage : tokensFromMetadata( response?.usageMetadata ),
302
+ tools : toolsFromResponse( response )
255
303
  });
256
304
 
257
305
  }
@@ -262,4 +310,4 @@ const google = {
262
310
 
263
311
  };
264
312
 
265
- export { MARKUP, cost, costForRequest, google, priceForRequest, pricing };
313
+ export { MARKUP, cost, costForRequest, google, priceForRequest, pricing, toolCost, toolPricing };
package/dist/ai.d.ts CHANGED
@@ -61,6 +61,22 @@ const cost = {
61
61
  // refactor later if tier-based pricing becomes a real need.
62
62
  const MARKUP = 1.3;
63
63
 
64
+ // Flat per-request fees Google charges on top of tokens when specific tools
65
+ // fire. Search grounding is $35/1k requests → 3.5 cents/request. Billed only
66
+ // when the response actually used the tool (response.candidates[0].
67
+ // groundingMetadata is populated) — enabling the tool in config without it
68
+ // being invoked is free. Add more tools here as Google introduces them.
69
+ const toolCost = {
70
+ search : 3.5
71
+ };
72
+
73
+ const toolPricing = Object.fromEntries(
74
+ Object.entries( toolCost ).map( ( [ tool, value ] ) => [
75
+ tool,
76
+ Math.ceil( value * MARKUP )
77
+ ] )
78
+ );
79
+
64
80
  // Derived retail pricing in cents per 1,000,000 tokens — what users pay.
65
81
  // Rounded so the per-1M rate is a whole-cent value; per-request rounding
66
82
  // happens in priceForRequest via Math.ceil so we never under-bill.
@@ -75,17 +91,33 @@ const pricing = Object.fromEntries(
75
91
  ] )
76
92
  );
77
93
 
94
+ // Gemini 2.5 emits internal reasoning ("thinking") tokens alongside the
95
+ // visible response. Google bills them at the standard output rate, so they
96
+ // roll into the same multiplier here even though they live as a distinct
97
+ // field on the tokens object for visibility / margin analysis.
78
98
  const sumCents = ( rates, tokens ) => Math.ceil(
79
99
  (
80
100
  Number( tokens?.cached || 0 ) * rates.cached +
81
101
  Number( tokens?.input || 0 ) * rates.input +
82
- Number( tokens?.output || 0 ) * rates.output
102
+ (
103
+ Number( tokens?.output || 0 ) +
104
+ Number( tokens?.thinking || 0 )
105
+ ) * rates.output
83
106
  ) / 1_000_000
84
107
  );
85
108
 
109
+ // Sum the per-request flat fees for the tools that actually fired. `tools`
110
+ // is the same { search: true } shape stamped on the transaction row, so the
111
+ // row's `tools` object and the amount it contributes are recomputable from
112
+ // the same source of truth.
113
+ const sumToolCents = ( table, tools ) => Object.entries( tools || {} ).reduce(
114
+ ( total, [ name, used ] ) => total + ( used && table[ name ] ? table[ name ] : 0 ),
115
+ 0
116
+ );
117
+
86
118
  // What we charge the user, rounded up so we never under-bill. Throws on an
87
119
  // unknown model so a typo doesn't silently fall back to a wrong price.
88
- const priceForRequest = ( model, usage ) => {
120
+ const priceForRequest = ( model, usage, tools ) => {
89
121
 
90
122
  const rates = pricing[ model ];
91
123
 
@@ -95,13 +127,13 @@ const priceForRequest = ( model, usage ) => {
95
127
 
96
128
  }
97
129
 
98
- return sumCents( rates, usage );
130
+ return sumCents( rates, usage ) + Math.ceil( sumToolCents( toolPricing, tools ) );
99
131
 
100
132
  };
101
133
 
102
134
  // What we pay the provider. Recorded on each transaction row so margin is
103
135
  // computable per request without backfilling from git history.
104
- const costForRequest = ( model, usage ) => {
136
+ const costForRequest = ( model, usage, tools ) => {
105
137
 
106
138
  const rates = cost[ model ];
107
139
 
@@ -111,15 +143,27 @@ const costForRequest = ( model, usage ) => {
111
143
 
112
144
  }
113
145
 
114
- return sumCents( rates, usage );
146
+ return sumCents( rates, usage ) + Math.ceil( sumToolCents( toolCost, tools ) );
115
147
 
116
148
  };
117
149
 
118
150
  // Map Google's usageMetadata shape into the pricing.tokens shape.
151
+ // `thinking` is Gemini 2.5's internal reasoning token count — billed at the
152
+ // same rate as `output` but tracked separately so the transaction row shows
153
+ // the visible-vs-reasoning split.
119
154
  const tokensFromMetadata = ( metadata ) => ({
120
155
  cached : Number( metadata?.cachedContentTokenCount ) || 0,
121
156
  input : Number( metadata?.promptTokenCount ) || 0,
122
- output : Number( metadata?.candidatesTokenCount ) || 0
157
+ output : Number( metadata?.candidatesTokenCount ) || 0,
158
+ thinking : Number( metadata?.thoughtsTokenCount ) || 0
159
+ });
160
+
161
+ // Derive which billable tools actually fired from the response. Google bills
162
+ // per-request when the tool was invoked, not when it was merely enabled in
163
+ // config — `groundingMetadata` is only present on candidates when search was
164
+ // used. Add more tools here as Google adds them.
165
+ const toolsFromResponse = ( response ) => ({
166
+ search : Boolean( response?.candidates?.[ 0 ]?.groundingMetadata )
123
167
  });
124
168
 
125
169
  // Compute gross + cost from response usage and atomically debit the user.
@@ -131,11 +175,12 @@ const billRequest = async ({
131
175
  db,
132
176
  user,
133
177
  model,
134
- usage
178
+ usage,
179
+ tools
135
180
  }) => {
136
181
 
137
- const gross = priceForRequest( model, usage );
138
- const cogs = costForRequest( model, usage );
182
+ const gross = priceForRequest( model, usage, tools );
183
+ const cogs = costForRequest( model, usage, tools );
139
184
 
140
185
  await debit({
141
186
  db,
@@ -148,6 +193,7 @@ const billRequest = async ({
148
193
  name : 'google',
149
194
  model,
150
195
  tokens : usage,
196
+ tools,
151
197
  rates : {
152
198
  wholesale : cost[ model ],
153
199
  retail : pricing[ model ]
@@ -206,7 +252,8 @@ const google = {
206
252
  db,
207
253
  user,
208
254
  model,
209
- usage : tokensFromMetadata( response?.usageMetadata )
255
+ usage : tokensFromMetadata( response?.usageMetadata ),
256
+ tools : toolsFromResponse( response )
210
257
  });
211
258
 
212
259
  }
@@ -251,7 +298,8 @@ const google = {
251
298
  db,
252
299
  user,
253
300
  model,
254
- usage : tokensFromMetadata( response?.usageMetadata )
301
+ usage : tokensFromMetadata( response?.usageMetadata ),
302
+ tools : toolsFromResponse( response )
255
303
  });
256
304
 
257
305
  }
@@ -262,4 +310,4 @@ const google = {
262
310
 
263
311
  };
264
312
 
265
- export { MARKUP, cost, costForRequest, google, priceForRequest, pricing };
313
+ export { MARKUP, cost, costForRequest, google, priceForRequest, pricing, toolCost, toolPricing };
package/dist/ai.js CHANGED
@@ -32,6 +32,15 @@ var cost = {
32
32
  }
33
33
  };
34
34
  var MARKUP = 1.3;
35
+ var toolCost = {
36
+ search: 3.5
37
+ };
38
+ var toolPricing = Object.fromEntries(
39
+ Object.entries(toolCost).map(([tool, value]) => [
40
+ tool,
41
+ Math.ceil(value * MARKUP)
42
+ ])
43
+ );
35
44
  var pricing = Object.fromEntries(
36
45
  Object.entries(cost).map(([model, rates]) => [
37
46
  model,
@@ -43,35 +52,47 @@ var pricing = Object.fromEntries(
43
52
  ])
44
53
  );
45
54
  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
55
+ (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
56
  );
48
- var priceForRequest = (model, usage) => {
57
+ var sumToolCents = (table, tools) => Object.entries(tools || {}).reduce(
58
+ (total, [name, used]) => total + (used && table[name] ? table[name] : 0),
59
+ 0
60
+ );
61
+ var priceForRequest = (model, usage, tools) => {
49
62
  const rates = pricing[model];
50
63
  if (!rates) {
51
64
  throw new Error(`Unknown model for pricing: ${model}`);
52
65
  }
53
- return sumCents(rates, usage);
66
+ return sumCents(rates, usage) + Math.ceil(sumToolCents(toolPricing, tools));
54
67
  };
55
- var costForRequest = (model, usage) => {
68
+ var costForRequest = (model, usage, tools) => {
56
69
  const rates = cost[model];
57
70
  if (!rates) {
58
71
  throw new Error(`Unknown model for cost: ${model}`);
59
72
  }
60
- return sumCents(rates, usage);
73
+ return sumCents(rates, usage) + Math.ceil(sumToolCents(toolCost, tools));
61
74
  };
62
75
  var tokensFromMetadata = (metadata) => ({
63
76
  cached: Number(metadata == null ? void 0 : metadata.cachedContentTokenCount) || 0,
64
77
  input: Number(metadata == null ? void 0 : metadata.promptTokenCount) || 0,
65
- output: Number(metadata == null ? void 0 : metadata.candidatesTokenCount) || 0
78
+ output: Number(metadata == null ? void 0 : metadata.candidatesTokenCount) || 0,
79
+ thinking: Number(metadata == null ? void 0 : metadata.thoughtsTokenCount) || 0
66
80
  });
81
+ var toolsFromResponse = (response) => {
82
+ var _a, _b;
83
+ return {
84
+ search: Boolean((_b = (_a = response == null ? void 0 : response.candidates) == null ? void 0 : _a[0]) == null ? void 0 : _b.groundingMetadata)
85
+ };
86
+ };
67
87
  var billRequest = async ({
68
88
  db,
69
89
  user,
70
90
  model,
71
- usage
91
+ usage,
92
+ tools
72
93
  }) => {
73
- const gross = priceForRequest(model, usage);
74
- const cogs = costForRequest(model, usage);
94
+ const gross = priceForRequest(model, usage, tools);
95
+ const cogs = costForRequest(model, usage, tools);
75
96
  await debit({
76
97
  db,
77
98
  user,
@@ -83,6 +104,7 @@ var billRequest = async ({
83
104
  name: "google",
84
105
  model,
85
106
  tokens: usage,
107
+ tools,
86
108
  rates: {
87
109
  wholesale: cost[model],
88
110
  retail: pricing[model]
@@ -127,7 +149,8 @@ var google = {
127
149
  db,
128
150
  user,
129
151
  model,
130
- usage: tokensFromMetadata(response == null ? void 0 : response.usageMetadata)
152
+ usage: tokensFromMetadata(response == null ? void 0 : response.usageMetadata),
153
+ tools: toolsFromResponse(response)
131
154
  });
132
155
  }
133
156
  return { content };
@@ -157,7 +180,8 @@ var google = {
157
180
  db,
158
181
  user,
159
182
  model,
160
- usage: tokensFromMetadata(response == null ? void 0 : response.usageMetadata)
183
+ usage: tokensFromMetadata(response == null ? void 0 : response.usageMetadata),
184
+ tools: toolsFromResponse(response)
161
185
  });
162
186
  }
163
187
  return { content };
@@ -169,5 +193,7 @@ export {
169
193
  costForRequest,
170
194
  google,
171
195
  priceForRequest,
172
- pricing
196
+ pricing,
197
+ toolCost,
198
+ toolPricing
173
199
  };
package/package.json CHANGED
@@ -103,5 +103,5 @@
103
103
  "build": "tsup && npm publish"
104
104
  },
105
105
  "types": "dist/index.d.ts",
106
- "version": "0.0.33"
106
+ "version": "0.0.35"
107
107
  }