deepline 0.2.70 → 0.2.72

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.
@@ -1156,16 +1156,16 @@ function isPrebuiltPlayDescription(
1156
1156
  function preferPrebuiltPlayDescriptions<T extends PlayDescription>(
1157
1157
  plays: T[],
1158
1158
  ): T[] {
1159
- const prebuilt: T[] = [];
1160
- const owned: T[] = [];
1161
- for (const play of plays) {
1162
- if (isPrebuiltPlayDescription(play)) {
1163
- prebuilt.push(play);
1164
- } else {
1165
- owned.push(play);
1166
- }
1167
- }
1168
- return [...prebuilt, ...owned];
1159
+ return plays
1160
+ .map((play, index) => ({ play, index }))
1161
+ .sort(
1162
+ (left, right) =>
1163
+ Number(right.play.pinned) - Number(left.play.pinned) ||
1164
+ Number(isPrebuiltPlayDescription(right.play)) -
1165
+ Number(isPrebuiltPlayDescription(left.play)) ||
1166
+ left.index - right.index,
1167
+ )
1168
+ .map(({ play }) => play);
1169
1169
  }
1170
1170
 
1171
1171
  function isPlayRunPackage(value: unknown): value is PlayRunPackage {
@@ -1775,6 +1775,8 @@ export class DeeplineClient {
1775
1775
  ...(play.reference ? { reference: play.reference } : {}),
1776
1776
  ...(play.displayName ? { displayName: play.displayName } : {}),
1777
1777
  ...(description ? { description } : {}),
1778
+ pinned: Boolean(play.pinned),
1779
+ toolCategories: play.toolCategories ?? [],
1778
1780
  origin: play.origin,
1779
1781
  ownerType: play.ownerType,
1780
1782
  canEdit: play.canEdit,
@@ -3778,9 +3780,24 @@ export class DeeplineClient {
3778
3780
  origin?: 'prebuilt' | 'owned';
3779
3781
  grep?: string;
3780
3782
  grepMode?: 'all' | 'any' | 'phrase';
3783
+ categories?: string | string[];
3784
+ includeToolCategories?: boolean;
3785
+ includeArchived?: boolean;
3781
3786
  }): Promise<PlayListItem[]> {
3782
3787
  const params = new URLSearchParams();
3783
3788
  if (options?.origin) params.set('origin', options.origin);
3789
+ if (options?.categories) {
3790
+ params.set(
3791
+ 'categories',
3792
+ Array.isArray(options.categories)
3793
+ ? options.categories.join(',')
3794
+ : options.categories,
3795
+ );
3796
+ }
3797
+ if (options?.categories || options?.includeToolCategories) {
3798
+ params.set('include_tool_categories', '1');
3799
+ }
3800
+ if (options?.includeArchived) params.set('include_archived', '1');
3784
3801
  if (options?.grep?.trim()) {
3785
3802
  params.set('grep', options.grep.trim());
3786
3803
  params.set('grep_mode', options.grepMode ?? 'all');
@@ -3793,6 +3810,16 @@ export class DeeplineClient {
3793
3810
  return response.plays ?? [];
3794
3811
  }
3795
3812
 
3813
+ /** Set whether an org-owned Play sorts before unpinned Plays. */
3814
+ async setPlayPinned(
3815
+ playName: string,
3816
+ pinned: boolean,
3817
+ ): Promise<{ name: string; pinned: boolean }> {
3818
+ return this.http.post(`/api/v2/plays/${encodeURIComponent(playName)}/pin`, {
3819
+ pinned,
3820
+ });
3821
+ }
3822
+
3796
3823
  /** Read product-notification destinations, subscriptions, event catalog, and DLQ health. */
3797
3824
  async getNotificationSettings(): Promise<ProductNotificationSettings> {
3798
3825
  return this.http.get('/api/v2/settings/notifications');
@@ -3981,9 +4008,15 @@ export class DeeplineClient {
3981
4008
  * console.log(`Total runs: ${detail.play.runCount}`);
3982
4009
  * ```
3983
4010
  */
3984
- async getPlay(name: string): Promise<PlayDetail> {
4011
+ async getPlay(
4012
+ name: string,
4013
+ options?: { source?: 'working' | 'live' | `version:${number}` },
4014
+ ): Promise<PlayDetail> {
3985
4015
  const encodedName = encodeURIComponent(name);
3986
- return this.http.get<PlayDetail>(`/api/v2/plays/${encodedName}`);
4016
+ const query = options?.source
4017
+ ? `?include=source&revision=${encodeURIComponent(options.source)}`
4018
+ : '';
4019
+ return this.http.get<PlayDetail>(`/api/v2/plays/${encodedName}${query}`);
3987
4020
  }
3988
4021
 
3989
4022
  /**
@@ -183,7 +183,7 @@ export const SDK_RELEASE = {
183
183
  // 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
184
184
  // exposed storage-dependent synchronous access. This deliberate minor
185
185
  // release keeps lazy paging semantics independent of row residency.
186
- version: '0.2.70',
186
+ version: '0.2.72',
187
187
  contracts: {
188
188
  api: {
189
189
  name: 'sdk-http-api',
@@ -1010,6 +1010,10 @@ export interface PlayDefinitionDetail {
1010
1010
  name: string;
1011
1011
  /** Human-friendly display name for UI surfaces. */
1012
1012
  displayName?: string;
1013
+ /** Whether this Play sorts before unpinned Plays. */
1014
+ pinned?: boolean;
1015
+ /** Canonical categories declared by tools used in this Play. */
1016
+ toolCategories?: string[];
1013
1017
  /** Whether this entry comes from the Deepline prebuilt registry or the org-owned catalog. */
1014
1018
  origin?: 'prebuilt' | 'owned';
1015
1019
  /** Ownership class used for permissions and badges. */
@@ -1064,6 +1068,8 @@ export interface PlayListItem {
1064
1068
  name: string;
1065
1069
  displayName?: string;
1066
1070
  description?: string | null;
1071
+ pinned?: boolean;
1072
+ toolCategories?: string[];
1067
1073
  origin?: 'prebuilt' | 'owned';
1068
1074
  ownerType?: 'deepline' | 'org';
1069
1075
  ownerSlug?: string;
@@ -1135,6 +1141,8 @@ export interface PlayDescription {
1135
1141
  reference?: string;
1136
1142
  displayName?: string;
1137
1143
  description?: string | null;
1144
+ pinned?: boolean;
1145
+ toolCategories?: string[];
1138
1146
  origin?: 'prebuilt' | 'owned';
1139
1147
  ownerType?: 'deepline' | 'org';
1140
1148
  canEdit?: boolean;
@@ -1192,6 +1200,16 @@ export interface PlayDetail {
1192
1200
  customerDbUrl: string;
1193
1201
  /** Change cursor for incremental sync. */
1194
1202
  deltaCursor: number;
1203
+ /**
1204
+ * Present only when requested through `getPlay(..., { source: ... })`.
1205
+ * `files` is the complete authored source tree, keyed by logical path.
1206
+ */
1207
+ source?: {
1208
+ selector: 'working' | 'live' | `version:${number}`;
1209
+ revision: { id: string; version: number };
1210
+ entryFile: string;
1211
+ files: Record<string, string>;
1212
+ };
1195
1213
  }
1196
1214
 
1197
1215
  export interface ClearPlayHistoryRequest {
@@ -61,16 +61,60 @@ const liveToolResultListDatasets = new Map<
61
61
  PlayDataset<Record<string, unknown>>
62
62
  >();
63
63
 
64
- const TARGET_FALLBACK_KEYS: Record<string, readonly RegExp[]> = {
65
- email: [/^email$/i, /^address$/i, /email/i],
66
- phone: [/^phone$/i, /mobile/i, /phone/i, /telephone/i],
67
- linkedin: [/^linkedin_url$/i, /^linkedin$/i, /linkedin/i],
68
- company_linkedin_url: [/company.*linkedin/i, /linkedin.*company/i],
69
- company_domain: [/^company_domain$/i, /company.*domain/i],
70
- company_name: [/^company_name$/i, /company.*name/i],
71
- domain: [/^domain$/i, /company_domain/i, /domain/i],
72
- status: [/^email_status$/i, /^status$/i],
73
- email_status: [/^email_status$/i, /^status$/i],
64
+ /**
65
+ * Keys that describe a field rather than carry it.
66
+ *
67
+ * These sit next to the real value under a similar name, so a substring match
68
+ * on the target name reaches them as soon as the real field is absent.
69
+ * `phone_status: "unfound"` is not a phone number and `email_type: "work"` is
70
+ * not an email address.
71
+ */
72
+ const DESCRIPTOR_SUFFIX =
73
+ /_(status|type|score|count|id|verified|valid|confidence|quality|source)$/i;
74
+
75
+ /** A company-scoped key is never the person-scoped answer. */
76
+ const COMPANY_SCOPED = /company/i;
77
+
78
+ type TargetFallbackRule = {
79
+ /** Key names that may carry this target's value. */
80
+ match: readonly RegExp[];
81
+ /** Key names that must never be treated as this target's value. */
82
+ reject?: readonly RegExp[];
83
+ };
84
+
85
+ const TARGET_FALLBACK_KEYS: Record<string, TargetFallbackRule> = {
86
+ email: {
87
+ match: [/^email$/i, /^address$/i, /email/i],
88
+ reject: [DESCRIPTOR_SUFFIX],
89
+ },
90
+ phone: {
91
+ match: [/^phone$/i, /mobile/i, /phone/i, /telephone/i],
92
+ reject: [DESCRIPTOR_SUFFIX],
93
+ },
94
+ linkedin: {
95
+ match: [/^linkedin_url$/i, /^linkedin$/i, /linkedin/i],
96
+ reject: [DESCRIPTOR_SUFFIX, COMPANY_SCOPED],
97
+ },
98
+ company_linkedin_url: {
99
+ match: [/company.*linkedin/i, /linkedin.*company/i],
100
+ reject: [DESCRIPTOR_SUFFIX],
101
+ },
102
+ company_domain: {
103
+ match: [/^company_domain$/i, /company.*domain/i],
104
+ reject: [DESCRIPTOR_SUFFIX],
105
+ },
106
+ company_name: {
107
+ match: [/^company_name$/i, /company.*name/i],
108
+ reject: [DESCRIPTOR_SUFFIX],
109
+ },
110
+ domain: {
111
+ match: [/^domain$/i, /company_domain/i, /domain/i],
112
+ reject: [DESCRIPTOR_SUFFIX],
113
+ },
114
+ // Status targets legitimately want the `_status` keys the suffix rule
115
+ // rejects elsewhere, so they carry no rejections.
116
+ status: { match: [/^email_status$/i, /^status$/i] },
117
+ email_status: { match: [/^email_status$/i, /^status$/i] },
74
118
  };
75
119
 
76
120
  function isRecord(value: unknown): value is Record<string, unknown> {
@@ -586,12 +630,13 @@ function findFirstTargetByKey(
586
630
  }
587
631
  if (!isRecord(result)) return null;
588
632
 
589
- const patterns = TARGET_FALLBACK_KEYS[target] ?? [
590
- new RegExp(`^${target}$`, 'i'),
591
- ];
633
+ const rule: TargetFallbackRule = TARGET_FALLBACK_KEYS[target] ?? {
634
+ match: [new RegExp(`^${target}$`, 'i')],
635
+ };
592
636
  for (const [key, value] of Object.entries(result)) {
637
+ if (rule.reject?.some((pattern) => pattern.test(key))) continue;
593
638
  if (
594
- patterns.some((pattern) => pattern.test(key)) &&
639
+ rule.match.some((pattern) => pattern.test(key)) &&
595
640
  isMeaningfulValue(value)
596
641
  ) {
597
642
  return { value, path: pathToString([...path, key]) };
@@ -896,6 +941,14 @@ function buildTargets(
896
941
  targets[target] = fromMetadata;
897
942
  continue;
898
943
  }
944
+ // Declared paths are routinely incomplete against the shape a provider
945
+ // actually returns (zerobounce declares `result.data.email` and answers
946
+ // with `address`), so the key scan stays as the rescue. It is bounded by
947
+ // each target's `reject` rules: the scan runs precisely when the provider
948
+ // returned no value, which is when a same-named neighbour is most likely
949
+ // to be a different field. Without those rules a real Wiza miss resolved
950
+ // `phone` to `phone_status: "unfound"` and `linkedin` to
951
+ // `company_linkedin`, reporting a company page as the person's profile.
899
952
  const fallback = findFirstTargetByKey(result, target);
900
953
  if (fallback) {
901
954
  targets[target] = fallback;
@@ -38,7 +38,7 @@ export const PLAY_AUTHORING_CONTRACT_CHANGELOG = [
38
38
  changed:
39
39
  'Materializes the declared input schema in the admitted snapshot so launch never reparses current source.',
40
40
  compatibilityOwner: 'Plays Runtime',
41
- newWritesEnd: null,
41
+ newWritesEnd: '2026-08-19',
42
42
  readerRemoval: null,
43
43
  },
44
44
  ] as const;
@@ -137,6 +137,18 @@ export type PlayBundlingAdapter = {
137
137
  warnAboutNonDevelopmentBundling?(filePath: string): void;
138
138
  };
139
139
 
140
+ type PathRelationshipApi = {
141
+ relative(from: string, to: string): string;
142
+ isAbsolute(path: string): boolean;
143
+ sep: string;
144
+ };
145
+
146
+ const defaultPathRelationshipApi: PathRelationshipApi = {
147
+ relative,
148
+ isAbsolute,
149
+ sep,
150
+ };
151
+
140
152
  function assertValidExportName(exportName: string): void {
141
153
  if (exportName === 'default') return;
142
154
  if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(exportName)) {
@@ -207,10 +219,10 @@ function sha256(value: string): string {
207
219
 
208
220
  function sourceIdentityPath(
209
221
  filePath: string,
210
- adapter: PlayBundlingAdapter,
222
+ sourceIdentityRoot: string | undefined,
211
223
  ): string {
212
- if (!adapter.sourceIdentityRoot) return filePath;
213
- const identityRoot = resolve(adapter.sourceIdentityRoot);
224
+ if (!sourceIdentityRoot) return filePath;
225
+ const identityRoot = resolve(sourceIdentityRoot);
214
226
  const logicalPath = relative(identityRoot, resolve(filePath));
215
227
  if (
216
228
  !logicalPath ||
@@ -254,8 +266,18 @@ function createPlayWorkspace(entryFile: string): PlayWorkspace {
254
266
  };
255
267
  }
256
268
 
257
- function isPathInsideDirectory(filePath: string, directory: string): boolean {
258
- return filePath === directory || filePath.startsWith(`${directory}/`);
269
+ export function isPathInsideDirectory(
270
+ filePath: string,
271
+ directory: string,
272
+ pathApi: PathRelationshipApi = defaultPathRelationshipApi,
273
+ ): boolean {
274
+ const relationship = pathApi.relative(directory, filePath);
275
+ return (
276
+ relationship === '' ||
277
+ (relationship !== '..' &&
278
+ !relationship.startsWith(`..${pathApi.sep}`) &&
279
+ !pathApi.isAbsolute(relationship))
280
+ );
259
281
  }
260
282
 
261
283
  function assertWithinPlayWorkspace(input: {
@@ -1851,6 +1873,7 @@ async function analyzeSourceGraph(
1851
1873
  ): Promise<SourceGraphAnalysis> {
1852
1874
  const absoluteEntryFile = await normalizeLocalPath(entryFile);
1853
1875
  const workspace = createPlayWorkspace(absoluteEntryFile);
1876
+ const sourceIdentityRoot = adapter.sourceIdentityRoot;
1854
1877
  const localFiles = new Map<string, string>();
1855
1878
  const nodeBuiltins = new Set<string>();
1856
1879
  const packages = new Map<string, string | null>();
@@ -1961,10 +1984,10 @@ async function analyzeSourceGraph(
1961
1984
  const sourceHash = sha256(sourceCode);
1962
1985
  const graphHash = sha256(
1963
1986
  JSON.stringify({
1964
- entryFile: sourceIdentityPath(absoluteEntryFile, adapter),
1987
+ entryFile: sourceIdentityPath(absoluteEntryFile, sourceIdentityRoot),
1965
1988
  localFiles: [...localFiles.entries()]
1966
1989
  .map(([filePath, contents]) => ({
1967
- filePath: sourceIdentityPath(filePath, adapter),
1990
+ filePath: sourceIdentityPath(filePath, sourceIdentityRoot),
1968
1991
  hash: sha256(contents),
1969
1992
  }))
1970
1993
  .sort((left, right) => left.filePath.localeCompare(right.filePath)),
@@ -1974,7 +1997,7 @@ async function analyzeSourceGraph(
1974
1997
  .sort((left, right) => left.name.localeCompare(right.name)),
1975
1998
  importedPlayDependencies: [...importedPlayDependencies.values()]
1976
1999
  .map((dependency) => ({
1977
- filePath: sourceIdentityPath(dependency.filePath, adapter),
2000
+ filePath: sourceIdentityPath(dependency.filePath, sourceIdentityRoot),
1978
2001
  playName: dependency.playName,
1979
2002
  }))
1980
2003
  .sort((left, right) => left.filePath.localeCompare(right.filePath)),
@@ -2199,6 +2222,11 @@ export async function bundlePlayFile(
2199
2222
  const exportName = options.exportName?.trim() || 'default';
2200
2223
  assertValidExportName(exportName);
2201
2224
  const absolutePath = await normalizeLocalPath(filePath);
2225
+ // Keep the existing absolute-path wire format unless a caller explicitly
2226
+ // opts into a logical identity root. Production's deployed checker still
2227
+ // uses absolute paths to reconcile bundled imports; source export normalizes
2228
+ // those paths into a portable tree at the API boundary.
2229
+ const sourceIdentityRoot = adapter.sourceIdentityRoot;
2202
2230
  adapter.warnAboutNonDevelopmentBundling?.(absolutePath);
2203
2231
 
2204
2232
  try {
@@ -2210,6 +2238,19 @@ export async function bundlePlayFile(
2210
2238
  analysis.graphHash = sha256(
2211
2239
  `${analysis.graphHash}\nentry-export:${exportName}\nauthoring-contract-edition:${PLAY_AUTHORING_CONTRACT_EDITION}`,
2212
2240
  );
2241
+ const sourceFiles = Object.fromEntries(
2242
+ Object.entries(analysis.sourceFiles).map(([sourcePath, sourceCode]) => [
2243
+ sourceIdentityPath(sourcePath, sourceIdentityRoot),
2244
+ sourceCode,
2245
+ ]),
2246
+ );
2247
+ const importPolicy: PlayImportPolicy = {
2248
+ ...analysis.importPolicy,
2249
+ localFiles: analysis.importPolicy.localFiles.map((sourcePath) =>
2250
+ sourceIdentityPath(sourcePath, sourceIdentityRoot),
2251
+ ),
2252
+ };
2253
+ const entryFile = sourceIdentityPath(absolutePath, sourceIdentityRoot);
2213
2254
  try {
2214
2255
  validatePlaySourceFilesHaveNoInlineSecrets(analysis.sourceFiles);
2215
2256
  } catch (error) {
@@ -2267,9 +2308,9 @@ export async function bundlePlayFile(
2267
2308
  success: true,
2268
2309
  artifact: {
2269
2310
  ...cachedArtifact,
2270
- entryFile: absolutePath,
2311
+ entryFile,
2271
2312
  sourceHash: analysis.sourceHash,
2272
- importPolicy: analysis.importPolicy,
2313
+ importPolicy,
2273
2314
  compatibility: buildPlayContractCompatibility({
2274
2315
  toolErrorSchemaVersion:
2275
2316
  analysis.toolErrorSchemaVersion ?? undefined,
@@ -2277,7 +2318,7 @@ export async function bundlePlayFile(
2277
2318
  cacheHit: true,
2278
2319
  },
2279
2320
  sourceCode: analysis.sourceCode,
2280
- sourceFiles: analysis.sourceFiles,
2321
+ sourceFiles,
2281
2322
  filePath: absolutePath,
2282
2323
  playName: analysis.playName,
2283
2324
  playDescription: analysis.playDescription,
@@ -2330,7 +2371,7 @@ export async function bundlePlayFile(
2330
2371
  const artifact: PlayBundleArtifact = {
2331
2372
  codeFormat: 'cjs_module',
2332
2373
  artifactKind: target,
2333
- entryFile: absolutePath,
2374
+ entryFile,
2334
2375
  virtualFilename,
2335
2376
  sourceHash: analysis.sourceHash,
2336
2377
  graphHash: analysis.graphHash,
@@ -2338,7 +2379,7 @@ export async function bundlePlayFile(
2338
2379
  sourceMapHash: sha256(normalizedSourceMap),
2339
2380
  bundledCode: executableCode,
2340
2381
  sourceMap: normalizedSourceMap,
2341
- importPolicy: analysis.importPolicy,
2382
+ importPolicy,
2342
2383
  compatibility: buildPlayContractCompatibility({
2343
2384
  toolErrorSchemaVersion: analysis.toolErrorSchemaVersion ?? undefined,
2344
2385
  }),
@@ -2352,7 +2393,7 @@ export async function bundlePlayFile(
2352
2393
  success: true,
2353
2394
  artifact,
2354
2395
  sourceCode: analysis.sourceCode,
2355
- sourceFiles: analysis.sourceFiles,
2396
+ sourceFiles,
2356
2397
  filePath: absolutePath,
2357
2398
  playName: analysis.playName,
2358
2399
  playDescription: analysis.playDescription,
@@ -691,10 +691,20 @@ export function compileEnrichConfigToPlaySource(
691
691
  const runOptionsSource = options.failFast
692
692
  ? `{ key: (row, index) => __dlEnrichRowKey(row, index + rowStart), onRowError: 'fail' as const }`
693
693
  : `{ key: (row, index) => __dlEnrichRowKey(row, index + rowStart) }`;
694
+ const describedColumns = config.commands
695
+ .filter((command) => isWaterfall(command) || !command.disabled)
696
+ .map((command) =>
697
+ (isWaterfall(command) ? command.with_waterfall : command.alias)
698
+ .replace(/[_-]+/g, ' ')
699
+ .trim(),
700
+ )
701
+ .filter(Boolean)
702
+ .slice(0, 3);
703
+ const generatedDescription = describedColumns.length
704
+ ? `Enrich CSV rows with ${describedColumns.join(', ')}.`
705
+ : 'Prepare CSV rows for enrichment.';
694
706
  const playOptionsSource = [
695
- `description: ${stringLiteral(
696
- 'Read a CSV file, run the configured Deepline enrich commands, and return enriched rows.',
697
- )}`,
707
+ `description: ${stringLiteral(generatedDescription)}`,
698
708
  ...(options.maxCreditsPerRun === undefined
699
709
  ? []
700
710
  : [`billing: { maxCreditsPerRun: ${String(options.maxCreditsPerRun)} }`]),
@@ -49,3 +49,15 @@ export const TOOL_CATEGORY_DESCRIPTIONS: Readonly<Record<string, string>> = {
49
49
  export function describeToolCategory(category: string): string | null {
50
50
  return TOOL_CATEGORY_DESCRIPTIONS[category] ?? null;
51
51
  }
52
+
53
+ /** Human-readable label for a canonical tool-category slug. */
54
+ export function formatToolCategoryLabel(category: string): string {
55
+ const normalized = category
56
+ .trim()
57
+ .replace(/[_-]+/g, ' ')
58
+ .replace(/\s+/g, ' ')
59
+ .toLowerCase();
60
+ return normalized
61
+ ? `${normalized[0]?.toUpperCase() ?? ''}${normalized.slice(1)}`
62
+ : '';
63
+ }