deepline 0.1.63 → 0.1.64

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.
@@ -183,11 +183,11 @@ function isRecord(value: unknown): value is Record<string, unknown> {
183
183
  function isPlayRunPackage(value: unknown): value is PlayRunPackage {
184
184
  return Boolean(
185
185
  value &&
186
- typeof value === 'object' &&
187
- !Array.isArray(value) &&
188
- (value as Record<string, unknown>).kind === 'play_run' &&
189
- (value as Record<string, unknown>).run &&
190
- typeof (value as { run?: { id?: unknown } }).run?.id === 'string',
186
+ typeof value === 'object' &&
187
+ !Array.isArray(value) &&
188
+ (value as Record<string, unknown>).kind === 'play_run' &&
189
+ (value as Record<string, unknown>).run &&
190
+ typeof (value as { run?: { id?: unknown } }).run?.id === 'string',
191
191
  );
192
192
  }
193
193
 
@@ -209,7 +209,7 @@ function normalizePlayStatus(raw: Record<string, unknown>): PlayStatus {
209
209
  ? raw.runId
210
210
  : typeof raw.workflowId === 'string'
211
211
  ? raw.workflowId
212
- : packageRun?.id ?? '';
212
+ : (packageRun?.id ?? '');
213
213
  return {
214
214
  ...(raw as unknown as Omit<PlayStatus, 'runId' | 'status'>),
215
215
  runId,
@@ -228,7 +228,9 @@ function normalizePlayRunStart(raw: Record<string, unknown>): PlayRunStart {
228
228
  return raw as unknown as PlayRunStart;
229
229
  }
230
230
  const status =
231
- typeof runPackage.run.status === 'string' ? runPackage.run.status : 'running';
231
+ typeof runPackage.run.status === 'string'
232
+ ? runPackage.run.status
233
+ : 'running';
232
234
  return {
233
235
  workflowId: runPackage.run.id,
234
236
  name: runPackage.run.playName,
@@ -236,9 +238,7 @@ function normalizePlayRunStart(raw: Record<string, unknown>): PlayRunStart {
236
238
  ...(runPackage.run.dashboardUrl
237
239
  ? { dashboardUrl: runPackage.run.dashboardUrl }
238
240
  : {}),
239
- ...(TERMINAL_PLAY_STATUSES.has(status)
240
- ? { finalStatus: runPackage }
241
- : {}),
241
+ ...(TERMINAL_PLAY_STATUSES.has(status) ? { finalStatus: runPackage } : {}),
242
242
  package: runPackage,
243
243
  };
244
244
  }
@@ -267,7 +267,9 @@ type PlayLiveStatusState = {
267
267
  latest: PlayStatus | null;
268
268
  };
269
269
 
270
- function getPlayLiveEventPayload(event: PlayLiveEvent): Record<string, unknown> {
270
+ function getPlayLiveEventPayload(
271
+ event: PlayLiveEvent,
272
+ ): Record<string, unknown> {
271
273
  return event.payload && typeof event.payload === 'object'
272
274
  ? (event.payload as Record<string, unknown>)
273
275
  : {};
@@ -309,16 +311,14 @@ function updatePlayLiveStatusState(
309
311
  ? payload.runId
310
312
  : isPlayRunPackage(payload)
311
313
  ? payload.run.id
312
- : state.runId;
314
+ : state.runId;
313
315
  const status =
314
316
  normalizeLiveStatus(payload.status) ??
315
317
  (isPlayRunPackage(payload)
316
318
  ? normalizeLiveStatus(payload.run.status)
317
319
  : null) ??
318
320
  state.status;
319
- const progressPayload = isRecord(payload.progress)
320
- ? payload.progress
321
- : {};
321
+ const progressPayload = isRecord(payload.progress) ? payload.progress : {};
322
322
  const payloadLogs = readStringArray(payload.logs);
323
323
  const progressLogs = readStringArray(progressPayload.logs);
324
324
  const logs = payloadLogs.length > 0 ? payloadLogs : progressLogs;
@@ -342,7 +342,10 @@ function updatePlayLiveStatusState(
342
342
 
343
343
  const progressRecord = progressPayload;
344
344
  const next: PlayStatus = {
345
- ...(payload as unknown as Omit<PlayStatus, 'runId' | 'status' | 'progress'>),
345
+ ...(payload as unknown as Omit<
346
+ PlayStatus,
347
+ 'runId' | 'status' | 'progress'
348
+ >),
346
349
  runId,
347
350
  status,
348
351
  ...(isPlayRunPackage(payload)
@@ -485,7 +488,9 @@ export class DeeplineClient {
485
488
  return `deepline plays run ${target} --input '{...}' --watch`;
486
489
  }
487
490
 
488
- private starterPlayPath(play: Pick<PlayListItem, 'name' | 'reference'>): string {
491
+ private starterPlayPath(
492
+ play: Pick<PlayListItem, 'name' | 'reference'>,
493
+ ): string {
489
494
  const target = play.reference || play.name;
490
495
  const unqualifiedName = target.split('/').pop() || play.name;
491
496
  const safeName = unqualifiedName
@@ -741,47 +746,50 @@ export class DeeplineClient {
741
746
  * artifactStorageKey: 'plays/v1/orgs/acme/plays/my-play/artifacts/playgraph_abc123.json',
742
747
  * });
743
748
  * ```
744
- */
749
+ */
745
750
  async startPlayRun(request: StartPlayRunRequest): Promise<PlayRunStart> {
746
- const response = await this.http.post<Record<string, unknown>>('/api/v2/plays/run', {
747
- ...(request.name ? { name: request.name } : {}),
748
- ...(request.revisionId ? { revisionId: request.revisionId } : {}),
749
- ...(request.artifactStorageKey
750
- ? { artifactStorageKey: request.artifactStorageKey }
751
- : {}),
752
- ...(request.sourceCode ? { sourceCode: request.sourceCode } : {}),
753
- ...(request.sourceFiles ? { sourceFiles: request.sourceFiles } : {}),
754
- ...('staticPipeline' in request
755
- ? { staticPipeline: request.staticPipeline }
756
- : {}),
757
- ...(request.artifactHash ? { artifactHash: request.artifactHash } : {}),
758
- ...(request.graphHash ? { graphHash: request.graphHash } : {}),
759
- ...(request.runtimeArtifact
760
- ? { runtimeArtifact: request.runtimeArtifact }
761
- : {}),
762
- ...(request.compilerManifest
763
- ? { compilerManifest: request.compilerManifest }
764
- : {}),
765
- ...(request.inputFileUpload
766
- ? { inputFileUpload: request.inputFileUpload }
767
- : {}),
768
- ...(request.packagedFileUploads?.length
769
- ? { packagedFileUploads: request.packagedFileUploads }
770
- : {}),
771
- ...(request.input ? { input: request.input } : {}),
772
- ...(request.inputFile ? { inputFile: request.inputFile } : {}),
773
- ...(request.packagedFiles?.length
774
- ? { packagedFiles: request.packagedFiles }
775
- : {}),
776
- ...(request.force ? { force: true } : {}),
777
- ...(typeof request.waitForCompletionMs === 'number'
778
- ? { waitForCompletionMs: request.waitForCompletionMs }
779
- : {}),
780
- // Profile selection is the API's job, not the CLI's. The server
781
- // hardcodes workers_edge as the default; tests that want a
782
- // different profile pass `request.profile` explicitly.
783
- ...(request.profile ? { profile: request.profile } : {}),
784
- });
751
+ const response = await this.http.post<Record<string, unknown>>(
752
+ '/api/v2/plays/run',
753
+ {
754
+ ...(request.name ? { name: request.name } : {}),
755
+ ...(request.revisionId ? { revisionId: request.revisionId } : {}),
756
+ ...(request.artifactStorageKey
757
+ ? { artifactStorageKey: request.artifactStorageKey }
758
+ : {}),
759
+ ...(request.sourceCode ? { sourceCode: request.sourceCode } : {}),
760
+ ...(request.sourceFiles ? { sourceFiles: request.sourceFiles } : {}),
761
+ ...('staticPipeline' in request
762
+ ? { staticPipeline: request.staticPipeline }
763
+ : {}),
764
+ ...(request.artifactHash ? { artifactHash: request.artifactHash } : {}),
765
+ ...(request.graphHash ? { graphHash: request.graphHash } : {}),
766
+ ...(request.runtimeArtifact
767
+ ? { runtimeArtifact: request.runtimeArtifact }
768
+ : {}),
769
+ ...(request.compilerManifest
770
+ ? { compilerManifest: request.compilerManifest }
771
+ : {}),
772
+ ...(request.inputFileUpload
773
+ ? { inputFileUpload: request.inputFileUpload }
774
+ : {}),
775
+ ...(request.packagedFileUploads?.length
776
+ ? { packagedFileUploads: request.packagedFileUploads }
777
+ : {}),
778
+ ...(request.input ? { input: request.input } : {}),
779
+ ...(request.inputFile ? { inputFile: request.inputFile } : {}),
780
+ ...(request.packagedFiles?.length
781
+ ? { packagedFiles: request.packagedFiles }
782
+ : {}),
783
+ ...(request.force ? { force: true } : {}),
784
+ ...(typeof request.waitForCompletionMs === 'number'
785
+ ? { waitForCompletionMs: request.waitForCompletionMs }
786
+ : {}),
787
+ // Profile selection is the API's job, not the CLI's. The server
788
+ // hardcodes workers_edge as the default; tests that want a
789
+ // different profile pass `request.profile` explicitly.
790
+ ...(request.profile ? { profile: request.profile } : {}),
791
+ },
792
+ );
785
793
  return normalizePlayRunStart(response);
786
794
  }
787
795
 
@@ -1173,10 +1181,9 @@ export class DeeplineClient {
1173
1181
  }
1174
1182
  return formData;
1175
1183
  };
1176
- const response = await this.http.postFormData<{ files: PlayStagedFileRef[] }>(
1177
- '/api/v2/plays/files/stage',
1178
- buildFormData,
1179
- );
1184
+ const response = await this.http.postFormData<{
1185
+ files: PlayStagedFileRef[];
1186
+ }>('/api/v2/plays/files/stage', buildFormData);
1180
1187
  return response.files;
1181
1188
  }
1182
1189
 
@@ -1733,8 +1740,9 @@ export class DeeplineClient {
1733
1740
  options?.onProgress?.(status);
1734
1741
 
1735
1742
  if (TERMINAL_PLAY_STATUSES.has(status.status)) {
1736
- const finalStatus = await this.getPlayStatus(status.runId || workflowId)
1737
- .catch(() => status);
1743
+ const finalStatus = await this.getPlayStatus(
1744
+ status.runId || workflowId,
1745
+ ).catch(() => status);
1738
1746
  return playRunResultFromStatus(finalStatus, start, workflowId);
1739
1747
  }
1740
1748
  }
@@ -92,7 +92,11 @@ export class RateLimitError extends DeeplineError {
92
92
  public retryAfterMs: number;
93
93
 
94
94
  constructor(retryAfterMs = 5000, message?: string) {
95
- super(message ?? `Rate limited. Retry after ${retryAfterMs}ms.`, 429, 'RATE_LIMIT');
95
+ super(
96
+ message ?? `Rate limited. Retry after ${retryAfterMs}ms.`,
97
+ 429,
98
+ 'RATE_LIMIT',
99
+ );
96
100
  this.name = 'RateLimitError';
97
101
  this.retryAfterMs = retryAfterMs;
98
102
  }
@@ -63,40 +63,45 @@ export class HttpClient {
63
63
 
64
64
  private authHeaders(extra?: Record<string, string>): Record<string, string> {
65
65
  const headers: Record<string, string> = {
66
- 'Authorization': `Bearer ${this.config.apiKey}`,
66
+ Authorization: `Bearer ${this.config.apiKey}`,
67
67
  'User-Agent': `deepline-ts-sdk/${SDK_VERSION}`,
68
68
  'X-Deepline-SDK-Version': SDK_VERSION,
69
69
  'X-Deepline-API-Contract': SDK_API_CONTRACT,
70
70
  ...extra,
71
71
  };
72
- const bypassToken = typeof process !== 'undefined'
73
- ? process.env?.VERCEL_PROTECTION_BYPASS_TOKEN
74
- : undefined;
72
+ const bypassToken =
73
+ typeof process !== 'undefined'
74
+ ? process.env?.VERCEL_PROTECTION_BYPASS_TOKEN
75
+ : undefined;
75
76
  if (bypassToken) {
76
77
  headers['x-vercel-protection-bypass'] = bypassToken;
77
78
  }
78
- const playArtifactR2Prefix = typeof process !== 'undefined'
79
- ? process.env?.DEEPLINE_PLAY_ARTIFACT_R2_PREFIX
80
- : undefined;
79
+ const playArtifactR2Prefix =
80
+ typeof process !== 'undefined'
81
+ ? process.env?.DEEPLINE_PLAY_ARTIFACT_R2_PREFIX
82
+ : undefined;
81
83
  if (playArtifactR2Prefix) {
82
84
  headers['x-deepline-play-artifact-r2-prefix'] = playArtifactR2Prefix;
83
85
  }
84
- const coordinatorUrl = typeof process !== 'undefined'
85
- ? process.env?.DEEPLINE_COORDINATOR_URL
86
- : undefined;
86
+ const coordinatorUrl =
87
+ typeof process !== 'undefined'
88
+ ? process.env?.DEEPLINE_COORDINATOR_URL
89
+ : undefined;
87
90
  if (coordinatorUrl?.trim()) {
88
91
  headers[COORDINATOR_URL_OVERRIDE_HEADER] = coordinatorUrl.trim();
89
- const coordinatorInternalToken = typeof process !== 'undefined'
90
- ? process.env?.DEEPLINE_INTERNAL_TOKEN
91
- : undefined;
92
+ const coordinatorInternalToken =
93
+ typeof process !== 'undefined'
94
+ ? process.env?.DEEPLINE_INTERNAL_TOKEN
95
+ : undefined;
92
96
  if (coordinatorInternalToken?.trim()) {
93
97
  headers[COORDINATOR_INTERNAL_TOKEN_HEADER] =
94
98
  coordinatorInternalToken.trim();
95
99
  }
96
100
  }
97
- const workerCallbackUrl = typeof process !== 'undefined'
98
- ? process.env?.DEEPLINE_WORKER_CALLBACK_URL
99
- : undefined;
101
+ const workerCallbackUrl =
102
+ typeof process !== 'undefined'
103
+ ? process.env?.DEEPLINE_WORKER_CALLBACK_URL
104
+ : undefined;
100
105
  if (workerCallbackUrl?.trim()) {
101
106
  headers[WORKER_CALLBACK_URL_OVERRIDE_HEADER] = workerCallbackUrl.trim();
102
107
  }
@@ -114,7 +119,10 @@ export class HttpClient {
114
119
  * @throws {@link RateLimitError} on HTTP 429 after all retries exhausted
115
120
  * @throws {@link DeeplineError} on other API errors or connection failures
116
121
  */
117
- async request<T = unknown>(path: string, options?: RequestOptions): Promise<T> {
122
+ async request<T = unknown>(
123
+ path: string,
124
+ options?: RequestOptions,
125
+ ): Promise<T> {
118
126
  const baseUrl = this.config.baseUrl;
119
127
  const url = `${baseUrl}${path}`;
120
128
  const method = options?.method ?? 'GET';
@@ -1,6 +1,14 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { readFile, stat } from 'node:fs/promises';
3
- import { basename, dirname, extname, isAbsolute, join, relative, resolve } from 'node:path';
3
+ import {
4
+ basename,
5
+ dirname,
6
+ extname,
7
+ isAbsolute,
8
+ join,
9
+ relative,
10
+ resolve,
11
+ } from 'node:path';
4
12
 
5
13
  export interface PlayLocalFileReference {
6
14
  sourceFragment: string;
@@ -33,7 +41,17 @@ export interface PlayStagedFileRef {
33
41
 
34
42
  type ConstMap = Map<string, string>;
35
43
 
36
- const SOURCE_EXTENSIONS = ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs', '.json'];
44
+ const SOURCE_EXTENSIONS = [
45
+ '.ts',
46
+ '.tsx',
47
+ '.mts',
48
+ '.cts',
49
+ '.js',
50
+ '.jsx',
51
+ '.mjs',
52
+ '.cjs',
53
+ '.json',
54
+ ];
37
55
 
38
56
  function sha256(buffer: Buffer): string {
39
57
  return createHash('sha256').update(buffer).digest('hex');
@@ -50,19 +68,28 @@ function contentTypeForFile(filePath: string): string {
50
68
  function stripCommentsToSpaces(source: string): string {
51
69
  return source
52
70
  .replace(/\/\*[\s\S]*?\*\//g, (match) => match.replace(/[^\n]/g, ' '))
53
- .replace(/(^|[^:])\/\/.*$/gm, (match, prefix: string) =>
54
- prefix + ' '.repeat(Math.max(0, match.length - prefix.length)),
71
+ .replace(
72
+ /(^|[^:])\/\/.*$/gm,
73
+ (match, prefix: string) =>
74
+ prefix + ' '.repeat(Math.max(0, match.length - prefix.length)),
55
75
  );
56
76
  }
57
77
 
58
78
  function unquoteStringLiteral(literal: string): string | null {
59
79
  const trimmed = literal.trim();
60
80
  const quote = trimmed[0];
61
- if ((quote !== '"' && quote !== "'") || trimmed[trimmed.length - 1] !== quote) {
81
+ if (
82
+ (quote !== '"' && quote !== "'") ||
83
+ trimmed[trimmed.length - 1] !== quote
84
+ ) {
62
85
  return null;
63
86
  }
64
87
  try {
65
- return JSON.parse(quote === '"' ? trimmed : `"${trimmed.slice(1, -1).replace(/"/g, '\\"')}"`);
88
+ return JSON.parse(
89
+ quote === '"'
90
+ ? trimmed
91
+ : `"${trimmed.slice(1, -1).replace(/"/g, '\\"')}"`,
92
+ );
66
93
  } catch {
67
94
  return trimmed.slice(1, -1);
68
95
  }
@@ -116,7 +143,10 @@ function isRuntimeInputExpression(expression: string): boolean {
116
143
  return /(^|[^\w$])input([^\w$]|$)/.test(expression);
117
144
  }
118
145
 
119
- function resolveStringExpression(expression: string, constants: ConstMap): string | null {
146
+ function resolveStringExpression(
147
+ expression: string,
148
+ constants: ConstMap,
149
+ ): string | null {
120
150
  const value = stripOuterParens(expression);
121
151
  if (/^(['"])(?:\\.|(?!\1)[\s\S])*\1$/.test(value)) {
122
152
  return unquoteStringLiteral(value);
@@ -129,7 +159,9 @@ function resolveStringExpression(expression: string, constants: ConstMap): strin
129
159
  }
130
160
  const parts = splitTopLevelPlus(value);
131
161
  if (parts) {
132
- const resolved = parts.map((part) => resolveStringExpression(part, constants));
162
+ const resolved = parts.map((part) =>
163
+ resolveStringExpression(part, constants),
164
+ );
133
165
  return resolved.every((part): part is string => part != null)
134
166
  ? resolved.join('')
135
167
  : null;
@@ -140,7 +172,9 @@ function resolveStringExpression(expression: string, constants: ConstMap): strin
140
172
  function collectTopLevelStringConstants(sourceCode: string): ConstMap {
141
173
  const constants: ConstMap = new Map();
142
174
  const source = stripCommentsToSpaces(sourceCode);
143
- for (const match of source.matchAll(/(?:^|\n)\s*const\s+([A-Za-z_$][\w$]*)\s*=\s*([^;\n]+)/g)) {
175
+ for (const match of source.matchAll(
176
+ /(?:^|\n)\s*const\s+([A-Za-z_$][\w$]*)\s*=\s*([^;\n]+)/g,
177
+ )) {
144
178
  const resolved = resolveStringExpression(match[2]!, constants);
145
179
  if (resolved != null) {
146
180
  constants.set(match[1]!, resolved);
@@ -190,7 +224,10 @@ function findCallOpenParen(source: string, afterCsvIndex: number): number {
190
224
  return source[index] === '(' ? index : -1;
191
225
  }
192
226
 
193
- function firstCallArgument(source: string, openParen: number): { text: string; start: number; end: number } | null {
227
+ function firstCallArgument(
228
+ source: string,
229
+ openParen: number,
230
+ ): { text: string; start: number; end: number } | null {
194
231
  let depth = 0;
195
232
  let quote: string | null = null;
196
233
  let escaped = false;
@@ -235,7 +272,9 @@ function localImportSpecifiers(sourceCode: string): string[] {
235
272
  )) {
236
273
  if (match[1]?.startsWith('.')) specifiers.push(match[1]);
237
274
  }
238
- for (const match of source.matchAll(/\brequire\s*\(\s*(['"])(\.[^'"]*)\1\s*\)/g)) {
275
+ for (const match of source.matchAll(
276
+ /\brequire\s*\(\s*(['"])(\.[^'"]*)\1\s*\)/g,
277
+ )) {
239
278
  specifiers.push(match[2]!);
240
279
  }
241
280
  return specifiers;
@@ -252,20 +291,34 @@ async function fileExists(filePath: string): Promise<boolean> {
252
291
 
253
292
  function isPathInsideDirectory(filePath: string, directory: string): boolean {
254
293
  const relativePath = relative(directory, filePath);
255
- return relativePath === '' || (!relativePath.startsWith('..') && !isAbsolute(relativePath));
294
+ return (
295
+ relativePath === '' ||
296
+ (!relativePath.startsWith('..') && !isAbsolute(relativePath))
297
+ );
256
298
  }
257
299
 
258
- async function resolveLocalImport(fromFile: string, specifier: string): Promise<string> {
259
- const base = isAbsolute(specifier) ? resolve(specifier) : resolve(dirname(fromFile), specifier);
300
+ async function resolveLocalImport(
301
+ fromFile: string,
302
+ specifier: string,
303
+ ): Promise<string> {
304
+ const base = isAbsolute(specifier)
305
+ ? resolve(specifier)
306
+ : resolve(dirname(fromFile), specifier);
260
307
  const candidates: string[] = [base];
261
308
  const explicitExtension = extname(base).toLowerCase();
262
309
 
263
310
  if (!explicitExtension) {
264
- candidates.push(...SOURCE_EXTENSIONS.map((extension) => `${base}${extension}`));
265
- candidates.push(...SOURCE_EXTENSIONS.map((extension) => join(base, `index${extension}`)));
311
+ candidates.push(
312
+ ...SOURCE_EXTENSIONS.map((extension) => `${base}${extension}`),
313
+ );
314
+ candidates.push(
315
+ ...SOURCE_EXTENSIONS.map((extension) => join(base, `index${extension}`)),
316
+ );
266
317
  } else if (['.js', '.jsx', '.mjs', '.cjs'].includes(explicitExtension)) {
267
318
  const stem = base.slice(0, -explicitExtension.length);
268
- candidates.push(...SOURCE_EXTENSIONS.map((extension) => `${stem}${extension}`));
319
+ candidates.push(
320
+ ...SOURCE_EXTENSIONS.map((extension) => `${stem}${extension}`),
321
+ );
269
322
  }
270
323
 
271
324
  for (const candidate of candidates) {
@@ -274,7 +327,9 @@ async function resolveLocalImport(fromFile: string, specifier: string): Promise<
274
327
  }
275
328
  }
276
329
 
277
- throw new Error(`Could not resolve local import "${specifier}" from ${fromFile}`);
330
+ throw new Error(
331
+ `Could not resolve local import "${specifier}" from ${fromFile}`,
332
+ );
278
333
  }
279
334
 
280
335
  export async function discoverPackagedLocalFiles(
@@ -298,12 +353,17 @@ export async function discoverPackagedLocalFiles(
298
353
  const constants = collectTopLevelStringConstants(sourceCode);
299
354
  const childVisits: Promise<void>[] = [];
300
355
 
301
- for (const match of scanSource.matchAll(/\b([A-Za-z_$][\w$]*)\s*\.\s*csv\b/g)) {
356
+ for (const match of scanSource.matchAll(
357
+ /\b([A-Za-z_$][\w$]*)\s*\.\s*csv\b/g,
358
+ )) {
302
359
  const target = match[1]!;
303
360
  if (target !== 'ctx' && !target.endsWith('Ctx')) {
304
361
  continue;
305
362
  }
306
- const openParen = findCallOpenParen(scanSource, match.index! + match[0].length);
363
+ const openParen = findCallOpenParen(
364
+ scanSource,
365
+ match.index! + match[0].length,
366
+ );
307
367
  if (openParen < 0) {
308
368
  continue;
309
369
  }
@@ -317,15 +377,22 @@ export async function discoverPackagedLocalFiles(
317
377
  const resolvedPath = resolveStringExpression(argument.text, constants);
318
378
  if (resolvedPath == null) {
319
379
  unresolved.push({
320
- sourceFragment: sourceCode.slice(argument.start, argument.end).trim(),
380
+ sourceFragment: sourceCode
381
+ .slice(argument.start, argument.end)
382
+ .trim(),
321
383
  message:
322
384
  'Could not resolve this ctx.csv(...) path at submit time. Use a string literal, a top-level const string, or pass a runtime input like input.file.',
323
385
  });
324
386
  } else {
325
387
  const absoluteCsvPath = resolve(dirname(absolutePath), resolvedPath);
326
- if (isAbsolute(resolvedPath) || !isPathInsideDirectory(absoluteCsvPath, packagingRoot)) {
388
+ if (
389
+ isAbsolute(resolvedPath) ||
390
+ !isPathInsideDirectory(absoluteCsvPath, packagingRoot)
391
+ ) {
327
392
  unresolved.push({
328
- sourceFragment: sourceCode.slice(argument.start, argument.end).trim(),
393
+ sourceFragment: sourceCode
394
+ .slice(argument.start, argument.end)
395
+ .trim(),
329
396
  message:
330
397
  'ctx.csv(...) packaged file paths must be relative paths inside the play directory. Pass external files at runtime with input.file instead.',
331
398
  });
@@ -334,7 +401,9 @@ export async function discoverPackagedLocalFiles(
334
401
  const buffer = await readFile(absoluteCsvPath);
335
402
  const stats = await stat(absoluteCsvPath);
336
403
  files.set(absoluteCsvPath, {
337
- sourceFragment: sourceCode.slice(argument.start, argument.end).trim(),
404
+ sourceFragment: sourceCode
405
+ .slice(argument.start, argument.end)
406
+ .trim(),
338
407
  logicalPath: resolvedPath,
339
408
  absolutePath: absoluteCsvPath,
340
409
  bytes: stats.size,
@@ -50,10 +50,10 @@ export type SdkRelease = {
50
50
  };
51
51
 
52
52
  export const SDK_RELEASE = {
53
- version: '0.1.63',
53
+ version: '0.1.64',
54
54
  apiContract: '2026-05-play-bootstrap-dataset-summary',
55
55
  supportPolicy: {
56
- latest: '0.1.63',
56
+ latest: '0.1.64',
57
57
  minimumSupported: '0.1.53',
58
58
  deprecatedBelow: '0.1.53',
59
59
  },
@@ -86,7 +86,9 @@ function normalizeScalarString(value: unknown): string | null {
86
86
  */
87
87
  function getByDottedPath(root: unknown, dottedPath: string): unknown {
88
88
  let current = root;
89
- for (const segment of String(dottedPath || '').split('.').filter(Boolean)) {
89
+ for (const segment of String(dottedPath || '')
90
+ .split('.')
91
+ .filter(Boolean)) {
90
92
  if (!isPlainObject(current) || !(segment in current)) {
91
93
  return null;
92
94
  }
@@ -111,8 +113,12 @@ function normalizeRows(value: unknown): Array<Record<string, unknown>> | null {
111
113
  * Generate candidate root objects to search for lists.
112
114
  * Tries: raw payload → V2 toolResponse.raw → legacy payload.output.body → legacy payload.result → legacy payload.result.data.
113
115
  */
114
- function candidateRoots(payload: unknown): Array<{ path: string | null; value: unknown }> {
115
- const roots: Array<{ path: string | null; value: unknown }> = [{ path: null, value: payload }];
116
+ function candidateRoots(
117
+ payload: unknown,
118
+ ): Array<{ path: string | null; value: unknown }> {
119
+ const roots: Array<{ path: string | null; value: unknown }> = [
120
+ { path: null, value: payload },
121
+ ];
116
122
  if (isPlainObject(payload) && isPlainObject(payload.toolResponse)) {
117
123
  roots.push({ path: 'toolResponse', value: payload.toolResponse });
118
124
  if (Object.prototype.hasOwnProperty.call(payload.toolResponse, 'raw')) {
@@ -150,7 +156,10 @@ function findBestArrayCandidate(
150
156
  if (depth > 5) return null;
151
157
 
152
158
  const directRows = normalizeRows(value);
153
- const hasObjectRow = directRows?.some((row) => Object.keys(row).some((key) => key !== 'value')) ?? false;
159
+ const hasObjectRow =
160
+ directRows?.some((row) =>
161
+ Object.keys(row).some((key) => key !== 'value'),
162
+ ) ?? false;
154
163
  let best: { path: string; rows: Array<Record<string, unknown>> } | null =
155
164
  directRows && directRows.length > 0 && hasObjectRow
156
165
  ? { path: pathPrefix, rows: directRows }
@@ -220,7 +229,10 @@ export function tryConvertToList(
220
229
  options?: { listExtractorPaths?: string[] },
221
230
  ): ListConversionResult | null {
222
231
  const listExtractorPaths = Array.isArray(options?.listExtractorPaths)
223
- ? options?.listExtractorPaths.filter((entry): entry is string => typeof entry === 'string' && entry.trim().length > 0)
232
+ ? options?.listExtractorPaths.filter(
233
+ (entry): entry is string =>
234
+ typeof entry === 'string' && entry.trim().length > 0,
235
+ )
224
236
  : [];
225
237
 
226
238
  if (listExtractorPaths.length > 0) {
@@ -229,7 +241,9 @@ export function tryConvertToList(
229
241
  const resolved = getByDottedPath(root.value, extractorPath);
230
242
  const rows = normalizeRows(resolved);
231
243
  if (rows && rows.length > 0) {
232
- const sourcePath = root.path ? `${root.path}.${extractorPath}` : extractorPath;
244
+ const sourcePath = root.path
245
+ ? `${root.path}.${extractorPath}`
246
+ : extractorPath;
233
247
  return { rows, strategy: 'configured_paths', sourcePath };
234
248
  }
235
249
  }
@@ -322,11 +336,14 @@ export function writeCsvOutputFile(
322
336
  }
323
337
 
324
338
  const escapeCell = (value: unknown): string => {
325
- const normalized = value == null
326
- ? ''
327
- : typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean'
328
- ? String(value)
329
- : JSON.stringify(value);
339
+ const normalized =
340
+ value == null
341
+ ? ''
342
+ : typeof value === 'string' ||
343
+ typeof value === 'number' ||
344
+ typeof value === 'boolean'
345
+ ? String(value)
346
+ : JSON.stringify(value);
330
347
  if (/[",\n]/.test(normalized)) {
331
348
  return `"${normalized.replace(/"/g, '""')}"`;
332
349
  }
@@ -345,7 +362,8 @@ export function writeCsvOutputFile(
345
362
  const preview = [
346
363
  previewColumns.join(','),
347
364
  ...previewRows.map((row) =>
348
- previewColumns.map((column) => escapeCell(row[column])).join(',')),
365
+ previewColumns.map((column) => escapeCell(row[column])).join(','),
366
+ ),
349
367
  ].join('\n');
350
368
 
351
369
  return {
@@ -378,14 +396,16 @@ export function extractSummaryFields(payload: unknown): Record<string, Scalar> {
378
396
  const candidates = candidateRoots(payload);
379
397
  for (const candidate of candidates) {
380
398
  if (!isPlainObject(candidate.value)) continue;
381
- const summaryEntries = Object.entries(candidate.value).filter(([, value]) => {
382
- return (
383
- value == null ||
384
- typeof value === 'string' ||
385
- typeof value === 'number' ||
386
- typeof value === 'boolean'
387
- );
388
- });
399
+ const summaryEntries = Object.entries(candidate.value).filter(
400
+ ([, value]) => {
401
+ return (
402
+ value == null ||
403
+ typeof value === 'string' ||
404
+ typeof value === 'number' ||
405
+ typeof value === 'boolean'
406
+ );
407
+ },
408
+ );
389
409
  if (summaryEntries.length === 0) continue;
390
410
  return Object.fromEntries(summaryEntries) as Record<string, Scalar>;
391
411
  }