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