@oxygen-agent/cli 1.1010.650 → 1.1010.905
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/auto-update.d.ts +129 -0
- package/dist/auto-update.js +392 -0
- package/dist/command-manifest.js +15 -1
- package/dist/credentials.d.ts +2 -0
- package/dist/credentials.js +6 -3
- package/dist/functions-commands.js +1 -1
- package/dist/http-client.js +28 -4
- package/dist/inbox-needs-reply-notice.d.ts +12 -0
- package/dist/inbox-needs-reply-notice.js +51 -0
- package/dist/index.js +756 -177
- package/dist/run-wait.d.ts +3 -1
- package/dist/run-wait.js +19 -5
- package/dist/skills.js +48 -22
- package/dist/streamed-file-import.d.ts +58 -0
- package/dist/streamed-file-import.js +115 -0
- package/dist/update.d.ts +29 -0
- package/dist/update.js +62 -16
- package/dist/workflow-plan-limit-notices.d.ts +8 -0
- package/dist/workflow-plan-limit-notices.js +28 -0
- package/node_modules/@oxygen/cli-ugc/dist/commands.js +3 -3
- package/node_modules/@oxygen/shared/dist/billing-anchors.d.ts +50 -2
- package/node_modules/@oxygen/shared/dist/billing-anchors.js +94 -2
- package/node_modules/@oxygen/shared/dist/billing.d.ts +247 -37
- package/node_modules/@oxygen/shared/dist/billing.js +418 -45
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +66 -6
- 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/cost-estimate-view.d.ts +50 -0
- package/node_modules/@oxygen/shared/dist/cost-estimate-view.js +90 -0
- package/node_modules/@oxygen/shared/dist/cost-estimate.d.ts +167 -0
- package/node_modules/@oxygen/shared/dist/cost-estimate.js +361 -0
- package/node_modules/@oxygen/shared/dist/credit-gate.d.ts +26 -0
- package/node_modules/@oxygen/shared/dist/credit-gate.js +65 -0
- package/node_modules/@oxygen/shared/dist/email-deliverability-policy.d.ts +51 -0
- package/node_modules/@oxygen/shared/dist/email-deliverability-policy.js +101 -0
- package/node_modules/@oxygen/shared/dist/email-hard-bounce.d.ts +27 -0
- package/node_modules/@oxygen/shared/dist/email-hard-bounce.js +27 -0
- package/node_modules/@oxygen/shared/dist/error-redaction.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/error-redaction.js +1 -1
- package/node_modules/@oxygen/shared/dist/feature-gates.d.ts +10 -1
- package/node_modules/@oxygen/shared/dist/feature-gates.js +12 -1
- package/node_modules/@oxygen/shared/dist/file-import.d.ts +13 -1
- package/node_modules/@oxygen/shared/dist/file-import.js +33 -6
- package/node_modules/@oxygen/shared/dist/hosted-ai.d.ts +73 -3
- package/node_modules/@oxygen/shared/dist/hosted-ai.js +246 -24
- package/node_modules/@oxygen/shared/dist/import-limits.d.ts +25 -1
- package/node_modules/@oxygen/shared/dist/import-limits.js +35 -2
- package/node_modules/@oxygen/shared/dist/index.d.ts +4 -23
- package/node_modules/@oxygen/shared/dist/index.js +4 -43
- package/node_modules/@oxygen/shared/dist/linkedin-sequences.d.ts +114 -0
- package/node_modules/@oxygen/shared/dist/linkedin-sequences.js +150 -0
- package/node_modules/@oxygen/shared/dist/object-storage.d.ts +9 -0
- package/node_modules/@oxygen/shared/dist/object-storage.js +17 -0
- package/node_modules/@oxygen/shared/dist/operational-telemetry.d.ts +41 -0
- package/node_modules/@oxygen/shared/dist/operational-telemetry.js +55 -0
- package/node_modules/@oxygen/shared/dist/otlp-log-sink.js +19 -2
- package/node_modules/@oxygen/shared/dist/plan-band.d.ts +234 -0
- package/node_modules/@oxygen/shared/dist/plan-band.js +312 -0
- package/node_modules/@oxygen/shared/dist/plan-capabilities.d.ts +77 -7
- package/node_modules/@oxygen/shared/dist/plan-capabilities.js +87 -7
- package/node_modules/@oxygen/shared/dist/plan-limits-view.d.ts +219 -0
- package/node_modules/@oxygen/shared/dist/plan-limits-view.js +330 -0
- package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +335 -126
- package/node_modules/@oxygen/shared/dist/plan-limits.js +277 -86
- package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +158 -49
- package/node_modules/@oxygen/shared/dist/pricing-sheet.js +139 -41
- package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.d.ts +42 -23
- package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.js +56 -37
- package/node_modules/@oxygen/shared/dist/process-resource.d.ts +4 -0
- package/node_modules/@oxygen/shared/dist/process-resource.js +25 -0
- package/node_modules/@oxygen/shared/dist/provider-http-error.d.ts +10 -0
- package/node_modules/@oxygen/shared/dist/provider-http-error.js +27 -0
- package/node_modules/@oxygen/shared/dist/repricing.d.ts +257 -0
- package/node_modules/@oxygen/shared/dist/repricing.js +721 -0
- package/node_modules/@oxygen/shared/dist/semver.d.ts +21 -0
- package/node_modules/@oxygen/shared/dist/semver.js +41 -0
- package/node_modules/@oxygen/shared/dist/sending-limits.d.ts +30 -0
- package/node_modules/@oxygen/shared/dist/sending-limits.js +43 -0
- package/node_modules/@oxygen/shared/dist/sending-seats.d.ts +18 -15
- package/node_modules/@oxygen/shared/dist/sending-seats.js +22 -17
- package/node_modules/@oxygen/shared/dist/sequence-failures.js +4 -1
- package/node_modules/@oxygen/shared/dist/spend-safety.d.ts +57 -8
- package/node_modules/@oxygen/shared/dist/spend-safety.js +64 -11
- package/node_modules/@oxygen/shared/dist/stripe-price-catalog.d.ts +33 -1
- package/node_modules/@oxygen/shared/dist/stripe-price-catalog.js +71 -1
- package/node_modules/@oxygen/shared/dist/table-capacity.d.ts +68 -10
- package/node_modules/@oxygen/shared/dist/table-capacity.js +85 -4
- package/node_modules/@oxygen/shared/dist/telemetry-export-observer.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/telemetry-export-observer.js +13 -5
- package/node_modules/@oxygen/shared/dist/telemetry-resource.d.ts +40 -0
- package/node_modules/@oxygen/shared/dist/telemetry-resource.js +35 -0
- package/node_modules/@oxygen/shared/dist/telemetry.d.ts +9 -0
- package/node_modules/@oxygen/shared/dist/telemetry.js +41 -2
- package/node_modules/@oxygen/shared/dist/trace-context.d.ts +29 -0
- package/node_modules/@oxygen/shared/dist/trace-context.js +88 -0
- package/node_modules/@oxygen/shared/dist/ugc.d.ts +15 -0
- package/node_modules/@oxygen/shared/dist/ugc.js +29 -0
- package/node_modules/@oxygen/shared/dist/version.d.ts +1 -3
- package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
- package/node_modules/@oxygen/shared/dist/version.js +14 -27
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.js +5 -0
- package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.d.ts +3 -3
- package/node_modules/@oxygen/workflows/dist/graph/types.d.ts +15 -1
- package/node_modules/@oxygen/workflows/dist/graph/types.js +15 -1
- package/node_modules/@oxygen/workflows/dist/index.d.ts +45 -0
- package/node_modules/@oxygen/workflows/dist/index.js +152 -2
- package/node_modules/@oxygen/workflows/dist/usage-estimate.d.ts +10 -1
- package/node_modules/@oxygen/workflows/dist/usage-estimate.js +33 -29
- package/package.json +1 -1
- package/node_modules/@oxygen/shared/dist/email-warmup-readiness.d.ts +0 -64
- package/node_modules/@oxygen/shared/dist/email-warmup-readiness.js +0 -90
|
@@ -13,9 +13,27 @@
|
|
|
13
13
|
/**
|
|
14
14
|
* Formats that must be held in memory to parse at all — a JSON array has no
|
|
15
15
|
* record boundary to stream from, and XLSX is a zip container. CSV and JSONL
|
|
16
|
-
* stream, so only these two
|
|
16
|
+
* stream from object storage up to the plan's file limit, so only these two
|
|
17
|
+
* carry this lower ceiling (raised from 10 MiB by decision F.8, 2026-09-27).
|
|
18
|
+
*
|
|
19
|
+
* It also bounds every path that holds a whole file in one request: the inline
|
|
20
|
+
* multipart import used when object storage is not configured.
|
|
17
21
|
*/
|
|
18
22
|
export declare const MAX_BUFFERED_IMPORT_PARSE_BYTES: number;
|
|
23
|
+
/**
|
|
24
|
+
* A link import (`--url`, `oxygen_tables_import_url`, the wizard's link card)
|
|
25
|
+
* is downloaded and parsed inside the web request, so it keeps its own small
|
|
26
|
+
* ceiling. It is not a plan limit: a bigger file is downloaded and imported
|
|
27
|
+
* with `tables import --file`, which stages it in object storage.
|
|
28
|
+
*/
|
|
29
|
+
export declare const MAX_URL_IMPORT_BYTES: number;
|
|
30
|
+
/**
|
|
31
|
+
* The largest object one presigned PUT may carry on an S3-compatible store (S3
|
|
32
|
+
* and Ceph-based stores such as Hetzner Object Storage cap a single PUT at
|
|
33
|
+
* 5 GiB). A staged import is one PUT, so no plan's import file limit may
|
|
34
|
+
* exceed it.
|
|
35
|
+
*/
|
|
36
|
+
export declare const IMPORT_OBJECT_SINGLE_PUT_MAX_BYTES: number;
|
|
19
37
|
/**
|
|
20
38
|
* The platform's request-body ceiling — infrastructure, not a plan limit.
|
|
21
39
|
*
|
|
@@ -26,3 +44,9 @@ export declare const MAX_BUFFERED_IMPORT_PARSE_BYTES: number;
|
|
|
26
44
|
export declare const VERCEL_REQUEST_BODY_LIMIT_BYTES: number;
|
|
27
45
|
/** True for the formats that cannot stream and so carry the buffered ceiling. */
|
|
28
46
|
export declare function isBufferedOnlyImportFormat(format: string): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* The refusal every surface gives a JSON or XLSX file over the buffered
|
|
49
|
+
* ceiling. It names the ceiling and the two formats that take a larger file,
|
|
50
|
+
* so the reader knows the fix is a format change, not a plan change.
|
|
51
|
+
*/
|
|
52
|
+
export declare function bufferedImportTooLargeMessage(format: string, fileBytes?: number | null): string;
|
|
@@ -10,12 +10,31 @@
|
|
|
10
10
|
* constants live here alone, and both of those modules re-export them, keeping
|
|
11
11
|
* one definition rather than a copy that drifts.
|
|
12
12
|
*/
|
|
13
|
+
const MIB = 1024 * 1024;
|
|
13
14
|
/**
|
|
14
15
|
* Formats that must be held in memory to parse at all — a JSON array has no
|
|
15
16
|
* record boundary to stream from, and XLSX is a zip container. CSV and JSONL
|
|
16
|
-
* stream, so only these two
|
|
17
|
+
* stream from object storage up to the plan's file limit, so only these two
|
|
18
|
+
* carry this lower ceiling (raised from 10 MiB by decision F.8, 2026-09-27).
|
|
19
|
+
*
|
|
20
|
+
* It also bounds every path that holds a whole file in one request: the inline
|
|
21
|
+
* multipart import used when object storage is not configured.
|
|
22
|
+
*/
|
|
23
|
+
export const MAX_BUFFERED_IMPORT_PARSE_BYTES = 100 * MIB;
|
|
24
|
+
/**
|
|
25
|
+
* A link import (`--url`, `oxygen_tables_import_url`, the wizard's link card)
|
|
26
|
+
* is downloaded and parsed inside the web request, so it keeps its own small
|
|
27
|
+
* ceiling. It is not a plan limit: a bigger file is downloaded and imported
|
|
28
|
+
* with `tables import --file`, which stages it in object storage.
|
|
29
|
+
*/
|
|
30
|
+
export const MAX_URL_IMPORT_BYTES = 10 * MIB;
|
|
31
|
+
/**
|
|
32
|
+
* The largest object one presigned PUT may carry on an S3-compatible store (S3
|
|
33
|
+
* and Ceph-based stores such as Hetzner Object Storage cap a single PUT at
|
|
34
|
+
* 5 GiB). A staged import is one PUT, so no plan's import file limit may
|
|
35
|
+
* exceed it.
|
|
17
36
|
*/
|
|
18
|
-
export const
|
|
37
|
+
export const IMPORT_OBJECT_SINGLE_PUT_MAX_BYTES = 5 * 1024 * MIB;
|
|
19
38
|
/**
|
|
20
39
|
* The platform's request-body ceiling — infrastructure, not a plan limit.
|
|
21
40
|
*
|
|
@@ -28,3 +47,17 @@ export const VERCEL_REQUEST_BODY_LIMIT_BYTES = 4 * 1024 * 1024;
|
|
|
28
47
|
export function isBufferedOnlyImportFormat(format) {
|
|
29
48
|
return format === "json" || format === "xlsx";
|
|
30
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* The refusal every surface gives a JSON or XLSX file over the buffered
|
|
52
|
+
* ceiling. It names the ceiling and the two formats that take a larger file,
|
|
53
|
+
* so the reader knows the fix is a format change, not a plan change.
|
|
54
|
+
*/
|
|
55
|
+
export function bufferedImportTooLargeMessage(format, fileBytes) {
|
|
56
|
+
const label = format.toUpperCase();
|
|
57
|
+
const size = typeof fileBytes === "number" ? ` This one is ${formatImportMib(fileBytes)}.` : "";
|
|
58
|
+
return `${label} files are read whole, so they are capped at ${formatImportMib(MAX_BUFFERED_IMPORT_PARSE_BYTES)}.${size} Save it as CSV or JSONL, which import up to your plan's file-size limit.`;
|
|
59
|
+
}
|
|
60
|
+
function formatImportMib(bytes) {
|
|
61
|
+
const mib = bytes / MIB;
|
|
62
|
+
return `${mib >= 10 ? Math.round(mib).toLocaleString("en-US") : mib.toFixed(1)} MiB`;
|
|
63
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { OXYGEN_MINIMUM_CLI_VERSION, OXYGEN_VERSION, } from "./version.js";
|
|
2
2
|
export { WORKFLOW_TRIGGER_AUTO_PAUSE_METADATA_KEYS, clearWorkflowTriggerAutoPauseMetadata, } from "./workflow-trigger-metadata.js";
|
|
3
3
|
export { WORKFLOW_STATUS_CHANGE_METADATA_KEY, type WorkflowStatusChange, type WorkflowStatusChangeActor, type WorkflowStatusChangeSource, describeWorkflowStatusChange, formatWorkflowStatusChangeTimestamp, parseWorkflowStatusChange, readWorkflowStatusChange, } from "./workflow-status-change.js";
|
|
4
4
|
export * from "./billing.js";
|
|
@@ -10,6 +10,7 @@ export * from "./capability-discovery.js";
|
|
|
10
10
|
export * from "./user-capability-routing.js";
|
|
11
11
|
export * from "./plan-capabilities.js";
|
|
12
12
|
export * from "./plan-limits.js";
|
|
13
|
+
export * from "./plan-band.js";
|
|
13
14
|
export * from "./sending-seats.js";
|
|
14
15
|
export * from "./sending-seat-capacity.js";
|
|
15
16
|
export * from "./plain-support-events.js";
|
|
@@ -37,7 +38,7 @@ export * from "./credit-guidance.js";
|
|
|
37
38
|
export * from "./deploy-env.js";
|
|
38
39
|
export * from "./directory.js";
|
|
39
40
|
export * from "./email-dsn.js";
|
|
40
|
-
export * from "./email-
|
|
41
|
+
export * from "./email-hard-bounce.js";
|
|
41
42
|
export * from "./email-tracking-token.js";
|
|
42
43
|
export * from "./email-unsubscribe-token.js";
|
|
43
44
|
export * from "./error-redaction.js";
|
|
@@ -110,27 +111,7 @@ export * from "./workspace-event-catalog.js";
|
|
|
110
111
|
export * from "./notetaker-events.js";
|
|
111
112
|
export * from "./workspace-agents.js";
|
|
112
113
|
export declare const MAX_ROW_LOOP_WRITE_ROWS = 500;
|
|
113
|
-
export
|
|
114
|
-
major: number;
|
|
115
|
-
minor: number;
|
|
116
|
-
patch: number;
|
|
117
|
-
};
|
|
118
|
-
/**
|
|
119
|
-
* Parse a three-segment semantic version (e.g. `1.142.17`). Pre-release and
|
|
120
|
-
* build metadata suffixes (`-rc.1`, `+build`) are tolerated but ignored.
|
|
121
|
-
* Returns `null` when the input is not a parseable `major.minor.patch` string.
|
|
122
|
-
*/
|
|
123
|
-
export declare function parseSemver(version: string): SemanticVersion | null;
|
|
124
|
-
/**
|
|
125
|
-
* Compare two semantic versions. Returns -1 when `a < b`, 1 when `a > b`, and
|
|
126
|
-
* 0 when they are equal. Unparseable inputs compare as equal (0) so callers
|
|
127
|
-
* fail open rather than misordering garbage.
|
|
128
|
-
*/
|
|
129
|
-
export declare function compareSemver(a: string, b: string): -1 | 0 | 1;
|
|
130
|
-
/** True when `a` is a strictly greater semantic version than `b`. */
|
|
131
|
-
export declare function isVersionGreater(a: string, b: string): boolean;
|
|
132
|
-
/** True when `a` is a strictly lesser semantic version than `b`. */
|
|
133
|
-
export declare function isVersionLess(a: string, b: string): boolean;
|
|
114
|
+
export * from "./semver.js";
|
|
134
115
|
export * from "./ugc.js";
|
|
135
116
|
export * from "./ugc-amplification-identity.js";
|
|
136
117
|
export * from "./knowledge-repository.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { OXYGEN_MINIMUM_CLI_VERSION, OXYGEN_VERSION, } from "./version.js";
|
|
2
2
|
export { WORKFLOW_TRIGGER_AUTO_PAUSE_METADATA_KEYS, clearWorkflowTriggerAutoPauseMetadata, } from "./workflow-trigger-metadata.js";
|
|
3
3
|
export { WORKFLOW_STATUS_CHANGE_METADATA_KEY, describeWorkflowStatusChange, formatWorkflowStatusChangeTimestamp, parseWorkflowStatusChange, readWorkflowStatusChange, } from "./workflow-status-change.js";
|
|
4
4
|
export * from "./billing.js";
|
|
@@ -10,6 +10,7 @@ export * from "./capability-discovery.js";
|
|
|
10
10
|
export * from "./user-capability-routing.js";
|
|
11
11
|
export * from "./plan-capabilities.js";
|
|
12
12
|
export * from "./plan-limits.js";
|
|
13
|
+
export * from "./plan-band.js";
|
|
13
14
|
export * from "./sending-seats.js";
|
|
14
15
|
export * from "./sending-seat-capacity.js";
|
|
15
16
|
export * from "./plain-support-events.js";
|
|
@@ -37,7 +38,7 @@ export * from "./credit-guidance.js";
|
|
|
37
38
|
export * from "./deploy-env.js";
|
|
38
39
|
export * from "./directory.js";
|
|
39
40
|
export * from "./email-dsn.js";
|
|
40
|
-
export * from "./email-
|
|
41
|
+
export * from "./email-hard-bounce.js";
|
|
41
42
|
export * from "./email-tracking-token.js";
|
|
42
43
|
export * from "./email-unsubscribe-token.js";
|
|
43
44
|
export * from "./error-redaction.js";
|
|
@@ -126,47 +127,7 @@ export * from "./workspace-agents.js";
|
|
|
126
127
|
// the COPY-based bulk engine. Tenant-db enforces this and the CLI/API row caps
|
|
127
128
|
// reference it so they never advertise a batch the row-loop will reject.
|
|
128
129
|
export const MAX_ROW_LOOP_WRITE_ROWS = 500;
|
|
129
|
-
|
|
130
|
-
* Parse a three-segment semantic version (e.g. `1.142.17`). Pre-release and
|
|
131
|
-
* build metadata suffixes (`-rc.1`, `+build`) are tolerated but ignored.
|
|
132
|
-
* Returns `null` when the input is not a parseable `major.minor.patch` string.
|
|
133
|
-
*/
|
|
134
|
-
export function parseSemver(version) {
|
|
135
|
-
const match = /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(version);
|
|
136
|
-
if (!match)
|
|
137
|
-
return null;
|
|
138
|
-
return {
|
|
139
|
-
major: Number(match[1]),
|
|
140
|
-
minor: Number(match[2]),
|
|
141
|
-
patch: Number(match[3]),
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Compare two semantic versions. Returns -1 when `a < b`, 1 when `a > b`, and
|
|
146
|
-
* 0 when they are equal. Unparseable inputs compare as equal (0) so callers
|
|
147
|
-
* fail open rather than misordering garbage.
|
|
148
|
-
*/
|
|
149
|
-
export function compareSemver(a, b) {
|
|
150
|
-
const left = parseSemver(a);
|
|
151
|
-
const right = parseSemver(b);
|
|
152
|
-
if (!left || !right)
|
|
153
|
-
return 0;
|
|
154
|
-
for (const key of ["major", "minor", "patch"]) {
|
|
155
|
-
if (left[key] > right[key])
|
|
156
|
-
return 1;
|
|
157
|
-
if (left[key] < right[key])
|
|
158
|
-
return -1;
|
|
159
|
-
}
|
|
160
|
-
return 0;
|
|
161
|
-
}
|
|
162
|
-
/** True when `a` is a strictly greater semantic version than `b`. */
|
|
163
|
-
export function isVersionGreater(a, b) {
|
|
164
|
-
return compareSemver(a, b) > 0;
|
|
165
|
-
}
|
|
166
|
-
/** True when `a` is a strictly lesser semantic version than `b`. */
|
|
167
|
-
export function isVersionLess(a, b) {
|
|
168
|
-
return compareSemver(a, b) < 0;
|
|
169
|
-
}
|
|
130
|
+
export * from "./semver.js";
|
|
170
131
|
export * from "./ugc.js";
|
|
171
132
|
export * from "./ugc-amplification-identity.js";
|
|
172
133
|
export * from "./knowledge-repository.js";
|
|
@@ -247,4 +247,118 @@ export declare function truncateLinkedInInviteNote(note: string, limit: number):
|
|
|
247
247
|
truncated: boolean;
|
|
248
248
|
originalLength: number;
|
|
249
249
|
};
|
|
250
|
+
export type LinkedInSenderLimits = {
|
|
251
|
+
invites_per_day: number;
|
|
252
|
+
invites_per_week: number;
|
|
253
|
+
messages_per_day: number;
|
|
254
|
+
inmails_per_day: number;
|
|
255
|
+
/**
|
|
256
|
+
* InMails in a rolling 30 days (today and the 29 before it, in the account's
|
|
257
|
+
* day). Checked under the same sender row lock as the weekly invite cap, so
|
|
258
|
+
* concurrent claims cannot both pass a stale sum.
|
|
259
|
+
*/
|
|
260
|
+
inmails_per_month: number;
|
|
261
|
+
profile_views_per_day: number;
|
|
262
|
+
/**
|
|
263
|
+
* Invisible profile and company lookups (`users_get` with notify:false,
|
|
264
|
+
* `company_get`). The member is never notified, so these are reads, but
|
|
265
|
+
* Unipile relays LinkedIn's ~100 profile retrievals a day for them.
|
|
266
|
+
*/
|
|
267
|
+
profile_lookups_per_day: number;
|
|
268
|
+
/** Skill endorsements. Used to share the follow cap. */
|
|
269
|
+
endorsements_per_day: number;
|
|
270
|
+
/**
|
|
271
|
+
* Sent-invitation withdrawals (the withdraw action itself, not the reads of
|
|
272
|
+
* the sent-invite list). LinkedIn blocks re-inviting a withdrawn member for
|
|
273
|
+
* three weeks; see the re-invite guard.
|
|
274
|
+
*/
|
|
275
|
+
withdrawals_per_day: number;
|
|
276
|
+
/** Posts to the member's own feed, from every caller. Timing-exempt, never uncapped. */
|
|
277
|
+
posts_per_day: number;
|
|
278
|
+
follows_per_day: number;
|
|
279
|
+
likes_per_day: number;
|
|
280
|
+
comments_per_day: number;
|
|
281
|
+
total_actions_per_day: number;
|
|
282
|
+
min_action_spacing_seconds: number;
|
|
283
|
+
action_spacing_jitter_seconds: number;
|
|
284
|
+
interactive_min_spacing_seconds: number;
|
|
285
|
+
interactive_spacing_jitter_seconds: number;
|
|
286
|
+
/**
|
|
287
|
+
* Backstop on the interactive lane: how many human-paced sends an account may
|
|
288
|
+
* make in a rolling hour.
|
|
289
|
+
*
|
|
290
|
+
* Mostly redundant with messages_per_day (whose maximum is 40), and that is
|
|
291
|
+
* deliberate — it bounds BURST SHAPE rather than volume. Volume is already
|
|
292
|
+
* capped; this stops the whole daily allowance being spent in four minutes if
|
|
293
|
+
* the interactive classification is ever wrong, which is the one failure mode
|
|
294
|
+
* the daily cap cannot see.
|
|
295
|
+
*/
|
|
296
|
+
interactive_sends_per_hour: number;
|
|
297
|
+
relations_reads_per_day: number;
|
|
298
|
+
messages_reads_per_day: number;
|
|
299
|
+
searches_per_day: number;
|
|
300
|
+
sales_nav_search_results_per_day: number;
|
|
301
|
+
api_reads_per_day: number;
|
|
302
|
+
total_reads_per_day: number;
|
|
303
|
+
relations_ingest_reads_per_day: number;
|
|
304
|
+
api_ingest_reads_per_day: number;
|
|
305
|
+
messages_ingest_reads_per_day: number;
|
|
306
|
+
/**
|
|
307
|
+
* Network-capture read budgets: the two-phase allowance for enumerating an
|
|
308
|
+
* account's OWN 1st-degree connections and followers into a workspace table.
|
|
309
|
+
*
|
|
310
|
+
* Two keys, not one, because Unipile's own guidance for the relations list is
|
|
311
|
+
* explicitly two-phase: a heavier initial sync, then "retrieving the first page
|
|
312
|
+
* only a few times a day with randomly spaced intervals". One flat number would
|
|
313
|
+
* either make the first walk take weeks or leave a steady-state drip reading far
|
|
314
|
+
* more than it needs forever. Both still meter as `relations_ingest_read` against
|
|
315
|
+
* one shared daily counter, so an account cannot spend both budgets at once.
|
|
316
|
+
*/
|
|
317
|
+
network_backfill_reads_per_day: number;
|
|
318
|
+
network_delta_reads_per_day: number;
|
|
319
|
+
};
|
|
320
|
+
/**
|
|
321
|
+
* Safe defaults for LinkedIn action limits.
|
|
322
|
+
*
|
|
323
|
+
* PROVENANCE, because these numbers get copied and should not be laundered into
|
|
324
|
+
* authority they do not have:
|
|
325
|
+
*
|
|
326
|
+
* - The 20-25/day connection-request range and the 40/day message posture come
|
|
327
|
+
* from third-party operator playbooks describing HeyReach-style outreach, NOT
|
|
328
|
+
* from HeyReach. HeyReach's own sending-limits article publishes no numbers at
|
|
329
|
+
* all — it only says you configure per-action daily limits and that it "will
|
|
330
|
+
* always try to reach those limits". An earlier version of this comment cited
|
|
331
|
+
* HeyReach directly; that attribution was wrong.
|
|
332
|
+
* - The READ budgets (relations/api/messages, ingest and network capture) are
|
|
333
|
+
* anchored on Unipile's documented per-route recommendations, which relay
|
|
334
|
+
* LinkedIn's own limits: ~80-100 invitations/day, ~100 profile retrievals/day,
|
|
335
|
+
* and 100/day per account for "all other routes" — the bucket users_relations
|
|
336
|
+
* and users_followers fall into. Unipile enforces none of it; cadence is ours
|
|
337
|
+
* to choose, and exceeding returns 429/500 and can disconnect the account.
|
|
338
|
+
* See developer.unipile.com/docs/provider-limits-and-restrictions.
|
|
339
|
+
*
|
|
340
|
+
* The weekly invite and 200-total-action guards stay enforced independently.
|
|
341
|
+
* Users can lower limits per account, but never raise them above
|
|
342
|
+
* LINKEDIN_SENDER_LIMIT_MAXIMUMS.
|
|
343
|
+
*/
|
|
344
|
+
export declare const LINKEDIN_SENDER_LIMIT_DEFAULTS: LinkedInSenderLimits;
|
|
345
|
+
export declare const LINKEDIN_SENDER_LIMIT_MAXIMUMS: LinkedInSenderLimits;
|
|
346
|
+
/**
|
|
347
|
+
* LinkedIn refuses a new invitation to a member whose earlier invitation from
|
|
348
|
+
* the same account was withdrawn, for about three weeks. OXYGEN records every
|
|
349
|
+
* withdrawal it makes and refuses the re-invite itself for this long, so the
|
|
350
|
+
* refusal is ours (with a reset time) instead of a provider 422.
|
|
351
|
+
*/
|
|
352
|
+
export declare const LINKEDIN_REINVITE_COOLDOWN_DAYS = 21;
|
|
353
|
+
/**
|
|
354
|
+
* Whether a refused invitation is LinkedIn's re-invite cooldown: Unipile answers
|
|
355
|
+
* HTTP 422 with the `cannot_resend_yet` error type. Anchored on the type, with
|
|
356
|
+
* the message as a fallback for the provider shapes that carry the type only in
|
|
357
|
+
* the sentence. Pure and total.
|
|
358
|
+
*/
|
|
359
|
+
export declare function isLinkedInReinviteCooldownRefusal(input: {
|
|
360
|
+
status: number | null;
|
|
361
|
+
providerType: string | null;
|
|
362
|
+
providerMessage: string | null;
|
|
363
|
+
}): boolean;
|
|
250
364
|
export {};
|
|
@@ -196,3 +196,153 @@ export function truncateLinkedInInviteNote(note, limit) {
|
|
|
196
196
|
const kept = cut === 0 ? head : head.slice(0, cut);
|
|
197
197
|
return { note: kept.join("").trimEnd(), truncated: true, originalLength };
|
|
198
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* Safe defaults for LinkedIn action limits.
|
|
201
|
+
*
|
|
202
|
+
* PROVENANCE, because these numbers get copied and should not be laundered into
|
|
203
|
+
* authority they do not have:
|
|
204
|
+
*
|
|
205
|
+
* - The 20-25/day connection-request range and the 40/day message posture come
|
|
206
|
+
* from third-party operator playbooks describing HeyReach-style outreach, NOT
|
|
207
|
+
* from HeyReach. HeyReach's own sending-limits article publishes no numbers at
|
|
208
|
+
* all — it only says you configure per-action daily limits and that it "will
|
|
209
|
+
* always try to reach those limits". An earlier version of this comment cited
|
|
210
|
+
* HeyReach directly; that attribution was wrong.
|
|
211
|
+
* - The READ budgets (relations/api/messages, ingest and network capture) are
|
|
212
|
+
* anchored on Unipile's documented per-route recommendations, which relay
|
|
213
|
+
* LinkedIn's own limits: ~80-100 invitations/day, ~100 profile retrievals/day,
|
|
214
|
+
* and 100/day per account for "all other routes" — the bucket users_relations
|
|
215
|
+
* and users_followers fall into. Unipile enforces none of it; cadence is ours
|
|
216
|
+
* to choose, and exceeding returns 429/500 and can disconnect the account.
|
|
217
|
+
* See developer.unipile.com/docs/provider-limits-and-restrictions.
|
|
218
|
+
*
|
|
219
|
+
* The weekly invite and 200-total-action guards stay enforced independently.
|
|
220
|
+
* Users can lower limits per account, but never raise them above
|
|
221
|
+
* LINKEDIN_SENDER_LIMIT_MAXIMUMS.
|
|
222
|
+
*/
|
|
223
|
+
export const LINKEDIN_SENDER_LIMIT_DEFAULTS = {
|
|
224
|
+
invites_per_day: 20,
|
|
225
|
+
invites_per_week: 100,
|
|
226
|
+
messages_per_day: 40,
|
|
227
|
+
inmails_per_day: 10,
|
|
228
|
+
// Competitors cap open-profile InMail near 800/month; 400 leaves headroom.
|
|
229
|
+
inmails_per_month: 400,
|
|
230
|
+
profile_views_per_day: 50,
|
|
231
|
+
profile_lookups_per_day: 80,
|
|
232
|
+
endorsements_per_day: 5,
|
|
233
|
+
withdrawals_per_day: 10,
|
|
234
|
+
// Matches the order of magnitude of a busy human poster; the publishing
|
|
235
|
+
// scheduler's own 25-per-24h ceiling stays a second guard.
|
|
236
|
+
posts_per_day: 10,
|
|
237
|
+
follows_per_day: 20,
|
|
238
|
+
likes_per_day: 20,
|
|
239
|
+
comments_per_day: 5,
|
|
240
|
+
total_actions_per_day: 150,
|
|
241
|
+
min_action_spacing_seconds: 300,
|
|
242
|
+
action_spacing_jitter_seconds: 300,
|
|
243
|
+
// 1s, no jitter: a person working their own inbox should never meet this gate.
|
|
244
|
+
// It exists only to stop a genuine burst — a stuck key, a runaway client — from
|
|
245
|
+
// firing an account's whole hour in a second, and to keep the per-account row
|
|
246
|
+
// lock (and with it the hourly counter) in play. Jitter is 0 deliberately: the
|
|
247
|
+
// variance in human sending is the human, and adding 0-5s on top only makes the
|
|
248
|
+
// product feel slow. Non-zero spacing is still required — see the fast-path note
|
|
249
|
+
// in reserveLinkedInSenderActionSlot.
|
|
250
|
+
interactive_min_spacing_seconds: 1,
|
|
251
|
+
interactive_spacing_jitter_seconds: 0,
|
|
252
|
+
// The primary bound on human sending now that it no longer claims the daily
|
|
253
|
+
// message cap. 200/hour is ~3/minute sustained: never felt by someone answering
|
|
254
|
+
// their inbox, and still a hard ceiling if the interactive classification is
|
|
255
|
+
// ever wrong.
|
|
256
|
+
interactive_sends_per_hour: 200,
|
|
257
|
+
// Conservative read budgets: roughly human-speed usage. Bulk scraping
|
|
258
|
+
// (full contact-list exports, whole-inbox pulls, search harvesting) is
|
|
259
|
+
// impossible by default — the pattern that gets accounts banned.
|
|
260
|
+
relations_reads_per_day: 20,
|
|
261
|
+
messages_reads_per_day: 200,
|
|
262
|
+
searches_per_day: 20,
|
|
263
|
+
sales_nav_search_results_per_day: 1000,
|
|
264
|
+
api_reads_per_day: 200,
|
|
265
|
+
total_reads_per_day: 1000,
|
|
266
|
+
// Conservative ingestion drips: a large network/history import spreads over
|
|
267
|
+
// many days rather than bursting, and stays well within total_reads_per_day.
|
|
268
|
+
// History backfill is the slowest drip (full thread walks are the most
|
|
269
|
+
// ban-sensitive bulk read), so its default is the tightest of the three.
|
|
270
|
+
relations_ingest_reads_per_day: 15,
|
|
271
|
+
api_ingest_reads_per_day: 20,
|
|
272
|
+
messages_ingest_reads_per_day: 10,
|
|
273
|
+
// Network capture, phase 1: 40% of Unipile's documented ~100/day recommendation
|
|
274
|
+
// for these routes. At 50 members a page that is ~2,000 people/day, so a typical
|
|
275
|
+
// network lands in days rather than weeks while leaving most of the account's
|
|
276
|
+
// daily read headroom unused.
|
|
277
|
+
network_backfill_reads_per_day: 40,
|
|
278
|
+
// Phase 2, once the walk is exhausted: Unipile's "first page a few times a day".
|
|
279
|
+
// New connections and followers arrive at the TOP of both listings, so a handful
|
|
280
|
+
// of first-page reads catches them all; the drip re-walks deeper only if it finds
|
|
281
|
+
// a full page of new people.
|
|
282
|
+
network_delta_reads_per_day: 4,
|
|
283
|
+
};
|
|
284
|
+
export const LINKEDIN_SENDER_LIMIT_MAXIMUMS = {
|
|
285
|
+
invites_per_day: 30,
|
|
286
|
+
invites_per_week: 150,
|
|
287
|
+
messages_per_day: 40,
|
|
288
|
+
// Lowered 40 → 25 and 150 → 100 (2026-09-26, founder decision "add missing
|
|
289
|
+
// caps + tighten"): operator playbooks put InMail at ≤25/day and profile views
|
|
290
|
+
// at 50-100. Stored overrides above these clamp at read time.
|
|
291
|
+
inmails_per_day: 25,
|
|
292
|
+
inmails_per_month: 800,
|
|
293
|
+
profile_views_per_day: 100,
|
|
294
|
+
// Unipile relays ~100 profile retrievals a day as LinkedIn's own ceiling.
|
|
295
|
+
profile_lookups_per_day: 100,
|
|
296
|
+
endorsements_per_day: 10,
|
|
297
|
+
withdrawals_per_day: 20,
|
|
298
|
+
posts_per_day: 25,
|
|
299
|
+
follows_per_day: 50,
|
|
300
|
+
likes_per_day: 50,
|
|
301
|
+
// Lowered 50 → 20 (2026-09-25): public comments are the most visible automated
|
|
302
|
+
// signal an account emits, and the one LinkedIn's inauthentic-engagement
|
|
303
|
+
// enforcement keys on. Stored overrides above this clamp at read time.
|
|
304
|
+
comments_per_day: 20,
|
|
305
|
+
total_actions_per_day: 200,
|
|
306
|
+
min_action_spacing_seconds: 3600,
|
|
307
|
+
action_spacing_jitter_seconds: 1800,
|
|
308
|
+
interactive_min_spacing_seconds: 3600,
|
|
309
|
+
interactive_spacing_jitter_seconds: 1800,
|
|
310
|
+
// Lowered 1000 → 300 (2026-09-25): 5/minute sustained is still far above
|
|
311
|
+
// anyone answering their own inbox, and bounds a misclassified bulk send.
|
|
312
|
+
interactive_sends_per_hour: 300,
|
|
313
|
+
relations_reads_per_day: 100,
|
|
314
|
+
messages_reads_per_day: 600,
|
|
315
|
+
searches_per_day: 100,
|
|
316
|
+
sales_nav_search_results_per_day: 1000,
|
|
317
|
+
api_reads_per_day: 1000,
|
|
318
|
+
total_reads_per_day: 2000,
|
|
319
|
+
relations_ingest_reads_per_day: 100,
|
|
320
|
+
api_ingest_reads_per_day: 200,
|
|
321
|
+
messages_ingest_reads_per_day: 200,
|
|
322
|
+
// 100/day is Unipile's own documented recommendation for "all other routes",
|
|
323
|
+
// which is the bucket users_relations and users_followers fall into. Our hard
|
|
324
|
+
// maximum is exactly the vendor's number and must not be raised above it.
|
|
325
|
+
network_backfill_reads_per_day: 100,
|
|
326
|
+
network_delta_reads_per_day: 20,
|
|
327
|
+
};
|
|
328
|
+
// ===== Re-invite cooldown =====
|
|
329
|
+
/**
|
|
330
|
+
* LinkedIn refuses a new invitation to a member whose earlier invitation from
|
|
331
|
+
* the same account was withdrawn, for about three weeks. OXYGEN records every
|
|
332
|
+
* withdrawal it makes and refuses the re-invite itself for this long, so the
|
|
333
|
+
* refusal is ours (with a reset time) instead of a provider 422.
|
|
334
|
+
*/
|
|
335
|
+
export const LINKEDIN_REINVITE_COOLDOWN_DAYS = 21;
|
|
336
|
+
/**
|
|
337
|
+
* Whether a refused invitation is LinkedIn's re-invite cooldown: Unipile answers
|
|
338
|
+
* HTTP 422 with the `cannot_resend_yet` error type. Anchored on the type, with
|
|
339
|
+
* the message as a fallback for the provider shapes that carry the type only in
|
|
340
|
+
* the sentence. Pure and total.
|
|
341
|
+
*/
|
|
342
|
+
export function isLinkedInReinviteCooldownRefusal(input) {
|
|
343
|
+
if (input.status !== 422)
|
|
344
|
+
return false;
|
|
345
|
+
if (typeof input.providerType === "string" && /(?:^|\/)cannot_resend_yet$/u.test(input.providerType.trim()))
|
|
346
|
+
return true;
|
|
347
|
+
return typeof input.providerMessage === "string" && /\bcannot_resend_yet\b/u.test(input.providerMessage);
|
|
348
|
+
}
|
|
@@ -62,6 +62,15 @@ export type PresignedImportUpload = {
|
|
|
62
62
|
expiresInSeconds: number;
|
|
63
63
|
};
|
|
64
64
|
export type PresignedPublishingMediaUpload = PresignedImportUpload;
|
|
65
|
+
/**
|
|
66
|
+
* Presign the one PUT that stages an import file under `imports/<org>/`.
|
|
67
|
+
*
|
|
68
|
+
* Import staging is deliberately NOT a workspace file (`workspace-file-storage`):
|
|
69
|
+
* it is transient input the worker streams once and deletes, so neither the
|
|
70
|
+
* 100 MiB per-file nor the 1 GiB retained-files ceiling of that store applies.
|
|
71
|
+
* Its ceiling is the caller's plan import limit, checked by the route before it
|
|
72
|
+
* asks, plus the single-PUT maximum below, which no plan exceeds.
|
|
73
|
+
*/
|
|
65
74
|
export declare function presignImportUpload(input: {
|
|
66
75
|
organizationId: string;
|
|
67
76
|
fileName?: string | null;
|
|
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, PutObjectCommand, S3Client, } from "@aws-sdk/client-s3";
|
|
3
3
|
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
4
4
|
import { OxygenError } from "./cli-result.js";
|
|
5
|
+
import { IMPORT_OBJECT_SINGLE_PUT_MAX_BYTES } from "./import-limits.js";
|
|
5
6
|
// S3-compatible object storage for large CSV/file imports. The CLI uploads the
|
|
6
7
|
// raw file straight to the bucket via a presigned PUT URL (bypassing Vercel's
|
|
7
8
|
// ~4.5MB request-body limit), then the Fly worker downloads it and COPY-loads
|
|
@@ -141,7 +142,23 @@ export function isCopilotAttachmentObjectKeyForOrganization(key, organizationId)
|
|
|
141
142
|
export function isPublishingMediaObjectKeyForOrganization(key, organizationId) {
|
|
142
143
|
return key.startsWith(`publishing-media/${organizationId}/`);
|
|
143
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Presign the one PUT that stages an import file under `imports/<org>/`.
|
|
147
|
+
*
|
|
148
|
+
* Import staging is deliberately NOT a workspace file (`workspace-file-storage`):
|
|
149
|
+
* it is transient input the worker streams once and deletes, so neither the
|
|
150
|
+
* 100 MiB per-file nor the 1 GiB retained-files ceiling of that store applies.
|
|
151
|
+
* Its ceiling is the caller's plan import limit, checked by the route before it
|
|
152
|
+
* asks, plus the single-PUT maximum below, which no plan exceeds.
|
|
153
|
+
*/
|
|
144
154
|
export async function presignImportUpload(input) {
|
|
155
|
+
if (!Number.isSafeInteger(input.contentLength) || input.contentLength < 1
|
|
156
|
+
|| input.contentLength > IMPORT_OBJECT_SINGLE_PUT_MAX_BYTES) {
|
|
157
|
+
throw new OxygenError("request_too_large", "Import file is too large to stage in one upload.", {
|
|
158
|
+
details: { file_bytes: input.contentLength, max_file_bytes: IMPORT_OBJECT_SINGLE_PUT_MAX_BYTES },
|
|
159
|
+
exitCode: 1,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
145
162
|
const { client, config } = resolveClient();
|
|
146
163
|
const storageKey = buildImportObjectKey({
|
|
147
164
|
organizationId: input.organizationId,
|
|
@@ -21,4 +21,45 @@ export declare function operationalSpanSnapshot<T extends {
|
|
|
21
21
|
instrumentationScope?: AttributeContainer;
|
|
22
22
|
spanContext(): unknown;
|
|
23
23
|
}>(span: T): T;
|
|
24
|
+
type ExportResource = {
|
|
25
|
+
attributes: Record<string, unknown>;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Maps the SDK resource onto the resource the collector destination exports,
|
|
29
|
+
* without touching the one every other destination reads. `rewrite` receives the
|
|
30
|
+
* SDK resource's attributes and returns the collector's; the result is
|
|
31
|
+
* sanitized once and memoized per source resource, so every span in a batch
|
|
32
|
+
* keeps sharing one resource object and the OTLP encoder still groups them into
|
|
33
|
+
* a single ResourceSpans entry.
|
|
34
|
+
*
|
|
35
|
+
* A resource whose async detectors are still pending (`asyncAttributesPending`)
|
|
36
|
+
* is mapped but not memoized, so attributes that settle later are picked up on
|
|
37
|
+
* a later export instead of an incomplete overlay being kept for the process.
|
|
38
|
+
*/
|
|
39
|
+
export declare function collectorResourceMapper(rewrite: (attributes: Record<string, unknown>) => Record<string, unknown>): (resource: MappableResource | undefined) => ExportResource;
|
|
40
|
+
type MappableResource = {
|
|
41
|
+
attributes?: Record<string, unknown>;
|
|
42
|
+
asyncAttributesPending?: boolean;
|
|
43
|
+
};
|
|
44
|
+
type SnapshotSpan = Parameters<typeof operationalSpanSnapshot>[0];
|
|
45
|
+
/**
|
|
46
|
+
* The collector's per-span export transform: the sanitized snapshot, with the
|
|
47
|
+
* resource swapped for the collector's when a mapper is given. Without one it
|
|
48
|
+
* is exactly `operationalSpanSnapshot`.
|
|
49
|
+
*/
|
|
50
|
+
export declare function collectorSpanSnapshot(resourceFor?: (resource: MappableResource | undefined) => ExportResource): <T extends SnapshotSpan>(span: T) => T;
|
|
51
|
+
type MetricExporterLike = {
|
|
52
|
+
export(metrics: unknown, resultCallback: (result: {
|
|
53
|
+
code: number;
|
|
54
|
+
error?: Error | undefined;
|
|
55
|
+
}) => void): void;
|
|
56
|
+
shutdown(): Promise<void>;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Wraps a metric exporter so each ResourceMetrics batch carries the collector's
|
|
60
|
+
* resource. The reader-facing methods (temporality/aggregation selection,
|
|
61
|
+
* forceFlush) are forwarded only when the inner exporter has them, so the
|
|
62
|
+
* PeriodicExportingMetricReader sees exactly what it would have unwrapped.
|
|
63
|
+
*/
|
|
64
|
+
export declare function withCollectorMetricResource<E extends MetricExporterLike>(inner: E, resourceFor: (resource: MappableResource | undefined) => ExportResource): E;
|
|
24
65
|
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizeTelemetryAttributes, redactSecretsInString } from "./redaction.js";
|
|
2
|
+
import { forwardExporterMethods } from "./telemetry-export-observer.js";
|
|
2
3
|
// Namespaced semantic-convention fields evade the generic exact-key log policy.
|
|
3
4
|
// Numeric usage remains operational data, subject to the shared secret policy.
|
|
4
5
|
const PAYLOAD_KEY = /(?:^|[._-])(?:prompts?|completions?|inputs?|outputs?|payloads?|rows?|messages?|content|body|customer_data)(?:$|[._-])|^(?:request|response)$/i;
|
|
@@ -71,3 +72,57 @@ function stripOperationalUrl(value) {
|
|
|
71
72
|
return value.split(/[?#]/, 1)[0] ?? "";
|
|
72
73
|
}
|
|
73
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Maps the SDK resource onto the resource the collector destination exports,
|
|
77
|
+
* without touching the one every other destination reads. `rewrite` receives the
|
|
78
|
+
* SDK resource's attributes and returns the collector's; the result is
|
|
79
|
+
* sanitized once and memoized per source resource, so every span in a batch
|
|
80
|
+
* keeps sharing one resource object and the OTLP encoder still groups them into
|
|
81
|
+
* a single ResourceSpans entry.
|
|
82
|
+
*
|
|
83
|
+
* A resource whose async detectors are still pending (`asyncAttributesPending`)
|
|
84
|
+
* is mapped but not memoized, so attributes that settle later are picked up on
|
|
85
|
+
* a later export instead of an incomplete overlay being kept for the process.
|
|
86
|
+
*/
|
|
87
|
+
export function collectorResourceMapper(rewrite) {
|
|
88
|
+
const cache = new WeakMap();
|
|
89
|
+
let withoutSource;
|
|
90
|
+
return (resource) => {
|
|
91
|
+
if (!resource)
|
|
92
|
+
return withoutSource ??= { attributes: operationalAttributes(rewrite({})) };
|
|
93
|
+
const cached = cache.get(resource);
|
|
94
|
+
if (cached)
|
|
95
|
+
return cached;
|
|
96
|
+
const mapped = { attributes: operationalAttributes(rewrite(resource.attributes ?? {})) };
|
|
97
|
+
if (!resource.asyncAttributesPending)
|
|
98
|
+
cache.set(resource, mapped);
|
|
99
|
+
return mapped;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The collector's per-span export transform: the sanitized snapshot, with the
|
|
104
|
+
* resource swapped for the collector's when a mapper is given. Without one it
|
|
105
|
+
* is exactly `operationalSpanSnapshot`.
|
|
106
|
+
*/
|
|
107
|
+
export function collectorSpanSnapshot(resourceFor) {
|
|
108
|
+
if (!resourceFor)
|
|
109
|
+
return (span) => operationalSpanSnapshot(span);
|
|
110
|
+
return (span) => ({ ...operationalSpanSnapshot(span), resource: resourceFor(span.resource) });
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Wraps a metric exporter so each ResourceMetrics batch carries the collector's
|
|
114
|
+
* resource. The reader-facing methods (temporality/aggregation selection,
|
|
115
|
+
* forceFlush) are forwarded only when the inner exporter has them, so the
|
|
116
|
+
* PeriodicExportingMetricReader sees exactly what it would have unwrapped.
|
|
117
|
+
*/
|
|
118
|
+
export function withCollectorMetricResource(inner, resourceFor) {
|
|
119
|
+
const wrapped = {
|
|
120
|
+
export(metrics, resultCallback) {
|
|
121
|
+
const batch = metrics;
|
|
122
|
+
inner.export({ ...batch, resource: resourceFor(batch.resource) }, resultCallback);
|
|
123
|
+
},
|
|
124
|
+
shutdown: () => inner.shutdown(),
|
|
125
|
+
};
|
|
126
|
+
forwardExporterMethods(inner, wrapped);
|
|
127
|
+
return wrapped;
|
|
128
|
+
}
|