smolmachines 1.27.3 → 1.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/README.md +7 -3
- package/dist/assets.js +3 -1
- package/dist/binding.d.ts +90 -8
- package/dist/errors.d.ts +22 -2
- package/dist/index.d.ts +2 -1
- package/dist/machine.d.ts +65 -4
- package/dist/machine.js +104 -4
- package/dist/native.d.ts +33 -3
- package/dist/transport.d.ts +25 -3
- package/dist/transport.js +180 -8
- package/dist/types.d.ts +35 -0
- package/package.json +6 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to the Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
Copyright 2024-2026 smolvm contributors
|
|
179
|
+
|
|
180
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
181
|
+
you may not use this file except in compliance with the License.
|
|
182
|
+
You may obtain a copy of the License at
|
|
183
|
+
|
|
184
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
185
|
+
|
|
186
|
+
Unless required by applicable law or agreed to in writing, software
|
|
187
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
188
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
189
|
+
See the License for the specific language governing permissions and
|
|
190
|
+
limitations under the License.
|
package/README.md
CHANGED
|
@@ -205,10 +205,14 @@ vLLM's unrestricted adapter loader.
|
|
|
205
205
|
```ts
|
|
206
206
|
import { Machine } from 'smolmachines';
|
|
207
207
|
|
|
208
|
-
|
|
208
|
+
// A machine of an image. The image is pulled once; later machines of the same
|
|
209
|
+
// image start from it in about a second.
|
|
210
|
+
const m = await Machine.create({
|
|
211
|
+
image: 'python:3.12',
|
|
212
|
+
resources: { cpus: 2, memoryMb: 1024, network: true },
|
|
213
|
+
});
|
|
209
214
|
try {
|
|
210
|
-
|
|
211
|
-
const res = await m.run('python:3.12', ['python', '-c', 'print(2 ** 10)']);
|
|
215
|
+
const res = await m.exec(['python', '-c', 'print(2 ** 10)']);
|
|
212
216
|
res.assertSuccess();
|
|
213
217
|
console.log(res.stdout); // "1024\n"
|
|
214
218
|
|
package/dist/assets.js
CHANGED
|
@@ -90,7 +90,9 @@ function wireBundledAssets() {
|
|
|
90
90
|
if (!assets.libDir) {
|
|
91
91
|
assets.libDir = nativeDir;
|
|
92
92
|
}
|
|
93
|
-
|
|
93
|
+
// Shipped zstd-compressed; an older or locally built package has the plain tar.
|
|
94
|
+
const compressedRootfs = (0, node_path_1.join)(nativeDir, 'agent-rootfs.tar.zst');
|
|
95
|
+
const rootfsTar = (0, node_fs_1.existsSync)(compressedRootfs) ? compressedRootfs : (0, node_path_1.join)(nativeDir, 'agent-rootfs.tar');
|
|
94
96
|
if (!assets.agentRootfs && !assets.agentRootfsTar && (0, node_fs_1.existsSync)(rootfsTar)) {
|
|
95
97
|
assets.agentRootfsTar = rootfsTar;
|
|
96
98
|
}
|
package/dist/binding.d.ts
CHANGED
|
@@ -77,6 +77,10 @@ export interface MachineSummary {
|
|
|
77
77
|
branchable: boolean
|
|
78
78
|
/** Creation time, seconds since the Unix epoch. */
|
|
79
79
|
createdAt: number
|
|
80
|
+
/** CPU, memory and disk sizes, readable whether or not it runs. */
|
|
81
|
+
resources: MachineSizes
|
|
82
|
+
/** Published ports, readable whether or not it runs. */
|
|
83
|
+
ports: Array<PublishedPort>
|
|
80
84
|
}
|
|
81
85
|
/** Whether this host can run local machines, from the engine's own checks. */
|
|
82
86
|
export interface HostAvailability {
|
|
@@ -214,6 +218,45 @@ export interface ExecOptions {
|
|
|
214
218
|
* machines only; a bare VM rejects it rather than run as root.
|
|
215
219
|
*/
|
|
216
220
|
user?: string
|
|
221
|
+
/**
|
|
222
|
+
* Text written to the command's stdin, which is then closed. It is
|
|
223
|
+
* passed over the agent connection and never written to the guest disk.
|
|
224
|
+
*/
|
|
225
|
+
stdin?: string
|
|
226
|
+
}
|
|
227
|
+
/** How a stop or delete treats work still running on the machine. */
|
|
228
|
+
export interface StopOptions {
|
|
229
|
+
/**
|
|
230
|
+
* Wait up to this many seconds for running commands and file transfers
|
|
231
|
+
* to finish first. What is still running then fails with code
|
|
232
|
+
* `INTERRUPTED`. Default: stop at once.
|
|
233
|
+
*/
|
|
234
|
+
drainSecs?: number
|
|
235
|
+
/**
|
|
236
|
+
* Seconds the workload gets to exit after its stop signal, instead of
|
|
237
|
+
* the machine's stop timeout; 0 kills it at once. Stop only.
|
|
238
|
+
*/
|
|
239
|
+
timeoutSecs?: number
|
|
240
|
+
}
|
|
241
|
+
/** CPU, memory and disk sizes of a machine. */
|
|
242
|
+
export interface MachineSizes {
|
|
243
|
+
/** vCPUs. */
|
|
244
|
+
cpus: number
|
|
245
|
+
/** Memory in MiB. */
|
|
246
|
+
memoryMib: number
|
|
247
|
+
/** Storage disk in GiB. */
|
|
248
|
+
storageGib: number
|
|
249
|
+
/** Container overlay disk in GiB. */
|
|
250
|
+
overlayGib: number
|
|
251
|
+
}
|
|
252
|
+
/** One published port of a machine. */
|
|
253
|
+
export interface PublishedPort {
|
|
254
|
+
/** Host port. */
|
|
255
|
+
host: number
|
|
256
|
+
/** Guest port. */
|
|
257
|
+
guest: number
|
|
258
|
+
/** `"tcp"` or `"udp"`. */
|
|
259
|
+
protocol: string
|
|
217
260
|
}
|
|
218
261
|
/** Options for writing a file into the VM. */
|
|
219
262
|
export interface FileWriteOptions {
|
|
@@ -297,9 +340,21 @@ export declare class NapiMachine {
|
|
|
297
340
|
/**
|
|
298
341
|
* Attach to an existing machine by name, starting it if stopped
|
|
299
342
|
* (start-or-reconnect). Lets a persisted machine be re-opened in a new
|
|
300
|
-
* process — backs the SDK's local `Machine.connect()`.
|
|
343
|
+
* process — backs the SDK's local `Machine.connect()`. The boot runs off
|
|
344
|
+
* the JavaScript thread, so the event loop keeps serving while it waits.
|
|
301
345
|
*/
|
|
302
|
-
static connect(name: string, interceptorAddress?: string | undefined | null, interceptorToken?: string | undefined | null): NapiMachine
|
|
346
|
+
static connect(name: string, interceptorAddress?: string | undefined | null, interceptorToken?: string | undefined | null): Promise<NapiMachine>
|
|
347
|
+
/**
|
|
348
|
+
* Open a handle to an existing machine without starting or connecting to
|
|
349
|
+
* it, to read its state, stop, resize or delete it. Fails when no machine
|
|
350
|
+
* of that name exists.
|
|
351
|
+
*/
|
|
352
|
+
static open(name: string): NapiMachine
|
|
353
|
+
/**
|
|
354
|
+
* Delete a machine by name, whether it runs or not, without booting it.
|
|
355
|
+
* A machine that does not exist is already deleted.
|
|
356
|
+
*/
|
|
357
|
+
static deleteByName(name: string, options?: StopOptions | undefined | null): Promise<void>
|
|
303
358
|
/**
|
|
304
359
|
* Every machine in this host's shared machine database, in name order.
|
|
305
360
|
* The database is shared with the CLI and every other embedder on the
|
|
@@ -328,7 +383,7 @@ export declare class NapiMachine {
|
|
|
328
383
|
/**
|
|
329
384
|
* Start a command and return a live `ExecStream` of its output events. A
|
|
330
385
|
* worker thread drives the engine's `exec_streaming_with` and feeds an mpsc
|
|
331
|
-
* channel the stream drains incrementally (
|
|
386
|
+
* channel the stream drains incrementally (apart from partial UTF-8).
|
|
332
387
|
*/
|
|
333
388
|
execStream(command: Array<string>, options?: ExecOptions | undefined | null): ExecStream
|
|
334
389
|
/** Get the child PID if the VM is running. */
|
|
@@ -391,18 +446,45 @@ export declare class NapiMachine {
|
|
|
391
446
|
execStreaming(command: Array<string>, options?: ExecOptions | undefined | null): Promise<Array<ExecStreamEvent>>
|
|
392
447
|
/** Copy guest-local staged mounts back to their host sources. */
|
|
393
448
|
sync(): Promise<void>
|
|
394
|
-
/**
|
|
395
|
-
|
|
449
|
+
/**
|
|
450
|
+
* Stop the machine VM gracefully: its workload gets its stop signal and
|
|
451
|
+
* stop timeout first. A command or file transfer still running fails
|
|
452
|
+
* with code `INTERRUPTED` unless `drainSecs` lets it finish.
|
|
453
|
+
*/
|
|
454
|
+
stop(options?: StopOptions | undefined | null): Promise<void>
|
|
396
455
|
/** Save execution durably and stop the machine. */
|
|
397
456
|
pause(): Promise<void>
|
|
398
457
|
resume(): Promise<void>
|
|
399
|
-
/**
|
|
400
|
-
|
|
458
|
+
/**
|
|
459
|
+
* The machine's published ports, read from its record whether or not it
|
|
460
|
+
* runs.
|
|
461
|
+
*/
|
|
462
|
+
publishedPorts(): Array<PublishedPort>
|
|
463
|
+
/**
|
|
464
|
+
* Make `ports` the machine's published ports. A running machine changes
|
|
465
|
+
* its TCP ports now; UDP ports, or any port of a machine not on
|
|
466
|
+
* virtio-net, change only while it is stopped.
|
|
467
|
+
*/
|
|
468
|
+
setPorts(ports: Array<PublishedPort>): Promise<void>
|
|
469
|
+
/**
|
|
470
|
+
* Pack this stopped machine into a `.smolmachine` at `output`; returns
|
|
471
|
+
* the artifact's path.
|
|
472
|
+
*/
|
|
473
|
+
pack(output: string): Promise<string>
|
|
474
|
+
/** Create the stopped machine `name` from a `.smolmachine` artifact. */
|
|
475
|
+
static createFromPack(name: string, pack: string): Promise<NapiMachine>
|
|
476
|
+
/**
|
|
477
|
+
* Stop the machine and clean up all storage (disks, config). A command or
|
|
478
|
+
* file transfer still running fails with code `INTERRUPTED` unless
|
|
479
|
+
* `drainSecs` lets it finish.
|
|
480
|
+
*/
|
|
481
|
+
delete(options?: StopOptions | undefined | null): Promise<void>
|
|
401
482
|
}
|
|
402
483
|
/**
|
|
403
484
|
* A live, incremental exec stream. `next()` resolves with the next event as it
|
|
404
485
|
* arrives (off the libuv loop via spawn_blocking), or `null` once the command
|
|
405
|
-
* exits and the channel closes.
|
|
486
|
+
* exits and the channel closes. Only an incomplete UTF-8 code point is held
|
|
487
|
+
* across events. Mirrors the Python SDK's ExecStream iterator.
|
|
406
488
|
*/
|
|
407
489
|
export declare class ExecStream {
|
|
408
490
|
next(): Promise<ExecStreamEvent | null>
|
package/dist/errors.d.ts
CHANGED
|
@@ -15,8 +15,28 @@ export interface CloudErrorDetail {
|
|
|
15
15
|
/** How long the server asked callers to wait before retrying (ms). */
|
|
16
16
|
retryAfterMs?: number | undefined;
|
|
17
17
|
}
|
|
18
|
+
/** The stable codes a `SmolError` carries, to branch on instead of matching
|
|
19
|
+
* message text (which may change). Unknown codes from a newer engine or
|
|
20
|
+
* control plane still type-check as plain strings.
|
|
21
|
+
*
|
|
22
|
+
* - `NOT_FOUND`: no such machine, file (`readFile` of a missing path),
|
|
23
|
+
* image or checkpoint.
|
|
24
|
+
* - `INTERRUPTED`: a `stop()` or `delete()` cut off this command or file
|
|
25
|
+
* transfer (local). `MACHINE_INTERRUPTED`: the same, on the cloud.
|
|
26
|
+
* - `CONFLICT`: the machine's state does not allow it now (it is being
|
|
27
|
+
* stopped or deleted, it has dependent branches, ...). `INVALID_STATE`:
|
|
28
|
+
* the same, from an older engine path.
|
|
29
|
+
* - `INVALID_CONFIG`: a usage error in the arguments. `CONFIG_ERROR`: the
|
|
30
|
+
* engine refused a configuration value. `NOT_SUPPORTED`: not on this
|
|
31
|
+
* target.
|
|
32
|
+
* - `KVM_UNAVAILABLE`, `HYPERVISOR_UNAVAILABLE`: this host cannot run
|
|
33
|
+
* machines. `STORAGE_ERROR`, `MOUNT_ERROR`: disk or mount failures.
|
|
34
|
+
* - `COMMAND_FAILED`: a command exited non-zero (`assertSuccess()`).
|
|
35
|
+
* - `TIMEOUT`, `CONNECTION`, `UNAUTHORIZED`: cloud transport failures.
|
|
36
|
+
* - `SMOLVM_ERROR`: anything not classified above. */
|
|
37
|
+
export type SmolErrorCode = "NOT_FOUND" | "INTERRUPTED" | "MACHINE_INTERRUPTED" | "CONFLICT" | "INVALID_STATE" | "FORBIDDEN" | "INVALID_CONFIG" | "CONFIG_ERROR" | "NOT_SUPPORTED" | "KVM_UNAVAILABLE" | "HYPERVISOR_UNAVAILABLE" | "STORAGE_ERROR" | "MOUNT_ERROR" | "COMMAND_FAILED" | "TIMEOUT" | "CONNECTION" | "UNAUTHORIZED" | "SMOLVM_ERROR" | (string & {});
|
|
18
38
|
export declare class SmolError extends Error {
|
|
19
|
-
readonly code:
|
|
39
|
+
readonly code: SmolErrorCode;
|
|
20
40
|
/** HTTP status, for an error the cloud API returned. */
|
|
21
41
|
readonly status?: number | undefined;
|
|
22
42
|
/** The server's stable problem code, for an error the cloud API returned. */
|
|
@@ -27,7 +47,7 @@ export declare class SmolError extends Error {
|
|
|
27
47
|
readonly retryable: boolean;
|
|
28
48
|
/** How long the server asked callers to wait before retrying (ms). */
|
|
29
49
|
readonly retryAfterMs?: number | undefined;
|
|
30
|
-
constructor(code:
|
|
50
|
+
constructor(code: SmolErrorCode, message: string, detail?: CloudErrorDetail);
|
|
31
51
|
}
|
|
32
52
|
/** The active backend can't serve this operation (e.g. volumes on local). */
|
|
33
53
|
export declare class NotSupportedError extends SmolError {
|
package/dist/index.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export { Machine, Episode, CacheDisk, Job } from './machine';
|
|
|
20
20
|
export { RolloutClient, RolloutError, adapterSha256 } from './rollout';
|
|
21
21
|
export type { BuildRolloutJobOptions, EnsureRolloutExecutorOptions, RolloutBatchItem, RolloutClientOptions, RolloutCompletion, RolloutExecutorInfo, RolloutGenerateResponse, RolloutJob, RolloutPolicyInfo, RolloutPrompt, RolloutSampling, } from './rollout';
|
|
22
22
|
export { SmolError, NotSupportedError, InvalidConfigError, ExecutionError } from './errors';
|
|
23
|
+
export type { SmolErrorCode } from './errors';
|
|
23
24
|
export type { CloudCheckpointInfo } from './transport';
|
|
24
25
|
export type { LocalAvailability, LocalUnavailableCode } from './availability';
|
|
25
|
-
export type { MachineConfig, CredentialSpec, CacheDiskRef, CacheDiskInfo, CacheDiskVersion, CreateCacheDiskOptions, PublishedCacheDisk, NetworkPolicy, RestoreCheckpointOptions, MachineState, ResourceSpec, MountSpec, PortSpec, BranchOptions, BranchBatchOptions, CheckpointOptions, ResizeOptions, MachineResources, ForkOptions, ForkBatchOptions, AssignOptions, ExecOptions, JobInfo, JobOptions, JobOutput, JobOutputOptions, JobState, JobWaitOptions, ExecResult, MachineUsageReport, ShareLink, PortableCheckpointInfo, MachineUsageTotals, MachineCostBreakdown, ImageInfo, MachineSummary, ListOptions, ExecEvent, ConnectOptions, EgressInterceptor, StartOptions, ResumeOptions, WaitReadyOptions, PortEndpoint, } from './types';
|
|
26
|
+
export type { MachineConfig, CredentialSpec, CacheDiskRef, CacheDiskInfo, CacheDiskVersion, CreateCacheDiskOptions, PublishedCacheDisk, NetworkPolicy, RestoreCheckpointOptions, MachineState, ResourceSpec, MountSpec, PortSpec, BranchOptions, BranchBatchOptions, CheckpointOptions, ResizeOptions, MachineResources, ForkOptions, ForkBatchOptions, AssignOptions, ExecOptions, JobInfo, JobOptions, JobOutput, JobOutputOptions, JobState, JobWaitOptions, ExecResult, MachineUsageReport, ShareLink, PortableCheckpointInfo, MachineUsageTotals, MachineCostBreakdown, ImageInfo, MachineSummary, MachineSizes, PublishedPort, StopOptions, ListOptions, ExecEvent, ConnectOptions, EgressInterceptor, StartOptions, ResumeOptions, WaitReadyOptions, PortEndpoint, } from './types';
|
package/dist/machine.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { type LocalAvailability } from "./availability";
|
|
11
11
|
import { type CloudCheckpointInfo, type Transport } from "./transport";
|
|
12
|
-
import type { CacheDiskInfo, CreateCacheDiskOptions, PublishedCacheDisk, MachineResources, ResizeOptions, AssignOptions, BranchBatchOptions, BranchOptions, CheckpointOptions, ConnectOptions, ExecEvent, ExecOptions, ExecResult, ForkBatchOptions, ForkOptions, ImageInfo, JobInfo, JobOptions, JobOutput, JobOutputOptions, JobWaitOptions, ListOptions, MachineConfig, MachineSummary, NetworkPolicy, RestoreCheckpointOptions, StartOptions, ResumeOptions, MachineUsageReport, ShareLink, PortableCheckpointInfo, PortEndpoint, PortSpec, WaitReadyOptions } from "./types";
|
|
12
|
+
import type { CacheDiskInfo, CreateCacheDiskOptions, PublishedCacheDisk, MachineResources, ResizeOptions, AssignOptions, BranchBatchOptions, BranchOptions, CheckpointOptions, ConnectOptions, ExecEvent, ExecOptions, ExecResult, ForkBatchOptions, ForkOptions, ImageInfo, JobInfo, JobOptions, JobOutput, JobOutputOptions, JobWaitOptions, ListOptions, MachineConfig, MachineSummary, NetworkPolicy, RestoreCheckpointOptions, StartOptions, ResumeOptions, MachineUsageReport, ShareLink, PortableCheckpointInfo, PortEndpoint, PortSpec, PublishedPort, StopOptions, WaitReadyOptions } from "./types";
|
|
13
13
|
export declare class Machine {
|
|
14
14
|
private readonly transport;
|
|
15
15
|
private constructor();
|
|
@@ -42,6 +42,22 @@ export declare class Machine {
|
|
|
42
42
|
* @param conn backend selection (local by default; cloud via `{ target: 'cloud', apiKey }` or `SMOL_CLOUD_TOKEN`)
|
|
43
43
|
*/
|
|
44
44
|
static connect(id: string, conn?: ConnectOptions): Promise<Machine>;
|
|
45
|
+
/**
|
|
46
|
+
* Open a handle to an EXISTING machine without starting it or waiting for
|
|
47
|
+
* it — to describe, stop, resize or delete it, including one that can no
|
|
48
|
+
* longer boot (its image is gone, the host lost KVM). `state()` reports
|
|
49
|
+
* `"stopped"` for a stopped machine. Call `start()` to boot it.
|
|
50
|
+
* - local: by name; rejects with code `NOT_FOUND` when none exists.
|
|
51
|
+
* - cloud: by id or name, as `connect`.
|
|
52
|
+
*/
|
|
53
|
+
static open(id: string, conn?: ConnectOptions): Promise<Machine>;
|
|
54
|
+
/**
|
|
55
|
+
* Delete a machine by name (cloud: id or name) without a handle and without
|
|
56
|
+
* booting it, whether it runs or not. Deleting one that does not exist
|
|
57
|
+
* succeeds. `drainSecs` lets running commands and transfers finish first
|
|
58
|
+
* (local).
|
|
59
|
+
*/
|
|
60
|
+
static delete(id: string, conn?: ConnectOptions, opts?: Pick<StopOptions, "drainSecs">): Promise<void>;
|
|
45
61
|
/**
|
|
46
62
|
* List every machine the target knows about — including ones created by
|
|
47
63
|
* other processes, or by this one before it last restarted. Locally that is
|
|
@@ -170,8 +186,43 @@ export declare class Machine {
|
|
|
170
186
|
* resumes saved memory (restored or paused) keeps its network backend, so a
|
|
171
187
|
* policy that would need a different one is refused. (local) */
|
|
172
188
|
setNetworkPolicy(policy: NetworkPolicy): Promise<void>;
|
|
173
|
-
/**
|
|
174
|
-
|
|
189
|
+
/** The machine's published ports, whether or not it runs. On the cloud each
|
|
190
|
+
* host port is the node's, and a port not placed yet reports host 0. */
|
|
191
|
+
ports(): Promise<PublishedPort[]>;
|
|
192
|
+
/**
|
|
193
|
+
* Publish a guest port on the host — on a running machine too, without a
|
|
194
|
+
* restart. Without `host`, a free host port is picked. Returns the mapping
|
|
195
|
+
* now in effect. A running machine takes a TCP port now; a UDP port, or a
|
|
196
|
+
* machine not on virtio-net networking, needs it stopped. On the cloud the
|
|
197
|
+
* machine's node picks the host port (`host` is ignored) and ports are TCP;
|
|
198
|
+
* reach the port with `connect` or the machine's URL.
|
|
199
|
+
*/
|
|
200
|
+
publish(port: {
|
|
201
|
+
guest: number;
|
|
202
|
+
host?: number;
|
|
203
|
+
protocol?: "tcp" | "udp";
|
|
204
|
+
}): Promise<PublishedPort>;
|
|
205
|
+
/**
|
|
206
|
+
* Stop publishing a port, matched by `guest` port (every host port
|
|
207
|
+
* forwarding to it) or by `host` port. Connections already open carry on.
|
|
208
|
+
*/
|
|
209
|
+
unpublish(port: {
|
|
210
|
+
guest?: number;
|
|
211
|
+
host?: number;
|
|
212
|
+
protocol?: "tcp" | "udp";
|
|
213
|
+
}): Promise<void>;
|
|
214
|
+
/**
|
|
215
|
+
* Pack this stopped machine into a `.smolmachine` at `path`: its disks and
|
|
216
|
+
* configuration in one artifact that stays on this host. Create machines
|
|
217
|
+
* from it with `Machine.create({ image: path })`. Returns the artifact's
|
|
218
|
+
* path. (local)
|
|
219
|
+
*/
|
|
220
|
+
pack(path: string): Promise<string>;
|
|
221
|
+
/** Stop the machine. Its workload is sent its stop signal and given its
|
|
222
|
+
* stop timeout to save state and exit first. A command or file transfer
|
|
223
|
+
* still running rejects with code `INTERRUPTED` unless `drainSecs` lets it
|
|
224
|
+
* finish; new ones are refused while the stop runs. */
|
|
225
|
+
stop(opts?: StopOptions): Promise<void>;
|
|
175
226
|
/** Save RAM and disk durably, then stop at that execution boundary. */
|
|
176
227
|
pause(): Promise<void>;
|
|
177
228
|
/** Resume saved execution in this machine. */
|
|
@@ -183,7 +234,7 @@ export declare class Machine {
|
|
|
183
234
|
* `{ includeUsage: true }` to also get the fully settled usage + cost back
|
|
184
235
|
* in one call — the control plane takes a final metering sample before
|
|
185
236
|
* teardown, so nothing accrues after the returned report. */
|
|
186
|
-
delete(): Promise<void>;
|
|
237
|
+
delete(opts?: Pick<StopOptions, "drainSecs">): Promise<void>;
|
|
187
238
|
delete(opts: {
|
|
188
239
|
includeUsage: true;
|
|
189
240
|
}): Promise<MachineUsageReport>;
|
|
@@ -200,6 +251,16 @@ export declare class Machine {
|
|
|
200
251
|
/** Revoke this machine's anonymous share link (cloud target). The existing
|
|
201
252
|
* URL immediately stops granting access. */
|
|
202
253
|
unshare(): Promise<void>;
|
|
254
|
+
/** A URL for each published port that reaches it with no login (cloud
|
|
255
|
+
* target), keyed by guest port: for a client that cannot send an API key,
|
|
256
|
+
* such as a sandbox framework calling its tunnel URLs. Anyone holding a link
|
|
257
|
+
* reaches the port, so treat it as a credential. The machine keeps one
|
|
258
|
+
* token, so calling again returns the same links; a port published later
|
|
259
|
+
* appears on the next call. {@link revokePortLinks} revokes them. */
|
|
260
|
+
portLinks(): Promise<Record<number, string>>;
|
|
261
|
+
/** Revoke this machine's port links (cloud target): every link stops working
|
|
262
|
+
* at once, and the next {@link portLinks} issues new ones. */
|
|
263
|
+
revokePortLinks(): Promise<void>;
|
|
203
264
|
/** Publish this machine's cache disk, the version it started from plus
|
|
204
265
|
* everything it wrote, as its cache disk's next version (cloud target).
|
|
205
266
|
* Stop the machine first: a running machine is still writing it. */
|
package/dist/machine.js
CHANGED
|
@@ -24,10 +24,23 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
24
24
|
exports.CacheDisk = exports.Episode = exports.Job = exports.Machine = void 0;
|
|
25
25
|
const node_crypto_1 = require("node:crypto");
|
|
26
26
|
const node_path_1 = require("node:path");
|
|
27
|
+
const node_net_1 = require("node:net");
|
|
27
28
|
const errors_1 = require("./errors");
|
|
28
29
|
const native_1 = require("./native");
|
|
29
30
|
const availability_1 = require("./availability");
|
|
30
31
|
const transport_1 = require("./transport");
|
|
32
|
+
/** A host port free right now on loopback, for a publish that names none. */
|
|
33
|
+
function freeHostPort() {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const server = (0, node_net_1.createServer)();
|
|
36
|
+
server.once("error", reject);
|
|
37
|
+
server.listen(0, "127.0.0.1", () => {
|
|
38
|
+
const address = server.address();
|
|
39
|
+
const port = typeof address === "object" && address ? address.port : 0;
|
|
40
|
+
server.close(() => (port ? resolve(port) : reject(new Error("no free port"))));
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
31
44
|
function makeExecResult(r) {
|
|
32
45
|
const success = r.exitCode === 0;
|
|
33
46
|
return {
|
|
@@ -93,6 +106,26 @@ class Machine {
|
|
|
93
106
|
static async connect(id, conn = {}) {
|
|
94
107
|
return new Machine(await (0, transport_1.connectTransport)(id, conn));
|
|
95
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Open a handle to an EXISTING machine without starting it or waiting for
|
|
111
|
+
* it — to describe, stop, resize or delete it, including one that can no
|
|
112
|
+
* longer boot (its image is gone, the host lost KVM). `state()` reports
|
|
113
|
+
* `"stopped"` for a stopped machine. Call `start()` to boot it.
|
|
114
|
+
* - local: by name; rejects with code `NOT_FOUND` when none exists.
|
|
115
|
+
* - cloud: by id or name, as `connect`.
|
|
116
|
+
*/
|
|
117
|
+
static async open(id, conn = {}) {
|
|
118
|
+
return new Machine(await (0, transport_1.openTransport)(id, conn));
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Delete a machine by name (cloud: id or name) without a handle and without
|
|
122
|
+
* booting it, whether it runs or not. Deleting one that does not exist
|
|
123
|
+
* succeeds. `drainSecs` lets running commands and transfers finish first
|
|
124
|
+
* (local).
|
|
125
|
+
*/
|
|
126
|
+
static async delete(id, conn = {}, opts) {
|
|
127
|
+
return (0, transport_1.deleteMachine)(id, conn, opts);
|
|
128
|
+
}
|
|
96
129
|
/**
|
|
97
130
|
* List every machine the target knows about — including ones created by
|
|
98
131
|
* other processes, or by this one before it last restarted. Locally that is
|
|
@@ -322,9 +355,62 @@ class Machine {
|
|
|
322
355
|
setNetworkPolicy(policy) {
|
|
323
356
|
return this.transport.setNetworkPolicy(policy);
|
|
324
357
|
}
|
|
325
|
-
/**
|
|
326
|
-
|
|
327
|
-
|
|
358
|
+
/** The machine's published ports, whether or not it runs. On the cloud each
|
|
359
|
+
* host port is the node's, and a port not placed yet reports host 0. */
|
|
360
|
+
ports() {
|
|
361
|
+
return this.transport.ports();
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Publish a guest port on the host — on a running machine too, without a
|
|
365
|
+
* restart. Without `host`, a free host port is picked. Returns the mapping
|
|
366
|
+
* now in effect. A running machine takes a TCP port now; a UDP port, or a
|
|
367
|
+
* machine not on virtio-net networking, needs it stopped. On the cloud the
|
|
368
|
+
* machine's node picks the host port (`host` is ignored) and ports are TCP;
|
|
369
|
+
* reach the port with `connect` or the machine's URL.
|
|
370
|
+
*/
|
|
371
|
+
async publish(port) {
|
|
372
|
+
const protocol = port.protocol ?? "tcp";
|
|
373
|
+
const host = port.host ?? (await freeHostPort());
|
|
374
|
+
const current = await this.transport.ports();
|
|
375
|
+
const mapping = { host, guest: port.guest, protocol };
|
|
376
|
+
const next = current.filter((p) => !(p.host === host && p.protocol === protocol));
|
|
377
|
+
await this.transport.setPorts([...next, mapping]);
|
|
378
|
+
// Report what took effect: on the cloud the node chose the host port.
|
|
379
|
+
const now = await this.transport.ports();
|
|
380
|
+
const same = (p) => p.guest === port.guest && p.protocol === protocol;
|
|
381
|
+
return now.find((p) => same(p) && p.host === host) ?? now.find(same) ?? mapping;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Stop publishing a port, matched by `guest` port (every host port
|
|
385
|
+
* forwarding to it) or by `host` port. Connections already open carry on.
|
|
386
|
+
*/
|
|
387
|
+
async unpublish(port) {
|
|
388
|
+
if (port.guest === undefined && port.host === undefined) {
|
|
389
|
+
throw new errors_1.InvalidConfigError("unpublish needs a guest or a host port.");
|
|
390
|
+
}
|
|
391
|
+
const protocol = port.protocol ?? "tcp";
|
|
392
|
+
const current = await this.transport.ports();
|
|
393
|
+
const next = current.filter((p) => p.protocol !== protocol ||
|
|
394
|
+
(port.guest !== undefined && p.guest !== port.guest) ||
|
|
395
|
+
(port.host !== undefined && p.host !== port.host));
|
|
396
|
+
if (next.length !== current.length)
|
|
397
|
+
await this.transport.setPorts(next);
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Pack this stopped machine into a `.smolmachine` at `path`: its disks and
|
|
401
|
+
* configuration in one artifact that stays on this host. Create machines
|
|
402
|
+
* from it with `Machine.create({ image: path })`. Returns the artifact's
|
|
403
|
+
* path. (local)
|
|
404
|
+
*/
|
|
405
|
+
pack(path) {
|
|
406
|
+
return this.transport.pack(path);
|
|
407
|
+
}
|
|
408
|
+
/** Stop the machine. Its workload is sent its stop signal and given its
|
|
409
|
+
* stop timeout to save state and exit first. A command or file transfer
|
|
410
|
+
* still running rejects with code `INTERRUPTED` unless `drainSecs` lets it
|
|
411
|
+
* finish; new ones are refused while the stop runs. */
|
|
412
|
+
stop(opts) {
|
|
413
|
+
return this.transport.stop(opts);
|
|
328
414
|
}
|
|
329
415
|
/** Save RAM and disk durably, then stop at that execution boundary. */
|
|
330
416
|
pause() { return this.transport.pause(); }
|
|
@@ -340,7 +426,7 @@ class Machine {
|
|
|
340
426
|
delete(opts) {
|
|
341
427
|
if (opts?.includeUsage)
|
|
342
428
|
return this.transport.deleteWithUsage();
|
|
343
|
-
return this.transport.delete();
|
|
429
|
+
return this.transport.delete(opts?.drainSecs !== undefined ? { drainSecs: opts.drainSecs } : undefined);
|
|
344
430
|
}
|
|
345
431
|
/** Metered usage + cost for this machine (cloud target). Usage records
|
|
346
432
|
* survive deletion for 30 days. CPU/memory accrue via a rollup that samples
|
|
@@ -361,6 +447,20 @@ class Machine {
|
|
|
361
447
|
unshare() {
|
|
362
448
|
return this.transport.unshare();
|
|
363
449
|
}
|
|
450
|
+
/** A URL for each published port that reaches it with no login (cloud
|
|
451
|
+
* target), keyed by guest port: for a client that cannot send an API key,
|
|
452
|
+
* such as a sandbox framework calling its tunnel URLs. Anyone holding a link
|
|
453
|
+
* reaches the port, so treat it as a credential. The machine keeps one
|
|
454
|
+
* token, so calling again returns the same links; a port published later
|
|
455
|
+
* appears on the next call. {@link revokePortLinks} revokes them. */
|
|
456
|
+
portLinks() {
|
|
457
|
+
return this.transport.portLinks();
|
|
458
|
+
}
|
|
459
|
+
/** Revoke this machine's port links (cloud target): every link stops working
|
|
460
|
+
* at once, and the next {@link portLinks} issues new ones. */
|
|
461
|
+
revokePortLinks() {
|
|
462
|
+
return this.transport.revokePortLinks();
|
|
463
|
+
}
|
|
364
464
|
/** Publish this machine's cache disk, the version it started from plus
|
|
365
465
|
* everything it wrote, as its cache disk's next version (cloud target).
|
|
366
466
|
* Stop the machine first: a running machine is still writing it. */
|
package/dist/native.d.ts
CHANGED
|
@@ -16,6 +16,11 @@ export interface NativeExecOptions {
|
|
|
16
16
|
workdir?: string | undefined;
|
|
17
17
|
timeoutSecs?: number | undefined;
|
|
18
18
|
user?: string | undefined;
|
|
19
|
+
stdin?: string | undefined;
|
|
20
|
+
}
|
|
21
|
+
export interface NativeStopOptions {
|
|
22
|
+
drainSecs?: number | undefined;
|
|
23
|
+
timeoutSecs?: number | undefined;
|
|
19
24
|
}
|
|
20
25
|
export interface NativeHostMount {
|
|
21
26
|
source: string;
|
|
@@ -72,6 +77,17 @@ export interface NativeMachineSummary {
|
|
|
72
77
|
detached: boolean;
|
|
73
78
|
branchable: boolean;
|
|
74
79
|
createdAt: number;
|
|
80
|
+
resources: {
|
|
81
|
+
cpus: number;
|
|
82
|
+
memoryMib: number;
|
|
83
|
+
storageGib: number;
|
|
84
|
+
overlayGib: number;
|
|
85
|
+
};
|
|
86
|
+
ports: {
|
|
87
|
+
host: number;
|
|
88
|
+
guest: number;
|
|
89
|
+
protocol: string;
|
|
90
|
+
}[];
|
|
75
91
|
}
|
|
76
92
|
export interface NativeExecResult {
|
|
77
93
|
exitCode: number;
|
|
@@ -143,15 +159,29 @@ export interface NapiMachine {
|
|
|
143
159
|
cidrs?: string[];
|
|
144
160
|
hosts?: string[];
|
|
145
161
|
}): void;
|
|
146
|
-
stop(): Promise<void>;
|
|
162
|
+
stop(options?: NativeStopOptions): Promise<void>;
|
|
163
|
+
publishedPorts(): {
|
|
164
|
+
host: number;
|
|
165
|
+
guest: number;
|
|
166
|
+
protocol: string;
|
|
167
|
+
}[];
|
|
168
|
+
setPorts(ports: {
|
|
169
|
+
host: number;
|
|
170
|
+
guest: number;
|
|
171
|
+
protocol: string;
|
|
172
|
+
}[]): Promise<void>;
|
|
173
|
+
pack(output: string): Promise<string>;
|
|
147
174
|
pause(): Promise<void>;
|
|
148
175
|
resume(): Promise<void>;
|
|
149
|
-
delete(): Promise<void>;
|
|
176
|
+
delete(options?: NativeStopOptions): Promise<void>;
|
|
150
177
|
}
|
|
151
178
|
export interface NapiMachineCtor {
|
|
152
179
|
new (config: NativeMachineConfig): NapiMachine;
|
|
153
180
|
prepareImage(image: string): Promise<string>;
|
|
154
|
-
connect(name: string, interceptorAddress?: string, interceptorToken?: string): NapiMachine
|
|
181
|
+
connect(name: string, interceptorAddress?: string, interceptorToken?: string): Promise<NapiMachine>;
|
|
182
|
+
open(name: string): NapiMachine;
|
|
183
|
+
deleteByName(name: string, options?: NativeStopOptions): Promise<void>;
|
|
184
|
+
createFromPack(name: string, pack: string): Promise<NapiMachine>;
|
|
155
185
|
list(): Promise<NativeMachineSummary[]>;
|
|
156
186
|
restoreCheckpoint(name: string, artifact: string, keepIdentity?: boolean): NapiMachine;
|
|
157
187
|
exportCheckpoint(source: string, output: string): number;
|
package/dist/transport.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Cloud-only/local-only capability gaps surface as `NotSupportedError`.
|
|
8
8
|
*/
|
|
9
9
|
import { type NativeMachineConfig } from "./native";
|
|
10
|
-
import type { CheckpointOptions, JobInfo, JobOptions, JobOutput, JobOutputOptions, CacheDiskInfo, CreateCacheDiskOptions, PublishedCacheDisk, MachineResources, ResizeOptions, ConnectOptions, NetworkPolicy, RestoreCheckpointOptions, ExecEvent, ExecOptions, EgressInterceptor, AssignOptions, ForkBatchOptions, ForkOptions, ImageInfo, ListOptions, MachineConfig, MachineSummary, MachineUsageReport, ShareLink, PortableCheckpointInfo, PortEndpoint, PortSpec, WaitReadyOptions } from "./types";
|
|
10
|
+
import type { CheckpointOptions, JobInfo, JobOptions, JobOutput, JobOutputOptions, CacheDiskInfo, CreateCacheDiskOptions, PublishedCacheDisk, MachineResources, ResizeOptions, ConnectOptions, NetworkPolicy, RestoreCheckpointOptions, ExecEvent, ExecOptions, EgressInterceptor, AssignOptions, ForkBatchOptions, ForkOptions, ImageInfo, ListOptions, MachineConfig, MachineSummary, MachineUsageReport, ShareLink, PortableCheckpointInfo, PortEndpoint, PortSpec, PublishedPort, StopOptions, WaitReadyOptions } from "./types";
|
|
11
11
|
/** Raw exec result (the ergonomic wrapper is added in machine.ts). */
|
|
12
12
|
export interface RawExec {
|
|
13
13
|
/** The detached process's pid, for a background exec. */
|
|
@@ -58,11 +58,17 @@ export interface Transport {
|
|
|
58
58
|
readonly machineId: string;
|
|
59
59
|
sync(): Promise<void>;
|
|
60
60
|
setNetworkPolicy(policy: NetworkPolicy): Promise<void>;
|
|
61
|
-
|
|
61
|
+
/** Published ports, whether or not the machine runs. */
|
|
62
|
+
ports(): Promise<PublishedPort[]>;
|
|
63
|
+
/** Replace the published ports. The cloud takes TCP guest ports and picks host ports. */
|
|
64
|
+
setPorts(ports: PublishedPort[]): Promise<void>;
|
|
65
|
+
/** Pack the stopped machine into a `.smolmachine`. (local) */
|
|
66
|
+
pack(output: string): Promise<string>;
|
|
67
|
+
stop(opts?: StopOptions): Promise<void>;
|
|
62
68
|
pause(): Promise<void>;
|
|
63
69
|
resume(waitUntilReady?: boolean): Promise<void>;
|
|
64
70
|
start(interceptor?: EgressInterceptor, waitUntilReady?: boolean): Promise<void>;
|
|
65
|
-
delete(): Promise<void>;
|
|
71
|
+
delete(opts?: StopOptions): Promise<void>;
|
|
66
72
|
/** Cloud only: delete and return the settled usage + cost in one call. */
|
|
67
73
|
deleteWithUsage(): Promise<MachineUsageReport>;
|
|
68
74
|
/** Cloud only: metered usage + cost; readable up to 30 days after delete. */
|
|
@@ -72,6 +78,10 @@ export interface Transport {
|
|
|
72
78
|
share(): Promise<ShareLink>;
|
|
73
79
|
/** Cloud only: revoke the machine's anonymous share link. */
|
|
74
80
|
unshare(): Promise<void>;
|
|
81
|
+
/** Cloud only: a login-free URL for each published port, by port. */
|
|
82
|
+
portLinks(): Promise<Record<number, string>>;
|
|
83
|
+
/** Cloud only: revoke the machine's port links. */
|
|
84
|
+
revokePortLinks(): Promise<void>;
|
|
75
85
|
/** Cloud only: publish a stopped machine's cache disk as its next version. */
|
|
76
86
|
publishCacheDisk(): Promise<PublishedCacheDisk>;
|
|
77
87
|
checkpoint(output?: string, options?: CheckpointOptions): Promise<PortableCheckpointInfo>;
|
|
@@ -175,6 +185,18 @@ export declare function makeTransport(config: MachineConfig, conn: ConnectOption
|
|
|
175
185
|
* the account. Labels tell one owner's machines from another's.
|
|
176
186
|
*/
|
|
177
187
|
export declare function listMachines(conn: ConnectOptions, options?: ListOptions): Promise<MachineSummary[]>;
|
|
188
|
+
/**
|
|
189
|
+
* A handle to an existing machine that does not start it or wait for it —
|
|
190
|
+
* to read its state, stop, resize or delete it. Locally it fails with
|
|
191
|
+
* `NOT_FOUND` when no machine has that name; on the cloud it looks the
|
|
192
|
+
* machine up by id or name.
|
|
193
|
+
*/
|
|
194
|
+
export declare function openTransport(id: string, conn: ConnectOptions): Promise<Transport>;
|
|
195
|
+
/**
|
|
196
|
+
* Delete a machine by name or id without a handle, whether or not it runs,
|
|
197
|
+
* and without booting it. Deleting a machine that does not exist succeeds.
|
|
198
|
+
*/
|
|
199
|
+
export declare function deleteMachine(id: string, conn: ConnectOptions, opts?: StopOptions): Promise<void>;
|
|
178
200
|
/**
|
|
179
201
|
* Attach to an EXISTING machine without creating a new one — for driving a
|
|
180
202
|
* machine made elsewhere (another process, the console, the API).
|
package/dist/transport.js
CHANGED
|
@@ -25,6 +25,8 @@ exports.sdkMayRetry = sdkMayRetry;
|
|
|
25
25
|
exports.selectsCloud = selectsCloud;
|
|
26
26
|
exports.makeTransport = makeTransport;
|
|
27
27
|
exports.listMachines = listMachines;
|
|
28
|
+
exports.openTransport = openTransport;
|
|
29
|
+
exports.deleteMachine = deleteMachine;
|
|
28
30
|
exports.connectTransport = connectTransport;
|
|
29
31
|
exports.deleteCheckpointTransport = deleteCheckpointTransport;
|
|
30
32
|
exports.restoreCheckpointTransport = restoreCheckpointTransport;
|
|
@@ -42,6 +44,11 @@ const node_os_1 = require("node:os");
|
|
|
42
44
|
const node_path_1 = require("node:path");
|
|
43
45
|
const native_1 = require("./native");
|
|
44
46
|
const errors_1 = require("./errors");
|
|
47
|
+
/** A cloud machine's published ports, as the SDK reports ports everywhere. A
|
|
48
|
+
* port not placed on a node yet has no host port, reported as 0. */
|
|
49
|
+
function cloudPorts(m) {
|
|
50
|
+
return (m.ports ?? []).map((p) => ({ host: p.hostPort ?? 0, guest: p.port, protocol: "tcp" }));
|
|
51
|
+
}
|
|
45
52
|
/** Byte-exact exec output from a cloud response: prefer the base64 field
|
|
46
53
|
* (binary-safe, untruncated), fall back to the UTF-8 bytes of the lossy text
|
|
47
54
|
* when a control predates it or the value is malformed. */
|
|
@@ -139,10 +146,22 @@ function toNativeExecOptions(opts) {
|
|
|
139
146
|
workdir: opts.workdir,
|
|
140
147
|
timeoutSecs: opts.timeout,
|
|
141
148
|
user: opts.user,
|
|
149
|
+
stdin: stdinText(opts.stdin),
|
|
142
150
|
env: opts.env &&
|
|
143
151
|
Object.entries(opts.env).map(([key, value]) => ({ key, value })),
|
|
144
152
|
};
|
|
145
153
|
}
|
|
154
|
+
/** `ExecOptions.stdin` as the text the agent protocol carries. */
|
|
155
|
+
function stdinText(stdin) {
|
|
156
|
+
if (stdin === undefined || typeof stdin === "string")
|
|
157
|
+
return stdin;
|
|
158
|
+
try {
|
|
159
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(stdin);
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
throw new errors_1.InvalidConfigError("exec stdin must be UTF-8 text.");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
146
165
|
/** Drain an exec stream into an exec result, for an `exec` that must be
|
|
147
166
|
* abortable (only the streaming path can kill a running command). */
|
|
148
167
|
async function collectExec(events, signal) {
|
|
@@ -163,6 +182,13 @@ async function collectExec(events, signal) {
|
|
|
163
182
|
}
|
|
164
183
|
throw new errors_1.SmolError("SMOLVM_ERROR", "command stream ended without an exit status");
|
|
165
184
|
}
|
|
185
|
+
/** The cloud stop and delete take no drain or stop timeout yet; say so
|
|
186
|
+
* rather than ignore them. */
|
|
187
|
+
function refuseCloudStopOptions(opts) {
|
|
188
|
+
if (opts?.drainSecs !== undefined || opts?.timeoutSecs !== undefined) {
|
|
189
|
+
throw new errors_1.NotSupportedError("drainSecs and timeoutSecs are local-only.");
|
|
190
|
+
}
|
|
191
|
+
}
|
|
166
192
|
/** The control plane echoes the user it ran a command as; an older one, which
|
|
167
193
|
* predates the field, echoes nothing because it silently dropped it. */
|
|
168
194
|
function assertCloudUserEcho(response, requested) {
|
|
@@ -510,6 +536,38 @@ class LocalTransport {
|
|
|
510
536
|
throw (0, errors_1.wrapNativeError)(e);
|
|
511
537
|
}
|
|
512
538
|
}
|
|
539
|
+
async ports() {
|
|
540
|
+
try {
|
|
541
|
+
return this.inner.publishedPorts().map((p) => ({
|
|
542
|
+
host: p.host,
|
|
543
|
+
guest: p.guest,
|
|
544
|
+
protocol: p.protocol === "udp" ? "udp" : "tcp",
|
|
545
|
+
}));
|
|
546
|
+
}
|
|
547
|
+
catch (e) {
|
|
548
|
+
throw (0, errors_1.wrapNativeError)(e);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
async setPorts(ports) {
|
|
552
|
+
try {
|
|
553
|
+
await this.inner.setPorts(ports.map((p) => {
|
|
554
|
+
if (p.host === undefined)
|
|
555
|
+
throw new errors_1.InvalidConfigError(`port ${p.guest} has no host port`);
|
|
556
|
+
return { host: p.host, guest: p.guest, protocol: p.protocol };
|
|
557
|
+
}));
|
|
558
|
+
}
|
|
559
|
+
catch (e) {
|
|
560
|
+
throw (0, errors_1.wrapNativeError)(e);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
async pack(output) {
|
|
564
|
+
try {
|
|
565
|
+
return await this.inner.pack((0, node_path_1.resolve)(output));
|
|
566
|
+
}
|
|
567
|
+
catch (e) {
|
|
568
|
+
throw (0, errors_1.wrapNativeError)(e);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
513
571
|
async setNetworkPolicy(policy) {
|
|
514
572
|
try {
|
|
515
573
|
this.inner.setEgressPolicy(egressPolicy(policy));
|
|
@@ -518,10 +576,10 @@ class LocalTransport {
|
|
|
518
576
|
throw (0, errors_1.wrapNativeError)(e);
|
|
519
577
|
}
|
|
520
578
|
}
|
|
521
|
-
async stop() {
|
|
579
|
+
async stop(opts) {
|
|
522
580
|
liveLocal.delete(this);
|
|
523
581
|
try {
|
|
524
|
-
await this.inner.stop();
|
|
582
|
+
await this.inner.stop(opts && { drainSecs: opts.drainSecs, timeoutSecs: opts.timeoutSecs });
|
|
525
583
|
}
|
|
526
584
|
catch (e) {
|
|
527
585
|
throw (0, errors_1.wrapNativeError)(e);
|
|
@@ -566,10 +624,10 @@ class LocalTransport {
|
|
|
566
624
|
if (waitUntilReady)
|
|
567
625
|
await this.waitUntilReady();
|
|
568
626
|
}
|
|
569
|
-
async delete() {
|
|
627
|
+
async delete(opts) {
|
|
570
628
|
liveLocal.delete(this);
|
|
571
629
|
try {
|
|
572
|
-
await this.inner.delete();
|
|
630
|
+
await this.inner.delete(opts && { drainSecs: opts.drainSecs });
|
|
573
631
|
}
|
|
574
632
|
catch (e) {
|
|
575
633
|
throw (0, errors_1.wrapNativeError)(e);
|
|
@@ -587,6 +645,12 @@ class LocalTransport {
|
|
|
587
645
|
async unshare() {
|
|
588
646
|
throw new errors_1.NotSupportedError("unshare() is cloud-only; a local machine has no share link to revoke.");
|
|
589
647
|
}
|
|
648
|
+
async portLinks() {
|
|
649
|
+
throw new errors_1.NotSupportedError("portLinks() is cloud-only; a local machine's ports are on this host (see endpoint()).");
|
|
650
|
+
}
|
|
651
|
+
async revokePortLinks() {
|
|
652
|
+
throw new errors_1.NotSupportedError("revokePortLinks() is cloud-only; a local machine has no port links.");
|
|
653
|
+
}
|
|
590
654
|
async publishCacheDisk() {
|
|
591
655
|
throw new errors_1.NotSupportedError("publishCacheDisk() is cloud-only; a local machine has no cache disk.");
|
|
592
656
|
}
|
|
@@ -1102,6 +1166,7 @@ class CloudTransport {
|
|
|
1102
1166
|
timeoutSeconds: opts?.timeout ?? null,
|
|
1103
1167
|
...(opts?.user !== undefined ? { user: opts.user } : {}),
|
|
1104
1168
|
...(opts?.background ? { background: true } : {}),
|
|
1169
|
+
...(opts?.stdin !== undefined ? { stdin: stdinText(opts.stdin) ?? null } : {}),
|
|
1105
1170
|
};
|
|
1106
1171
|
// The command may legitimately run far longer than the default cloud
|
|
1107
1172
|
// timeout, so size the request abort timeout off the request's own timeout
|
|
@@ -1327,10 +1392,29 @@ class CloudTransport {
|
|
|
1327
1392
|
async sync() {
|
|
1328
1393
|
throw new errors_1.NotSupportedError("sync() is local-only because cloud machines cannot carry host-directory mounts.");
|
|
1329
1394
|
}
|
|
1395
|
+
async ports() {
|
|
1396
|
+
const m = await cloudFetch(this.conn, "GET", `/v1/machines/${this.id}`);
|
|
1397
|
+
return cloudPorts(m);
|
|
1398
|
+
}
|
|
1399
|
+
async setPorts(ports) {
|
|
1400
|
+
// The cloud publishes TCP guest ports and picks each one's host port on the
|
|
1401
|
+
// machine's node; clients reach them through connect, tunnel and the proxy.
|
|
1402
|
+
if (ports.some((p) => p.protocol === "udp")) {
|
|
1403
|
+
throw new errors_1.NotSupportedError("UDP ports are local-only; the cloud publishes TCP ports.");
|
|
1404
|
+
}
|
|
1405
|
+
const guests = [...new Set(ports.map((p) => p.guest))];
|
|
1406
|
+
await cloudFetch(this.conn, "PUT", `/v1/machines/${this.id}/ports`, {
|
|
1407
|
+
json: { ports: guests.map((port) => ({ port })) },
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
async pack() {
|
|
1411
|
+
throw new errors_1.NotSupportedError("pack() is local-only; use checkpoint() on the cloud.");
|
|
1412
|
+
}
|
|
1330
1413
|
async setNetworkPolicy() {
|
|
1331
1414
|
throw new errors_1.NotSupportedError("setNetworkPolicy() is local-only.");
|
|
1332
1415
|
}
|
|
1333
|
-
async stop() {
|
|
1416
|
+
async stop(opts) {
|
|
1417
|
+
refuseCloudStopOptions(opts);
|
|
1334
1418
|
await cloudFetch(this.conn, "POST", `/v1/machines/${this.id}/stop`, { idempotent: true });
|
|
1335
1419
|
}
|
|
1336
1420
|
async pause() {
|
|
@@ -1408,7 +1492,8 @@ class CloudTransport {
|
|
|
1408
1492
|
// Cancelling twice is harmless, so a 503 may be resent.
|
|
1409
1493
|
return cloudFetch(this.conn, "POST", `/v1/machines/${this.id}/jobs/${encodeURIComponent(jobId)}/cancel`, { idempotent: true });
|
|
1410
1494
|
}
|
|
1411
|
-
async delete() {
|
|
1495
|
+
async delete(opts) {
|
|
1496
|
+
refuseCloudStopOptions(opts);
|
|
1412
1497
|
await cloudFetch(this.conn, "DELETE", `/v1/machines/${this.id}`);
|
|
1413
1498
|
}
|
|
1414
1499
|
async deleteWithUsage() {
|
|
@@ -1429,6 +1514,13 @@ class CloudTransport {
|
|
|
1429
1514
|
async unshare() {
|
|
1430
1515
|
await cloudFetch(this.conn, "DELETE", `/v1/machines/${this.id}/share`);
|
|
1431
1516
|
}
|
|
1517
|
+
async portLinks() {
|
|
1518
|
+
const out = await cloudFetch(this.conn, "POST", `/v1/machines/${this.id}/port-links`);
|
|
1519
|
+
return Object.fromEntries(Object.entries(out.urls ?? {}).map(([port, url]) => [Number(port), url]));
|
|
1520
|
+
}
|
|
1521
|
+
async revokePortLinks() {
|
|
1522
|
+
await cloudFetch(this.conn, "DELETE", `/v1/machines/${this.id}/port-links`);
|
|
1523
|
+
}
|
|
1432
1524
|
async publishCacheDisk() {
|
|
1433
1525
|
// Flattening and uploading a large cache can take minutes.
|
|
1434
1526
|
return cloudFetch(this.conn, "POST", `/v1/machines/${this.id}/cache-disk/publish`, { timeoutMs: 30 * 60 * 1000 });
|
|
@@ -1800,6 +1892,21 @@ async function makeTransport(config, conn) {
|
|
|
1800
1892
|
throw new errors_1.InvalidConfigError("egressInterceptor cannot be combined with branchable machines.");
|
|
1801
1893
|
}
|
|
1802
1894
|
let transport;
|
|
1895
|
+
// A `.smolmachine` image is a packed machine: create from it (as
|
|
1896
|
+
// `smolvm machine create --from`), then start it.
|
|
1897
|
+
if (config.image?.endsWith(".smolmachine")) {
|
|
1898
|
+
const owned = !config.detach && (conn.handleSignals ?? true);
|
|
1899
|
+
try {
|
|
1900
|
+
const inner = await (0, native_1.getNapiMachine)().createFromPack(name, (0, node_path_1.resolve)(config.image));
|
|
1901
|
+
transport = new LocalTransport(inner, owned, interceptor, owned);
|
|
1902
|
+
await transport.start(interceptor, config.waitForPorts !== false);
|
|
1903
|
+
return transport;
|
|
1904
|
+
}
|
|
1905
|
+
catch (e) {
|
|
1906
|
+
await transport?.delete().catch(() => { });
|
|
1907
|
+
throw (0, errors_1.wrapNativeError)(e);
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1803
1910
|
try {
|
|
1804
1911
|
// Validate the config before loading the engine, so a config the local
|
|
1805
1912
|
// target cannot honour fails the same way whether or not it is installed.
|
|
@@ -1862,6 +1969,16 @@ async function listMachines(conn, options = {}) {
|
|
|
1862
1969
|
detached: r.detached,
|
|
1863
1970
|
branchable: r.branchable,
|
|
1864
1971
|
createdAt: new Date(r.createdAt * 1000).toISOString(),
|
|
1972
|
+
resources: {
|
|
1973
|
+
cpus: r.resources.cpus,
|
|
1974
|
+
memoryMib: r.resources.memoryMib,
|
|
1975
|
+
storageGib: r.resources.storageGib,
|
|
1976
|
+
},
|
|
1977
|
+
ports: r.ports.map((p) => ({
|
|
1978
|
+
host: p.host,
|
|
1979
|
+
guest: p.guest,
|
|
1980
|
+
protocol: p.protocol === "udp" ? "udp" : "tcp",
|
|
1981
|
+
})),
|
|
1865
1982
|
}));
|
|
1866
1983
|
}
|
|
1867
1984
|
else {
|
|
@@ -1903,6 +2020,21 @@ async function listMachines(conn, options = {}) {
|
|
|
1903
2020
|
detached: true,
|
|
1904
2021
|
branchable: m.branchable ?? m.forkable ?? false,
|
|
1905
2022
|
createdAt: m.createdAt,
|
|
2023
|
+
...(m.resources?.cpus != null &&
|
|
2024
|
+
m.resources.memoryMb != null && {
|
|
2025
|
+
resources: {
|
|
2026
|
+
cpus: m.resources.cpus,
|
|
2027
|
+
memoryMib: m.resources.memoryMb,
|
|
2028
|
+
storageGib: m.resources.diskGb ?? 20,
|
|
2029
|
+
},
|
|
2030
|
+
}),
|
|
2031
|
+
...(m.ports && {
|
|
2032
|
+
ports: m.ports.map((p) => ({
|
|
2033
|
+
...(p.hostPort != null && { host: p.hostPort }),
|
|
2034
|
+
guest: p.port,
|
|
2035
|
+
protocol: p.protocol === "udp" ? "udp" : "tcp",
|
|
2036
|
+
})),
|
|
2037
|
+
}),
|
|
1906
2038
|
}));
|
|
1907
2039
|
}
|
|
1908
2040
|
const want = Object.entries(options.labels ?? {});
|
|
@@ -1910,6 +2042,44 @@ async function listMachines(conn, options = {}) {
|
|
|
1910
2042
|
return all;
|
|
1911
2043
|
return all.filter((m) => want.every(([k, v]) => m.labels[k] === v));
|
|
1912
2044
|
}
|
|
2045
|
+
/**
|
|
2046
|
+
* A handle to an existing machine that does not start it or wait for it —
|
|
2047
|
+
* to read its state, stop, resize or delete it. Locally it fails with
|
|
2048
|
+
* `NOT_FOUND` when no machine has that name; on the cloud it looks the
|
|
2049
|
+
* machine up by id or name.
|
|
2050
|
+
*/
|
|
2051
|
+
async function openTransport(id, conn) {
|
|
2052
|
+
if (!selectsCloud(conn)) {
|
|
2053
|
+
try {
|
|
2054
|
+
return new LocalTransport((0, native_1.getNapiMachine)().open(id), false, undefined, conn.handleSignals ?? true);
|
|
2055
|
+
}
|
|
2056
|
+
catch (e) {
|
|
2057
|
+
throw (0, errors_1.wrapNativeError)(e);
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
// The cloud connect looks the machine up without starting it, and waits
|
|
2061
|
+
// for nothing unless asked to.
|
|
2062
|
+
const { waitForPorts: _ignored, ...rest } = conn;
|
|
2063
|
+
return connectTransport(id, rest);
|
|
2064
|
+
}
|
|
2065
|
+
/**
|
|
2066
|
+
* Delete a machine by name or id without a handle, whether or not it runs,
|
|
2067
|
+
* and without booting it. Deleting a machine that does not exist succeeds.
|
|
2068
|
+
*/
|
|
2069
|
+
async function deleteMachine(id, conn, opts) {
|
|
2070
|
+
if (!selectsCloud(conn)) {
|
|
2071
|
+
try {
|
|
2072
|
+
await (0, native_1.getNapiMachine)().deleteByName(id, opts && { drainSecs: opts.drainSecs });
|
|
2073
|
+
}
|
|
2074
|
+
catch (e) {
|
|
2075
|
+
throw (0, errors_1.wrapNativeError)(e);
|
|
2076
|
+
}
|
|
2077
|
+
return;
|
|
2078
|
+
}
|
|
2079
|
+
refuseCloudStopOptions(opts);
|
|
2080
|
+
const transport = await openTransport(id, conn);
|
|
2081
|
+
await transport.delete();
|
|
2082
|
+
}
|
|
1913
2083
|
/**
|
|
1914
2084
|
* Attach to an EXISTING machine without creating a new one — for driving a
|
|
1915
2085
|
* machine made elsewhere (another process, the console, the API).
|
|
@@ -1930,9 +2100,11 @@ async function connectTransport(id, conn) {
|
|
|
1930
2100
|
try {
|
|
1931
2101
|
// Connecting borrows an existing machine. A signal in this client must
|
|
1932
2102
|
// not stop a durable checkpoint owned by another process/controller.
|
|
2103
|
+
// The native connect boots off the JavaScript thread, so a slow boot
|
|
2104
|
+
// does not hold the event loop.
|
|
1933
2105
|
const transport = new LocalTransport(conn.egressInterceptor
|
|
1934
|
-
? (0, native_1.getNapiMachine)().connect(id, conn.egressInterceptor.address, conn.egressInterceptor.token)
|
|
1935
|
-
: (0, native_1.getNapiMachine)().connect(id), false, conn.egressInterceptor, conn.handleSignals ?? true);
|
|
2106
|
+
? await (0, native_1.getNapiMachine)().connect(id, conn.egressInterceptor.address, conn.egressInterceptor.token)
|
|
2107
|
+
: await (0, native_1.getNapiMachine)().connect(id), false, conn.egressInterceptor, conn.handleSignals ?? true);
|
|
1936
2108
|
// Frozen and durably paused machines are intentionally not agent-ready.
|
|
1937
2109
|
// A paused machine must be resumed explicitly before it can serve work.
|
|
1938
2110
|
const state = await transport.state();
|
package/dist/types.d.ts
CHANGED
|
@@ -297,6 +297,37 @@ export interface MachineSummary {
|
|
|
297
297
|
branchable: boolean;
|
|
298
298
|
/** When the machine was created (RFC3339). */
|
|
299
299
|
createdAt: string;
|
|
300
|
+
/** CPU, memory and disk sizes, whether or not the machine runs. Missing
|
|
301
|
+
* only from a control plane that does not report them. */
|
|
302
|
+
resources?: MachineSizes;
|
|
303
|
+
/** Published ports, whether or not the machine runs. `host` is the host
|
|
304
|
+
* port forwarding to `guest`; on the cloud it is the node port, set once
|
|
305
|
+
* the machine has been placed. */
|
|
306
|
+
ports?: PublishedPort[];
|
|
307
|
+
}
|
|
308
|
+
/** CPU, memory and disk sizes of a machine, as `Machine.list()` reports them. */
|
|
309
|
+
export interface MachineSizes {
|
|
310
|
+
cpus: number;
|
|
311
|
+
memoryMib: number;
|
|
312
|
+
storageGib: number;
|
|
313
|
+
}
|
|
314
|
+
/** A published port, as `Machine.list()` reports it. */
|
|
315
|
+
export interface PublishedPort {
|
|
316
|
+
host?: number;
|
|
317
|
+
guest: number;
|
|
318
|
+
protocol: "tcp" | "udp";
|
|
319
|
+
}
|
|
320
|
+
/** How `stop()` and `delete()` treat work still running on the machine. */
|
|
321
|
+
export interface StopOptions {
|
|
322
|
+
/** Wait up to this many seconds for running commands and file transfers to
|
|
323
|
+
* finish before stopping. Whatever still runs then rejects with
|
|
324
|
+
* `SmolError` code `INTERRUPTED`. Default: stop at once. (local) */
|
|
325
|
+
drainSecs?: number;
|
|
326
|
+
/** Seconds the workload (the image ENTRYPOINT/CMD) gets to exit after its
|
|
327
|
+
* stop signal before it is killed, instead of the machine's own stop
|
|
328
|
+
* timeout; `0` kills it at once. `stop()` only; `delete()` never waits.
|
|
329
|
+
* (local) */
|
|
330
|
+
timeoutSecs?: number;
|
|
300
331
|
}
|
|
301
332
|
/** Filters for `Machine.list()`. */
|
|
302
333
|
export interface ListOptions {
|
|
@@ -346,6 +377,10 @@ export interface ExecOptions {
|
|
|
346
377
|
* `NotSupportedError` — before running anything — if it predates them. It is
|
|
347
378
|
* never silently ignored. */
|
|
348
379
|
user?: string;
|
|
380
|
+
/** Text written to the command's stdin, which is then closed. Passed over
|
|
381
|
+
* the agent connection and never written to the machine's disk, so it can
|
|
382
|
+
* carry a script or a secret. A `Uint8Array` must be UTF-8. */
|
|
383
|
+
stdin?: string | Uint8Array;
|
|
349
384
|
/** Abort the command. Locally the command is killed in the machine and the
|
|
350
385
|
* call (or the `execStream` iteration) rejects with `signal.reason`. On the
|
|
351
386
|
* cloud target the request is cancelled and the call rejects the same way;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "smolmachines",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.29.0",
|
|
4
4
|
"description": "Embed isolated microVM sandboxes directly in your code — no server to run.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "smol-machines",
|
|
@@ -54,7 +54,8 @@
|
|
|
54
54
|
"test:paused-connect": "tsx test/local-paused-connect.ts",
|
|
55
55
|
"gen:openapi": "bash scripts/gen-openapi.sh",
|
|
56
56
|
"test:cloud": "tsx test/cloud-mock.ts && tsx test/cloud-deferred-ports.ts",
|
|
57
|
-
"test:unit": "tsx test/unit.ts"
|
|
57
|
+
"test:unit": "tsx test/unit.ts",
|
|
58
|
+
"test:host-daemon": "tsx test/host-daemon-e2e.ts"
|
|
58
59
|
},
|
|
59
60
|
"devDependencies": {
|
|
60
61
|
"@napi-rs/cli": "^2.18.4",
|
|
@@ -64,8 +65,8 @@
|
|
|
64
65
|
"typescript": "^5.6.0"
|
|
65
66
|
},
|
|
66
67
|
"optionalDependencies": {
|
|
67
|
-
"smolmachines-darwin-arm64": "1.
|
|
68
|
-
"smolmachines-linux-x64-gnu": "1.
|
|
69
|
-
"smolmachines-linux-arm64-gnu": "1.
|
|
68
|
+
"smolmachines-darwin-arm64": "1.29.0",
|
|
69
|
+
"smolmachines-linux-x64-gnu": "1.29.0",
|
|
70
|
+
"smolmachines-linux-arm64-gnu": "1.29.0"
|
|
70
71
|
}
|
|
71
72
|
}
|