deepline 0.3.247 → 0.3.249

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +177 -13
  2. package/dist/bundling-sources/sdk/src/index.ts +8 -0
  3. package/dist/bundling-sources/sdk/src/plays/input-example.ts +40 -0
  4. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  5. package/dist/bundling-sources/sdk/src/runs/overview.ts +457 -0
  6. package/dist/bundling-sources/sdk/src/runs/receipts.ts +23 -0
  7. package/dist/bundling-sources/sdk/src/tool-calls.ts +21 -1
  8. package/dist/bundling-sources/sdk/src/types.ts +16 -1
  9. package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +19 -1
  10. package/dist/bundling-sources/shared_libs/play-data-plane/column-names.ts +17 -3
  11. package/dist/bundling-sources/shared_libs/play-data-plane/sheet-contract.ts +40 -8
  12. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +26 -1
  13. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +144 -14
  14. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +2 -1
  15. package/dist/bundling-sources/shared_libs/play-runtime/daytona-runtime-config.ts +6 -3
  16. package/dist/bundling-sources/shared_libs/play-runtime/daytona-sandbox-janitor.ts +26 -21
  17. package/dist/bundling-sources/shared_libs/play-runtime/daytona-sandbox-reaper.ts +23 -21
  18. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +17 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/log-context.ts +105 -0
  20. package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +8 -1
  21. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/job.ts +1 -0
  22. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/ports.ts +1 -0
  23. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +9 -0
  24. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger-event-transport.ts +40 -4
  25. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +50 -5
  26. package/dist/bundling-sources/shared_libs/play-runtime/run-lifecycle-policy.ts +3 -1
  27. package/dist/bundling-sources/shared_libs/play-runtime/run-log-buffer.ts +28 -1
  28. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +14 -1
  29. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-terminal-push.ts +56 -16
  30. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-control-transport.ts +184 -0
  31. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-launch-transport.ts +327 -0
  32. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +87 -30
  33. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-session-execution.ts +24 -13
  34. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-start-admission.ts +23 -6
  35. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +76 -33
  36. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +90 -35
  37. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +16 -0
  38. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +112 -50
  39. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +447 -77
  40. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +22 -4
  41. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-outbox-recovery.ts +105 -121
  42. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +1648 -0
  43. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-queue-health.ts +16 -19
  44. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-receipt-value-storage.ts +14 -6
  45. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +16 -0
  46. package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +40 -0
  47. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +5 -2
  48. package/dist/bundling-sources/shared_libs/plays/dataset-summary.ts +11 -7
  49. package/dist/bundling-sources/shared_libs/plays/static-pipeline-storage-codec.ts +5 -2
  50. package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +11 -2
  51. package/dist/cli/index.js +3504 -2368
  52. package/dist/cli/index.mjs +3188 -2052
  53. package/dist/{compiler-manifest-n8ZtPv2h.d.mts → compiler-manifest-DavmLa8y.d.mts} +1 -0
  54. package/dist/{compiler-manifest-n8ZtPv2h.d.ts → compiler-manifest-DavmLa8y.d.ts} +1 -0
  55. package/dist/index.d.mts +97 -5
  56. package/dist/index.d.ts +97 -5
  57. package/dist/index.js +135 -15
  58. package/dist/index.mjs +135 -15
  59. package/dist/install-integrity.json +9 -2
  60. package/dist/plays/bundle-play-file.d.mts +2 -2
  61. package/dist/plays/bundle-play-file.d.ts +2 -2
  62. package/dist/plays/bundle-play-file.mjs +1 -1
  63. package/dist/release.d.mts +1 -1
  64. package/dist/release.d.ts +1 -1
  65. package/dist/release.js +1 -1
  66. package/dist/release.mjs +1 -1
  67. package/package.json +1 -1
@@ -3,6 +3,7 @@ import {
3
3
  type PlayRunnerEvent,
4
4
  type PlayRunnerResult,
5
5
  } from '../protocol';
6
+ import type { PlayLogContext } from '../log-context';
6
7
  import { parseCtxFetchHttpFailureDiagnostic } from '../log-provenance';
7
8
  import {
8
9
  RUNNER_TERMINAL_PUSH_MAX_BODY_BYTES,
@@ -170,6 +171,7 @@ function retainArrayWithinJsonByteLimit<T>(
170
171
  */
171
172
  function retainTerminalLogTailWithRuntimeDiagnostics(logs: readonly string[]): {
172
173
  values: string[];
174
+ indexes: number[];
173
175
  truncated: boolean;
174
176
  } {
175
177
  const retainedIndexes = new Set<number>();
@@ -204,6 +206,7 @@ function retainTerminalLogTailWithRuntimeDiagnostics(logs: readonly string[]): {
204
206
  const indexes = [...retainedIndexes].sort((left, right) => left - right);
205
207
  return {
206
208
  values: indexes.map((index) => logs[index]!),
209
+ indexes,
207
210
  truncated: indexes.length !== logs.length,
208
211
  };
209
212
  }
@@ -231,11 +234,13 @@ function retainRecordWithinJsonByteLimit(
231
234
  function appendTerminalDiagnosticsMarker(
232
235
  logs: string[],
233
236
  truncatedFields: string[],
234
- ): string[] {
237
+ logContexts?: Array<PlayLogContext | null>,
238
+ ): { logs: string[]; logContexts?: Array<PlayLogContext | null> } {
235
239
  const marker =
236
240
  `[deepline] terminal diagnostic metadata was bounded (${truncatedFields.join(', ')}); ` +
237
241
  'the authored play output is complete.';
238
242
  const marked = [marker, ...logs];
243
+ const markedContexts = logContexts ? [null, ...logContexts] : undefined;
239
244
  while (
240
245
  marked.length > 1 &&
241
246
  !fitsJsonByteLimit(marked, TERMINAL_TRANSPORT_LOG_TAIL_MAX_BYTES)
@@ -247,11 +252,22 @@ function appendTerminalDiagnosticsMarker(
247
252
  (line, index) =>
248
253
  index > 0 && parseCtxFetchHttpFailureDiagnostic(line) === null,
249
254
  );
250
- marked.splice(removableIndex === -1 ? 1 : removableIndex, 1);
255
+ const removeAt = removableIndex === -1 ? 1 : removableIndex;
256
+ marked.splice(removeAt, 1);
257
+ markedContexts?.splice(removeAt, 1);
251
258
  }
252
- return fitsJsonByteLimit(marked, TERMINAL_TRANSPORT_LOG_TAIL_MAX_BYTES)
253
- ? marked
254
- : [marker];
259
+ const logsFit = fitsJsonByteLimit(
260
+ marked,
261
+ TERMINAL_TRANSPORT_LOG_TAIL_MAX_BYTES,
262
+ );
263
+ const retainedLogs = logsFit ? marked : [marker];
264
+ const retainedContexts = logsFit ? markedContexts : [null];
265
+ return {
266
+ logs: retainedLogs,
267
+ ...(retainedContexts?.some(Boolean)
268
+ ? { logContexts: retainedContexts }
269
+ : {}),
270
+ };
255
271
  }
256
272
 
257
273
  /**
@@ -266,6 +282,15 @@ function boundSettledTerminalTransportDiagnostics(
266
282
  const logProjection = retainTerminalLogTailWithRuntimeDiagnostics(
267
283
  result.logs,
268
284
  );
285
+ if (
286
+ result.logContexts !== undefined &&
287
+ result.logContexts.length !== result.logs.length
288
+ ) {
289
+ throw new TypeError('Terminal log contexts must align with result logs.');
290
+ }
291
+ const logContexts = result.logContexts?.length
292
+ ? logProjection.indexes.map((index) => result.logContexts![index] ?? null)
293
+ : undefined;
269
294
  const statsProjection = retainRecordWithinJsonByteLimit(
270
295
  result.stats,
271
296
  TERMINAL_TRANSPORT_STATS_MAX_BYTES,
@@ -303,15 +328,21 @@ function boundSettledTerminalTransportDiagnostics(
303
328
  'head',
304
329
  );
305
330
  if (warningsProjection.truncated) truncatedFields.push('outputWarnings');
331
+ const boundedLogs =
332
+ truncatedFields.length > 0
333
+ ? appendTerminalDiagnosticsMarker(
334
+ logProjection.values,
335
+ truncatedFields,
336
+ logContexts,
337
+ )
338
+ : {
339
+ logs: logProjection.values,
340
+ ...(logContexts?.some(Boolean) ? { logContexts } : {}),
341
+ };
306
342
  return {
307
343
  ...result,
308
- logs:
309
- truncatedFields.length > 0
310
- ? appendTerminalDiagnosticsMarker(
311
- logProjection.values,
312
- truncatedFields,
313
- )
314
- : logProjection.values,
344
+ logs: boundedLogs.logs,
345
+ logContexts: boundedLogs.logContexts,
315
346
  stats: statsProjection.value,
316
347
  steps: stepsProjection.values,
317
348
  ...(result.outputWarnings
@@ -337,13 +368,22 @@ function boundSettledTerminalTransportDiagnostics(
337
368
  );
338
369
  if (error.truncated) truncatedFields.push('error');
339
370
  if (errorsProjection.truncated) truncatedFields.push('errors');
371
+ const boundedLogs =
372
+ truncatedFields.length > 0
373
+ ? appendTerminalDiagnosticsMarker(
374
+ logProjection.values,
375
+ truncatedFields,
376
+ logContexts,
377
+ )
378
+ : {
379
+ logs: logProjection.values,
380
+ ...(logContexts?.some(Boolean) ? { logContexts } : {}),
381
+ };
340
382
  return {
341
383
  ...result,
342
384
  error: error.value,
343
- logs:
344
- truncatedFields.length > 0
345
- ? appendTerminalDiagnosticsMarker(logProjection.values, truncatedFields)
346
- : logProjection.values,
385
+ logs: boundedLogs.logs,
386
+ logContexts: boundedLogs.logContexts,
347
387
  stats: statsProjection.value,
348
388
  steps: stepsProjection.values,
349
389
  ...(result.errors ? { errors: errorsProjection.values } : {}),
@@ -0,0 +1,184 @@
1
+ import {
2
+ Configuration,
3
+ SandboxApi,
4
+ type SandboxListItem,
5
+ type Sandbox as SandboxData,
6
+ } from '@daytona/api-client';
7
+ import { Daytona, Sandbox } from '@daytonaio/sdk';
8
+ import axios from 'axios';
9
+ import type { DaytonaClientOptions } from '../../daytona-runtime-config';
10
+
11
+ /** Control-plane recovery must not inherit the SDK's 24-hour HTTP timeout. */
12
+ export const DAYTONA_CONTROL_TIMEOUT_MS = 10_000;
13
+
14
+ export class DaytonaControlTimeoutError extends Error {
15
+ readonly code = 'ETIMEDOUT';
16
+
17
+ constructor() {
18
+ super('Daytona control request timed out');
19
+ this.name = 'DaytonaControlTimeoutError';
20
+ }
21
+ }
22
+
23
+ /**
24
+ * One lookup and its follow-up toolbox/delete requests share a native transport
25
+ * deadline. The caller must dispose the client in finally. This adapter never
26
+ * creates sandboxes or starts customer code; ambiguous starts retain their
27
+ * existing lifecycle owner.
28
+ */
29
+ export function createDaytonaControlClient(
30
+ options: DaytonaClientOptions,
31
+ timeoutMs = DAYTONA_CONTROL_TIMEOUT_MS,
32
+ ) {
33
+ if (!options.apiKey?.trim()) {
34
+ throw new Error('Daytona control requests require the configured API key.');
35
+ }
36
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
37
+ throw new Error('Daytona control timeout must be positive.');
38
+ }
39
+ const controller = new AbortController();
40
+ const deadlineAtMs = Date.now() + timeoutMs;
41
+ const timer = setTimeout(
42
+ () => controller.abort(new DaytonaControlTimeoutError()),
43
+ timeoutMs,
44
+ );
45
+ timer.unref();
46
+ const configuration = new Configuration({
47
+ basePath:
48
+ options.apiUrl || options.serverUrl || 'https://app.daytona.io/api',
49
+ baseOptions: {
50
+ signal: controller.signal,
51
+ timeout: timeoutMs,
52
+ headers: {
53
+ Authorization: `Bearer ${options.apiKey}`,
54
+ 'X-Daytona-Source': 'deepline-runtime-control',
55
+ },
56
+ },
57
+ });
58
+ const createTransport = () => {
59
+ const transport = axios.create({
60
+ signal: controller.signal,
61
+ timeout: timeoutMs,
62
+ });
63
+ transport.interceptors.request.use((request) => {
64
+ if (Date.now() >= deadlineAtMs) {
65
+ controller.abort(new DaytonaControlTimeoutError());
66
+ }
67
+ controller.signal.throwIfAborted();
68
+ return request;
69
+ });
70
+ transport.interceptors.response.use(undefined, (error: unknown) => {
71
+ if (controller.signal.aborted) throw controller.signal.reason;
72
+ throw error;
73
+ });
74
+ return transport;
75
+ };
76
+ // The generated API is CommonJS and owns its Axios declaration/transport.
77
+ // Its public request options carry native cancellation without crossing the
78
+ // incompatible Axios ESM/CJS interceptor types into its constructor.
79
+ const api = new SandboxApi(configuration);
80
+ const sandboxData = new WeakMap<Sandbox, SandboxData>();
81
+ const call = async <T>(operation: () => Promise<T>): Promise<T> => {
82
+ if (Date.now() >= deadlineAtMs) {
83
+ controller.abort(new DaytonaControlTimeoutError());
84
+ }
85
+ controller.signal.throwIfAborted();
86
+ try {
87
+ return await operation();
88
+ } catch (error) {
89
+ if (controller.signal.aborted) throw controller.signal.reason;
90
+ throw error;
91
+ }
92
+ };
93
+ return {
94
+ deadlineAtMs,
95
+ async listPage(query: { labels: Record<string, string> }, cursor?: string) {
96
+ const { data } = await call(() =>
97
+ api.listSandboxes(
98
+ undefined,
99
+ cursor,
100
+ undefined,
101
+ undefined,
102
+ undefined,
103
+ JSON.stringify(query.labels),
104
+ ),
105
+ );
106
+ return data;
107
+ },
108
+ async get(sandboxId: string): Promise<Sandbox> {
109
+ const { data } = await call(() => api.getSandbox(sandboxId));
110
+ // Sandbox sets its configuration/base URL to the provider-returned
111
+ // toolbox endpoint. Keep that separate from the control API's authority.
112
+ const sandbox = new Sandbox(
113
+ data,
114
+ new Configuration({
115
+ basePath: configuration.basePath,
116
+ baseOptions: {
117
+ ...configuration.baseOptions,
118
+ headers: { ...configuration.baseOptions.headers },
119
+ },
120
+ }),
121
+ createTransport(),
122
+ api,
123
+ );
124
+ sandboxData.set(sandbox, data);
125
+ return sandbox;
126
+ },
127
+ /**
128
+ * Full captured stdout is a legacy data-plane artifact, not fixed-size
129
+ * metadata. Preserve its SDK file-download timeout independently of this
130
+ * control scope. This facade cannot perform another sandbox lookup.
131
+ */
132
+ legacyOutputRecovery(sandbox: Sandbox): Pick<Sandbox, 'fs'> {
133
+ const data = sandboxData.get(sandbox);
134
+ if (!data)
135
+ throw new Error(
136
+ "Output recovery requires this control client's sandbox.",
137
+ );
138
+ const recovery = new Sandbox(
139
+ data,
140
+ new Configuration({
141
+ basePath: configuration.basePath,
142
+ baseOptions: { headers: { ...configuration.baseOptions.headers } },
143
+ }),
144
+ Daytona.createAxiosInstance(),
145
+ api,
146
+ );
147
+ return { fs: recovery.fs };
148
+ },
149
+ async delete(sandbox: Pick<Sandbox, 'id'>): Promise<void> {
150
+ // SDK Sandbox.delete starts an unawaited refresh after success. The
151
+ // generated deletion endpoint preserves the same confirmed outcome
152
+ // without starting a new request after this scope finishes.
153
+ await call(() => api.deleteSandbox(sandbox.id));
154
+ },
155
+ dispose(): void {
156
+ clearTimeout(timer);
157
+ controller.abort(new Error('Daytona control request scope finished'));
158
+ },
159
+ };
160
+ }
161
+
162
+ /**
163
+ * Bound each external inventory request, not the sweep. Consume every provider
164
+ * cursor; a failed page throws instead of reporting a partially scanned fleet
165
+ * as success. Release the HTTP scope before the caller processes page items.
166
+ */
167
+ export async function* listDaytonaControlSandboxes(
168
+ options: DaytonaClientOptions,
169
+ query: { labels: Record<string, string> },
170
+ pageTimeoutMs = DAYTONA_CONTROL_TIMEOUT_MS,
171
+ ): AsyncGenerator<SandboxListItem> {
172
+ let cursor: string | undefined;
173
+ do {
174
+ const control = createDaytonaControlClient(options, pageTimeoutMs);
175
+ let page;
176
+ try {
177
+ page = await control.listPage(query, cursor);
178
+ } finally {
179
+ control.dispose();
180
+ }
181
+ yield* page.items;
182
+ cursor = page.nextCursor ?? undefined;
183
+ } while (cursor);
184
+ }
@@ -0,0 +1,327 @@
1
+ import {
2
+ Configuration,
3
+ SandboxApi,
4
+ type Sandbox as SandboxData,
5
+ } from '@daytona/api-client';
6
+ import {
7
+ Sandbox,
8
+ DaytonaError,
9
+ DaytonaAuthenticationError,
10
+ DaytonaAuthorizationError,
11
+ DaytonaNotFoundError,
12
+ DaytonaConflictError,
13
+ DaytonaRateLimitError,
14
+ DaytonaValidationError,
15
+ type CreateSandboxFromSnapshotParams,
16
+ } from '@daytonaio/sdk';
17
+ import axios, { type InternalAxiosRequestConfig } from 'axios';
18
+ import { Readable } from 'node:stream';
19
+ import type { DaytonaClientOptions } from '../../daytona-runtime-config';
20
+ import {
21
+ DAYTONA_CONTROL_TIMEOUT_MS,
22
+ DaytonaControlTimeoutError,
23
+ } from './daytona-control-transport';
24
+
25
+ /** A sent create may have committed even when no response reached the worker. */
26
+ export class DaytonaLaunchOutcomeUnknownError extends Error {
27
+ constructor(cause: unknown) {
28
+ super(
29
+ 'Daytona named create outcome is unknown; reconcile the same operation before another launch.',
30
+ { cause },
31
+ );
32
+ this.name = 'DaytonaLaunchOutcomeUnknownError';
33
+ }
34
+ }
35
+
36
+ function providerError(error: unknown): unknown {
37
+ if (!axios.isAxiosError(error)) return error;
38
+ const status = error.response?.status;
39
+ const data = error.response?.data;
40
+ const message =
41
+ typeof data === 'string'
42
+ ? data
43
+ : typeof data?.message === 'string'
44
+ ? data.message
45
+ : error.message;
46
+ const errorClasses: Record<number, typeof DaytonaError> = {
47
+ 400: DaytonaValidationError,
48
+ 401: DaytonaAuthenticationError,
49
+ 403: DaytonaAuthorizationError,
50
+ 404: DaytonaNotFoundError,
51
+ 409: DaytonaConflictError,
52
+ 429: DaytonaRateLimitError,
53
+ };
54
+ const ErrorClass = errorClasses[status ?? 0] ?? DaytonaError;
55
+ return new ErrorClass(
56
+ message,
57
+ status,
58
+ undefined,
59
+ typeof data?.code === 'string' ? data.code : error.code,
60
+ );
61
+ }
62
+
63
+ /** Every SDK request owns its socket deadline, including streamed response bodies. */
64
+ function createTransport(signal: () => AbortSignal | undefined) {
65
+ const active = new WeakMap<
66
+ InternalAxiosRequestConfig,
67
+ { finish(): void; signal: AbortSignal }
68
+ >();
69
+ const transport = axios.create();
70
+ transport.interceptors.request.use((request) => {
71
+ const controller = new AbortController();
72
+ // Honor the SDK's shorter command/delete windows; upload gets at most the
73
+ // existing 90s staging budget even without an enclosing phase scope.
74
+ const timeoutMs = Math.min(
75
+ request.timeout || DAYTONA_CONTROL_TIMEOUT_MS,
76
+ 90_000,
77
+ );
78
+ const timer = setTimeout(
79
+ () => controller.abort(new DaytonaControlTimeoutError()),
80
+ timeoutMs,
81
+ );
82
+ timer.unref();
83
+ const inherited = signal();
84
+ const combined = AbortSignal.any([
85
+ controller.signal,
86
+ ...(inherited ? [inherited] : []),
87
+ ...(request.signal instanceof AbortSignal ? [request.signal] : []),
88
+ ]);
89
+ const finish = () => clearTimeout(timer);
90
+ if (combined.aborted) {
91
+ finish();
92
+ combined.throwIfAborted();
93
+ }
94
+ request.signal = combined;
95
+ active.set(request, { finish, signal: combined });
96
+ return request;
97
+ });
98
+ transport.interceptors.response.use(
99
+ (response) => {
100
+ const scope = active.get(response.config);
101
+ if (scope && response.data instanceof Readable) {
102
+ const body = response.data;
103
+ const abort = () =>
104
+ body.destroy(
105
+ scope.signal.reason instanceof Error
106
+ ? scope.signal.reason
107
+ : new Error('Daytona request aborted'),
108
+ );
109
+ const finish = () => {
110
+ scope.finish();
111
+ scope.signal.removeEventListener('abort', abort);
112
+ };
113
+ scope.signal.addEventListener('abort', abort, { once: true });
114
+ body.once('end', finish).once('close', finish).once('error', finish);
115
+ if (scope.signal.aborted) abort();
116
+ } else scope?.finish();
117
+ return response;
118
+ },
119
+ (error: unknown) => {
120
+ const scope =
121
+ axios.isAxiosError(error) && error.config
122
+ ? active.get(error.config)
123
+ : undefined;
124
+ scope?.finish();
125
+ if (scope?.signal.aborted) throw scope.signal.reason;
126
+ throw providerError(error);
127
+ },
128
+ );
129
+ return transport;
130
+ }
131
+
132
+ function deadline(timeoutMs: number, signal?: AbortSignal) {
133
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
134
+ throw new Error('Daytona phase deadline must be positive.');
135
+ const controller = new AbortController();
136
+ const timer = setTimeout(
137
+ () => controller.abort(new DaytonaControlTimeoutError()),
138
+ timeoutMs,
139
+ );
140
+ timer.unref();
141
+ const combined = signal
142
+ ? AbortSignal.any([controller.signal, signal])
143
+ : controller.signal;
144
+ const deadlineAtMs = Date.now() + timeoutMs;
145
+ return {
146
+ get signal() {
147
+ if (Date.now() >= deadlineAtMs)
148
+ controller.abort(new DaytonaControlTimeoutError());
149
+ return combined;
150
+ },
151
+ dispose() {
152
+ clearTimeout(timer);
153
+ controller.abort(new Error('Daytona phase finished'));
154
+ },
155
+ };
156
+ }
157
+
158
+ /** Deepline's default-snapshot launch contract; image/build orchestration stays in the SDK. */
159
+ export function createDaytonaLaunchClient(
160
+ options: DaytonaClientOptions,
161
+ cancellation?: AbortSignal,
162
+ ) {
163
+ if (!options.apiKey?.trim())
164
+ throw new Error('Daytona launch requires the configured API key.');
165
+ const configuration = new Configuration({
166
+ basePath:
167
+ options.apiUrl || options.serverUrl || 'https://app.daytona.io/api',
168
+ baseOptions: {
169
+ headers: {
170
+ Authorization: `Bearer ${options.apiKey}`,
171
+ 'X-Daytona-Source': 'deepline-runtime-launch',
172
+ },
173
+ },
174
+ });
175
+ const dataBySandbox = new WeakMap<Sandbox, SandboxData>();
176
+ const makeSandbox = (
177
+ data: SandboxData,
178
+ signal: () => AbortSignal | undefined,
179
+ ): Sandbox => {
180
+ const scopedConfiguration = new Configuration({
181
+ basePath: configuration.basePath,
182
+ baseOptions: {
183
+ ...configuration.baseOptions,
184
+ signal: signal(),
185
+ timeout: DAYTONA_CONTROL_TIMEOUT_MS,
186
+ },
187
+ });
188
+ const api = new SandboxApi(scopedConfiguration);
189
+ const sandbox = new Sandbox(
190
+ data,
191
+ new Configuration({
192
+ basePath: configuration.basePath,
193
+ baseOptions: { ...configuration.baseOptions },
194
+ }),
195
+ createTransport(signal),
196
+ api,
197
+ );
198
+ dataBySandbox.set(sandbox, data);
199
+ return sandbox;
200
+ };
201
+ const optionsTarget = options.target;
202
+ return {
203
+ async create(
204
+ params: CreateSandboxFromSnapshotParams,
205
+ options: { timeout?: number } = {},
206
+ ) {
207
+ const timeoutMs = (options.timeout ?? 60) * 1_000;
208
+ const phase = deadline(timeoutMs, cancellation);
209
+ let sent = false;
210
+ let data: SandboxData | undefined;
211
+ try {
212
+ phase.signal.throwIfAborted();
213
+ const api = new SandboxApi(
214
+ new Configuration({
215
+ basePath: configuration.basePath,
216
+ baseOptions: { ...configuration.baseOptions, signal: phase.signal },
217
+ }),
218
+ );
219
+ sent = true;
220
+ const response = await api.createSandbox(
221
+ {
222
+ name: params.name,
223
+ snapshot: params.snapshot,
224
+ user: params.user,
225
+ env: params.envVars ?? {},
226
+ labels: {
227
+ ...params.labels,
228
+ 'code-toolbox-language': params.language ?? 'python',
229
+ },
230
+ public: params.public,
231
+ target: optionsTarget,
232
+ autoStopInterval: params.autoStopInterval,
233
+ autoArchiveInterval: params.autoArchiveInterval,
234
+ autoDeleteInterval: params.ephemeral
235
+ ? 0
236
+ : params.autoDeleteInterval,
237
+ volumes: params.volumes,
238
+ networkBlockAll: params.networkBlockAll,
239
+ networkAllowList: params.networkAllowList,
240
+ linkedSandbox: params.linkedSandbox,
241
+ },
242
+ undefined,
243
+ { timeout: timeoutMs },
244
+ );
245
+ data = response.data;
246
+ const sandbox = makeSandbox(data, () => phase.signal);
247
+ await sandbox.waitUntilStarted(options.timeout ?? 60);
248
+ // Future phases own fresh deadlines; none inherits this create scope.
249
+ return makeSandbox(
250
+ {
251
+ ...data,
252
+ state: sandbox.state,
253
+ id: sandbox.id,
254
+ name: sandbox.name,
255
+ organizationId: sandbox.organizationId,
256
+ cpu: sandbox.cpu,
257
+ gpu: sandbox.gpu,
258
+ memory: sandbox.memory,
259
+ disk: sandbox.disk,
260
+ snapshot: sandbox.snapshot,
261
+ labels: sandbox.labels,
262
+ toolboxProxyUrl: sandbox.toolboxProxyUrl,
263
+ },
264
+ () => cancellation,
265
+ );
266
+ } catch (caught) {
267
+ const error = providerError(caught);
268
+ const confirmedRejection =
269
+ !data &&
270
+ error instanceof DaytonaError &&
271
+ [400, 401, 403, 429].includes(error.statusCode ?? 0);
272
+ if (sent && !confirmedRejection)
273
+ throw new DaytonaLaunchOutcomeUnknownError(error);
274
+ throw error;
275
+ } finally {
276
+ phase.dispose();
277
+ }
278
+ },
279
+ async get(id: string) {
280
+ const phase = deadline(DAYTONA_CONTROL_TIMEOUT_MS, cancellation);
281
+ try {
282
+ phase.signal.throwIfAborted();
283
+ const api = new SandboxApi(configuration);
284
+ const { data } = await api.getSandbox(id, undefined, undefined, {
285
+ signal: phase.signal,
286
+ });
287
+ return makeSandbox(data, () => cancellation);
288
+ } catch (error) {
289
+ throw providerError(error);
290
+ } finally {
291
+ phase.dispose();
292
+ }
293
+ },
294
+ async delete(sandbox: Pick<Sandbox, 'id'>, timeoutSeconds = 30) {
295
+ // Cleanup must survive execution cancellation and must not trigger the
296
+ // SDK's unawaited refreshDataSafe after a confirmed delete response.
297
+ const phase = deadline(timeoutSeconds * 1_000);
298
+ try {
299
+ const api = new SandboxApi(
300
+ new Configuration({
301
+ basePath: configuration.basePath,
302
+ baseOptions: { ...configuration.baseOptions, signal: phase.signal },
303
+ }),
304
+ );
305
+ await api.deleteSandbox(sandbox.id, undefined, {
306
+ timeout: timeoutSeconds * 1_000,
307
+ });
308
+ } catch (error) {
309
+ throw providerError(error);
310
+ } finally {
311
+ phase.dispose();
312
+ }
313
+ },
314
+ scope(sandbox: Sandbox, timeoutMs: number) {
315
+ const data = dataBySandbox.get(sandbox);
316
+ if (!data)
317
+ throw new Error(
318
+ 'Daytona phase requires a sandbox owned by this launch client.',
319
+ );
320
+ const phase = deadline(timeoutMs, cancellation);
321
+ return {
322
+ sandbox: makeSandbox(data, () => phase.signal),
323
+ dispose: phase.dispose,
324
+ };
325
+ },
326
+ };
327
+ }