deepline 0.1.54 → 0.1.56

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.
@@ -1,5 +1,7 @@
1
1
  import {
2
2
  applyCsvRenameProjection,
3
+ stripCsvProjectedFields,
4
+ stripCsvProjectionMetadata,
3
5
  type CsvRenameOptions,
4
6
  } from '../../../../shared_libs/play-runtime/csv-rename';
5
7
  import {
@@ -38,7 +40,32 @@ const datasetCountHints = new WeakMap<object, number | null>();
38
40
  const datasetCapabilities = new WeakMap<object, WorkerDatasetCapabilities>();
39
41
 
40
42
  function cloneRow<T extends DatasetRow>(row: T): T {
41
- return { ...row };
43
+ const cloned: DatasetRow = {};
44
+ for (const key of Reflect.ownKeys(row)) {
45
+ const descriptor = Object.getOwnPropertyDescriptor(row, key);
46
+ if (!descriptor) continue;
47
+ Object.defineProperty(cloned, key, descriptor);
48
+ }
49
+ return cloned as T;
50
+ }
51
+
52
+ function internalDatasetRow<T extends DatasetRow>(row: T): T {
53
+ const stripped = stripCsvProjectionMetadata(row) as DatasetRow;
54
+ const publicRow: DatasetRow = {};
55
+ for (const key of Reflect.ownKeys(stripped)) {
56
+ if (typeof key === 'string' && key.startsWith('__deepline')) continue;
57
+ const descriptor = Object.getOwnPropertyDescriptor(stripped, key);
58
+ if (!descriptor) continue;
59
+ Object.defineProperty(publicRow, key, descriptor);
60
+ }
61
+ return publicRow as T;
62
+ }
63
+
64
+ function materializedDatasetRow<T extends DatasetRow>(row: T): T {
65
+ const stripped = stripCsvProjectedFields(row) as DatasetRow;
66
+ return Object.fromEntries(
67
+ Object.entries(stripped).filter(([key]) => !key.startsWith('__deepline')),
68
+ ) as T;
42
69
  }
43
70
 
44
71
  function registerChunkReader<T extends DatasetRow>(
@@ -138,10 +165,10 @@ export function createPersistedDatasetHandle<T extends DatasetRow>(input: {
138
165
  const count = Math.max(0, Math.floor(input.count));
139
166
  const previewRows = (input.previewRows ?? [])
140
167
  .slice(0, WORKER_DATASET_PREVIEW_ROWS)
141
- .map(cloneRow);
168
+ .map(materializedDatasetRow);
142
169
  const cachedRows =
143
170
  input.cachedRows && input.cachedRows.length <= WORKER_DATASET_IN_MEMORY_ROWS
144
- ? input.cachedRows.map(cloneRow)
171
+ ? input.cachedRows.map(internalDatasetRow)
145
172
  : null;
146
173
 
147
174
  async function loadRows(limit: number, offset: number): Promise<T[]> {
@@ -157,7 +184,7 @@ export function createPersistedDatasetHandle<T extends DatasetRow>(input: {
157
184
  ) {
158
185
  return cachedRows
159
186
  .slice(normalizedOffset, normalizedOffset + normalizedLimit)
160
- .map(cloneRow);
187
+ .map(internalDatasetRow);
161
188
  }
162
189
  const startedAt = input.nowMs();
163
190
  const rows = await input.readRows({
@@ -172,7 +199,7 @@ export function createPersistedDatasetHandle<T extends DatasetRow>(input: {
172
199
  offset: normalizedOffset,
173
200
  rows: rows.length,
174
201
  });
175
- return rows.map(cloneRow);
202
+ return rows.map(internalDatasetRow);
176
203
  }
177
204
 
178
205
  async function* readChunks(chunkSize: number): AsyncGenerator<T[], void, void> {
@@ -205,14 +232,15 @@ export function createPersistedDatasetHandle<T extends DatasetRow>(input: {
205
232
  workProgress: input.workProgress,
206
233
  resolvers: {
207
234
  count: async () => count,
208
- peek: async (limit) => await loadRows(Math.max(0, limit), 0),
235
+ peek: async (limit) =>
236
+ (await loadRows(Math.max(0, limit), 0)).map(materializedDatasetRow),
209
237
  materialize: async (limit) => {
210
238
  const rows: T[] = [];
211
239
  const maxRows = limit ?? count;
212
240
  for await (const chunk of readChunks(STREAM_MATERIALIZE_CHUNK_ROWS)) {
213
241
  for (const row of chunk) {
214
242
  if (rows.length >= maxRows) return rows;
215
- rows.push(row);
243
+ rows.push(materializedDatasetRow(row));
216
244
  }
217
245
  }
218
246
  return rows;
@@ -485,6 +485,37 @@ export class DeeplineClient {
485
485
  return `deepline plays run ${target} --input '{...}' --watch`;
486
486
  }
487
487
 
488
+ private starterPlayPath(play: Pick<PlayListItem, 'name' | 'reference'>): string {
489
+ const target = play.reference || play.name;
490
+ const unqualifiedName = target.split('/').pop() || play.name;
491
+ const safeName = unqualifiedName
492
+ .trim()
493
+ .toLowerCase()
494
+ .replace(/[^a-z0-9-]/g, '-')
495
+ .replace(/-+/g, '-')
496
+ .replace(/^-|-$/g, '');
497
+ return `./${safeName || 'play'}.play.ts`;
498
+ }
499
+
500
+ private playCloneEditStarter(
501
+ play: Pick<
502
+ PlayListItem,
503
+ 'name' | 'reference' | 'canClone' | 'canEdit' | 'origin' | 'ownerType'
504
+ >,
505
+ ): PlayDescription['cloneEditStarter'] | undefined {
506
+ const readonlyPrebuilt =
507
+ (play.origin === 'prebuilt' || play.ownerType === 'deepline') &&
508
+ !play.canEdit;
509
+ if (!play.canClone && !readonlyPrebuilt) return undefined;
510
+ const target = play.reference || play.name;
511
+ const path = this.starterPlayPath(play);
512
+ return {
513
+ path,
514
+ command: `deepline plays get ${target} --source --out ${path}`,
515
+ checkCommand: `deepline plays check ${path}`,
516
+ };
517
+ }
518
+
488
519
  private summarizePlayListItem(
489
520
  play: PlayListItem,
490
521
  options?: { compact?: boolean },
@@ -496,6 +527,7 @@ export class DeeplineClient {
496
527
  'rowOutputSchema',
497
528
  );
498
529
  const runCommand = this.playRunCommand(play, { csvInput });
530
+ const cloneEditStarter = this.playCloneEditStarter(play);
499
531
  return {
500
532
  name: play.name,
501
533
  ...(play.reference ? { reference: play.reference } : {}),
@@ -515,6 +547,7 @@ export class DeeplineClient {
515
547
  ...(rowOutputSchema ? { rowOutputSchema } : {}),
516
548
  runCommand,
517
549
  examples: [runCommand],
550
+ ...(cloneEditStarter ? { cloneEditStarter } : {}),
518
551
  currentPublishedVersion: play.currentPublishedVersion ?? null,
519
552
  isDraftDirty: play.isDraftDirty,
520
553
  };
@@ -1472,12 +1505,10 @@ export class DeeplineClient {
1472
1505
 
1473
1506
  async searchPlays(options: {
1474
1507
  query: string;
1475
- origin?: 'prebuilt' | 'owned';
1476
1508
  compact?: boolean;
1477
1509
  }): Promise<PlayDescription[]> {
1478
1510
  const params = new URLSearchParams();
1479
1511
  params.set('search', options.query.trim());
1480
- if (options.origin) params.set('origin', options.origin);
1481
1512
  const response = await this.http.get<{ plays: PlayListItem[] }>(
1482
1513
  `/api/v2/plays?${params.toString()}`,
1483
1514
  );
@@ -161,10 +161,20 @@ export type {
161
161
  ToolResultTargetAccessor as ToolExtractedValue,
162
162
  } from '../../shared_libs/play-runtime/tool-result-types.js';
163
163
 
164
+ /**
165
+ * Keyword-style request object for `ctx.tools.execute(...)`.
166
+ *
167
+ * The `tool` value comes from live tool discovery. The `id` is the stable
168
+ * logical call name inside this play and participates in replay/idempotency.
169
+ */
164
170
  export type ToolExecutionRequest = {
171
+ /** Stable logical id for this tool call within the play. */
165
172
  id: string;
173
+ /** Current tool id from `deepline tools search` / `deepline tools describe`. */
166
174
  tool: string;
175
+ /** JSON-serializable provider/tool input object. */
167
176
  input: Record<string, unknown>;
177
+ /** Human-readable description for logs and run inspection. */
168
178
  description?: string;
169
179
  staleAfterSeconds?: number;
170
180
  };
@@ -226,10 +236,32 @@ export type MapStepBuilder<
226
236
  InputRow extends object,
227
237
  OutputRow extends object,
228
238
  > = {
239
+ /**
240
+ * Define one output column for every row in this map dataset.
241
+ *
242
+ * The `name` becomes a field on each output row. For example,
243
+ * `.step('contact', ...)` creates `row.contact` in later map stages; it does
244
+ * not spread returned object fields such as `contact.email` into `row.email`.
245
+ * Add a later column resolver when you want a top-level export field:
246
+ * `.step('email', row => row.contact?.email ?? null)`.
247
+ *
248
+ * @param name - Output column name.
249
+ * @param resolver - Computes the value for one row.
250
+ * @returns The same map builder with the new column type.
251
+ */
229
252
  step<Name extends string, Value>(
230
253
  name: Name,
231
254
  resolver: MapStepResolver<OutputRow, Value>,
232
255
  ): MapStepBuilder<InputRow, OutputRow & Record<Name, Value>>;
256
+ /**
257
+ * Execute the row-column program and return a durable dataset handle.
258
+ *
259
+ * The returned {@link PlayDataset} preserves one output row per input row,
260
+ * with original fields merged with the columns produced by `.step(...)`.
261
+ *
262
+ * @param options - Run options.
263
+ * @returns Output rows as a dataset handle.
264
+ */
233
265
  run(options?: {
234
266
  description?: string;
235
267
  staleAfterSeconds?: number;
@@ -270,10 +302,15 @@ export type ColumnMap<TRow extends object> = Partial<
270
302
  Record<Extract<keyof TRow, string>, string | readonly string[]>
271
303
  >;
272
304
 
305
+ /** Options for loading a staged CSV with `ctx.csv(...)`. */
273
306
  export type CsvOptions = {
307
+ /** Human-readable description for runtime logs and inspection. */
274
308
  description?: string;
309
+ /** Canonical field-to-header aliases, e.g. `{ domain: ['domain', 'Company Domain'] }`. */
275
310
  columns?: CsvRenameMap;
311
+ /** Header rename map; use `columns` for new code. */
276
312
  rename?: CsvRenameMap;
313
+ /** Canonical fields that must be present after header normalization. */
277
314
  required?: readonly string[];
278
315
  };
279
316
 
@@ -324,17 +361,22 @@ export type CsvOptions = {
324
361
  */
325
362
  export interface DeeplinePlayRuntimeContext {
326
363
  /**
327
- * Load a CSV file as a dataset handle.
364
+ * Load a staged CSV file as a durable dataset handle.
328
365
  *
329
- * The CSV must be staged or available at the given path. Each row becomes
330
- * an object keyed by column headers.
366
+ * Use this when a play receives a CSV path from the CLI or API and row work
367
+ * should continue through {@link DeeplinePlayRuntimeContext.map}. The path is
368
+ * normally an input field such as `input.csv`, populated by
369
+ * `deepline plays run my.play.ts --csv rows.csv`.
370
+ *
371
+ * Each CSV row becomes an object keyed by canonical column names. Use
372
+ * `options.columns` / `options.rename` to map user headers such as
373
+ * `"Company Domain"` to stable code fields such as `domain`.
331
374
  *
332
375
  * @typeParam T - Row type (defaults to `Record<string, unknown>`)
333
- * @param path - Relative path to the CSV file
334
- * The returned dataset supports `for await`, `peek()`, `count()`, and
335
- * explicit `materialize()` for small result sets.
376
+ * @param path - Staged CSV path.
377
+ * @param options - CSV load options.
336
378
  *
337
- * @returns Parsed dataset rows
379
+ * @returns A {@link PlayDataset} whose rows should usually flow directly into `ctx.map(...)`.
338
380
  */
339
381
  csv<T = Record<string, unknown>>(
340
382
  path: string,
@@ -342,27 +384,28 @@ export interface DeeplinePlayRuntimeContext {
342
384
  ): Promise<PlayDataset<T>>;
343
385
 
344
386
  /**
345
- * Fan-out: process each item through one or more named columns.
387
+ * Create a persisted row dataset/table from input rows.
346
388
  *
347
- * Each key in `columns` becomes an output column. Each value is an async
348
- * callback `(row, ctx) => result` that receives the current row and the
349
- * play context call tools, run waterfalls, do arbitrary logic.
389
+ * `ctx.map` is Deepline's row-work primitive. It records row identity,
390
+ * progress, retries, table output, and idempotency for a collection of rows.
391
+ * Use `.step(name, resolver)` on the returned builder to define output
392
+ * columns, then `.run(...)` to execute the row program.
350
393
  *
351
- * Items are processed in parallel (paced by the rate-limit scheduler).
352
- * `key` identifies the logical output dataset/table. Row identity is derived
353
- * automatically from item content unless advanced run options override it.
354
- * `options.staleAfterSeconds` intentionally partitions the durable cache on a
355
- * relative time window. Use `86400` for daily reruns; retries inside the same
356
- * window still replay safely.
394
+ * The `key` identifies the logical dataset/table. Renaming it is a persistence
395
+ * migration: existing rows may no longer be reused. Row identity is derived
396
+ * automatically from input row content unless `.run({ key: ... })` overrides
397
+ * it with stable business fields such as `domain`, `email`, or `linkedin_url`.
357
398
  *
358
- * Returns a dataset handle containing the original rows merged with the new
359
- * columns. Input may be a normal array, iterable, async iterable, or another
360
- * play dataset handle.
399
+ * By default, `ctx.map` is row-preserving: one input row produces one output
400
+ * row, with original fields merged with the columns produced by
401
+ * `.step(...)`. If one input entity must become many output rows, use the
402
+ * documented expand/flatten recipe instead of assuming `ctx.map` changes
403
+ * row cardinality.
361
404
  *
362
405
  * @typeParam T - Row type
363
- * @param key - Logical output dataset/table name (e.g. `'companies'`, `'email_lookup'`)
364
- * @param items - Input rows or dataset handle
365
- * @returns Dataset of rows merged with the computed column values
406
+ * @param key - Dataset/table name.
407
+ * @param items - Input rows.
408
+ * @returns A builder. Calling `.run()` returns a `PlayDataset` of rows plus computed columns.
366
409
  *
367
410
  * @example Single tool per row
368
411
  * ```typescript
@@ -405,25 +448,78 @@ export interface DeeplinePlayRuntimeContext {
405
448
  /**
406
449
  * Execute a single tool with a keyword-style request object.
407
450
  *
408
- * @param request.id - Stable step key for idempotent execution
409
- * @param request.tool - Tool identifier (e.g. `'test_company_search'`)
410
- * @param request.input - Tool-specific input parameters
411
- * @returns The tool's output
451
+ * @param request - Tool call request.
452
+ * @returns Tool execution result.
412
453
  */
413
454
  execute<TOutput = LoosePlayObject>(
414
455
  request: ToolExecutionRequest & { staleAfterSeconds?: number },
415
456
  ): Promise<ToolExecuteResult<TOutput>>;
416
457
  };
458
+ /**
459
+ * Execute a single tool by stable step key and tool ID.
460
+ *
461
+ * Shorthand for `ctx.tools.execute(...)`; this is the preferred spelling in
462
+ * row-level step programs.
463
+ */
464
+ tool<TOutput = LoosePlayObject>(
465
+ key: string,
466
+ toolId: string,
467
+ input: Record<string, unknown>,
468
+ options?: { description?: string },
469
+ ): Promise<ToolExecuteResult<TOutput>>;
470
+ /**
471
+ * Run a reusable step program against one scalar input object.
472
+ *
473
+ * `steps().step(...)` is a composable mini-pipeline. Use `ctx.runSteps(...)`
474
+ * when that mini-pipeline should execute outside a row dataset. Inside a
475
+ * `ctx.map` column resolver, pass the step program directly to
476
+ * `.step(name, program)` instead.
477
+ *
478
+ * @param program - Step program.
479
+ * @param input - Program input.
480
+ * @param options - Run options.
481
+ * @returns Program output.
482
+ */
417
483
  runSteps<TInput extends Record<string, unknown>, TOutput>(
418
484
  program: StepProgram<TInput, unknown, TOutput>,
419
485
  input: TInput,
420
486
  options?: { description?: string },
421
487
  ): Promise<TOutput>;
488
+ /**
489
+ * Create one scalar checkpoint for the whole play run.
490
+ *
491
+ * Use `ctx.step` when a value is nondeterministic, expensive, external, or
492
+ * useful to inspect as a named boundary. The first execution stores the
493
+ * JSON-serializable output under `id`; replay and retries return the stored
494
+ * value instead of running `run` again.
495
+ *
496
+ * Plain deterministic assignment does not need `ctx.step`. Use
497
+ * `ctx.map(...).step(...)`, not `ctx.step`, when the value should become a
498
+ * field on each exported row.
499
+ *
500
+ * @param id - Checkpoint id.
501
+ * @param run - Computes the value once.
502
+ * @param options - Checkpoint options.
503
+ * @returns Checkpoint value.
504
+ */
422
505
  step<T>(
423
506
  id: string,
424
507
  run: () => T | Promise<T>,
425
508
  options?: { staleAfterSeconds?: number },
426
509
  ): Promise<T>;
510
+ /**
511
+ * Durable HTTP fetch.
512
+ *
513
+ * Use this for non-provider HTTP calls that must replay safely. The response
514
+ * is recorded under `key` so workflow replay sees the same value. Prefer
515
+ * `ctx.tools.execute(...)` for Deepline-managed provider APIs because tools
516
+ * handle auth, retries, rate limits, extraction metadata, and spend tracking.
517
+ *
518
+ * @param key - Checkpoint id.
519
+ * @param url - URL to fetch.
520
+ * @param init - Fetch options.
521
+ * @returns Recorded response.
522
+ */
427
523
  fetch(
428
524
  key: string,
429
525
  url: string | URL,
@@ -438,6 +534,22 @@ export interface DeeplinePlayRuntimeContext {
438
534
  bodyText: string;
439
535
  json: unknown | null;
440
536
  }>;
537
+ /**
538
+ * Invoke another registered or file-backed play as a child workflow.
539
+ *
540
+ * Use this for real composition boundaries, especially when a fitting
541
+ * scalar prebuilt play already encodes provider order, fallbacks,
542
+ * normalization, and no-result behavior. Do not invoke plays through
543
+ * `ctx.tools.execute`; tools and plays are separate namespaces.
544
+ *
545
+ * `key` is the stable child-call identity for idempotency and traceability.
546
+ *
547
+ * @param key - Child call id.
548
+ * @param playRef - Play name or handle.
549
+ * @param input - Child input.
550
+ * @param options - Run options.
551
+ * @returns Child play output.
552
+ */
441
553
  runPlay(
442
554
  key: string,
443
555
  playRef: string | PlayReferenceLike,
@@ -646,11 +758,24 @@ export type PlayInputContract<TInput> = {
646
758
  readonly __inputType?: TInput;
647
759
  };
648
760
 
761
+ /**
762
+ * Object-form play definition accepted by `definePlay(config)`.
763
+ *
764
+ * Use this form when the input contract should be explicit at definition time
765
+ * through `defineInput<T>(schema)`, or when configuration reads clearer as one
766
+ * object. The shorthand `definePlay(name, fn, bindings?)` is equivalent for
767
+ * simple file-backed plays.
768
+ */
649
769
  export type DefinePlayConfig<TInput, TOutput extends PlayReturnObject> = {
770
+ /** Play id/name. */
650
771
  id: string;
772
+ /** Input schema. */
651
773
  input: PlayInputContract<TInput>;
774
+ /** Play function. */
652
775
  run: (ctx: DeeplinePlayRuntimeContext, input: TInput) => Promise<TOutput>;
776
+ /** Trigger bindings. */
653
777
  bindings?: PlayBindings;
778
+ /** Billing options. */
654
779
  billing?: PlayBindings['billing'];
655
780
  };
656
781
 
@@ -1184,9 +1309,10 @@ export function defineInput<TInput>(
1184
1309
  *
1185
1310
  * @typeParam TInput - The input type accepted by the play
1186
1311
  * @typeParam TOutput - The return type of the play
1187
- * @param name - Unique play name (used for publishing, running by name, and CLI reference)
1188
- * @param fn - Async function that receives a {@link DeeplinePlayRuntimeContext} and typed input
1189
- * @param bindings - Optional trigger bindings (cron schedule, webhook with HMAC)
1312
+ * @param config - Object-form play config.
1313
+ * @param name - Play name.
1314
+ * @param fn - Play function.
1315
+ * @param bindings - Trigger bindings.
1190
1316
  * @returns A {@link DefinedPlay} that is both callable and has lifecycle methods
1191
1317
  *
1192
1318
  * @example Basic play
@@ -1207,9 +1333,9 @@ export function defineInput<TInput>(
1207
1333
  *
1208
1334
  * @example CSV processing play
1209
1335
  * ```typescript
1210
- * export default definePlay('bulk-enrich', async (ctx) => {
1211
- * const leads = await ctx.csv('leads.csv');
1212
- * ctx.log(`Processing ${leads.length} rows`);
1336
+ * export default definePlay('bulk-enrich', async (ctx, input: { csv: string }) => {
1337
+ * const leads = await ctx.csv(input.csv);
1338
+ * ctx.log(`Processing ${await leads.count()} rows`);
1213
1339
  * const results = await ctx
1214
1340
  * .map('companies', leads)
1215
1341
  * .step('company', (row, ctx) =>
@@ -1256,6 +1382,14 @@ export function defineInput<TInput>(
1256
1382
  export function definePlay<TInput, TOutput extends PlayReturnObject>(
1257
1383
  config: DefinePlayConfig<TInput, TOutput>,
1258
1384
  ): DefinedPlay<TInput, TOutput>;
1385
+ /**
1386
+ * Define a play with a name and function.
1387
+ *
1388
+ * @param name - Play name.
1389
+ * @param fn - Play function.
1390
+ * @param bindings - Trigger bindings.
1391
+ * @returns Play handle.
1392
+ */
1259
1393
  export function definePlay<TInput, TOutput extends PlayReturnObject>(
1260
1394
  name: string,
1261
1395
  fn: (ctx: DeeplinePlayRuntimeContext, input: TInput) => Promise<TOutput>,
@@ -14,13 +14,10 @@ import {
14
14
  } from '../../../shared_libs/plays/bundling/index.js';
15
15
  import {
16
16
  PLAY_ARTIFACT_KINDS,
17
- PLAY_BACKEND_DESCRIPTORS,
18
17
  type PlayArtifactKind,
19
18
  } from '../../../shared_libs/play-runtime/backend.js';
20
19
  import { resolveExecutionProfile } from '../../../shared_libs/play-runtime/profiles.js';
21
- import {
22
- discoverPackagedLocalFiles,
23
- } from './local-file-discovery.js';
20
+ import { discoverPackagedLocalFiles } from './local-file-discovery.js';
24
21
 
25
22
  export type {
26
23
  BundlePlayFileOptions,
@@ -39,9 +36,7 @@ export type {
39
36
  PlayRuntimeFeature,
40
37
  } from '../../../shared_libs/plays/bundling/index.js';
41
38
 
42
- export {
43
- extractDefinedPlayName,
44
- } from '../../../shared_libs/plays/bundling/index.js';
39
+ export { extractDefinedPlayName } from '../../../shared_libs/plays/bundling/index.js';
45
40
 
46
41
  const PLAY_BUNDLE_CACHE_VERSION = 30;
47
42
  const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
@@ -57,21 +52,32 @@ const HAS_PACKAGED_BUNDLING_SOURCES = existsSync(
57
52
  const PROJECT_ROOT = HAS_SOURCE_BUNDLING_SOURCES
58
53
  ? SOURCE_REPO_ROOT
59
54
  : HAS_PACKAGED_BUNDLING_SOURCES
60
- ? PACKAGED_REPO_ROOT
61
- : resolve(SDK_PACKAGE_ROOT, '..');
55
+ ? PACKAGED_REPO_ROOT
56
+ : resolve(SDK_PACKAGE_ROOT, '..');
62
57
  const SDK_SOURCE_ROOT = HAS_SOURCE_BUNDLING_SOURCES
63
58
  ? resolve(SOURCE_REPO_ROOT, 'sdk', 'src')
64
59
  : HAS_PACKAGED_BUNDLING_SOURCES
65
- ? resolve(PACKAGED_REPO_ROOT, 'sdk', 'src')
66
- : resolve(SDK_PACKAGE_ROOT, 'src');
60
+ ? resolve(PACKAGED_REPO_ROOT, 'sdk', 'src')
61
+ : resolve(SDK_PACKAGE_ROOT, 'src');
67
62
  const SDK_PACKAGE_JSON = resolve(SDK_PACKAGE_ROOT, 'package.json');
68
63
  const SDK_ENTRY_FILE = resolve(SDK_SOURCE_ROOT, 'index.ts');
69
64
  const SDK_TYPES_ENTRY_FILE = HAS_SOURCE_BUNDLING_SOURCES
70
65
  ? SDK_ENTRY_FILE
71
66
  : resolve(SDK_PACKAGE_ROOT, 'dist', 'index.d.ts');
72
67
  const SDK_WORKERS_ENTRY_FILE = resolve(SDK_SOURCE_ROOT, 'worker-play-entry.ts');
73
- const WORKERS_HARNESS_ENTRY_FILE = resolve(PROJECT_ROOT, 'apps', 'play-runner-workers', 'src', 'entry.ts');
74
- const WORKERS_HARNESS_FILES_DIR = resolve(PROJECT_ROOT, 'apps', 'play-runner-workers', 'src');
68
+ const WORKERS_HARNESS_ENTRY_FILE = resolve(
69
+ PROJECT_ROOT,
70
+ 'apps',
71
+ 'play-runner-workers',
72
+ 'src',
73
+ 'entry.ts',
74
+ );
75
+ const WORKERS_HARNESS_FILES_DIR = resolve(
76
+ PROJECT_ROOT,
77
+ 'apps',
78
+ 'play-runner-workers',
79
+ 'src',
80
+ );
75
81
  let hasWarnedAboutNonDevelopmentBundling = false;
76
82
 
77
83
  /**
@@ -93,7 +99,9 @@ function warnAboutNonDevelopmentBundling(filePath: string): void {
93
99
  return;
94
100
  }
95
101
 
96
- const nodeEnv = String(process.env.NODE_ENV ?? '').trim().toLowerCase();
102
+ const nodeEnv = String(process.env.NODE_ENV ?? '')
103
+ .trim()
104
+ .toLowerCase();
97
105
  if (!nodeEnv || nodeEnv === 'development' || nodeEnv === 'test') {
98
106
  return;
99
107
  }
@@ -111,16 +119,17 @@ function warnAboutNonDevelopmentBundling(filePath: string): void {
111
119
  }
112
120
 
113
121
  function defaultPlayBundleTarget(): PlayArtifactKind {
114
- return PLAY_BACKEND_DESCRIPTORS[
115
- resolveExecutionProfile(null).runner
116
- ].artifactKind;
122
+ return resolveExecutionProfile(null).artifactKind;
117
123
  }
118
124
 
119
125
  export function createSdkPlayBundlingAdapter(): PlayBundlingAdapter {
120
126
  return {
121
127
  projectRoot: PROJECT_ROOT,
122
128
  nodeModulesDir: resolve(PROJECT_ROOT, 'node_modules'),
123
- cacheDir: join(tmpdir(), `deepline-play-artifacts-v${PLAY_BUNDLE_CACHE_VERSION}`),
129
+ cacheDir: join(
130
+ tmpdir(),
131
+ `deepline-play-artifacts-v${PLAY_BUNDLE_CACHE_VERSION}`,
132
+ ),
124
133
  sdkSourceRoot: SDK_SOURCE_ROOT,
125
134
  sdkPackageJson: SDK_PACKAGE_JSON,
126
135
  sdkEntryFile: SDK_ENTRY_FILE,
@@ -50,10 +50,10 @@ export type SdkRelease = {
50
50
  };
51
51
 
52
52
  export const SDK_RELEASE = {
53
- version: '0.1.54',
54
- apiContract: '2026-05-run-response-package',
53
+ version: '0.1.56',
54
+ apiContract: '2026-05-play-tool-describe-starters',
55
55
  supportPolicy: {
56
- latest: '0.1.54',
56
+ latest: '0.1.56',
57
57
  minimumSupported: '0.1.53',
58
58
  deprecatedBelow: '0.1.53',
59
59
  },
@@ -125,6 +125,22 @@ export interface ToolDefinition {
125
125
  /** Copyable play-runtime guidance for V2 tool execution results. */
126
126
  usageGuidance?: {
127
127
  execute?: string;
128
+ prefer?: string[];
129
+ access?: {
130
+ extractedLists?: {
131
+ expression?: string;
132
+ meaning?: string;
133
+ };
134
+ extractedValues?: {
135
+ expression?: string;
136
+ meaning?: string;
137
+ };
138
+ rawToolResponse?: {
139
+ expression?: string;
140
+ meaning?: string;
141
+ };
142
+ invalidGetterHint?: string;
143
+ };
128
144
  toolExecutionResult?: {
129
145
  type?: 'ToolExecutionResult';
130
146
  toolResponse?: {
@@ -637,6 +653,11 @@ export interface PlayDescription {
637
653
  rowOutputSchema?: Record<string, unknown> | null;
638
654
  runCommand: string;
639
655
  examples: string[];
656
+ cloneEditStarter?: {
657
+ path: string;
658
+ command: string;
659
+ checkCommand: string;
660
+ };
640
661
  currentPublishedVersion?: number | null;
641
662
  isDraftDirty?: boolean;
642
663
  latestRunId?: string | null;