deepline 0.1.63 → 0.1.65

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.
@@ -35,7 +35,10 @@ export type WorkerDatasetCapabilities = {
35
35
  materialization: 'bounded' | 'in_memory';
36
36
  };
37
37
 
38
- const datasetChunkReaders = new WeakMap<object, DatasetChunkReader<DatasetRow>>();
38
+ const datasetChunkReaders = new WeakMap<
39
+ object,
40
+ DatasetChunkReader<DatasetRow>
41
+ >();
39
42
  const datasetCountHints = new WeakMap<object, number | null>();
40
43
  const datasetCapabilities = new WeakMap<object, WorkerDatasetCapabilities>();
41
44
 
@@ -154,11 +157,7 @@ export function createPersistedDatasetHandle<T extends DatasetRow>(input: {
154
157
  sourceLabel?: string | null;
155
158
  workProgress?: PlayDatasetWorkProgressSummary;
156
159
  readRows: (input: { limit: number; offset: number }) => Promise<readonly T[]>;
157
- trace?: (
158
- phase: string,
159
- ms: number,
160
- extra?: Record<string, unknown>,
161
- ) => void;
160
+ trace?: (phase: string, ms: number, extra?: Record<string, unknown>) => void;
162
161
  nowMs: () => number;
163
162
  }): WorkerDatasetHandle<T> {
164
163
  const datasetKind = input.datasetKind ?? 'map';
@@ -202,7 +201,9 @@ export function createPersistedDatasetHandle<T extends DatasetRow>(input: {
202
201
  return rows.map(internalDatasetRow);
203
202
  }
204
203
 
205
- async function* readChunks(chunkSize: number): AsyncGenerator<T[], void, void> {
204
+ async function* readChunks(
205
+ chunkSize: number,
206
+ ): AsyncGenerator<T[], void, void> {
206
207
  const size = normalizedChunkSize(chunkSize);
207
208
  let offset = 0;
208
209
  while (offset < count) {
@@ -255,13 +256,18 @@ export function createPersistedDatasetHandle<T extends DatasetRow>(input: {
255
256
  }) as AsyncIterable<T>,
256
257
  },
257
258
  });
258
- return registerChunkReader(dataset, (chunkSize) => readChunks(chunkSize), count, {
259
- storage: 'runtime_sheet',
260
- chunkReadable: true,
261
- pageBacked: true,
262
- replayable: true,
263
- materialization: 'bounded',
264
- });
259
+ return registerChunkReader(
260
+ dataset,
261
+ (chunkSize) => readChunks(chunkSize),
262
+ count,
263
+ {
264
+ storage: 'runtime_sheet',
265
+ chunkReadable: true,
266
+ pageBacked: true,
267
+ replayable: true,
268
+ materialization: 'bounded',
269
+ },
270
+ );
265
271
  }
266
272
 
267
273
  export function createCsvDatasetHandle<T extends DatasetRow>(input: {
@@ -274,17 +280,15 @@ export function createCsvDatasetHandle<T extends DatasetRow>(input: {
274
280
  byteChunks: AsyncIterable<Uint8Array>,
275
281
  chunkSize: number,
276
282
  ) => AsyncIterable<T[]>;
277
- trace?: (
278
- phase: string,
279
- ms: number,
280
- extra?: Record<string, unknown>,
281
- ) => void;
283
+ trace?: (phase: string, ms: number, extra?: Record<string, unknown>) => void;
282
284
  nowMs: () => number;
283
285
  }): WorkerDatasetHandle<T> {
284
286
  const datasetId = `csv:${input.name}`;
285
287
  let cachedCount: number | null = null;
286
288
 
287
- async function* readChunks(chunkSize: number): AsyncGenerator<T[], void, void> {
289
+ async function* readChunks(
290
+ chunkSize: number,
291
+ ): AsyncGenerator<T[], void, void> {
288
292
  const startedAt = input.nowMs();
289
293
  let yieldedRows = 0;
290
294
  let yieldedChunks = 0;
@@ -386,13 +390,18 @@ export function createCsvDatasetHandle<T extends DatasetRow>(input: {
386
390
  }) as AsyncIterable<T>,
387
391
  },
388
392
  });
389
- return registerChunkReader(dataset, (chunkSize) => readChunks(chunkSize), null, {
390
- storage: 'csv_stream',
391
- chunkReadable: true,
392
- pageBacked: false,
393
- replayable: true,
394
- materialization: 'bounded',
395
- });
393
+ return registerChunkReader(
394
+ dataset,
395
+ (chunkSize) => readChunks(chunkSize),
396
+ null,
397
+ {
398
+ storage: 'csv_stream',
399
+ chunkReadable: true,
400
+ pageBacked: false,
401
+ replayable: true,
402
+ materialization: 'bounded',
403
+ },
404
+ );
396
405
  }
397
406
 
398
407
  export function createInlineDatasetHandle<T extends DatasetRow>(
@@ -79,7 +79,8 @@ function normalizeHardBillingPayload(
79
79
  : 'MONTHLY_BILLING_LIMIT_EXCEEDED',
80
80
  error_category: 'billing',
81
81
  failure_origin:
82
- typeof payload.failure_origin === 'string' && payload.failure_origin.trim()
82
+ typeof payload.failure_origin === 'string' &&
83
+ payload.failure_origin.trim()
83
84
  ? payload.failure_origin
84
85
  : 'deepline_billing',
85
86
  message:
@@ -194,5 +195,7 @@ export function extractErrorBilling(
194
195
  }
195
196
 
196
197
  export function isHardBillingToolHttpError(error: unknown): boolean {
197
- return error instanceof ToolHttpError && isHardBillingFailurePayload(error.billing);
198
+ return (
199
+ error instanceof ToolHttpError && isHardBillingFailurePayload(error.billing)
200
+ );
198
201
  }
@@ -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
  }
@@ -18,16 +18,22 @@
18
18
  *
19
19
  * @module
20
20
  */
21
+ import { existsSync, readFileSync } from 'node:fs';
22
+ import { homedir } from 'node:os';
23
+ import { join } from 'node:path';
21
24
  import type { ResolvedConfig } from './types.js';
22
25
  import { AuthError, DeeplineError, RateLimitError } from './errors.js';
23
26
  import { SDK_API_CONTRACT, SDK_VERSION } from './version.js';
24
27
  import type { LiveEventEnvelope } from './types.js';
28
+ import { baseUrlSlug } from './config.js';
25
29
  import {
26
30
  COORDINATOR_INTERNAL_TOKEN_HEADER,
27
31
  COORDINATOR_URL_OVERRIDE_HEADER,
28
32
  WORKER_CALLBACK_URL_OVERRIDE_HEADER,
29
33
  } from '../../shared_libs/play-runtime/coordinator-headers.js';
30
34
 
35
+ const MAX_DIAGNOSTIC_HEADER_LENGTH = 120;
36
+
31
37
  interface RequestOptions {
32
38
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
33
39
  body?: unknown;
@@ -61,42 +67,85 @@ interface StreamOptions {
61
67
  export class HttpClient {
62
68
  constructor(private config: ResolvedConfig) {}
63
69
 
70
+ private cleanDiagnosticHeader(
71
+ value: string | null | undefined,
72
+ ): string | null {
73
+ const normalized = String(value ?? '')
74
+ .replace(/[\u0000-\u001f\u007f]/g, ' ')
75
+ .trim()
76
+ .slice(0, MAX_DIAGNOSTIC_HEADER_LENGTH);
77
+ return normalized || null;
78
+ }
79
+
80
+ private readSkillsVersionHeader(): string | null {
81
+ const explicit = this.cleanDiagnosticHeader(
82
+ process.env.DEEPLINE_SKILLS_VERSION,
83
+ );
84
+ if (explicit) return explicit;
85
+ try {
86
+ const versionPath = join(
87
+ process.env.HOME?.trim() || homedir(),
88
+ '.local',
89
+ 'deepline',
90
+ baseUrlSlug(this.config.baseUrl),
91
+ 'sdk-skills',
92
+ '.version',
93
+ );
94
+ if (!existsSync(versionPath)) return null;
95
+ return this.cleanDiagnosticHeader(readFileSync(versionPath, 'utf-8'));
96
+ } catch {
97
+ return null;
98
+ }
99
+ }
100
+
64
101
  private authHeaders(extra?: Record<string, string>): Record<string, string> {
65
102
  const headers: Record<string, string> = {
66
- 'Authorization': `Bearer ${this.config.apiKey}`,
103
+ Authorization: `Bearer ${this.config.apiKey}`,
67
104
  'User-Agent': `deepline-ts-sdk/${SDK_VERSION}`,
105
+ 'X-Deepline-Client-Family': 'sdk',
106
+ 'X-Deepline-CLI-Family': 'sdk',
107
+ 'X-Deepline-CLI-Version': SDK_VERSION,
68
108
  'X-Deepline-SDK-Version': SDK_VERSION,
69
109
  'X-Deepline-API-Contract': SDK_API_CONTRACT,
70
110
  ...extra,
71
111
  };
72
- const bypassToken = typeof process !== 'undefined'
73
- ? process.env?.VERCEL_PROTECTION_BYPASS_TOKEN
74
- : undefined;
112
+ const skillsVersion = this.readSkillsVersionHeader();
113
+ if (skillsVersion) {
114
+ headers['X-Deepline-Skills-Version'] = skillsVersion;
115
+ }
116
+ const bypassToken =
117
+ typeof process !== 'undefined'
118
+ ? process.env?.VERCEL_PROTECTION_BYPASS_TOKEN
119
+ : undefined;
75
120
  if (bypassToken) {
76
121
  headers['x-vercel-protection-bypass'] = bypassToken;
77
122
  }
78
- const playArtifactR2Prefix = typeof process !== 'undefined'
79
- ? process.env?.DEEPLINE_PLAY_ARTIFACT_R2_PREFIX
80
- : undefined;
123
+ const playArtifactR2Prefix =
124
+ typeof process !== 'undefined'
125
+ ? process.env?.DEEPLINE_PLAY_ARTIFACT_R2_PREFIX
126
+ : undefined;
81
127
  if (playArtifactR2Prefix) {
82
128
  headers['x-deepline-play-artifact-r2-prefix'] = playArtifactR2Prefix;
83
129
  }
84
- const coordinatorUrl = typeof process !== 'undefined'
85
- ? process.env?.DEEPLINE_COORDINATOR_URL
86
- : undefined;
130
+ const coordinatorUrl =
131
+ typeof process !== 'undefined'
132
+ ? process.env?.DEEPLINE_COORDINATOR_URL
133
+ : undefined;
87
134
  if (coordinatorUrl?.trim()) {
88
135
  headers[COORDINATOR_URL_OVERRIDE_HEADER] = coordinatorUrl.trim();
89
- const coordinatorInternalToken = typeof process !== 'undefined'
90
- ? process.env?.DEEPLINE_INTERNAL_TOKEN
91
- : undefined;
136
+ const coordinatorInternalToken =
137
+ typeof process !== 'undefined'
138
+ ? process.env?.DEEPLINE_INTERNAL_TOKEN
139
+ : undefined;
92
140
  if (coordinatorInternalToken?.trim()) {
93
141
  headers[COORDINATOR_INTERNAL_TOKEN_HEADER] =
94
142
  coordinatorInternalToken.trim();
95
143
  }
96
144
  }
97
- const workerCallbackUrl = typeof process !== 'undefined'
98
- ? process.env?.DEEPLINE_WORKER_CALLBACK_URL
99
- : undefined;
145
+ const workerCallbackUrl =
146
+ typeof process !== 'undefined'
147
+ ? process.env?.DEEPLINE_WORKER_CALLBACK_URL
148
+ : undefined;
100
149
  if (workerCallbackUrl?.trim()) {
101
150
  headers[WORKER_CALLBACK_URL_OVERRIDE_HEADER] = workerCallbackUrl.trim();
102
151
  }
@@ -114,7 +163,10 @@ export class HttpClient {
114
163
  * @throws {@link RateLimitError} on HTTP 429 after all retries exhausted
115
164
  * @throws {@link DeeplineError} on other API errors or connection failures
116
165
  */
117
- async request<T = unknown>(path: string, options?: RequestOptions): Promise<T> {
166
+ async request<T = unknown>(
167
+ path: string,
168
+ options?: RequestOptions,
169
+ ): Promise<T> {
118
170
  const baseUrl = this.config.baseUrl;
119
171
  const url = `${baseUrl}${path}`;
120
172
  const method = options?.method ?? 'GET';