@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.
- package/README.md +1 -1
- package/dist/admin-primary-providers-render.d.ts +0 -2
- package/dist/admin-primary-providers-render.js +1 -1
- package/dist/browser-login.js +1 -4
- package/dist/column-decision-options.d.ts +20 -0
- package/dist/column-decision-options.js +54 -0
- package/dist/command-manifest.d.ts +3 -2
- package/dist/command-manifest.js +25 -2
- package/dist/credentials.d.ts +1 -1
- package/dist/functions-commands.js +13 -9
- package/dist/help.d.ts +8 -0
- package/dist/help.js +46 -0
- package/dist/index.js +2751 -242
- package/dist/knowledge-mirror.d.ts +2 -2
- package/dist/runtime.d.ts +0 -15
- package/dist/runtime.js +1 -1
- package/dist/search-ai-filter-notice.d.ts +17 -0
- package/dist/search-ai-filter-notice.js +38 -0
- package/dist/session.d.ts +4 -3
- package/dist/skills.d.ts +8 -7
- package/dist/skills.js +58 -20
- package/dist/transcript.d.ts +2 -1
- package/dist/util.d.ts +10 -1
- package/dist/util.js +14 -2
- package/node_modules/@oxygen/cli-ugc/dist/commands.js +296 -140
- package/node_modules/@oxygen/cli-ugc/dist/field-parser.d.ts +9 -0
- package/node_modules/@oxygen/cli-ugc/dist/field-parser.js +34 -0
- package/node_modules/@oxygen/formula/dist/coerce.d.ts +10 -0
- package/node_modules/@oxygen/formula/dist/coerce.js +10 -0
- package/node_modules/@oxygen/formula/dist/formula-functions.js +65 -0
- package/node_modules/@oxygen/formula/dist/hash.d.ts +19 -0
- package/node_modules/@oxygen/formula/dist/hash.js +199 -0
- package/node_modules/@oxygen/formula/dist/value-cleaners.d.ts +6 -1
- package/node_modules/@oxygen/formula/dist/value-cleaners.js +10 -26
- package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +13 -0
- package/node_modules/@oxygen/shared/dist/array-utils.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/array-utils.js +11 -0
- package/node_modules/@oxygen/shared/dist/billing.d.ts +99 -22
- package/node_modules/@oxygen/shared/dist/billing.js +195 -40
- package/node_modules/@oxygen/shared/dist/byok-connect.js +5 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +27 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +311 -28
- package/node_modules/@oxygen/shared/dist/cli-http-error.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/cli-http-error.js +8 -0
- package/node_modules/@oxygen/shared/dist/cli-result.js +1 -0
- package/node_modules/@oxygen/shared/dist/column-autofill.d.ts +52 -0
- package/node_modules/@oxygen/shared/dist/column-autofill.js +62 -0
- package/node_modules/@oxygen/shared/dist/column-decision.d.ts +50 -0
- package/node_modules/@oxygen/shared/dist/column-decision.js +228 -0
- package/node_modules/@oxygen/shared/dist/column-output-fields.js +2 -6
- package/node_modules/@oxygen/shared/dist/company-enrichment-fields.d.ts +113 -0
- package/node_modules/@oxygen/shared/dist/company-enrichment-fields.js +548 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.d.ts +6 -6
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.js +6 -6
- package/node_modules/@oxygen/shared/dist/cutover-freeze.d.ts +26 -0
- package/node_modules/@oxygen/shared/dist/cutover-freeze.js +52 -0
- package/node_modules/@oxygen/shared/dist/data-suppliers.d.ts +57 -0
- package/node_modules/@oxygen/shared/dist/data-suppliers.js +59 -0
- package/node_modules/@oxygen/shared/dist/deploy-env.d.ts +74 -0
- package/node_modules/@oxygen/shared/dist/deploy-env.js +82 -0
- package/node_modules/@oxygen/shared/dist/dnc-rules.d.ts +130 -0
- package/node_modules/@oxygen/shared/dist/dnc-rules.js +221 -0
- package/node_modules/@oxygen/shared/dist/enrichment-intents.d.ts +107 -0
- package/node_modules/@oxygen/shared/dist/enrichment-intents.js +809 -0
- package/node_modules/@oxygen/shared/dist/error-message.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/error-message.js +3 -0
- package/node_modules/@oxygen/shared/dist/error-redaction.js +1 -3
- package/node_modules/@oxygen/shared/dist/external-write-policy.d.ts +33 -0
- package/node_modules/@oxygen/shared/dist/external-write-policy.js +68 -0
- package/node_modules/@oxygen/shared/dist/format-percent.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/format-percent.js +13 -0
- package/node_modules/@oxygen/shared/dist/freemail-domains.d.ts +81 -0
- package/node_modules/@oxygen/shared/dist/freemail-domains.js +157 -0
- package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.js +1 -1
- package/node_modules/@oxygen/shared/dist/hosted-ai.d.ts +60 -4
- package/node_modules/@oxygen/shared/dist/hosted-ai.js +125 -10
- package/node_modules/@oxygen/shared/dist/index.d.ts +15 -0
- package/node_modules/@oxygen/shared/dist/index.js +15 -0
- package/node_modules/@oxygen/shared/dist/json-path.js +1 -3
- package/node_modules/@oxygen/shared/dist/knowledge-bases.js +1 -3
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +2 -2
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +2 -2
- package/node_modules/@oxygen/shared/dist/langfuse.d.ts +44 -1
- package/node_modules/@oxygen/shared/dist/langfuse.js +416 -14
- package/node_modules/@oxygen/shared/dist/linkedin-countries.d.ts +33 -0
- package/node_modules/@oxygen/shared/dist/linkedin-countries.js +361 -0
- package/node_modules/@oxygen/shared/dist/linkedin-country-timezones.d.ts +24 -0
- package/node_modules/@oxygen/shared/dist/linkedin-country-timezones.js +276 -0
- package/node_modules/@oxygen/shared/dist/linkedin-message-deletion.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/linkedin-message-deletion.js +5 -0
- package/node_modules/@oxygen/shared/dist/linkedin-post-keywords.d.ts +44 -0
- package/node_modules/@oxygen/shared/dist/linkedin-post-keywords.js +116 -0
- package/node_modules/@oxygen/shared/dist/linkedin-sequences.d.ts +96 -0
- package/node_modules/@oxygen/shared/dist/linkedin-sequences.js +123 -0
- package/node_modules/@oxygen/shared/dist/llm-durable-capture.d.ts +24 -0
- package/node_modules/@oxygen/shared/dist/llm-durable-capture.js +89 -0
- package/node_modules/@oxygen/shared/dist/llm-prompts.d.ts +75 -0
- package/node_modules/@oxygen/shared/dist/llm-prompts.js +161 -0
- package/node_modules/@oxygen/shared/dist/log-sink-selector.d.ts +39 -0
- package/node_modules/@oxygen/shared/dist/log-sink-selector.js +56 -0
- package/node_modules/@oxygen/shared/dist/log.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/log.js +6 -1
- package/node_modules/@oxygen/shared/dist/mailbox-egress-ownership.d.ts +90 -0
- package/node_modules/@oxygen/shared/dist/mailbox-egress-ownership.js +130 -0
- package/node_modules/@oxygen/shared/dist/object-storage.d.ts +17 -0
- package/node_modules/@oxygen/shared/dist/object-storage.js +21 -0
- package/node_modules/@oxygen/shared/dist/operational-telemetry.d.ts +24 -0
- package/node_modules/@oxygen/shared/dist/operational-telemetry.js +73 -0
- package/node_modules/@oxygen/shared/dist/otlp-log-sink.d.ts +79 -0
- package/node_modules/@oxygen/shared/dist/otlp-log-sink.js +366 -0
- package/node_modules/@oxygen/shared/dist/plan-capabilities.js +1 -0
- package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +23 -22
- package/node_modules/@oxygen/shared/dist/plan-limits.js +45 -18
- package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +48 -41
- package/node_modules/@oxygen/shared/dist/pricing-sheet.js +36 -25
- package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.d.ts +22 -22
- package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.js +40 -34
- package/node_modules/@oxygen/shared/dist/product-analytics-environment.js +9 -0
- package/node_modules/@oxygen/shared/dist/product-analytics-events.d.ts +36 -2
- package/node_modules/@oxygen/shared/dist/product-analytics-events.js +36 -1
- package/node_modules/@oxygen/shared/dist/rate-window.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/rate-window.js +8 -0
- package/node_modules/@oxygen/shared/dist/redaction.js +4 -1
- package/node_modules/@oxygen/shared/dist/research-output-contract.js +1 -3
- package/node_modules/@oxygen/shared/dist/scraper-lane-credential.d.ts +18 -0
- package/node_modules/@oxygen/shared/dist/scraper-lane-credential.js +23 -0
- package/node_modules/@oxygen/shared/dist/search-vocab.js +4 -5
- package/node_modules/@oxygen/shared/dist/select-options.js +6 -1
- package/node_modules/@oxygen/shared/dist/sequence-failures.js +1 -5
- package/node_modules/@oxygen/shared/dist/sequences.d.ts +23 -0
- package/node_modules/@oxygen/shared/dist/sequences.js +115 -5
- package/node_modules/@oxygen/shared/dist/signup-lead-payload.d.ts +80 -0
- package/node_modules/@oxygen/shared/dist/signup-lead-payload.js +198 -0
- package/node_modules/@oxygen/shared/dist/social-capabilities.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/social-capabilities.js +25 -16
- package/node_modules/@oxygen/shared/dist/social-post-metrics-core.d.ts +32 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-core.js +32 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-linkedin.d.ts +31 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-linkedin.js +103 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-series.d.ts +96 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-series.js +213 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-x.d.ts +13 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-x.js +78 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics.d.ts +36 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics.js +51 -0
- package/node_modules/@oxygen/shared/dist/spend-safety.d.ts +22 -10
- package/node_modules/@oxygen/shared/dist/spend-safety.js +15 -21
- package/node_modules/@oxygen/shared/dist/sql-rows.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/sql-rows.js +3 -0
- package/node_modules/@oxygen/shared/dist/stripe-price-catalog.d.ts +36 -0
- package/node_modules/@oxygen/shared/dist/stripe-price-catalog.js +184 -0
- package/node_modules/@oxygen/shared/dist/stripe-subscription-kind.d.ts +41 -0
- package/node_modules/@oxygen/shared/dist/stripe-subscription-kind.js +44 -0
- package/node_modules/@oxygen/shared/dist/table-limits.d.ts +3 -0
- package/node_modules/@oxygen/shared/dist/table-limits.js +3 -0
- package/node_modules/@oxygen/shared/dist/telemetry-export-observer.d.ts +94 -0
- package/node_modules/@oxygen/shared/dist/telemetry-export-observer.js +298 -0
- package/node_modules/@oxygen/shared/dist/telemetry.d.ts +11 -0
- package/node_modules/@oxygen/shared/dist/telemetry.js +28 -2
- package/node_modules/@oxygen/shared/dist/type-guards.d.ts +22 -0
- package/node_modules/@oxygen/shared/dist/type-guards.js +35 -0
- package/node_modules/@oxygen/shared/dist/ugc.d.ts +22 -11
- package/node_modules/@oxygen/shared/dist/ugc.js +10 -0
- package/node_modules/@oxygen/shared/dist/value-readers.d.ts +21 -0
- package/node_modules/@oxygen/shared/dist/value-readers.js +59 -0
- package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/version.generated.js +2 -0
- package/node_modules/@oxygen/shared/dist/version.js +8 -1
- package/node_modules/@oxygen/shared/dist/workspace-event-catalog.js +0 -23
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.d.ts +29 -0
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.js +31 -0
- package/node_modules/@oxygen/shared/package.json +59 -0
- package/node_modules/@oxygen/workflows/dist/graph/expression.js +2 -5
- package/node_modules/@oxygen/workflows/dist/graph/params.js +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
export const EARNED_SERIES_MAX_PRORATE_MS = 3 * 24 * 60 * 60 * 1000;
|
|
2
|
+
function pointsFor(timeline, metric) {
|
|
3
|
+
const points = [];
|
|
4
|
+
const first = timeline.observations.find((observation) => observation.metrics[metric] !== undefined);
|
|
5
|
+
if (!first)
|
|
6
|
+
return points;
|
|
7
|
+
if (timeline.publishedAt && timeline.publishedAt.getTime() <= first.observedAt.getTime()) {
|
|
8
|
+
points.push({ from: timeline.publishedAt.getTime(), to: timeline.publishedAt.getTime(), value: 0, anchor: true });
|
|
9
|
+
}
|
|
10
|
+
for (const observation of timeline.observations) {
|
|
11
|
+
const value = observation.metrics[metric];
|
|
12
|
+
if (value === undefined)
|
|
13
|
+
continue;
|
|
14
|
+
points.push({ from: observation.observedAt.getTime(), to: observation.confirmedAt.getTime(), value, anchor: false });
|
|
15
|
+
}
|
|
16
|
+
return points;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Earned per day for one metric, or the sum of several (engagements), across a
|
|
20
|
+
* set of posts. `dayStarts` are the instants each local day begins; `windowEnd`
|
|
21
|
+
* closes the last day.
|
|
22
|
+
*/
|
|
23
|
+
export function computeEarnedWindow(input) {
|
|
24
|
+
const maxProrateMs = input.maxProrateMs ?? EARNED_SERIES_MAX_PRORATE_MS;
|
|
25
|
+
const bounds = input.dayStarts.map((day, index) => ({
|
|
26
|
+
date: day.date,
|
|
27
|
+
start: day.start.getTime(),
|
|
28
|
+
end: (input.dayStarts[index + 1]?.start ?? input.windowEnd).getTime(),
|
|
29
|
+
}));
|
|
30
|
+
const windowStart = bounds[0]?.start ?? input.windowEnd.getTime();
|
|
31
|
+
const windowEnd = input.windowEnd.getTime();
|
|
32
|
+
const values = bounds.map(() => 0);
|
|
33
|
+
const estimated = bounds.map(() => false);
|
|
34
|
+
const tracked = bounds.map(() => new Set());
|
|
35
|
+
let unattributed = 0;
|
|
36
|
+
let decreases = 0;
|
|
37
|
+
let reporting = 0;
|
|
38
|
+
input.timelines.forEach((timeline, postIndex) => {
|
|
39
|
+
let reports = false;
|
|
40
|
+
for (const metric of input.metrics) {
|
|
41
|
+
const points = pointsFor(timeline, metric);
|
|
42
|
+
if (points.length === 0)
|
|
43
|
+
continue;
|
|
44
|
+
reports = true;
|
|
45
|
+
// A post is tracked on a day only where its earning is known: a flat
|
|
46
|
+
// interval it was observed through, or a short gap between two reads.
|
|
47
|
+
const markTracked = (from, to) => {
|
|
48
|
+
bounds.forEach((day, index) => {
|
|
49
|
+
if (day.start <= to && day.end > from)
|
|
50
|
+
tracked[index].add(postIndex);
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
for (const point of points)
|
|
54
|
+
if (!point.anchor)
|
|
55
|
+
markTracked(point.from, point.to);
|
|
56
|
+
for (let index = 1; index < points.length; index += 1) {
|
|
57
|
+
const previous = points[index - 1];
|
|
58
|
+
const current = points[index];
|
|
59
|
+
const delta = current.value - previous.value;
|
|
60
|
+
if (delta === 0)
|
|
61
|
+
continue;
|
|
62
|
+
if (delta < 0)
|
|
63
|
+
decreases += 1;
|
|
64
|
+
const gapStart = Math.min(previous.to, current.from);
|
|
65
|
+
const gapEnd = current.from;
|
|
66
|
+
const gap = gapEnd - gapStart;
|
|
67
|
+
if (gap <= 0) {
|
|
68
|
+
const day = bounds.findIndex((bound) => gapEnd >= bound.start && gapEnd < bound.end);
|
|
69
|
+
if (day >= 0)
|
|
70
|
+
values[day] += delta;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const insideWindow = Math.max(0, Math.min(gapEnd, windowEnd) - Math.max(gapStart, windowStart));
|
|
74
|
+
if (insideWindow === 0)
|
|
75
|
+
continue;
|
|
76
|
+
if (gap > maxProrateMs) {
|
|
77
|
+
unattributed += delta * (insideWindow / gap);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
markTracked(gapStart, gapEnd);
|
|
81
|
+
let spannedDays = 0;
|
|
82
|
+
bounds.forEach((bound, day) => {
|
|
83
|
+
const overlap = Math.max(0, Math.min(gapEnd, bound.end) - Math.max(gapStart, bound.start));
|
|
84
|
+
if (overlap === 0)
|
|
85
|
+
return;
|
|
86
|
+
spannedDays += 1;
|
|
87
|
+
values[day] += delta * (overlap / gap);
|
|
88
|
+
});
|
|
89
|
+
if (spannedDays > 1 || insideWindow < gap) {
|
|
90
|
+
bounds.forEach((bound, day) => {
|
|
91
|
+
if (Math.min(gapEnd, bound.end) > Math.max(gapStart, bound.start))
|
|
92
|
+
estimated[day] = true;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (reports)
|
|
98
|
+
reporting += 1;
|
|
99
|
+
});
|
|
100
|
+
const days = bounds.map((bound, index) => ({
|
|
101
|
+
date: bound.date,
|
|
102
|
+
value: tracked[index].size > 0 ? Math.round(values[index]) : null,
|
|
103
|
+
estimated: tracked[index].size > 0 && estimated[index],
|
|
104
|
+
postsTracked: tracked[index].size,
|
|
105
|
+
}));
|
|
106
|
+
// Round the window once from unrounded parts: per-day rounding would let a net
|
|
107
|
+
// change of -2 spread over two half-days read as -1.
|
|
108
|
+
const anyTracked = tracked.some((posts) => posts.size > 0);
|
|
109
|
+
const trackedSum = values.reduce((sum, value, index) => sum + (tracked[index].size > 0 ? value : 0), 0);
|
|
110
|
+
return {
|
|
111
|
+
days,
|
|
112
|
+
total: anyTracked || Math.round(unattributed) !== 0 ? Math.round(trackedSum + unattributed) : null,
|
|
113
|
+
unattributed: Math.round(unattributed),
|
|
114
|
+
decreases,
|
|
115
|
+
postsReporting: reporting,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* A post's cumulative metric (or sum of metrics) exactly `ageMs` after publishing,
|
|
120
|
+
* so posts can be compared at the same age ("impressions after 24 hours"). Exact
|
|
121
|
+
* inside a known-flat interval; interpolated linearly across a gap no longer than
|
|
122
|
+
* `maxProrateMs`; null when the age falls in a longer gap, before the first read,
|
|
123
|
+
* after the last confirmed read, or when the post has no publish time.
|
|
124
|
+
*/
|
|
125
|
+
export function valueAtAge(input) {
|
|
126
|
+
if (!input.timeline.publishedAt)
|
|
127
|
+
return null;
|
|
128
|
+
const maxProrateMs = input.maxProrateMs ?? EARNED_SERIES_MAX_PRORATE_MS;
|
|
129
|
+
const target = input.timeline.publishedAt.getTime() + input.ageMs;
|
|
130
|
+
let total = 0;
|
|
131
|
+
let interpolated = false;
|
|
132
|
+
for (const metric of input.metrics) {
|
|
133
|
+
const points = pointsFor(input.timeline, metric);
|
|
134
|
+
if (points.length === 0)
|
|
135
|
+
return null;
|
|
136
|
+
let found = null;
|
|
137
|
+
for (let index = 0; index < points.length && found === null; index += 1) {
|
|
138
|
+
const point = points[index];
|
|
139
|
+
if (target >= point.from && target <= point.to) {
|
|
140
|
+
found = point.value;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
const next = points[index + 1];
|
|
144
|
+
if (next && target > point.to && target < next.from) {
|
|
145
|
+
const gap = next.from - point.to;
|
|
146
|
+
if (gap > maxProrateMs)
|
|
147
|
+
return null;
|
|
148
|
+
found = point.value + (next.value - point.value) * ((target - point.to) / gap);
|
|
149
|
+
interpolated = true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (found === null)
|
|
153
|
+
return null;
|
|
154
|
+
total += found;
|
|
155
|
+
}
|
|
156
|
+
return { value: Math.round(total), interpolated };
|
|
157
|
+
}
|
|
158
|
+
/** Median of the values, or null for none. */
|
|
159
|
+
export function medianOf(values) {
|
|
160
|
+
if (values.length === 0)
|
|
161
|
+
return null;
|
|
162
|
+
const sorted = [...values].sort((left, right) => left - right);
|
|
163
|
+
const middle = Math.floor(sorted.length / 2);
|
|
164
|
+
return sorted.length % 2 === 1 ? sorted[middle] : Math.round((sorted[middle - 1] + sorted[middle]) / 2);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* The instant each calendar day in [fromDate, toDate] (inclusive, YYYY-MM-DD)
|
|
168
|
+
* starts in `timeZone`, plus the instant after the last day. DST-safe: each start
|
|
169
|
+
* is resolved against that day's own offset.
|
|
170
|
+
*/
|
|
171
|
+
export function zonedDayStarts(fromDate, toDate, timeZone) {
|
|
172
|
+
const days = [];
|
|
173
|
+
const cursor = new Date(`${fromDate}T00:00:00.000Z`);
|
|
174
|
+
const last = new Date(`${toDate}T00:00:00.000Z`);
|
|
175
|
+
while (cursor.getTime() <= last.getTime()) {
|
|
176
|
+
const date = cursor.toISOString().slice(0, 10);
|
|
177
|
+
days.push({ date, start: startOfZonedDay(date, timeZone) });
|
|
178
|
+
cursor.setUTCDate(cursor.getUTCDate() + 1);
|
|
179
|
+
}
|
|
180
|
+
return { days, end: startOfZonedDay(cursor.toISOString().slice(0, 10), timeZone) };
|
|
181
|
+
}
|
|
182
|
+
function zoneOffsetMs(instant, timeZone) {
|
|
183
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
184
|
+
timeZone,
|
|
185
|
+
hourCycle: "h23",
|
|
186
|
+
year: "numeric",
|
|
187
|
+
month: "2-digit",
|
|
188
|
+
day: "2-digit",
|
|
189
|
+
hour: "2-digit",
|
|
190
|
+
minute: "2-digit",
|
|
191
|
+
second: "2-digit",
|
|
192
|
+
}).formatToParts(instant);
|
|
193
|
+
const read = (type) => Number(parts.find((part) => part.type === type)?.value ?? 0);
|
|
194
|
+
const asUtc = Date.UTC(read("year"), read("month") - 1, read("day"), read("hour"), read("minute"), read("second"));
|
|
195
|
+
return asUtc - Math.floor(instant.getTime() / 1000) * 1000;
|
|
196
|
+
}
|
|
197
|
+
function startOfZonedDay(date, timeZone) {
|
|
198
|
+
const utcMidnight = new Date(`${date}T00:00:00.000Z`).getTime();
|
|
199
|
+
const firstGuess = utcMidnight - zoneOffsetMs(new Date(utcMidnight), timeZone);
|
|
200
|
+
return new Date(utcMidnight - zoneOffsetMs(new Date(firstGuess), timeZone));
|
|
201
|
+
}
|
|
202
|
+
/** An IANA zone Intl accepts, or null. Validated here so Postgres never sees a bad one. */
|
|
203
|
+
export function validTimeZone(value) {
|
|
204
|
+
if (!value)
|
|
205
|
+
return null;
|
|
206
|
+
try {
|
|
207
|
+
new Intl.DateTimeFormat("en-US", { timeZone: value });
|
|
208
|
+
return value;
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ParsedSocialPostMetrics } from "./social-post-metrics-core.js";
|
|
2
|
+
export declare const X_COMPOSIO_POST_METRICS_PARSER_VERSION = 1;
|
|
3
|
+
/**
|
|
4
|
+
* Every field the X parse reads, and nothing else: the post's and its media's
|
|
5
|
+
* metric blocks, without the post text or author.
|
|
6
|
+
*/
|
|
7
|
+
export declare function xComposioMetricEvidence(payload: Record<string, unknown>): Record<string, unknown>;
|
|
8
|
+
/**
|
|
9
|
+
* Map an X v2 post lookup (through Composio) into the registry vocabulary. X post
|
|
10
|
+
* lookup exposes no send/share-via-DM counter, and link clicks only come back in
|
|
11
|
+
* the author's user context.
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseXComposioPostMetrics(payload: Record<string, unknown>): ParsedSocialPostMetrics;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { pickMetricCount, presentMetrics, } from "./social-post-metrics-core.js";
|
|
2
|
+
import { isRecord } from "./type-guards.js";
|
|
3
|
+
export const X_COMPOSIO_POST_METRICS_PARSER_VERSION = 1;
|
|
4
|
+
function xPostEnvelope(payload) {
|
|
5
|
+
const firstData = isRecord(payload.data) ? payload.data : payload;
|
|
6
|
+
const post = isRecord(firstData.data) ? firstData.data : firstData;
|
|
7
|
+
const includes = isRecord(payload.includes)
|
|
8
|
+
? payload.includes
|
|
9
|
+
: isRecord(firstData.includes)
|
|
10
|
+
? firstData.includes
|
|
11
|
+
: {};
|
|
12
|
+
return { post, includes };
|
|
13
|
+
}
|
|
14
|
+
function sumXMediaViews(includes) {
|
|
15
|
+
const media = Array.isArray(includes.media) ? includes.media : [];
|
|
16
|
+
let total = 0;
|
|
17
|
+
let found = false;
|
|
18
|
+
for (const item of media) {
|
|
19
|
+
if (!isRecord(item))
|
|
20
|
+
continue;
|
|
21
|
+
const publicMetrics = isRecord(item.public_metrics) ? item.public_metrics : {};
|
|
22
|
+
const nonPublicMetrics = isRecord(item.non_public_metrics) ? item.non_public_metrics : {};
|
|
23
|
+
const organicMetrics = isRecord(item.organic_metrics) ? item.organic_metrics : {};
|
|
24
|
+
const count = pickMetricCount({ ...organicMetrics, ...nonPublicMetrics, ...publicMetrics }, ["view_count", "video_view_count"]);
|
|
25
|
+
if (count === null)
|
|
26
|
+
continue;
|
|
27
|
+
total += count;
|
|
28
|
+
found = true;
|
|
29
|
+
}
|
|
30
|
+
return found ? total : null;
|
|
31
|
+
}
|
|
32
|
+
const X_METRIC_FIELDS = ["public_metrics", "non_public_metrics", "organic_metrics"];
|
|
33
|
+
function pickMetricFields(record, identity) {
|
|
34
|
+
const picked = {};
|
|
35
|
+
for (const key of [...identity, ...X_METRIC_FIELDS]) {
|
|
36
|
+
if (key in record)
|
|
37
|
+
picked[key] = record[key];
|
|
38
|
+
}
|
|
39
|
+
return picked;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Every field the X parse reads, and nothing else: the post's and its media's
|
|
43
|
+
* metric blocks, without the post text or author.
|
|
44
|
+
*/
|
|
45
|
+
export function xComposioMetricEvidence(payload) {
|
|
46
|
+
const { post, includes } = xPostEnvelope(payload);
|
|
47
|
+
const media = Array.isArray(includes.media)
|
|
48
|
+
? includes.media.filter(isRecord).map((item) => pickMetricFields(item, ["media_key", "type"]))
|
|
49
|
+
: [];
|
|
50
|
+
return { data: pickMetricFields(post, ["id"]), ...(media.length > 0 ? { includes: { media } } : {}) };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Map an X v2 post lookup (through Composio) into the registry vocabulary. X post
|
|
54
|
+
* lookup exposes no send/share-via-DM counter, and link clicks only come back in
|
|
55
|
+
* the author's user context.
|
|
56
|
+
*/
|
|
57
|
+
export function parseXComposioPostMetrics(payload) {
|
|
58
|
+
const { post, includes } = xPostEnvelope(payload);
|
|
59
|
+
const publicMetrics = isRecord(post.public_metrics) ? post.public_metrics : {};
|
|
60
|
+
const nonPublicMetrics = isRecord(post.non_public_metrics) ? post.non_public_metrics : {};
|
|
61
|
+
const organicMetrics = isRecord(post.organic_metrics) ? post.organic_metrics : {};
|
|
62
|
+
const privateMetrics = { ...organicMetrics, ...nonPublicMetrics };
|
|
63
|
+
return {
|
|
64
|
+
metrics: presentMetrics({
|
|
65
|
+
impressions: pickMetricCount(publicMetrics, ["impression_count"]),
|
|
66
|
+
reactions: pickMetricCount(publicMetrics, ["like_count"]),
|
|
67
|
+
comments: pickMetricCount(publicMetrics, ["reply_count"]),
|
|
68
|
+
reposts: pickMetricCount(publicMetrics, ["retweet_count", "repost_count"]),
|
|
69
|
+
saves: pickMetricCount(publicMetrics, ["bookmark_count"]),
|
|
70
|
+
clicks: pickMetricCount(privateMetrics, ["url_link_clicks"]),
|
|
71
|
+
video_views: sumXMediaViews(includes),
|
|
72
|
+
}),
|
|
73
|
+
extended: {},
|
|
74
|
+
shape: isRecord(post.public_metrics) ? "x_v2_lookup" : "unknown",
|
|
75
|
+
attributable: true,
|
|
76
|
+
rejected: [],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type SocialPostMetric } from "./social-capabilities.js";
|
|
2
|
+
import type { ParsedSocialPostMetrics } from "./social-post-metrics-core.js";
|
|
3
|
+
export type { ParsedSocialPostMetrics } from "./social-post-metrics-core.js";
|
|
4
|
+
/** Null when no parser is registered for the pair. */
|
|
5
|
+
export declare function parseSocialPostMetrics(input: {
|
|
6
|
+
channel: string;
|
|
7
|
+
providerRail: string;
|
|
8
|
+
payload: Record<string, unknown>;
|
|
9
|
+
}): ParsedSocialPostMetrics | null;
|
|
10
|
+
export declare function socialPostMetricsParserVersion(channel: string, providerRail: string): number | null;
|
|
11
|
+
/**
|
|
12
|
+
* The metric-bearing subset of a payload, stored with each observation so a
|
|
13
|
+
* parser fix can re-derive it without keeping post text, authors or media.
|
|
14
|
+
* Unknown pairs keep nothing.
|
|
15
|
+
*/
|
|
16
|
+
export declare function socialPostMetricEvidence(input: {
|
|
17
|
+
channel: string;
|
|
18
|
+
providerRail: string;
|
|
19
|
+
payload: Record<string, unknown>;
|
|
20
|
+
}): Record<string, unknown>;
|
|
21
|
+
/** Every registered (channel, provider rail) pair with its current parser version. */
|
|
22
|
+
export declare function listSocialPostMetricsParsers(): Array<{
|
|
23
|
+
channel: string;
|
|
24
|
+
providerRail: string;
|
|
25
|
+
version: number;
|
|
26
|
+
}>;
|
|
27
|
+
/**
|
|
28
|
+
* Metrics the registry declares fully `available` for the pair that a parse did
|
|
29
|
+
* not produce. A non-empty answer on a real provider read is parser drift — the
|
|
30
|
+
* failure that silently stored every LinkedIn metric as NULL twice.
|
|
31
|
+
*/
|
|
32
|
+
export declare function missingDeclaredPostMetrics(input: {
|
|
33
|
+
channel: string;
|
|
34
|
+
providerRail: string;
|
|
35
|
+
parsed: ParsedSocialPostMetrics;
|
|
36
|
+
}): SocialPostMetric[];
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { getSocialCapabilityProfile, SOCIAL_POST_METRICS, } from "./social-capabilities.js";
|
|
2
|
+
import { LINKEDIN_UNIPILE_POST_METRICS_PARSER_VERSION, linkedInUnipileMetricEvidence, parseLinkedInUnipilePostMetrics, } from "./social-post-metrics-linkedin.js";
|
|
3
|
+
import { parseXComposioPostMetrics, X_COMPOSIO_POST_METRICS_PARSER_VERSION, xComposioMetricEvidence, } from "./social-post-metrics-x.js";
|
|
4
|
+
const POST_METRICS_PARSERS = {
|
|
5
|
+
"linkedin:unipile": {
|
|
6
|
+
version: LINKEDIN_UNIPILE_POST_METRICS_PARSER_VERSION,
|
|
7
|
+
parse: parseLinkedInUnipilePostMetrics,
|
|
8
|
+
evidence: linkedInUnipileMetricEvidence,
|
|
9
|
+
},
|
|
10
|
+
"x:composio": {
|
|
11
|
+
version: X_COMPOSIO_POST_METRICS_PARSER_VERSION,
|
|
12
|
+
parse: parseXComposioPostMetrics,
|
|
13
|
+
evidence: xComposioMetricEvidence,
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
function parserFor(channel, providerRail) {
|
|
17
|
+
return POST_METRICS_PARSERS[`${channel}:${providerRail}`] ?? null;
|
|
18
|
+
}
|
|
19
|
+
/** Null when no parser is registered for the pair. */
|
|
20
|
+
export function parseSocialPostMetrics(input) {
|
|
21
|
+
return parserFor(input.channel, input.providerRail)?.parse(input.payload) ?? null;
|
|
22
|
+
}
|
|
23
|
+
export function socialPostMetricsParserVersion(channel, providerRail) {
|
|
24
|
+
return parserFor(channel, providerRail)?.version ?? null;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The metric-bearing subset of a payload, stored with each observation so a
|
|
28
|
+
* parser fix can re-derive it without keeping post text, authors or media.
|
|
29
|
+
* Unknown pairs keep nothing.
|
|
30
|
+
*/
|
|
31
|
+
export function socialPostMetricEvidence(input) {
|
|
32
|
+
return parserFor(input.channel, input.providerRail)?.evidence(input.payload) ?? {};
|
|
33
|
+
}
|
|
34
|
+
/** Every registered (channel, provider rail) pair with its current parser version. */
|
|
35
|
+
export function listSocialPostMetricsParsers() {
|
|
36
|
+
return Object.entries(POST_METRICS_PARSERS).map(([key, parser]) => {
|
|
37
|
+
const [channel, providerRail] = key.split(":");
|
|
38
|
+
return { channel, providerRail, version: parser.version };
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Metrics the registry declares fully `available` for the pair that a parse did
|
|
43
|
+
* not produce. A non-empty answer on a real provider read is parser drift — the
|
|
44
|
+
* failure that silently stored every LinkedIn metric as NULL twice.
|
|
45
|
+
*/
|
|
46
|
+
export function missingDeclaredPostMetrics(input) {
|
|
47
|
+
const profile = getSocialCapabilityProfile(input.channel, input.providerRail);
|
|
48
|
+
if (!profile)
|
|
49
|
+
return [];
|
|
50
|
+
return SOCIAL_POST_METRICS.filter((metric) => profile.metrics[metric].status === "available" && input.parsed.metrics[metric] === undefined);
|
|
51
|
+
}
|
|
@@ -20,19 +20,19 @@ export declare const AUTONOMOUS_WORKFLOW_TRIGGER_TYPES: ReadonlySet<string>;
|
|
|
20
20
|
* Per-run managed-credit ceiling for a LIVE workflow run fired by an autonomous
|
|
21
21
|
* trigger when neither run metadata nor the manifest declares `max_credits`.
|
|
22
22
|
*/
|
|
23
|
-
export declare const DEFAULT_TRIGGER_RUN_CREDIT_CEILING: Record<
|
|
23
|
+
export declare const DEFAULT_TRIGGER_RUN_CREDIT_CEILING: Record<SpendSafetyPlanTier, number | null>;
|
|
24
24
|
/**
|
|
25
25
|
* Per-delivery credit ceiling stamped onto a standing/webhook table auto-run
|
|
26
26
|
* batch when the armed configuration carries no explicit `max_credits`.
|
|
27
27
|
*/
|
|
28
|
-
export declare const DEFAULT_AUTO_RUN_BATCH_CREDIT_CEILING: Record<
|
|
28
|
+
export declare const DEFAULT_AUTO_RUN_BATCH_CREDIT_CEILING: Record<SpendSafetyPlanTier, number | null>;
|
|
29
29
|
/**
|
|
30
30
|
* Row ceiling for a BYOK AI-column run whose caller gave no explicit row bound
|
|
31
31
|
* (no limit, no row_ids, selection "all"). BYOK bills the customer's own
|
|
32
32
|
* provider account, so a credit ceiling is meaningless — row count is the
|
|
33
33
|
* enforceable unit. An explicit limit (up to the 500k platform row cap) wins.
|
|
34
34
|
*/
|
|
35
|
-
export declare const DEFAULT_BYOK_COLUMN_RUN_MAX_ROWS: Record<
|
|
35
|
+
export declare const DEFAULT_BYOK_COLUMN_RUN_MAX_ROWS: Record<SpendSafetyPlanTier, number | null>;
|
|
36
36
|
/**
|
|
37
37
|
* Default per-provider DAILY call cap for BYOK provider traffic (the customer's
|
|
38
38
|
* own key). Short-window pacing defaults already exist in provider-fetch; this
|
|
@@ -40,7 +40,7 @@ export declare const DEFAULT_BYOK_COLUMN_RUN_MAX_ROWS: Record<PlanTier, number |
|
|
|
40
40
|
* with the self-serve override surface live. A workspace
|
|
41
41
|
* provider_rate_limit_policies row with windowSeconds=86400 overrides it.
|
|
42
42
|
*/
|
|
43
|
-
export declare const DEFAULT_BYOK_PROVIDER_DAILY_CALL_CAP: Record<
|
|
43
|
+
export declare const DEFAULT_BYOK_PROVIDER_DAILY_CALL_CAP: Record<SpendSafetyPlanTier, number | null>;
|
|
44
44
|
export declare const BYOK_PROVIDER_DAILY_WINDOW_SECONDS: number;
|
|
45
45
|
export declare const BYOK_PROVIDER_DAILY_CAP_ENFORCEMENT_ENV = "OXYGEN_BYOK_DAILY_CAPS";
|
|
46
46
|
export type ByokProviderDailyCapEnforcementMode = "observe_only" | "enforced";
|
|
@@ -105,10 +105,14 @@ export type OrgDailySpendGuard = {
|
|
|
105
105
|
* decision is made, so enforcement and the read surfaces cannot drift apart.
|
|
106
106
|
*/
|
|
107
107
|
export declare function resolveOrgDailySpendGuard(monthlyCredits: number | null | undefined): OrgDailySpendGuard | null;
|
|
108
|
-
|
|
109
|
-
export declare function
|
|
110
|
-
|
|
111
|
-
export declare function
|
|
108
|
+
/** Credits carry 3 decimals everywhere (control-DB numeric(18,3)). */
|
|
109
|
+
export declare function roundCredits(value: number): number;
|
|
110
|
+
/** Credit estimates that present to 2 decimals (cent-grained customer copy). */
|
|
111
|
+
export declare function roundCreditsToCents(value: number): number;
|
|
112
|
+
export declare function resolveDefaultTriggerRunCreditCeiling(tier: SpendSafetyPlanTier): number | null;
|
|
113
|
+
export declare function resolveDefaultAutoRunBatchCreditCeiling(tier: SpendSafetyPlanTier): number | null;
|
|
114
|
+
export declare function resolveDefaultByokColumnRunMaxRows(tier: SpendSafetyPlanTier): number | null;
|
|
115
|
+
export declare function resolveDefaultByokProviderDailyCallCap(tier: SpendSafetyPlanTier): number | null;
|
|
112
116
|
/**
|
|
113
117
|
* The plan tiers the spend-safety default tables above are keyed by. `limitsTier`
|
|
114
118
|
* collapses "enterprise" onto the "scale" enforcement rung, but these tables carry
|
|
@@ -119,5 +123,13 @@ export declare function resolveDefaultByokProviderDailyCallCap(tier: PlanTier):
|
|
|
119
123
|
* `oxygen limits` report, the org-facing provider-limits API, and the BYOK
|
|
120
124
|
* daily-cap enforcement resolver — they must agree on which column an org reads.
|
|
121
125
|
*/
|
|
122
|
-
|
|
123
|
-
|
|
126
|
+
/**
|
|
127
|
+
* The tiers these tables are keyed by. `oxygen` is deliberately ABSENT: its six
|
|
128
|
+
* rungs span four enforcement rungs, so an Oxygen plan must fall through to its
|
|
129
|
+
* limitsTier (the price band) rather than read one shared column. Adding an
|
|
130
|
+
* "oxygen" column here would silently give a $1,999 workspace the same ceiling
|
|
131
|
+
* as a $49 one.
|
|
132
|
+
*/
|
|
133
|
+
export type SpendSafetyPlanTier = Exclude<PlanTier, "oxygen">;
|
|
134
|
+
export declare const SPEND_SAFETY_PLAN_TIERS: readonly SpendSafetyPlanTier[];
|
|
135
|
+
export declare function resolveSpendSafetyPlanTier(planTier: string | null, limitsTier: LimitsTier): SpendSafetyPlanTier;
|
|
@@ -9,11 +9,11 @@ export const AUTONOMOUS_WORKFLOW_TRIGGER_TYPES = new Set([
|
|
|
9
9
|
* trigger when neither run metadata nor the manifest declares `max_credits`.
|
|
10
10
|
*/
|
|
11
11
|
export const DEFAULT_TRIGGER_RUN_CREDIT_CEILING = {
|
|
12
|
-
free:
|
|
13
|
-
starter:
|
|
14
|
-
pro:
|
|
15
|
-
team:
|
|
16
|
-
scale:
|
|
12
|
+
free: 50,
|
|
13
|
+
starter: 500,
|
|
14
|
+
pro: 1_000,
|
|
15
|
+
team: 2_500,
|
|
16
|
+
scale: 25_000,
|
|
17
17
|
enterprise: null,
|
|
18
18
|
};
|
|
19
19
|
/**
|
|
@@ -21,11 +21,11 @@ export const DEFAULT_TRIGGER_RUN_CREDIT_CEILING = {
|
|
|
21
21
|
* batch when the armed configuration carries no explicit `max_credits`.
|
|
22
22
|
*/
|
|
23
23
|
export const DEFAULT_AUTO_RUN_BATCH_CREDIT_CEILING = {
|
|
24
|
-
free:
|
|
25
|
-
starter:
|
|
26
|
-
pro:
|
|
27
|
-
team:
|
|
28
|
-
scale:
|
|
24
|
+
free: 50,
|
|
25
|
+
starter: 500,
|
|
26
|
+
pro: 1_000,
|
|
27
|
+
team: 2_500,
|
|
28
|
+
scale: 25_000,
|
|
29
29
|
enterprise: null,
|
|
30
30
|
};
|
|
31
31
|
/**
|
|
@@ -129,9 +129,13 @@ export function resolveOrgDailySpendGuard(monthlyCredits) {
|
|
|
129
129
|
};
|
|
130
130
|
}
|
|
131
131
|
/** Credits carry 3 decimals everywhere (control-DB numeric(18,3)). */
|
|
132
|
-
function roundCredits(value) {
|
|
132
|
+
export function roundCredits(value) {
|
|
133
133
|
return Math.round(value * 1000) / 1000;
|
|
134
134
|
}
|
|
135
|
+
/** Credit estimates that present to 2 decimals (cent-grained customer copy). */
|
|
136
|
+
export function roundCreditsToCents(value) {
|
|
137
|
+
return Math.round((value + Number.EPSILON) * 100) / 100;
|
|
138
|
+
}
|
|
135
139
|
export function resolveDefaultTriggerRunCreditCeiling(tier) {
|
|
136
140
|
return DEFAULT_TRIGGER_RUN_CREDIT_CEILING[tier];
|
|
137
141
|
}
|
|
@@ -144,16 +148,6 @@ export function resolveDefaultByokColumnRunMaxRows(tier) {
|
|
|
144
148
|
export function resolveDefaultByokProviderDailyCallCap(tier) {
|
|
145
149
|
return DEFAULT_BYOK_PROVIDER_DAILY_CALL_CAP[tier];
|
|
146
150
|
}
|
|
147
|
-
/**
|
|
148
|
-
* The plan tiers the spend-safety default tables above are keyed by. `limitsTier`
|
|
149
|
-
* collapses "enterprise" onto the "scale" enforcement rung, but these tables carry
|
|
150
|
-
* a distinct "enterprise" column (null = runs on custom limits) — so prefer the
|
|
151
|
-
* raw plan_tier whenever it is itself a table key, and fall back to the limits rung
|
|
152
|
-
* (always a valid PlanTier key) when plan_tier is absent or an unknown/legacy
|
|
153
|
-
* string (e.g. a Stripe price nickname). Single source of truth for the
|
|
154
|
-
* `oxygen limits` report, the org-facing provider-limits API, and the BYOK
|
|
155
|
-
* daily-cap enforcement resolver — they must agree on which column an org reads.
|
|
156
|
-
*/
|
|
157
151
|
export const SPEND_SAFETY_PLAN_TIERS = [
|
|
158
152
|
"free",
|
|
159
153
|
"starter",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function executeRows<T>(value: unknown): T[];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { CONTACT_SALES_URL, PURCHASABLE_PLAN_KEYS, SELF_SERVE_PLAN_TIERS, normalizeBillingCurrency, type BillingCurrency, type OxygenPlanKey, type PricingPlanDefinition, type SelfServePlanTier } from "./billing.js";
|
|
2
|
+
export { CONTACT_SALES_URL, PURCHASABLE_PLAN_KEYS, SELF_SERVE_PLAN_TIERS, normalizeBillingCurrency, };
|
|
3
|
+
export type { BillingCurrency, OxygenPlanKey, SelfServePlanTier };
|
|
4
|
+
export type PricingPlan = PricingPlanDefinition & {
|
|
5
|
+
priceIds: Record<BillingCurrency, string>;
|
|
6
|
+
priceId: string;
|
|
7
|
+
};
|
|
8
|
+
export declare const PRICING_PLANS: {
|
|
9
|
+
readonly free: PricingPlan;
|
|
10
|
+
readonly oxygen: PricingPlan;
|
|
11
|
+
readonly starter: PricingPlan;
|
|
12
|
+
readonly pro: PricingPlan;
|
|
13
|
+
readonly team: PricingPlan;
|
|
14
|
+
readonly scale: PricingPlan;
|
|
15
|
+
readonly enterprise: PricingPlan;
|
|
16
|
+
};
|
|
17
|
+
/** The six purchasable rungs with their Stripe price ids, cheapest first. */
|
|
18
|
+
export declare const PURCHASABLE_PLANS: Record<OxygenPlanKey, PricingPlan>;
|
|
19
|
+
export declare function getPlanPriceId(plan: PricingPlan, currency: BillingCurrency): string;
|
|
20
|
+
/**
|
|
21
|
+
* Resolve a Stripe price id to the plan key stored in `subscriptions.tier`.
|
|
22
|
+
*
|
|
23
|
+
* Returns a plan KEY ("oxygen_499") for the six purchasable rungs and a TIER
|
|
24
|
+
* ("starter", "growth_250") for grandfathered plans. Both are resolvable by
|
|
25
|
+
* resolveBasePricingPlan, which is what the credit grant reads — so the rung a
|
|
26
|
+
* customer paid for is the allowance they receive.
|
|
27
|
+
*/
|
|
28
|
+
export declare function tierFromPriceId(priceId: string): string | null;
|
|
29
|
+
/**
|
|
30
|
+
* Complete runtime-recognized Price set grouped by the current self-serve
|
|
31
|
+
* family. Catalog audit/migration code consumes this instead of maintaining a
|
|
32
|
+
* second env map that can silently omit grandfathered subscriptions.
|
|
33
|
+
*/
|
|
34
|
+
export declare function recognizedStripePriceIdsBySelfServeTier(): Record<SelfServePlanTier, readonly string[]>;
|
|
35
|
+
export declare function formatMonthlyPrice(priceCents: number | null, currency?: BillingCurrency): string;
|
|
36
|
+
export declare function getMonthlyAllowanceLabel(plan: PricingPlanDefinition): string;
|