@oxygen-agent/cli 1.662.0 → 1.681.2
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/index.js +101 -439
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +6 -1
- package/node_modules/@oxygen/shared/dist/collab.d.ts +6 -5
- package/node_modules/@oxygen/shared/dist/collab.js +11 -4
- package/node_modules/@oxygen/shared/dist/credit-guidance.d.ts +9 -0
- package/node_modules/@oxygen/shared/dist/credit-guidance.js +90 -1
- package/node_modules/@oxygen/shared/dist/file-import.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/file-import.js +16 -13
- package/node_modules/@oxygen/shared/dist/mailbox-import.d.ts +58 -0
- package/node_modules/@oxygen/shared/dist/mailbox-import.js +556 -0
- package/node_modules/@oxygen/shared/dist/tags.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/tags.js +7 -1
- package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.js +1 -1
- package/node_modules/@oxygen/shared/package.json +5 -0
- package/package.json +1 -1
|
@@ -294,7 +294,12 @@ export const OXYGEN_CAPABILITY_ROUTES = [
|
|
|
294
294
|
gatewayCommands: ["sequences send", "sequences create", "sequences enroll", "sequences start", "sequences list"],
|
|
295
295
|
skills: ["oxygen-sequencer"],
|
|
296
296
|
endpointSections: ["senders", "sequencer", "sequences", "spintax", "suppressions", "voice"],
|
|
297
|
-
|
|
297
|
+
// "sender identity", "sender profile" and "phone number" are here because a
|
|
298
|
+
// blind-user eval (2026-08-09) asked for "our sender identity" and got routed
|
|
299
|
+
// to workspace-access — "identity" matched the AUTH sense. The nouns a user
|
|
300
|
+
// reaches for when naming a sending identity or the dialing pool have to land
|
|
301
|
+
// on the group that actually owns `senders profiles` and `voice numbers`.
|
|
302
|
+
intentTerms: ["sequence", "campaign", "cadence", "enroll", "outreach", "linkedin message", "linkedin dm", "nurture", "follow up", "sender rotation", "stop on reply", "sender identity", "sending identity", "sender profile", "phone number", "dialing", "dialer"],
|
|
298
303
|
},
|
|
299
304
|
{
|
|
300
305
|
id: "publishing",
|
|
@@ -4,14 +4,15 @@ import { type TagKind } from "./tags.js";
|
|
|
4
4
|
*
|
|
5
5
|
* Seeded from {@link TAG_KINDS}: anything worth labelling for a campaign is
|
|
6
6
|
* worth discussing with the client who signs that campaign off. The list is
|
|
7
|
-
* RESTATED rather than aliased because it is also a stored value —
|
|
8
|
-
* `ox_collab`
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* RESTATED rather than aliased because it is also a stored value — existing
|
|
8
|
+
* `ox_collab` rows pin it — so widening the tag vocabulary must be a deliberate
|
|
9
|
+
* decision here, not a silent side effect. (`subject_kind` deliberately carries
|
|
10
|
+
* no enum CHECK; 0182_collab.sql explains why, so adding a kind here needs no
|
|
11
|
+
* tenant migration.) The alignment is enforced
|
|
11
12
|
* both ways: {@link EveryTagKindIsCommentable} fails the build if a taggable
|
|
12
13
|
* kind is missing, and the paired test pins today's list exactly.
|
|
13
14
|
*/
|
|
14
|
-
export declare const COLLAB_SUBJECT_KINDS: readonly ["knowledge_page", "publishing_post", "sequence", "table", "workflow", "recipe", "conversation", "mailbox", "sender", "record", "domain", "project"];
|
|
15
|
+
export declare const COLLAB_SUBJECT_KINDS: readonly ["knowledge_page", "publishing_post", "sequence", "table", "workflow", "recipe", "conversation", "mailbox", "sender", "record", "domain", "project", "sender_profile", "voice_number"];
|
|
15
16
|
export type CollabSubjectKind = (typeof COLLAB_SUBJECT_KINDS)[number];
|
|
16
17
|
/** Compile-time assertion helper: instantiating with `false` is a type error. */
|
|
17
18
|
type AssertTrue<T extends true> = T;
|
|
@@ -22,10 +22,11 @@ import { OxygenError } from "./cli-result.js";
|
|
|
22
22
|
*
|
|
23
23
|
* Seeded from {@link TAG_KINDS}: anything worth labelling for a campaign is
|
|
24
24
|
* worth discussing with the client who signs that campaign off. The list is
|
|
25
|
-
* RESTATED rather than aliased because it is also a stored value —
|
|
26
|
-
* `ox_collab`
|
|
27
|
-
*
|
|
28
|
-
*
|
|
25
|
+
* RESTATED rather than aliased because it is also a stored value — existing
|
|
26
|
+
* `ox_collab` rows pin it — so widening the tag vocabulary must be a deliberate
|
|
27
|
+
* decision here, not a silent side effect. (`subject_kind` deliberately carries
|
|
28
|
+
* no enum CHECK; 0182_collab.sql explains why, so adding a kind here needs no
|
|
29
|
+
* tenant migration.) The alignment is enforced
|
|
29
30
|
* both ways: {@link EveryTagKindIsCommentable} fails the build if a taggable
|
|
30
31
|
* kind is missing, and the paired test pins today's list exactly.
|
|
31
32
|
*/
|
|
@@ -42,6 +43,8 @@ export const COLLAB_SUBJECT_KINDS = [
|
|
|
42
43
|
"record",
|
|
43
44
|
"domain",
|
|
44
45
|
"project",
|
|
46
|
+
"sender_profile",
|
|
47
|
+
"voice_number",
|
|
45
48
|
];
|
|
46
49
|
export function isCollabSubjectKind(value) {
|
|
47
50
|
return typeof value === "string" && COLLAB_SUBJECT_KINDS.includes(value);
|
|
@@ -69,6 +72,8 @@ export const COLLAB_SUBJECT_LABELS = {
|
|
|
69
72
|
record: { one: "CRM record", many: "CRM records" },
|
|
70
73
|
domain: { one: "sending domain", many: "sending domains" },
|
|
71
74
|
project: { one: "project", many: "projects" },
|
|
75
|
+
sender_profile: { one: "sender profile", many: "sender profiles" },
|
|
76
|
+
voice_number: { one: "phone number", many: "phone numbers" },
|
|
72
77
|
};
|
|
73
78
|
/**
|
|
74
79
|
* What a thread on each kind is usually about, for `--help` and MCP tool
|
|
@@ -93,6 +98,8 @@ export const COLLAB_SUBJECT_PROSE = {
|
|
|
93
98
|
record: "a CRM record's truth",
|
|
94
99
|
domain: "a sending domain's DNS and warmup",
|
|
95
100
|
project: "a project's scope",
|
|
101
|
+
sender_profile: "a sending identity's accounts and inboxes",
|
|
102
|
+
voice_number: "a dialing number's caps and warmup",
|
|
96
103
|
};
|
|
97
104
|
/** Oxford-comma list: ["a"] -> "a"; ["a","b"] -> "a and b"; ["a","b","c"] -> "a, b, and c". */
|
|
98
105
|
function formatProseList(items) {
|
|
@@ -5,9 +5,18 @@ export type CreditGuidance = {
|
|
|
5
5
|
available_credits: number | null;
|
|
6
6
|
credit_posture: CreditPosture;
|
|
7
7
|
credit_guidance: string;
|
|
8
|
+
/** Present only when the caller supplied an expected (average) figure. */
|
|
9
|
+
expected_credits?: number;
|
|
8
10
|
};
|
|
9
11
|
export declare function buildCreditGuidance(input: {
|
|
10
12
|
estimatedCredits: number | null | undefined;
|
|
11
13
|
availableCredits: number | null | undefined;
|
|
12
14
|
headroomMultiplier?: number;
|
|
15
|
+
/** The average a run actually costs. When given, the ceiling is sized from this. */
|
|
16
|
+
expectedCredits?: number | null | undefined;
|
|
17
|
+
/** Dearest single lane a row can reach — the tail the band covers. */
|
|
18
|
+
worstCaseCreditsPerRow?: number | null | undefined;
|
|
19
|
+
rowCount?: number | null | undefined;
|
|
20
|
+
/** True when estimatedCredits is a genuine per-row bound (bill-on-hit lanes). */
|
|
21
|
+
worstCaseIsHardBound?: boolean;
|
|
13
22
|
}): CreditGuidance;
|
|
@@ -1,10 +1,66 @@
|
|
|
1
1
|
const DEFAULT_HEADROOM_MULTIPLIER = 1.25;
|
|
2
|
+
/**
|
|
3
|
+
* Size a ceiling from the EXPECTED cost plus a variance band, rather than from
|
|
4
|
+
* the worst case.
|
|
5
|
+
*
|
|
6
|
+
* A waterfall lane bills only on a hit and a row stops at the first lane that
|
|
7
|
+
* resolves it, so the worst case is the dearest lane on EVERY row — a tail event,
|
|
8
|
+
* not a price. Quoting it made a 4.8k job read as 31.6k and told workspaces with
|
|
9
|
+
* ample balance they were "tight".
|
|
10
|
+
*
|
|
11
|
+
* The band is `expected + worst_row x tailRows`, where `tailRows` scales like
|
|
12
|
+
* sqrt(n): unlucky rows are a sample of the tail, and their count grows with the
|
|
13
|
+
* square root of the run, not linearly. `+1` guarantees a run can always afford at
|
|
14
|
+
* least one row above the mean, and `min(n, ...)` collapses the whole thing to the
|
|
15
|
+
* true worst case on tiny runs, where it is cheap to just cover everything.
|
|
16
|
+
*
|
|
17
|
+
* `expected x 1.25` remains the floor, so a flat/cheap chain with no meaningful
|
|
18
|
+
* tail keeps exactly the old headroom.
|
|
19
|
+
*/
|
|
20
|
+
function expectedCreditCeiling(input) {
|
|
21
|
+
const floor = input.expectedCredits * input.headroomMultiplier;
|
|
22
|
+
let ceiling = floor;
|
|
23
|
+
if (input.worstCaseCreditsPerRow !== null && input.rowCount !== null && input.rowCount > 0) {
|
|
24
|
+
const tailRows = Math.min(input.rowCount, Math.ceil(Math.sqrt(input.rowCount)) + 1);
|
|
25
|
+
ceiling = Math.max(floor, input.expectedCredits + input.worstCaseCreditsPerRow * tailRows);
|
|
26
|
+
}
|
|
27
|
+
// Only clamp where the worst case is a REAL per-row bound — bill-on-hit lanes,
|
|
28
|
+
// where one lane is the most a row can cost. An AI/tool column's "estimate" is a
|
|
29
|
+
// token guess that real usage can exceed, so clamping there would reintroduce
|
|
30
|
+
// mid-run truncation on exactly the columns that have no ceiling of their own.
|
|
31
|
+
//
|
|
32
|
+
// Clamp to the worst case WITH its headroom, not to the bare figure: the row
|
|
33
|
+
// count is itself an estimate for a filtered selection, and the multiplier is
|
|
34
|
+
// what absorbs an undercount. Clamping to the bare total would make a small run
|
|
35
|
+
// strictly tighter than before this change, which is the opposite of the point.
|
|
36
|
+
if (input.worstCaseIsHardBound && input.worstCaseTotal !== null) {
|
|
37
|
+
ceiling = Math.min(ceiling, input.worstCaseTotal * input.headroomMultiplier);
|
|
38
|
+
}
|
|
39
|
+
return Math.max(1, Math.ceil(ceiling));
|
|
40
|
+
}
|
|
2
41
|
export function buildCreditGuidance(input) {
|
|
3
42
|
const estimatedCredits = normalizeCreditValue(input.estimatedCredits);
|
|
4
43
|
const availableCredits = normalizeCreditValue(input.availableCredits);
|
|
5
|
-
const
|
|
44
|
+
const expectedCredits = normalizeCreditValue(input.expectedCredits);
|
|
45
|
+
const headroomMultiplier = normalizeMultiplier(input.headroomMultiplier);
|
|
46
|
+
// No expected figure supplied => byte-identical to the pre-existing behaviour,
|
|
47
|
+
// so publishing / sequences / AI callers are untouched by this change.
|
|
48
|
+
const recommendedMaxCredits = expectedCredits === null
|
|
49
|
+
? recommendedCreditCeiling(estimatedCredits, headroomMultiplier)
|
|
50
|
+
: expectedCreditCeiling({
|
|
51
|
+
expectedCredits,
|
|
52
|
+
worstCaseCreditsPerRow: normalizeCreditValue(input.worstCaseCreditsPerRow),
|
|
53
|
+
worstCaseTotal: estimatedCredits,
|
|
54
|
+
rowCount: typeof input.rowCount === "number" && Number.isFinite(input.rowCount)
|
|
55
|
+
? Math.max(0, Math.floor(input.rowCount))
|
|
56
|
+
: null,
|
|
57
|
+
headroomMultiplier,
|
|
58
|
+
worstCaseIsHardBound: input.worstCaseIsHardBound === true,
|
|
59
|
+
});
|
|
60
|
+
const expectedFields = expectedCredits === null ? {} : { expected_credits: expectedCredits };
|
|
6
61
|
if (estimatedCredits === null) {
|
|
7
62
|
return {
|
|
63
|
+
...expectedFields,
|
|
8
64
|
estimated_credits: null,
|
|
9
65
|
recommended_max_credits: null,
|
|
10
66
|
available_credits: availableCredits,
|
|
@@ -14,6 +70,7 @@ export function buildCreditGuidance(input) {
|
|
|
14
70
|
}
|
|
15
71
|
if (availableCredits === null) {
|
|
16
72
|
return {
|
|
73
|
+
...expectedFields,
|
|
17
74
|
estimated_credits: estimatedCredits,
|
|
18
75
|
recommended_max_credits: recommendedMaxCredits,
|
|
19
76
|
available_credits: null,
|
|
@@ -22,7 +79,30 @@ export function buildCreditGuidance(input) {
|
|
|
22
79
|
};
|
|
23
80
|
}
|
|
24
81
|
if (recommendedMaxCredits !== null && recommendedMaxCredits <= availableCredits) {
|
|
82
|
+
// When an expected figure drove the ceiling, the copy has to describe THAT,
|
|
83
|
+
// not "the estimate plus 25% headroom" — the old sentence would misstate how
|
|
84
|
+
// the number was derived. The tail warning is added only when a full
|
|
85
|
+
// fall-through really could outrun the balance; saying it otherwise invents
|
|
86
|
+
// a risk that does not exist.
|
|
87
|
+
if (expectedCredits !== null) {
|
|
88
|
+
const tailWarning = estimatedCredits > availableCredits
|
|
89
|
+
? " Only a run where nearly every row falls through to the dearest provider could exceed your"
|
|
90
|
+
+ " balance — the run keeps going and stops when the balance runs out, keeping every row it"
|
|
91
|
+
+ " has already completed."
|
|
92
|
+
: "";
|
|
93
|
+
return {
|
|
94
|
+
...expectedFields,
|
|
95
|
+
estimated_credits: estimatedCredits,
|
|
96
|
+
recommended_max_credits: recommendedMaxCredits,
|
|
97
|
+
available_credits: availableCredits,
|
|
98
|
+
credit_posture: "sufficient",
|
|
99
|
+
credit_guidance: `Credits cover the expected cost (≈${formatCreditsForCopy(expectedCredits)} credits) with `
|
|
100
|
+
+ "headroom for the rows that fall through to a pricier provider."
|
|
101
|
+
+ `${tailWarning} Unused credits are not spent.`,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
25
104
|
return {
|
|
105
|
+
...expectedFields,
|
|
26
106
|
estimated_credits: estimatedCredits,
|
|
27
107
|
recommended_max_credits: recommendedMaxCredits,
|
|
28
108
|
available_credits: availableCredits,
|
|
@@ -33,6 +113,7 @@ export function buildCreditGuidance(input) {
|
|
|
33
113
|
};
|
|
34
114
|
}
|
|
35
115
|
return {
|
|
116
|
+
...expectedFields,
|
|
36
117
|
estimated_credits: estimatedCredits,
|
|
37
118
|
recommended_max_credits: availableCredits > 0 ? roundCreditValue(availableCredits) : null,
|
|
38
119
|
available_credits: availableCredits,
|
|
@@ -51,6 +132,14 @@ function recommendedCreditCeiling(estimatedCredits, headroomMultiplier = DEFAULT
|
|
|
51
132
|
: DEFAULT_HEADROOM_MULTIPLIER;
|
|
52
133
|
return Math.max(1, Math.ceil(normalized * multiplier));
|
|
53
134
|
}
|
|
135
|
+
function normalizeMultiplier(value) {
|
|
136
|
+
return Number.isFinite(value) && (value ?? 0) > 0
|
|
137
|
+
? value
|
|
138
|
+
: DEFAULT_HEADROOM_MULTIPLIER;
|
|
139
|
+
}
|
|
140
|
+
function formatCreditsForCopy(value) {
|
|
141
|
+
return Math.round(value).toLocaleString("en-US");
|
|
142
|
+
}
|
|
54
143
|
function normalizeCreditValue(value) {
|
|
55
144
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
|
|
56
145
|
return null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Row } from "read-excel-file/node";
|
|
1
2
|
import { type ImportColumnDataType } from "./column-types.js";
|
|
2
3
|
export type RowsFileFormat = "json" | "jsonl" | "csv" | "xlsx";
|
|
3
4
|
export type ImportTableColumn = {
|
|
@@ -16,6 +17,10 @@ export declare function normalizeRowsFormat(value: string | undefined, fallback:
|
|
|
16
17
|
export declare function parseRowsFileBuffer(buffer: Buffer, format: RowsFileFormat, options?: {
|
|
17
18
|
sheet?: string;
|
|
18
19
|
}): Promise<Record<string, unknown>[]>;
|
|
20
|
+
/** Read raw workbook cells for consumers with stricter, domain-specific headers. */
|
|
21
|
+
export declare function parseXlsxWorkbookBuffer(buffer: Buffer, options?: {
|
|
22
|
+
sheet?: string;
|
|
23
|
+
}): Promise<Row[]>;
|
|
19
24
|
export declare function iterateRowsFileBufferBatches(buffer: Buffer, format: RowsFileFormat, options?: {
|
|
20
25
|
sheet?: string;
|
|
21
26
|
batchSize?: number;
|
|
@@ -32,6 +32,21 @@ export async function parseRowsFileBuffer(buffer, format, options = {}) {
|
|
|
32
32
|
return await parseXlsxRows(buffer, options);
|
|
33
33
|
return parseRowsText(buffer.toString("utf8"), format);
|
|
34
34
|
}
|
|
35
|
+
/** Read raw workbook cells for consumers with stricter, domain-specific headers. */
|
|
36
|
+
export async function parseXlsxWorkbookBuffer(buffer, options = {}) {
|
|
37
|
+
try {
|
|
38
|
+
return await readXlsxFile(buffer, options.sheet ? { sheet: options.sheet } : {});
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
throw new OxygenError("invalid_rows", "XLSX sheet was not found.", {
|
|
42
|
+
details: {
|
|
43
|
+
sheet: options.sheet ?? null,
|
|
44
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
45
|
+
},
|
|
46
|
+
exitCode: 1,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
35
50
|
export async function* iterateRowsFileBufferBatches(buffer, format, options = {}) {
|
|
36
51
|
const batchSize = normalizeBatchSize(options.batchSize);
|
|
37
52
|
if (format === "csv") {
|
|
@@ -127,19 +142,7 @@ export function normalizeImportColumnKey(value) {
|
|
|
127
142
|
return toSnakeIdentifier(value, "column");
|
|
128
143
|
}
|
|
129
144
|
async function parseXlsxRows(buffer, options) {
|
|
130
|
-
|
|
131
|
-
try {
|
|
132
|
-
rows = await readXlsxFile(buffer, options.sheet ? { sheet: options.sheet } : {});
|
|
133
|
-
}
|
|
134
|
-
catch (error) {
|
|
135
|
-
throw new OxygenError("invalid_rows", "XLSX sheet was not found.", {
|
|
136
|
-
details: {
|
|
137
|
-
sheet: options.sheet ?? null,
|
|
138
|
-
reason: error instanceof Error ? error.message : String(error),
|
|
139
|
-
},
|
|
140
|
-
exitCode: 1,
|
|
141
|
-
});
|
|
142
|
-
}
|
|
145
|
+
const rows = await parseXlsxWorkbookBuffer(buffer, options);
|
|
143
146
|
const [header, ...body] = rows;
|
|
144
147
|
if (!header || header.length === 0)
|
|
145
148
|
return [];
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export type MailboxImportMode = "identity" | "credential";
|
|
2
|
+
export type MailboxImportFileFormat = "json" | "jsonl" | "csv" | "xlsx";
|
|
3
|
+
export type MailboxImportValidationSource = "identity_file" | "credential_file" | "hypertide_file";
|
|
4
|
+
export type NormalizedMailboxImportRow = {
|
|
5
|
+
email_address: string;
|
|
6
|
+
provider: "google" | "microsoft";
|
|
7
|
+
workspace_external_id?: string;
|
|
8
|
+
infrastructure_platform?: "google_workspace" | "microsoft_365" | "microsoft_azure";
|
|
9
|
+
tenant_id?: string;
|
|
10
|
+
app_password?: string;
|
|
11
|
+
};
|
|
12
|
+
export type MailboxImportValidationSummary = {
|
|
13
|
+
valid: true;
|
|
14
|
+
source: MailboxImportValidationSource;
|
|
15
|
+
source_provider?: string;
|
|
16
|
+
rows: number;
|
|
17
|
+
providers: {
|
|
18
|
+
google: number;
|
|
19
|
+
microsoft: number;
|
|
20
|
+
};
|
|
21
|
+
infrastructure_platforms: Record<string, number>;
|
|
22
|
+
credential_rows: number;
|
|
23
|
+
identity_only_rows: number;
|
|
24
|
+
limits: {
|
|
25
|
+
max_rows: number;
|
|
26
|
+
max_file_bytes: number;
|
|
27
|
+
};
|
|
28
|
+
mutation: false;
|
|
29
|
+
network_request: false;
|
|
30
|
+
provider_call: false;
|
|
31
|
+
credits_used: 0;
|
|
32
|
+
next_action: string;
|
|
33
|
+
};
|
|
34
|
+
export declare const MAILBOX_IMPORT_FILE_MAX_BYTES: number;
|
|
35
|
+
export declare const MAILBOX_IMPORT_ROW_LIMIT = 500;
|
|
36
|
+
/**
|
|
37
|
+
* Infer the bounded mailbox-import format without node:path so browser and CLI
|
|
38
|
+
* preflight use the same extension contract.
|
|
39
|
+
*/
|
|
40
|
+
export declare function inferMailboxImportFileFormat(name: string): MailboxImportFileFormat;
|
|
41
|
+
/**
|
|
42
|
+
* Parse credential-sensitive text without ever echoing file contents. JSON may
|
|
43
|
+
* be either a bare array or the common `{ mailboxes: [...] }` export wrapper.
|
|
44
|
+
*/
|
|
45
|
+
export declare function parseMailboxImportText(text: string, format: Exclude<MailboxImportFileFormat, "xlsx">): unknown[];
|
|
46
|
+
/** Convert read-excel-file's browser rows to the same object rows as CSV. */
|
|
47
|
+
export declare function normalizeMailboxWorkbookRows(rows: readonly (readonly unknown[])[]): unknown[];
|
|
48
|
+
/**
|
|
49
|
+
* Whitelist mailbox fields before a local file crosses the network. The same
|
|
50
|
+
* function runs in the CLI and first-party web app, so accepted aliases and
|
|
51
|
+
* non-transferable-secret rejection cannot drift by surface.
|
|
52
|
+
*/
|
|
53
|
+
export declare function normalizeMailboxImportFile(value: unknown, mode: MailboxImportMode): NormalizedMailboxImportRow[];
|
|
54
|
+
export declare function summarizeMailboxImportValidation(mailboxes: readonly NormalizedMailboxImportRow[], input: {
|
|
55
|
+
source: MailboxImportValidationSource;
|
|
56
|
+
sourceProvider: string | null;
|
|
57
|
+
}): MailboxImportValidationSummary;
|
|
58
|
+
export declare function normalizeMailboxImportVendor(raw: string | null | undefined, from: string | null | undefined): string | null;
|