@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
|
@@ -1051,6 +1051,74 @@ var drawbridge_default = `<svg width="500" height="500" viewBox="0 0 500 500" fi
|
|
|
1051
1051
|
</defs>
|
|
1052
1052
|
</svg>`;
|
|
1053
1053
|
|
|
1054
|
+
// lib/transactions.js
|
|
1055
|
+
import { currentTraceId } from "@drawbridge/drawbridge-telemetry";
|
|
1056
|
+
|
|
1057
|
+
// lib/billing.js
|
|
1058
|
+
import { createLogger } from "@drawbridge/drawbridge-telemetry";
|
|
1059
|
+
var logger = createLogger();
|
|
1060
|
+
var MARKUP = 1.3;
|
|
1061
|
+
var cost = {
|
|
1062
|
+
// gemini-3.5-flash — verified against Google's pricing page 2026-07-09:
|
|
1063
|
+
// $0.15 cached / $1.50 input / $9.00 output per 1M tokens (thinking billed at
|
|
1064
|
+
// output). ~3.6x the retired 2.5-flash output rate.
|
|
1065
|
+
"gemini-3.5-flash": {
|
|
1066
|
+
cached: 15,
|
|
1067
|
+
input: 150,
|
|
1068
|
+
output: 900
|
|
1069
|
+
},
|
|
1070
|
+
// gemini-3.5-flash-lite — verified against Google's pricing page 2026-08-20:
|
|
1071
|
+
// $0.03 cached / $0.30 input / $2.50 output per 1M tokens (thinking billed at
|
|
1072
|
+
// output). A fifth of flash on input, ~a quarter on output. Growth's assistant
|
|
1073
|
+
// ranks the feed on this tier — one call per page — so its rows were the
|
|
1074
|
+
// unpriced ones until now.
|
|
1075
|
+
"gemini-3.5-flash-lite": {
|
|
1076
|
+
cached: 3,
|
|
1077
|
+
input: 30,
|
|
1078
|
+
output: 250
|
|
1079
|
+
},
|
|
1080
|
+
"gemini-2.5-flash": {
|
|
1081
|
+
cached: 3,
|
|
1082
|
+
input: 30,
|
|
1083
|
+
output: 250
|
|
1084
|
+
},
|
|
1085
|
+
"gemini-2.5-flash-image": {
|
|
1086
|
+
cached: 3,
|
|
1087
|
+
input: 30,
|
|
1088
|
+
output: 3e3
|
|
1089
|
+
},
|
|
1090
|
+
// gemini-3-pro-image-preview — verified against Google's pricing page
|
|
1091
|
+
// 2026-08-12: $2.00 input / $12.00 text output per 1M, and image output
|
|
1092
|
+
// tokens at ~$120/1M (a 1K-2K image is 1120 tokens = $0.134, a 4K image
|
|
1093
|
+
// 2000 tokens = $0.24). Encoded the flash-image way: one flat output rate
|
|
1094
|
+
// that reproduces the per-image price from the tokens usageMetadata
|
|
1095
|
+
// reports. Growth's hero generation runs this model today.
|
|
1096
|
+
"gemini-3-pro-image-preview": {
|
|
1097
|
+
cached: 20,
|
|
1098
|
+
input: 200,
|
|
1099
|
+
output: 12e3
|
|
1100
|
+
}
|
|
1101
|
+
};
|
|
1102
|
+
var toolCost = {
|
|
1103
|
+
search: 3.5
|
|
1104
|
+
};
|
|
1105
|
+
var toolPricing = Object.fromEntries(
|
|
1106
|
+
Object.entries(toolCost).map(([tool, value]) => [
|
|
1107
|
+
tool,
|
|
1108
|
+
Math.ceil(value * MARKUP)
|
|
1109
|
+
])
|
|
1110
|
+
);
|
|
1111
|
+
var pricing = Object.fromEntries(
|
|
1112
|
+
Object.entries(cost).map(([model, rates]) => [
|
|
1113
|
+
model,
|
|
1114
|
+
{
|
|
1115
|
+
cached: Math.round(rates.cached * MARKUP),
|
|
1116
|
+
input: Math.round(rates.input * MARKUP),
|
|
1117
|
+
output: Math.round(rates.output * MARKUP)
|
|
1118
|
+
}
|
|
1119
|
+
])
|
|
1120
|
+
);
|
|
1121
|
+
|
|
1054
1122
|
// lib/features.js
|
|
1055
1123
|
var page = {
|
|
1056
1124
|
qrcode: {
|
|
@@ -1513,74 +1581,6 @@ var conversionRate = (subscription) => {
|
|
|
1513
1581
|
return ((_a = resolvePlan(subscription)) == null ? void 0 : _a.conversion) ?? free.conversion;
|
|
1514
1582
|
};
|
|
1515
1583
|
|
|
1516
|
-
// lib/transactions.js
|
|
1517
|
-
import { currentTraceId } from "@drawbridge/drawbridge-telemetry";
|
|
1518
|
-
|
|
1519
|
-
// lib/billing.js
|
|
1520
|
-
import { createLogger } from "@drawbridge/drawbridge-telemetry";
|
|
1521
|
-
var logger = createLogger();
|
|
1522
|
-
var MARKUP = 1.3;
|
|
1523
|
-
var cost = {
|
|
1524
|
-
// gemini-3.5-flash — verified against Google's pricing page 2026-07-09:
|
|
1525
|
-
// $0.15 cached / $1.50 input / $9.00 output per 1M tokens (thinking billed at
|
|
1526
|
-
// output). ~3.6x the retired 2.5-flash output rate.
|
|
1527
|
-
"gemini-3.5-flash": {
|
|
1528
|
-
cached: 15,
|
|
1529
|
-
input: 150,
|
|
1530
|
-
output: 900
|
|
1531
|
-
},
|
|
1532
|
-
// gemini-3.5-flash-lite — verified against Google's pricing page 2026-08-20:
|
|
1533
|
-
// $0.03 cached / $0.30 input / $2.50 output per 1M tokens (thinking billed at
|
|
1534
|
-
// output). A fifth of flash on input, ~a quarter on output. Growth's assistant
|
|
1535
|
-
// ranks the feed on this tier — one call per page — so its rows were the
|
|
1536
|
-
// unpriced ones until now.
|
|
1537
|
-
"gemini-3.5-flash-lite": {
|
|
1538
|
-
cached: 3,
|
|
1539
|
-
input: 30,
|
|
1540
|
-
output: 250
|
|
1541
|
-
},
|
|
1542
|
-
"gemini-2.5-flash": {
|
|
1543
|
-
cached: 3,
|
|
1544
|
-
input: 30,
|
|
1545
|
-
output: 250
|
|
1546
|
-
},
|
|
1547
|
-
"gemini-2.5-flash-image": {
|
|
1548
|
-
cached: 3,
|
|
1549
|
-
input: 30,
|
|
1550
|
-
output: 3e3
|
|
1551
|
-
},
|
|
1552
|
-
// gemini-3-pro-image-preview — verified against Google's pricing page
|
|
1553
|
-
// 2026-08-12: $2.00 input / $12.00 text output per 1M, and image output
|
|
1554
|
-
// tokens at ~$120/1M (a 1K-2K image is 1120 tokens = $0.134, a 4K image
|
|
1555
|
-
// 2000 tokens = $0.24). Encoded the flash-image way: one flat output rate
|
|
1556
|
-
// that reproduces the per-image price from the tokens usageMetadata
|
|
1557
|
-
// reports. Growth's hero generation runs this model today.
|
|
1558
|
-
"gemini-3-pro-image-preview": {
|
|
1559
|
-
cached: 20,
|
|
1560
|
-
input: 200,
|
|
1561
|
-
output: 12e3
|
|
1562
|
-
}
|
|
1563
|
-
};
|
|
1564
|
-
var toolCost = {
|
|
1565
|
-
search: 3.5
|
|
1566
|
-
};
|
|
1567
|
-
var toolPricing = Object.fromEntries(
|
|
1568
|
-
Object.entries(toolCost).map(([tool, value]) => [
|
|
1569
|
-
tool,
|
|
1570
|
-
Math.ceil(value * MARKUP)
|
|
1571
|
-
])
|
|
1572
|
-
);
|
|
1573
|
-
var pricing = Object.fromEntries(
|
|
1574
|
-
Object.entries(cost).map(([model, rates]) => [
|
|
1575
|
-
model,
|
|
1576
|
-
{
|
|
1577
|
-
cached: Math.round(rates.cached * MARKUP),
|
|
1578
|
-
input: Math.round(rates.input * MARKUP),
|
|
1579
|
-
output: Math.round(rates.output * MARKUP)
|
|
1580
|
-
}
|
|
1581
|
-
])
|
|
1582
|
-
);
|
|
1583
|
-
|
|
1584
1584
|
// lib/pricing.js
|
|
1585
1585
|
var emailPlans = {
|
|
1586
1586
|
essentials50k: {
|
|
@@ -1650,6 +1650,33 @@ var sending = {
|
|
|
1650
1650
|
plan: emailPlan,
|
|
1651
1651
|
plans: emailPlans,
|
|
1652
1652
|
title: emailPlans[emailPlan].title
|
|
1653
|
+
},
|
|
1654
|
+
// What one outbound SMS SEGMENT costs us, against what channels.sms below
|
|
1655
|
+
// sells it for. Here for the same reason the email figures are: this file is
|
|
1656
|
+
// where a rate is found without knowing whose module owns it — and its
|
|
1657
|
+
// absence is why "does an SMS action cover its cost" could not be answered
|
|
1658
|
+
// from this repo at all.
|
|
1659
|
+
//
|
|
1660
|
+
// US toll-free outbound, read from Twilio's SMS pricing page 2026-09-03
|
|
1661
|
+
// (https://www.twilio.com/en-us/sms/pricing/us): $0.0083 per segment, plus a
|
|
1662
|
+
// carrier fee that varies by the DESTINATION carrier — $0.0035 AT&T, $0.0045
|
|
1663
|
+
// T-Mobile / Verizon / US Cellular, $0.004 everywhere else. CENTS per
|
|
1664
|
+
// segment, like every other rate in this file.
|
|
1665
|
+
//
|
|
1666
|
+
// WORST CASE, deliberately. A price floor computed from the average is a
|
|
1667
|
+
// floor that half of real traffic sits underneath.
|
|
1668
|
+
//
|
|
1669
|
+
// A segment is 153 GSM-7 characters once a message spans more than one — or
|
|
1670
|
+
// 67 if the body contains a single emoji or curly quote, which flips the
|
|
1671
|
+
// whole message to UCS-2. Twilio reports the count as `num_segments` on the
|
|
1672
|
+
// Message resource, which is what the send bills on.
|
|
1673
|
+
sms: {
|
|
1674
|
+
baseCents: 0.83,
|
|
1675
|
+
// The dearest carrier fee, not the mean: see WORST CASE above.
|
|
1676
|
+
carrierCents: 0.45,
|
|
1677
|
+
// Stated, not summed — 0.83 + 0.45 is 1.2800000000000002 in binary
|
|
1678
|
+
// floating point, and this number is compared against money.
|
|
1679
|
+
segmentCostCents: 1.28
|
|
1653
1680
|
}
|
|
1654
1681
|
};
|
|
1655
1682
|
var channels = {
|
|
@@ -1665,6 +1692,9 @@ var channels = {
|
|
|
1665
1692
|
includedInAllowance: false
|
|
1666
1693
|
}
|
|
1667
1694
|
};
|
|
1695
|
+
var MINIMUM_ACTION_CENTS = Math.round(
|
|
1696
|
+
sending.sms.segmentCostCents / channels.sms.actionsPerSegment * MARKUP * 1e3
|
|
1697
|
+
) / 1e3;
|
|
1668
1698
|
|
|
1669
1699
|
// lib/connections/providers/drawbridge.js
|
|
1670
1700
|
var STOP_KEYWORDS = ["STOP", "STOPALL", "UNSUBSCRIBE", "CANCEL", "END", "QUIT"];
|
|
@@ -1689,13 +1719,16 @@ var teamRecipients = async ({ memberIds = [], organization: organization2, read
|
|
|
1689
1719
|
]
|
|
1690
1720
|
}) : [];
|
|
1691
1721
|
const seen = /* @__PURE__ */ new Set();
|
|
1692
|
-
return
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1722
|
+
return {
|
|
1723
|
+
org,
|
|
1724
|
+
recipients: [owner, ...members].filter((member) => {
|
|
1725
|
+
if (!(member == null ? void 0 : member.id) || !(member == null ? void 0 : member.email)) return false;
|
|
1726
|
+
const address = member.email.toLowerCase();
|
|
1727
|
+
if (seen.has(address)) return false;
|
|
1728
|
+
seen.add(address);
|
|
1729
|
+
return true;
|
|
1730
|
+
})
|
|
1731
|
+
};
|
|
1699
1732
|
};
|
|
1700
1733
|
var queueNotification = (data2) => ({ collection: "notification", data: data2, operation: "create" });
|
|
1701
1734
|
var drawbridge_default2 = {
|
|
@@ -1771,7 +1804,7 @@ var drawbridge_default2 = {
|
|
|
1771
1804
|
const count = Number((counted == null ? void 0 : counted.count) || 0);
|
|
1772
1805
|
const request2 = { campaign, count, days };
|
|
1773
1806
|
if (!count) return { message: "No new leads in the period \u2014 digest skipped.", request: request2, response: { skipped: true }, skipped: true };
|
|
1774
|
-
const recipients = await teamRecipients({
|
|
1807
|
+
const { recipients } = await teamRecipients({
|
|
1775
1808
|
memberIds: ((_d = step.settings) == null ? void 0 : _d.members) || [],
|
|
1776
1809
|
organization: workflow.organization,
|
|
1777
1810
|
read
|
|
@@ -1807,7 +1840,7 @@ var drawbridge_default2 = {
|
|
|
1807
1840
|
var _a;
|
|
1808
1841
|
const memberIds = ((_a = step.settings) == null ? void 0 : _a.members) || [];
|
|
1809
1842
|
const request2 = { members: memberIds.length };
|
|
1810
|
-
const recipients = await teamRecipients({ memberIds, organization: workflow.organization, read });
|
|
1843
|
+
const { org, recipients } = await teamRecipients({ memberIds, organization: workflow.organization, read });
|
|
1811
1844
|
if (!recipients.length) {
|
|
1812
1845
|
return {
|
|
1813
1846
|
message: "No owner or accepted member with an email address \u2014 team notification skipped.",
|
|
@@ -1817,32 +1850,37 @@ var drawbridge_default2 = {
|
|
|
1817
1850
|
};
|
|
1818
1851
|
}
|
|
1819
1852
|
const bucket = Math.floor(Date.now() / (15 * 60 * 1e3));
|
|
1853
|
+
const campaign = (context == null ? void 0 : context.campaign) ? await read.get({ collection: "campaign", query: { id: context.campaign } }) : null;
|
|
1854
|
+
const link = (context == null ? void 0 : context.campaign) && (context == null ? void 0 : context.lead) ? {
|
|
1855
|
+
href: "/organizations/" + workflow.organization + "/campaigns/" + context.campaign + "/leads/" + context.lead,
|
|
1856
|
+
text: "View lead"
|
|
1857
|
+
} : null;
|
|
1858
|
+
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 : "") + ".";
|
|
1859
|
+
const subject = "New lead" + ((campaign == null ? void 0 : campaign.title) ? " \u2014 " + campaign.title : "");
|
|
1820
1860
|
return {
|
|
1821
1861
|
message: "Team notification queued for " + recipients.length + " recipient(s).",
|
|
1822
1862
|
request: request2,
|
|
1823
1863
|
response: { notified: recipients.length },
|
|
1824
|
-
writes: recipients.map((member) => {
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
};
|
|
1845
|
-
})
|
|
1864
|
+
writes: recipients.map((member) => ({
|
|
1865
|
+
...queueNotification({
|
|
1866
|
+
audience: "member",
|
|
1867
|
+
// Per workflow, recipient AND bucket, so one recipient's damper
|
|
1868
|
+
// can never swallow another's mail and a later bucket is never
|
|
1869
|
+
// mistaken for a duplicate of an earlier one.
|
|
1870
|
+
key: "team.notify." + workflow.id + "." + member.id + "." + bucket,
|
|
1871
|
+
link,
|
|
1872
|
+
message: body,
|
|
1873
|
+
organization: workflow.organization,
|
|
1874
|
+
send: { type: "email", email: member.email },
|
|
1875
|
+
title: subject,
|
|
1876
|
+
workflow: workflow.id
|
|
1877
|
+
}),
|
|
1878
|
+
// E11000 IS THE DAMPER WORKING: this recipient has already been
|
|
1879
|
+
// told within the bucket. Declared per write rather than assumed by
|
|
1880
|
+
// the shell, because on every other write here a duplicate key is a
|
|
1881
|
+
// real failure.
|
|
1882
|
+
ignoreDuplicate: true
|
|
1883
|
+
}))
|
|
1846
1884
|
};
|
|
1847
1885
|
},
|
|
1848
1886
|
// Drawbridge sends lead-facing email itself — no merchant provider gates
|
|
@@ -2248,14 +2286,24 @@ var drawbridge_default2 = {
|
|
|
2248
2286
|
}),
|
|
2249
2287
|
// To organization MEMBERS. Never suppressed — an entrant's opt-out must
|
|
2250
2288
|
// not silence an alert to staff — and not billed.
|
|
2289
|
+
//
|
|
2290
|
+
// ITS COPY IS WRITTEN, NOT ASKED FOR. This says the same thing every
|
|
2291
|
+
// time — a lead arrived, here it is — so the merchant chooses who hears
|
|
2292
|
+
// it, not how it reads, and the hook composes the subject, the body and
|
|
2293
|
+
// the link to the lead.
|
|
2294
|
+
//
|
|
2295
|
+
// `message` and `subject` stay declared, optional and IGNORED: stored
|
|
2296
|
+
// workflows still carry them and the api validates settings with
|
|
2297
|
+
// noUnknown().strict(), so dropping the keys would 400 the next save of
|
|
2298
|
+
// a workflow built before this.
|
|
2251
2299
|
notify: () => ({
|
|
2252
2300
|
hook: "email.notify",
|
|
2253
2301
|
key: "Email \u2014 Notification",
|
|
2254
2302
|
queue: "notification",
|
|
2255
2303
|
settings: {
|
|
2256
2304
|
members: { of: "string", type: "array" },
|
|
2257
|
-
message: {
|
|
2258
|
-
subject: {
|
|
2305
|
+
message: { type: "string" },
|
|
2306
|
+
subject: { type: "string" }
|
|
2259
2307
|
},
|
|
2260
2308
|
triggers: ["lead.insert"],
|
|
2261
2309
|
// Zero is a PRICE, and a deliberate one. Declared rather than omitted
|