deepline 0.1.0 → 0.1.1

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,1188 @@
1
+ /**
2
+ * The main API client for interacting with the Deepline platform.
3
+ *
4
+ * `DeeplineClient` is the low-level workhorse — it maps 1:1 to the REST API
5
+ * and handles authentication, retries, and response parsing. For a higher-level
6
+ * interface with named play handles and tool shortcuts, use {@link Deepline.connect}
7
+ * or {@link DeeplineContext} instead.
8
+ *
9
+ * ## Quick start
10
+ *
11
+ * ```typescript
12
+ * import { DeeplineClient } from 'deepline';
13
+ *
14
+ * const client = new DeeplineClient();
15
+ *
16
+ * // List available tools
17
+ * const tools = await client.listTools();
18
+ *
19
+ * // Execute a tool
20
+ * const result = await client.executeTool('test_company_search', { domain: 'stripe.com' });
21
+ *
22
+ * // Run a play end-to-end
23
+ * const playResult = await client.runPlay(bundledCode, null, 'my-play', {
24
+ * onProgress: (status) => console.log(status.progress?.logs),
25
+ * });
26
+ * ```
27
+ *
28
+ * ## Configuration
29
+ *
30
+ * All options are optional — the client resolves API keys and URLs automatically
31
+ * from environment variables and CLI config files. See {@link resolveConfig} for
32
+ * the full resolution order.
33
+ *
34
+ * @module
35
+ */
36
+ import { resolveConfig } from './config.js';
37
+ import { HttpClient } from './http.js';
38
+ import type {
39
+ DeeplineClientOptions,
40
+ ResolvedConfig,
41
+ PlayRevisionSummary,
42
+ PlayRunListItem,
43
+ PlayDetail,
44
+ PlayCheckResult,
45
+ PlayRunResult,
46
+ PlayRunStart,
47
+ PlayStatus,
48
+ PlayLiveEvent,
49
+ PlayListItem,
50
+ PlayDescription,
51
+ StopPlayRunResult,
52
+ ClearPlayHistoryRequest,
53
+ ClearPlayHistoryResult,
54
+ PublishPlayVersionRequest,
55
+ PublishPlayVersionResult,
56
+ StartPlayRunRequest,
57
+ DeletePlayResult,
58
+ ToolDefinition,
59
+ ToolMetadata,
60
+ CustomerDbQueryResult,
61
+ } from './types.js';
62
+ import type { PlayStagedFileRef } from './plays/local-file-discovery.js';
63
+ import type { PlayCompilerManifest } from '../../shared_libs/plays/compiler-manifest.js';
64
+
65
+ const TERMINAL_PLAY_STATUSES = new Set(['completed', 'failed', 'cancelled']);
66
+
67
+ function normalizePlayStatus(raw: Record<string, unknown>): PlayStatus {
68
+ const status =
69
+ typeof raw.status === 'string'
70
+ ? raw.status
71
+ : typeof raw.temporalStatus === 'string'
72
+ ? mapLegacyTemporalStatus(raw.temporalStatus)
73
+ : 'running';
74
+ const runId =
75
+ typeof raw.runId === 'string'
76
+ ? raw.runId
77
+ : typeof raw.workflowId === 'string'
78
+ ? raw.workflowId
79
+ : '';
80
+ return {
81
+ ...(raw as unknown as Omit<PlayStatus, 'runId' | 'status'>),
82
+ runId,
83
+ status: status as PlayStatus['status'],
84
+ };
85
+ }
86
+
87
+ function mapLegacyTemporalStatus(status: string): PlayStatus['status'] {
88
+ switch (status.trim().toUpperCase()) {
89
+ case 'PENDING':
90
+ return 'queued';
91
+ case 'COMPLETED':
92
+ return 'completed';
93
+ case 'FAILED':
94
+ return 'failed';
95
+ case 'CANCELLED':
96
+ case 'TERMINATED':
97
+ case 'TIMED_OUT':
98
+ return 'cancelled';
99
+ case 'RUNNING':
100
+ default:
101
+ return 'running';
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Low-level client for the Deepline REST API.
107
+ *
108
+ * Provides typed methods for every API endpoint: tools, plays, auth, and health.
109
+ * Handles authentication, retries, and localhost failover automatically.
110
+ *
111
+ * @example
112
+ * ```typescript
113
+ * import { DeeplineClient } from 'deepline';
114
+ *
115
+ * // Zero-config (uses env vars / CLI auth):
116
+ * const client = new DeeplineClient();
117
+ *
118
+ * // Explicit config:
119
+ * const client2 = new DeeplineClient({
120
+ * apiKey: 'dl_test_...',
121
+ * baseUrl: 'http://localhost:3000',
122
+ * });
123
+ * ```
124
+ */
125
+ export class DeeplineClient {
126
+ private readonly http: HttpClient;
127
+ private readonly config: ResolvedConfig;
128
+
129
+ /**
130
+ * @param options - Optional overrides for API key, base URL, timeout, and retries.
131
+ * @throws {@link ConfigError} if no API key can be resolved from any source.
132
+ */
133
+ constructor(options?: DeeplineClientOptions) {
134
+ this.config = resolveConfig(options);
135
+ this.http = new HttpClient(this.config);
136
+ }
137
+
138
+ /** The resolved base URL this client is targeting (e.g. `"http://localhost:3000"`). */
139
+ get baseUrl(): string {
140
+ return this.config.baseUrl;
141
+ }
142
+
143
+ private compactSchema(schema: Record<string, unknown> | null | undefined) {
144
+ if (!schema) return null;
145
+ const fields = Array.isArray(schema.fields)
146
+ ? schema.fields
147
+ .map((field) =>
148
+ field && typeof field === 'object'
149
+ ? {
150
+ name: String((field as Record<string, unknown>).name ?? ''),
151
+ type: (field as Record<string, unknown>).type ?? undefined,
152
+ required:
153
+ (field as Record<string, unknown>).required ?? undefined,
154
+ }
155
+ : null,
156
+ )
157
+ .filter((field) => Boolean(field?.name))
158
+ : [];
159
+ return fields.length > 0 ? { fields } : schema;
160
+ }
161
+
162
+ private playRunCommand(name: string): string {
163
+ return `deepline plays run ${name} --input '{...}' --watch`;
164
+ }
165
+
166
+ private summarizePlayListItem(
167
+ play: PlayListItem,
168
+ options?: { compact?: boolean },
169
+ ): PlayDescription {
170
+ const aliases = play.aliases?.length ? play.aliases : [play.name];
171
+ const runCommand = this.playRunCommand(play.name);
172
+ return {
173
+ name: play.name,
174
+ ...(play.reference ? { reference: play.reference } : {}),
175
+ ...(play.displayName ? { displayName: play.displayName } : {}),
176
+ origin: play.origin,
177
+ ownerType: play.ownerType,
178
+ canEdit: play.canEdit,
179
+ canClone: play.canClone,
180
+ aliases,
181
+ inputSchema: options?.compact
182
+ ? this.compactSchema(play.inputSchema)
183
+ : (play.inputSchema ?? null),
184
+ outputSchema: options?.compact
185
+ ? this.compactSchema(play.outputSchema)
186
+ : (play.outputSchema ?? null),
187
+ runCommand,
188
+ examples: [runCommand],
189
+ currentPublishedVersion: play.currentPublishedVersion ?? null,
190
+ isDraftDirty: play.isDraftDirty,
191
+ };
192
+ }
193
+
194
+ private summarizePlayDetail(
195
+ detail: PlayDetail,
196
+ options?: { compact?: boolean },
197
+ ): PlayDescription {
198
+ const play = detail.play;
199
+ return {
200
+ ...this.summarizePlayListItem(play, options),
201
+ currentPublishedVersion:
202
+ play.currentPublishedVersion ?? play.liveRevision?.version ?? null,
203
+ latestRunId: play.latestRunId ?? detail.latestRuns[0]?.workflowId ?? null,
204
+ };
205
+ }
206
+
207
+ // ——————————————————————————————————————————————————————————
208
+ // Tools
209
+ // ——————————————————————————————————————————————————————————
210
+
211
+ /**
212
+ * List all available tools.
213
+ *
214
+ * Returns tool definitions including ID, provider, description, input/output schemas,
215
+ * and list extractor paths for automatic CSV conversion.
216
+ *
217
+ * @returns Array of tool definitions
218
+ *
219
+ * @example
220
+ * ```typescript
221
+ * const tools = await client.listTools();
222
+ * const searchTools = tools.filter(t => t.categories.includes('search'));
223
+ * console.log(`Found ${searchTools.length} search tools`);
224
+ * ```
225
+ */
226
+ async listTools(): Promise<ToolDefinition[]> {
227
+ const res = await this.http.get<{ tools: ToolDefinition[] }>(
228
+ '/api/v2/tools',
229
+ );
230
+ return res.tools;
231
+ }
232
+
233
+ /**
234
+ * Get detailed metadata for a single tool.
235
+ *
236
+ * Returns everything from {@link ToolDefinition} plus pricing info, sample
237
+ * inputs/outputs, failure modes, and cost estimates.
238
+ *
239
+ * @param toolId - Tool identifier (e.g. `"apollo_people_search"`)
240
+ * @returns Full tool metadata
241
+ *
242
+ * @example
243
+ * ```typescript
244
+ * const meta = await client.getTool('apollo_people_search');
245
+ * console.log(`Cost: ${meta.estimatedCreditsRange} credits`);
246
+ * console.log(`Input schema:`, meta.inputSchema);
247
+ * ```
248
+ */
249
+ async getTool(toolId: string): Promise<ToolMetadata> {
250
+ return this.http.request<ToolMetadata>(
251
+ `/api/v2/integrations/${encodeURIComponent(toolId)}/get`,
252
+ {
253
+ method: 'GET',
254
+ headers: {
255
+ 'x-deepline-tool-meta-only': '1',
256
+ },
257
+ },
258
+ );
259
+ }
260
+
261
+ /**
262
+ * Execute a tool and return the extracted result.
263
+ *
264
+ * Sends the input payload to the tool and returns the `.result` field from the
265
+ * response. For the full response envelope (including job_id, credits, etc.),
266
+ * use {@link executeToolRaw}.
267
+ *
268
+ * @param toolId - Tool identifier (e.g. `"test_company_search"`)
269
+ * @param input - Tool-specific input parameters
270
+ * @returns The tool's output (shape varies by tool)
271
+ * @throws {@link DeeplineError} if the tool execution fails
272
+ *
273
+ * @example
274
+ * ```typescript
275
+ * const company = await client.executeTool('test_company_search', {
276
+ * domain: 'stripe.com',
277
+ * });
278
+ * console.log(company); // { name: "Stripe", industry: "Financial Services", ... }
279
+ * ```
280
+ */
281
+ async executeTool(
282
+ toolId: string,
283
+ input: Record<string, unknown>,
284
+ ): Promise<unknown> {
285
+ const res = await this.http.post<{ result: unknown }>(
286
+ `/api/v2/integrations/${encodeURIComponent(toolId)}/execute`,
287
+ { payload: input },
288
+ );
289
+ return res.result ?? res;
290
+ }
291
+
292
+ /**
293
+ * Execute a tool and return the full response envelope.
294
+ *
295
+ * Unlike {@link executeTool}, this returns the complete API response including
296
+ * `job_id`, `status`, `credits`, and the raw `result` object.
297
+ *
298
+ * @param toolId - Tool identifier
299
+ * @param input - Tool-specific input parameters
300
+ * @returns Full response with job metadata and result
301
+ *
302
+ * @example
303
+ * ```typescript
304
+ * const raw = await client.executeToolRaw('test_company_search', { domain: 'stripe.com' });
305
+ * console.log(`Job: ${raw.job_id}, Credits: ${raw.credits}`);
306
+ * console.log(`Result:`, raw.result);
307
+ * ```
308
+ */
309
+ async executeToolRaw(
310
+ toolId: string,
311
+ input: Record<string, unknown>,
312
+ ): Promise<Record<string, unknown>> {
313
+ return this.http.post<Record<string, unknown>>(
314
+ `/api/v2/integrations/${encodeURIComponent(toolId)}/execute`,
315
+ { payload: input },
316
+ );
317
+ }
318
+
319
+ async queryCustomerDb(input: {
320
+ sql: string;
321
+ maxRows?: number;
322
+ }): Promise<CustomerDbQueryResult> {
323
+ return this.http.post<CustomerDbQueryResult>('/api/v2/db/query', {
324
+ sql: input.sql,
325
+ ...(input.maxRows ? { max_rows: input.maxRows } : {}),
326
+ });
327
+ }
328
+
329
+ // ——————————————————————————————————————————————————————————
330
+ // Plays — submission and lifecycle
331
+ // ——————————————————————————————————————————————————————————
332
+
333
+ /**
334
+ * Start a play run.
335
+ *
336
+ * Internal/advanced primitive. For normal callers, prefer the public
337
+ * entrypoints: the CLI, {@link Deepline.connect}, {@link submitPlay},
338
+ * or {@link runPlay}.
339
+ *
340
+ * Supported invocation surfaces intentionally share this same run contract:
341
+ * `deepline play run`, repo scripts such as `bun run deepline -- play run`,
342
+ * SDK context calls like `Deepline.connect().play(name).run()`, and direct
343
+ * `POST /api/v2/plays/run` calls all return a workflow/run id. The completed
344
+ * output is always retrievable from `getPlayStatus(runId).result` (or from
345
+ * `PlayJob.get()` for SDK context calls). Execution logs live under
346
+ * `progress.logs`; they are not part of the user output object.
347
+ *
348
+ * @param request - Play run configuration (name, code, input, etc.)
349
+ * @returns Workflow metadata including the `workflowId` for status polling
350
+ *
351
+ * @example
352
+ * ```typescript
353
+ * // Run a live play by name:
354
+ * const started = await client.startPlayRun({
355
+ * name: 'email-waterfall',
356
+ * input: { linkedin_url: 'https://linkedin.com/in/jdoe', domain: 'acme.com' },
357
+ * });
358
+ * console.log(`Workflow: ${started.workflowId}`);
359
+ *
360
+ * // Run an ad hoc artifact-backed play:
361
+ * const started2 = await client.startPlayRun({
362
+ * artifactStorageKey: 'plays/v1/orgs/acme/plays/my-play/artifacts/playgraph_abc123.json',
363
+ * });
364
+ * ```
365
+ */
366
+ async startPlayRun(request: StartPlayRunRequest): Promise<PlayRunStart> {
367
+ return this.http.post<PlayRunStart>('/api/v2/plays/run', {
368
+ ...(request.name ? { name: request.name } : {}),
369
+ ...(request.revisionId ? { revisionId: request.revisionId } : {}),
370
+ ...(request.artifactStorageKey
371
+ ? { artifactStorageKey: request.artifactStorageKey }
372
+ : {}),
373
+ ...(request.sourceCode ? { sourceCode: request.sourceCode } : {}),
374
+ ...('staticPipeline' in request
375
+ ? { staticPipeline: request.staticPipeline }
376
+ : {}),
377
+ ...(request.artifactHash ? { artifactHash: request.artifactHash } : {}),
378
+ ...(request.graphHash ? { graphHash: request.graphHash } : {}),
379
+ ...(request.runtimeArtifact
380
+ ? { runtimeArtifact: request.runtimeArtifact }
381
+ : {}),
382
+ ...(request.compilerManifest
383
+ ? { compilerManifest: request.compilerManifest }
384
+ : {}),
385
+ ...(request.inputFileUpload
386
+ ? { inputFileUpload: request.inputFileUpload }
387
+ : {}),
388
+ ...(request.packagedFileUploads?.length
389
+ ? { packagedFileUploads: request.packagedFileUploads }
390
+ : {}),
391
+ ...(request.input ? { input: request.input } : {}),
392
+ ...(request.inputFile ? { inputFile: request.inputFile } : {}),
393
+ ...(request.packagedFiles?.length
394
+ ? { packagedFiles: request.packagedFiles }
395
+ : {}),
396
+ ...(request.force ? { force: true } : {}),
397
+ ...(typeof request.waitForCompletionMs === 'number'
398
+ ? { waitForCompletionMs: request.waitForCompletionMs }
399
+ : {}),
400
+ // Profile selection is the API's job, not the CLI's. The server
401
+ // hardcodes workers_edge as the default; tests that want a
402
+ // different profile pass `request.profile` explicitly.
403
+ ...(request.profile ? { profile: request.profile } : {}),
404
+ });
405
+ }
406
+
407
+ async *startPlayRunStream(
408
+ request: StartPlayRunRequest,
409
+ options?: { signal?: AbortSignal },
410
+ ): AsyncGenerator<PlayLiveEvent> {
411
+ const body = {
412
+ ...(request.name ? { name: request.name } : {}),
413
+ ...(request.revisionId ? { revisionId: request.revisionId } : {}),
414
+ ...(request.artifactStorageKey
415
+ ? { artifactStorageKey: request.artifactStorageKey }
416
+ : {}),
417
+ ...(request.sourceCode ? { sourceCode: request.sourceCode } : {}),
418
+ ...('staticPipeline' in request
419
+ ? { staticPipeline: request.staticPipeline }
420
+ : {}),
421
+ ...(request.artifactHash ? { artifactHash: request.artifactHash } : {}),
422
+ ...(request.graphHash ? { graphHash: request.graphHash } : {}),
423
+ ...(request.runtimeArtifact
424
+ ? { runtimeArtifact: request.runtimeArtifact }
425
+ : {}),
426
+ ...(request.compilerManifest
427
+ ? { compilerManifest: request.compilerManifest }
428
+ : {}),
429
+ ...(request.inputFileUpload
430
+ ? { inputFileUpload: request.inputFileUpload }
431
+ : {}),
432
+ ...(request.packagedFileUploads?.length
433
+ ? { packagedFileUploads: request.packagedFileUploads }
434
+ : {}),
435
+ ...(request.input ? { input: request.input } : {}),
436
+ ...(request.inputFile ? { inputFile: request.inputFile } : {}),
437
+ ...(request.packagedFiles?.length
438
+ ? { packagedFiles: request.packagedFiles }
439
+ : {}),
440
+ ...(request.force ? { force: true } : {}),
441
+ ...(request.profile ? { profile: request.profile } : {}),
442
+ };
443
+ for await (const event of this.http.streamSse<PlayLiveEvent>(
444
+ '/api/v2/plays/run?stream=true',
445
+ {
446
+ method: 'POST',
447
+ body,
448
+ signal: options?.signal,
449
+ },
450
+ )) {
451
+ if (event.scope === 'play') {
452
+ yield event;
453
+ }
454
+ }
455
+ }
456
+
457
+ /**
458
+ * Register a bundled play artifact.
459
+ *
460
+ * Internal/advanced primitive used by packaging flows. Public callers should
461
+ * prefer the CLI, {@link submitPlay}, or {@link runPlay}.
462
+ */
463
+ async registerPlayArtifact(input: {
464
+ name: string;
465
+ sourceCode: string;
466
+ artifact: Record<string, unknown>;
467
+ compilerManifest?: PlayCompilerManifest;
468
+ publish?: boolean;
469
+ ownerType?: 'org' | 'deepline';
470
+ scope?: 'org' | 'system';
471
+ userId?: string;
472
+ }): Promise<{
473
+ success?: boolean;
474
+ name?: string;
475
+ artifactStorageKey: string;
476
+ artifactMetadata?: Record<string, unknown> | null;
477
+ staticPipeline?: unknown;
478
+ definitionId?: string | null;
479
+ revisionId?: string | null;
480
+ version?: number | null;
481
+ liveVersion?: number | null;
482
+ triggerMetadata?: unknown;
483
+ triggerBindings?: unknown;
484
+ }> {
485
+ const compilerManifest =
486
+ input.compilerManifest ??
487
+ (await this.compilePlayManifest({
488
+ name: input.name,
489
+ sourceCode: input.sourceCode,
490
+ artifact: input.artifact,
491
+ }));
492
+ return this.http.post('/api/v2/plays/artifacts', {
493
+ ...input,
494
+ compilerManifest,
495
+ });
496
+ }
497
+
498
+ async registerPlayArtifacts(
499
+ artifacts: Array<{
500
+ name: string;
501
+ sourceCode: string;
502
+ artifact: Record<string, unknown>;
503
+ compilerManifest?: PlayCompilerManifest;
504
+ publish?: boolean;
505
+ ownerType?: 'org' | 'deepline';
506
+ scope?: 'org' | 'system';
507
+ userId?: string;
508
+ }>,
509
+ ): Promise<{
510
+ success: boolean;
511
+ artifacts: Array<{
512
+ success?: boolean;
513
+ name?: string;
514
+ artifactStorageKey: string;
515
+ artifactMetadata?: Record<string, unknown> | null;
516
+ staticPipeline?: unknown;
517
+ definitionId?: string | null;
518
+ revisionId?: string | null;
519
+ version?: number | null;
520
+ liveVersion?: number | null;
521
+ triggerMetadata?: unknown;
522
+ triggerBindings?: unknown;
523
+ }>;
524
+ }> {
525
+ const compiledArtifacts = await Promise.all(
526
+ artifacts.map(async (artifact) => ({
527
+ ...artifact,
528
+ compilerManifest:
529
+ artifact.compilerManifest ??
530
+ (await this.compilePlayManifest({
531
+ name: artifact.name,
532
+ sourceCode: artifact.sourceCode,
533
+ artifact: artifact.artifact,
534
+ })),
535
+ })),
536
+ );
537
+ return this.http.post('/api/v2/plays/artifacts', {
538
+ artifacts: compiledArtifacts,
539
+ });
540
+ }
541
+
542
+ async compilePlayManifest(input: {
543
+ name: string;
544
+ sourceCode: string;
545
+ artifact: Record<string, unknown>;
546
+ importedPlayDependencies?: PlayCompilerManifest[];
547
+ }): Promise<PlayCompilerManifest> {
548
+ const response = await this.http.post<{
549
+ compilerManifest: PlayCompilerManifest;
550
+ }>('/api/v2/plays/compile-manifest', input);
551
+ return response.compilerManifest;
552
+ }
553
+
554
+ /**
555
+ * Check a bundled play artifact against the server's current play compiler.
556
+ *
557
+ * Unlike {@link registerPlayArtifact}, this does not store the artifact,
558
+ * publish a revision, or start a run. It is the authoritative cloud validation
559
+ * path used by `deepline play check`.
560
+ */
561
+ async checkPlayArtifact(input: {
562
+ name?: string;
563
+ sourceCode: string;
564
+ artifact: Record<string, unknown>;
565
+ }): Promise<PlayCheckResult> {
566
+ return this.http.post('/api/v2/plays/check', input);
567
+ }
568
+
569
+ async startPlayRunFromBundle(input: {
570
+ name: string;
571
+ sourceCode: string;
572
+ artifact: Record<string, unknown>;
573
+ compilerManifest?: PlayCompilerManifest;
574
+ input?: Record<string, unknown>;
575
+ inputFile?: PlayStagedFileRef | null;
576
+ packagedFiles?: PlayStagedFileRef[];
577
+ force?: boolean;
578
+ }): Promise<PlayRunStart> {
579
+ const compilerManifest =
580
+ input.compilerManifest ??
581
+ (await this.compilePlayManifest({
582
+ name: input.name,
583
+ sourceCode: input.sourceCode,
584
+ artifact: input.artifact,
585
+ }));
586
+ const registeredArtifact = await this.registerPlayArtifact({
587
+ name: input.name,
588
+ sourceCode: input.sourceCode,
589
+ artifact: input.artifact,
590
+ compilerManifest,
591
+ publish: false,
592
+ });
593
+ if (!registeredArtifact.artifactStorageKey) {
594
+ throw new Error(
595
+ 'registerPlayArtifact did not return an artifactStorageKey.',
596
+ );
597
+ }
598
+
599
+ return this.startPlayRun({
600
+ name: input.name,
601
+ artifactStorageKey: registeredArtifact.artifactStorageKey,
602
+ compilerManifest,
603
+ ...(input.input ? { input: input.input } : {}),
604
+ ...(input.inputFile ? { inputFile: input.inputFile } : {}),
605
+ ...(input.packagedFiles?.length
606
+ ? { packagedFiles: input.packagedFiles }
607
+ : {}),
608
+ ...(input.force ? { force: true } : {}),
609
+ });
610
+ }
611
+
612
+ /**
613
+ * Register a bundled play artifact and start a run from the live revision.
614
+ *
615
+ * Convenience wrapper around {@link registerPlayArtifact} plus
616
+ * {@link startPlayRun}. This is the canonical file-backed path used by wrappers.
617
+ * The returned id can be passed to {@link getPlayStatus} to retrieve the same
618
+ * durable `{ result }` object that the CLI prints after `--watch` completes.
619
+ *
620
+ * @param code - Source string fallback; the bundled artifact should be passed in `options.artifact`
621
+ * @param csvPath - Path to input CSV file, or `null`
622
+ * @param name - Play name (extracted from source if omitted)
623
+ * @param options - Additional submission options
624
+ * @returns Workflow metadata with `workflowId`
625
+ *
626
+ * @example
627
+ * ```typescript
628
+ * const started = await client.submitPlay(
629
+ * originalSource,
630
+ * './leads.csv',
631
+ * 'bulk-enrich',
632
+ * { artifact: bundledArtifact, input: { limit: 100 } },
633
+ * );
634
+ * ```
635
+ */
636
+ async submitPlay(
637
+ code: string,
638
+ csvPath: string | null,
639
+ name?: string,
640
+ options?: {
641
+ sourceCode?: string;
642
+ artifact?: Record<string, unknown>;
643
+ compilerManifest?: PlayCompilerManifest;
644
+ input?: Record<string, unknown>;
645
+ inputFile?: PlayStagedFileRef | null;
646
+ packagedFiles?: PlayStagedFileRef[];
647
+ force?: boolean;
648
+ },
649
+ ): Promise<PlayRunStart> {
650
+ const runtimeInput = options?.input ? { ...options.input } : {};
651
+
652
+ if (csvPath) {
653
+ runtimeInput.file = csvPath;
654
+ }
655
+
656
+ const sourceCode = options?.sourceCode ?? code;
657
+ const artifact = options?.artifact;
658
+ if (!name?.trim()) {
659
+ throw new Error('submitPlay requires a play name.');
660
+ }
661
+ if (!artifact) {
662
+ throw new Error('submitPlay requires a bundled play artifact.');
663
+ }
664
+ const compilerManifest =
665
+ options?.compilerManifest ??
666
+ (await this.compilePlayManifest({
667
+ name,
668
+ sourceCode,
669
+ artifact,
670
+ }));
671
+
672
+ const registeredArtifact = await this.registerPlayArtifact({
673
+ name,
674
+ sourceCode,
675
+ artifact,
676
+ compilerManifest,
677
+ publish: false,
678
+ });
679
+ if (!registeredArtifact.artifactStorageKey) {
680
+ throw new Error(
681
+ 'registerPlayArtifact did not return an artifactStorageKey.',
682
+ );
683
+ }
684
+
685
+ return this.startPlayRun({
686
+ name,
687
+ artifactStorageKey: registeredArtifact.artifactStorageKey,
688
+ sourceCode,
689
+ staticPipeline: registeredArtifact.staticPipeline ?? null,
690
+ artifactHash:
691
+ typeof artifact.artifactHash === 'string'
692
+ ? artifact.artifactHash
693
+ : undefined,
694
+ graphHash:
695
+ typeof artifact.graphHash === 'string' ? artifact.graphHash : undefined,
696
+ runtimeArtifact: artifact,
697
+ compilerManifest,
698
+ ...(Object.keys(runtimeInput).length > 0 ? { input: runtimeInput } : {}),
699
+ ...(options?.inputFile ? { inputFile: options.inputFile } : {}),
700
+ ...(options?.packagedFiles?.length
701
+ ? { packagedFiles: options.packagedFiles }
702
+ : {}),
703
+ ...(options?.force ? { force: true } : {}),
704
+ });
705
+ }
706
+
707
+ /**
708
+ * Upload files to the staging area for use in play runs.
709
+ *
710
+ * Internal/advanced primitive used by packaging flows. Public callers should
711
+ * prefer the CLI, {@link submitPlay}, or {@link runPlay}.
712
+ *
713
+ * Staged files are referenced by their returned {@link PlayStagedFileRef}
714
+ * in subsequent {@link startPlayRun} calls via `inputFile` or `packagedFiles`.
715
+ *
716
+ * @param files - Array of files to stage (base64-encoded content)
717
+ * @returns Array of staged file references
718
+ *
719
+ * @example
720
+ * ```typescript
721
+ * const staged = await client.stagePlayFiles([{
722
+ * logicalPath: 'data/leads.csv',
723
+ * contentBase64: Buffer.from(csvContent).toString('base64'),
724
+ * contentHash: sha256(csvContent),
725
+ * contentType: 'text/csv',
726
+ * bytes: csvContent.length,
727
+ * }]);
728
+ * // Use staged[0] as inputFile in startPlayRun
729
+ * ```
730
+ */
731
+ async stagePlayFiles(
732
+ files: Array<{
733
+ logicalPath: string;
734
+ contentBase64: string;
735
+ contentHash: string;
736
+ contentType: string;
737
+ bytes: number;
738
+ }>,
739
+ ): Promise<PlayStagedFileRef[]> {
740
+ const response = await this.http.post<{ files: PlayStagedFileRef[] }>(
741
+ '/api/v2/plays/files/stage',
742
+ { files },
743
+ );
744
+ return response.files;
745
+ }
746
+
747
+ async resolveStagedPlayFiles(
748
+ files: Array<{
749
+ logicalPath: string;
750
+ contentHash: string;
751
+ contentType: string;
752
+ bytes: number;
753
+ }>,
754
+ ): Promise<{
755
+ files: PlayStagedFileRef[];
756
+ missing: Array<{ logicalPath: string; contentHash: string }>;
757
+ }> {
758
+ return this.http.post<{
759
+ files: PlayStagedFileRef[];
760
+ missing: Array<{ logicalPath: string; contentHash: string }>;
761
+ }>('/api/v2/plays/files/stage', { files });
762
+ }
763
+
764
+ // ——————————————————————————————————————————————————————————
765
+ // Plays — status and monitoring
766
+ // ——————————————————————————————————————————————————————————
767
+
768
+ /**
769
+ * Get the current status of a play execution.
770
+ *
771
+ * Internal/advanced primitive. Public callers should usually prefer
772
+ * {@link runPlay}, {@link PlayJob.get}, or `deepline play run --watch`.
773
+ *
774
+ * Poll this method until `status` reaches a terminal state:
775
+ * `'completed'`, `'failed'`, or `'cancelled'`.
776
+ *
777
+ * @param workflowId - Play-run id from {@link startPlayRun}
778
+ * @returns Current status with progress logs and partial results
779
+ *
780
+ * @example
781
+ * ```typescript
782
+ * const status = await client.getPlayStatus('play-abc123');
783
+ * console.log(`Status: ${status.status}`);
784
+ * console.log(`Logs: ${status.progress?.logs.length ?? 0} lines`);
785
+ * ```
786
+ */
787
+ async getPlayStatus(workflowId: string): Promise<PlayStatus> {
788
+ const response = await this.http.get<Record<string, unknown>>(
789
+ `/api/v2/plays/run/${encodeURIComponent(workflowId)}`,
790
+ );
791
+ return normalizePlayStatus(response);
792
+ }
793
+
794
+ /**
795
+ * Get the lightweight tail-polling status for a play execution.
796
+ *
797
+ * This is intentionally smaller than {@link getPlayStatus}: it returns the
798
+ * fields needed for CLI log tailing while the run is in flight, without
799
+ * forcing the API to rebuild final result views on every poll. Call
800
+ * {@link getPlayStatus} once after a terminal state for the full result.
801
+ */
802
+ async getPlayTailStatus(
803
+ workflowId: string,
804
+ options?: {
805
+ afterLogIndex?: number;
806
+ waitMs?: number;
807
+ terminalOnly?: boolean;
808
+ },
809
+ ): Promise<PlayStatus> {
810
+ const params = new URLSearchParams({ mode: 'tail' });
811
+ if (typeof options?.afterLogIndex === 'number') {
812
+ params.set('afterLogIndex', String(options.afterLogIndex));
813
+ }
814
+ if (typeof options?.waitMs === 'number') {
815
+ params.set('waitMs', String(options.waitMs));
816
+ }
817
+ if (options?.terminalOnly) {
818
+ params.set('terminalOnly', 'true');
819
+ }
820
+ const response = await this.http.get<Record<string, unknown>>(
821
+ `/api/v2/plays/run/${encodeURIComponent(workflowId)}?${params.toString()}`,
822
+ );
823
+ return normalizePlayStatus(response);
824
+ }
825
+
826
+ /**
827
+ * Stream semantic play-run events using the same SSE feed as the dashboard.
828
+ *
829
+ * Consumers should still keep a polling fallback: SSE is the fast live-update
830
+ * transport, while the status endpoints remain the authoritative recovery path.
831
+ */
832
+ async *streamPlayRunEvents(
833
+ workflowId: string,
834
+ options?: {
835
+ signal?: AbortSignal;
836
+ lastEventId?: string;
837
+ mode?: 'cli' | 'ui';
838
+ },
839
+ ): AsyncGenerator<PlayLiveEvent> {
840
+ const headers =
841
+ options?.lastEventId && options.lastEventId.trim()
842
+ ? { 'Last-Event-ID': options.lastEventId.trim() }
843
+ : undefined;
844
+ const params = new URLSearchParams({ stream: 'true' });
845
+ params.set('mode', options?.mode ?? 'cli');
846
+ for await (const event of this.http.streamSse<PlayLiveEvent>(
847
+ `/api/v2/plays/run/${encodeURIComponent(workflowId)}?${params.toString()}`,
848
+ { signal: options?.signal, headers },
849
+ )) {
850
+ if (event.scope === 'play') {
851
+ yield event;
852
+ }
853
+ }
854
+ }
855
+
856
+ /**
857
+ * Cancel a running play execution.
858
+ *
859
+ * Sends a stop request for the run.
860
+ *
861
+ * @param workflowId - Temporal workflow ID to cancel
862
+ *
863
+ * @example
864
+ * ```typescript
865
+ * await client.cancelPlay('play-abc123');
866
+ * ```
867
+ */
868
+ async cancelPlay(workflowId: string): Promise<void> {
869
+ await this.http.request(
870
+ `/api/v2/plays/run/${encodeURIComponent(workflowId)}/stop`,
871
+ { method: 'POST' },
872
+ );
873
+ }
874
+
875
+ /**
876
+ * Stop a running play execution, including open HITL waits.
877
+ *
878
+ * @param workflowId - Temporal workflow ID to stop
879
+ * @param options.reason - Optional audit/debug reason
880
+ */
881
+ async stopPlay(
882
+ workflowId: string,
883
+ options?: { reason?: string },
884
+ ): Promise<StopPlayRunResult> {
885
+ return this.http.post<StopPlayRunResult>(
886
+ `/api/v2/plays/run/${encodeURIComponent(workflowId)}/stop`,
887
+ options?.reason ? { reason: options.reason } : {},
888
+ );
889
+ }
890
+
891
+ /**
892
+ * List recent runs for a named play.
893
+ *
894
+ * Returns runs sorted by start time (newest first), including workflow IDs,
895
+ * status, timestamps, and metadata.
896
+ *
897
+ * @param playName - The play name to query
898
+ * @returns Array of run summaries (empty array if no runs exist)
899
+ *
900
+ * @example
901
+ * ```typescript
902
+ * const runs = await client.listPlayRuns('email-waterfall');
903
+ * for (const run of runs) {
904
+ * console.log(`${run.workflowId}: ${run.status} (${run.executionTime})`);
905
+ * }
906
+ * ```
907
+ */
908
+ async listPlayRuns(playName: string): Promise<PlayRunListItem[]> {
909
+ const encodedName = encodeURIComponent(playName);
910
+ const response = await this.http.get<{ runs: PlayRunListItem[] }>(
911
+ `/api/v2/plays/${encodedName}/runs`,
912
+ );
913
+ return response.runs ?? [];
914
+ }
915
+
916
+ async listPlays(): Promise<PlayListItem[]> {
917
+ const response = await this.http.get<{ plays: PlayListItem[] }>(
918
+ '/api/v2/plays',
919
+ );
920
+ return response.plays ?? [];
921
+ }
922
+
923
+ async searchPlays(options: {
924
+ query: string;
925
+ origin?: 'prebuilt' | 'owned';
926
+ compact?: boolean;
927
+ }): Promise<PlayDescription[]> {
928
+ const query = options.query.trim().toLowerCase();
929
+ const terms = query.split(/\s+/).filter(Boolean);
930
+ const plays = await this.listPlays();
931
+ return plays
932
+ .filter((play) => {
933
+ if (options.origin && (play.origin ?? 'owned') !== options.origin) {
934
+ return false;
935
+ }
936
+ const haystack = [
937
+ play.name,
938
+ play.reference,
939
+ play.displayName,
940
+ play.origin,
941
+ ...(play.aliases ?? []),
942
+ play.inputSchema ? JSON.stringify(play.inputSchema) : '',
943
+ ]
944
+ .filter(Boolean)
945
+ .join(' ')
946
+ .toLowerCase();
947
+ return terms.every((term) => haystack.includes(term));
948
+ })
949
+ .map((play) => this.summarizePlayListItem(play, options));
950
+ }
951
+
952
+ /**
953
+ * Get the full definition and state of a named play.
954
+ *
955
+ * Returns the play's revision state (draft, live), recent runs,
956
+ * sheet processing summary, and database URL.
957
+ *
958
+ * @param name - Play name
959
+ * @returns Complete play detail
960
+ *
961
+ * @example
962
+ * ```typescript
963
+ * const detail = await client.getPlay('email-waterfall');
964
+ * console.log(`Live: v${detail.play.currentPublishedVersion}`);
965
+ * console.log(`Draft dirty: ${detail.play.isDraftDirty}`);
966
+ * console.log(`Total runs: ${detail.play.runCount}`);
967
+ * ```
968
+ */
969
+ async getPlay(name: string): Promise<PlayDetail> {
970
+ const encodedName = encodeURIComponent(name);
971
+ return this.http.get<PlayDetail>(`/api/v2/plays/${encodedName}`);
972
+ }
973
+
974
+ async describePlay(
975
+ name: string,
976
+ options?: { compact?: boolean },
977
+ ): Promise<PlayDescription> {
978
+ const detail = await this.getPlay(name);
979
+ return this.summarizePlayDetail(detail, options);
980
+ }
981
+
982
+ /**
983
+ * Clear run history and durable sheet/result data for a play without deleting
984
+ * the play definition or revisions.
985
+ */
986
+ async clearPlayHistory(
987
+ name: string,
988
+ request: ClearPlayHistoryRequest = {},
989
+ ): Promise<ClearPlayHistoryResult> {
990
+ const encodedName = encodeURIComponent(name);
991
+ return this.http.post<ClearPlayHistoryResult>(
992
+ `/api/v2/plays/${encodedName}/history/clear`,
993
+ request,
994
+ );
995
+ }
996
+
997
+ /**
998
+ * List saved versions for a named play.
999
+ *
1000
+ * Returns immutable revision snapshots newest-first, including the revision
1001
+ * id needed for exact-version runs and live-version switching.
1002
+ *
1003
+ * @param name - Play name
1004
+ * @returns Version list (newest first)
1005
+ */
1006
+ async listPlayVersions(name: string): Promise<PlayRevisionSummary[]> {
1007
+ const encodedName = encodeURIComponent(name);
1008
+ const response = await this.http.get<{ versions: PlayRevisionSummary[] }>(
1009
+ `/api/v2/plays/${encodedName}/versions`,
1010
+ );
1011
+ return response.versions ?? [];
1012
+ }
1013
+
1014
+ /**
1015
+ * Make a play revision live.
1016
+ *
1017
+ * When `revisionId` is omitted, the current working revision becomes live.
1018
+ * The live version is what executes when the play is run by name without
1019
+ * specifying an explicit revision.
1020
+ *
1021
+ * @param name - Play name
1022
+ * @param request - Optional explicit revision to make live
1023
+ * @returns Result with the new live version number
1024
+ *
1025
+ * @example
1026
+ * ```typescript
1027
+ * const result = await client.publishPlayVersion('email-waterfall');
1028
+ * if (result.success) {
1029
+ * console.log(`Live v${result.liveVersion}`);
1030
+ * }
1031
+ * ```
1032
+ */
1033
+ async publishPlayVersion(
1034
+ name: string,
1035
+ request: PublishPlayVersionRequest = {},
1036
+ ): Promise<PublishPlayVersionResult> {
1037
+ const encodedName = encodeURIComponent(name);
1038
+ return this.http.post<PublishPlayVersionResult>(
1039
+ `/api/v2/plays/${encodedName}/live`,
1040
+ request,
1041
+ );
1042
+ }
1043
+
1044
+ /**
1045
+ * Delete an org-owned play definition, including its revisions, trigger
1046
+ * bindings, and local run records. Deepline prebuilt plays are read-only.
1047
+ */
1048
+ async deletePlay(name: string): Promise<DeletePlayResult> {
1049
+ const encodedName = encodeURIComponent(name);
1050
+ return this.http.delete<DeletePlayResult>(`/api/v2/plays/${encodedName}`);
1051
+ }
1052
+
1053
+ // ——————————————————————————————————————————————————————————
1054
+ // Plays — high-level orchestration
1055
+ // ——————————————————————————————————————————————————————————
1056
+
1057
+ /**
1058
+ * Run a play end-to-end: submit, poll until terminal, return result.
1059
+ *
1060
+ * This is the highest-level play execution method. It submits the play,
1061
+ * polls for status updates, and returns a structured result with logs
1062
+ * and timing. Supports cancellation via `AbortSignal`.
1063
+ *
1064
+ * @param code - Source string fallback; pass the bundled artifact in `options.artifact`
1065
+ * @param csvPath - Input CSV path, or `null`
1066
+ * @param name - Play name
1067
+ * @param options - Execution options
1068
+ * @returns Final execution result with success/failure, output, logs, and duration
1069
+ *
1070
+ * @example
1071
+ * ```typescript
1072
+ * const result = await client.runPlay(bundledCode, null, 'my-play', {
1073
+ * input: { domain: 'stripe.com' },
1074
+ * onProgress: (status) => {
1075
+ * const logs = status.progress?.logs ?? [];
1076
+ * console.log(`[${status.status}] ${logs.length} log lines`);
1077
+ * },
1078
+ * pollIntervalMs: 1000,
1079
+ * });
1080
+ *
1081
+ * if (result.success) {
1082
+ * console.log('Output:', result.result);
1083
+ * } else {
1084
+ * console.error(`Failed after ${result.durationMs}ms:`, result.error);
1085
+ * }
1086
+ * ```
1087
+ *
1088
+ * @example Cancellation
1089
+ * ```typescript
1090
+ * const controller = new AbortController();
1091
+ * setTimeout(() => controller.abort(), 30_000); // 30s timeout
1092
+ *
1093
+ * const result = await client.runPlay(code, null, 'slow-play', {
1094
+ * signal: controller.signal,
1095
+ * });
1096
+ * // result.success === false, result.error === 'Cancelled by user'
1097
+ * ```
1098
+ */
1099
+ async runPlay(
1100
+ code: string,
1101
+ csvPath: string | null,
1102
+ name?: string,
1103
+ options?: {
1104
+ /** Called on each poll iteration with the current status. */
1105
+ onProgress?: (status: PlayStatus) => void;
1106
+ /** Milliseconds between status polls. Default: `500`. */
1107
+ pollIntervalMs?: number;
1108
+ /** Abort signal — triggers cancellation and immediate return. */
1109
+ signal?: AbortSignal;
1110
+ /** Runtime input for the play function. */
1111
+ input?: Record<string, unknown>;
1112
+ sourceCode?: string;
1113
+ artifact?: Record<string, unknown>;
1114
+ compilerManifest?: PlayCompilerManifest;
1115
+ inputFile?: PlayStagedFileRef | null;
1116
+ packagedFiles?: PlayStagedFileRef[];
1117
+ /** Force-supersede any active runs for this play before starting. */
1118
+ force?: boolean;
1119
+ },
1120
+ ): Promise<PlayRunResult> {
1121
+ const { workflowId } = await this.submitPlay(code, csvPath, name, {
1122
+ input: options?.input,
1123
+ sourceCode: options?.sourceCode,
1124
+ artifact: options?.artifact,
1125
+ compilerManifest: options?.compilerManifest,
1126
+ inputFile: options?.inputFile,
1127
+ packagedFiles: options?.packagedFiles,
1128
+ force: options?.force,
1129
+ });
1130
+ const pollInterval = options?.pollIntervalMs ?? 500;
1131
+ const start = Date.now();
1132
+
1133
+ while (true) {
1134
+ if (options?.signal?.aborted) {
1135
+ await this.cancelPlay(workflowId);
1136
+ return {
1137
+ success: false,
1138
+ runId: workflowId,
1139
+ logs: [],
1140
+ durationMs: Date.now() - start,
1141
+ error: 'Cancelled by user',
1142
+ };
1143
+ }
1144
+
1145
+ const status = await this.getPlayStatus(workflowId);
1146
+ options?.onProgress?.(status);
1147
+
1148
+ if (TERMINAL_PLAY_STATUSES.has(status.status)) {
1149
+ return {
1150
+ success: status.status === 'completed',
1151
+ runId: status.runId || workflowId,
1152
+ result: status.result,
1153
+ logs: status.progress?.logs ?? [],
1154
+ durationMs: Date.now() - start,
1155
+ error:
1156
+ status.progress?.error ??
1157
+ (status.status !== 'completed' ? status.status : undefined),
1158
+ };
1159
+ }
1160
+
1161
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
1162
+ }
1163
+ }
1164
+
1165
+ // ——————————————————————————————————————————————————————————
1166
+ // Health
1167
+ // ——————————————————————————————————————————————————————————
1168
+
1169
+ /**
1170
+ * Check API connectivity and server health.
1171
+ *
1172
+ * @returns Health status with API version
1173
+ *
1174
+ * @example
1175
+ * ```typescript
1176
+ * const health = await client.health();
1177
+ * console.log(`API: ${health.status} (${health.version})`);
1178
+ * // { status: "ok", version: "v2" }
1179
+ * ```
1180
+ */
1181
+ async health(): Promise<{ status: string; version?: string }> {
1182
+ return this.http.get<{ status: string; version?: string }>(
1183
+ '/api/v2/health',
1184
+ );
1185
+ }
1186
+ }
1187
+
1188
+ export type { PlayRunListItem, PlayStatus } from './types.js';