deepline 0.1.0 → 0.1.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.
Files changed (97) hide show
  1. package/dist/cli/index.js +212 -54
  2. package/dist/cli/index.js.map +1 -1
  3. package/dist/cli/index.mjs +198 -40
  4. package/dist/cli/index.mjs.map +1 -1
  5. package/dist/index.d.mts +1 -1
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.js +1 -1
  8. package/dist/index.mjs +1 -1
  9. package/dist/repo/apps/play-runner-workers/src/coordinator-entry.ts +3256 -0
  10. package/dist/repo/apps/play-runner-workers/src/dedup-do.ts +710 -0
  11. package/dist/repo/apps/play-runner-workers/src/entry.ts +5070 -0
  12. package/dist/repo/apps/play-runner-workers/src/runtime/README.md +21 -0
  13. package/dist/repo/apps/play-runner-workers/src/runtime/batching.ts +177 -0
  14. package/dist/repo/apps/play-runner-workers/src/runtime/execution-plan.ts +52 -0
  15. package/dist/repo/apps/play-runner-workers/src/runtime/tool-batch.ts +100 -0
  16. package/dist/repo/apps/play-runner-workers/src/runtime/tool-result.ts +184 -0
  17. package/dist/repo/sdk/src/cli/commands/auth.ts +482 -0
  18. package/dist/repo/sdk/src/cli/commands/billing.ts +188 -0
  19. package/dist/repo/sdk/src/cli/commands/csv.ts +123 -0
  20. package/dist/repo/sdk/src/cli/commands/db.ts +119 -0
  21. package/dist/repo/sdk/src/cli/commands/feedback.ts +40 -0
  22. package/dist/repo/sdk/src/cli/commands/org.ts +117 -0
  23. package/dist/repo/sdk/src/cli/commands/play.ts +3200 -0
  24. package/dist/repo/sdk/src/cli/commands/tools.ts +687 -0
  25. package/dist/repo/sdk/src/cli/dataset-stats.ts +341 -0
  26. package/dist/repo/sdk/src/cli/index.ts +138 -0
  27. package/dist/repo/sdk/src/cli/progress.ts +135 -0
  28. package/dist/repo/sdk/src/cli/trace.ts +61 -0
  29. package/dist/repo/sdk/src/cli/utils.ts +145 -0
  30. package/dist/repo/sdk/src/client.ts +1188 -0
  31. package/dist/repo/sdk/src/compat.ts +77 -0
  32. package/dist/repo/sdk/src/config.ts +285 -0
  33. package/dist/repo/sdk/src/errors.ts +125 -0
  34. package/dist/repo/sdk/src/http.ts +391 -0
  35. package/dist/repo/sdk/src/index.ts +139 -0
  36. package/dist/repo/sdk/src/play.ts +1330 -0
  37. package/dist/repo/sdk/src/plays/bundle-play-file.ts +133 -0
  38. package/dist/repo/sdk/src/plays/harness-stub.ts +210 -0
  39. package/dist/repo/sdk/src/plays/local-file-discovery.ts +326 -0
  40. package/dist/repo/sdk/src/tool-output.ts +489 -0
  41. package/dist/repo/sdk/src/types.ts +669 -0
  42. package/dist/repo/sdk/src/version.ts +2 -0
  43. package/dist/repo/sdk/src/worker-play-entry.ts +286 -0
  44. package/dist/repo/shared_libs/observability/node-tracing.ts +129 -0
  45. package/dist/repo/shared_libs/observability/tracing.ts +98 -0
  46. package/dist/repo/shared_libs/play-runtime/backend.ts +139 -0
  47. package/dist/repo/shared_libs/play-runtime/batch-runtime.ts +182 -0
  48. package/dist/repo/shared_libs/play-runtime/batching-types.ts +91 -0
  49. package/dist/repo/shared_libs/play-runtime/context.ts +3999 -0
  50. package/dist/repo/shared_libs/play-runtime/coordinator-headers.ts +78 -0
  51. package/dist/repo/shared_libs/play-runtime/ctx-contract.ts +250 -0
  52. package/dist/repo/shared_libs/play-runtime/ctx-types.ts +713 -0
  53. package/dist/repo/shared_libs/play-runtime/dataset-id.ts +10 -0
  54. package/dist/repo/shared_libs/play-runtime/db-session-crypto.ts +304 -0
  55. package/dist/repo/shared_libs/play-runtime/db-session.ts +462 -0
  56. package/dist/repo/shared_libs/play-runtime/dedup-backend.ts +0 -0
  57. package/dist/repo/shared_libs/play-runtime/default-batch-strategies.ts +124 -0
  58. package/dist/repo/shared_libs/play-runtime/execution-plan.ts +262 -0
  59. package/dist/repo/shared_libs/play-runtime/live-events.ts +214 -0
  60. package/dist/repo/shared_libs/play-runtime/live-state-contract.ts +50 -0
  61. package/dist/repo/shared_libs/play-runtime/map-execution-frame.ts +114 -0
  62. package/dist/repo/shared_libs/play-runtime/map-row-identity.ts +158 -0
  63. package/dist/repo/shared_libs/play-runtime/profiles.ts +90 -0
  64. package/dist/repo/shared_libs/play-runtime/progress-emitter.ts +172 -0
  65. package/dist/repo/shared_libs/play-runtime/protocol.ts +121 -0
  66. package/dist/repo/shared_libs/play-runtime/public-play-contract.ts +42 -0
  67. package/dist/repo/shared_libs/play-runtime/result-normalization.ts +33 -0
  68. package/dist/repo/shared_libs/play-runtime/runtime-actions.ts +208 -0
  69. package/dist/repo/shared_libs/play-runtime/runtime-api.ts +1873 -0
  70. package/dist/repo/shared_libs/play-runtime/runtime-constraints.ts +2 -0
  71. package/dist/repo/shared_libs/play-runtime/runtime-pg-driver-neon-serverless.ts +201 -0
  72. package/dist/repo/shared_libs/play-runtime/runtime-pg-driver-pg.ts +48 -0
  73. package/dist/repo/shared_libs/play-runtime/runtime-pg-driver.ts +84 -0
  74. package/dist/repo/shared_libs/play-runtime/scheduler-backend.ts +174 -0
  75. package/dist/repo/shared_libs/play-runtime/static-pipeline-types.ts +147 -0
  76. package/dist/repo/shared_libs/play-runtime/suspension.ts +68 -0
  77. package/dist/repo/shared_libs/play-runtime/tool-batch-executor.ts +146 -0
  78. package/dist/repo/shared_libs/play-runtime/tool-result.ts +387 -0
  79. package/dist/repo/shared_libs/play-runtime/tracing.ts +31 -0
  80. package/dist/repo/shared_libs/play-runtime/waterfall-replay.ts +75 -0
  81. package/dist/repo/shared_libs/play-runtime/worker-api-types.ts +140 -0
  82. package/dist/repo/shared_libs/plays/artifact-transport.ts +14 -0
  83. package/dist/repo/shared_libs/plays/artifact-types.ts +49 -0
  84. package/dist/repo/shared_libs/plays/bundling/index.ts +1346 -0
  85. package/dist/repo/shared_libs/plays/compiler-manifest.ts +186 -0
  86. package/dist/repo/shared_libs/plays/contracts.ts +51 -0
  87. package/dist/repo/shared_libs/plays/dataset.ts +308 -0
  88. package/dist/repo/shared_libs/plays/definition.ts +264 -0
  89. package/dist/repo/shared_libs/plays/file-refs.ts +11 -0
  90. package/dist/repo/shared_libs/plays/rate-limit-scheduler.ts +206 -0
  91. package/dist/repo/shared_libs/plays/resolve-static-pipeline.ts +164 -0
  92. package/dist/repo/shared_libs/plays/row-identity.ts +302 -0
  93. package/dist/repo/shared_libs/plays/runtime-validation.ts +415 -0
  94. package/dist/repo/shared_libs/plays/static-pipeline.ts +560 -0
  95. package/dist/repo/shared_libs/temporal/constants.ts +39 -0
  96. package/dist/repo/shared_libs/temporal/preview-config.ts +153 -0
  97. package/package.json +4 -4
@@ -0,0 +1,1330 @@
1
+ /**
2
+ * Play definition, execution, and lifecycle management.
3
+ *
4
+ * This module provides the high-level API for defining and running plays —
5
+ * composable TypeScript workflows that orchestrate tool calls into repeatable
6
+ * pipelines. Plays run on Temporal for durable execution with automatic retries,
7
+ * timeouts, and full observability.
8
+ *
9
+ * ## Core concepts
10
+ *
11
+ * - **{@link definePlay}** — Define a play as a typed async function. The returned
12
+ * value is both callable (for server-side execution) and has methods for
13
+ * remote lifecycle management (run, get, publish, tail).
14
+ *
15
+ * - **{@link Deepline.connect}** — Create a client context for programmatic SDK
16
+ * usage. Returns a {@link DeeplineContext} with tool and play handles.
17
+ *
18
+ * - **{@link PlayJob}** — Handle to a running play execution. Poll for status,
19
+ * stream logs, wait for completion, or cancel.
20
+ *
21
+ * ## Usage patterns
22
+ *
23
+ * ### Define and run a play (file-backed)
24
+ *
25
+ * ```typescript
26
+ * // my-play.play.ts
27
+ * import { definePlay } from 'deepline';
28
+ *
29
+ * export default definePlay('my-play', async (ctx, input: { domain: string }) => {
30
+ * ctx.log(`Looking up ${input.domain}`);
31
+ * const company = await ctx.tools.execute({
32
+ * id: 'company_search',
33
+ * tool: 'test_company_search',
34
+ * input: { domain: input.domain },
35
+ * description: 'Look up company details by domain.',
36
+ * });
37
+ * return { company: company.result };
38
+ * });
39
+ * ```
40
+ *
41
+ * Then run via CLI: `deepline play run --file my-play.play.ts --watch`
42
+ *
43
+ * ### Programmatic usage
44
+ *
45
+ * ```typescript
46
+ * import { Deepline } from 'deepline';
47
+ *
48
+ * const ctx = await Deepline.connect();
49
+ * const job = await ctx.play('my-play').run({ domain: 'stripe.com' });
50
+ * const result = await job.get(); // Polls until complete
51
+ * ```
52
+ *
53
+ * ### With bindings (cron, webhook)
54
+ *
55
+ * ```typescript
56
+ * import { definePlay } from 'deepline';
57
+ *
58
+ * export default definePlay('daily-sync', async (ctx) => {
59
+ * const data = await ctx.tools.execute({
60
+ * id: 'crm_export',
61
+ * tool: 'crm_export',
62
+ * input: {},
63
+ * description: 'Export CRM records for the daily sync.',
64
+ * });
65
+ * return data;
66
+ * }, {
67
+ * cron: { schedule: '0 9 * * *', timezone: 'America/New_York' },
68
+ * });
69
+ * ```
70
+ *
71
+ * @module
72
+ */
73
+ import { DeeplineClient } from './client.js';
74
+ import { DeeplineError } from './errors.js';
75
+ import { createToolCallResult } from './tool-output.js';
76
+ import type { ToolCallResult } from './tool-output.js';
77
+ import type {
78
+ PlayDataset,
79
+ PlayDatasetInput,
80
+ } from '../../shared_libs/plays/dataset.js';
81
+ import type {
82
+ DeeplineClientOptions,
83
+ PlayDetail,
84
+ ClearPlayHistoryResult,
85
+ PlayRevisionSummary,
86
+ PlayRunListItem,
87
+ PlayStatus,
88
+ PublishPlayVersionResult,
89
+ StopPlayRunResult,
90
+ ToolDefinition,
91
+ ToolMetadata,
92
+ } from './types.js';
93
+
94
+ /**
95
+ * Optional trigger bindings for a play.
96
+ *
97
+ * Plays can be triggered by webhooks (with HMAC signature verification)
98
+ * or cron schedules. Bindings are declared as the third argument to
99
+ * {@link definePlay}.
100
+ *
101
+ * @example Webhook with HMAC verification
102
+ * ```typescript
103
+ * definePlay('webhook-handler', handler, {
104
+ * webhook: {
105
+ * hmac: {
106
+ * algorithm: 'sha256',
107
+ * header: 'X-Hub-Signature-256',
108
+ * secretEnv: 'WEBHOOK_SECRET',
109
+ * },
110
+ * },
111
+ * });
112
+ * ```
113
+ *
114
+ * @example Cron schedule
115
+ * ```typescript
116
+ * definePlay('nightly-sync', handler, {
117
+ * cron: { schedule: '0 2 * * *', timezone: 'UTC' },
118
+ * });
119
+ * ```
120
+ */
121
+ export type PlayBindings = {
122
+ /** Optional per-run billing controls enforced by the runtime. */
123
+ billing?: {
124
+ /** Stop the run before a billed action would push total run credits above this cap. */
125
+ maxCreditsPerRun?: number;
126
+ };
127
+ /** Webhook trigger with optional HMAC signature verification. */
128
+ webhook?: {
129
+ hmac?: {
130
+ /** Hash algorithm. Currently only `'sha256'` is supported. */
131
+ algorithm?: 'sha256';
132
+ /** HTTP header containing the signature (e.g. `'X-Hub-Signature-256'`). */
133
+ header?: string;
134
+ /** Environment variable name holding the HMAC secret. */
135
+ secretEnv: string;
136
+ };
137
+ };
138
+ /** Cron schedule trigger. */
139
+ cron?: {
140
+ /** Cron expression (e.g. `'0 9 * * *'` for daily at 9am). */
141
+ schedule: string;
142
+ /** IANA timezone (e.g. `'America/New_York'`). Defaults to UTC. */
143
+ timezone?: string;
144
+ };
145
+ };
146
+
147
+ export type LoosePlayObject = {
148
+ [key: string]: LoosePlayObject;
149
+ };
150
+
151
+ export type ToolExecutionRequest = {
152
+ id: string;
153
+ tool: string;
154
+ input: Record<string, unknown>;
155
+ description?: string;
156
+ };
157
+
158
+ export type SdkToolExecutionRequest = {
159
+ tool: string;
160
+ input: Record<string, unknown>;
161
+ };
162
+
163
+ export type ToolExecuteResult<TResult = unknown> = {
164
+ status: string;
165
+ result: TResult;
166
+ _metadata: {
167
+ toolId: string;
168
+ execution: {
169
+ idempotent: true;
170
+ cached: boolean;
171
+ source: 'live' | 'checkpoint' | 'cache';
172
+ cacheKey?: string;
173
+ };
174
+ targets: Record<string, { value: unknown; path: string }>;
175
+ lists: Record<
176
+ string,
177
+ {
178
+ path: string;
179
+ count: number | null;
180
+ keys: Record<string, string>;
181
+ }
182
+ >;
183
+ };
184
+ get<T = unknown>(target: string): T | null;
185
+ getEmail(): string | null;
186
+ getPhone(): string | null;
187
+ getLinkedin(): string | null;
188
+ list<T = Record<string, unknown>>(name?: string): T[] | null;
189
+ listPick<const TKeys extends readonly string[]>(
190
+ keys: TKeys,
191
+ name?: string,
192
+ ): Array<Record<TKeys[number], unknown>> | null;
193
+ listKeys(name?: string): Record<string, string>;
194
+ };
195
+
196
+ export type StepResolver<Row, Value> = (
197
+ row: Row,
198
+ ctx: DeeplinePlayRuntimeContext,
199
+ index: number,
200
+ ) => Value | Promise<Value>;
201
+
202
+ export type ConditionalStepResolver<Row, Value, Else = null> = {
203
+ readonly kind: 'conditional';
204
+ readonly when: (row: Row, index: number) => boolean | Promise<boolean>;
205
+ readonly run: StepResolver<Row, Value>;
206
+ readonly elseValue: Else;
207
+ else<ValueElse>(
208
+ value: ValueElse,
209
+ ): ConditionalStepResolver<Row, Value, ValueElse>;
210
+ };
211
+
212
+ export type StepProgram<Input, Output, Return = Output> = {
213
+ readonly kind: 'steps';
214
+ readonly steps: readonly PlayStepProgramStep[];
215
+ readonly returnResolver?: StepResolver<Output, Return>;
216
+ readonly __inputType?: (input: Input) => void;
217
+ step<Name extends string, Value>(
218
+ name: Name,
219
+ resolver:
220
+ | StepResolver<Output, Value>
221
+ | ConditionalStepResolver<Output, Value>
222
+ | StepProgramResolver<Output, Value>,
223
+ ): StepProgram<Input, Output & Record<Name, Value>, Return>;
224
+ return<Value>(
225
+ resolver: StepResolver<Output, Value>,
226
+ ): StepProgram<Input, Output, Value>;
227
+ };
228
+
229
+ export type StepProgramResolver<Input, Return> = {
230
+ readonly kind: 'steps';
231
+ readonly steps: readonly PlayStepProgramStep[];
232
+ readonly returnResolver?: StepResolver<never, Return>;
233
+ readonly __inputType?: (input: Input) => void;
234
+ };
235
+
236
+ export type PlayStepProgramStep = {
237
+ readonly name: string;
238
+ readonly resolver:
239
+ | StepResolver<Record<string, unknown>, unknown>
240
+ | ConditionalStepResolver<Record<string, unknown>, unknown>
241
+ | StepProgramResolver<Record<string, unknown>, unknown>;
242
+ };
243
+
244
+ export type MapStepResolver<Row, Value> =
245
+ | StepResolver<Row, Value>
246
+ | ConditionalStepResolver<Row, Value>
247
+ | StepProgramResolver<Row, Value>;
248
+
249
+ export type MapRowKey<InputRow extends object> =
250
+ | (keyof InputRow & string)
251
+ | readonly (keyof InputRow & string)[]
252
+ | ((row: InputRow, index: number) => string | number | readonly unknown[]);
253
+
254
+ export type MapDefinitionOptions<InputRow extends object> = {
255
+ staleAfterSeconds?: number;
256
+ key?: MapRowKey<InputRow>;
257
+ };
258
+
259
+ export type MapRunOptions = {
260
+ description?: string;
261
+ };
262
+
263
+ export type MapStepBuilder<
264
+ InputRow extends object,
265
+ OutputRow extends object,
266
+ > = {
267
+ step<Name extends string, Value>(
268
+ name: Name,
269
+ resolver: MapStepResolver<OutputRow, Value>,
270
+ ): MapStepBuilder<InputRow, OutputRow & Record<Name, Value>>;
271
+ run(options?: MapRunOptions): Promise<PlayDataset<OutputRow>>;
272
+ };
273
+
274
+ /**
275
+ * Runtime context available inside a play function.
276
+ *
277
+ * Provides methods for calling tools, processing data, and emitting logs.
278
+ * This context is injected by the Temporal worker — you never construct it directly.
279
+ *
280
+ * @example
281
+ * ```typescript
282
+ * definePlay('example', async (ctx, input: { domain: string }) => {
283
+ * // Call a tool
284
+ * const company = await ctx.tools.execute({
285
+ * id: 'company_search',
286
+ * tool: 'test_company_search',
287
+ * input: { domain: input.domain },
288
+ * description: 'Look up company details by domain.',
289
+ * });
290
+ *
291
+ * // Fan-out: process items with named steps
292
+ * const enriched = await ctx
293
+ * .map('companies', [{ domain: 'a.com' }, { domain: 'b.com' }], { key: 'domain' })
294
+ * .step('company', (row, rowCtx) =>
295
+ * rowCtx.tool({
296
+ * id: 'company_search',
297
+ * tool: 'test_company_search',
298
+ * input: { domain: row.domain },
299
+ * description: 'Look up company details by domain.',
300
+ * }))
301
+ * .run({ description: 'Look up company details.' });
302
+ *
303
+ * // Load CSV data
304
+ * const leads = await ctx.csv('leads.csv');
305
+ *
306
+ * // Emit a log line (visible in `play tail`)
307
+ * ctx.log(`Loaded ${await leads.count()} leads`);
308
+ *
309
+ * // Pause execution
310
+ * await ctx.sleep(1000);
311
+ *
312
+ * // Access the raw input object
313
+ * console.log(ctx.input);
314
+ *
315
+ * return { company, enriched };
316
+ * });
317
+ * ```
318
+ */
319
+ export interface DeeplinePlayRuntimeContext {
320
+ /**
321
+ * Load a CSV file as a dataset handle.
322
+ *
323
+ * The CSV must be staged or available at the given path. Each row becomes
324
+ * an object keyed by column headers.
325
+ *
326
+ * @typeParam T - Row type (defaults to `Record<string, unknown>`)
327
+ * @param path - Relative path to the CSV file
328
+ * The returned dataset supports `for await`, `peek()`, `count()`, and
329
+ * explicit `materialize()` for small result sets.
330
+ *
331
+ * @returns Parsed dataset rows
332
+ */
333
+ csv<T = Record<string, unknown>>(
334
+ path: string,
335
+ options?: { description?: string },
336
+ ): Promise<PlayDataset<T>>;
337
+
338
+ /**
339
+ * Fan-out: process each item through one or more named columns.
340
+ *
341
+ * Each key in `columns` becomes an output column. Each value is an async
342
+ * callback `(row, ctx) => result` that receives the current row and the
343
+ * play context — call tools, run waterfalls, do arbitrary logic.
344
+ *
345
+ * Items are processed in parallel (paced by the rate-limit scheduler).
346
+ * The first argument identifies the logical map/table namespace. Use
347
+ * `options.key` to pin durable row identity to stable input fields instead of
348
+ * hashing the full input row.
349
+ * `options.staleAfterSeconds` intentionally partitions the durable cache on a
350
+ * relative time window. Use `86400` for daily reruns; retries inside the same
351
+ * window still replay safely.
352
+ *
353
+ * Returns a dataset handle containing the original rows merged with the new
354
+ * columns. Input may be a normal array, iterable, async iterable, or another
355
+ * play dataset handle.
356
+ *
357
+ * @typeParam T - Row type
358
+ * @param key - Logical map key used to isolate row identities (e.g. `'main_table'`, `'email_lookup'`)
359
+ * @param items - Input rows or dataset handle
360
+ * @returns Dataset of rows merged with the computed column values
361
+ *
362
+ * @example Single tool per row
363
+ * ```typescript
364
+ * const results = await ctx
365
+ * .map('leads', leads, { key: 'domain' })
366
+ * .step('company', (row, ctx) =>
367
+ * ctx.tools.execute({
368
+ * id: 'company_search',
369
+ * tool: 'test_company_search',
370
+ * input: { domain: row.domain },
371
+ * description: 'Look up company details by domain.',
372
+ * }))
373
+ * .run({ description: 'Look up companies.' });
374
+ * // [{ domain: 'stripe.com', company: { name: 'Stripe', ... } }, ...]
375
+ * ```
376
+ *
377
+ * @example Multiple columns with pre/post logic
378
+ * ```typescript
379
+ * const results = await ctx
380
+ * .map('leads', leads, { key: 'lead_id' })
381
+ * .step('company', (row, ctx) =>
382
+ * ctx.tools.execute({
383
+ * id: 'company_search',
384
+ * tool: 'test_company_search',
385
+ * input: { domain: row.domain },
386
+ * description: 'Look up company details by domain.',
387
+ * }))
388
+ * .step('score', (row) =>
389
+ * row.company?.employeeCount > 100 ? 'enterprise' : 'smb')
390
+ * .run({ description: 'Enrich leads.' });
391
+ * ```
392
+ */
393
+ map<TItem extends object>(
394
+ key: string,
395
+ items: PlayDatasetInput<TItem>,
396
+ options?: MapDefinitionOptions<TItem>,
397
+ ): MapStepBuilder<TItem, TItem>;
398
+
399
+ /** Tool execution namespace. */
400
+ tools: {
401
+ /**
402
+ * Execute a single tool by stable durable execution id and tool ID.
403
+ *
404
+ * @param request - Tool execution request. `id` is the durable execution id;
405
+ * omit it in row/map steps when the surrounding step id is the execution id.
406
+ * @returns The tool's output
407
+ */
408
+ execute<TOutput = LoosePlayObject>(
409
+ request: ToolExecutionRequest,
410
+ ): Promise<ToolExecuteResult<TOutput>>;
411
+ };
412
+ /**
413
+ * Execute a single tool by stable durable execution id and tool ID.
414
+ *
415
+ * Shorthand for `ctx.tools.execute({ id, tool, input })`; this is the
416
+ * preferred spelling in row-level step programs.
417
+ */
418
+ tool<TOutput = LoosePlayObject>(
419
+ request: ToolExecutionRequest,
420
+ ): Promise<ToolExecuteResult<TOutput>>;
421
+ step<T>(id: string, run: () => T | Promise<T>): Promise<T>;
422
+ fetch(
423
+ key: string,
424
+ url: string | URL,
425
+ init?: RequestInit,
426
+ ): Promise<{
427
+ ok: boolean;
428
+ status: number;
429
+ statusText: string;
430
+ url: string;
431
+ headers: Record<string, string>;
432
+ bodyText: string;
433
+ json: unknown | null;
434
+ }>;
435
+ runPlay(
436
+ key: string,
437
+ playRef: string | PlayReferenceLike,
438
+ input: Record<string, unknown>,
439
+ options: { description?: string },
440
+ ): Promise<Record<string, unknown>>;
441
+
442
+ /**
443
+ * Emit a log line visible in `play tail` and the play's progress logs.
444
+ *
445
+ * @param message - Log message (plain text)
446
+ */
447
+ log(message: string): void;
448
+
449
+ /**
450
+ * Pause play execution for the specified duration.
451
+ *
452
+ * Uses Temporal's durable timer — safe across worker restarts.
453
+ *
454
+ * @param ms - Duration in milliseconds
455
+ */
456
+ sleep(ms: number): Promise<void>;
457
+
458
+ /** The raw input object passed when the play was started. */
459
+ readonly input: Record<string, unknown>;
460
+ }
461
+
462
+ /**
463
+ * Handle to a running play execution.
464
+ *
465
+ * Provides methods to check status, stream logs, wait for completion,
466
+ * or cancel the execution.
467
+ *
468
+ * This handle is the SDK-context equivalent of `deepline play run --watch` and
469
+ * `POST /api/v2/plays/run`: every surface returns a run id first, then exposes
470
+ * the completed user output through `PlayJob.get()` or the status endpoint's
471
+ * `result` field. Runtime logs are available from `status().progress.logs` and
472
+ * are intentionally separate from the returned output object.
473
+ *
474
+ * @typeParam TOutput - The play's return type
475
+ *
476
+ * @example
477
+ * ```typescript
478
+ * const job: PlayJob = await ctx.play('my-play').run({ domain: 'stripe.com' });
479
+ *
480
+ * // Poll status
481
+ * const status = await job.status();
482
+ * console.log(status.temporalStatus); // 'RUNNING'
483
+ *
484
+ * // Stream logs until completion
485
+ * const finalStatus = await job.tail({
486
+ * onLog: (line) => console.log(`[play] ${line}`),
487
+ * });
488
+ *
489
+ * // Or just wait for the result
490
+ * const output = await job.get();
491
+ *
492
+ * // Cancel if needed
493
+ * await job.cancel();
494
+ * ```
495
+ */
496
+ export interface PlayJob<TOutput = unknown> {
497
+ /** Temporal workflow ID for this execution. */
498
+ id: string;
499
+
500
+ /** Get the current execution status (single poll). */
501
+ status(): Promise<PlayStatus>;
502
+
503
+ /**
504
+ * Stream logs and wait for completion.
505
+ *
506
+ * Polls until the play reaches a terminal state, invoking `onLog` for
507
+ * each new log line. Returns the final status.
508
+ *
509
+ * @param options.intervalMs - Poll interval in ms. Default: `500`.
510
+ * @param options.onLog - Callback for each log line. Default: `console.log`.
511
+ */
512
+ tail(options?: {
513
+ intervalMs?: number;
514
+ onLog?: (line: string) => void;
515
+ }): Promise<PlayStatus>;
516
+
517
+ /**
518
+ * Wait for the play to complete and return its output.
519
+ *
520
+ * Polls until terminal state. Throws {@link DeeplineError} if the play
521
+ * fails, is cancelled, or times out.
522
+ *
523
+ * @param options.intervalMs - Poll interval in ms. Default: `500`.
524
+ * @returns The play's return value
525
+ * @throws {@link DeeplineError} if the play did not complete successfully
526
+ */
527
+ get(options?: { intervalMs?: number }): Promise<TOutput>;
528
+
529
+ /** Cancel this play execution. */
530
+ cancel(): Promise<void>;
531
+
532
+ /** Deep-stop this play execution, including open HITL waits. */
533
+ stop(options?: { reason?: string }): Promise<StopPlayRunResult>;
534
+ }
535
+
536
+ /**
537
+ * Handle to a named play for remote lifecycle operations.
538
+ *
539
+ * Returned by {@link DeeplineContext.play} and attached to {@link DefinedPlay}.
540
+ * Provides methods to run, inspect, list runs, and publish a play by name.
541
+ *
542
+ * @typeParam TInput - The play's input type
543
+ * @typeParam TOutput - The play's return type
544
+ *
545
+ * @example
546
+ * ```typescript
547
+ * const ctx = await Deepline.connect();
548
+ * const play = ctx.play<{ domain: string }, Company>('company-lookup');
549
+ *
550
+ * // Get play definition
551
+ * const detail = await play.get();
552
+ * console.log(`Live: v${detail.play.currentPublishedVersion}`);
553
+ *
554
+ * // Run and wait
555
+ * const result = await play.runSync({ domain: 'stripe.com' });
556
+ *
557
+ * // Run async
558
+ * const job = await play.run({ domain: 'stripe.com' });
559
+ * const output = await job.get();
560
+ *
561
+ * // List recent runs
562
+ * const runs = await play.runs();
563
+ *
564
+ * // List saved versions
565
+ * const versions = await play.versions();
566
+ *
567
+ * // Publish the current draft
568
+ * await play.publish();
569
+ * ```
570
+ */
571
+ export interface DeeplineNamedPlay<
572
+ TInput = Record<string, unknown>,
573
+ TOutput = unknown,
574
+ > {
575
+ /** The play's name. */
576
+ readonly name: string;
577
+
578
+ /** Fetch the full play definition with revision history and run stats. */
579
+ get(): Promise<PlayDetail>;
580
+
581
+ /** List recent runs for this play. */
582
+ runs(): Promise<PlayRunListItem[]>;
583
+
584
+ /** List saved versions for this play (newest first). */
585
+ versions(): Promise<PlayRevisionSummary[]>;
586
+
587
+ /** Publish a play revision. Defaults to the current working revision. */
588
+ publish(options?: { revisionId?: string }): Promise<PublishPlayVersionResult>;
589
+
590
+ /**
591
+ * Clear run history and durable sheet/result data for this play while keeping
592
+ * the play definition and revisions.
593
+ */
594
+ clearHistory(options?: {
595
+ tableNamespaces?: string[];
596
+ }): Promise<ClearPlayHistoryResult>;
597
+
598
+ /**
599
+ * Start a new run of this play. Returns a {@link PlayJob} for monitoring.
600
+ *
601
+ * @param input - Runtime input passed to the play function
602
+ */
603
+ run(
604
+ input: TInput,
605
+ options?: { revisionId?: string },
606
+ ): Promise<PlayJob<TOutput>>;
607
+
608
+ /**
609
+ * Run this play and wait for completion.
610
+ *
611
+ * Equivalent to `play.run(input).then(job => job.get())`.
612
+ *
613
+ * @param input - Runtime input
614
+ * @returns The play's return value
615
+ */
616
+ runSync(input: TInput, options?: { revisionId?: string }): Promise<TOutput>;
617
+ }
618
+
619
+ export type PrebuiltPlayRef = {
620
+ readonly playName: string;
621
+ readonly name: string;
622
+ };
623
+
624
+ export type PlayReferenceLike = {
625
+ readonly playName?: string;
626
+ readonly name?: string;
627
+ };
628
+
629
+ export type {
630
+ PlayDataset,
631
+ PlayDatasetInput,
632
+ } from '../../shared_libs/plays/dataset.js';
633
+
634
+ export type PlayReturnObject = Record<string, unknown> & {
635
+ readonly _metadata?: never;
636
+ };
637
+
638
+ export type PlayInputContract<TInput> = {
639
+ readonly schema: Record<string, unknown>;
640
+ readonly __inputType?: TInput;
641
+ };
642
+
643
+ export type DefinePlayConfig<TInput, TOutput extends PlayReturnObject> = {
644
+ id: string;
645
+ input: PlayInputContract<TInput>;
646
+ run: (ctx: DeeplinePlayRuntimeContext, input: TInput) => Promise<TOutput>;
647
+ bindings?: PlayBindings;
648
+ billing?: PlayBindings['billing'];
649
+ };
650
+
651
+ class DeeplineConditionalStepResolver<
652
+ Row,
653
+ Value,
654
+ ElseValue,
655
+ > implements ConditionalStepResolver<Row, Value, ElseValue> {
656
+ readonly kind = 'conditional' as const;
657
+
658
+ constructor(
659
+ readonly when: (row: Row, index: number) => boolean | Promise<boolean>,
660
+ readonly run: StepResolver<Row, Value>,
661
+ readonly elseValue: ElseValue,
662
+ ) {}
663
+
664
+ else<ValueElse>(
665
+ value: ValueElse,
666
+ ): ConditionalStepResolver<Row, Value, ValueElse> {
667
+ return new DeeplineConditionalStepResolver(this.when, this.run, value);
668
+ }
669
+ }
670
+
671
+ class DeeplineStepProgram<Input, Output, ReturnValue> implements StepProgram<
672
+ Input,
673
+ Output,
674
+ ReturnValue
675
+ > {
676
+ readonly kind = 'steps' as const;
677
+ declare readonly __inputType?: (input: Input) => void;
678
+
679
+ constructor(
680
+ readonly steps: readonly PlayStepProgramStep[],
681
+ readonly returnResolver?: StepResolver<Output, ReturnValue>,
682
+ ) {}
683
+
684
+ step<Name extends string, Value>(
685
+ name: Name,
686
+ resolver:
687
+ | StepResolver<Output, Value>
688
+ | ConditionalStepResolver<Output, Value>
689
+ | StepProgramResolver<Output, Value>,
690
+ ): StepProgram<Input, Output & Record<Name, Value>, ReturnValue> {
691
+ if (!name.trim()) {
692
+ throw new Error(
693
+ 'steps().step(name, ...) requires a non-empty step name.',
694
+ );
695
+ }
696
+ return new DeeplineStepProgram(
697
+ [
698
+ ...this.steps,
699
+ {
700
+ name,
701
+ resolver: resolver as PlayStepProgramStep['resolver'],
702
+ },
703
+ ],
704
+ this.returnResolver as StepResolver<
705
+ Output & Record<Name, Value>,
706
+ ReturnValue
707
+ >,
708
+ );
709
+ }
710
+
711
+ return<Value>(
712
+ resolver: StepResolver<Output, Value>,
713
+ ): StepProgram<Input, Output, Value> {
714
+ return new DeeplineStepProgram(this.steps, resolver);
715
+ }
716
+ }
717
+
718
+ export function steps<TInput>(): StepProgram<TInput, TInput, TInput> {
719
+ return new DeeplineStepProgram<TInput, TInput, TInput>([]);
720
+ }
721
+
722
+ export function when<Row, Value>(
723
+ predicate: (row: Row, index: number) => boolean | Promise<boolean>,
724
+ resolver: StepResolver<Row, Value>,
725
+ ): ConditionalStepResolver<Row, Value, null> {
726
+ return new DeeplineConditionalStepResolver(predicate, resolver, null);
727
+ }
728
+
729
+ /**
730
+ * A defined play: both a callable function and a named play handle.
731
+ *
732
+ * Created by {@link definePlay}. Can be:
733
+ * 1. Called directly as a function (for server-side Temporal execution)
734
+ * 2. Used as a {@link DeeplineNamedPlay} for remote lifecycle operations
735
+ *
736
+ * @typeParam TInput - The play's input type
737
+ * @typeParam TOutput - The play's return type
738
+ *
739
+ * @example
740
+ * ```typescript
741
+ * import { definePlay } from 'deepline';
742
+ *
743
+ * const myPlay = definePlay('my-play', async (ctx, input: { domain: string }) => {
744
+ * const company = await ctx.tools.execute({
745
+ * id: 'company_search',
746
+ * tool: 'test_company_search',
747
+ * input: { domain: input.domain },
748
+ * description: 'Look up company details by domain.',
749
+ * });
750
+ * return { company: company.result };
751
+ * });
752
+ *
753
+ * // Type is: DefinedPlay<{ domain: string }, unknown>
754
+ *
755
+ * // Use as named play handle:
756
+ * const detail = await myPlay.get();
757
+ * const result = await myPlay.runSync({ domain: 'stripe.com' });
758
+ *
759
+ * // Access metadata:
760
+ * console.log(myPlay.playName); // "my-play"
761
+ * console.log(myPlay.bindings); // undefined (no cron/webhook)
762
+ * ```
763
+ */
764
+ export type DefinedPlay<TInput, TOutput extends PlayReturnObject> = ((
765
+ ctx: DeeplinePlayRuntimeContext,
766
+ input: TInput,
767
+ ) => Promise<TOutput>) &
768
+ DeeplineNamedPlay<TInput, TOutput> & {
769
+ /** Optional trigger bindings (cron, webhook). */
770
+ readonly bindings?: PlayBindings;
771
+ /** The play's name (same as `.name`). */
772
+ readonly playName: string;
773
+ };
774
+
775
+ type PlayMetadata = {
776
+ name: string;
777
+ bindings?: PlayBindings;
778
+ inputSchema?: Record<string, unknown>;
779
+ billing?: PlayBindings['billing'];
780
+ };
781
+
782
+ const PLAY_METADATA_SYMBOL = Symbol.for('deepline.play.metadata');
783
+
784
+ class DeeplinePlayJobImpl<TOutput = unknown> implements PlayJob<TOutput> {
785
+ readonly id: string;
786
+
787
+ constructor(
788
+ private readonly client: DeeplineClient,
789
+ runId: string,
790
+ ) {
791
+ this.id = runId;
792
+ }
793
+
794
+ async status(): Promise<PlayStatus> {
795
+ return this.client.getPlayStatus(this.id);
796
+ }
797
+
798
+ async tail(options?: {
799
+ intervalMs?: number;
800
+ onLog?: (line: string) => void;
801
+ }): Promise<PlayStatus> {
802
+ const intervalMs = options?.intervalMs ?? 500;
803
+ const onLog = options?.onLog ?? ((line: string) => console.log(line));
804
+ const terminalStates = new Set(['completed', 'failed', 'cancelled']);
805
+ let lastLogIndex = 0;
806
+
807
+ while (true) {
808
+ const status = await this.status();
809
+ const logs = status.progress?.logs ?? [];
810
+ for (let index = lastLogIndex; index < logs.length; index += 1) {
811
+ onLog(logs[index]!);
812
+ }
813
+ lastLogIndex = logs.length;
814
+
815
+ if (terminalStates.has(status.status)) {
816
+ return status;
817
+ }
818
+
819
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
820
+ }
821
+ }
822
+
823
+ async get(options?: { intervalMs?: number }): Promise<TOutput> {
824
+ const intervalMs = options?.intervalMs ?? 500;
825
+ const terminalStates = new Set(['completed', 'failed', 'cancelled']);
826
+
827
+ while (true) {
828
+ const status = await this.status();
829
+ if (terminalStates.has(status.status)) {
830
+ if (status.status !== 'completed') {
831
+ throw new DeeplineError(
832
+ status.progress?.error ||
833
+ `Play run ${this.id} ended with ${status.status}.`,
834
+ );
835
+ }
836
+ const payload = status.result as { output?: unknown } | undefined;
837
+ return ((payload && 'output' in payload
838
+ ? payload.output
839
+ : status.result) ?? null) as TOutput;
840
+ }
841
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
842
+ }
843
+ }
844
+
845
+ async cancel(): Promise<void> {
846
+ await this.client.cancelPlay(this.id);
847
+ }
848
+
849
+ async stop(options?: { reason?: string }): Promise<StopPlayRunResult> {
850
+ return this.client.stopPlay(this.id, options);
851
+ }
852
+ }
853
+
854
+ function createNamedPlayHandle<
855
+ TInput = Record<string, unknown>,
856
+ TOutput = unknown,
857
+ >(
858
+ clientFactory: () => DeeplineClient,
859
+ name: string,
860
+ ): DeeplineNamedPlay<TInput, TOutput> {
861
+ return {
862
+ name,
863
+ get: () => clientFactory().getPlay(name),
864
+ runs: () => clientFactory().listPlayRuns(name),
865
+ versions: () => clientFactory().listPlayVersions(name),
866
+ publish: (options) => clientFactory().publishPlayVersion(name, options),
867
+ clearHistory: (options) => clientFactory().clearPlayHistory(name, options),
868
+ async run(
869
+ input: TInput,
870
+ options?: { revisionId?: string },
871
+ ): Promise<PlayJob<TOutput>> {
872
+ const client = clientFactory();
873
+ const started = await client.startPlayRun({
874
+ name,
875
+ ...(options?.revisionId ? { revisionId: options.revisionId } : {}),
876
+ input: input as Record<string, unknown>,
877
+ });
878
+ return new DeeplinePlayJobImpl<TOutput>(client, started.workflowId);
879
+ },
880
+ async runSync(
881
+ input: TInput,
882
+ options?: { revisionId?: string },
883
+ ): Promise<TOutput> {
884
+ const job = await this.run(input, options);
885
+ return job.get();
886
+ },
887
+ };
888
+ }
889
+
890
+ /**
891
+ * High-level SDK context with tool shortcuts and play handles.
892
+ *
893
+ * Created by {@link Deepline.connect}. Wraps a {@link DeeplineClient} with
894
+ * a friendlier API for common operations.
895
+ *
896
+ * @example
897
+ * ```typescript
898
+ * const ctx = await Deepline.connect();
899
+ *
900
+ * // Tools
901
+ * const tools = await ctx.tools.list();
902
+ * const result = await ctx.tools.execute({
903
+ * tool: 'test_company_search',
904
+ * input: { domain: 'stripe.com' },
905
+ * });
906
+ *
907
+ * // Plays
908
+ * const job = await ctx.play('email-waterfall').run({ domain: 'stripe.com' });
909
+ * const output = await job.get();
910
+ * ```
911
+ */
912
+ export class DeeplineContext {
913
+ private readonly client: DeeplineClient;
914
+
915
+ constructor(options?: DeeplineClientOptions) {
916
+ this.client = new DeeplineClient(options);
917
+ }
918
+
919
+ /**
920
+ * Tool operations namespace.
921
+ *
922
+ * @example
923
+ * ```typescript
924
+ * const tools = await ctx.tools.list();
925
+ * const meta = await ctx.tools.get('apollo_people_search');
926
+ * const result = await ctx.tools.execute({
927
+ * tool: 'test_company_search',
928
+ * input: { domain: 'stripe.com' },
929
+ * });
930
+ * const rows = result.tryList({ listExtractorPaths: ['people'] });
931
+ * const email = result.getEmail();
932
+ * ```
933
+ */
934
+ get tools() {
935
+ return {
936
+ /** List all available tools. */
937
+ list: (): Promise<ToolDefinition[]> => this.client.listTools(),
938
+ /** Get detailed metadata for a tool. */
939
+ get: (toolId: string): Promise<ToolMetadata> =>
940
+ this.client.getTool(toolId),
941
+ /** Execute a tool and return an ergonomic result wrapper. */
942
+ execute: async (
943
+ request: SdkToolExecutionRequest,
944
+ ): Promise<ToolCallResult> =>
945
+ createToolCallResult(
946
+ await this.client.executeTool(request.tool, request.input),
947
+ ),
948
+ };
949
+ }
950
+
951
+ get plays() {
952
+ return {
953
+ list: () => this.client.listPlays(),
954
+ get: <TInput = Record<string, unknown>, TOutput = unknown>(
955
+ name: string,
956
+ ) => this.play<TInput, TOutput>(name),
957
+ };
958
+ }
959
+
960
+ get prebuilt(): Record<string, PrebuiltPlayRef> {
961
+ const explicit = {
962
+ companyToContact: {
963
+ playName: 'prebuilt/company-to-contact',
964
+ name: 'prebuilt/company-to-contact',
965
+ },
966
+ personToPhone: {
967
+ playName: 'prebuilt/person-to-phone',
968
+ name: 'prebuilt/person-to-phone',
969
+ },
970
+ personToEmail: {
971
+ playName: 'prebuilt/person-to-email',
972
+ name: 'prebuilt/person-to-email',
973
+ },
974
+ personLinkedinToEmail: {
975
+ playName: 'prebuilt/person-linkedin-to-email',
976
+ name: 'prebuilt/person-linkedin-to-email',
977
+ },
978
+ } satisfies Record<string, PrebuiltPlayRef>;
979
+ return new Proxy(
980
+ {},
981
+ {
982
+ get: (_target, prop) => {
983
+ if (typeof prop !== 'string') return undefined;
984
+ if (prop in explicit) {
985
+ return explicit[prop as keyof typeof explicit];
986
+ }
987
+ const playName = prop.startsWith('prebuilt/')
988
+ ? prop
989
+ : `prebuilt/${prop}`;
990
+ return {
991
+ playName,
992
+ name: playName,
993
+ } satisfies PrebuiltPlayRef;
994
+ },
995
+ },
996
+ ) as Record<string, PrebuiltPlayRef>;
997
+ }
998
+
999
+ /**
1000
+ * Get a named play handle for remote lifecycle operations.
1001
+ *
1002
+ * @typeParam TInput - Expected input type
1003
+ * @typeParam TOutput - Expected output type
1004
+ * @param name - Play name (as registered on the server)
1005
+ * @returns Named play handle with run, versions, get, publish, etc.
1006
+ *
1007
+ * @example
1008
+ * ```typescript
1009
+ * const play = ctx.play<{ domain: string }>('email-waterfall');
1010
+ * const job = await play.run({ domain: 'stripe.com' });
1011
+ * const result = await job.get();
1012
+ * ```
1013
+ */
1014
+ play<TInput = Record<string, unknown>, TOutput = unknown>(
1015
+ name: string,
1016
+ ): DeeplineNamedPlay<TInput, TOutput> {
1017
+ return createNamedPlayHandle<TInput, TOutput>(() => this.client, name);
1018
+ }
1019
+
1020
+ async runPlay<TInput = Record<string, unknown>, TOutput = unknown>(
1021
+ playOrRef: string | PlayReferenceLike,
1022
+ input: TInput,
1023
+ ): Promise<TOutput> {
1024
+ const name =
1025
+ typeof playOrRef === 'string'
1026
+ ? playOrRef
1027
+ : (playOrRef.playName ?? playOrRef.name ?? '');
1028
+ return await this.play<TInput, TOutput>(name).runSync(input);
1029
+ }
1030
+ }
1031
+
1032
+ /**
1033
+ * Static entry point for the Deepline SDK.
1034
+ *
1035
+ * @example
1036
+ * ```typescript
1037
+ * import { Deepline } from 'deepline';
1038
+ *
1039
+ * const ctx = await Deepline.connect();
1040
+ * const tools = await ctx.tools.list();
1041
+ * const result = await ctx.tools.execute({
1042
+ * tool: 'test_company_search',
1043
+ * input: { domain: 'stripe.com' },
1044
+ * });
1045
+ * ```
1046
+ */
1047
+ export class Deepline {
1048
+ /**
1049
+ * Create a connected SDK context.
1050
+ *
1051
+ * Resolves configuration from options, environment variables, and CLI config
1052
+ * files. See {@link resolveConfig} for the resolution order.
1053
+ *
1054
+ * @param options - Optional overrides for API key, base URL, etc.
1055
+ * @returns Ready-to-use SDK context
1056
+ * @throws {@link ConfigError} if no API key can be resolved
1057
+ *
1058
+ * @example
1059
+ * ```typescript
1060
+ * // Auto-config (uses env vars / CLI auth):
1061
+ * const ctx = await Deepline.connect();
1062
+ *
1063
+ * // Explicit config:
1064
+ * const ctx2 = await Deepline.connect({
1065
+ * apiKey: 'dl_test_...',
1066
+ * baseUrl: 'http://localhost:3000',
1067
+ * });
1068
+ * ```
1069
+ */
1070
+ static async connect(
1071
+ options?: DeeplineClientOptions,
1072
+ ): Promise<DeeplineContext> {
1073
+ return new DeeplineContext(options);
1074
+ }
1075
+ }
1076
+
1077
+ export function defineInput<TInput>(
1078
+ schema: Record<string, unknown>,
1079
+ ): PlayInputContract<TInput> {
1080
+ if (!schema || typeof schema !== 'object' || Array.isArray(schema)) {
1081
+ throw new Error(
1082
+ 'defineInput<T>(schema) requires a JSON-schema-like object.',
1083
+ );
1084
+ }
1085
+ return { schema };
1086
+ }
1087
+
1088
+ /**
1089
+ * Define a play — a composable TypeScript workflow for the Deepline platform.
1090
+ *
1091
+ * The returned value is both:
1092
+ * 1. **A callable function** — invoked by the Temporal worker with a runtime context
1093
+ * 2. **A named play handle** — with `.run()`, `.versions()`, `.get()`, `.publish()`, etc. for remote lifecycle management
1094
+ *
1095
+ * Plays are the primary abstraction for building repeatable data pipelines.
1096
+ * They run on Temporal for durable execution with automatic retries and timeouts.
1097
+ *
1098
+ * @typeParam TInput - The input type accepted by the play
1099
+ * @typeParam TOutput - The return type of the play
1100
+ * @param name - Unique play name (used for publishing, running by name, and CLI reference)
1101
+ * @param fn - Async function that receives a {@link DeeplinePlayRuntimeContext} and typed input
1102
+ * @param bindings - Optional trigger bindings (cron schedule, webhook with HMAC)
1103
+ * @returns A {@link DefinedPlay} that is both callable and has lifecycle methods
1104
+ *
1105
+ * @example Basic play
1106
+ * ```typescript
1107
+ * import { definePlay } from 'deepline';
1108
+ *
1109
+ * export default definePlay('company-lookup', async (ctx, input: { domain: string }) => {
1110
+ * ctx.log(`Searching for ${input.domain}`);
1111
+ * const company = await ctx.tools.execute({
1112
+ * id: 'company_search',
1113
+ * tool: 'test_company_search',
1114
+ * input: { domain: input.domain },
1115
+ * description: 'Look up company details by domain.',
1116
+ * });
1117
+ * return { company: company.result };
1118
+ * });
1119
+ * ```
1120
+ *
1121
+ * @example CSV processing play
1122
+ * ```typescript
1123
+ * export default definePlay('bulk-enrich', async (ctx) => {
1124
+ * const leads = await ctx.csv('leads.csv');
1125
+ * ctx.log(`Processing ${leads.length} rows`);
1126
+ * const results = await ctx
1127
+ * .map('domain', leads)
1128
+ * .step('company', (row, ctx) =>
1129
+ * ctx.tools.execute({
1130
+ * id: 'company_search',
1131
+ * tool: 'test_company_search',
1132
+ * input: { domain: row.domain },
1133
+ * description: 'Look up company details by domain.',
1134
+ * }))
1135
+ * .run({ description: 'Enrich lead companies.' });
1136
+ * return results;
1137
+ * });
1138
+ * ```
1139
+ *
1140
+ * @example With cron binding
1141
+ * ```typescript
1142
+ * export default definePlay('daily-report', async (ctx) => {
1143
+ * const data = await ctx.tools.execute({
1144
+ * id: 'crm_export',
1145
+ * tool: 'crm_export',
1146
+ * input: { since: 'yesterday' },
1147
+ * description: 'Export yesterday CRM records.',
1148
+ * });
1149
+ * return data;
1150
+ * }, {
1151
+ * cron: { schedule: '0 9 * * *', timezone: 'America/New_York' },
1152
+ * });
1153
+ * ```
1154
+ *
1155
+ * @example Programmatic lifecycle
1156
+ * ```typescript
1157
+ * const myPlay = definePlay('my-play', handler);
1158
+ *
1159
+ * // Get play definition:
1160
+ * const detail = await myPlay.get();
1161
+ *
1162
+ * // Run remotely:
1163
+ * const result = await myPlay.runSync({ domain: 'stripe.com' });
1164
+ *
1165
+ * // Make the current draft live:
1166
+ * await myPlay.publish();
1167
+ * ```
1168
+ */
1169
+ export function definePlay<TInput, TOutput extends PlayReturnObject>(
1170
+ config: DefinePlayConfig<TInput, TOutput>,
1171
+ ): DefinedPlay<TInput, TOutput>;
1172
+ export function definePlay<TInput, TOutput extends PlayReturnObject>(
1173
+ name: string,
1174
+ fn: (ctx: DeeplinePlayRuntimeContext, input: TInput) => Promise<TOutput>,
1175
+ bindings?: PlayBindings,
1176
+ ): DefinedPlay<TInput, TOutput>;
1177
+ export function definePlay<TInput, TOutput extends PlayReturnObject>(
1178
+ nameOrConfig: string | DefinePlayConfig<TInput, TOutput>,
1179
+ maybeFn?: (
1180
+ ctx: DeeplinePlayRuntimeContext,
1181
+ input: TInput,
1182
+ ) => Promise<TOutput>,
1183
+ maybeBindings?: PlayBindings,
1184
+ ): DefinedPlay<TInput, TOutput> {
1185
+ const config =
1186
+ typeof nameOrConfig === 'string'
1187
+ ? {
1188
+ name: nameOrConfig,
1189
+ fn: maybeFn,
1190
+ bindings: maybeBindings,
1191
+ inputSchema: undefined,
1192
+ billing: maybeBindings?.billing,
1193
+ }
1194
+ : {
1195
+ name: nameOrConfig.id,
1196
+ fn: nameOrConfig.run,
1197
+ bindings: nameOrConfig.bindings,
1198
+ inputSchema: nameOrConfig.input.schema,
1199
+ billing: nameOrConfig.billing,
1200
+ };
1201
+ const name = config.name;
1202
+ const fn = config.fn;
1203
+ const bindings = config.bindings;
1204
+ const billing = config.billing;
1205
+ const inputSchema = config.inputSchema;
1206
+ if (typeof fn !== 'function') {
1207
+ throw new Error('definePlay(...) requires an async run function.');
1208
+ }
1209
+ if (name.includes('/')) {
1210
+ throw new Error(
1211
+ 'definePlay(name, ...) play names cannot contain "/". Slash is reserved for qualified references like "prebuilt/example" or "self/example".',
1212
+ );
1213
+ }
1214
+ const normalizedName = name
1215
+ .trim()
1216
+ .replace(/[^a-z0-9]+/gi, '_')
1217
+ .replace(/_+/g, '_')
1218
+ .replace(/^_+|_+$/g, '')
1219
+ .toLowerCase();
1220
+ if (!normalizedName) {
1221
+ throw new Error(
1222
+ 'definePlay(name, ...) requires a play name with at least one letter or number. ' +
1223
+ 'Use only letters, numbers, underscores, or hyphens.',
1224
+ );
1225
+ }
1226
+ if (normalizedName.length > 63) {
1227
+ throw new Error(
1228
+ `definePlay("${name}", ...) is too long after normalization (${normalizedName.length}/63). ` +
1229
+ `Shorten the play name to 63 characters or fewer. Normalized value: "${normalizedName}".`,
1230
+ );
1231
+ }
1232
+
1233
+ const metadata: PlayMetadata = {
1234
+ name,
1235
+ ...(bindings ? { bindings } : {}),
1236
+ ...(inputSchema ? { inputSchema } : {}),
1237
+ ...(billing ? { billing } : {}),
1238
+ };
1239
+ const play = fn as DefinedPlay<TInput, TOutput>;
1240
+
1241
+ Object.defineProperty(play, PLAY_METADATA_SYMBOL, {
1242
+ value: metadata,
1243
+ enumerable: false,
1244
+ configurable: false,
1245
+ writable: false,
1246
+ });
1247
+
1248
+ Object.defineProperty(play, 'playName', {
1249
+ value: name,
1250
+ enumerable: true,
1251
+ configurable: false,
1252
+ writable: false,
1253
+ });
1254
+
1255
+ Object.defineProperty(play, 'bindings', {
1256
+ value: bindings,
1257
+ enumerable: true,
1258
+ configurable: false,
1259
+ writable: false,
1260
+ });
1261
+
1262
+ const handle = createNamedPlayHandle<TInput, TOutput>(
1263
+ () => new DeeplineClient(),
1264
+ name,
1265
+ );
1266
+ for (const key of [
1267
+ 'name',
1268
+ 'get',
1269
+ 'runs',
1270
+ 'versions',
1271
+ 'publish',
1272
+ 'run',
1273
+ 'runSync',
1274
+ ] as const) {
1275
+ Object.defineProperty(play, key, {
1276
+ value: handle[key],
1277
+ enumerable: false,
1278
+ configurable: false,
1279
+ writable: false,
1280
+ });
1281
+ }
1282
+
1283
+ return play;
1284
+ }
1285
+
1286
+ /**
1287
+ * Alias for {@link definePlay}. Workflows and plays share the same public
1288
+ * Deepline SDK contract; the selected execution profile decides whether the
1289
+ * run is backed by local Node, Temporal/Daytona, or Cloudflare Dynamic
1290
+ * Workflows.
1291
+ */
1292
+ export const defineWorkflow = definePlay;
1293
+
1294
+ /**
1295
+ * Extract play metadata from a value that may be a defined play.
1296
+ *
1297
+ * Used internally by the CLI and bundler to detect `definePlay()` exports
1298
+ * and extract the play name and bindings.
1299
+ *
1300
+ * @param value - Any value (typically a module's default export)
1301
+ * @returns Play metadata if the value is a defined play, `null` otherwise
1302
+ *
1303
+ * @example
1304
+ * ```typescript
1305
+ * import { getDefinedPlayMetadata } from 'deepline';
1306
+ *
1307
+ * const mod = await import('./my-play.play.ts');
1308
+ * const meta = getDefinedPlayMetadata(mod.default);
1309
+ * if (meta) {
1310
+ * console.log(`Play name: ${meta.name}`);
1311
+ * console.log(`Bindings:`, meta.bindings);
1312
+ * }
1313
+ * ```
1314
+ */
1315
+ export function getDefinedPlayMetadata(value: unknown): PlayMetadata | null {
1316
+ if (typeof value !== 'function') {
1317
+ return null;
1318
+ }
1319
+ const metadata = (value as unknown as Record<PropertyKey, unknown>)[
1320
+ PLAY_METADATA_SYMBOL
1321
+ ];
1322
+ if (!metadata || typeof metadata !== 'object') {
1323
+ return null;
1324
+ }
1325
+ const candidate = metadata as PlayMetadata;
1326
+ if (!candidate.name || typeof candidate.name !== 'string') {
1327
+ return null;
1328
+ }
1329
+ return candidate;
1330
+ }