deepline 0.3.473 → 0.3.474

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 (175) hide show
  1. package/dist/bundling-sources/sdk/src/client-core.ts +4 -1
  2. package/dist/bundling-sources/sdk/src/play.ts +7 -2
  3. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  4. package/dist/bundling-sources/shared_libs/integration-contracts/billing-entitlements.ts +3 -1
  5. package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +18 -0
  6. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +101 -2
  7. package/dist/bundling-sources/shared_libs/play-runtime/modal-runtime-config.ts +62 -20
  8. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-launch-transport.ts +2 -2
  9. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +24 -8
  10. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +19 -7
  11. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-runtime-watchdog.ts +196 -10
  12. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +40 -17
  13. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts +4 -1
  14. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/modal-resource-usage.ts +222 -0
  15. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/modal.ts +46 -14
  16. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +2 -0
  17. package/dist/bundling-sources/shared_libs/play-runtime/runtime-incident-drills.ts +9 -5
  18. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-compute-usage.ts +8 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/contract.ts +2 -0
  20. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +2 -0
  21. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +5 -1
  22. package/dist/bundling-sources/shared_libs/play-runtime/worker-api-types.ts +36 -0
  23. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +18 -8
  24. package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +1 -1
  25. package/dist/bundling-sources/shared_libs/plays/sandbox-runtime-limits.ts +64 -16
  26. package/dist/bundling-sources/shared_libs/plays/tool-result-types.ts +1 -1
  27. package/dist/bundling-sources/shared_libs/product-notifications/billing-sales.ts +14 -1
  28. package/dist/bundling-sources/shared_libs/product-notifications/catalog.ts +40 -0
  29. package/dist/bundling-sources/shared_libs/product-notifications/default-policy.ts +23 -1
  30. package/dist/bundling-sources/shared_libs/product-notifications/events.ts +18 -0
  31. package/dist/bundling-sources/shared_libs/product-notifications/slack-delivery.ts +2 -1
  32. package/dist/cli/auth-O6KAH543.js +41 -0
  33. package/dist/cli/{auth-KLJOQ4DZ.mjs → auth-QMANU5PV.mjs} +5 -5
  34. package/dist/cli/{billing-IQNJXKH5.js → billing-WZ5POBZX.js} +85 -83
  35. package/dist/cli/{billing-DNDYNBQ7.mjs → billing-YJAP4VZN.mjs} +12 -10
  36. package/dist/cli/{chunk-GTH7JORC.mjs → chunk-26CZVDQN.mjs} +2 -2
  37. package/dist/cli/{chunk-VGVSHQN5.mjs → chunk-35SZHTRW.mjs} +5 -5
  38. package/dist/cli/{chunk-GAWP2YF5.mjs → chunk-3MZPWIOE.mjs} +1 -1
  39. package/dist/cli/{chunk-B3BM3PAI.js → chunk-3NRZFMX4.js} +6 -6
  40. package/dist/cli/{chunk-63OOFT6B.js → chunk-3ODBCZLL.js} +93 -93
  41. package/dist/cli/{chunk-D4B3EV55.js → chunk-475V572K.js} +36 -36
  42. package/dist/cli/{chunk-36KEQFWZ.js → chunk-4C34LGI4.js} +10 -10
  43. package/dist/cli/{chunk-6ZTSHKJF.js → chunk-5QS4T4KR.js} +10 -3
  44. package/dist/cli/{chunk-GQBAJTQU.mjs → chunk-7IYOWIEJ.mjs} +2 -2
  45. package/dist/cli/{chunk-3J2N7YF5.js → chunk-AIPTW3EK.js} +3 -3
  46. package/dist/cli/{chunk-YHB6MASP.js → chunk-AWJKYS25.js} +2 -2
  47. package/dist/cli/{chunk-V25CXRQ7.mjs → chunk-BB4RZMWA.mjs} +5 -5
  48. package/dist/cli/{chunk-OHUGXZ7N.js → chunk-CXBEZAPU.js} +16 -16
  49. package/dist/cli/{chunk-Z52WEIBR.js → chunk-DKR7QPEO.js} +11 -7
  50. package/dist/cli/{chunk-X6R5UNDZ.mjs → chunk-DQZBERDL.mjs} +1 -1
  51. package/dist/cli/{chunk-YUNPYX5T.mjs → chunk-DUGZKPRK.mjs} +10 -3
  52. package/dist/cli/{chunk-HQCTPAXP.mjs → chunk-EIKONWW7.mjs} +1 -1
  53. package/dist/cli/{chunk-MREO5LZD.js → chunk-EK2TAZJZ.js} +2 -2
  54. package/dist/cli/{chunk-RVRG57OB.js → chunk-G3UHAYRX.js} +8 -8
  55. package/dist/cli/{chunk-3LFSIUFI.js → chunk-IK3VOGDR.js} +21 -21
  56. package/dist/cli/{chunk-IC6AHOYS.js → chunk-JCNJQH6X.js} +1 -1
  57. package/dist/cli/{chunk-NTFM7JQW.mjs → chunk-JEYX3345.mjs} +3 -3
  58. package/dist/cli/{chunk-IFZ76E5H.mjs → chunk-JJVYUKLY.mjs} +1 -1
  59. package/dist/cli/{chunk-N4ZCMQ54.js → chunk-JSXMJSBE.js} +8 -8
  60. package/dist/cli/{chunk-QG3THSTN.mjs → chunk-KKWQ24VM.mjs} +2 -2
  61. package/dist/cli/{chunk-TUFQFTOC.js → chunk-LHOEWAVD.js} +53 -53
  62. package/dist/cli/{chunk-QD6IW2HP.mjs → chunk-MKSB2FTU.mjs} +4 -4
  63. package/dist/cli/{chunk-6YOGOXOJ.mjs → chunk-MNK4ZK2C.mjs} +10 -10
  64. package/dist/cli/{chunk-ABLEU3QI.mjs → chunk-MXUJVCKV.mjs} +4 -4
  65. package/dist/cli/{chunk-WGQKOLXH.js → chunk-N62MS3XJ.js} +6 -6
  66. package/dist/cli/{chunk-YH5SFKHJ.js → chunk-ORQYKA4I.js} +4 -4
  67. package/dist/cli/{chunk-4U3RNA3J.js → chunk-PGKELBIY.js} +51 -51
  68. package/dist/cli/{chunk-SWAYZST7.mjs → chunk-RGIQ5ZJX.mjs} +2 -2
  69. package/dist/cli/{chunk-UON3WCBD.js → chunk-RLQIGBFK.js} +3 -3
  70. package/dist/cli/{chunk-F4PUDYBY.js → chunk-RXITRC37.js} +3 -3
  71. package/dist/cli/{chunk-DD7E4ZVH.js → chunk-STLJYWUC.js} +7 -7
  72. package/dist/cli/{chunk-3OPQLFUK.mjs → chunk-THDXVSNL.mjs} +4 -4
  73. package/dist/cli/{chunk-TBLBYIKO.mjs → chunk-UGYLGT7B.mjs} +1 -1
  74. package/dist/cli/{chunk-CNCK2D6B.mjs → chunk-UKX4FESF.mjs} +2 -2
  75. package/dist/cli/{chunk-RZHUHL53.mjs → chunk-V4ILQ2DS.mjs} +11 -7
  76. package/dist/cli/{chunk-QI73EL5C.js → chunk-XNP7XTBN.js} +26 -26
  77. package/dist/cli/{chunk-PMJAGTSQ.js → chunk-XP7FAYLN.js} +5 -5
  78. package/dist/cli/{chunk-D22QIUE3.js → chunk-YFDTF2XC.js} +9 -9
  79. package/dist/cli/{chunk-GE5GPLNM.mjs → chunk-YK64UZXN.mjs} +1 -1
  80. package/dist/cli/{chunk-ZVLYUKJU.js → chunk-YKNULB7V.js} +5 -5
  81. package/dist/cli/{chunk-VS7JKS22.mjs → chunk-YVJ6IWVD.mjs} +1 -1
  82. package/dist/cli/{chunk-6QS7522K.mjs → chunk-YX762HFK.mjs} +2 -2
  83. package/dist/cli/{chunk-EUHTZV2E.mjs → chunk-Z27NSLPC.mjs} +1 -1
  84. package/dist/cli/{chunk-EVLUSXQG.mjs → chunk-ZGC2SUAA.mjs} +2 -2
  85. package/dist/cli/{chunk-GISBRE2H.mjs → chunk-ZRWQKTSX.mjs} +3 -3
  86. package/dist/cli/command-docs.js +2 -2
  87. package/dist/cli/command-docs.mjs +1 -1
  88. package/dist/cli/{csv-OVV3HWN4.js → csv-ENKCFCSM.js} +13 -13
  89. package/dist/cli/{csv-NCY5MWS2.mjs → csv-QCLQOIEA.mjs} +4 -4
  90. package/dist/cli/{db-BOELNDS4.js → db-5TLBZDT3.js} +27 -27
  91. package/dist/cli/{db-VPH6JHUD.mjs → db-P25LGP33.mjs} +6 -6
  92. package/dist/cli/describe-44L7OBGM.js +48 -0
  93. package/dist/cli/{describe-FFDCPYZH.mjs → describe-W2H5DHR6.mjs} +17 -17
  94. package/dist/cli/describe-core.d.mts +3 -3
  95. package/dist/cli/describe-core.d.ts +3 -3
  96. package/dist/cli/{executions-5GCCDENS.mjs → executions-VYZCYUBR.mjs} +6 -6
  97. package/dist/cli/{executions-RF44ANF5.js → executions-YHFAWJOJ.js} +8 -8
  98. package/dist/cli/{feedback-OI2A5L6F.mjs → feedback-K2FWYYLZ.mjs} +4 -4
  99. package/dist/cli/{feedback-NJM3V7QV.js → feedback-RJF3QSBB.js} +8 -8
  100. package/dist/cli/git-F3EI5DY5.js +25 -0
  101. package/dist/cli/{git-EOGSZ3T2.mjs → git-S3APUPLL.mjs} +5 -5
  102. package/dist/cli/index.js +139 -139
  103. package/dist/cli/index.mjs +41 -41
  104. package/dist/cli/{legacy-noops-5QM7XGGX.js → legacy-noops-3SWNRE7Z.js} +8 -8
  105. package/dist/cli/{legacy-noops-UYKK7AAB.mjs → legacy-noops-ICYZACTW.mjs} +5 -5
  106. package/dist/cli/{monitors-N3ITC5MP.mjs → monitors-DEZCEQLB.mjs} +7 -7
  107. package/dist/cli/{monitors-52GW2V4V.js → monitors-VRIWTRPG.js} +91 -91
  108. package/dist/cli/{org-KUZUG33W.mjs → org-IQW5ZIAO.mjs} +6 -6
  109. package/dist/cli/{org-ADF47NPV.js → org-NX752DTE.js} +72 -72
  110. package/dist/cli/{pages-C3S5WU3S.mjs → pages-5WQRVVKX.mjs} +6 -6
  111. package/dist/cli/{pages-H3H7ODBN.js → pages-YIBWYGVS.js} +17 -17
  112. package/dist/cli/{play-YWZWBG32.js → play-3EXNH3HD.js} +662 -601
  113. package/dist/cli/{play-SZETTQPV.mjs → play-WOMJXS4O.mjs} +110 -49
  114. package/dist/cli/{providers-V3BP7ZB2.js → providers-C2WYSR2Y.js} +9 -9
  115. package/dist/cli/{providers-FSCD35HB.mjs → providers-XDVPYRTS.mjs} +6 -6
  116. package/dist/cli/{quickstart-ICULVGAR.js → quickstart-7RABZF66.js} +6 -6
  117. package/dist/cli/{quickstart-SYZCT2WI.mjs → quickstart-YMSIL2TK.mjs} +4 -4
  118. package/dist/cli/{run-WUJSKGUM.js → run-5QVVWFGL.js} +36 -36
  119. package/dist/cli/{run-HA5B3AYQ.mjs → run-A4JD6TAR.mjs} +22 -22
  120. package/dist/cli/{search-A27AGYDV.mjs → search-2TOSF2G3.mjs} +7 -7
  121. package/dist/cli/search-772R7YHI.js +32 -0
  122. package/dist/cli/{secrets-2L4VRJ2F.mjs → secrets-BUN4ORE7.mjs} +6 -6
  123. package/dist/cli/{secrets-ODX56QBF.js → secrets-Q7UVOSNM.js} +15 -15
  124. package/dist/cli/{sessions-PRF36DHO.mjs → sessions-IAOORI6X.mjs} +5 -5
  125. package/dist/cli/{sessions-DVZ4FECP.js → sessions-NXQOQ4HX.js} +6 -6
  126. package/dist/cli/{settings-LUATIKJE.mjs → settings-BMI373AI.mjs} +11 -11
  127. package/dist/cli/{settings-RJT5DBWG.js → settings-WD4KSY5S.js} +78 -78
  128. package/dist/cli/{setup-FJSBKCLU.js → setup-3IA7BVPE.js} +44 -44
  129. package/dist/cli/{setup-IQXTYLRH.mjs → setup-55VIWAWR.mjs} +9 -9
  130. package/dist/cli/{skills-XJH4ZC5Y.mjs → skills-GKITMBTM.mjs} +7 -7
  131. package/dist/cli/skills-PUUTFMP3.js +31 -0
  132. package/dist/cli/tools-3WX7TUUG.js +55 -0
  133. package/dist/cli/{tools-IGXLZCKZ.mjs → tools-XHCFC7GP.mjs} +16 -16
  134. package/dist/cli/update-PC3CLODV.js +33 -0
  135. package/dist/cli/{update-AH7G5VSL.mjs → update-SWB24YKE.mjs} +9 -9
  136. package/dist/cli/{workflow-IQ2J7LWM.js → workflow-5QPMFI3Z.js} +17 -17
  137. package/dist/cli/{workflow-WOYSCWVD.mjs → workflow-XO4CKUA7.mjs} +7 -7
  138. package/dist/{client-C9bJ0QXu.d.mts → client-Bgl0_zXi.d.mts} +1 -1
  139. package/dist/{client-CuNH54w3.d.ts → client-_ah6IuhK.d.ts} +1 -1
  140. package/dist/{client-core-CSiXbe2n.d.mts → client-core-Bfsedm0_.d.mts} +44 -3
  141. package/dist/{client-core-C8Q6DKga.d.ts → client-core-C-ZF5vh9.d.ts} +44 -3
  142. package/dist/{compiler-manifest-Zwm9yqOA.d.mts → compiler-manifest-CCOqDGjU.d.mts} +12 -10
  143. package/dist/{compiler-manifest-Zwm9yqOA.d.ts → compiler-manifest-CCOqDGjU.d.ts} +12 -10
  144. package/dist/{errors-nEsqNLL8.d.mts → errors-BLccD7tG.d.mts} +1 -1
  145. package/dist/{errors-CsYwlpn4.d.ts → errors-Cy3kPnsi.d.ts} +1 -1
  146. package/dist/{index-CXF2jtza.d.mts → index-4-j44HuT.d.mts} +1 -1
  147. package/dist/{index-9pUZqkPH.d.ts → index-D5e4h1-H.d.ts} +1 -1
  148. package/dist/index.d.mts +14 -9
  149. package/dist/index.d.ts +14 -9
  150. package/dist/index.js +9 -9
  151. package/dist/index.mjs +6 -6
  152. package/dist/install-integrity.json +111 -110
  153. package/dist/plays/bundle-play-file.d.mts +3 -3
  154. package/dist/plays/bundle-play-file.d.ts +3 -3
  155. package/dist/plays/bundle-play-file.mjs +1 -1
  156. package/dist/plays/play-description.d.mts +2 -2
  157. package/dist/plays/play-description.d.ts +2 -2
  158. package/dist/plays/run-request.d.mts +2 -2
  159. package/dist/plays/run-request.d.ts +2 -2
  160. package/dist/release.d.mts +1 -1
  161. package/dist/release.d.ts +1 -1
  162. package/dist/release.js +2 -2
  163. package/dist/release.mjs +1 -1
  164. package/dist/server.d.mts +4 -4
  165. package/dist/server.d.ts +4 -4
  166. package/dist/server.js +5 -5
  167. package/dist/server.mjs +3 -3
  168. package/package.json +1 -1
  169. package/dist/cli/auth-IJOWKNSB.js +0 -41
  170. package/dist/cli/describe-7SFESEFL.js +0 -48
  171. package/dist/cli/git-4V5ZS5SE.js +0 -25
  172. package/dist/cli/search-XATDPNEM.js +0 -32
  173. package/dist/cli/skills-N6CPBF6T.js +0 -31
  174. package/dist/cli/tools-YMVEVRCE.js +0 -55
  175. package/dist/cli/update-MUCWGTXH.js +0 -33
@@ -1,4 +1,9 @@
1
- import { STANDARD_PLAY_SANDBOX_RUNTIME_LIMITS } from '../../../plays/sandbox-runtime-limits';
1
+ import {
2
+ STANDARD_PLAY_SANDBOX_RUNTIME_LIMITS,
3
+ MAX_PLAY_SANDBOX_RUNTIME_LIMITS,
4
+ validatePlaySandboxRuntimeLimits,
5
+ type PlaySandboxRuntimeLimits,
6
+ } from '../../../plays/sandbox-runtime-limits';
2
7
  import { RUNTIME_SANDBOX_MEMORY_ABORT_CODE } from '../../run-failure';
3
8
  import { PLAY_RUNTIME_TRANSPORT_ATTEMPT_HEADER } from '../../runtime-contract';
4
9
  import { RUNTIME_CLAIM_HEARTBEAT_DIVISOR } from '../../runtime-constants';
@@ -11,7 +16,7 @@ import {
11
16
  /** Exit code reserved for the external runner watchdog's runtime deadline. */
12
17
  export const DAYTONA_RUNTIME_WATCHDOG_EXIT_CODE = 124;
13
18
 
14
- /** The cgroup memory envelope one standard sandbox is given, in MiB. */
19
+ /** The standard profile's cgroup memory envelope, in MiB. */
15
20
  export const STANDARD_PLAY_SANDBOX_MEMORY_MIB =
16
21
  STANDARD_PLAY_SANDBOX_RUNTIME_LIMITS.memoryGiB * 1024;
17
22
 
@@ -75,6 +80,31 @@ export const PLAY_RUNNER_NODE_EXEC_ARGV: readonly string[] = [
75
80
  '--single-threaded-gc',
76
81
  ];
77
82
 
83
+ /** Select the V8 heap envelope from the sandbox profile that will run the Play. */
84
+ export function playRunnerNodeExecArgvForLimits(
85
+ limits: PlaySandboxRuntimeLimits,
86
+ memoryLimitMiB?: number,
87
+ ): readonly string[] {
88
+ const validated = validatePlaySandboxRuntimeLimits(limits);
89
+ const reservationMemoryMiB = validated.memoryGiB * 1024;
90
+ const sandboxMemoryMiB = memoryLimitMiB ?? reservationMemoryMiB;
91
+ if (
92
+ !Number.isSafeInteger(sandboxMemoryMiB) ||
93
+ sandboxMemoryMiB < reservationMemoryMiB ||
94
+ sandboxMemoryMiB > MAX_PLAY_SANDBOX_RUNTIME_LIMITS.memoryGiB * 1024
95
+ ) {
96
+ throw new Error(
97
+ `Invalid runner memory ceiling: ${sandboxMemoryMiB} MiB for a ${reservationMemoryMiB} MiB reservation.`,
98
+ );
99
+ }
100
+ return [
101
+ `--max-old-space-size=${Math.floor(sandboxMemoryMiB * STANDARD_PLAY_RUNNER_OLD_SPACE_SHARE)}`,
102
+ `--max-semi-space-size=${STANDARD_PLAY_RUNNER_MAX_SEMI_SPACE_SIZE_MIB}`,
103
+ '--expose-gc',
104
+ '--single-threaded-gc',
105
+ ];
106
+ }
107
+
78
108
  /**
79
109
  * libuv threadpool size for the customer runner. The default is 4 and it is
80
110
  * sized for a machine, not a one-core cgroup: zlib and fs work on four threads
@@ -128,6 +158,85 @@ export const RUNNER_SANDBOX_STALL_REPORT_MS = 5_000;
128
158
  export const RUNTIME_SANDBOX_MEMORY_ABORT_REASON =
129
159
  RUNTIME_SANDBOX_MEMORY_ABORT_CODE;
130
160
 
161
+ /** Keep anonymous memory, active cache and dirty/writeback pages in the guard. */
162
+ export function cgroupMemoryWorkingSet(
163
+ usageBytes: number,
164
+ stat: string,
165
+ version: 'v1' | 'v2',
166
+ ): { workingSetBytes: number; reclaimableFileBytes: number } {
167
+ const values = new Map(
168
+ stat
169
+ .trim()
170
+ .split('\n')
171
+ .map((line) => {
172
+ const [key, value] = line.trim().split(/\s+/);
173
+ return [key, /^\d+$/.test(value ?? '') ? Number(value) : NaN] as const;
174
+ }),
175
+ );
176
+ // v1 usage includes descendants; subtract only its matching total counters.
177
+ const keys =
178
+ version === 'v2'
179
+ ? ['inactive_file', 'file_dirty', 'file_writeback']
180
+ : ['total_inactive_file', 'total_dirty', 'total_writeback'];
181
+ const [inactive, dirty, writeback] = keys.map((key) => values.get(key));
182
+ if (
183
+ !Number.isSafeInteger(usageBytes) ||
184
+ usageBytes < 0 ||
185
+ [inactive, dirty, writeback].some(
186
+ (value) =>
187
+ value === undefined || !Number.isSafeInteger(value) || value < 0,
188
+ ) ||
189
+ inactive! > usageBytes
190
+ ) {
191
+ return { workingSetBytes: usageBytes, reclaimableFileBytes: 0 };
192
+ }
193
+ const reclaimableFileBytes = Math.max(0, inactive! - dirty! - writeback!);
194
+ return {
195
+ workingSetBytes: usageBytes - reclaimableFileBytes,
196
+ reclaimableFileBytes,
197
+ };
198
+ }
199
+
200
+ export type SampledCgroupMemoryUsage = {
201
+ memoryGiBSeconds: number;
202
+ sampledDurationSeconds: number;
203
+ peakUsageBytes: number;
204
+ peakWorkingSetBytes: number;
205
+ samples: number;
206
+ lastSample: { monotonicAtMs: number; usageBytes: number } | null;
207
+ };
208
+
209
+ /** Fixed-size diagnostic estimate; a missing sample breaks measured coverage. */
210
+ export function accumulateSampledCgroupMemoryUsage(
211
+ state: SampledCgroupMemoryUsage,
212
+ sample: { usageBytes: number; workingSetBytes: number } | null,
213
+ monotonicAtMs: number,
214
+ reservationMemoryBytes: number,
215
+ ): SampledCgroupMemoryUsage {
216
+ if (!sample) return { ...state, lastSample: null };
217
+ const durationSeconds = state.lastSample
218
+ ? Math.max(0, monotonicAtMs - state.lastSample.monotonicAtMs) / 1_000
219
+ : 0;
220
+ return {
221
+ memoryGiBSeconds:
222
+ state.memoryGiBSeconds +
223
+ (durationSeconds *
224
+ Math.max(
225
+ reservationMemoryBytes,
226
+ state.lastSample?.usageBytes ?? reservationMemoryBytes,
227
+ )) /
228
+ 1024 ** 3,
229
+ sampledDurationSeconds: state.sampledDurationSeconds + durationSeconds,
230
+ peakUsageBytes: Math.max(state.peakUsageBytes, sample.usageBytes),
231
+ peakWorkingSetBytes: Math.max(
232
+ state.peakWorkingSetBytes,
233
+ sample.workingSetBytes,
234
+ ),
235
+ samples: state.samples + 1,
236
+ lastSample: { monotonicAtMs, usageBytes: sample.usageBytes },
237
+ };
238
+ }
239
+
131
240
  /**
132
241
  * Build the parent process that owns runner liveness, the hard runtime
133
242
  * deadline, and the sandbox memory envelope outside the customer-code Node
@@ -141,14 +250,28 @@ export const RUNTIME_SANDBOX_MEMORY_ABORT_REASON =
141
250
  * 9 terminationDiagnosticPath, 10 memoryLedgerPath, 11 memoryPressurePath,
142
251
  * 12 cgroupRoot, 13 runnerPidPath.
143
252
  */
144
- export function buildDaytonaRuntimeWatchdogSource(): string {
253
+ export function buildDaytonaRuntimeWatchdogSource(
254
+ sandboxRuntimeLimits: PlaySandboxRuntimeLimits = STANDARD_PLAY_SANDBOX_RUNTIME_LIMITS,
255
+ memoryLimitMiB?: number,
256
+ ): string {
257
+ const nodeExecArgv = playRunnerNodeExecArgvForLimits(
258
+ sandboxRuntimeLimits,
259
+ memoryLimitMiB,
260
+ );
145
261
  return `
146
262
  const fs = require('node:fs');
147
263
  const { spawn } = require('node:child_process');
264
+ const cgroupMemoryWorkingSet = ${cgroupMemoryWorkingSet.toString()};
265
+ const accumulateSampledCgroupMemoryUsage = ${accumulateSampledCgroupMemoryUsage.toString()};
266
+ const configuredMemoryLimitBytes = ${memoryLimitMiB === undefined ? 'null' : memoryLimitMiB * 1024 * 1024};
148
267
  const runnerPath = process.argv[1];
149
268
  const configPath = process.argv[2];
150
269
  const runtimeStartedPath = process.argv[3];
151
270
  const runtimeCompletedPath = process.argv[4];
271
+ const sampledMemoryUsagePath = configuredMemoryLimitBytes === null
272
+ ? ''
273
+ : runtimeCompletedPath + '.memory-usage.json';
274
+ const reservationMemoryBytes = ${sandboxRuntimeLimits.memoryGiB * 1024 ** 3};
152
275
  const limitMs = Math.max(1, Number.parseInt(process.argv[5] || '', 10) || 1);
153
276
  const startupLimitMs = Math.max(1, Number.parseInt(process.argv[6] || '', 10) || 1);
154
277
  const terminalGraceMs = Math.max(1, Number.parseInt(process.argv[7] || '', 10) || 1);
@@ -161,7 +284,7 @@ const runnerPidPath = process.argv[13] || '';
161
284
  const runtimeLimitSeconds = Math.max(1, Math.ceil(limitMs / 1_000));
162
285
  const child = spawn(
163
286
  process.execPath,
164
- [${PLAY_RUNNER_NODE_EXEC_ARGV.map((flag) => JSON.stringify(flag)).join(', ')}, runnerPath, configPath],
287
+ [${nodeExecArgv.map((flag) => JSON.stringify(flag)).join(', ')}, runnerPath, configPath],
165
288
  {
166
289
  env: {
167
290
  ...process.env,
@@ -555,6 +678,47 @@ function markRuntimeLimit() {
555
678
  let memorySource = null;
556
679
  let memoryWatchDisabledReported = false;
557
680
  let lastMemoryPressureSignalAt = 0;
681
+ let sampledMemoryUsage = {
682
+ memoryGiBSeconds: 0,
683
+ sampledDurationSeconds: 0,
684
+ peakUsageBytes: 0,
685
+ peakWorkingSetBytes: 0,
686
+ samples: 0,
687
+ lastSample: null,
688
+ };
689
+ let memoryUsageWriteFailureReported = false;
690
+
691
+ // This is an internal estimate, not an authoritative Modal invoice. Raw cgroup
692
+ // usage includes cache; only the pressure guard subtracts reclaimable pages.
693
+ // A separate parent process observes Python peaks even during spawnSync.
694
+ function recordSampledMemoryUsage(sample) {
695
+ if (!sampledMemoryUsagePath) return;
696
+ sampledMemoryUsage = accumulateSampledCgroupMemoryUsage(
697
+ sampledMemoryUsage, sample, performance.now(), reservationMemoryBytes,
698
+ );
699
+ try {
700
+ fs.writeFileSync(sampledMemoryUsagePath + '.tmp', JSON.stringify({
701
+ schemaVersion: 1,
702
+ source: 'sampled_cgroup_estimate',
703
+ memoryGiBSeconds: sampledMemoryUsage.memoryGiBSeconds,
704
+ sampledDurationSeconds: sampledMemoryUsage.sampledDurationSeconds,
705
+ peakUsageBytes: sampledMemoryUsage.peakUsageBytes,
706
+ peakWorkingSetBytes: sampledMemoryUsage.peakWorkingSetBytes,
707
+ samples: sampledMemoryUsage.samples,
708
+ reservationMemoryMiB: reservationMemoryBytes / ${1024 ** 2},
709
+ observedAt: Date.now(),
710
+ }));
711
+ fs.renameSync(sampledMemoryUsagePath + '.tmp', sampledMemoryUsagePath);
712
+ } catch (error) {
713
+ if (memoryUsageWriteFailureReported) return;
714
+ memoryUsageWriteFailureReported = true;
715
+ process.stderr.write('[play-runner.memory_usage] ' + JSON.stringify({
716
+ event: 'sampled_memory_usage_write_failed',
717
+ errorName: error && typeof error.name === 'string' ? error.name : 'Error',
718
+ errorCode: error && typeof error.code === 'string' ? error.code : null,
719
+ }) + '\\n');
720
+ }
721
+ }
558
722
 
559
723
  function readMemoryNumberFile(path) {
560
724
  try {
@@ -574,16 +738,22 @@ function memorySourceCandidates() {
574
738
  kind: 'cgroup_v2',
575
739
  usagePath: cgroupRoot + '/memory.current',
576
740
  limitPath: cgroupRoot + '/memory.max',
741
+ statPath: cgroupRoot + '/memory.stat',
742
+ version: 'v2',
577
743
  },
578
744
  {
579
745
  kind: 'cgroup_v1',
580
746
  usagePath: cgroupRoot + '/memory.usage_in_bytes',
581
747
  limitPath: cgroupRoot + '/memory.limit_in_bytes',
748
+ statPath: cgroupRoot + '/memory.stat',
749
+ version: 'v1',
582
750
  },
583
751
  {
584
752
  kind: 'cgroup_v1_nested',
585
753
  usagePath: cgroupRoot + '/memory/memory.usage_in_bytes',
586
754
  limitPath: cgroupRoot + '/memory/memory.limit_in_bytes',
755
+ statPath: cgroupRoot + '/memory/memory.stat',
756
+ version: 'v1',
587
757
  },
588
758
  ];
589
759
  }
@@ -593,16 +763,24 @@ function readCgroupMemory() {
593
763
  for (let index = 0; index < candidates.length; index += 1) {
594
764
  const candidate = candidates[index];
595
765
  const usageBytes = readMemoryNumberFile(candidate.usagePath);
596
- const limitBytes = readMemoryNumberFile(candidate.limitPath);
597
- if (usageBytes === null || limitBytes === null) continue;
766
+ const providerLimitBytes = readMemoryNumberFile(candidate.limitPath);
767
+ if (usageBytes === null || providerLimitBytes === null) continue;
768
+ const limitBytes = configuredMemoryLimitBytes === null
769
+ ? providerLimitBytes
770
+ : Math.min(providerLimitBytes, configuredMemoryLimitBytes);
598
771
  if (!(limitBytes > 0)) continue;
599
772
  if (limitBytes >= ${RUNNER_MEMORY_CGROUP_UNLIMITED_BYTES}) continue;
600
773
  memorySource = candidate;
774
+ let stat = '';
775
+ try { stat = fs.readFileSync(candidate.statPath, 'utf8'); } catch {}
776
+ const workingSet = cgroupMemoryWorkingSet(usageBytes, stat, candidate.version);
601
777
  return {
602
778
  source: candidate.kind,
603
779
  usageBytes: usageBytes,
604
780
  limitBytes: limitBytes,
605
781
  ratio: usageBytes / limitBytes,
782
+ ...workingSet,
783
+ workingSetRatio: workingSet.workingSetBytes / limitBytes,
606
784
  };
607
785
  }
608
786
  return null;
@@ -641,6 +819,8 @@ function publishMemoryPressure(sample) {
641
819
  usageBytes: sample.usageBytes,
642
820
  limitBytes: sample.limitBytes,
643
821
  ratio: sample.ratio,
822
+ workingSetBytes: sample.workingSetBytes,
823
+ reclaimableFileBytes: sample.reclaimableFileBytes,
644
824
  at: Date.now(),
645
825
  }),
646
826
  );
@@ -650,11 +830,12 @@ function publishMemoryPressure(sample) {
650
830
  const memoryWatch = setInterval(() => {
651
831
  if (settled || memoryAborted) return;
652
832
  const sample = readCgroupMemory();
833
+ recordSampledMemoryUsage(sample);
653
834
  if (!sample) {
654
835
  reportMemoryWatchDisabled();
655
836
  return;
656
837
  }
657
- if (sample.ratio >= ${RUNNER_MEMORY_ABORT_RATIO}) {
838
+ if (sample.workingSetRatio >= ${RUNNER_MEMORY_ABORT_RATIO}) {
658
839
  memoryAborted = true;
659
840
  clearInterval(memoryWatch);
660
841
  process.stderr.write(
@@ -669,11 +850,13 @@ const memoryWatch = setInterval(() => {
669
850
  memoryUsageBytes: sample.usageBytes,
670
851
  memoryLimitBytes: sample.limitBytes,
671
852
  memoryUsageRatio: Math.round(sample.ratio * 10_000) / 10_000,
853
+ memoryWorkingSetBytes: sample.workingSetBytes,
854
+ memoryReclaimableFileBytes: sample.reclaimableFileBytes,
672
855
  memoryLedger: readMemoryLedger(),
673
856
  });
674
857
  return;
675
858
  }
676
- if (sample.ratio >= ${RUNNER_MEMORY_PRESSURE_RATIO}) {
859
+ if (sample.workingSetRatio >= ${RUNNER_MEMORY_PRESSURE_RATIO}) {
677
860
  const now = Date.now();
678
861
  if (now - lastMemoryPressureSignalAt < ${RUNNER_MEMORY_PRESSURE_SIGNAL_INTERVAL_MS}) {
679
862
  return;
@@ -743,8 +926,9 @@ const startupTimer = setTimeout(() => {
743
926
  if (fs.existsSync(runtimeStartedPath)) armRuntimeDeadline();
744
927
  if (fs.existsSync(runtimeCompletedPath)) armTerminalGrace();
745
928
 
746
- function finish(code) {
929
+ function finish(code, signal) {
747
930
  if (settled) return;
931
+ recordSampledMemoryUsage(readCgroupMemory());
748
932
  settled = true;
749
933
  clearInterval(runtimeBoundaryPoll);
750
934
  clearInterval(memoryWatch);
@@ -764,6 +948,8 @@ function finish(code) {
764
948
  ? 1
765
949
  : typeof code === 'number'
766
950
  ? code
951
+ : signal === 'SIGABRT'
952
+ ? 134
767
953
  : 1;
768
954
  }
769
955
 
@@ -779,7 +965,7 @@ child.once('exit', (code, signal) => {
779
965
  childExitCode: typeof code === 'number' ? code : null,
780
966
  childSignal: typeof signal === 'string' ? signal : null,
781
967
  });
782
- finish(code);
968
+ finish(code, signal);
783
969
  });
784
970
  process.once('SIGTERM', () =>
785
971
  stopChild('watchdog_signal', { watchdogSignal: 'SIGTERM' }),
@@ -40,6 +40,10 @@ import {
40
40
  PLAY_RUNNER_TERMINAL_GRACE_SECONDS,
41
41
  STANDARD_PLAY_RUNTIME_LIMIT_SECONDS,
42
42
  } from '../../runtime-constants';
43
+ import {
44
+ STANDARD_PLAY_SANDBOX_RUNTIME_LIMITS,
45
+ validatePlaySandboxRuntimeLimits,
46
+ } from '../../../plays/sandbox-runtime-limits';
43
47
  import {
44
48
  loadDaytonaRequiredConfig,
45
49
  loadDaytonaRunnerPathsConfig,
@@ -1842,6 +1846,20 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
1842
1846
  ) {
1843
1847
  let sandboxForAttempt: DaytonaSandbox | null = null;
1844
1848
  let resourceAccounting: Promise<void> | null = null;
1849
+ const trackResourceAccounting = (pending: Promise<void>) => {
1850
+ resourceAccountingPending = true;
1851
+ resourceAccounting = pending;
1852
+ void pending.then(
1853
+ () => {
1854
+ resourceAccountingPending = false;
1855
+ },
1856
+ () => {
1857
+ resourceAccountingPending = false;
1858
+ resourceAccountingFailed = true;
1859
+ },
1860
+ );
1861
+ return pending;
1862
+ };
1845
1863
  // How far this sandbox's start got. The retry decision below reads it.
1846
1864
  let commandSent = false;
1847
1865
  let parkStarted = false;
@@ -1871,28 +1889,33 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
1871
1889
  acquiredSandbox.billingStartedAt - startedAt;
1872
1890
  const sandbox = acquiredSandbox.sandbox;
1873
1891
  sandboxForAttempt = sandbox;
1892
+ const sandboxLimits = validatePlaySandboxRuntimeLimits(
1893
+ config.context.sandboxRuntimeLimits ?? {
1894
+ ...STANDARD_PLAY_SANDBOX_RUNTIME_LIMITS,
1895
+ },
1896
+ );
1874
1897
  // Payload upload and session allocation do not execute customer code.
1875
1898
  // Overlap them with accounting, but join every resource ACK before
1876
1899
  // command dispatch, retry, or cancellation cleanup.
1877
- resourceAccountingPending = true;
1878
- resourceAccounting = (async () => {
1879
- await reportRuntimeResource(acquiredSandbox);
1880
- await sandboxLifecycle.settlePendingCreates();
1881
- for (const acquired of sandboxLifecycle.acquiredSandboxes()) {
1882
- await reportRuntimeResource(acquired);
1883
- }
1884
- })();
1885
- // Observe settlement immediately; the joins below propagate failures.
1886
- void resourceAccounting.then(
1887
- () => {
1888
- resourceAccountingPending = false;
1889
- },
1890
- () => {
1891
- resourceAccountingPending = false;
1892
- resourceAccountingFailed = true;
1893
- },
1900
+ const resourceAccountingForAttempt = trackResourceAccounting(
1901
+ (async () => {
1902
+ await reportRuntimeResource(acquiredSandbox);
1903
+ await sandboxLifecycle.settlePendingCreates();
1904
+ for (const acquired of sandboxLifecycle.acquiredSandboxes()) {
1905
+ await reportRuntimeResource(acquired);
1906
+ }
1907
+ })(),
1894
1908
  );
1895
1909
  throwIfCancellationRequested();
1910
+ if (
1911
+ sandbox.cpu !== sandboxLimits.cpu ||
1912
+ sandbox.memory !== sandboxLimits.memoryGiB
1913
+ ) {
1914
+ await resourceAccountingForAttempt;
1915
+ throw new Error(
1916
+ `RUNTIME_SANDBOX_PROFILE_MISMATCH: Daytona created ${sandbox.cpu} CPU / ${sandbox.memory}GiB memory for a ${sandboxLimits.cpu} CPU / ${sandboxLimits.memoryGiB}GiB Play profile requested at create.`,
1917
+ );
1918
+ }
1896
1919
 
1897
1920
  const { workdir: workDir } = loadDaytonaRunnerPathsConfig();
1898
1921
  emitDaytonaStage(callbacks, config.context, 'workdir:resolved', {
@@ -477,7 +477,10 @@ export const localProcessPlayRunnerBackend: PlayRunnerBackend = {
477
477
  'node',
478
478
  [
479
479
  '-e',
480
- buildDaytonaRuntimeWatchdogSource(),
480
+ buildDaytonaRuntimeWatchdogSource(
481
+ config.context.sandboxRuntimeLimits ??
482
+ STANDARD_PLAY_SANDBOX_RUNTIME_LIMITS,
483
+ ),
481
484
  runnerPath,
482
485
  configPath,
483
486
  runtimeStartedPath,
@@ -0,0 +1,222 @@
1
+ import type { ModalClient } from 'modal';
2
+ import { loadModalClientConfig } from '../../modal-runtime-config';
3
+ import { MODAL_SANDBOX_COMPUTE_PRICING_USD } from '../../worker-api-types';
4
+
5
+ type ResourceUsageClient = {
6
+ cpClient: Pick<
7
+ ModalClient['cpClient'],
8
+ 'sandboxList' | 'sandboxGetResourceUsage'
9
+ >;
10
+ environmentName: ModalClient['environmentName'];
11
+ };
12
+
13
+ type ResourceUsageIdentity = {
14
+ source: 'provider_resource_usage';
15
+ sandboxId: string;
16
+ appId: string;
17
+ readStartedAt: number;
18
+ observedAt: number;
19
+ /** The provider exposes neither a finality marker nor a sample horizon. */
20
+ partial: true;
21
+ };
22
+
23
+ export type ModalSandboxResourceUsage = ResourceUsageIdentity &
24
+ (
25
+ | {
26
+ status: 'observed' | 'empty';
27
+ raw: {
28
+ cpuCoreNanosecs: number;
29
+ memGibNanosecs: number;
30
+ gpuNanosecs: number;
31
+ gpuType: string | null;
32
+ };
33
+ cpuCoreSeconds: number;
34
+ memoryGiBSeconds: number;
35
+ /** CPU/memory counter cost, not a bill or billing-adjusted usage. */
36
+ reportedResourceCostEstimateUsd: number;
37
+ billingAdjustment: 'unknown';
38
+ gpuCostIncluded: false;
39
+ }
40
+ | {
41
+ status: 'unavailable';
42
+ reason:
43
+ | 'deadline'
44
+ | 'ownership_not_observed'
45
+ | 'ownership_mismatch'
46
+ | 'invalid_provider_response'
47
+ | 'provider_read_failed';
48
+ providerErrorCode?: number;
49
+ }
50
+ );
51
+
52
+ /**
53
+ * Preview diagnostics only. Modal's installed SDK has no high-level usage
54
+ * reader, so keep its typed, unsupported control-plane RPC at this edge.
55
+ * These counters have undocumented billing adjustment and ingestion lag;
56
+ * they must never become an irrevocable customer settlement fact.
57
+ */
58
+ export async function readModalSandboxResourceUsage(input: {
59
+ sandboxId: string;
60
+ expectedAppId: string;
61
+ /** Exact selector retained by this diagnostic's sandbox creator. */
62
+ ownershipTags: Record<string, string>;
63
+ environment: 'preview';
64
+ /** Modal's environment name is independent of Deepline's Preview lane. */
65
+ modalEnvironment?: string;
66
+ timeoutMs?: number;
67
+ client?: ResourceUsageClient;
68
+ }): Promise<ModalSandboxResourceUsage> {
69
+ const sandboxId = input.sandboxId.trim();
70
+ const appId = input.expectedAppId.trim();
71
+ const tags = Object.entries(input.ownershipTags);
72
+ const timeoutMs = input.timeoutMs ?? 10_000;
73
+ if (input.environment !== 'preview') {
74
+ throw new Error(
75
+ 'Modal resource usage diagnostics are restricted to Preview.',
76
+ );
77
+ }
78
+ if (
79
+ !sandboxId ||
80
+ !appId ||
81
+ tags.length === 0 ||
82
+ tags.some(([key, value]) => !key.trim() || !value.trim())
83
+ ) {
84
+ throw new Error(
85
+ 'Modal usage diagnostics require a sandbox id, app id, and exact non-empty creation tags.',
86
+ );
87
+ }
88
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) {
89
+ throw new Error(
90
+ 'Modal usage diagnostic timeout must be positive milliseconds.',
91
+ );
92
+ }
93
+
94
+ const readStartedAt = Date.now();
95
+ const identity = (): ResourceUsageIdentity => ({
96
+ source: 'provider_resource_usage',
97
+ sandboxId,
98
+ appId,
99
+ readStartedAt,
100
+ observedAt: Date.now(),
101
+ partial: true,
102
+ });
103
+ const unavailable = (
104
+ reason: Extract<
105
+ ModalSandboxResourceUsage,
106
+ { status: 'unavailable' }
107
+ >['reason'],
108
+ ): Extract<ModalSandboxResourceUsage, { status: 'unavailable' }> => ({
109
+ ...identity(),
110
+ status: 'unavailable',
111
+ reason,
112
+ });
113
+ const controller = new AbortController();
114
+ let timer: ReturnType<typeof setTimeout> | undefined;
115
+ const deadline = new Promise<ModalSandboxResourceUsage>((resolve) => {
116
+ timer = setTimeout(() => {
117
+ controller.abort();
118
+ resolve(unavailable('deadline'));
119
+ }, timeoutMs);
120
+ });
121
+ const read = async (): Promise<ModalSandboxResourceUsage> => {
122
+ try {
123
+ const client = input.client ?? (await loadModalClientConfig()).client;
124
+ const options = {
125
+ signal: controller.signal,
126
+ timeoutMs,
127
+ retries: 0,
128
+ };
129
+ let beforeTimestamp = 0;
130
+ for (;;) {
131
+ controller.signal.throwIfAborted();
132
+ const page = await client.cpClient.sandboxList(
133
+ {
134
+ appId,
135
+ environmentName: client.environmentName(input.modalEnvironment),
136
+ includeFinished: true,
137
+ tags: tags.map(([tagName, tagValue]) => ({ tagName, tagValue })),
138
+ beforeTimestamp,
139
+ },
140
+ options,
141
+ );
142
+ controller.signal.throwIfAborted();
143
+ const sandbox = page.sandboxes.find((entry) => entry.id === sandboxId);
144
+ if (sandbox) {
145
+ const observedTags = new Map(
146
+ sandbox.tags.map((tag) => [tag.tagName, tag.tagValue]),
147
+ );
148
+ if (
149
+ sandbox.appId !== appId ||
150
+ tags.some(([key, value]) => observedTags.get(key) !== value)
151
+ ) {
152
+ return unavailable('ownership_mismatch');
153
+ }
154
+ break;
155
+ }
156
+ if (page.sandboxes.length === 0) {
157
+ return unavailable('ownership_not_observed');
158
+ }
159
+ const oldest = Math.min(
160
+ ...page.sandboxes.map((entry) => entry.createdAt),
161
+ );
162
+ if (
163
+ !Number.isFinite(oldest) ||
164
+ oldest <= 0 ||
165
+ (beforeTimestamp !== 0 && oldest >= beforeTimestamp)
166
+ ) {
167
+ return unavailable('invalid_provider_response');
168
+ }
169
+ beforeTimestamp = oldest;
170
+ }
171
+ const raw = await client.cpClient.sandboxGetResourceUsage(
172
+ { sandboxId },
173
+ options,
174
+ );
175
+ controller.signal.throwIfAborted();
176
+ const counters = [
177
+ raw.cpuCoreNanosecs,
178
+ raw.memGibNanosecs,
179
+ raw.gpuNanosecs,
180
+ ];
181
+ if (counters.some((value) => !Number.isSafeInteger(value) || value < 0)) {
182
+ return unavailable('invalid_provider_response');
183
+ }
184
+ const cpuCoreSeconds = raw.cpuCoreNanosecs / 1_000_000_000;
185
+ const memoryGiBSeconds = raw.memGibNanosecs / 1_000_000_000;
186
+ return {
187
+ ...identity(),
188
+ status: counters.every((value) => value === 0) ? 'empty' : 'observed',
189
+ raw: {
190
+ cpuCoreNanosecs: raw.cpuCoreNanosecs,
191
+ memGibNanosecs: raw.memGibNanosecs,
192
+ gpuNanosecs: raw.gpuNanosecs,
193
+ gpuType: raw.gpuType ?? null,
194
+ },
195
+ cpuCoreSeconds,
196
+ memoryGiBSeconds,
197
+ reportedResourceCostEstimateUsd: Number(
198
+ (
199
+ cpuCoreSeconds *
200
+ MODAL_SANDBOX_COMPUTE_PRICING_USD.physicalCoreSecond +
201
+ memoryGiBSeconds * MODAL_SANDBOX_COMPUTE_PRICING_USD.memoryGiBSecond
202
+ ).toFixed(12),
203
+ ),
204
+ billingAdjustment: 'unknown',
205
+ gpuCostIncluded: false,
206
+ };
207
+ } catch (error) {
208
+ const result = unavailable(
209
+ controller.signal.aborted ? 'deadline' : 'provider_read_failed',
210
+ );
211
+ const code = (error as { code?: unknown } | null)?.code;
212
+ return typeof code === 'number' && Number.isFinite(code)
213
+ ? { ...result, providerErrorCode: code }
214
+ : result;
215
+ }
216
+ };
217
+ try {
218
+ return await Promise.race([read(), deadline]);
219
+ } finally {
220
+ clearTimeout(timer);
221
+ }
222
+ }