@oxygen-agent/cli 1.987.20 → 1.1010.1

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.
Files changed (176) hide show
  1. package/README.md +1 -1
  2. package/dist/admin-primary-providers-render.d.ts +0 -2
  3. package/dist/admin-primary-providers-render.js +1 -1
  4. package/dist/browser-login.js +1 -4
  5. package/dist/column-decision-options.d.ts +20 -0
  6. package/dist/column-decision-options.js +54 -0
  7. package/dist/command-manifest.d.ts +3 -2
  8. package/dist/command-manifest.js +25 -2
  9. package/dist/credentials.d.ts +1 -1
  10. package/dist/functions-commands.js +13 -9
  11. package/dist/help.d.ts +8 -0
  12. package/dist/help.js +46 -0
  13. package/dist/index.js +2751 -242
  14. package/dist/knowledge-mirror.d.ts +2 -2
  15. package/dist/runtime.d.ts +0 -15
  16. package/dist/runtime.js +1 -1
  17. package/dist/search-ai-filter-notice.d.ts +17 -0
  18. package/dist/search-ai-filter-notice.js +38 -0
  19. package/dist/session.d.ts +4 -3
  20. package/dist/skills.d.ts +8 -7
  21. package/dist/skills.js +58 -20
  22. package/dist/transcript.d.ts +2 -1
  23. package/dist/util.d.ts +10 -1
  24. package/dist/util.js +14 -2
  25. package/node_modules/@oxygen/cli-ugc/dist/commands.js +296 -140
  26. package/node_modules/@oxygen/cli-ugc/dist/field-parser.d.ts +9 -0
  27. package/node_modules/@oxygen/cli-ugc/dist/field-parser.js +34 -0
  28. package/node_modules/@oxygen/formula/dist/coerce.d.ts +10 -0
  29. package/node_modules/@oxygen/formula/dist/coerce.js +10 -0
  30. package/node_modules/@oxygen/formula/dist/formula-functions.js +65 -0
  31. package/node_modules/@oxygen/formula/dist/hash.d.ts +19 -0
  32. package/node_modules/@oxygen/formula/dist/hash.js +199 -0
  33. package/node_modules/@oxygen/formula/dist/value-cleaners.d.ts +6 -1
  34. package/node_modules/@oxygen/formula/dist/value-cleaners.js +10 -26
  35. package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +13 -0
  36. package/node_modules/@oxygen/shared/dist/array-utils.d.ts +5 -0
  37. package/node_modules/@oxygen/shared/dist/array-utils.js +11 -0
  38. package/node_modules/@oxygen/shared/dist/billing.d.ts +99 -22
  39. package/node_modules/@oxygen/shared/dist/billing.js +195 -40
  40. package/node_modules/@oxygen/shared/dist/byok-connect.js +5 -0
  41. package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +27 -0
  42. package/node_modules/@oxygen/shared/dist/capability-discovery.js +311 -28
  43. package/node_modules/@oxygen/shared/dist/cli-http-error.d.ts +8 -0
  44. package/node_modules/@oxygen/shared/dist/cli-http-error.js +8 -0
  45. package/node_modules/@oxygen/shared/dist/cli-result.js +1 -0
  46. package/node_modules/@oxygen/shared/dist/column-autofill.d.ts +52 -0
  47. package/node_modules/@oxygen/shared/dist/column-autofill.js +62 -0
  48. package/node_modules/@oxygen/shared/dist/column-decision.d.ts +50 -0
  49. package/node_modules/@oxygen/shared/dist/column-decision.js +228 -0
  50. package/node_modules/@oxygen/shared/dist/column-output-fields.js +2 -6
  51. package/node_modules/@oxygen/shared/dist/company-enrichment-fields.d.ts +113 -0
  52. package/node_modules/@oxygen/shared/dist/company-enrichment-fields.js +548 -0
  53. package/node_modules/@oxygen/shared/dist/copilot-skills.generated.d.ts +6 -6
  54. package/node_modules/@oxygen/shared/dist/copilot-skills.generated.js +6 -6
  55. package/node_modules/@oxygen/shared/dist/cutover-freeze.d.ts +26 -0
  56. package/node_modules/@oxygen/shared/dist/cutover-freeze.js +52 -0
  57. package/node_modules/@oxygen/shared/dist/data-suppliers.d.ts +57 -0
  58. package/node_modules/@oxygen/shared/dist/data-suppliers.js +59 -0
  59. package/node_modules/@oxygen/shared/dist/deploy-env.d.ts +74 -0
  60. package/node_modules/@oxygen/shared/dist/deploy-env.js +82 -0
  61. package/node_modules/@oxygen/shared/dist/dnc-rules.d.ts +130 -0
  62. package/node_modules/@oxygen/shared/dist/dnc-rules.js +221 -0
  63. package/node_modules/@oxygen/shared/dist/enrichment-intents.d.ts +107 -0
  64. package/node_modules/@oxygen/shared/dist/enrichment-intents.js +809 -0
  65. package/node_modules/@oxygen/shared/dist/error-message.d.ts +1 -0
  66. package/node_modules/@oxygen/shared/dist/error-message.js +3 -0
  67. package/node_modules/@oxygen/shared/dist/error-redaction.js +1 -3
  68. package/node_modules/@oxygen/shared/dist/external-write-policy.d.ts +33 -0
  69. package/node_modules/@oxygen/shared/dist/external-write-policy.js +68 -0
  70. package/node_modules/@oxygen/shared/dist/format-percent.d.ts +8 -0
  71. package/node_modules/@oxygen/shared/dist/format-percent.js +13 -0
  72. package/node_modules/@oxygen/shared/dist/freemail-domains.d.ts +81 -0
  73. package/node_modules/@oxygen/shared/dist/freemail-domains.js +157 -0
  74. package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.d.ts +1 -0
  75. package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.js +1 -1
  76. package/node_modules/@oxygen/shared/dist/hosted-ai.d.ts +60 -4
  77. package/node_modules/@oxygen/shared/dist/hosted-ai.js +125 -10
  78. package/node_modules/@oxygen/shared/dist/index.d.ts +15 -0
  79. package/node_modules/@oxygen/shared/dist/index.js +15 -0
  80. package/node_modules/@oxygen/shared/dist/json-path.js +1 -3
  81. package/node_modules/@oxygen/shared/dist/knowledge-bases.js +1 -3
  82. package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +2 -2
  83. package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +2 -2
  84. package/node_modules/@oxygen/shared/dist/langfuse.d.ts +44 -1
  85. package/node_modules/@oxygen/shared/dist/langfuse.js +416 -14
  86. package/node_modules/@oxygen/shared/dist/linkedin-countries.d.ts +33 -0
  87. package/node_modules/@oxygen/shared/dist/linkedin-countries.js +361 -0
  88. package/node_modules/@oxygen/shared/dist/linkedin-country-timezones.d.ts +24 -0
  89. package/node_modules/@oxygen/shared/dist/linkedin-country-timezones.js +276 -0
  90. package/node_modules/@oxygen/shared/dist/linkedin-message-deletion.d.ts +2 -0
  91. package/node_modules/@oxygen/shared/dist/linkedin-message-deletion.js +5 -0
  92. package/node_modules/@oxygen/shared/dist/linkedin-post-keywords.d.ts +44 -0
  93. package/node_modules/@oxygen/shared/dist/linkedin-post-keywords.js +116 -0
  94. package/node_modules/@oxygen/shared/dist/linkedin-sequences.d.ts +96 -0
  95. package/node_modules/@oxygen/shared/dist/linkedin-sequences.js +123 -0
  96. package/node_modules/@oxygen/shared/dist/llm-durable-capture.d.ts +24 -0
  97. package/node_modules/@oxygen/shared/dist/llm-durable-capture.js +89 -0
  98. package/node_modules/@oxygen/shared/dist/llm-prompts.d.ts +75 -0
  99. package/node_modules/@oxygen/shared/dist/llm-prompts.js +161 -0
  100. package/node_modules/@oxygen/shared/dist/log-sink-selector.d.ts +39 -0
  101. package/node_modules/@oxygen/shared/dist/log-sink-selector.js +56 -0
  102. package/node_modules/@oxygen/shared/dist/log.d.ts +1 -0
  103. package/node_modules/@oxygen/shared/dist/log.js +6 -1
  104. package/node_modules/@oxygen/shared/dist/mailbox-egress-ownership.d.ts +90 -0
  105. package/node_modules/@oxygen/shared/dist/mailbox-egress-ownership.js +130 -0
  106. package/node_modules/@oxygen/shared/dist/object-storage.d.ts +17 -0
  107. package/node_modules/@oxygen/shared/dist/object-storage.js +21 -0
  108. package/node_modules/@oxygen/shared/dist/operational-telemetry.d.ts +24 -0
  109. package/node_modules/@oxygen/shared/dist/operational-telemetry.js +73 -0
  110. package/node_modules/@oxygen/shared/dist/otlp-log-sink.d.ts +79 -0
  111. package/node_modules/@oxygen/shared/dist/otlp-log-sink.js +366 -0
  112. package/node_modules/@oxygen/shared/dist/plan-capabilities.js +1 -0
  113. package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +23 -22
  114. package/node_modules/@oxygen/shared/dist/plan-limits.js +45 -18
  115. package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +48 -41
  116. package/node_modules/@oxygen/shared/dist/pricing-sheet.js +36 -25
  117. package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.d.ts +22 -22
  118. package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.js +40 -34
  119. package/node_modules/@oxygen/shared/dist/product-analytics-environment.js +9 -0
  120. package/node_modules/@oxygen/shared/dist/product-analytics-events.d.ts +36 -2
  121. package/node_modules/@oxygen/shared/dist/product-analytics-events.js +36 -1
  122. package/node_modules/@oxygen/shared/dist/rate-window.d.ts +5 -0
  123. package/node_modules/@oxygen/shared/dist/rate-window.js +8 -0
  124. package/node_modules/@oxygen/shared/dist/redaction.js +4 -1
  125. package/node_modules/@oxygen/shared/dist/research-output-contract.js +1 -3
  126. package/node_modules/@oxygen/shared/dist/scraper-lane-credential.d.ts +18 -0
  127. package/node_modules/@oxygen/shared/dist/scraper-lane-credential.js +23 -0
  128. package/node_modules/@oxygen/shared/dist/search-vocab.js +4 -5
  129. package/node_modules/@oxygen/shared/dist/select-options.js +6 -1
  130. package/node_modules/@oxygen/shared/dist/sequence-failures.js +1 -5
  131. package/node_modules/@oxygen/shared/dist/sequences.d.ts +23 -0
  132. package/node_modules/@oxygen/shared/dist/sequences.js +115 -5
  133. package/node_modules/@oxygen/shared/dist/signup-lead-payload.d.ts +80 -0
  134. package/node_modules/@oxygen/shared/dist/signup-lead-payload.js +198 -0
  135. package/node_modules/@oxygen/shared/dist/social-capabilities.d.ts +6 -0
  136. package/node_modules/@oxygen/shared/dist/social-capabilities.js +25 -16
  137. package/node_modules/@oxygen/shared/dist/social-post-metrics-core.d.ts +32 -0
  138. package/node_modules/@oxygen/shared/dist/social-post-metrics-core.js +32 -0
  139. package/node_modules/@oxygen/shared/dist/social-post-metrics-linkedin.d.ts +31 -0
  140. package/node_modules/@oxygen/shared/dist/social-post-metrics-linkedin.js +103 -0
  141. package/node_modules/@oxygen/shared/dist/social-post-metrics-series.d.ts +96 -0
  142. package/node_modules/@oxygen/shared/dist/social-post-metrics-series.js +213 -0
  143. package/node_modules/@oxygen/shared/dist/social-post-metrics-x.d.ts +13 -0
  144. package/node_modules/@oxygen/shared/dist/social-post-metrics-x.js +78 -0
  145. package/node_modules/@oxygen/shared/dist/social-post-metrics.d.ts +36 -0
  146. package/node_modules/@oxygen/shared/dist/social-post-metrics.js +51 -0
  147. package/node_modules/@oxygen/shared/dist/spend-safety.d.ts +22 -10
  148. package/node_modules/@oxygen/shared/dist/spend-safety.js +15 -21
  149. package/node_modules/@oxygen/shared/dist/sql-rows.d.ts +1 -0
  150. package/node_modules/@oxygen/shared/dist/sql-rows.js +3 -0
  151. package/node_modules/@oxygen/shared/dist/stripe-price-catalog.d.ts +36 -0
  152. package/node_modules/@oxygen/shared/dist/stripe-price-catalog.js +184 -0
  153. package/node_modules/@oxygen/shared/dist/stripe-subscription-kind.d.ts +41 -0
  154. package/node_modules/@oxygen/shared/dist/stripe-subscription-kind.js +44 -0
  155. package/node_modules/@oxygen/shared/dist/table-limits.d.ts +3 -0
  156. package/node_modules/@oxygen/shared/dist/table-limits.js +3 -0
  157. package/node_modules/@oxygen/shared/dist/telemetry-export-observer.d.ts +94 -0
  158. package/node_modules/@oxygen/shared/dist/telemetry-export-observer.js +298 -0
  159. package/node_modules/@oxygen/shared/dist/telemetry.d.ts +11 -0
  160. package/node_modules/@oxygen/shared/dist/telemetry.js +28 -2
  161. package/node_modules/@oxygen/shared/dist/type-guards.d.ts +22 -0
  162. package/node_modules/@oxygen/shared/dist/type-guards.js +35 -0
  163. package/node_modules/@oxygen/shared/dist/ugc.d.ts +22 -11
  164. package/node_modules/@oxygen/shared/dist/ugc.js +10 -0
  165. package/node_modules/@oxygen/shared/dist/value-readers.d.ts +21 -0
  166. package/node_modules/@oxygen/shared/dist/value-readers.js +59 -0
  167. package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -0
  168. package/node_modules/@oxygen/shared/dist/version.generated.js +2 -0
  169. package/node_modules/@oxygen/shared/dist/version.js +8 -1
  170. package/node_modules/@oxygen/shared/dist/workspace-event-catalog.js +0 -23
  171. package/node_modules/@oxygen/shared/dist/workspace-file-storage.d.ts +29 -0
  172. package/node_modules/@oxygen/shared/dist/workspace-file-storage.js +31 -0
  173. package/node_modules/@oxygen/shared/package.json +59 -0
  174. package/node_modules/@oxygen/workflows/dist/graph/expression.js +2 -5
  175. package/node_modules/@oxygen/workflows/dist/graph/params.js +1 -1
  176. package/package.json +3 -2
@@ -0,0 +1,184 @@
1
+ import { BASE_PRICING_PLANS, CONTACT_SALES_URL, DEFAULT_BILLING_CURRENCY, PURCHASABLE_PLAN_KEYS, SELF_SERVE_PLAN_TIERS, normalizeBillingCurrency, resolveBasePricingPlan, } from "./billing.js";
2
+ export { CONTACT_SALES_URL, PURCHASABLE_PLAN_KEYS, SELF_SERVE_PLAN_TIERS, normalizeBillingCurrency, };
3
+ const EMPTY_PRICE_IDS = { usd: "" };
4
+ /**
5
+ * The six purchasable Oxygen rungs: one Stripe Product EACH, one monthly
6
+ * Price on each. They cannot share a Product -- a Billing Portal configuration
7
+ * refuses two Prices of the same interval under one Product, which is what
8
+ * makes the plan picker configurable at all.
9
+ *
10
+ * These are consulted BEFORE the grandfathered maps in tierFromPriceId: if an
11
+ * Oxygen price id were ever also listed in a legacy env var, the legacy branch
12
+ * winning would grant a $499 customer a $99 plan's credits.
13
+ */
14
+ const STRIPE_PRICE_OXYGEN_IDS = {
15
+ oxygen_49: { usd: process.env.STRIPE_PRICE_OXYGEN_49_USD ?? "" },
16
+ oxygen_99: { usd: process.env.STRIPE_PRICE_OXYGEN_99_USD ?? "" },
17
+ oxygen_199: { usd: process.env.STRIPE_PRICE_OXYGEN_199_USD ?? "" },
18
+ oxygen_499: { usd: process.env.STRIPE_PRICE_OXYGEN_499_USD ?? "" },
19
+ oxygen_999: { usd: process.env.STRIPE_PRICE_OXYGEN_999_USD ?? "" },
20
+ oxygen_1999: { usd: process.env.STRIPE_PRICE_OXYGEN_1999_USD ?? "" },
21
+ };
22
+ // Grandfathered on 2026-09-19. These three stay pointed at their LIVE prices
23
+ // (35 Starter + 2 Pro subscriptions bill against them) rather than moving into
24
+ // the legacy CSV lists: tierFromPriceId must keep returning "starter"/"pro"/
25
+ // "team" for them so resolveBasePricingPlan yields the plan those customers
26
+ // actually bought. They are simply no longer offered for sale.
27
+ const STRIPE_PRICE_STARTER_IDS = {
28
+ usd: process.env.STRIPE_PRICE_STARTER_99_USD ?? "",
29
+ };
30
+ const STRIPE_PRICE_PRO_IDS = {
31
+ usd: process.env.STRIPE_PRICE_PRO_249_USD ?? "",
32
+ };
33
+ const STRIPE_PRICE_TEAM_IDS = {
34
+ usd: process.env.STRIPE_PRICE_TEAM_749_USD ?? "",
35
+ };
36
+ const LEGACY_STRIPE_PRICE_IDS = {
37
+ starter_50: {
38
+ usd: process.env.STRIPE_PRICE_STARTER_USD ?? "",
39
+ },
40
+ pro_149: {
41
+ usd: process.env.STRIPE_PRICE_PRO_USD ?? "",
42
+ },
43
+ team_399: {
44
+ usd: process.env.STRIPE_PRICE_TEAM_USD ?? "",
45
+ },
46
+ };
47
+ const LEGACY_CURRENT_PLAN_PRICE_IDS = {
48
+ oxygen: readCommaSeparatedPriceIds(process.env.STRIPE_LEGACY_PRICE_OXYGEN_IDS),
49
+ starter: readCommaSeparatedPriceIds(process.env.STRIPE_LEGACY_PRICE_STARTER_IDS),
50
+ pro: readCommaSeparatedPriceIds(process.env.STRIPE_LEGACY_PRICE_PRO_IDS),
51
+ team: readCommaSeparatedPriceIds(process.env.STRIPE_LEGACY_PRICE_TEAM_IDS),
52
+ };
53
+ const SELF_SERVE_TIER_BY_LEGACY_TIER = {
54
+ starter_50: "starter",
55
+ pro_149: "pro",
56
+ team_399: "team",
57
+ };
58
+ function withPriceIds(plan, priceIds = EMPTY_PRICE_IDS) {
59
+ return {
60
+ ...plan,
61
+ priceIds,
62
+ priceId: priceIds[DEFAULT_BILLING_CURRENCY],
63
+ };
64
+ }
65
+ export const PRICING_PLANS = {
66
+ free: withPriceIds(BASE_PRICING_PLANS.free),
67
+ oxygen: withPriceIds(BASE_PRICING_PLANS.oxygen, STRIPE_PRICE_OXYGEN_IDS.oxygen_49),
68
+ starter: withPriceIds(BASE_PRICING_PLANS.starter, STRIPE_PRICE_STARTER_IDS),
69
+ pro: withPriceIds(BASE_PRICING_PLANS.pro, STRIPE_PRICE_PRO_IDS),
70
+ team: withPriceIds(BASE_PRICING_PLANS.team, STRIPE_PRICE_TEAM_IDS),
71
+ scale: withPriceIds(BASE_PRICING_PLANS.scale),
72
+ enterprise: withPriceIds(BASE_PRICING_PLANS.enterprise),
73
+ };
74
+ /** The six purchasable rungs with their Stripe price ids, cheapest first. */
75
+ export const PURCHASABLE_PLANS = Object.fromEntries(PURCHASABLE_PLAN_KEYS.map((planKey) => {
76
+ const plan = resolveBasePricingPlan(planKey);
77
+ if (!plan)
78
+ throw new Error(`purchasable plan "${planKey}" is missing from the catalog`);
79
+ return [planKey, withPriceIds(plan, STRIPE_PRICE_OXYGEN_IDS[planKey])];
80
+ }));
81
+ export function getPlanPriceId(plan, currency) {
82
+ return (plan.priceIds[currency]
83
+ || (currency === DEFAULT_BILLING_CURRENCY ? plan.priceId : ""));
84
+ }
85
+ /**
86
+ * Resolve a Stripe price id to the plan key stored in `subscriptions.tier`.
87
+ *
88
+ * Returns a plan KEY ("oxygen_499") for the six purchasable rungs and a TIER
89
+ * ("starter", "growth_250") for grandfathered plans. Both are resolvable by
90
+ * resolveBasePricingPlan, which is what the credit grant reads — so the rung a
91
+ * customer paid for is the allowance they receive.
92
+ */
93
+ export function tierFromPriceId(priceId) {
94
+ // An absent/unset Stripe price is "" on every map below (each env read falls
95
+ // back to ""), so without this guard an empty id matches the FIRST unset rung
96
+ // and silently provisions a paid plan. Never resolve a tier from no price.
97
+ if (!priceId)
98
+ return null;
99
+ for (const planKey of PURCHASABLE_PLAN_KEYS) {
100
+ if (Object.values(STRIPE_PRICE_OXYGEN_IDS[planKey]).includes(priceId)) {
101
+ return planKey;
102
+ }
103
+ }
104
+ for (const tier of SELF_SERVE_PLAN_TIERS) {
105
+ const plan = PRICING_PLANS[tier];
106
+ if (plan.priceId === priceId
107
+ || Object.values(plan.priceIds).includes(priceId)) {
108
+ return tier;
109
+ }
110
+ }
111
+ for (const [tier, priceIds] of Object.entries(LEGACY_STRIPE_PRICE_IDS)) {
112
+ if (Object.values(priceIds).includes(priceId))
113
+ return tier;
114
+ }
115
+ for (const [tier, priceIds] of Object.entries(LEGACY_CURRENT_PLAN_PRICE_IDS)) {
116
+ if (priceIds.includes(priceId))
117
+ return tier;
118
+ }
119
+ return null;
120
+ }
121
+ /**
122
+ * Complete runtime-recognized Price set grouped by the current self-serve
123
+ * family. Catalog audit/migration code consumes this instead of maintaining a
124
+ * second env map that can silently omit grandfathered subscriptions.
125
+ */
126
+ export function recognizedStripePriceIdsBySelfServeTier() {
127
+ const byTier = {
128
+ oxygen: [
129
+ ...PURCHASABLE_PLAN_KEYS.flatMap((planKey) => Object.values(STRIPE_PRICE_OXYGEN_IDS[planKey])),
130
+ ...LEGACY_CURRENT_PLAN_PRICE_IDS.oxygen,
131
+ ],
132
+ starter: [
133
+ ...Object.values(STRIPE_PRICE_STARTER_IDS),
134
+ ...LEGACY_CURRENT_PLAN_PRICE_IDS.starter,
135
+ ],
136
+ pro: [
137
+ ...Object.values(STRIPE_PRICE_PRO_IDS),
138
+ ...LEGACY_CURRENT_PLAN_PRICE_IDS.pro,
139
+ ],
140
+ team: [
141
+ ...Object.values(STRIPE_PRICE_TEAM_IDS),
142
+ ...LEGACY_CURRENT_PLAN_PRICE_IDS.team,
143
+ ],
144
+ };
145
+ for (const legacyTier of Object.keys(LEGACY_STRIPE_PRICE_IDS)) {
146
+ byTier[SELF_SERVE_TIER_BY_LEGACY_TIER[legacyTier]]
147
+ .push(...Object.values(LEGACY_STRIPE_PRICE_IDS[legacyTier]));
148
+ }
149
+ return {
150
+ oxygen: uniqueNonEmptyPriceIds(byTier.oxygen),
151
+ starter: uniqueNonEmptyPriceIds(byTier.starter),
152
+ pro: uniqueNonEmptyPriceIds(byTier.pro),
153
+ team: uniqueNonEmptyPriceIds(byTier.team),
154
+ };
155
+ }
156
+ function readCommaSeparatedPriceIds(value) {
157
+ if (!value)
158
+ return [];
159
+ return [...new Set(value.split(",").map((entry) => entry.trim()).filter(Boolean))];
160
+ }
161
+ function uniqueNonEmptyPriceIds(values) {
162
+ return [...new Set(values.map((entry) => entry.trim()).filter(Boolean))];
163
+ }
164
+ export function formatMonthlyPrice(priceCents, currency = DEFAULT_BILLING_CURRENCY) {
165
+ if (priceCents == null)
166
+ return "Custom";
167
+ if (priceCents === 0)
168
+ return "Free";
169
+ return new Intl.NumberFormat("en-US", {
170
+ style: "currency",
171
+ currency: currency.toUpperCase(),
172
+ maximumFractionDigits: 0,
173
+ }).format(priceCents / 100);
174
+ }
175
+ function formatCredits(credits) {
176
+ if (credits == null)
177
+ return "Custom credits";
178
+ return `${new Intl.NumberFormat("en-US").format(credits)} credits`;
179
+ }
180
+ export function getMonthlyAllowanceLabel(plan) {
181
+ if (plan.monthlyCredits == null)
182
+ return "Custom monthly credits";
183
+ return `${formatCredits(plan.monthlyCredits)} / month`;
184
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Which Stripe subscriptions are NOT an OXYGEN plan.
3
+ *
4
+ * OXYGEN runs three recurring rails on the same Stripe customer, and only one of
5
+ * them is a plan: the PLAN subscription (born from a Checkout Session),
6
+ * EMAIL INFRASTRUCTURE (`infra-subscription.ts`) and SENDING SEATS
7
+ * (`seat-subscription.ts`). The two rails are separate on purpose — a workspace
8
+ * with no plan at all must be able to hold managed inboxes and buy a sending
9
+ * seat — so a rail event is not a smaller version of a plan event, it is a
10
+ * different subject entirely, and plan-shaped handling of one is always wrong.
11
+ *
12
+ * THE WITNESS IS `metadata.oxygen_kind`. Both rails stamp it at
13
+ * `subscriptions.create` (the only two such call sites in the repo), and a plan
14
+ * subscription never carries it: a plan is born from a Checkout Session whose
15
+ * `subscription_data.metadata` carries organization_id/clerk_org_id and no
16
+ * `oxygen_kind`. (The third `oxygen_kind` in the codebase, `credit_topup`, rides
17
+ * a one-off `mode: "payment"` Checkout Session and never produces a
18
+ * subscription at all.) So the key's PRESENCE is a positive witness of "not a
19
+ * plan".
20
+ *
21
+ * PRESENCE, NOT A VALUE LIST, deliberately. Every previous gate matched the one
22
+ * value `"email_infra"`, which is why the seat rail — shipped later — walked
23
+ * straight into the plan handlers: adding a rail silently opted it INTO plan
24
+ * treatment, the wrong default for a safety gate. Testing presence means the
25
+ * next rail needs no edit here. This is the same reasoning `orphan-reconcile.ts`
26
+ * measured in production on 2026-09-06, where all 24 of the 24 daily
27
+ * `billing.subscription_health.unknown_plan_price` error lines were the same 4
28
+ * live sending-seat subscriptions x 6 cron runs.
29
+ *
30
+ * DELIBERATELY NARROW. This answers "did the subscription declare itself a
31
+ * non-plan rail", never "does it carry a recognised plan price". The catalog
32
+ * question stays with `tierFromPriceId` / `recognizedStripePlanItems`, and
33
+ * callers that need both keep asking both — `orphan-reconcile.ts` reclassifies
34
+ * only the zero-recognised-item half for exactly that reason.
35
+ */
36
+ type StripeSubscriptionMetadata = Readonly<Record<string, string | null | undefined>> | null | undefined;
37
+ /** The declared non-plan rail (`email_infra`, `sending_seats`, …), or null for a plan. */
38
+ export declare function nonPlanSubscriptionKind(metadata: StripeSubscriptionMetadata): string | null;
39
+ /** True when the subscription declared itself one of OXYGEN's non-plan rails. */
40
+ export declare function isNonPlanSubscriptionKind(metadata: StripeSubscriptionMetadata): boolean;
41
+ export {};
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Which Stripe subscriptions are NOT an OXYGEN plan.
3
+ *
4
+ * OXYGEN runs three recurring rails on the same Stripe customer, and only one of
5
+ * them is a plan: the PLAN subscription (born from a Checkout Session),
6
+ * EMAIL INFRASTRUCTURE (`infra-subscription.ts`) and SENDING SEATS
7
+ * (`seat-subscription.ts`). The two rails are separate on purpose — a workspace
8
+ * with no plan at all must be able to hold managed inboxes and buy a sending
9
+ * seat — so a rail event is not a smaller version of a plan event, it is a
10
+ * different subject entirely, and plan-shaped handling of one is always wrong.
11
+ *
12
+ * THE WITNESS IS `metadata.oxygen_kind`. Both rails stamp it at
13
+ * `subscriptions.create` (the only two such call sites in the repo), and a plan
14
+ * subscription never carries it: a plan is born from a Checkout Session whose
15
+ * `subscription_data.metadata` carries organization_id/clerk_org_id and no
16
+ * `oxygen_kind`. (The third `oxygen_kind` in the codebase, `credit_topup`, rides
17
+ * a one-off `mode: "payment"` Checkout Session and never produces a
18
+ * subscription at all.) So the key's PRESENCE is a positive witness of "not a
19
+ * plan".
20
+ *
21
+ * PRESENCE, NOT A VALUE LIST, deliberately. Every previous gate matched the one
22
+ * value `"email_infra"`, which is why the seat rail — shipped later — walked
23
+ * straight into the plan handlers: adding a rail silently opted it INTO plan
24
+ * treatment, the wrong default for a safety gate. Testing presence means the
25
+ * next rail needs no edit here. This is the same reasoning `orphan-reconcile.ts`
26
+ * measured in production on 2026-09-06, where all 24 of the 24 daily
27
+ * `billing.subscription_health.unknown_plan_price` error lines were the same 4
28
+ * live sending-seat subscriptions x 6 cron runs.
29
+ *
30
+ * DELIBERATELY NARROW. This answers "did the subscription declare itself a
31
+ * non-plan rail", never "does it carry a recognised plan price". The catalog
32
+ * question stays with `tierFromPriceId` / `recognizedStripePlanItems`, and
33
+ * callers that need both keep asking both — `orphan-reconcile.ts` reclassifies
34
+ * only the zero-recognised-item half for exactly that reason.
35
+ */
36
+ /** The declared non-plan rail (`email_infra`, `sending_seats`, …), or null for a plan. */
37
+ export function nonPlanSubscriptionKind(metadata) {
38
+ const kind = metadata?.oxygen_kind;
39
+ return typeof kind === "string" && kind.length > 0 ? kind : null;
40
+ }
41
+ /** True when the subscription declared itself one of OXYGEN's non-plan rails. */
42
+ export function isNonPlanSubscriptionKind(metadata) {
43
+ return nonPlanSubscriptionKind(metadata) !== null;
44
+ }
@@ -1,4 +1,7 @@
1
1
  export declare const MAX_TABLE_ACTION_RUN_ROWS = 500000;
2
+ /** Only runs whose resolved steps are all provider-free formulas may use these ceilings. */
3
+ export declare const MAX_FORMULA_ACTION_RUN_ROWS = 1000000;
4
+ export declare const MAX_FORMULA_ACTION_RUN_ITEMS = 5000000;
2
5
  export declare const MAX_WORKSPACE_ROW_DELETE_ROWS = 50000;
3
6
  /**
4
7
  * Platform ceiling for a table action run's `max_concurrency` (T-27).
@@ -1,6 +1,9 @@
1
1
  // Shared request/action ceilings used by tenant-db enforcement and every
2
2
  // surface that resolves a symbolic row selection before invoking it.
3
3
  export const MAX_TABLE_ACTION_RUN_ROWS = 500_000;
4
+ /** Only runs whose resolved steps are all provider-free formulas may use these ceilings. */
5
+ export const MAX_FORMULA_ACTION_RUN_ROWS = 1_000_000;
6
+ export const MAX_FORMULA_ACTION_RUN_ITEMS = 5_000_000;
4
7
  export const MAX_WORKSPACE_ROW_DELETE_ROWS = 50_000;
5
8
  /**
6
9
  * Platform ceiling for a table action run's `max_concurrency` (T-27).
@@ -0,0 +1,94 @@
1
+ import { DiagLogLevel, type DiagLogger } from "@opentelemetry/api";
2
+ /**
3
+ * Makes a failing OTLP trace/metric export visible in LOGS.
4
+ *
5
+ * The SDK exporters report a failed export only through their result callback,
6
+ * and the processors hand that to `diag`, which has no logger unless one is
7
+ * installed — so a broken collector path (SigNoz over the private network) and a
8
+ * healthy one look identical from the sending process. This wraps an exporter so
9
+ * a failed export becomes one throttled `telemetry.export_failed` line and the
10
+ * first success afterwards one `telemetry.export_recovered` line, both through
11
+ * `log()` and therefore through whichever log sinks the process has.
12
+ *
13
+ * No recursion: `log()` records go to the LOG sinks, never to a trace or metric
14
+ * exporter, so a failing trace/metric lane cannot feed itself.
15
+ *
16
+ * Never throws, never changes what the SDK sees: the inner result is passed to
17
+ * the SDK callback untouched, every other exporter method is forwarded, and a
18
+ * synchronous throw from the inner exporter is recorded and rethrown exactly as
19
+ * it would have been without the wrapper.
20
+ *
21
+ * Nothing from the error's text is logged. OTLP errors carry receiver response
22
+ * bodies (`OTLPExporterError.data`) and transport errors can carry URLs; the
23
+ * failure is reduced to an HTTP status and a fixed code (see
24
+ * `classifyTelemetryExportError`), and the endpoint to origin + path.
25
+ *
26
+ * Structural types, not `@opentelemetry/core`/`sdk-metrics` imports: this
27
+ * package depends on neither, and the shapes used here (`{ code, error }`, the
28
+ * `ResourceMetrics` tree) are the SDK's stable public contract.
29
+ */
30
+ export type TelemetryExportSignal = "traces" | "metrics";
31
+ export type TelemetryExportSurface = "web" | "worker";
32
+ type ExportResultLike = {
33
+ code: number;
34
+ error?: Error | undefined;
35
+ };
36
+ type ObservableExporter = {
37
+ export(items: unknown, resultCallback: (result: ExportResultLike) => void): void;
38
+ shutdown(): Promise<void>;
39
+ };
40
+ export type TelemetryExportObserverOptions = {
41
+ signal: TelemetryExportSignal;
42
+ /** The exporter's full URL; only origin + path is ever logged. */
43
+ endpoint: string;
44
+ surface: TelemetryExportSurface;
45
+ /** At most one `telemetry.export_failed` line per window. Default 60 s. */
46
+ windowMs?: number | undefined;
47
+ now?: (() => number) | undefined;
48
+ };
49
+ /** Origin + path only: never query strings, fragments or URL credentials. */
50
+ export declare function telemetryEndpointForLog(endpoint: string): string;
51
+ /**
52
+ * Reduces an OTLP export error to `{ status?, error_code }`. `status` is the
53
+ * HTTP status an `OTLPExporterError` carries in `code` for a non-retryable
54
+ * response; a retryable one (429/502/503/504) that exhausted its retries carries
55
+ * none, hence `http_retryable`. The message is matched against the exporter's
56
+ * own fixed strings and never copied.
57
+ */
58
+ export declare function classifyTelemetryExportError(error: unknown): {
59
+ status?: number;
60
+ error_code: string;
61
+ };
62
+ export declare function observeTelemetryExporter<E extends ObservableExporter>(inner: E, options: TelemetryExportObserverOptions): E;
63
+ export type TelemetryDropDiagLoggerOptions = {
64
+ surface: TelemetryExportSurface;
65
+ /** An existing diagnostics logger that still receives every call unchanged. */
66
+ next?: DiagLogger | undefined;
67
+ windowMs?: number | undefined;
68
+ now?: (() => number) | undefined;
69
+ };
70
+ /**
71
+ * A `diag` logger that recognises exactly one message: the BatchSpanProcessor's
72
+ * `Dropped N spans because maxQueueSize reached`, its only report of queue-full
73
+ * drops. Those become a throttled `telemetry.export_dropped` line carrying the
74
+ * count; every other diag call is ignored, because SDK diagnostics can carry
75
+ * receiver responses and exported payloads (the OTLP delegate logs the items it
76
+ * is about to send at debug). Nothing but the parsed count leaves this function.
77
+ *
78
+ * The count cannot name which BatchSpanProcessor dropped: diag is process-wide,
79
+ * so when a process also runs the Axiom processor, a drop line covers both.
80
+ *
81
+ * `next` keeps an operator-enabled diagnostics logger working: it receives every
82
+ * call as before, and this logger only adds the drop count.
83
+ */
84
+ export declare function createTelemetryDropDiagLogger(options: TelemetryDropDiagLoggerOptions): DiagLogger;
85
+ /**
86
+ * Installs `createTelemetryDropDiagLogger` as the global diag logger, once per
87
+ * process. Returns false when it was already installed. Callers install it only
88
+ * when an OTLP collector endpoint is configured, so a process without one keeps
89
+ * its diag configuration exactly as it was.
90
+ */
91
+ export declare function installTelemetryDropDiagLogger(options: TelemetryDropDiagLoggerOptions & {
92
+ logLevel?: DiagLogLevel | undefined;
93
+ }): boolean;
94
+ export {};
@@ -0,0 +1,298 @@
1
+ import { diag, DiagLogLevel } from "@opentelemetry/api";
2
+ import { log } from "./log.js";
3
+ // @opentelemetry/core ExportResultCode.SUCCESS / FAILED.
4
+ const EXPORT_RESULT_SUCCESS = 0;
5
+ const DEFAULT_WINDOW_MS = 60_000;
6
+ // Methods the SDK may call on an exporter besides `export`. Forwarded only when
7
+ // the inner exporter has them, so a reader that probes for an optional method
8
+ // (PeriodicExportingMetricReader reads selectAggregationTemporality) sees
9
+ // exactly what it would have seen on the unwrapped exporter.
10
+ const FORWARDED_METHODS = [
11
+ "forceFlush",
12
+ "selectAggregationTemporality",
13
+ "selectAggregation",
14
+ ];
15
+ /** Origin + path only: never query strings, fragments or URL credentials. */
16
+ export function telemetryEndpointForLog(endpoint) {
17
+ try {
18
+ const url = new URL(endpoint);
19
+ return `${url.origin}${url.pathname}`;
20
+ }
21
+ catch {
22
+ return "invalid_url";
23
+ }
24
+ }
25
+ const SYSTEM_ERROR_CODES = {
26
+ ETIMEDOUT: "timeout",
27
+ ESOCKETTIMEDOUT: "timeout",
28
+ ECONNREFUSED: "connection_refused",
29
+ ECONNRESET: "connection_reset",
30
+ EPIPE: "connection_reset",
31
+ ENOTFOUND: "dns_failed",
32
+ EAI_AGAIN: "dns_failed",
33
+ EHOSTUNREACH: "host_unreachable",
34
+ ENETUNREACH: "host_unreachable",
35
+ };
36
+ /**
37
+ * Reduces an OTLP export error to `{ status?, error_code }`. `status` is the
38
+ * HTTP status an `OTLPExporterError` carries in `code` for a non-retryable
39
+ * response; a retryable one (429/502/503/504) that exhausted its retries carries
40
+ * none, hence `http_retryable`. The message is matched against the exporter's
41
+ * own fixed strings and never copied.
42
+ */
43
+ export function classifyTelemetryExportError(error) {
44
+ if (!error || typeof error !== "object")
45
+ return { error_code: "unknown" };
46
+ const candidate = error;
47
+ const code = candidate.code;
48
+ if (typeof code === "number" && Number.isInteger(code) && code >= 100 && code <= 599) {
49
+ return { status: code, error_code: code >= 500 ? "http_5xx" : `http_${code}` };
50
+ }
51
+ const systemCode = typeof code === "string"
52
+ ? code
53
+ : typeof candidate.cause?.code === "string"
54
+ ? candidate.cause.code
55
+ : undefined;
56
+ if (systemCode)
57
+ return { error_code: SYSTEM_ERROR_CODES[systemCode] ?? "transport_failed" };
58
+ const message = typeof candidate.message === "string" ? candidate.message : "";
59
+ if (candidate.name === "TimeoutError" || /timed out|timeout/i.test(message))
60
+ return { error_code: "timeout" };
61
+ if (/concurren(?:t|cy) (?:export )?limit/i.test(message))
62
+ return { error_code: "concurrency_limit" };
63
+ if (/retryable status/i.test(message))
64
+ return { error_code: "http_retryable" };
65
+ if (/exceeded size limit/i.test(message))
66
+ return { error_code: "response_too_large" };
67
+ return { error_code: "transport_failed" };
68
+ }
69
+ function countItems(signal, items) {
70
+ if (signal === "traces")
71
+ return Array.isArray(items) ? items.length : 0;
72
+ // Metric data points (= live series under cumulative temporality).
73
+ let points = 0;
74
+ const scopeMetrics = items?.scopeMetrics;
75
+ if (!Array.isArray(scopeMetrics))
76
+ return 0;
77
+ for (const scope of scopeMetrics) {
78
+ const metrics = scope?.metrics;
79
+ if (!Array.isArray(metrics))
80
+ continue;
81
+ for (const metric of metrics) {
82
+ const dataPoints = metric?.dataPoints;
83
+ if (Array.isArray(dataPoints))
84
+ points += dataPoints.length;
85
+ }
86
+ }
87
+ return points;
88
+ }
89
+ export function observeTelemetryExporter(inner, options) {
90
+ const endpoint = telemetryEndpointForLog(options.endpoint);
91
+ const windowMs = options.windowMs ?? DEFAULT_WINDOW_MS;
92
+ const now = options.now ?? Date.now;
93
+ const { signal, surface } = options;
94
+ let consecutiveFailures = 0;
95
+ let outageStartedAt = 0;
96
+ let outageDropped = 0;
97
+ // Only an outage that produced an export_failed line gets a recovery line, so
98
+ // a flapping endpoint cannot bypass the failure throttle through recoveries.
99
+ let outageReported = false;
100
+ let lastFailureLogAt = Number.NEGATIVE_INFINITY;
101
+ // Accumulated since the last emitted export_failed line, reported on the next.
102
+ let suppressedFailures = 0;
103
+ let pendingDropped = 0;
104
+ const recordFailure = (error, dropped) => {
105
+ try {
106
+ const at = now();
107
+ if (consecutiveFailures === 0) {
108
+ outageStartedAt = at;
109
+ outageDropped = 0;
110
+ outageReported = false;
111
+ }
112
+ consecutiveFailures += 1;
113
+ outageDropped += dropped;
114
+ pendingDropped += dropped;
115
+ if (at - lastFailureLogAt < windowMs) {
116
+ suppressedFailures += 1;
117
+ return;
118
+ }
119
+ lastFailureLogAt = at;
120
+ outageReported = true;
121
+ const { status, error_code } = classifyTelemetryExportError(error);
122
+ log("warn", "telemetry.export_failed", {
123
+ signal,
124
+ endpoint,
125
+ ...(status === undefined ? {} : { status }),
126
+ error_code,
127
+ dropped: pendingDropped,
128
+ consecutive_failures: consecutiveFailures,
129
+ suppressed_failures: suppressedFailures,
130
+ surface,
131
+ });
132
+ pendingDropped = 0;
133
+ suppressedFailures = 0;
134
+ }
135
+ catch {
136
+ // Observation must never change export semantics.
137
+ }
138
+ };
139
+ const recordSuccess = () => {
140
+ try {
141
+ if (consecutiveFailures === 0)
142
+ return;
143
+ if (outageReported) {
144
+ log("info", "telemetry.export_recovered", {
145
+ signal,
146
+ endpoint,
147
+ failures: consecutiveFailures,
148
+ dropped: outageDropped,
149
+ outage_ms: Math.max(0, now() - outageStartedAt),
150
+ surface,
151
+ });
152
+ // Every suppressed failure since the last line belonged to this outage,
153
+ // and the recovery line has just accounted for it.
154
+ pendingDropped = 0;
155
+ suppressedFailures = 0;
156
+ }
157
+ consecutiveFailures = 0;
158
+ }
159
+ catch {
160
+ // Observation must never change export semantics.
161
+ }
162
+ };
163
+ const observed = {
164
+ export(items, resultCallback) {
165
+ let dropped = 0;
166
+ try {
167
+ dropped = countItems(signal, items);
168
+ }
169
+ catch {
170
+ // Counting is best-effort; never let it block the export itself.
171
+ }
172
+ try {
173
+ inner.export(items, (result) => {
174
+ if (result?.code === EXPORT_RESULT_SUCCESS)
175
+ recordSuccess();
176
+ else
177
+ recordFailure(result?.error, dropped);
178
+ resultCallback(result);
179
+ });
180
+ }
181
+ catch (error) {
182
+ recordFailure(error, dropped);
183
+ throw error;
184
+ }
185
+ },
186
+ shutdown() {
187
+ return inner.shutdown();
188
+ },
189
+ };
190
+ for (const method of FORWARDED_METHODS) {
191
+ const fn = inner[method];
192
+ if (typeof fn === "function")
193
+ observed[method] = fn.bind(inner);
194
+ }
195
+ return observed;
196
+ }
197
+ const DROPPED_SPANS_MESSAGE = /^Dropped (\d+) spans because maxQueueSize reached$/;
198
+ /**
199
+ * A `diag` logger that recognises exactly one message: the BatchSpanProcessor's
200
+ * `Dropped N spans because maxQueueSize reached`, its only report of queue-full
201
+ * drops. Those become a throttled `telemetry.export_dropped` line carrying the
202
+ * count; every other diag call is ignored, because SDK diagnostics can carry
203
+ * receiver responses and exported payloads (the OTLP delegate logs the items it
204
+ * is about to send at debug). Nothing but the parsed count leaves this function.
205
+ *
206
+ * The count cannot name which BatchSpanProcessor dropped: diag is process-wide,
207
+ * so when a process also runs the Axiom processor, a drop line covers both.
208
+ *
209
+ * `next` keeps an operator-enabled diagnostics logger working: it receives every
210
+ * call as before, and this logger only adds the drop count.
211
+ */
212
+ export function createTelemetryDropDiagLogger(options) {
213
+ const windowMs = options.windowMs ?? DEFAULT_WINDOW_MS;
214
+ const now = options.now ?? Date.now;
215
+ const { next, surface } = options;
216
+ let pending = 0;
217
+ let lastEmitAt = Number.NEGATIVE_INFINITY;
218
+ let timer = null;
219
+ let emitting = false;
220
+ const emit = () => {
221
+ if (pending === 0 || emitting)
222
+ return;
223
+ emitting = true;
224
+ try {
225
+ lastEmitAt = now();
226
+ const dropped = pending;
227
+ pending = 0;
228
+ log("warn", "telemetry.export_dropped", { signal: "traces", dropped, reason: "queue_full", surface });
229
+ }
230
+ finally {
231
+ emitting = false;
232
+ }
233
+ };
234
+ const record = (dropped) => {
235
+ pending += dropped;
236
+ const remaining = windowMs - (now() - lastEmitAt);
237
+ if (remaining <= 0) {
238
+ emit();
239
+ return;
240
+ }
241
+ // The BatchSpanProcessor reports a drop run only once it ends, so a
242
+ // suppressed count could otherwise wait forever for another drop to carry it.
243
+ if (timer)
244
+ return;
245
+ timer = setTimeout(() => {
246
+ timer = null;
247
+ try {
248
+ emit();
249
+ }
250
+ catch {
251
+ // Telemetry about telemetry never throws.
252
+ }
253
+ }, remaining);
254
+ timer.unref?.();
255
+ };
256
+ const forward = (level) => (message, ...args) => {
257
+ if (level === "warn") {
258
+ try {
259
+ const match = typeof message === "string" ? DROPPED_SPANS_MESSAGE.exec(message) : null;
260
+ const dropped = match ? Number(match[1]) : 0;
261
+ if (Number.isSafeInteger(dropped) && dropped > 0)
262
+ record(dropped);
263
+ }
264
+ catch {
265
+ // Telemetry about telemetry never throws.
266
+ }
267
+ }
268
+ next?.[level](message, ...args);
269
+ };
270
+ return {
271
+ error: forward("error"),
272
+ warn: forward("warn"),
273
+ info: forward("info"),
274
+ debug: forward("debug"),
275
+ verbose: forward("verbose"),
276
+ };
277
+ }
278
+ // globalThis, not a module-local: Next.js bundles can load this module more
279
+ // than once per process, and a second `diag.setLogger` would replace the first
280
+ // (and, without suppressOverrideMessage, log an override warning).
281
+ const DIAG_BRIDGE_SLOT = Symbol.for("oxygen.telemetry.diag_drop_bridge");
282
+ /**
283
+ * Installs `createTelemetryDropDiagLogger` as the global diag logger, once per
284
+ * process. Returns false when it was already installed. Callers install it only
285
+ * when an OTLP collector endpoint is configured, so a process without one keeps
286
+ * its diag configuration exactly as it was.
287
+ */
288
+ export function installTelemetryDropDiagLogger(options) {
289
+ const carrier = globalThis;
290
+ if (carrier[DIAG_BRIDGE_SLOT])
291
+ return false;
292
+ carrier[DIAG_BRIDGE_SLOT] = true;
293
+ diag.setLogger(createTelemetryDropDiagLogger(options), {
294
+ logLevel: options.logLevel ?? DiagLogLevel.WARN,
295
+ suppressOverrideMessage: true,
296
+ });
297
+ return true;
298
+ }