deepline 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/config.ts
2
- import { readFileSync, existsSync } from "fs";
2
+ import { readFileSync, existsSync, mkdirSync, writeFileSync } from "fs";
3
3
  import { homedir } from "os";
4
- import { join } from "path";
4
+ import { dirname, join, resolve } from "path";
5
5
 
6
6
  // src/errors.ts
7
7
  var DeeplineError = class extends Error {
@@ -23,6 +23,7 @@ var AuthError = class extends DeeplineError {
23
23
  }
24
24
  };
25
25
  var RateLimitError = class extends DeeplineError {
26
+ /** Milliseconds to wait before retrying, from the `Retry-After` response header. Defaults to 5000. */
26
27
  retryAfterMs;
27
28
  constructor(retryAfterMs = 5e3, message) {
28
29
  super(message ?? `Rate limited. Retry after ${retryAfterMs}ms.`, 429, "RATE_LIMIT");
@@ -76,22 +77,62 @@ function parseEnvFile(filePath) {
76
77
  }
77
78
  return env;
78
79
  }
79
- function loadCliEnv(baseUrl) {
80
- const slug = baseUrlSlug(baseUrl);
81
- const envPath = join(homedir(), ".local", "deepline", slug, ".env");
80
+ function findNearestWorktreeEnv(startDir = process.cwd()) {
81
+ let current = resolve(startDir);
82
+ while (true) {
83
+ const values = parseEnvFile(join(current, ".env.worktree"));
84
+ if (Object.keys(values).length > 0) return values;
85
+ const parent = dirname(current);
86
+ if (parent === current) return {};
87
+ current = parent;
88
+ }
89
+ }
90
+ function normalizeWorktreeBaseUrl(baseUrl, worktreeEnv = findNearestWorktreeEnv()) {
91
+ const trimmed = baseUrl.trim().replace(/\/$/, "");
92
+ if (!trimmed) return trimmed;
93
+ try {
94
+ const parsed = new URL(trimmed);
95
+ if (parsed.hostname.endsWith(".localhost") && parsed.port === "1355") {
96
+ const port = worktreeEnv.WORKTREE_APP_PORT || worktreeEnv.PORT;
97
+ if (port) return `${parsed.protocol}//localhost:${port}`;
98
+ }
99
+ } catch {
100
+ }
101
+ return trimmed;
102
+ }
103
+ function resolveWorktreeBaseUrl() {
104
+ const worktreeEnv = findNearestWorktreeEnv();
105
+ const declared = worktreeEnv.DEEPLINE_API_BASE_URL || worktreeEnv.WORKTREE_PUBLIC_APP_URL || worktreeEnv.APP_URL || "";
106
+ if (declared) return normalizeWorktreeBaseUrl(declared, worktreeEnv);
107
+ const port = worktreeEnv.WORKTREE_APP_PORT || worktreeEnv.PORT || "";
108
+ return port ? `http://localhost:${port}` : "";
109
+ }
110
+ function sdkCliEnvFilePath(baseUrl) {
111
+ const home = process.env.HOME?.trim() || homedir();
112
+ return join(home, ".local", "deepline", baseUrlSlug(baseUrl || PROD_URL), ".env");
113
+ }
114
+ function loadCliEnv(baseUrl = PROD_URL) {
115
+ const envPath = sdkCliEnvFilePath(baseUrl);
82
116
  return parseEnvFile(envPath);
83
117
  }
118
+ function loadGlobalCliEnv() {
119
+ return loadCliEnv(PROD_URL);
120
+ }
84
121
  function autoDetectBaseUrl() {
122
+ const envOrigin = process.env.DEEPLINE_ORIGIN_URL?.trim();
123
+ if (envOrigin) return normalizeWorktreeBaseUrl(envOrigin);
85
124
  const envBase = process.env.DEEPLINE_API_BASE_URL?.trim();
86
- if (envBase) return envBase;
87
- const localEnvPath = join(homedir(), ".local", "deepline", "localhost-3000", ".env");
88
- if (existsSync(localEnvPath)) {
89
- return "http://localhost:3000";
90
- }
125
+ if (envBase) return normalizeWorktreeBaseUrl(envBase);
126
+ const worktreeBaseUrl = resolveWorktreeBaseUrl();
127
+ if (worktreeBaseUrl) return worktreeBaseUrl;
128
+ const globalEnv = loadGlobalCliEnv();
129
+ const globalOrigin = globalEnv.DEEPLINE_ORIGIN_URL?.trim();
130
+ if (globalOrigin) return normalizeWorktreeBaseUrl(globalOrigin);
91
131
  return PROD_URL;
92
132
  }
93
133
  function resolveConfig(options) {
94
- const baseUrl = options?.baseUrl?.trim() || autoDetectBaseUrl();
134
+ const requestedBaseUrl = options?.baseUrl?.trim() || autoDetectBaseUrl();
135
+ const baseUrl = normalizeWorktreeBaseUrl(requestedBaseUrl);
95
136
  const cliEnv = loadCliEnv(baseUrl);
96
137
  const apiKey = options?.apiKey?.trim() || process.env.DEEPLINE_API_KEY?.trim() || cliEnv.DEEPLINE_API_KEY || "";
97
138
  if (!apiKey) {
@@ -101,94 +142,214 @@ function resolveConfig(options) {
101
142
  }
102
143
  return {
103
144
  apiKey,
104
- baseUrl: baseUrl.replace(/\/$/, ""),
145
+ baseUrl,
105
146
  timeout: options?.timeout ?? DEFAULT_TIMEOUT,
106
147
  maxRetries: options?.maxRetries ?? DEFAULT_MAX_RETRIES
107
148
  };
108
149
  }
109
150
 
110
- // src/http.ts
151
+ // src/version.ts
111
152
  var SDK_VERSION = "0.1.0";
153
+ var SDK_API_CONTRACT = "2026-04-plays-v1";
154
+
155
+ // ../shared_libs/play-runtime/coordinator-headers.ts
156
+ var COORDINATOR_URL_OVERRIDE_HEADER = "x-deepline-coordinator-url";
157
+ var WORKER_CALLBACK_URL_OVERRIDE_HEADER = "x-deepline-worker-callback-url";
158
+
159
+ // src/http.ts
112
160
  var HttpClient = class {
113
161
  constructor(config) {
114
162
  this.config = config;
115
163
  }
116
164
  config;
117
- async request(path, options) {
118
- const url = `${this.config.baseUrl}${path}`;
119
- const method = options?.method ?? "GET";
165
+ authHeaders(extra) {
120
166
  const headers = {
121
167
  "Authorization": `Bearer ${this.config.apiKey}`,
122
168
  "User-Agent": `deepline-ts-sdk/${SDK_VERSION}`,
123
- ...options?.headers
169
+ "X-Deepline-SDK-Version": SDK_VERSION,
170
+ "X-Deepline-API-Contract": SDK_API_CONTRACT,
171
+ ...extra
124
172
  };
173
+ const bypassToken = typeof process !== "undefined" ? process.env?.VERCEL_PROTECTION_BYPASS_TOKEN : void 0;
174
+ if (bypassToken) {
175
+ headers["x-vercel-protection-bypass"] = bypassToken;
176
+ }
177
+ const playArtifactR2Prefix = typeof process !== "undefined" ? process.env?.DEEPLINE_PLAY_ARTIFACT_R2_PREFIX : void 0;
178
+ if (playArtifactR2Prefix) {
179
+ headers["x-deepline-play-artifact-r2-prefix"] = playArtifactR2Prefix;
180
+ }
181
+ const coordinatorUrl = typeof process !== "undefined" ? process.env?.DEEPLINE_COORDINATOR_URL : void 0;
182
+ if (coordinatorUrl?.trim()) {
183
+ headers[COORDINATOR_URL_OVERRIDE_HEADER] = coordinatorUrl.trim();
184
+ }
185
+ const workerCallbackUrl = typeof process !== "undefined" ? process.env?.DEEPLINE_WORKER_CALLBACK_URL : void 0;
186
+ if (workerCallbackUrl?.trim()) {
187
+ headers[WORKER_CALLBACK_URL_OVERRIDE_HEADER] = workerCallbackUrl.trim();
188
+ }
189
+ return headers;
190
+ }
191
+ /**
192
+ * Send an HTTP request with automatic retries and error handling.
193
+ *
194
+ * @typeParam T - Expected response body type
195
+ * @param path - API path (e.g. `"/api/v2/tools"`)
196
+ * @param options - HTTP method, body, headers, and timeout
197
+ * @returns Parsed JSON response body
198
+ * @throws {@link AuthError} on HTTP 401/403 (immediate, no retry)
199
+ * @throws {@link RateLimitError} on HTTP 429 after all retries exhausted
200
+ * @throws {@link DeeplineError} on other API errors or connection failures
201
+ */
202
+ async request(path, options) {
203
+ const baseUrl = this.config.baseUrl;
204
+ const url = `${baseUrl}${path}`;
205
+ const method = options?.method ?? "GET";
206
+ const headers = this.authHeaders(options?.headers);
125
207
  if (options?.body !== void 0) {
126
208
  headers["Content-Type"] = "application/json";
127
209
  }
128
210
  let lastError = null;
211
+ const candidateUrls = buildCandidateUrls(url);
212
+ let retryAfterDelayMs = null;
129
213
  for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {
130
214
  if (attempt > 0) {
131
215
  const backoffMs = Math.min(1e3 * Math.pow(2, attempt - 1), 3e4);
132
- await sleep(backoffMs);
216
+ const delayMs = retryAfterDelayMs === null ? backoffMs : Math.max(backoffMs, retryAfterDelayMs);
217
+ retryAfterDelayMs = null;
218
+ await sleep(delayMs);
133
219
  }
134
- const controller = new AbortController();
135
- const timeoutId = setTimeout(
136
- () => controller.abort(),
137
- options?.timeout ?? this.config.timeout
138
- );
220
+ for (const candidateUrl of candidateUrls) {
221
+ const controller = new AbortController();
222
+ const timeoutId = setTimeout(
223
+ () => controller.abort(),
224
+ options?.timeout ?? this.config.timeout
225
+ );
226
+ try {
227
+ const response = await fetch(candidateUrl, {
228
+ method,
229
+ headers,
230
+ body: options?.body !== void 0 ? JSON.stringify(options.body) : void 0,
231
+ signal: controller.signal
232
+ });
233
+ clearTimeout(timeoutId);
234
+ if (response.status === 401 || response.status === 403) {
235
+ throw new AuthError();
236
+ }
237
+ if (response.status === 429) {
238
+ const retryAfter = parseRetryAfter(response);
239
+ lastError = new RateLimitError(retryAfter);
240
+ if (attempt < this.config.maxRetries) {
241
+ retryAfterDelayMs = retryAfter;
242
+ break;
243
+ }
244
+ throw lastError;
245
+ }
246
+ const body = await response.text();
247
+ let parsed;
248
+ try {
249
+ parsed = JSON.parse(body);
250
+ } catch {
251
+ parsed = body;
252
+ }
253
+ if (!response.ok) {
254
+ const msg = typeof parsed === "object" && parsed && "error" in parsed ? String(parsed.error) : `HTTP ${response.status}`;
255
+ throw new DeeplineError(msg, response.status, "API_ERROR", {
256
+ response: parsed
257
+ });
258
+ }
259
+ return parsed;
260
+ } catch (error) {
261
+ clearTimeout(timeoutId);
262
+ if (error instanceof AuthError || error instanceof DeeplineError) {
263
+ throw error;
264
+ }
265
+ if (error instanceof RateLimitError) {
266
+ lastError = error;
267
+ break;
268
+ }
269
+ lastError = error instanceof Error ? error : new Error(String(error));
270
+ }
271
+ }
272
+ if (attempt < this.config.maxRetries) continue;
273
+ }
274
+ if (lastError instanceof DeeplineError) {
275
+ throw lastError;
276
+ }
277
+ const errorMessage = lastError?.message ? `Unable to connect to ${baseUrl}. ${lastError.message}` : `Unable to connect to ${baseUrl}. Is the computer able to access the url?`;
278
+ throw new DeeplineError(errorMessage);
279
+ }
280
+ /**
281
+ * Send a GET request.
282
+ *
283
+ * @typeParam T - Expected response body type
284
+ * @param path - API path (e.g. `"/api/v2/tools"`)
285
+ */
286
+ async get(path) {
287
+ return this.request(path, { method: "GET" });
288
+ }
289
+ async *streamSse(path, options) {
290
+ const url = `${this.config.baseUrl}${path}`;
291
+ const method = options?.method ?? "GET";
292
+ const headers = this.authHeaders({
293
+ Accept: "text/event-stream",
294
+ ...options?.headers
295
+ });
296
+ if (options?.body !== void 0) {
297
+ headers["Content-Type"] = "application/json";
298
+ }
299
+ let lastError = null;
300
+ for (const candidateUrl of buildCandidateUrls(url)) {
139
301
  try {
140
- const response = await fetch(url, {
302
+ const response = await fetch(candidateUrl, {
141
303
  method,
142
304
  headers,
143
305
  body: options?.body !== void 0 ? JSON.stringify(options.body) : void 0,
144
- signal: controller.signal
306
+ signal: options?.signal
145
307
  });
146
- clearTimeout(timeoutId);
147
308
  if (response.status === 401 || response.status === 403) {
148
309
  throw new AuthError();
149
310
  }
150
- if (response.status === 429) {
151
- const retryAfter = parseRetryAfter(response);
152
- lastError = new RateLimitError(retryAfter);
153
- if (attempt < this.config.maxRetries) continue;
154
- throw lastError;
155
- }
156
- const body = await response.text();
157
- let parsed;
158
- try {
159
- parsed = JSON.parse(body);
160
- } catch {
161
- parsed = body;
162
- }
163
311
  if (!response.ok) {
164
- const msg = typeof parsed === "object" && parsed && "error" in parsed ? String(parsed.error) : `HTTP ${response.status}`;
165
- throw new DeeplineError(msg, response.status, "API_ERROR", {
166
- response: parsed
167
- });
312
+ throw new DeeplineError(
313
+ `HTTP ${response.status}`,
314
+ response.status,
315
+ "API_ERROR"
316
+ );
317
+ }
318
+ if (!response.body) {
319
+ throw new DeeplineError("SSE response did not include a body.");
168
320
  }
169
- return parsed;
321
+ yield* decodeSseStream(response.body);
322
+ return;
170
323
  } catch (error) {
171
- clearTimeout(timeoutId);
172
324
  if (error instanceof AuthError || error instanceof DeeplineError) {
173
325
  throw error;
174
326
  }
175
- if (error instanceof RateLimitError) {
176
- lastError = error;
177
- if (attempt < this.config.maxRetries) continue;
178
- throw error;
179
- }
180
327
  lastError = error instanceof Error ? error : new Error(String(error));
181
- if (attempt < this.config.maxRetries) continue;
182
328
  }
183
329
  }
184
- throw lastError ?? new DeeplineError("Request failed after retries");
185
- }
186
- async get(path) {
187
- return this.request(path, { method: "GET" });
330
+ throw new DeeplineError(
331
+ lastError?.message ? `Unable to stream from ${this.config.baseUrl}. ${lastError.message}` : `Unable to stream from ${this.config.baseUrl}.`
332
+ );
188
333
  }
334
+ /**
335
+ * Send a POST request with a JSON body.
336
+ *
337
+ * @typeParam T - Expected response body type
338
+ * @param path - API path
339
+ * @param body - Request body (will be JSON-serialized)
340
+ */
189
341
  async post(path, body) {
190
342
  return this.request(path, { method: "POST", body });
191
343
  }
344
+ /**
345
+ * Send a DELETE request.
346
+ *
347
+ * @typeParam T - Expected response body type
348
+ * @param path - API path
349
+ */
350
+ async delete(path) {
351
+ return this.request(path, { method: "DELETE" });
352
+ }
192
353
  };
193
354
  function parseRetryAfter(response) {
194
355
  const header = response.headers.get("retry-after");
@@ -200,27 +361,218 @@ function parseRetryAfter(response) {
200
361
  }
201
362
  return 5e3;
202
363
  }
364
+ function buildCandidateUrls(url) {
365
+ try {
366
+ const parsed = new URL(url);
367
+ const candidates = [url];
368
+ if (parsed.hostname === "localhost") {
369
+ const loopback = new URL(url);
370
+ loopback.hostname = "127.0.0.1";
371
+ candidates.push(loopback.toString());
372
+ }
373
+ return [...new Set(candidates)];
374
+ } catch {
375
+ return [url];
376
+ }
377
+ }
378
+ async function* decodeSseStream(body) {
379
+ const reader = body.getReader();
380
+ const decoder = new TextDecoder();
381
+ let buffered = "";
382
+ try {
383
+ while (true) {
384
+ const { value, done } = await reader.read();
385
+ if (done) break;
386
+ buffered += decoder.decode(value, { stream: true });
387
+ const frames = buffered.split(/\r?\n\r?\n/);
388
+ buffered = frames.pop() ?? "";
389
+ for (const frame of frames) {
390
+ const event2 = decodeSseFrame(frame);
391
+ if (event2) {
392
+ yield event2;
393
+ }
394
+ }
395
+ }
396
+ buffered += decoder.decode();
397
+ const event = decodeSseFrame(buffered);
398
+ if (event) {
399
+ yield event;
400
+ }
401
+ } finally {
402
+ reader.releaseLock();
403
+ }
404
+ }
405
+ function decodeSseFrame(frame) {
406
+ const data = frame.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice("data:".length).trimStart()).join("\n").trim();
407
+ if (!data) {
408
+ return null;
409
+ }
410
+ const parsed = JSON.parse(data);
411
+ if (!parsed || typeof parsed !== "object" || typeof parsed.cursor !== "string" || typeof parsed.streamId !== "string" || typeof parsed.scope !== "string" || typeof parsed.type !== "string" || typeof parsed.at !== "string") {
412
+ return null;
413
+ }
414
+ return parsed;
415
+ }
203
416
  function sleep(ms) {
204
- return new Promise((resolve) => setTimeout(resolve, ms));
417
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
205
418
  }
206
419
 
207
420
  // src/client.ts
421
+ var TERMINAL_PLAY_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
422
+ function normalizePlayStatus(raw) {
423
+ const status = typeof raw.status === "string" ? raw.status : typeof raw.temporalStatus === "string" ? mapLegacyTemporalStatus(raw.temporalStatus) : "running";
424
+ const runId = typeof raw.runId === "string" ? raw.runId : typeof raw.workflowId === "string" ? raw.workflowId : "";
425
+ return {
426
+ ...raw,
427
+ runId,
428
+ status
429
+ };
430
+ }
431
+ function mapLegacyTemporalStatus(status) {
432
+ switch (status.trim().toUpperCase()) {
433
+ case "PENDING":
434
+ return "queued";
435
+ case "COMPLETED":
436
+ return "completed";
437
+ case "FAILED":
438
+ return "failed";
439
+ case "CANCELLED":
440
+ case "TERMINATED":
441
+ case "TIMED_OUT":
442
+ return "cancelled";
443
+ case "RUNNING":
444
+ default:
445
+ return "running";
446
+ }
447
+ }
208
448
  var DeeplineClient = class {
209
449
  http;
210
450
  config;
451
+ /**
452
+ * @param options - Optional overrides for API key, base URL, timeout, and retries.
453
+ * @throws {@link ConfigError} if no API key can be resolved from any source.
454
+ */
211
455
  constructor(options) {
212
456
  this.config = resolveConfig(options);
213
457
  this.http = new HttpClient(this.config);
214
458
  }
459
+ /** The resolved base URL this client is targeting (e.g. `"http://localhost:3000"`). */
215
460
  get baseUrl() {
216
461
  return this.config.baseUrl;
217
462
  }
218
- /** List available tools. */
463
+ compactSchema(schema) {
464
+ if (!schema) return null;
465
+ const fields = Array.isArray(schema.fields) ? schema.fields.map(
466
+ (field) => field && typeof field === "object" ? {
467
+ name: String(field.name ?? ""),
468
+ type: field.type ?? void 0,
469
+ required: field.required ?? void 0
470
+ } : null
471
+ ).filter((field) => Boolean(field?.name)) : [];
472
+ return fields.length > 0 ? { fields } : schema;
473
+ }
474
+ playRunCommand(name) {
475
+ return `deepline plays run ${name} --input '{...}' --watch`;
476
+ }
477
+ summarizePlayListItem(play, options) {
478
+ const aliases = play.aliases?.length ? play.aliases : [play.name];
479
+ const runCommand = this.playRunCommand(play.name);
480
+ return {
481
+ name: play.name,
482
+ ...play.reference ? { reference: play.reference } : {},
483
+ ...play.displayName ? { displayName: play.displayName } : {},
484
+ origin: play.origin,
485
+ ownerType: play.ownerType,
486
+ canEdit: play.canEdit,
487
+ canClone: play.canClone,
488
+ aliases,
489
+ inputSchema: options?.compact ? this.compactSchema(play.inputSchema) : play.inputSchema ?? null,
490
+ outputSchema: options?.compact ? this.compactSchema(play.outputSchema) : play.outputSchema ?? null,
491
+ runCommand,
492
+ examples: [runCommand],
493
+ currentPublishedVersion: play.currentPublishedVersion ?? null,
494
+ isDraftDirty: play.isDraftDirty
495
+ };
496
+ }
497
+ summarizePlayDetail(detail, options) {
498
+ const play = detail.play;
499
+ return {
500
+ ...this.summarizePlayListItem(play, options),
501
+ currentPublishedVersion: play.currentPublishedVersion ?? play.liveRevision?.version ?? null,
502
+ latestRunId: play.latestRunId ?? detail.latestRuns[0]?.workflowId ?? null
503
+ };
504
+ }
505
+ // ——————————————————————————————————————————————————————————
506
+ // Tools
507
+ // ——————————————————————————————————————————————————————————
508
+ /**
509
+ * List all available tools.
510
+ *
511
+ * Returns tool definitions including ID, provider, description, input/output schemas,
512
+ * and list extractor paths for automatic CSV conversion.
513
+ *
514
+ * @returns Array of tool definitions
515
+ *
516
+ * @example
517
+ * ```typescript
518
+ * const tools = await client.listTools();
519
+ * const searchTools = tools.filter(t => t.categories.includes('search'));
520
+ * console.log(`Found ${searchTools.length} search tools`);
521
+ * ```
522
+ */
219
523
  async listTools() {
220
- const res = await this.http.get("/api/v2/tools");
524
+ const res = await this.http.get(
525
+ "/api/v2/tools"
526
+ );
221
527
  return res.tools;
222
528
  }
223
- /** Execute a single tool. */
529
+ /**
530
+ * Get detailed metadata for a single tool.
531
+ *
532
+ * Returns everything from {@link ToolDefinition} plus pricing info, sample
533
+ * inputs/outputs, failure modes, and cost estimates.
534
+ *
535
+ * @param toolId - Tool identifier (e.g. `"apollo_people_search"`)
536
+ * @returns Full tool metadata
537
+ *
538
+ * @example
539
+ * ```typescript
540
+ * const meta = await client.getTool('apollo_people_search');
541
+ * console.log(`Cost: ${meta.estimatedCreditsRange} credits`);
542
+ * console.log(`Input schema:`, meta.inputSchema);
543
+ * ```
544
+ */
545
+ async getTool(toolId) {
546
+ return this.http.request(
547
+ `/api/v2/integrations/${encodeURIComponent(toolId)}/get`,
548
+ {
549
+ method: "GET",
550
+ headers: {
551
+ "x-deepline-tool-meta-only": "1"
552
+ }
553
+ }
554
+ );
555
+ }
556
+ /**
557
+ * Execute a tool and return the extracted result.
558
+ *
559
+ * Sends the input payload to the tool and returns the `.result` field from the
560
+ * response. For the full response envelope (including job_id, credits, etc.),
561
+ * use {@link executeToolRaw}.
562
+ *
563
+ * @param toolId - Tool identifier (e.g. `"test_company_search"`)
564
+ * @param input - Tool-specific input parameters
565
+ * @returns The tool's output (shape varies by tool)
566
+ * @throws {@link DeeplineError} if the tool execution fails
567
+ *
568
+ * @example
569
+ * ```typescript
570
+ * const company = await client.executeTool('test_company_search', {
571
+ * domain: 'stripe.com',
572
+ * });
573
+ * console.log(company); // { name: "Stripe", industry: "Financial Services", ... }
574
+ * ```
575
+ */
224
576
  async executeTool(toolId, input) {
225
577
  const res = await this.http.post(
226
578
  `/api/v2/integrations/${encodeURIComponent(toolId)}/execute`,
@@ -229,38 +581,602 @@ var DeeplineClient = class {
229
581
  return res.result ?? res;
230
582
  }
231
583
  /**
232
- * Submit a play for cloud execution.
233
- * Returns a workflowId. Use pollPlay() to get status.
584
+ * Execute a tool and return the full response envelope.
585
+ *
586
+ * Unlike {@link executeTool}, this returns the complete API response including
587
+ * `job_id`, `status`, `credits`, and the raw `result` object.
588
+ *
589
+ * @param toolId - Tool identifier
590
+ * @param input - Tool-specific input parameters
591
+ * @returns Full response with job metadata and result
592
+ *
593
+ * @example
594
+ * ```typescript
595
+ * const raw = await client.executeToolRaw('test_company_search', { domain: 'stripe.com' });
596
+ * console.log(`Job: ${raw.job_id}, Credits: ${raw.credits}`);
597
+ * console.log(`Result:`, raw.result);
598
+ * ```
599
+ */
600
+ async executeToolRaw(toolId, input) {
601
+ return this.http.post(
602
+ `/api/v2/integrations/${encodeURIComponent(toolId)}/execute`,
603
+ { payload: input }
604
+ );
605
+ }
606
+ async queryCustomerDb(input) {
607
+ return this.http.post("/api/v2/db/query", {
608
+ sql: input.sql,
609
+ ...input.maxRows ? { max_rows: input.maxRows } : {}
610
+ });
611
+ }
612
+ // ——————————————————————————————————————————————————————————
613
+ // Plays — submission and lifecycle
614
+ // ——————————————————————————————————————————————————————————
615
+ /**
616
+ * Start a play run.
617
+ *
618
+ * Internal/advanced primitive. For normal callers, prefer the public
619
+ * entrypoints: the CLI, {@link Deepline.connect}, {@link submitPlay},
620
+ * or {@link runPlay}.
621
+ *
622
+ * Supported invocation surfaces intentionally share this same run contract:
623
+ * `deepline play run`, repo scripts such as `bun run deepline -- play run`,
624
+ * SDK context calls like `Deepline.connect().play(name).run()`, and direct
625
+ * `POST /api/v2/plays/run` calls all return a workflow/run id. The completed
626
+ * output is always retrievable from `getPlayStatus(runId).result` (or from
627
+ * `PlayJob.get()` for SDK context calls). Execution logs live under
628
+ * `progress.logs`; they are not part of the user output object.
629
+ *
630
+ * @param request - Play run configuration (name, code, input, etc.)
631
+ * @returns Workflow metadata including the `workflowId` for status polling
632
+ *
633
+ * @example
634
+ * ```typescript
635
+ * // Run a live play by name:
636
+ * const started = await client.startPlayRun({
637
+ * name: 'email-waterfall',
638
+ * input: { linkedin_url: 'https://linkedin.com/in/jdoe', domain: 'acme.com' },
639
+ * });
640
+ * console.log(`Workflow: ${started.workflowId}`);
641
+ *
642
+ * // Run an ad hoc artifact-backed play:
643
+ * const started2 = await client.startPlayRun({
644
+ * artifactStorageKey: 'plays/v1/orgs/acme/plays/my-play/artifacts/playgraph_abc123.json',
645
+ * });
646
+ * ```
234
647
  */
235
- async submitPlay(code, csvPath, name) {
648
+ async startPlayRun(request) {
236
649
  return this.http.post("/api/v2/plays/run", {
237
- code,
238
- csvPath,
239
- name
650
+ ...request.name ? { name: request.name } : {},
651
+ ...request.revisionId ? { revisionId: request.revisionId } : {},
652
+ ...request.artifactStorageKey ? { artifactStorageKey: request.artifactStorageKey } : {},
653
+ ...request.sourceCode ? { sourceCode: request.sourceCode } : {},
654
+ ..."staticPipeline" in request ? { staticPipeline: request.staticPipeline } : {},
655
+ ...request.artifactHash ? { artifactHash: request.artifactHash } : {},
656
+ ...request.graphHash ? { graphHash: request.graphHash } : {},
657
+ ...request.runtimeArtifact ? { runtimeArtifact: request.runtimeArtifact } : {},
658
+ ...request.compilerManifest ? { compilerManifest: request.compilerManifest } : {},
659
+ ...request.inputFileUpload ? { inputFileUpload: request.inputFileUpload } : {},
660
+ ...request.packagedFileUploads?.length ? { packagedFileUploads: request.packagedFileUploads } : {},
661
+ ...request.input ? { input: request.input } : {},
662
+ ...request.inputFile ? { inputFile: request.inputFile } : {},
663
+ ...request.packagedFiles?.length ? { packagedFiles: request.packagedFiles } : {},
664
+ ...request.force ? { force: true } : {},
665
+ ...typeof request.waitForCompletionMs === "number" ? { waitForCompletionMs: request.waitForCompletionMs } : {},
666
+ // Profile selection is the API's job, not the CLI's. The server
667
+ // hardcodes workers_edge as the default; tests that want a
668
+ // different profile pass `request.profile` explicitly.
669
+ ...request.profile ? { profile: request.profile } : {}
670
+ });
671
+ }
672
+ async *startPlayRunStream(request, options) {
673
+ const body = {
674
+ ...request.name ? { name: request.name } : {},
675
+ ...request.revisionId ? { revisionId: request.revisionId } : {},
676
+ ...request.artifactStorageKey ? { artifactStorageKey: request.artifactStorageKey } : {},
677
+ ...request.sourceCode ? { sourceCode: request.sourceCode } : {},
678
+ ..."staticPipeline" in request ? { staticPipeline: request.staticPipeline } : {},
679
+ ...request.artifactHash ? { artifactHash: request.artifactHash } : {},
680
+ ...request.graphHash ? { graphHash: request.graphHash } : {},
681
+ ...request.runtimeArtifact ? { runtimeArtifact: request.runtimeArtifact } : {},
682
+ ...request.compilerManifest ? { compilerManifest: request.compilerManifest } : {},
683
+ ...request.inputFileUpload ? { inputFileUpload: request.inputFileUpload } : {},
684
+ ...request.packagedFileUploads?.length ? { packagedFileUploads: request.packagedFileUploads } : {},
685
+ ...request.input ? { input: request.input } : {},
686
+ ...request.inputFile ? { inputFile: request.inputFile } : {},
687
+ ...request.packagedFiles?.length ? { packagedFiles: request.packagedFiles } : {},
688
+ ...request.force ? { force: true } : {},
689
+ ...request.profile ? { profile: request.profile } : {}
690
+ };
691
+ for await (const event of this.http.streamSse(
692
+ "/api/v2/plays/run?stream=true",
693
+ {
694
+ method: "POST",
695
+ body,
696
+ signal: options?.signal
697
+ }
698
+ )) {
699
+ if (event.scope === "play") {
700
+ yield event;
701
+ }
702
+ }
703
+ }
704
+ /**
705
+ * Register a bundled play artifact.
706
+ *
707
+ * Internal/advanced primitive used by packaging flows. Public callers should
708
+ * prefer the CLI, {@link submitPlay}, or {@link runPlay}.
709
+ */
710
+ async registerPlayArtifact(input) {
711
+ const compilerManifest = input.compilerManifest ?? await this.compilePlayManifest({
712
+ name: input.name,
713
+ sourceCode: input.sourceCode,
714
+ artifact: input.artifact
715
+ });
716
+ return this.http.post("/api/v2/plays/artifacts", {
717
+ ...input,
718
+ compilerManifest
719
+ });
720
+ }
721
+ async registerPlayArtifacts(artifacts) {
722
+ const compiledArtifacts = await Promise.all(
723
+ artifacts.map(async (artifact) => ({
724
+ ...artifact,
725
+ compilerManifest: artifact.compilerManifest ?? await this.compilePlayManifest({
726
+ name: artifact.name,
727
+ sourceCode: artifact.sourceCode,
728
+ artifact: artifact.artifact
729
+ })
730
+ }))
731
+ );
732
+ return this.http.post("/api/v2/plays/artifacts", {
733
+ artifacts: compiledArtifacts
734
+ });
735
+ }
736
+ async compilePlayManifest(input) {
737
+ const response = await this.http.post("/api/v2/plays/compile-manifest", input);
738
+ return response.compilerManifest;
739
+ }
740
+ /**
741
+ * Check a bundled play artifact against the server's current play compiler.
742
+ *
743
+ * Unlike {@link registerPlayArtifact}, this does not store the artifact,
744
+ * publish a revision, or start a run. It is the authoritative cloud validation
745
+ * path used by `deepline play check`.
746
+ */
747
+ async checkPlayArtifact(input) {
748
+ return this.http.post("/api/v2/plays/check", input);
749
+ }
750
+ async startPlayRunFromBundle(input) {
751
+ const compilerManifest = input.compilerManifest ?? await this.compilePlayManifest({
752
+ name: input.name,
753
+ sourceCode: input.sourceCode,
754
+ artifact: input.artifact
755
+ });
756
+ const registeredArtifact = await this.registerPlayArtifact({
757
+ name: input.name,
758
+ sourceCode: input.sourceCode,
759
+ artifact: input.artifact,
760
+ compilerManifest,
761
+ publish: false
240
762
  });
763
+ if (!registeredArtifact.artifactStorageKey) {
764
+ throw new Error(
765
+ "registerPlayArtifact did not return an artifactStorageKey."
766
+ );
767
+ }
768
+ return this.startPlayRun({
769
+ name: input.name,
770
+ artifactStorageKey: registeredArtifact.artifactStorageKey,
771
+ compilerManifest,
772
+ ...input.input ? { input: input.input } : {},
773
+ ...input.inputFile ? { inputFile: input.inputFile } : {},
774
+ ...input.packagedFiles?.length ? { packagedFiles: input.packagedFiles } : {},
775
+ ...input.force ? { force: true } : {}
776
+ });
777
+ }
778
+ /**
779
+ * Register a bundled play artifact and start a run from the live revision.
780
+ *
781
+ * Convenience wrapper around {@link registerPlayArtifact} plus
782
+ * {@link startPlayRun}. This is the canonical file-backed path used by wrappers.
783
+ * The returned id can be passed to {@link getPlayStatus} to retrieve the same
784
+ * durable `{ result }` object that the CLI prints after `--watch` completes.
785
+ *
786
+ * @param code - Source string fallback; the bundled artifact should be passed in `options.artifact`
787
+ * @param csvPath - Path to input CSV file, or `null`
788
+ * @param name - Play name (extracted from source if omitted)
789
+ * @param options - Additional submission options
790
+ * @returns Workflow metadata with `workflowId`
791
+ *
792
+ * @example
793
+ * ```typescript
794
+ * const started = await client.submitPlay(
795
+ * originalSource,
796
+ * './leads.csv',
797
+ * 'bulk-enrich',
798
+ * { artifact: bundledArtifact, input: { limit: 100 } },
799
+ * );
800
+ * ```
801
+ */
802
+ async submitPlay(code, csvPath, name, options) {
803
+ const runtimeInput = options?.input ? { ...options.input } : {};
804
+ if (csvPath) {
805
+ runtimeInput.file = csvPath;
806
+ }
807
+ const sourceCode = options?.sourceCode ?? code;
808
+ const artifact = options?.artifact;
809
+ if (!name?.trim()) {
810
+ throw new Error("submitPlay requires a play name.");
811
+ }
812
+ if (!artifact) {
813
+ throw new Error("submitPlay requires a bundled play artifact.");
814
+ }
815
+ const compilerManifest = options?.compilerManifest ?? await this.compilePlayManifest({
816
+ name,
817
+ sourceCode,
818
+ artifact
819
+ });
820
+ const registeredArtifact = await this.registerPlayArtifact({
821
+ name,
822
+ sourceCode,
823
+ artifact,
824
+ compilerManifest,
825
+ publish: false
826
+ });
827
+ if (!registeredArtifact.artifactStorageKey) {
828
+ throw new Error(
829
+ "registerPlayArtifact did not return an artifactStorageKey."
830
+ );
831
+ }
832
+ return this.startPlayRun({
833
+ name,
834
+ artifactStorageKey: registeredArtifact.artifactStorageKey,
835
+ sourceCode,
836
+ staticPipeline: registeredArtifact.staticPipeline ?? null,
837
+ artifactHash: typeof artifact.artifactHash === "string" ? artifact.artifactHash : void 0,
838
+ graphHash: typeof artifact.graphHash === "string" ? artifact.graphHash : void 0,
839
+ runtimeArtifact: artifact,
840
+ compilerManifest,
841
+ ...Object.keys(runtimeInput).length > 0 ? { input: runtimeInput } : {},
842
+ ...options?.inputFile ? { inputFile: options.inputFile } : {},
843
+ ...options?.packagedFiles?.length ? { packagedFiles: options.packagedFiles } : {},
844
+ ...options?.force ? { force: true } : {}
845
+ });
846
+ }
847
+ /**
848
+ * Upload files to the staging area for use in play runs.
849
+ *
850
+ * Internal/advanced primitive used by packaging flows. Public callers should
851
+ * prefer the CLI, {@link submitPlay}, or {@link runPlay}.
852
+ *
853
+ * Staged files are referenced by their returned {@link PlayStagedFileRef}
854
+ * in subsequent {@link startPlayRun} calls via `inputFile` or `packagedFiles`.
855
+ *
856
+ * @param files - Array of files to stage (base64-encoded content)
857
+ * @returns Array of staged file references
858
+ *
859
+ * @example
860
+ * ```typescript
861
+ * const staged = await client.stagePlayFiles([{
862
+ * logicalPath: 'data/leads.csv',
863
+ * contentBase64: Buffer.from(csvContent).toString('base64'),
864
+ * contentHash: sha256(csvContent),
865
+ * contentType: 'text/csv',
866
+ * bytes: csvContent.length,
867
+ * }]);
868
+ * // Use staged[0] as inputFile in startPlayRun
869
+ * ```
870
+ */
871
+ async stagePlayFiles(files) {
872
+ const response = await this.http.post(
873
+ "/api/v2/plays/files/stage",
874
+ { files }
875
+ );
876
+ return response.files;
241
877
  }
242
- /** Poll workflow status. */
878
+ async resolveStagedPlayFiles(files) {
879
+ return this.http.post("/api/v2/plays/files/stage", { files });
880
+ }
881
+ // ——————————————————————————————————————————————————————————
882
+ // Plays — status and monitoring
883
+ // ——————————————————————————————————————————————————————————
884
+ /**
885
+ * Get the current status of a play execution.
886
+ *
887
+ * Internal/advanced primitive. Public callers should usually prefer
888
+ * {@link runPlay}, {@link PlayJob.get}, or `deepline play run --watch`.
889
+ *
890
+ * Poll this method until `status` reaches a terminal state:
891
+ * `'completed'`, `'failed'`, or `'cancelled'`.
892
+ *
893
+ * @param workflowId - Play-run id from {@link startPlayRun}
894
+ * @returns Current status with progress logs and partial results
895
+ *
896
+ * @example
897
+ * ```typescript
898
+ * const status = await client.getPlayStatus('play-abc123');
899
+ * console.log(`Status: ${status.status}`);
900
+ * console.log(`Logs: ${status.progress?.logs.length ?? 0} lines`);
901
+ * ```
902
+ */
243
903
  async getPlayStatus(workflowId) {
244
- return this.http.get(`/api/v2/plays/run/${workflowId}`);
904
+ const response = await this.http.get(
905
+ `/api/v2/plays/run/${encodeURIComponent(workflowId)}`
906
+ );
907
+ return normalizePlayStatus(response);
245
908
  }
246
- /** Cancel a running play. */
909
+ /**
910
+ * Get the lightweight tail-polling status for a play execution.
911
+ *
912
+ * This is intentionally smaller than {@link getPlayStatus}: it returns the
913
+ * fields needed for CLI log tailing while the run is in flight, without
914
+ * forcing the API to rebuild final result views on every poll. Call
915
+ * {@link getPlayStatus} once after a terminal state for the full result.
916
+ */
917
+ async getPlayTailStatus(workflowId, options) {
918
+ const params = new URLSearchParams({ mode: "tail" });
919
+ if (typeof options?.afterLogIndex === "number") {
920
+ params.set("afterLogIndex", String(options.afterLogIndex));
921
+ }
922
+ if (typeof options?.waitMs === "number") {
923
+ params.set("waitMs", String(options.waitMs));
924
+ }
925
+ if (options?.terminalOnly) {
926
+ params.set("terminalOnly", "true");
927
+ }
928
+ const response = await this.http.get(
929
+ `/api/v2/plays/run/${encodeURIComponent(workflowId)}?${params.toString()}`
930
+ );
931
+ return normalizePlayStatus(response);
932
+ }
933
+ /**
934
+ * Stream semantic play-run events using the same SSE feed as the dashboard.
935
+ *
936
+ * Consumers should still keep a polling fallback: SSE is the fast live-update
937
+ * transport, while the status endpoints remain the authoritative recovery path.
938
+ */
939
+ async *streamPlayRunEvents(workflowId, options) {
940
+ const headers = options?.lastEventId && options.lastEventId.trim() ? { "Last-Event-ID": options.lastEventId.trim() } : void 0;
941
+ const params = new URLSearchParams({ stream: "true" });
942
+ params.set("mode", options?.mode ?? "cli");
943
+ for await (const event of this.http.streamSse(
944
+ `/api/v2/plays/run/${encodeURIComponent(workflowId)}?${params.toString()}`,
945
+ { signal: options?.signal, headers }
946
+ )) {
947
+ if (event.scope === "play") {
948
+ yield event;
949
+ }
950
+ }
951
+ }
952
+ /**
953
+ * Cancel a running play execution.
954
+ *
955
+ * Sends a stop request for the run.
956
+ *
957
+ * @param workflowId - Temporal workflow ID to cancel
958
+ *
959
+ * @example
960
+ * ```typescript
961
+ * await client.cancelPlay('play-abc123');
962
+ * ```
963
+ */
247
964
  async cancelPlay(workflowId) {
248
- await this.http.request(`/api/v2/plays/run/${workflowId}`, { method: "DELETE" });
965
+ await this.http.request(
966
+ `/api/v2/plays/run/${encodeURIComponent(workflowId)}/stop`,
967
+ { method: "POST" }
968
+ );
969
+ }
970
+ /**
971
+ * Stop a running play execution, including open HITL waits.
972
+ *
973
+ * @param workflowId - Temporal workflow ID to stop
974
+ * @param options.reason - Optional audit/debug reason
975
+ */
976
+ async stopPlay(workflowId, options) {
977
+ return this.http.post(
978
+ `/api/v2/plays/run/${encodeURIComponent(workflowId)}/stop`,
979
+ options?.reason ? { reason: options.reason } : {}
980
+ );
981
+ }
982
+ /**
983
+ * List recent runs for a named play.
984
+ *
985
+ * Returns runs sorted by start time (newest first), including workflow IDs,
986
+ * status, timestamps, and metadata.
987
+ *
988
+ * @param playName - The play name to query
989
+ * @returns Array of run summaries (empty array if no runs exist)
990
+ *
991
+ * @example
992
+ * ```typescript
993
+ * const runs = await client.listPlayRuns('email-waterfall');
994
+ * for (const run of runs) {
995
+ * console.log(`${run.workflowId}: ${run.status} (${run.executionTime})`);
996
+ * }
997
+ * ```
998
+ */
999
+ async listPlayRuns(playName) {
1000
+ const encodedName = encodeURIComponent(playName);
1001
+ const response = await this.http.get(
1002
+ `/api/v2/plays/${encodedName}/runs`
1003
+ );
1004
+ return response.runs ?? [];
1005
+ }
1006
+ async listPlays() {
1007
+ const response = await this.http.get(
1008
+ "/api/v2/plays"
1009
+ );
1010
+ return response.plays ?? [];
1011
+ }
1012
+ async searchPlays(options) {
1013
+ const query = options.query.trim().toLowerCase();
1014
+ const terms = query.split(/\s+/).filter(Boolean);
1015
+ const plays = await this.listPlays();
1016
+ return plays.filter((play) => {
1017
+ if (options.origin && (play.origin ?? "owned") !== options.origin) {
1018
+ return false;
1019
+ }
1020
+ const haystack = [
1021
+ play.name,
1022
+ play.reference,
1023
+ play.displayName,
1024
+ play.origin,
1025
+ ...play.aliases ?? [],
1026
+ play.inputSchema ? JSON.stringify(play.inputSchema) : ""
1027
+ ].filter(Boolean).join(" ").toLowerCase();
1028
+ return terms.every((term) => haystack.includes(term));
1029
+ }).map((play) => this.summarizePlayListItem(play, options));
1030
+ }
1031
+ /**
1032
+ * Get the full definition and state of a named play.
1033
+ *
1034
+ * Returns the play's revision state (draft, live), recent runs,
1035
+ * sheet processing summary, and database URL.
1036
+ *
1037
+ * @param name - Play name
1038
+ * @returns Complete play detail
1039
+ *
1040
+ * @example
1041
+ * ```typescript
1042
+ * const detail = await client.getPlay('email-waterfall');
1043
+ * console.log(`Live: v${detail.play.currentPublishedVersion}`);
1044
+ * console.log(`Draft dirty: ${detail.play.isDraftDirty}`);
1045
+ * console.log(`Total runs: ${detail.play.runCount}`);
1046
+ * ```
1047
+ */
1048
+ async getPlay(name) {
1049
+ const encodedName = encodeURIComponent(name);
1050
+ return this.http.get(`/api/v2/plays/${encodedName}`);
1051
+ }
1052
+ async describePlay(name, options) {
1053
+ const detail = await this.getPlay(name);
1054
+ return this.summarizePlayDetail(detail, options);
1055
+ }
1056
+ /**
1057
+ * Clear run history and durable sheet/result data for a play without deleting
1058
+ * the play definition or revisions.
1059
+ */
1060
+ async clearPlayHistory(name, request = {}) {
1061
+ const encodedName = encodeURIComponent(name);
1062
+ return this.http.post(
1063
+ `/api/v2/plays/${encodedName}/history/clear`,
1064
+ request
1065
+ );
249
1066
  }
250
1067
  /**
251
- * Run a play end-to-end: submit, poll until done, return result.
252
- * onProgress is called on each poll with current status.
1068
+ * List saved versions for a named play.
1069
+ *
1070
+ * Returns immutable revision snapshots newest-first, including the revision
1071
+ * id needed for exact-version runs and live-version switching.
1072
+ *
1073
+ * @param name - Play name
1074
+ * @returns Version list (newest first)
1075
+ */
1076
+ async listPlayVersions(name) {
1077
+ const encodedName = encodeURIComponent(name);
1078
+ const response = await this.http.get(
1079
+ `/api/v2/plays/${encodedName}/versions`
1080
+ );
1081
+ return response.versions ?? [];
1082
+ }
1083
+ /**
1084
+ * Make a play revision live.
1085
+ *
1086
+ * When `revisionId` is omitted, the current working revision becomes live.
1087
+ * The live version is what executes when the play is run by name without
1088
+ * specifying an explicit revision.
1089
+ *
1090
+ * @param name - Play name
1091
+ * @param request - Optional explicit revision to make live
1092
+ * @returns Result with the new live version number
1093
+ *
1094
+ * @example
1095
+ * ```typescript
1096
+ * const result = await client.publishPlayVersion('email-waterfall');
1097
+ * if (result.success) {
1098
+ * console.log(`Live v${result.liveVersion}`);
1099
+ * }
1100
+ * ```
1101
+ */
1102
+ async publishPlayVersion(name, request = {}) {
1103
+ const encodedName = encodeURIComponent(name);
1104
+ return this.http.post(
1105
+ `/api/v2/plays/${encodedName}/live`,
1106
+ request
1107
+ );
1108
+ }
1109
+ /**
1110
+ * Delete an org-owned play definition, including its revisions, trigger
1111
+ * bindings, and local run records. Deepline prebuilt plays are read-only.
1112
+ */
1113
+ async deletePlay(name) {
1114
+ const encodedName = encodeURIComponent(name);
1115
+ return this.http.delete(`/api/v2/plays/${encodedName}`);
1116
+ }
1117
+ // ——————————————————————————————————————————————————————————
1118
+ // Plays — high-level orchestration
1119
+ // ——————————————————————————————————————————————————————————
1120
+ /**
1121
+ * Run a play end-to-end: submit, poll until terminal, return result.
1122
+ *
1123
+ * This is the highest-level play execution method. It submits the play,
1124
+ * polls for status updates, and returns a structured result with logs
1125
+ * and timing. Supports cancellation via `AbortSignal`.
1126
+ *
1127
+ * @param code - Source string fallback; pass the bundled artifact in `options.artifact`
1128
+ * @param csvPath - Input CSV path, or `null`
1129
+ * @param name - Play name
1130
+ * @param options - Execution options
1131
+ * @returns Final execution result with success/failure, output, logs, and duration
1132
+ *
1133
+ * @example
1134
+ * ```typescript
1135
+ * const result = await client.runPlay(bundledCode, null, 'my-play', {
1136
+ * input: { domain: 'stripe.com' },
1137
+ * onProgress: (status) => {
1138
+ * const logs = status.progress?.logs ?? [];
1139
+ * console.log(`[${status.status}] ${logs.length} log lines`);
1140
+ * },
1141
+ * pollIntervalMs: 1000,
1142
+ * });
1143
+ *
1144
+ * if (result.success) {
1145
+ * console.log('Output:', result.result);
1146
+ * } else {
1147
+ * console.error(`Failed after ${result.durationMs}ms:`, result.error);
1148
+ * }
1149
+ * ```
1150
+ *
1151
+ * @example Cancellation
1152
+ * ```typescript
1153
+ * const controller = new AbortController();
1154
+ * setTimeout(() => controller.abort(), 30_000); // 30s timeout
1155
+ *
1156
+ * const result = await client.runPlay(code, null, 'slow-play', {
1157
+ * signal: controller.signal,
1158
+ * });
1159
+ * // result.success === false, result.error === 'Cancelled by user'
1160
+ * ```
253
1161
  */
254
1162
  async runPlay(code, csvPath, name, options) {
255
- const { workflowId } = await this.submitPlay(code, csvPath, name);
256
- const pollInterval = options?.pollIntervalMs ?? 2e3;
1163
+ const { workflowId } = await this.submitPlay(code, csvPath, name, {
1164
+ input: options?.input,
1165
+ sourceCode: options?.sourceCode,
1166
+ artifact: options?.artifact,
1167
+ compilerManifest: options?.compilerManifest,
1168
+ inputFile: options?.inputFile,
1169
+ packagedFiles: options?.packagedFiles,
1170
+ force: options?.force
1171
+ });
1172
+ const pollInterval = options?.pollIntervalMs ?? 500;
257
1173
  const start = Date.now();
258
1174
  while (true) {
259
1175
  if (options?.signal?.aborted) {
260
1176
  await this.cancelPlay(workflowId);
261
1177
  return {
262
1178
  success: false,
263
- workflowId,
1179
+ runId: workflowId,
264
1180
  logs: [],
265
1181
  durationMs: Date.now() - start,
266
1182
  error: "Cancelled by user"
@@ -268,32 +1184,650 @@ var DeeplineClient = class {
268
1184
  }
269
1185
  const status = await this.getPlayStatus(workflowId);
270
1186
  options?.onProgress?.(status);
271
- const terminal = ["COMPLETED", "FAILED", "CANCELLED", "TERMINATED", "TIMED_OUT"];
272
- if (terminal.includes(status.temporalStatus)) {
1187
+ if (TERMINAL_PLAY_STATUSES.has(status.status)) {
273
1188
  return {
274
- success: status.temporalStatus === "COMPLETED",
275
- workflowId,
1189
+ success: status.status === "completed",
1190
+ runId: status.runId || workflowId,
276
1191
  result: status.result,
277
1192
  logs: status.progress?.logs ?? [],
278
1193
  durationMs: Date.now() - start,
279
- error: status.progress?.error ?? (status.temporalStatus !== "COMPLETED" ? status.temporalStatus : void 0)
1194
+ error: status.progress?.error ?? (status.status !== "completed" ? status.status : void 0)
280
1195
  };
281
1196
  }
282
- await new Promise((resolve) => setTimeout(resolve, pollInterval));
1197
+ await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
283
1198
  }
284
1199
  }
285
- /** Health check. */
1200
+ // ——————————————————————————————————————————————————————————
1201
+ // Health
1202
+ // ——————————————————————————————————————————————————————————
1203
+ /**
1204
+ * Check API connectivity and server health.
1205
+ *
1206
+ * @returns Health status with API version
1207
+ *
1208
+ * @example
1209
+ * ```typescript
1210
+ * const health = await client.health();
1211
+ * console.log(`API: ${health.status} (${health.version})`);
1212
+ * // { status: "ok", version: "v2" }
1213
+ * ```
1214
+ */
286
1215
  async health() {
287
- return this.http.get("/api/v2/health");
1216
+ return this.http.get(
1217
+ "/api/v2/health"
1218
+ );
1219
+ }
1220
+ };
1221
+
1222
+ // src/tool-output.ts
1223
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
1224
+ import { homedir as homedir2 } from "os";
1225
+ import { join as join2 } from "path";
1226
+ function isPlainObject(value) {
1227
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1228
+ }
1229
+ var EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1230
+ var PHONE_KEY_PATTERN = /(^|[_-])(phone|mobile|cell|telephone|tel)([_-]|$)|phone|mobile|telephone/i;
1231
+ function normalizeScalarString(value) {
1232
+ if (typeof value === "string") {
1233
+ const trimmed = value.trim();
1234
+ return trimmed.length > 0 ? trimmed : null;
1235
+ }
1236
+ if (typeof value === "number" && Number.isFinite(value)) {
1237
+ return String(value);
1238
+ }
1239
+ return null;
1240
+ }
1241
+ function looksLikeEmail(value) {
1242
+ const candidate = normalizeScalarString(value);
1243
+ if (!candidate || !EMAIL_PATTERN.test(candidate)) return null;
1244
+ return candidate;
1245
+ }
1246
+ function looksLikePhone(value) {
1247
+ const candidate = normalizeScalarString(value);
1248
+ if (!candidate) return null;
1249
+ const digits = candidate.replace(/\D/g, "");
1250
+ if (digits.length < 7 || digits.length > 16) return null;
1251
+ return candidate;
1252
+ }
1253
+ function findEmail(value, depth = 0) {
1254
+ if (depth > 6) return null;
1255
+ const direct = looksLikeEmail(value);
1256
+ if (direct) return direct;
1257
+ if (Array.isArray(value)) {
1258
+ for (const entry of value) {
1259
+ const nested = findEmail(entry, depth + 1);
1260
+ if (nested) return nested;
1261
+ }
1262
+ return null;
1263
+ }
1264
+ if (!isPlainObject(value)) return null;
1265
+ for (const [key, child] of Object.entries(value)) {
1266
+ if (/email/i.test(key)) {
1267
+ const keyed = looksLikeEmail(child);
1268
+ if (keyed) return keyed;
1269
+ }
1270
+ }
1271
+ for (const child of Object.values(value)) {
1272
+ const nested = findEmail(child, depth + 1);
1273
+ if (nested) return nested;
1274
+ }
1275
+ return null;
1276
+ }
1277
+ function findPhone(value, depth = 0) {
1278
+ if (depth > 6) return null;
1279
+ if (Array.isArray(value)) {
1280
+ for (const entry of value) {
1281
+ const nested = findPhone(entry, depth + 1);
1282
+ if (nested) return nested;
1283
+ }
1284
+ return null;
1285
+ }
1286
+ if (!isPlainObject(value)) return null;
1287
+ for (const [key, child] of Object.entries(value)) {
1288
+ if (PHONE_KEY_PATTERN.test(key)) {
1289
+ const keyed = looksLikePhone(child);
1290
+ if (keyed) return keyed;
1291
+ }
1292
+ }
1293
+ for (const child of Object.values(value)) {
1294
+ const nested = findPhone(child, depth + 1);
1295
+ if (nested) return nested;
1296
+ }
1297
+ return null;
1298
+ }
1299
+ var DeeplineToolCallResult = class {
1300
+ constructor(value) {
1301
+ this.value = value;
1302
+ }
1303
+ value;
1304
+ getEmail() {
1305
+ return findEmail(this.value);
1306
+ }
1307
+ getPhone() {
1308
+ return findPhone(this.value);
1309
+ }
1310
+ tryList(options) {
1311
+ return tryConvertToList(this.value, options)?.rows ?? null;
1312
+ }
1313
+ };
1314
+ function createToolCallResult(value) {
1315
+ return new DeeplineToolCallResult(value);
1316
+ }
1317
+ function getByDottedPath(root, dottedPath) {
1318
+ let current = root;
1319
+ for (const segment of String(dottedPath || "").split(".").filter(Boolean)) {
1320
+ if (!isPlainObject(current) || !(segment in current)) {
1321
+ return null;
1322
+ }
1323
+ current = current[segment];
1324
+ }
1325
+ return current;
1326
+ }
1327
+ function normalizeRows(value) {
1328
+ if (!Array.isArray(value)) return null;
1329
+ return value.map((entry) => {
1330
+ if (isPlainObject(entry)) return entry;
1331
+ return { value: entry };
1332
+ });
1333
+ }
1334
+ function candidateRoots(payload) {
1335
+ const roots = [{ path: null, value: payload }];
1336
+ if (isPlainObject(payload) && isPlainObject(payload.result)) {
1337
+ roots.push({ path: "result", value: payload.result });
1338
+ if (isPlainObject(payload.result.data)) {
1339
+ roots.push({ path: "result.data", value: payload.result.data });
1340
+ }
1341
+ }
1342
+ return roots;
1343
+ }
1344
+ function findBestArrayCandidate(value, pathPrefix = "", depth = 0) {
1345
+ if (depth > 5) return null;
1346
+ const directRows = normalizeRows(value);
1347
+ const hasObjectRow = directRows?.some((row) => Object.keys(row).some((key) => key !== "value")) ?? false;
1348
+ let best = directRows && directRows.length > 0 && hasObjectRow ? { path: pathPrefix, rows: directRows } : null;
1349
+ if (!isPlainObject(value)) {
1350
+ return best;
1351
+ }
1352
+ for (const [key, child] of Object.entries(value)) {
1353
+ const childPath = pathPrefix ? `${pathPrefix}.${key}` : key;
1354
+ const candidate = findBestArrayCandidate(child, childPath, depth + 1);
1355
+ if (!candidate) continue;
1356
+ if (!best || candidate.rows.length > best.rows.length) {
1357
+ best = candidate;
1358
+ }
1359
+ }
1360
+ return best;
1361
+ }
1362
+ function tryConvertToList(payload, options) {
1363
+ const listExtractorPaths = Array.isArray(options?.listExtractorPaths) ? options?.listExtractorPaths.filter((entry) => typeof entry === "string" && entry.trim().length > 0) : [];
1364
+ if (listExtractorPaths.length > 0) {
1365
+ for (const root of candidateRoots(payload)) {
1366
+ for (const extractorPath of listExtractorPaths) {
1367
+ const resolved = getByDottedPath(root.value, extractorPath);
1368
+ const rows = normalizeRows(resolved);
1369
+ if (rows && rows.length > 0) {
1370
+ const sourcePath = root.path ? `${root.path}.${extractorPath}` : extractorPath;
1371
+ return { rows, strategy: "configured_paths", sourcePath };
1372
+ }
1373
+ }
1374
+ }
1375
+ }
1376
+ for (const root of candidateRoots(payload)) {
1377
+ const candidate = findBestArrayCandidate(root.value, root.path ?? "");
1378
+ if (!candidate || candidate.rows.length === 0) continue;
1379
+ return {
1380
+ rows: candidate.rows,
1381
+ strategy: "auto_detected",
1382
+ sourcePath: candidate.path || root.path
1383
+ };
1384
+ }
1385
+ return null;
1386
+ }
1387
+ function ensureOutputDir() {
1388
+ const outputDir = join2(homedir2(), ".local", "share", "deepline", "data");
1389
+ mkdirSync2(outputDir, { recursive: true });
1390
+ return outputDir;
1391
+ }
1392
+ function writeJsonOutputFile(payload, stem) {
1393
+ const outputDir = ensureOutputDir();
1394
+ const outputPath = join2(outputDir, `${stem}_${Date.now()}.json`);
1395
+ writeFileSync2(outputPath, JSON.stringify(payload, null, 2), "utf-8");
1396
+ return outputPath;
1397
+ }
1398
+ function writeCsvOutputFile(rows, stem) {
1399
+ const outputDir = ensureOutputDir();
1400
+ const outputPath = join2(outputDir, `${stem}_${Date.now()}.csv`);
1401
+ const seen = /* @__PURE__ */ new Set();
1402
+ const columns = [];
1403
+ for (const row of rows) {
1404
+ for (const key of Object.keys(row)) {
1405
+ if (!seen.has(key)) {
1406
+ seen.add(key);
1407
+ columns.push(key);
1408
+ }
1409
+ }
1410
+ }
1411
+ const escapeCell = (value) => {
1412
+ const normalized = value == null ? "" : typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : JSON.stringify(value);
1413
+ if (/[",\n]/.test(normalized)) {
1414
+ return `"${normalized.replace(/"/g, '""')}"`;
1415
+ }
1416
+ return normalized;
1417
+ };
1418
+ const lines = [];
1419
+ lines.push(columns.map(escapeCell).join(","));
1420
+ for (const row of rows) {
1421
+ lines.push(columns.map((column) => escapeCell(row[column])).join(","));
1422
+ }
1423
+ writeFileSync2(outputPath, `${lines.join("\n")}
1424
+ `, "utf-8");
1425
+ const previewRows = rows.slice(0, 5);
1426
+ const previewColumns = columns.slice(0, 5);
1427
+ const preview = [
1428
+ previewColumns.join(","),
1429
+ ...previewRows.map((row) => previewColumns.map((column) => escapeCell(row[column])).join(","))
1430
+ ].join("\n");
1431
+ return {
1432
+ path: outputPath,
1433
+ rowCount: rows.length,
1434
+ columns,
1435
+ preview
1436
+ };
1437
+ }
1438
+ function extractSummaryFields(payload) {
1439
+ const candidates = candidateRoots(payload);
1440
+ for (const candidate of candidates) {
1441
+ if (!isPlainObject(candidate.value)) continue;
1442
+ const summaryEntries = Object.entries(candidate.value).filter(([, value]) => {
1443
+ return value == null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
1444
+ });
1445
+ if (summaryEntries.length === 0) continue;
1446
+ return Object.fromEntries(summaryEntries);
1447
+ }
1448
+ return {};
1449
+ }
1450
+
1451
+ // src/play.ts
1452
+ var DeeplineConditionalStepResolver = class _DeeplineConditionalStepResolver {
1453
+ constructor(when2, run, elseValue) {
1454
+ this.when = when2;
1455
+ this.run = run;
1456
+ this.elseValue = elseValue;
1457
+ }
1458
+ when;
1459
+ run;
1460
+ elseValue;
1461
+ kind = "conditional";
1462
+ else(value) {
1463
+ return new _DeeplineConditionalStepResolver(this.when, this.run, value);
1464
+ }
1465
+ };
1466
+ var DeeplineStepProgram = class _DeeplineStepProgram {
1467
+ constructor(steps2, returnResolver) {
1468
+ this.steps = steps2;
1469
+ this.returnResolver = returnResolver;
1470
+ }
1471
+ steps;
1472
+ returnResolver;
1473
+ kind = "steps";
1474
+ step(name, resolver) {
1475
+ if (!name.trim()) {
1476
+ throw new Error(
1477
+ "steps().step(name, ...) requires a non-empty step name."
1478
+ );
1479
+ }
1480
+ return new _DeeplineStepProgram(
1481
+ [
1482
+ ...this.steps,
1483
+ {
1484
+ name,
1485
+ resolver
1486
+ }
1487
+ ],
1488
+ this.returnResolver
1489
+ );
1490
+ }
1491
+ return(resolver) {
1492
+ return new _DeeplineStepProgram(this.steps, resolver);
1493
+ }
1494
+ };
1495
+ function steps() {
1496
+ return new DeeplineStepProgram([]);
1497
+ }
1498
+ function when(predicate, resolver) {
1499
+ return new DeeplineConditionalStepResolver(predicate, resolver, null);
1500
+ }
1501
+ var PLAY_METADATA_SYMBOL = /* @__PURE__ */ Symbol.for("deepline.play.metadata");
1502
+ var DeeplinePlayJobImpl = class {
1503
+ constructor(client, runId) {
1504
+ this.client = client;
1505
+ this.id = runId;
1506
+ }
1507
+ client;
1508
+ id;
1509
+ async status() {
1510
+ return this.client.getPlayStatus(this.id);
1511
+ }
1512
+ async tail(options) {
1513
+ const intervalMs = options?.intervalMs ?? 500;
1514
+ const onLog = options?.onLog ?? ((line) => console.log(line));
1515
+ const terminalStates = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
1516
+ let lastLogIndex = 0;
1517
+ while (true) {
1518
+ const status = await this.status();
1519
+ const logs = status.progress?.logs ?? [];
1520
+ for (let index = lastLogIndex; index < logs.length; index += 1) {
1521
+ onLog(logs[index]);
1522
+ }
1523
+ lastLogIndex = logs.length;
1524
+ if (terminalStates.has(status.status)) {
1525
+ return status;
1526
+ }
1527
+ await new Promise((resolve2) => setTimeout(resolve2, intervalMs));
1528
+ }
1529
+ }
1530
+ async get(options) {
1531
+ const intervalMs = options?.intervalMs ?? 500;
1532
+ const terminalStates = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
1533
+ while (true) {
1534
+ const status = await this.status();
1535
+ if (terminalStates.has(status.status)) {
1536
+ if (status.status !== "completed") {
1537
+ throw new DeeplineError(
1538
+ status.progress?.error || `Play run ${this.id} ended with ${status.status}.`
1539
+ );
1540
+ }
1541
+ const payload = status.result;
1542
+ return (payload && "output" in payload ? payload.output : status.result) ?? null;
1543
+ }
1544
+ await new Promise((resolve2) => setTimeout(resolve2, intervalMs));
1545
+ }
1546
+ }
1547
+ async cancel() {
1548
+ await this.client.cancelPlay(this.id);
1549
+ }
1550
+ async stop(options) {
1551
+ return this.client.stopPlay(this.id, options);
1552
+ }
1553
+ };
1554
+ function createNamedPlayHandle(clientFactory, name) {
1555
+ return {
1556
+ name,
1557
+ get: () => clientFactory().getPlay(name),
1558
+ runs: () => clientFactory().listPlayRuns(name),
1559
+ versions: () => clientFactory().listPlayVersions(name),
1560
+ publish: (options) => clientFactory().publishPlayVersion(name, options),
1561
+ clearHistory: (options) => clientFactory().clearPlayHistory(name, options),
1562
+ async run(input, options) {
1563
+ const client = clientFactory();
1564
+ const started = await client.startPlayRun({
1565
+ name,
1566
+ ...options?.revisionId ? { revisionId: options.revisionId } : {},
1567
+ input
1568
+ });
1569
+ return new DeeplinePlayJobImpl(client, started.workflowId);
1570
+ },
1571
+ async runSync(input, options) {
1572
+ const job = await this.run(input, options);
1573
+ return job.get();
1574
+ }
1575
+ };
1576
+ }
1577
+ var DeeplineContext = class {
1578
+ client;
1579
+ constructor(options) {
1580
+ this.client = new DeeplineClient(options);
1581
+ }
1582
+ /**
1583
+ * Tool operations namespace.
1584
+ *
1585
+ * @example
1586
+ * ```typescript
1587
+ * const tools = await ctx.tools.list();
1588
+ * const meta = await ctx.tools.get('apollo_people_search');
1589
+ * const result = await ctx.tools.execute({
1590
+ * tool: 'test_company_search',
1591
+ * input: { domain: 'stripe.com' },
1592
+ * });
1593
+ * const rows = result.tryList({ listExtractorPaths: ['people'] });
1594
+ * const email = result.getEmail();
1595
+ * ```
1596
+ */
1597
+ get tools() {
1598
+ return {
1599
+ /** List all available tools. */
1600
+ list: () => this.client.listTools(),
1601
+ /** Get detailed metadata for a tool. */
1602
+ get: (toolId) => this.client.getTool(toolId),
1603
+ /** Execute a tool and return an ergonomic result wrapper. */
1604
+ execute: async (request) => createToolCallResult(
1605
+ await this.client.executeTool(request.tool, request.input)
1606
+ )
1607
+ };
1608
+ }
1609
+ get plays() {
1610
+ return {
1611
+ list: () => this.client.listPlays(),
1612
+ get: (name) => this.play(name)
1613
+ };
1614
+ }
1615
+ get prebuilt() {
1616
+ const explicit = {
1617
+ companyToContact: {
1618
+ playName: "prebuilt/company-to-contact",
1619
+ name: "prebuilt/company-to-contact"
1620
+ },
1621
+ personToPhone: {
1622
+ playName: "prebuilt/person-to-phone",
1623
+ name: "prebuilt/person-to-phone"
1624
+ },
1625
+ personToEmail: {
1626
+ playName: "prebuilt/person-to-email",
1627
+ name: "prebuilt/person-to-email"
1628
+ },
1629
+ personLinkedinToEmail: {
1630
+ playName: "prebuilt/person-linkedin-to-email",
1631
+ name: "prebuilt/person-linkedin-to-email"
1632
+ }
1633
+ };
1634
+ return new Proxy(
1635
+ {},
1636
+ {
1637
+ get: (_target, prop) => {
1638
+ if (typeof prop !== "string") return void 0;
1639
+ if (prop in explicit) {
1640
+ return explicit[prop];
1641
+ }
1642
+ const playName = prop.startsWith("prebuilt/") ? prop : `prebuilt/${prop}`;
1643
+ return {
1644
+ playName,
1645
+ name: playName
1646
+ };
1647
+ }
1648
+ }
1649
+ );
1650
+ }
1651
+ /**
1652
+ * Get a named play handle for remote lifecycle operations.
1653
+ *
1654
+ * @typeParam TInput - Expected input type
1655
+ * @typeParam TOutput - Expected output type
1656
+ * @param name - Play name (as registered on the server)
1657
+ * @returns Named play handle with run, versions, get, publish, etc.
1658
+ *
1659
+ * @example
1660
+ * ```typescript
1661
+ * const play = ctx.play<{ domain: string }>('email-waterfall');
1662
+ * const job = await play.run({ domain: 'stripe.com' });
1663
+ * const result = await job.get();
1664
+ * ```
1665
+ */
1666
+ play(name) {
1667
+ return createNamedPlayHandle(() => this.client, name);
1668
+ }
1669
+ async runPlay(playOrRef, input) {
1670
+ const name = typeof playOrRef === "string" ? playOrRef : playOrRef.playName ?? playOrRef.name ?? "";
1671
+ return await this.play(name).runSync(input);
1672
+ }
1673
+ };
1674
+ var Deepline = class {
1675
+ /**
1676
+ * Create a connected SDK context.
1677
+ *
1678
+ * Resolves configuration from options, environment variables, and CLI config
1679
+ * files. See {@link resolveConfig} for the resolution order.
1680
+ *
1681
+ * @param options - Optional overrides for API key, base URL, etc.
1682
+ * @returns Ready-to-use SDK context
1683
+ * @throws {@link ConfigError} if no API key can be resolved
1684
+ *
1685
+ * @example
1686
+ * ```typescript
1687
+ * // Auto-config (uses env vars / CLI auth):
1688
+ * const ctx = await Deepline.connect();
1689
+ *
1690
+ * // Explicit config:
1691
+ * const ctx2 = await Deepline.connect({
1692
+ * apiKey: 'dl_test_...',
1693
+ * baseUrl: 'http://localhost:3000',
1694
+ * });
1695
+ * ```
1696
+ */
1697
+ static async connect(options) {
1698
+ return new DeeplineContext(options);
288
1699
  }
289
1700
  };
1701
+ function defineInput(schema) {
1702
+ if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
1703
+ throw new Error(
1704
+ "defineInput<T>(schema) requires a JSON-schema-like object."
1705
+ );
1706
+ }
1707
+ return { schema };
1708
+ }
1709
+ function definePlay(nameOrConfig, maybeFn, maybeBindings) {
1710
+ const config = typeof nameOrConfig === "string" ? {
1711
+ name: nameOrConfig,
1712
+ fn: maybeFn,
1713
+ bindings: maybeBindings,
1714
+ inputSchema: void 0,
1715
+ billing: maybeBindings?.billing
1716
+ } : {
1717
+ name: nameOrConfig.id,
1718
+ fn: nameOrConfig.run,
1719
+ bindings: nameOrConfig.bindings,
1720
+ inputSchema: nameOrConfig.input.schema,
1721
+ billing: nameOrConfig.billing
1722
+ };
1723
+ const name = config.name;
1724
+ const fn = config.fn;
1725
+ const bindings = config.bindings;
1726
+ const billing = config.billing;
1727
+ const inputSchema = config.inputSchema;
1728
+ if (typeof fn !== "function") {
1729
+ throw new Error("definePlay(...) requires an async run function.");
1730
+ }
1731
+ if (name.includes("/")) {
1732
+ throw new Error(
1733
+ 'definePlay(name, ...) play names cannot contain "/". Slash is reserved for qualified references like "prebuilt/example" or "self/example".'
1734
+ );
1735
+ }
1736
+ const normalizedName = name.trim().replace(/[^a-z0-9]+/gi, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
1737
+ if (!normalizedName) {
1738
+ throw new Error(
1739
+ "definePlay(name, ...) requires a play name with at least one letter or number. Use only letters, numbers, underscores, or hyphens."
1740
+ );
1741
+ }
1742
+ if (normalizedName.length > 63) {
1743
+ throw new Error(
1744
+ `definePlay("${name}", ...) is too long after normalization (${normalizedName.length}/63). Shorten the play name to 63 characters or fewer. Normalized value: "${normalizedName}".`
1745
+ );
1746
+ }
1747
+ const metadata = {
1748
+ name,
1749
+ ...bindings ? { bindings } : {},
1750
+ ...inputSchema ? { inputSchema } : {},
1751
+ ...billing ? { billing } : {}
1752
+ };
1753
+ const play = fn;
1754
+ Object.defineProperty(play, PLAY_METADATA_SYMBOL, {
1755
+ value: metadata,
1756
+ enumerable: false,
1757
+ configurable: false,
1758
+ writable: false
1759
+ });
1760
+ Object.defineProperty(play, "playName", {
1761
+ value: name,
1762
+ enumerable: true,
1763
+ configurable: false,
1764
+ writable: false
1765
+ });
1766
+ Object.defineProperty(play, "bindings", {
1767
+ value: bindings,
1768
+ enumerable: true,
1769
+ configurable: false,
1770
+ writable: false
1771
+ });
1772
+ const handle = createNamedPlayHandle(
1773
+ () => new DeeplineClient(),
1774
+ name
1775
+ );
1776
+ for (const key of [
1777
+ "name",
1778
+ "get",
1779
+ "runs",
1780
+ "versions",
1781
+ "publish",
1782
+ "run",
1783
+ "runSync"
1784
+ ]) {
1785
+ Object.defineProperty(play, key, {
1786
+ value: handle[key],
1787
+ enumerable: false,
1788
+ configurable: false,
1789
+ writable: false
1790
+ });
1791
+ }
1792
+ return play;
1793
+ }
1794
+ var defineWorkflow = definePlay;
1795
+ function getDefinedPlayMetadata(value) {
1796
+ if (typeof value !== "function") {
1797
+ return null;
1798
+ }
1799
+ const metadata = value[PLAY_METADATA_SYMBOL];
1800
+ if (!metadata || typeof metadata !== "object") {
1801
+ return null;
1802
+ }
1803
+ const candidate = metadata;
1804
+ if (!candidate.name || typeof candidate.name !== "string") {
1805
+ return null;
1806
+ }
1807
+ return candidate;
1808
+ }
290
1809
  export {
291
1810
  AuthError,
292
1811
  ConfigError,
1812
+ Deepline,
293
1813
  DeeplineClient,
1814
+ DeeplineContext,
294
1815
  DeeplineError,
295
1816
  PROD_URL,
296
1817
  RateLimitError,
297
- resolveConfig
1818
+ SDK_API_CONTRACT,
1819
+ SDK_VERSION,
1820
+ createToolCallResult,
1821
+ defineInput,
1822
+ definePlay,
1823
+ defineWorkflow,
1824
+ extractSummaryFields,
1825
+ getDefinedPlayMetadata,
1826
+ resolveConfig,
1827
+ steps,
1828
+ tryConvertToList,
1829
+ when,
1830
+ writeCsvOutputFile,
1831
+ writeJsonOutputFile
298
1832
  };
299
1833
  //# sourceMappingURL=index.mjs.map