@drawbridge/drawbridge-utils 0.0.135 → 0.0.136
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/connections/index.cjs +149 -101
- package/dist/connections/index.d.cts +67 -13
- package/dist/connections/index.d.ts +67 -13
- package/dist/connections/index.js +149 -101
- package/dist/pricing.cjs +727 -695
- package/dist/pricing.d.cts +53 -5
- package/dist/pricing.d.ts +53 -5
- package/dist/pricing.js +726 -695
- package/dist/providers.cjs +149 -101
- package/dist/providers.d.cts +4 -4
- package/dist/providers.d.ts +4 -4
- package/dist/providers.js +149 -101
- package/package.json +2 -2
package/dist/providers.js
CHANGED
|
@@ -987,6 +987,74 @@ var drawbridge_default = `<svg width="500" height="500" viewBox="0 0 500 500" fi
|
|
|
987
987
|
</defs>
|
|
988
988
|
</svg>`;
|
|
989
989
|
|
|
990
|
+
// lib/transactions.js
|
|
991
|
+
import { currentTraceId } from "@drawbridge/drawbridge-telemetry";
|
|
992
|
+
|
|
993
|
+
// lib/billing.js
|
|
994
|
+
import { createLogger } from "@drawbridge/drawbridge-telemetry";
|
|
995
|
+
var logger = createLogger();
|
|
996
|
+
var MARKUP = 1.3;
|
|
997
|
+
var cost = {
|
|
998
|
+
// gemini-3.5-flash — verified against Google's pricing page 2026-07-09:
|
|
999
|
+
// $0.15 cached / $1.50 input / $9.00 output per 1M tokens (thinking billed at
|
|
1000
|
+
// output). ~3.6x the retired 2.5-flash output rate.
|
|
1001
|
+
"gemini-3.5-flash": {
|
|
1002
|
+
cached: 15,
|
|
1003
|
+
input: 150,
|
|
1004
|
+
output: 900
|
|
1005
|
+
},
|
|
1006
|
+
// gemini-3.5-flash-lite — verified against Google's pricing page 2026-08-20:
|
|
1007
|
+
// $0.03 cached / $0.30 input / $2.50 output per 1M tokens (thinking billed at
|
|
1008
|
+
// output). A fifth of flash on input, ~a quarter on output. Growth's assistant
|
|
1009
|
+
// ranks the feed on this tier — one call per page — so its rows were the
|
|
1010
|
+
// unpriced ones until now.
|
|
1011
|
+
"gemini-3.5-flash-lite": {
|
|
1012
|
+
cached: 3,
|
|
1013
|
+
input: 30,
|
|
1014
|
+
output: 250
|
|
1015
|
+
},
|
|
1016
|
+
"gemini-2.5-flash": {
|
|
1017
|
+
cached: 3,
|
|
1018
|
+
input: 30,
|
|
1019
|
+
output: 250
|
|
1020
|
+
},
|
|
1021
|
+
"gemini-2.5-flash-image": {
|
|
1022
|
+
cached: 3,
|
|
1023
|
+
input: 30,
|
|
1024
|
+
output: 3e3
|
|
1025
|
+
},
|
|
1026
|
+
// gemini-3-pro-image-preview — verified against Google's pricing page
|
|
1027
|
+
// 2026-08-12: $2.00 input / $12.00 text output per 1M, and image output
|
|
1028
|
+
// tokens at ~$120/1M (a 1K-2K image is 1120 tokens = $0.134, a 4K image
|
|
1029
|
+
// 2000 tokens = $0.24). Encoded the flash-image way: one flat output rate
|
|
1030
|
+
// that reproduces the per-image price from the tokens usageMetadata
|
|
1031
|
+
// reports. Growth's hero generation runs this model today.
|
|
1032
|
+
"gemini-3-pro-image-preview": {
|
|
1033
|
+
cached: 20,
|
|
1034
|
+
input: 200,
|
|
1035
|
+
output: 12e3
|
|
1036
|
+
}
|
|
1037
|
+
};
|
|
1038
|
+
var toolCost = {
|
|
1039
|
+
search: 3.5
|
|
1040
|
+
};
|
|
1041
|
+
var toolPricing = Object.fromEntries(
|
|
1042
|
+
Object.entries(toolCost).map(([tool, value]) => [
|
|
1043
|
+
tool,
|
|
1044
|
+
Math.ceil(value * MARKUP)
|
|
1045
|
+
])
|
|
1046
|
+
);
|
|
1047
|
+
var pricing = Object.fromEntries(
|
|
1048
|
+
Object.entries(cost).map(([model, rates]) => [
|
|
1049
|
+
model,
|
|
1050
|
+
{
|
|
1051
|
+
cached: Math.round(rates.cached * MARKUP),
|
|
1052
|
+
input: Math.round(rates.input * MARKUP),
|
|
1053
|
+
output: Math.round(rates.output * MARKUP)
|
|
1054
|
+
}
|
|
1055
|
+
])
|
|
1056
|
+
);
|
|
1057
|
+
|
|
990
1058
|
// lib/features.js
|
|
991
1059
|
var page = {
|
|
992
1060
|
qrcode: {
|
|
@@ -1449,74 +1517,6 @@ var conversionRate = (subscription) => {
|
|
|
1449
1517
|
return ((_a = resolvePlan(subscription)) == null ? void 0 : _a.conversion) ?? free.conversion;
|
|
1450
1518
|
};
|
|
1451
1519
|
|
|
1452
|
-
// lib/transactions.js
|
|
1453
|
-
import { currentTraceId } from "@drawbridge/drawbridge-telemetry";
|
|
1454
|
-
|
|
1455
|
-
// lib/billing.js
|
|
1456
|
-
import { createLogger } from "@drawbridge/drawbridge-telemetry";
|
|
1457
|
-
var logger = createLogger();
|
|
1458
|
-
var MARKUP = 1.3;
|
|
1459
|
-
var cost = {
|
|
1460
|
-
// gemini-3.5-flash — verified against Google's pricing page 2026-07-09:
|
|
1461
|
-
// $0.15 cached / $1.50 input / $9.00 output per 1M tokens (thinking billed at
|
|
1462
|
-
// output). ~3.6x the retired 2.5-flash output rate.
|
|
1463
|
-
"gemini-3.5-flash": {
|
|
1464
|
-
cached: 15,
|
|
1465
|
-
input: 150,
|
|
1466
|
-
output: 900
|
|
1467
|
-
},
|
|
1468
|
-
// gemini-3.5-flash-lite — verified against Google's pricing page 2026-08-20:
|
|
1469
|
-
// $0.03 cached / $0.30 input / $2.50 output per 1M tokens (thinking billed at
|
|
1470
|
-
// output). A fifth of flash on input, ~a quarter on output. Growth's assistant
|
|
1471
|
-
// ranks the feed on this tier — one call per page — so its rows were the
|
|
1472
|
-
// unpriced ones until now.
|
|
1473
|
-
"gemini-3.5-flash-lite": {
|
|
1474
|
-
cached: 3,
|
|
1475
|
-
input: 30,
|
|
1476
|
-
output: 250
|
|
1477
|
-
},
|
|
1478
|
-
"gemini-2.5-flash": {
|
|
1479
|
-
cached: 3,
|
|
1480
|
-
input: 30,
|
|
1481
|
-
output: 250
|
|
1482
|
-
},
|
|
1483
|
-
"gemini-2.5-flash-image": {
|
|
1484
|
-
cached: 3,
|
|
1485
|
-
input: 30,
|
|
1486
|
-
output: 3e3
|
|
1487
|
-
},
|
|
1488
|
-
// gemini-3-pro-image-preview — verified against Google's pricing page
|
|
1489
|
-
// 2026-08-12: $2.00 input / $12.00 text output per 1M, and image output
|
|
1490
|
-
// tokens at ~$120/1M (a 1K-2K image is 1120 tokens = $0.134, a 4K image
|
|
1491
|
-
// 2000 tokens = $0.24). Encoded the flash-image way: one flat output rate
|
|
1492
|
-
// that reproduces the per-image price from the tokens usageMetadata
|
|
1493
|
-
// reports. Growth's hero generation runs this model today.
|
|
1494
|
-
"gemini-3-pro-image-preview": {
|
|
1495
|
-
cached: 20,
|
|
1496
|
-
input: 200,
|
|
1497
|
-
output: 12e3
|
|
1498
|
-
}
|
|
1499
|
-
};
|
|
1500
|
-
var toolCost = {
|
|
1501
|
-
search: 3.5
|
|
1502
|
-
};
|
|
1503
|
-
var toolPricing = Object.fromEntries(
|
|
1504
|
-
Object.entries(toolCost).map(([tool, value]) => [
|
|
1505
|
-
tool,
|
|
1506
|
-
Math.ceil(value * MARKUP)
|
|
1507
|
-
])
|
|
1508
|
-
);
|
|
1509
|
-
var pricing = Object.fromEntries(
|
|
1510
|
-
Object.entries(cost).map(([model, rates]) => [
|
|
1511
|
-
model,
|
|
1512
|
-
{
|
|
1513
|
-
cached: Math.round(rates.cached * MARKUP),
|
|
1514
|
-
input: Math.round(rates.input * MARKUP),
|
|
1515
|
-
output: Math.round(rates.output * MARKUP)
|
|
1516
|
-
}
|
|
1517
|
-
])
|
|
1518
|
-
);
|
|
1519
|
-
|
|
1520
1520
|
// lib/pricing.js
|
|
1521
1521
|
var emailPlans = {
|
|
1522
1522
|
essentials50k: {
|
|
@@ -1586,6 +1586,33 @@ var sending = {
|
|
|
1586
1586
|
plan: emailPlan,
|
|
1587
1587
|
plans: emailPlans,
|
|
1588
1588
|
title: emailPlans[emailPlan].title
|
|
1589
|
+
},
|
|
1590
|
+
// What one outbound SMS SEGMENT costs us, against what channels.sms below
|
|
1591
|
+
// sells it for. Here for the same reason the email figures are: this file is
|
|
1592
|
+
// where a rate is found without knowing whose module owns it — and its
|
|
1593
|
+
// absence is why "does an SMS action cover its cost" could not be answered
|
|
1594
|
+
// from this repo at all.
|
|
1595
|
+
//
|
|
1596
|
+
// US toll-free outbound, read from Twilio's SMS pricing page 2026-09-03
|
|
1597
|
+
// (https://www.twilio.com/en-us/sms/pricing/us): $0.0083 per segment, plus a
|
|
1598
|
+
// carrier fee that varies by the DESTINATION carrier — $0.0035 AT&T, $0.0045
|
|
1599
|
+
// T-Mobile / Verizon / US Cellular, $0.004 everywhere else. CENTS per
|
|
1600
|
+
// segment, like every other rate in this file.
|
|
1601
|
+
//
|
|
1602
|
+
// WORST CASE, deliberately. A price floor computed from the average is a
|
|
1603
|
+
// floor that half of real traffic sits underneath.
|
|
1604
|
+
//
|
|
1605
|
+
// A segment is 153 GSM-7 characters once a message spans more than one — or
|
|
1606
|
+
// 67 if the body contains a single emoji or curly quote, which flips the
|
|
1607
|
+
// whole message to UCS-2. Twilio reports the count as `num_segments` on the
|
|
1608
|
+
// Message resource, which is what the send bills on.
|
|
1609
|
+
sms: {
|
|
1610
|
+
baseCents: 0.83,
|
|
1611
|
+
// The dearest carrier fee, not the mean: see WORST CASE above.
|
|
1612
|
+
carrierCents: 0.45,
|
|
1613
|
+
// Stated, not summed — 0.83 + 0.45 is 1.2800000000000002 in binary
|
|
1614
|
+
// floating point, and this number is compared against money.
|
|
1615
|
+
segmentCostCents: 1.28
|
|
1589
1616
|
}
|
|
1590
1617
|
};
|
|
1591
1618
|
var channels = {
|
|
@@ -1601,6 +1628,9 @@ var channels = {
|
|
|
1601
1628
|
includedInAllowance: false
|
|
1602
1629
|
}
|
|
1603
1630
|
};
|
|
1631
|
+
var MINIMUM_ACTION_CENTS = Math.round(
|
|
1632
|
+
sending.sms.segmentCostCents / channels.sms.actionsPerSegment * MARKUP * 1e3
|
|
1633
|
+
) / 1e3;
|
|
1604
1634
|
|
|
1605
1635
|
// lib/connections/providers/drawbridge.js
|
|
1606
1636
|
var STOP_KEYWORDS = ["STOP", "STOPALL", "UNSUBSCRIBE", "CANCEL", "END", "QUIT"];
|
|
@@ -1625,13 +1655,16 @@ var teamRecipients = async ({ memberIds = [], organization: organization2, read
|
|
|
1625
1655
|
]
|
|
1626
1656
|
}) : [];
|
|
1627
1657
|
const seen = /* @__PURE__ */ new Set();
|
|
1628
|
-
return
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1658
|
+
return {
|
|
1659
|
+
org,
|
|
1660
|
+
recipients: [owner, ...members].filter((member) => {
|
|
1661
|
+
if (!(member == null ? void 0 : member.id) || !(member == null ? void 0 : member.email)) return false;
|
|
1662
|
+
const address = member.email.toLowerCase();
|
|
1663
|
+
if (seen.has(address)) return false;
|
|
1664
|
+
seen.add(address);
|
|
1665
|
+
return true;
|
|
1666
|
+
})
|
|
1667
|
+
};
|
|
1635
1668
|
};
|
|
1636
1669
|
var queueNotification = (data2) => ({ collection: "notification", data: data2, operation: "create" });
|
|
1637
1670
|
var drawbridge_default2 = {
|
|
@@ -1707,7 +1740,7 @@ var drawbridge_default2 = {
|
|
|
1707
1740
|
const count = Number((counted == null ? void 0 : counted.count) || 0);
|
|
1708
1741
|
const request2 = { campaign, count, days };
|
|
1709
1742
|
if (!count) return { message: "No new leads in the period \u2014 digest skipped.", request: request2, response: { skipped: true }, skipped: true };
|
|
1710
|
-
const recipients = await teamRecipients({
|
|
1743
|
+
const { recipients } = await teamRecipients({
|
|
1711
1744
|
memberIds: ((_d = step.settings) == null ? void 0 : _d.members) || [],
|
|
1712
1745
|
organization: workflow.organization,
|
|
1713
1746
|
read
|
|
@@ -1743,7 +1776,7 @@ var drawbridge_default2 = {
|
|
|
1743
1776
|
var _a;
|
|
1744
1777
|
const memberIds = ((_a = step.settings) == null ? void 0 : _a.members) || [];
|
|
1745
1778
|
const request2 = { members: memberIds.length };
|
|
1746
|
-
const recipients = await teamRecipients({ memberIds, organization: workflow.organization, read });
|
|
1779
|
+
const { org, recipients } = await teamRecipients({ memberIds, organization: workflow.organization, read });
|
|
1747
1780
|
if (!recipients.length) {
|
|
1748
1781
|
return {
|
|
1749
1782
|
message: "No owner or accepted member with an email address \u2014 team notification skipped.",
|
|
@@ -1753,32 +1786,37 @@ var drawbridge_default2 = {
|
|
|
1753
1786
|
};
|
|
1754
1787
|
}
|
|
1755
1788
|
const bucket = Math.floor(Date.now() / (15 * 60 * 1e3));
|
|
1789
|
+
const campaign = (context == null ? void 0 : context.campaign) ? await read.get({ collection: "campaign", query: { id: context.campaign } }) : null;
|
|
1790
|
+
const link = (context == null ? void 0 : context.campaign) && (context == null ? void 0 : context.lead) ? {
|
|
1791
|
+
href: "/organizations/" + workflow.organization + "/campaigns/" + context.campaign + "/leads/" + context.lead,
|
|
1792
|
+
text: "View lead"
|
|
1793
|
+
} : null;
|
|
1794
|
+
const body = ((context == null ? void 0 : context.email) || "Someone") + " just entered" + ((campaign == null ? void 0 : campaign.title) ? " " + campaign.title : "") + ((org == null ? void 0 : org.title) ? " at " + org.title : "") + ".";
|
|
1795
|
+
const subject = "New lead" + ((campaign == null ? void 0 : campaign.title) ? " \u2014 " + campaign.title : "");
|
|
1756
1796
|
return {
|
|
1757
1797
|
message: "Team notification queued for " + recipients.length + " recipient(s).",
|
|
1758
1798
|
request: request2,
|
|
1759
1799
|
response: { notified: recipients.length },
|
|
1760
|
-
writes: recipients.map((member) => {
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
};
|
|
1781
|
-
})
|
|
1800
|
+
writes: recipients.map((member) => ({
|
|
1801
|
+
...queueNotification({
|
|
1802
|
+
audience: "member",
|
|
1803
|
+
// Per workflow, recipient AND bucket, so one recipient's damper
|
|
1804
|
+
// can never swallow another's mail and a later bucket is never
|
|
1805
|
+
// mistaken for a duplicate of an earlier one.
|
|
1806
|
+
key: "team.notify." + workflow.id + "." + member.id + "." + bucket,
|
|
1807
|
+
link,
|
|
1808
|
+
message: body,
|
|
1809
|
+
organization: workflow.organization,
|
|
1810
|
+
send: { type: "email", email: member.email },
|
|
1811
|
+
title: subject,
|
|
1812
|
+
workflow: workflow.id
|
|
1813
|
+
}),
|
|
1814
|
+
// E11000 IS THE DAMPER WORKING: this recipient has already been
|
|
1815
|
+
// told within the bucket. Declared per write rather than assumed by
|
|
1816
|
+
// the shell, because on every other write here a duplicate key is a
|
|
1817
|
+
// real failure.
|
|
1818
|
+
ignoreDuplicate: true
|
|
1819
|
+
}))
|
|
1782
1820
|
};
|
|
1783
1821
|
},
|
|
1784
1822
|
// Drawbridge sends lead-facing email itself — no merchant provider gates
|
|
@@ -2184,14 +2222,24 @@ var drawbridge_default2 = {
|
|
|
2184
2222
|
}),
|
|
2185
2223
|
// To organization MEMBERS. Never suppressed — an entrant's opt-out must
|
|
2186
2224
|
// not silence an alert to staff — and not billed.
|
|
2225
|
+
//
|
|
2226
|
+
// ITS COPY IS WRITTEN, NOT ASKED FOR. This says the same thing every
|
|
2227
|
+
// time — a lead arrived, here it is — so the merchant chooses who hears
|
|
2228
|
+
// it, not how it reads, and the hook composes the subject, the body and
|
|
2229
|
+
// the link to the lead.
|
|
2230
|
+
//
|
|
2231
|
+
// `message` and `subject` stay declared, optional and IGNORED: stored
|
|
2232
|
+
// workflows still carry them and the api validates settings with
|
|
2233
|
+
// noUnknown().strict(), so dropping the keys would 400 the next save of
|
|
2234
|
+
// a workflow built before this.
|
|
2187
2235
|
notify: () => ({
|
|
2188
2236
|
hook: "email.notify",
|
|
2189
2237
|
key: "Email \u2014 Notification",
|
|
2190
2238
|
queue: "notification",
|
|
2191
2239
|
settings: {
|
|
2192
2240
|
members: { of: "string", type: "array" },
|
|
2193
|
-
message: {
|
|
2194
|
-
subject: {
|
|
2241
|
+
message: { type: "string" },
|
|
2242
|
+
subject: { type: "string" }
|
|
2195
2243
|
},
|
|
2196
2244
|
triggers: ["lead.insert"],
|
|
2197
2245
|
// Zero is a PRICE, and a deliberate one. Declared rather than omitted
|
package/package.json
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"tinycolor2": "1.6.0"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
|
-
"@drawbridge/drawbridge-agents": "0.1.
|
|
16
|
+
"@drawbridge/drawbridge-agents": "0.1.47",
|
|
17
17
|
"@node-oauth/oauth2-server": "5.3.0",
|
|
18
18
|
"tsup": "8.5.1",
|
|
19
19
|
"typescript": "5.9.3",
|
|
@@ -215,5 +215,5 @@
|
|
|
215
215
|
"test": ". \"$HOME/.nvm/nvm.sh\" && nvm use && node --test"
|
|
216
216
|
},
|
|
217
217
|
"types": "dist/index.d.ts",
|
|
218
|
-
"version": "0.0.
|
|
218
|
+
"version": "0.0.136"
|
|
219
219
|
}
|