@drawbridge/drawbridge-utils 0.0.60 → 0.0.62
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 +3 -2
- package/dist/ai.d.cts +14 -7
- package/dist/ai.d.ts +14 -7
- package/dist/ai.js +3 -2
- package/dist/index.cjs +28 -0
- package/dist/index.d.cts +58 -1
- package/dist/index.d.ts +58 -1
- package/dist/index.js +26 -0
- package/package.json +1 -4
package/dist/ai.cjs
CHANGED
|
@@ -157,7 +157,7 @@ var debit = async ({
|
|
|
157
157
|
|
|
158
158
|
// lib/ai.js
|
|
159
159
|
var google_client = new import_genai.GoogleGenAI({
|
|
160
|
-
apiKey: process.env.
|
|
160
|
+
apiKey: process.env.GOOGLE_GEMINI_API_KEY
|
|
161
161
|
});
|
|
162
162
|
var googleCircuit = circuit({
|
|
163
163
|
name: "google-ai",
|
|
@@ -303,13 +303,14 @@ var google = {
|
|
|
303
303
|
text: async ({
|
|
304
304
|
config = {},
|
|
305
305
|
images = [],
|
|
306
|
+
parts = null,
|
|
306
307
|
prompt = []
|
|
307
308
|
}, {
|
|
308
309
|
db,
|
|
309
310
|
user
|
|
310
311
|
}) => {
|
|
311
312
|
const model = models.text;
|
|
312
|
-
const contents = images.length ? { parts: [...images.map((image) => ({ inlineData: { data: image.data, mimeType: image.mimeType } })), { text: prompt.join("\n") }] } : prompt.join("\n");
|
|
313
|
+
const contents = parts ? { parts } : images.length ? { parts: [...images.map((image) => ({ inlineData: { data: image.data, mimeType: image.mimeType } })), { text: prompt.join("\n") }] } : prompt.join("\n");
|
|
313
314
|
const response = await googleCircuit(() => google_client.models.generateContent({
|
|
314
315
|
config: {
|
|
315
316
|
...config,
|
package/dist/ai.d.cts
CHANGED
|
@@ -23,7 +23,7 @@ import '@drawbridge/drawbridge-telemetry';
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
const google_client = new GoogleGenAI({
|
|
26
|
-
apiKey : process.env.
|
|
26
|
+
apiKey : process.env.GOOGLE_GEMINI_API_KEY
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
const googleCircuit = circuit({
|
|
@@ -266,6 +266,7 @@ const google = {
|
|
|
266
266
|
{
|
|
267
267
|
config = {},
|
|
268
268
|
images = [],
|
|
269
|
+
parts = null,
|
|
269
270
|
prompt = []
|
|
270
271
|
},
|
|
271
272
|
{
|
|
@@ -276,12 +277,18 @@ const google = {
|
|
|
276
277
|
|
|
277
278
|
const model = models.text;
|
|
278
279
|
|
|
279
|
-
//
|
|
280
|
-
// parts
|
|
281
|
-
//
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
280
|
+
// Three contents shapes, in priority order:
|
|
281
|
+
// 1. `parts` — a caller-built array of interleaved text/inlineData parts,
|
|
282
|
+
// used verbatim. For calls where ordering matters (e.g. the hero-pick
|
|
283
|
+
// interleaves "Image N:" labels with each thumbnail so the model can
|
|
284
|
+
// map an index to an image); images/prompt can't express that.
|
|
285
|
+
// 2. `images` — a screenshot etc. sent multimodally before the prompt.
|
|
286
|
+
// 3. plain joined string for text-only calls.
|
|
287
|
+
const contents = parts
|
|
288
|
+
? { parts }
|
|
289
|
+
: images.length
|
|
290
|
+
? { parts : [ ...images.map( ( image ) => ({ inlineData : { data : image.data, mimeType : image.mimeType } }) ), { text : prompt.join( '\n' ) } ] }
|
|
291
|
+
: prompt.join( '\n' );
|
|
285
292
|
|
|
286
293
|
const response = await googleCircuit( () => google_client.models.generateContent({
|
|
287
294
|
config : {
|
package/dist/ai.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ import '@drawbridge/drawbridge-telemetry';
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
const google_client = new GoogleGenAI({
|
|
26
|
-
apiKey : process.env.
|
|
26
|
+
apiKey : process.env.GOOGLE_GEMINI_API_KEY
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
const googleCircuit = circuit({
|
|
@@ -266,6 +266,7 @@ const google = {
|
|
|
266
266
|
{
|
|
267
267
|
config = {},
|
|
268
268
|
images = [],
|
|
269
|
+
parts = null,
|
|
269
270
|
prompt = []
|
|
270
271
|
},
|
|
271
272
|
{
|
|
@@ -276,12 +277,18 @@ const google = {
|
|
|
276
277
|
|
|
277
278
|
const model = models.text;
|
|
278
279
|
|
|
279
|
-
//
|
|
280
|
-
// parts
|
|
281
|
-
//
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
280
|
+
// Three contents shapes, in priority order:
|
|
281
|
+
// 1. `parts` — a caller-built array of interleaved text/inlineData parts,
|
|
282
|
+
// used verbatim. For calls where ordering matters (e.g. the hero-pick
|
|
283
|
+
// interleaves "Image N:" labels with each thumbnail so the model can
|
|
284
|
+
// map an index to an image); images/prompt can't express that.
|
|
285
|
+
// 2. `images` — a screenshot etc. sent multimodally before the prompt.
|
|
286
|
+
// 3. plain joined string for text-only calls.
|
|
287
|
+
const contents = parts
|
|
288
|
+
? { parts }
|
|
289
|
+
: images.length
|
|
290
|
+
? { parts : [ ...images.map( ( image ) => ({ inlineData : { data : image.data, mimeType : image.mimeType } }) ), { text : prompt.join( '\n' ) } ] }
|
|
291
|
+
: prompt.join( '\n' );
|
|
285
292
|
|
|
286
293
|
const response = await googleCircuit( () => google_client.models.generateContent({
|
|
287
294
|
config : {
|
package/dist/ai.js
CHANGED
|
@@ -127,7 +127,7 @@ var debit = async ({
|
|
|
127
127
|
|
|
128
128
|
// lib/ai.js
|
|
129
129
|
var google_client = new GoogleGenAI({
|
|
130
|
-
apiKey: process.env.
|
|
130
|
+
apiKey: process.env.GOOGLE_GEMINI_API_KEY
|
|
131
131
|
});
|
|
132
132
|
var googleCircuit = circuit({
|
|
133
133
|
name: "google-ai",
|
|
@@ -273,13 +273,14 @@ var google = {
|
|
|
273
273
|
text: async ({
|
|
274
274
|
config = {},
|
|
275
275
|
images = [],
|
|
276
|
+
parts = null,
|
|
276
277
|
prompt = []
|
|
277
278
|
}, {
|
|
278
279
|
db,
|
|
279
280
|
user
|
|
280
281
|
}) => {
|
|
281
282
|
const model = models.text;
|
|
282
|
-
const contents = images.length ? { parts: [...images.map((image) => ({ inlineData: { data: image.data, mimeType: image.mimeType } })), { text: prompt.join("\n") }] } : prompt.join("\n");
|
|
283
|
+
const contents = parts ? { parts } : images.length ? { parts: [...images.map((image) => ({ inlineData: { data: image.data, mimeType: image.mimeType } })), { text: prompt.join("\n") }] } : prompt.join("\n");
|
|
283
284
|
const response = await googleCircuit(() => google_client.models.generateContent({
|
|
284
285
|
config: {
|
|
285
286
|
...config,
|
package/dist/index.cjs
CHANGED
|
@@ -43,10 +43,12 @@ __export(index_exports, {
|
|
|
43
43
|
gigabyte: () => gigabyte,
|
|
44
44
|
incrementUsageTotals: () => incrementUsageTotals,
|
|
45
45
|
infinite: () => infinite,
|
|
46
|
+
isDuplicateKey: () => isDuplicateKey,
|
|
46
47
|
isInfinite: () => isInfinite,
|
|
47
48
|
megabyte: () => megabyte,
|
|
48
49
|
nanoid: () => nanoid,
|
|
49
50
|
percentage: () => percentage,
|
|
51
|
+
recordAction: () => recordAction,
|
|
50
52
|
reducers: () => reducers,
|
|
51
53
|
regex: () => regex,
|
|
52
54
|
shareUrls: () => shareUrls,
|
|
@@ -390,6 +392,30 @@ var incrementUsageTotals = async ({ controller, usageId, $inc, session }) => {
|
|
|
390
392
|
query: { id: usageId }
|
|
391
393
|
});
|
|
392
394
|
};
|
|
395
|
+
var isDuplicateKey = (error) => (error == null ? void 0 : error.code) === 11e3 || (error == null ? void 0 : error.code) === "11000" || /E11000|duplicate key/i.test((error == null ? void 0 : error.message) || "");
|
|
396
|
+
var recordAction = async ({ controller, organization, usageId, type, units = 1, key, meta = {}, traceId }) => {
|
|
397
|
+
if (!controller || !organization || !usageId || !type || !key) return;
|
|
398
|
+
const amount = Number(units);
|
|
399
|
+
if (!Number.isFinite(amount) || amount === 0) return;
|
|
400
|
+
try {
|
|
401
|
+
await controller.create({
|
|
402
|
+
collection: "action",
|
|
403
|
+
data: {
|
|
404
|
+
organization,
|
|
405
|
+
usage: usageId,
|
|
406
|
+
type,
|
|
407
|
+
units: amount,
|
|
408
|
+
key,
|
|
409
|
+
meta,
|
|
410
|
+
...traceId && { traceId }
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
} catch (error) {
|
|
414
|
+
if (isDuplicateKey(error)) return;
|
|
415
|
+
throw error;
|
|
416
|
+
}
|
|
417
|
+
await incrementUsageTotals({ controller, usageId, $inc: { "totals.actions": amount } });
|
|
418
|
+
};
|
|
393
419
|
var percentage = (value1, value2, decimals = 1) => ((value1 || 0) / (value2 || 0) * 100 || 0).toFixed(decimals);
|
|
394
420
|
var reducers = {
|
|
395
421
|
fields: (data2 = [], callback = () => ({})) => data2.reduce(
|
|
@@ -483,10 +509,12 @@ var currency = (val) => (0, import_currency_codes.code)(val);
|
|
|
483
509
|
gigabyte,
|
|
484
510
|
incrementUsageTotals,
|
|
485
511
|
infinite,
|
|
512
|
+
isDuplicateKey,
|
|
486
513
|
isInfinite,
|
|
487
514
|
megabyte,
|
|
488
515
|
nanoid,
|
|
489
516
|
percentage,
|
|
517
|
+
recordAction,
|
|
490
518
|
reducers,
|
|
491
519
|
regex,
|
|
492
520
|
shareUrls,
|
package/dist/index.d.cts
CHANGED
|
@@ -379,6 +379,63 @@ const incrementUsageTotals = async ({ controller, usageId, $inc, session }) => {
|
|
|
379
379
|
|
|
380
380
|
};
|
|
381
381
|
|
|
382
|
+
// True for a MongoDB duplicate-key error (unique-index violation).
|
|
383
|
+
const isDuplicateKey = ( error ) =>
|
|
384
|
+
error?.code === 11000
|
|
385
|
+
|| error?.code === '11000'
|
|
386
|
+
|| /E11000|duplicate key/i.test( error?.message || '' );
|
|
387
|
+
|
|
388
|
+
// Record a billable action as a row in the append-only `action` ledger — the
|
|
389
|
+
// source of truth for usage billing (summed per period at changeover). The unique
|
|
390
|
+
// `key` makes recording idempotent: a duplicate (the segment realtime path AND the
|
|
391
|
+
// sweep firing for the same membership, or a job retry) hits the unique index and
|
|
392
|
+
// is skipped, so it can never double-count. On a genuinely new row we bump the
|
|
393
|
+
// display cache `usage.totals.actions` by the same units; the billed number always
|
|
394
|
+
// comes from summing the ledger, so a missed cache $inc is only cosmetic (it
|
|
395
|
+
// self-heals at changeover). No transaction needed — the unique index is the
|
|
396
|
+
// atomic dedup.
|
|
397
|
+
// key deterministic per source event, e.g. 'segment.join:<contact>:<segment>'
|
|
398
|
+
// units billable units (default 1; N for batch, e.g. accepted entries)
|
|
399
|
+
// meta attribution ids { campaign, page, submission, segment, workflow, step, ... }
|
|
400
|
+
const recordAction = async ({ controller, organization, usageId, type, units = 1, key, meta = {}, traceId }) => {
|
|
401
|
+
|
|
402
|
+
if( ! controller || ! organization || ! usageId || ! type || ! key ) return;
|
|
403
|
+
|
|
404
|
+
const amount = Number( units );
|
|
405
|
+
|
|
406
|
+
if( ! Number.isFinite( amount ) || amount === 0 ) return;
|
|
407
|
+
|
|
408
|
+
try {
|
|
409
|
+
|
|
410
|
+
await controller.create({
|
|
411
|
+
collection : 'action',
|
|
412
|
+
data : {
|
|
413
|
+
organization,
|
|
414
|
+
usage : usageId,
|
|
415
|
+
type,
|
|
416
|
+
units : amount,
|
|
417
|
+
key,
|
|
418
|
+
meta,
|
|
419
|
+
...( traceId && { traceId })
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
} catch ( error ) {
|
|
424
|
+
|
|
425
|
+
// Duplicate key = this exact action was already recorded — skip silently.
|
|
426
|
+
// This is the dedup that makes billing drift-proof. Re-throw anything else.
|
|
427
|
+
if( isDuplicateKey( error ) ) return;
|
|
428
|
+
|
|
429
|
+
throw error;
|
|
430
|
+
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// New row only — keep the display cache in step. Billing sums the ledger, so
|
|
434
|
+
// this is cosmetic and self-heals at changeover if it ever fails.
|
|
435
|
+
await incrementUsageTotals({ controller, usageId, $inc : { 'totals.actions' : amount } });
|
|
436
|
+
|
|
437
|
+
};
|
|
438
|
+
|
|
382
439
|
const percentage = ( value1, value2, decimals = 1 ) => ( ( ( ( value1 || 0 ) / ( value2 || 0 ) ) * 100 ) || 0 ).toFixed( decimals );
|
|
383
440
|
|
|
384
441
|
const reducers = {
|
|
@@ -477,4 +534,4 @@ const currencies = data.map( ( item ) => ({
|
|
|
477
534
|
|
|
478
535
|
const currency = ( val ) => code( val );
|
|
479
536
|
|
|
480
|
-
export { bytesToGB, bytesToMB, capitalize, constants, currencies, currency, expiredPaymentMethod, formatCurrency, formatDateString, formatNumber, getPlanFeature, gigabyte, incrementUsageTotals, infinite, isInfinite, megabyte, nanoid, percentage, reducers, regex, shareUrls, urlRoot };
|
|
537
|
+
export { bytesToGB, bytesToMB, capitalize, constants, currencies, currency, expiredPaymentMethod, formatCurrency, formatDateString, formatNumber, getPlanFeature, gigabyte, incrementUsageTotals, infinite, isDuplicateKey, isInfinite, megabyte, nanoid, percentage, recordAction, reducers, regex, shareUrls, urlRoot };
|
package/dist/index.d.ts
CHANGED
|
@@ -379,6 +379,63 @@ const incrementUsageTotals = async ({ controller, usageId, $inc, session }) => {
|
|
|
379
379
|
|
|
380
380
|
};
|
|
381
381
|
|
|
382
|
+
// True for a MongoDB duplicate-key error (unique-index violation).
|
|
383
|
+
const isDuplicateKey = ( error ) =>
|
|
384
|
+
error?.code === 11000
|
|
385
|
+
|| error?.code === '11000'
|
|
386
|
+
|| /E11000|duplicate key/i.test( error?.message || '' );
|
|
387
|
+
|
|
388
|
+
// Record a billable action as a row in the append-only `action` ledger — the
|
|
389
|
+
// source of truth for usage billing (summed per period at changeover). The unique
|
|
390
|
+
// `key` makes recording idempotent: a duplicate (the segment realtime path AND the
|
|
391
|
+
// sweep firing for the same membership, or a job retry) hits the unique index and
|
|
392
|
+
// is skipped, so it can never double-count. On a genuinely new row we bump the
|
|
393
|
+
// display cache `usage.totals.actions` by the same units; the billed number always
|
|
394
|
+
// comes from summing the ledger, so a missed cache $inc is only cosmetic (it
|
|
395
|
+
// self-heals at changeover). No transaction needed — the unique index is the
|
|
396
|
+
// atomic dedup.
|
|
397
|
+
// key deterministic per source event, e.g. 'segment.join:<contact>:<segment>'
|
|
398
|
+
// units billable units (default 1; N for batch, e.g. accepted entries)
|
|
399
|
+
// meta attribution ids { campaign, page, submission, segment, workflow, step, ... }
|
|
400
|
+
const recordAction = async ({ controller, organization, usageId, type, units = 1, key, meta = {}, traceId }) => {
|
|
401
|
+
|
|
402
|
+
if( ! controller || ! organization || ! usageId || ! type || ! key ) return;
|
|
403
|
+
|
|
404
|
+
const amount = Number( units );
|
|
405
|
+
|
|
406
|
+
if( ! Number.isFinite( amount ) || amount === 0 ) return;
|
|
407
|
+
|
|
408
|
+
try {
|
|
409
|
+
|
|
410
|
+
await controller.create({
|
|
411
|
+
collection : 'action',
|
|
412
|
+
data : {
|
|
413
|
+
organization,
|
|
414
|
+
usage : usageId,
|
|
415
|
+
type,
|
|
416
|
+
units : amount,
|
|
417
|
+
key,
|
|
418
|
+
meta,
|
|
419
|
+
...( traceId && { traceId })
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
} catch ( error ) {
|
|
424
|
+
|
|
425
|
+
// Duplicate key = this exact action was already recorded — skip silently.
|
|
426
|
+
// This is the dedup that makes billing drift-proof. Re-throw anything else.
|
|
427
|
+
if( isDuplicateKey( error ) ) return;
|
|
428
|
+
|
|
429
|
+
throw error;
|
|
430
|
+
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// New row only — keep the display cache in step. Billing sums the ledger, so
|
|
434
|
+
// this is cosmetic and self-heals at changeover if it ever fails.
|
|
435
|
+
await incrementUsageTotals({ controller, usageId, $inc : { 'totals.actions' : amount } });
|
|
436
|
+
|
|
437
|
+
};
|
|
438
|
+
|
|
382
439
|
const percentage = ( value1, value2, decimals = 1 ) => ( ( ( ( value1 || 0 ) / ( value2 || 0 ) ) * 100 ) || 0 ).toFixed( decimals );
|
|
383
440
|
|
|
384
441
|
const reducers = {
|
|
@@ -477,4 +534,4 @@ const currencies = data.map( ( item ) => ({
|
|
|
477
534
|
|
|
478
535
|
const currency = ( val ) => code( val );
|
|
479
536
|
|
|
480
|
-
export { bytesToGB, bytesToMB, capitalize, constants, currencies, currency, expiredPaymentMethod, formatCurrency, formatDateString, formatNumber, getPlanFeature, gigabyte, incrementUsageTotals, infinite, isInfinite, megabyte, nanoid, percentage, reducers, regex, shareUrls, urlRoot };
|
|
537
|
+
export { bytesToGB, bytesToMB, capitalize, constants, currencies, currency, expiredPaymentMethod, formatCurrency, formatDateString, formatNumber, getPlanFeature, gigabyte, incrementUsageTotals, infinite, isDuplicateKey, isInfinite, megabyte, nanoid, percentage, recordAction, reducers, regex, shareUrls, urlRoot };
|
package/dist/index.js
CHANGED
|
@@ -336,6 +336,30 @@ var incrementUsageTotals = async ({ controller, usageId, $inc, session }) => {
|
|
|
336
336
|
query: { id: usageId }
|
|
337
337
|
});
|
|
338
338
|
};
|
|
339
|
+
var isDuplicateKey = (error) => (error == null ? void 0 : error.code) === 11e3 || (error == null ? void 0 : error.code) === "11000" || /E11000|duplicate key/i.test((error == null ? void 0 : error.message) || "");
|
|
340
|
+
var recordAction = async ({ controller, organization, usageId, type, units = 1, key, meta = {}, traceId }) => {
|
|
341
|
+
if (!controller || !organization || !usageId || !type || !key) return;
|
|
342
|
+
const amount = Number(units);
|
|
343
|
+
if (!Number.isFinite(amount) || amount === 0) return;
|
|
344
|
+
try {
|
|
345
|
+
await controller.create({
|
|
346
|
+
collection: "action",
|
|
347
|
+
data: {
|
|
348
|
+
organization,
|
|
349
|
+
usage: usageId,
|
|
350
|
+
type,
|
|
351
|
+
units: amount,
|
|
352
|
+
key,
|
|
353
|
+
meta,
|
|
354
|
+
...traceId && { traceId }
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
} catch (error) {
|
|
358
|
+
if (isDuplicateKey(error)) return;
|
|
359
|
+
throw error;
|
|
360
|
+
}
|
|
361
|
+
await incrementUsageTotals({ controller, usageId, $inc: { "totals.actions": amount } });
|
|
362
|
+
};
|
|
339
363
|
var percentage = (value1, value2, decimals = 1) => ((value1 || 0) / (value2 || 0) * 100 || 0).toFixed(decimals);
|
|
340
364
|
var reducers = {
|
|
341
365
|
fields: (data2 = [], callback = () => ({})) => data2.reduce(
|
|
@@ -428,10 +452,12 @@ export {
|
|
|
428
452
|
gigabyte,
|
|
429
453
|
incrementUsageTotals,
|
|
430
454
|
infinite,
|
|
455
|
+
isDuplicateKey,
|
|
431
456
|
isInfinite,
|
|
432
457
|
megabyte,
|
|
433
458
|
nanoid,
|
|
434
459
|
percentage,
|
|
460
|
+
recordAction,
|
|
435
461
|
reducers,
|
|
436
462
|
regex,
|
|
437
463
|
shareUrls,
|
package/package.json
CHANGED
|
@@ -17,9 +17,6 @@
|
|
|
17
17
|
"tsup": "8.5.1",
|
|
18
18
|
"typescript": "5.9.3"
|
|
19
19
|
},
|
|
20
|
-
"overrides": {
|
|
21
|
-
"esbuild": "0.28.1"
|
|
22
|
-
},
|
|
23
20
|
"peerDependencies": {
|
|
24
21
|
"@node-oauth/oauth2-server": "^5.3.0"
|
|
25
22
|
},
|
|
@@ -160,5 +157,5 @@
|
|
|
160
157
|
"build": "tsup && npm publish"
|
|
161
158
|
},
|
|
162
159
|
"types": "dist/index.d.ts",
|
|
163
|
-
"version": "0.0.
|
|
160
|
+
"version": "0.0.62"
|
|
164
161
|
}
|