deepline 0.1.76 → 0.1.78
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 +5 -8
- package/dist/cli/index.js +534 -350
- package/dist/cli/index.mjs +547 -363
- package/dist/index.d.mts +40 -2
- package/dist/index.d.ts +40 -2
- package/dist/index.js +198 -2
- package/dist/index.mjs +198 -2
- package/dist/repo/sdk/src/client.ts +7 -0
- package/dist/repo/sdk/src/play.ts +1 -1
- package/dist/repo/sdk/src/release.ts +2 -2
- package/dist/repo/sdk/src/types.ts +4 -0
- package/dist/repo/shared_libs/play-runtime/email-status.ts +301 -0
- package/dist/repo/shared_libs/play-runtime/tool-result.ts +58 -1
- package/dist/repo/shared_libs/plays/dataset.ts +3 -1
- package/package.json +1 -1
|
@@ -663,6 +663,9 @@ export interface PlayListItem {
|
|
|
663
663
|
isDraftDirty?: boolean;
|
|
664
664
|
inputSchema?: Record<string, unknown> | null;
|
|
665
665
|
outputSchema?: Record<string, unknown> | null;
|
|
666
|
+
staticPipeline?: unknown;
|
|
667
|
+
currentRevision?: PlayRevisionSummary | null;
|
|
668
|
+
liveRevision?: PlayRevisionSummary | null;
|
|
666
669
|
aliases?: string[];
|
|
667
670
|
}
|
|
668
671
|
|
|
@@ -677,6 +680,7 @@ export interface PlayDescription {
|
|
|
677
680
|
aliases: string[];
|
|
678
681
|
inputSchema?: Record<string, unknown> | null;
|
|
679
682
|
outputSchema?: Record<string, unknown> | null;
|
|
683
|
+
staticPipeline?: Record<string, unknown> | null;
|
|
680
684
|
csvInput?: Record<string, unknown> | null;
|
|
681
685
|
rowOutputSchema?: Record<string, unknown> | null;
|
|
682
686
|
runCommand: string;
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
export type EmailStatusVerdict =
|
|
2
|
+
| 'send'
|
|
3
|
+
| 'send_with_caution'
|
|
4
|
+
| 'verify_next'
|
|
5
|
+
| 'hold'
|
|
6
|
+
| 'drop';
|
|
7
|
+
|
|
8
|
+
export type EmailStatusValue =
|
|
9
|
+
| 'valid'
|
|
10
|
+
| 'invalid'
|
|
11
|
+
| 'catch_all'
|
|
12
|
+
| 'valid_catch_all'
|
|
13
|
+
| 'unknown'
|
|
14
|
+
| 'do_not_mail'
|
|
15
|
+
| 'spamtrap'
|
|
16
|
+
| 'abuse'
|
|
17
|
+
| 'disposable';
|
|
18
|
+
|
|
19
|
+
export type EmailDeliverability = 'high' | 'medium' | 'low' | 'unknown';
|
|
20
|
+
|
|
21
|
+
export type EmailMxClass =
|
|
22
|
+
| 'consumer_mailbox'
|
|
23
|
+
| 'workspace_mailbox'
|
|
24
|
+
| 'security_gateway'
|
|
25
|
+
| 'on_prem'
|
|
26
|
+
| 'unknown';
|
|
27
|
+
|
|
28
|
+
export type EmailStatus = {
|
|
29
|
+
verdict: EmailStatusVerdict;
|
|
30
|
+
status: EmailStatusValue;
|
|
31
|
+
verified: boolean;
|
|
32
|
+
confidence: number | null;
|
|
33
|
+
reasons: string[];
|
|
34
|
+
signals: {
|
|
35
|
+
catch_all: boolean | null;
|
|
36
|
+
deliverability: EmailDeliverability;
|
|
37
|
+
mx_class: EmailMxClass;
|
|
38
|
+
mx_provider: string | null;
|
|
39
|
+
mx_record: string | null;
|
|
40
|
+
fraud_score: number | null;
|
|
41
|
+
disposable: boolean | null;
|
|
42
|
+
role_based: boolean | null;
|
|
43
|
+
free_email: boolean | null;
|
|
44
|
+
abuse: boolean | null;
|
|
45
|
+
spamtrap: boolean | null;
|
|
46
|
+
suspect: boolean | null;
|
|
47
|
+
valid: boolean | null;
|
|
48
|
+
};
|
|
49
|
+
provider: {
|
|
50
|
+
name: string;
|
|
51
|
+
raw_status: string | boolean | number | null;
|
|
52
|
+
raw_score: number | null;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type EmailStatusMapEntry = {
|
|
57
|
+
status: EmailStatusValue;
|
|
58
|
+
verdict?: EmailStatusVerdict;
|
|
59
|
+
verified?: boolean;
|
|
60
|
+
reason?: string;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type EmailStatusRule = EmailStatusMapEntry & {
|
|
64
|
+
when: Record<string, string | number | boolean | null>;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type EmailStatusExtractorConfig = {
|
|
68
|
+
provider: string;
|
|
69
|
+
rawStatus?: string[];
|
|
70
|
+
rawScore?: string[];
|
|
71
|
+
valid?: string[];
|
|
72
|
+
deliverability?: string[];
|
|
73
|
+
catchAll?: string[];
|
|
74
|
+
mxProvider?: string[];
|
|
75
|
+
mxRecord?: string[];
|
|
76
|
+
fraudScore?: string[];
|
|
77
|
+
disposable?: string[];
|
|
78
|
+
roleBased?: string[];
|
|
79
|
+
freeEmail?: string[];
|
|
80
|
+
abuse?: string[];
|
|
81
|
+
spamtrap?: string[];
|
|
82
|
+
suspect?: string[];
|
|
83
|
+
statusMap?: Record<string, EmailStatusMapEntry>;
|
|
84
|
+
rules?: EmailStatusRule[];
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export type EmailStatusBuildInput = {
|
|
88
|
+
config: EmailStatusExtractorConfig;
|
|
89
|
+
values: Record<string, unknown>;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const DEFAULT_STATUS_MAP: Record<string, EmailStatusMapEntry> = {
|
|
93
|
+
verified: { status: 'valid', verdict: 'send', verified: true },
|
|
94
|
+
valid: { status: 'valid', verdict: 'send', verified: true },
|
|
95
|
+
deliverable: { status: 'valid', verdict: 'send', verified: true },
|
|
96
|
+
true: { status: 'valid', verdict: 'send', verified: true },
|
|
97
|
+
invalid: { status: 'invalid', verdict: 'drop', verified: false },
|
|
98
|
+
undeliverable: { status: 'invalid', verdict: 'drop', verified: false },
|
|
99
|
+
false: { status: 'invalid', verdict: 'drop', verified: false },
|
|
100
|
+
'catch-all': {
|
|
101
|
+
status: 'catch_all',
|
|
102
|
+
verdict: 'verify_next',
|
|
103
|
+
verified: false,
|
|
104
|
+
},
|
|
105
|
+
catch_all: {
|
|
106
|
+
status: 'catch_all',
|
|
107
|
+
verdict: 'verify_next',
|
|
108
|
+
verified: false,
|
|
109
|
+
},
|
|
110
|
+
valid_catch_all: {
|
|
111
|
+
status: 'valid_catch_all',
|
|
112
|
+
verdict: 'send_with_caution',
|
|
113
|
+
verified: true,
|
|
114
|
+
},
|
|
115
|
+
accept_all: {
|
|
116
|
+
status: 'catch_all',
|
|
117
|
+
verdict: 'verify_next',
|
|
118
|
+
verified: false,
|
|
119
|
+
},
|
|
120
|
+
unknown: { status: 'unknown', verdict: 'hold', verified: false },
|
|
121
|
+
unavailable: { status: 'unknown', verdict: 'hold', verified: false },
|
|
122
|
+
do_not_mail: { status: 'do_not_mail', verdict: 'drop', verified: false },
|
|
123
|
+
spamtrap: { status: 'spamtrap', verdict: 'drop', verified: false },
|
|
124
|
+
abuse: { status: 'abuse', verdict: 'drop', verified: false },
|
|
125
|
+
disposable: { status: 'disposable', verdict: 'drop', verified: false },
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
function normalizeKey(value: unknown): string | null {
|
|
129
|
+
if (value == null) return null;
|
|
130
|
+
if (typeof value === 'boolean') return String(value);
|
|
131
|
+
const normalized = String(value).trim().toLowerCase().replace(/\s+/g, '_');
|
|
132
|
+
return normalized || null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function boolish(value: unknown): boolean | null {
|
|
136
|
+
if (typeof value === 'boolean') return value;
|
|
137
|
+
if (typeof value === 'number') return value === 1 ? true : value === 0 ? false : null;
|
|
138
|
+
if (typeof value !== 'string') return null;
|
|
139
|
+
const normalized = value.trim().toLowerCase();
|
|
140
|
+
if (['true', 'yes', 'y', '1'].includes(normalized)) return true;
|
|
141
|
+
if (['false', 'no', 'n', '0'].includes(normalized)) return false;
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function numberish(value: unknown): number | null {
|
|
146
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
147
|
+
if (typeof value !== 'string' || value.trim() === '') return null;
|
|
148
|
+
const parsed = Number(value);
|
|
149
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function stringish(value: unknown): string | null {
|
|
153
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function deliverability(value: unknown): EmailDeliverability {
|
|
157
|
+
const normalized = normalizeKey(value);
|
|
158
|
+
return normalized === 'high' || normalized === 'medium' || normalized === 'low'
|
|
159
|
+
? normalized
|
|
160
|
+
: 'unknown';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function mxClass(mxProvider: unknown, mxRecord: unknown): EmailMxClass {
|
|
164
|
+
const haystack = `${stringish(mxProvider) ?? ''} ${stringish(mxRecord) ?? ''}`.toLowerCase();
|
|
165
|
+
if (!haystack.trim()) return 'unknown';
|
|
166
|
+
if (
|
|
167
|
+
/proofpoint|pphosted|mimecast|barracuda|ess\.barracudanetworks|ironport|cisco|iphmx|messagelabs|symantec/.test(
|
|
168
|
+
haystack,
|
|
169
|
+
)
|
|
170
|
+
) {
|
|
171
|
+
return 'security_gateway';
|
|
172
|
+
}
|
|
173
|
+
if (/aspmx\.l\.google|google|g-suite|google workspace/.test(haystack)) {
|
|
174
|
+
return 'workspace_mailbox';
|
|
175
|
+
}
|
|
176
|
+
if (/protection\.outlook|office365|microsoft|outlook|exchange online/.test(haystack)) {
|
|
177
|
+
return 'workspace_mailbox';
|
|
178
|
+
}
|
|
179
|
+
if (/gmail|yahoo|icloud|aol|hotmail/.test(haystack)) return 'consumer_mailbox';
|
|
180
|
+
if (/postfix|exim|sendmail|zimbra|plesk|cpanel|mail\./.test(haystack)) return 'on_prem';
|
|
181
|
+
return 'unknown';
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function entryForStatus(
|
|
185
|
+
key: string | null,
|
|
186
|
+
map: Record<string, EmailStatusMapEntry> | undefined,
|
|
187
|
+
): EmailStatusMapEntry | null {
|
|
188
|
+
if (!key) return null;
|
|
189
|
+
return map?.[key] ?? DEFAULT_STATUS_MAP[key] ?? null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function read(values: Record<string, unknown>, name: string): unknown {
|
|
193
|
+
return values[name];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function matchesRule(
|
|
197
|
+
rule: EmailStatusRule,
|
|
198
|
+
values: Record<string, unknown>,
|
|
199
|
+
): boolean {
|
|
200
|
+
return Object.entries(rule.when).every(([key, expected]) => {
|
|
201
|
+
const actual = read(values, key);
|
|
202
|
+
if (key.endsWith('Lt')) {
|
|
203
|
+
const source = numberish(read(values, key.slice(0, -2)));
|
|
204
|
+
return typeof expected === 'number' && source != null && source < expected;
|
|
205
|
+
}
|
|
206
|
+
if (typeof expected === 'boolean') return boolish(actual) === expected;
|
|
207
|
+
if (typeof expected === 'number') return numberish(actual) === expected;
|
|
208
|
+
return normalizeKey(actual) === normalizeKey(expected);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function buildEmailStatus({
|
|
213
|
+
config,
|
|
214
|
+
values,
|
|
215
|
+
}: EmailStatusBuildInput): EmailStatus {
|
|
216
|
+
const rawStatus = read(values, 'rawStatus');
|
|
217
|
+
const rawScore = numberish(read(values, 'rawScore'));
|
|
218
|
+
const valid = boolish(read(values, 'valid'));
|
|
219
|
+
const catchAll = boolish(read(values, 'catchAll'));
|
|
220
|
+
const disposable = boolish(read(values, 'disposable'));
|
|
221
|
+
const abuse = boolish(read(values, 'abuse'));
|
|
222
|
+
const spamtrap = boolish(read(values, 'spamtrap'));
|
|
223
|
+
const suspect = boolish(read(values, 'suspect'));
|
|
224
|
+
const rawKey = normalizeKey(rawStatus);
|
|
225
|
+
const mapped =
|
|
226
|
+
config.rules?.find((rule) => matchesRule(rule, values)) ??
|
|
227
|
+
entryForStatus(rawKey, config.statusMap) ??
|
|
228
|
+
entryForStatus(valid == null ? null : String(valid), config.statusMap);
|
|
229
|
+
|
|
230
|
+
const status =
|
|
231
|
+
mapped?.status ??
|
|
232
|
+
(disposable
|
|
233
|
+
? 'disposable'
|
|
234
|
+
: abuse
|
|
235
|
+
? 'abuse'
|
|
236
|
+
: spamtrap
|
|
237
|
+
? 'spamtrap'
|
|
238
|
+
: catchAll
|
|
239
|
+
? 'catch_all'
|
|
240
|
+
: valid === true
|
|
241
|
+
? 'valid'
|
|
242
|
+
: valid === false
|
|
243
|
+
? 'invalid'
|
|
244
|
+
: 'unknown');
|
|
245
|
+
const defaultVerdict: EmailStatusVerdict =
|
|
246
|
+
status === 'valid'
|
|
247
|
+
? 'send'
|
|
248
|
+
: status === 'valid_catch_all'
|
|
249
|
+
? 'send_with_caution'
|
|
250
|
+
: status === 'catch_all'
|
|
251
|
+
? 'verify_next'
|
|
252
|
+
: status === 'unknown'
|
|
253
|
+
? 'hold'
|
|
254
|
+
: 'drop';
|
|
255
|
+
const verdict = mapped?.verdict ?? defaultVerdict;
|
|
256
|
+
const verified =
|
|
257
|
+
mapped?.verified ??
|
|
258
|
+
(status === 'valid' || status === 'valid_catch_all' || verdict === 'send');
|
|
259
|
+
const reasons = [
|
|
260
|
+
mapped?.reason,
|
|
261
|
+
catchAll ? 'catch_all_domain' : null,
|
|
262
|
+
mxClass(read(values, 'mxProvider'), read(values, 'mxRecord')) ===
|
|
263
|
+
'security_gateway'
|
|
264
|
+
? 'security_gateway_mx'
|
|
265
|
+
: null,
|
|
266
|
+
suspect ? 'provider_marked_suspect' : null,
|
|
267
|
+
].filter((reason): reason is string => typeof reason === 'string');
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
verdict,
|
|
271
|
+
status,
|
|
272
|
+
verified,
|
|
273
|
+
confidence: rawScore,
|
|
274
|
+
reasons,
|
|
275
|
+
signals: {
|
|
276
|
+
catch_all: catchAll,
|
|
277
|
+
deliverability: deliverability(read(values, 'deliverability')),
|
|
278
|
+
mx_class: mxClass(read(values, 'mxProvider'), read(values, 'mxRecord')),
|
|
279
|
+
mx_provider: stringish(read(values, 'mxProvider')),
|
|
280
|
+
mx_record: stringish(read(values, 'mxRecord')),
|
|
281
|
+
fraud_score: numberish(read(values, 'fraudScore')),
|
|
282
|
+
disposable,
|
|
283
|
+
role_based: boolish(read(values, 'roleBased')),
|
|
284
|
+
free_email: boolish(read(values, 'freeEmail')),
|
|
285
|
+
abuse,
|
|
286
|
+
spamtrap,
|
|
287
|
+
suspect,
|
|
288
|
+
valid,
|
|
289
|
+
},
|
|
290
|
+
provider: {
|
|
291
|
+
name: config.provider,
|
|
292
|
+
raw_status:
|
|
293
|
+
typeof rawStatus === 'string' ||
|
|
294
|
+
typeof rawStatus === 'boolean' ||
|
|
295
|
+
typeof rawStatus === 'number'
|
|
296
|
+
? rawStatus
|
|
297
|
+
: null,
|
|
298
|
+
raw_score: rawScore,
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
}
|
|
@@ -9,7 +9,15 @@ export type {
|
|
|
9
9
|
ToolResultTargetMetadata,
|
|
10
10
|
ToolResultTargetAccessor,
|
|
11
11
|
} from './tool-result-types';
|
|
12
|
-
|
|
12
|
+
export type {
|
|
13
|
+
EmailDeliverability,
|
|
14
|
+
EmailMxClass,
|
|
15
|
+
EmailStatus,
|
|
16
|
+
EmailStatusValue,
|
|
17
|
+
EmailStatusVerdict,
|
|
18
|
+
} from './email-status';
|
|
19
|
+
|
|
20
|
+
import { buildEmailStatus } from './email-status';
|
|
13
21
|
import type {
|
|
14
22
|
SerializedToolExecuteResult,
|
|
15
23
|
ToolExecuteResult,
|
|
@@ -302,6 +310,50 @@ function findFirstTargetByPath(
|
|
|
302
310
|
return null;
|
|
303
311
|
}
|
|
304
312
|
|
|
313
|
+
function firstValueForPaths(
|
|
314
|
+
result: unknown,
|
|
315
|
+
paths: readonly string[] | undefined,
|
|
316
|
+
): ToolResultTargetMetadata | null {
|
|
317
|
+
return findFirstTargetByPath(result, paths);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function buildEmailStatusTarget(
|
|
321
|
+
result: unknown,
|
|
322
|
+
descriptor: ToolResultExtractorDescriptor,
|
|
323
|
+
): ToolResultTargetMetadata | null {
|
|
324
|
+
const config = descriptor.emailStatus;
|
|
325
|
+
if (!config) return null;
|
|
326
|
+
const values: Record<string, unknown> = {};
|
|
327
|
+
const pathSets: Record<string, readonly string[] | undefined> = {
|
|
328
|
+
rawStatus: config.rawStatus,
|
|
329
|
+
rawScore: config.rawScore,
|
|
330
|
+
valid: config.valid,
|
|
331
|
+
deliverability: config.deliverability,
|
|
332
|
+
catchAll: config.catchAll,
|
|
333
|
+
mxProvider: config.mxProvider,
|
|
334
|
+
mxRecord: config.mxRecord,
|
|
335
|
+
fraudScore: config.fraudScore,
|
|
336
|
+
disposable: config.disposable,
|
|
337
|
+
roleBased: config.roleBased,
|
|
338
|
+
freeEmail: config.freeEmail,
|
|
339
|
+
abuse: config.abuse,
|
|
340
|
+
spamtrap: config.spamtrap,
|
|
341
|
+
suspect: config.suspect,
|
|
342
|
+
};
|
|
343
|
+
let firstPath: string | null = null;
|
|
344
|
+
for (const [name, paths] of Object.entries(pathSets)) {
|
|
345
|
+
const match = firstValueForPaths(result, paths);
|
|
346
|
+
if (!match) continue;
|
|
347
|
+
values[name] = match.value;
|
|
348
|
+
firstPath ??= match.path;
|
|
349
|
+
}
|
|
350
|
+
if (!firstPath) return null;
|
|
351
|
+
return {
|
|
352
|
+
path: firstPath,
|
|
353
|
+
value: buildEmailStatus({ config, values }),
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
305
357
|
function findFirstTargetByKey(
|
|
306
358
|
result: unknown,
|
|
307
359
|
target: string,
|
|
@@ -507,6 +559,11 @@ function buildTargets(
|
|
|
507
559
|
): Record<string, ToolResultTargetMetadata> {
|
|
508
560
|
const targets: Record<string, ToolResultTargetMetadata> = {};
|
|
509
561
|
for (const [target, descriptor] of Object.entries(extractors ?? {})) {
|
|
562
|
+
const emailStatusTarget = buildEmailStatusTarget(result, descriptor);
|
|
563
|
+
if (emailStatusTarget) {
|
|
564
|
+
targets[target] = emailStatusTarget;
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
510
567
|
const fromExtractor = findFirstTargetByPath(result, descriptor.paths);
|
|
511
568
|
if (!fromExtractor) continue;
|
|
512
569
|
const transformed = coerceToEnum(
|
|
@@ -71,7 +71,9 @@ export type PlayDatasetTransformOptions = {
|
|
|
71
71
|
* Deepline keeps row progress, retries, memory use, and table output under
|
|
72
72
|
* runtime control. Use `count()` and `peek()` for bounded inspection. Use
|
|
73
73
|
* `materialize(limit)` or async iteration only when the dataset is intentionally
|
|
74
|
-
* small and bounded.
|
|
74
|
+
* small and bounded. `PlayDataset` intentionally does not expose `.rows`,
|
|
75
|
+
* `.toArray()`, or other array aliases; those hide the runtime cost of loading
|
|
76
|
+
* persisted rows into memory.
|
|
75
77
|
*/
|
|
76
78
|
export interface PlayDataset<T> extends AsyncIterable<T> {
|
|
77
79
|
readonly [PLAY_DATASET_BRAND]: true;
|