@veris-ai/daytona 0.1.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/dist/index.js ADDED
@@ -0,0 +1,855 @@
1
+ // src/index.ts
2
+ export * from "@daytona/sdk";
3
+
4
+ // src/daytona.ts
5
+ import { Daytona as BaseDaytona } from "@daytona/sdk";
6
+
7
+ // src/errors.ts
8
+ var VerisError = class extends Error {
9
+ phase;
10
+ /** The Veris twin's sandbox id, when one exists yet. */
11
+ verisSandboxId;
12
+ /** Verbatim control-plane response body, when the failure came from an API call. */
13
+ responseBody;
14
+ constructor(message, opts = {}) {
15
+ super(message, opts.cause !== void 0 ? { cause: opts.cause } : void 0);
16
+ this.name = new.target.name;
17
+ this.phase = opts.phase;
18
+ this.verisSandboxId = opts.verisSandboxId;
19
+ this.responseBody = opts.responseBody;
20
+ }
21
+ };
22
+ var MissingCredentialsError = class extends VerisError {
23
+ };
24
+ var VerisGatewayUnreachableError = class extends VerisError {
25
+ };
26
+ var VerisGatewayNotOfferedError = class extends VerisError {
27
+ /** Server-announced minimum SDK version, when the refusal carried one. */
28
+ minSdk;
29
+ constructor(message, opts = {}) {
30
+ super(message, opts);
31
+ this.minSdk = opts.minSdk;
32
+ }
33
+ };
34
+ var ReceiptIntegrityError = class extends VerisError {
35
+ };
36
+ var VerisUntouchedError = class extends VerisError {
37
+ service;
38
+ constructor(message, service, opts = {}) {
39
+ super(message, opts);
40
+ this.service = service;
41
+ }
42
+ };
43
+ var TwinExpiredError = class extends VerisError {
44
+ };
45
+ var SnapshotUnsupportedError = class extends VerisError {
46
+ };
47
+ var UnsupportedOperationError = class extends VerisError {
48
+ };
49
+
50
+ // src/control-plane.ts
51
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
52
+ var ControlPlane = class {
53
+ apiBase;
54
+ headers;
55
+ constructor(opts) {
56
+ this.apiBase = opts.apiBase.replace(/\/$/, "");
57
+ this.headers = {
58
+ "X-API-Key": opts.apiKey,
59
+ "X-Veris-SDK": opts.sdkVersion,
60
+ "Content-Type": "application/json"
61
+ };
62
+ }
63
+ async request(method, path, body) {
64
+ let res;
65
+ try {
66
+ res = await fetch(`${this.apiBase}${path}`, {
67
+ method,
68
+ headers: this.headers,
69
+ body: body === void 0 ? void 0 : JSON.stringify(body)
70
+ });
71
+ } catch (cause) {
72
+ throw new VerisError(`Veris control plane unreachable (${method} ${path})`, { cause });
73
+ }
74
+ return res;
75
+ }
76
+ async json(res, context, phase) {
77
+ const text = await res.text();
78
+ let parsed;
79
+ try {
80
+ parsed = text ? JSON.parse(text) : void 0;
81
+ } catch {
82
+ parsed = text;
83
+ }
84
+ if (!res.ok) {
85
+ throw new VerisError(`${context}: ${res.status}`, { phase, responseBody: parsed });
86
+ }
87
+ if (parsed === void 0) {
88
+ throw new VerisError(`${context}: empty response body`, { phase, responseBody: text });
89
+ }
90
+ return parsed;
91
+ }
92
+ async createTwin(environmentId, opts = {}) {
93
+ const res = await this.request("POST", `/v1/environments/${environmentId}/sandboxes`, {
94
+ ttl_minutes: opts.ttlMinutes,
95
+ metadata: opts.metadata
96
+ });
97
+ return this.json(res, `create sandbox in environment ${environmentId}`, "twin-provision");
98
+ }
99
+ async getTwin(sandboxId) {
100
+ const res = await this.request("GET", `/v1/sandboxes/${sandboxId}`);
101
+ if (res.status === 404) return null;
102
+ return this.json(res, `get sandbox ${sandboxId}`);
103
+ }
104
+ /** Poll until the twin reports ready. "failed" is terminal per the API docs. */
105
+ async waitReady(sandboxId, timeoutMs) {
106
+ const deadline = Date.now() + timeoutMs;
107
+ for (; ; ) {
108
+ const twin = await this.getTwin(sandboxId);
109
+ if (!twin) throw new TwinExpiredError(`Veris sandbox ${sandboxId} disappeared while provisioning`, { verisSandboxId: sandboxId });
110
+ if (twin.status === "ready") return twin;
111
+ if (twin.status === "failed") {
112
+ throw new VerisError(
113
+ `Veris sandbox ${sandboxId} failed to provision: ${twin.failure_reason ?? "no failure_reason"}`,
114
+ { phase: "twin-provision", verisSandboxId: sandboxId }
115
+ );
116
+ }
117
+ if (Date.now() > deadline) {
118
+ throw new VerisError(
119
+ `Veris sandbox ${sandboxId} not ready after ${timeoutMs}ms (status: ${twin.status})`,
120
+ { phase: "twin-provision", verisSandboxId: sandboxId }
121
+ );
122
+ }
123
+ await sleep(1500);
124
+ }
125
+ }
126
+ async services(sandboxId) {
127
+ const res = await this.request("GET", `/v1/sandboxes/${sandboxId}/services`);
128
+ if (res.status === 404) {
129
+ throw new TwinExpiredError(`Veris sandbox ${sandboxId} not found \u2014 expired or deleted`, { verisSandboxId: sandboxId });
130
+ }
131
+ return this.json(res, `services of sandbox ${sandboxId}`, "receipt");
132
+ }
133
+ async deleteTwin(environmentId, sandboxId) {
134
+ const res = await this.request("DELETE", `/v1/environments/${environmentId}/sandboxes/${sandboxId}`);
135
+ if (res.status === 404) return false;
136
+ if (!res.ok) await this.json(res, `delete sandbox ${sandboxId}`);
137
+ return true;
138
+ }
139
+ /**
140
+ * Mint (or re-mint) the gateway egress credential for a twin. Returns null
141
+ * when the control plane does not offer gateway mode at all (404 — route
142
+ * absent), so `mode: 'auto'` can fall back; throws VerisGatewayNotOfferedError
143
+ * on an explicit version refusal (409 sdk_too_old).
144
+ */
145
+ async mintEgressCredential(environmentId, sandboxId) {
146
+ const res = await this.request("POST", `/v1/environments/${environmentId}/sandboxes/${sandboxId}/egress-credential`);
147
+ if (res.status === 404) return null;
148
+ if (res.status === 409) {
149
+ const body = await res.json().catch(() => ({}));
150
+ throw new VerisGatewayNotOfferedError(
151
+ `this SDK version is below the control plane's minimum for gateway mode${body.min_sdk ? ` (min_sdk ${body.min_sdk})` : ""} \u2014 upgrade @veris-ai/daytona`,
152
+ { phase: "credential-mint", verisSandboxId: sandboxId, minSdk: body.min_sdk, responseBody: body }
153
+ );
154
+ }
155
+ return this.json(res, `mint egress credential for ${sandboxId}`, "credential-mint");
156
+ }
157
+ /** PATCH the twin resource. Omitted fields are untouched by the server. */
158
+ async updateSandbox(environmentId, sandboxId, patch) {
159
+ const res = await this.request("PATCH", `/v1/environments/${environmentId}/sandboxes/${sandboxId}`, patch);
160
+ if (res.status === 404) {
161
+ throw new TwinExpiredError(`Veris sandbox ${sandboxId} not found`, { verisSandboxId: sandboxId });
162
+ }
163
+ if (!res.ok) await this.json(res, `update sandbox ${sandboxId}`);
164
+ }
165
+ /** Extend a twin's TTL so it stays in lockstep with an extended Daytona sandbox. */
166
+ async extendTtl(environmentId, sandboxId, ttlMinutes) {
167
+ const res = await this.request("PATCH", `/v1/environments/${environmentId}/sandboxes/${sandboxId}`, { ttl_minutes: ttlMinutes });
168
+ if (res.status === 404) {
169
+ throw new TwinExpiredError(`Veris sandbox ${sandboxId} not found \u2014 cannot extend TTL`, { verisSandboxId: sandboxId });
170
+ }
171
+ if (!res.ok && res.status !== 405) await this.json(res, `extend TTL of ${sandboxId}`);
172
+ }
173
+ /** Create-time preflight: is the gateway infrastructure up, per the control plane? */
174
+ async gatewayHealth() {
175
+ const res = await this.request("GET", "/v1/gateway/health");
176
+ if (res.status === 404) return;
177
+ if (!res.ok) {
178
+ throw new VerisGatewayUnreachableError(
179
+ `Veris gateway reported unhealthy (${res.status})`,
180
+ { phase: "gateway-preflight" }
181
+ );
182
+ }
183
+ }
184
+ };
185
+
186
+ // src/receipt.ts
187
+ function parseRequestsBody(body) {
188
+ const rows = Array.isArray(body?.requests) ? body.requests : [];
189
+ const entries = rows.map((r) => {
190
+ const row = r;
191
+ return {
192
+ method: String(row.method ?? ""),
193
+ path: String(row.path ?? ""),
194
+ status: typeof row.status === "number" ? row.status : null
195
+ };
196
+ });
197
+ return { count: entries.length, entries };
198
+ }
199
+ async function fetchReceiptEntry(svc) {
200
+ const url = `${svc.control_url}/veris/requests`;
201
+ const res = await fetch(url);
202
+ const text = await res.text();
203
+ if (!res.ok) {
204
+ throw new VerisError(`could not read receipt for service '${svc.name}' (${res.status})`, {
205
+ phase: "receipt",
206
+ responseBody: text.slice(0, 500)
207
+ });
208
+ }
209
+ let raw;
210
+ try {
211
+ raw = JSON.parse(text);
212
+ } catch {
213
+ throw new VerisError(`service '${svc.name}' returned a non-JSON receipt body`, {
214
+ phase: "receipt",
215
+ responseBody: text.slice(0, 500)
216
+ });
217
+ }
218
+ const { count, entries } = parseRequestsBody(raw);
219
+ return { requests: count, controlUrl: svc.control_url, entries, raw };
220
+ }
221
+
222
+ // src/network.ts
223
+ var isHttpUrl = (u) => /^https?:/.test(u);
224
+ function vendorHosts(services) {
225
+ const hosts = /* @__PURE__ */ new Set();
226
+ for (const svc of services) {
227
+ for (const r of svc.routes ?? []) hosts.add(r.host);
228
+ }
229
+ return [...hosts].sort();
230
+ }
231
+ function twinHosts(services) {
232
+ const hosts = /* @__PURE__ */ new Set();
233
+ for (const svc of services) {
234
+ for (const u of [svc.control_url, svc.url]) {
235
+ if (!u || !isHttpUrl(u)) continue;
236
+ try {
237
+ hosts.add(new URL(u).hostname);
238
+ } catch {
239
+ }
240
+ }
241
+ }
242
+ return [...hosts].sort();
243
+ }
244
+ function dataPlaneHosts(services) {
245
+ const hosts = /* @__PURE__ */ new Set();
246
+ for (const svc of services) {
247
+ if (!svc.url || isHttpUrl(svc.url)) continue;
248
+ for (const h of hostsFromDsn(svc.url)) hosts.add(h);
249
+ }
250
+ return [...hosts].sort();
251
+ }
252
+ function hostsFromDsn(dsn) {
253
+ const out = [];
254
+ try {
255
+ const u = new URL(dsn);
256
+ if (u.hostname) out.push(u.hostname.replace(/^\[|\]$/g, ""));
257
+ } catch {
258
+ }
259
+ const authority = dsn.replace(/^[^:]+:\/\//, "").split(/[/?]/)[0] ?? "";
260
+ const afterAt = authority.includes("@") ? authority.slice(authority.lastIndexOf("@") + 1) : authority;
261
+ for (const part of afterAt.split(",")) {
262
+ const m = part.match(/^\[?([A-Za-z0-9_.:-]+?)\]?(?::\d+)?$/);
263
+ if (m?.[1] && !/^\d+$/.test(m[1])) out.push(m[1].replace(/^\[|\]$/g, ""));
264
+ }
265
+ return out;
266
+ }
267
+ function dataPlaneEnv(services) {
268
+ const envs = {};
269
+ for (const svc of services) {
270
+ if (!svc.env_hint || !svc.url || isHttpUrl(svc.url)) continue;
271
+ if (!isSafeEnvName(svc.env_hint)) continue;
272
+ envs[svc.env_hint] = svc.url;
273
+ }
274
+ return envs;
275
+ }
276
+ var PROCESS_CONTROLLING = /* @__PURE__ */ new Set([
277
+ "PATH",
278
+ "LD_PRELOAD",
279
+ "LD_LIBRARY_PATH",
280
+ "NODE_OPTIONS",
281
+ "BASH_ENV",
282
+ "ENV",
283
+ "PYTHONPATH",
284
+ "PYTHONSTARTUP",
285
+ "SHELL",
286
+ "IFS",
287
+ "HOME",
288
+ "PROMPT_COMMAND"
289
+ ]);
290
+ function isSafeEnvName(name) {
291
+ return /^[A-Z][A-Z0-9_]{0,63}$/.test(name) && !PROCESS_CONTROLLING.has(name);
292
+ }
293
+ var DEFAULT_REGISTRY_HOSTS = [
294
+ // JS
295
+ "registry.npmjs.org",
296
+ "registry.yarnpkg.com",
297
+ // Python
298
+ "pypi.org",
299
+ "files.pythonhosted.org",
300
+ // Go
301
+ "proxy.golang.org",
302
+ "sum.golang.org",
303
+ // Rust
304
+ "crates.io",
305
+ "static.crates.io",
306
+ "index.crates.io",
307
+ // Debian/Ubuntu
308
+ "deb.debian.org",
309
+ "security.debian.org",
310
+ "archive.ubuntu.com",
311
+ "security.ubuntu.com",
312
+ // Source + container hosts the above routinely redirect to
313
+ "github.com",
314
+ "codeload.github.com",
315
+ "objects.githubusercontent.com",
316
+ "raw.githubusercontent.com",
317
+ "ghcr.io"
318
+ ];
319
+ function buildNetwork(args) {
320
+ const { services, mode, gatewayHosts, allowOut = [], allowRegistries = true } = args;
321
+ if (mode === "open") return {};
322
+ const domains = [
323
+ ...vendorHosts(services),
324
+ ...gatewayHosts,
325
+ ...dataPlaneHosts(services),
326
+ ...allowRegistries ? DEFAULT_REGISTRY_HOSTS : [],
327
+ ...allowOut
328
+ ].filter((h) => Boolean(h));
329
+ return {
330
+ // NOT networkBlockAll: that blocks everything including the gateway, and the
331
+ // allowlist is what Daytona documents as "unbypassable network-layer
332
+ // enforcement". Blocking all and then allowing is not a shape the API
333
+ // offers; a non-empty domainAllowList IS the deny-by-default.
334
+ domainAllowList: [...new Set(domains)].sort().join(",")
335
+ };
336
+ }
337
+
338
+ // src/trust.ts
339
+ var CA_CERT_PATH = "/usr/local/share/ca-certificates/veris-ca.crt";
340
+ var SYSTEM_BUNDLE = "/etc/ssl/certs/ca-certificates.crt";
341
+ var VERIS_CA_FILE = "/tmp/veris-ca.crt";
342
+ var VERIS_BUNDLE = "/tmp/veris-ca-bundle.crt";
343
+ function vendoredTrustEnv() {
344
+ return {
345
+ SSL_CERT_FILE: VERIS_BUNDLE,
346
+ REQUESTS_CA_BUNDLE: VERIS_BUNDLE,
347
+ CURL_CA_BUNDLE: VERIS_BUNDLE,
348
+ GIT_SSL_CAINFO: VERIS_BUNDLE,
349
+ AWS_CA_BUNDLE: VERIS_BUNDLE,
350
+ CARGO_HTTP_CAINFO: VERIS_BUNDLE,
351
+ DENO_CERT: VERIS_BUNDLE,
352
+ PIP_CERT: VERIS_BUNDLE,
353
+ npm_config_cafile: VERIS_BUNDLE,
354
+ GRPC_DEFAULT_SSL_ROOTS_FILE_PATH: VERIS_BUNDLE,
355
+ BUNDLE_SSL_CA_CERT: VERIS_BUNDLE,
356
+ COMPOSER_CAFILE: VERIS_BUNDLE,
357
+ HEX_CACERTS_PATH: VERIS_BUNDLE,
358
+ JULIA_SSL_CA_ROOTS_PATH: VERIS_BUNDLE,
359
+ NIX_SSL_CERT_FILE: VERIS_BUNDLE,
360
+ PERL_LWP_SSL_CA_FILE: VERIS_BUNDLE,
361
+ CLOUDSDK_CORE_CUSTOM_CA_CERTS_FILE: VERIS_BUNDLE,
362
+ NODE_EXTRA_CA_CERTS: VERIS_CA_FILE
363
+ };
364
+ }
365
+ var CA_INSTALL_CMD = [
366
+ "update-ca-certificates",
367
+ `(keytool -importcert -noprompt -cacerts -storepass changeit -alias veris -file ${CA_CERT_PATH} 2>/dev/null || true)`,
368
+ `(command -v certutil >/dev/null 2>&1 && for db in $(find /home /root -maxdepth 4 -name "cert9.db" 2>/dev/null | xargs -r -n1 dirname); do certutil -A -n veris -t "C,," -i ${CA_CERT_PATH} -d "sql:$db" 2>/dev/null || true; done || true)`
369
+ ].join(" && ");
370
+ function sanitizeTrustEnv(served) {
371
+ const vendored = vendoredTrustEnv();
372
+ const out = { ...vendored };
373
+ for (const [k, val] of Object.entries(served ?? {})) {
374
+ if (!(k in vendored)) continue;
375
+ if (typeof val !== "string") continue;
376
+ if (!/^\/[\w./+~-]+$/.test(val)) continue;
377
+ out[k] = val;
378
+ }
379
+ return out;
380
+ }
381
+
382
+ // src/gateway.ts
383
+ var HOSTNAME_RE = /^[A-Za-z0-9.-]+$/;
384
+ var HOSTPORT_RE = /^[A-Za-z0-9.-]+:\d{1,5}$/;
385
+ function shellQuote(s) {
386
+ return `'${s.replace(/'/g, `'\\''`)}'`;
387
+ }
388
+ var sh = (sandbox, cmd, timeoutSec = 60) => sandbox.process.executeCommand(`sh -lc ${shellQuote(cmd)}`, void 0, void 0, timeoutSec);
389
+ function gatewayProxyUrl(credential) {
390
+ if (credential.http_proxy_url) return assertProxyUrl(credential.http_proxy_url);
391
+ if (!credential.connect_address || !HOSTPORT_RE.test(credential.connect_address)) {
392
+ throw new VerisError(
393
+ `the control plane returned a malformed gateway address: ${JSON.stringify(credential.connect_address)} (expected host:port)`,
394
+ { phase: "credential-mint" }
395
+ );
396
+ }
397
+ return `http://${encodeURIComponent(credential.username)}:x@${credential.connect_address}`;
398
+ }
399
+ function assertProxyUrl(raw) {
400
+ let u;
401
+ try {
402
+ u = new URL(raw);
403
+ } catch {
404
+ throw new VerisError(
405
+ `the control plane returned an unparseable gateway proxy URL: ${JSON.stringify(raw)}`,
406
+ { phase: "credential-mint" }
407
+ );
408
+ }
409
+ if (u.protocol !== "http:" && u.protocol !== "https:") {
410
+ throw new VerisError(
411
+ `the gateway proxy URL uses scheme "${u.protocol.replace(":", "")}", and Daytona accepts only http or https outbound proxies`,
412
+ { phase: "credential-mint" }
413
+ );
414
+ }
415
+ if (!u.hostname || !u.port) {
416
+ throw new VerisError(
417
+ `the gateway proxy URL is missing a host or port: ${JSON.stringify(raw)}`,
418
+ { phase: "credential-mint" }
419
+ );
420
+ }
421
+ return raw;
422
+ }
423
+ async function installCa(sandbox, caPem) {
424
+ await sandbox.fs.uploadFile(Buffer.from(caPem, "utf8"), VERIS_CA_FILE);
425
+ const script = [
426
+ `chmod 0644 ${VERIS_CA_FILE}`,
427
+ // Public roots first so they keep working; ours appended. `cat` of a
428
+ // missing file is tolerated — an image with no roots at all still gets a
429
+ // bundle containing the one CA that matters here.
430
+ `{ cat ${SYSTEM_BUNDLE} 2>/dev/null; cat ${VERIS_CA_FILE}; } > ${VERIS_BUNDLE}`,
431
+ `chmod 0644 ${VERIS_BUNDLE}`,
432
+ // Best-effort, for the stacks that read a store rather than a variable:
433
+ // the system bundle, the JVM truststore, and NSS databases. All of it needs
434
+ // root and tooling that may not be there, so none of it is load-bearing —
435
+ // but a Java client honours no CA env var at all, so where we CAN do it,
436
+ // we should.
437
+ `SUDO=; [ "$(id -u)" = 0 ] || SUDO="sudo -n"`,
438
+ `($SUDO install -m 0644 -D ${VERIS_CA_FILE} ${CA_CERT_PATH} 2>/dev/null && $SUDO sh -c ${shellQuote(CA_INSTALL_CMD)} 2>/dev/null) || true`,
439
+ // The bundle is the load-bearing one: fail loudly if it is not there.
440
+ `[ -s ${VERIS_BUNDLE} ] && echo __VERIS_CA_OK__`
441
+ ].join("; ");
442
+ const r = await sh(sandbox, script, 120).catch((e) => ({ exitCode: 1, result: String(e) }));
443
+ if (!(r.result ?? "").includes("__VERIS_CA_OK__")) {
444
+ throw new SnapshotUnsupportedError(
445
+ `could not assemble a CA bundle at ${VERIS_BUNDLE}, so the gateway's certificates cannot be trusted (${(r.result ?? "").trim().slice(0, 200)})`,
446
+ { phase: "ca-install" }
447
+ );
448
+ }
449
+ }
450
+ async function probeCanary(sandbox, canaryHost, expectedTwinId) {
451
+ if (!HOSTNAME_RE.test(canaryHost)) {
452
+ throw new ReceiptIntegrityError(
453
+ `refusing to probe a malformed canary host from the control plane: ${JSON.stringify(canaryHost)}`,
454
+ { phase: "canary", verisSandboxId: expectedTwinId }
455
+ );
456
+ }
457
+ const r = await sh(
458
+ sandbox,
459
+ `curl -sS --cacert ${VERIS_BUNDLE} --max-time 20 https://${canaryHost}/ || echo __VERIS_CANARY_FAIL__`,
460
+ 45
461
+ ).catch((e) => ({ exitCode: 1, result: String(e) }));
462
+ let body = {};
463
+ try {
464
+ body = JSON.parse(r.result ?? "");
465
+ } catch {
466
+ }
467
+ if (body.veris_sandbox_id !== expectedTwinId) {
468
+ throw new ReceiptIntegrityError(
469
+ `canary probe failed: egress from this Daytona sandbox is not tunnelled through the Veris gateway (expected twin ${expectedTwinId}, canary answered: ${(r.result || "nothing").trim().slice(0, 200)})`,
470
+ { phase: "canary", verisSandboxId: expectedTwinId }
471
+ );
472
+ }
473
+ }
474
+
475
+ // src/veris-api.ts
476
+ var VerisApiImpl = class {
477
+ constructor(ctx) {
478
+ this.ctx = ctx;
479
+ }
480
+ ctx;
481
+ get sandboxId() {
482
+ return this.ctx.twinId;
483
+ }
484
+ get mode() {
485
+ return "gateway";
486
+ }
487
+ services() {
488
+ return this.ctx.controlPlane.services(this.ctx.twinId);
489
+ }
490
+ async receipt(service) {
491
+ await probeCanary(this.ctx.sandbox, this.ctx.canaryHost, this.ctx.twinId);
492
+ const services = await this.services();
493
+ if (service !== void 0) {
494
+ const svc = services.find((s) => s.name === service);
495
+ if (!svc) {
496
+ throw new VerisError(
497
+ `unknown service '${service}' \u2014 the twin has no service by that name (available: ${services.map((s) => s.name).join(", ") || "none"})`,
498
+ { verisSandboxId: this.ctx.twinId }
499
+ );
500
+ }
501
+ return fetchReceiptEntry(svc);
502
+ }
503
+ const entries = await Promise.all(
504
+ services.filter((s) => isHttpUrl(s.control_url)).map(async (svc) => [svc.name, await fetchReceiptEntry(svc)])
505
+ );
506
+ return {
507
+ services: Object.fromEntries(entries),
508
+ mode: "gateway",
509
+ integrity: "verified",
510
+ leaks: this.leaks()
511
+ };
512
+ }
513
+ /**
514
+ * What this receipt cannot see. The gateway relays TCP, so QUIC/HTTP3 and ECH
515
+ * ride around it — named rather than rounded off.
516
+ */
517
+ leaks() {
518
+ return ["udp-quic-possible", "ech-possible"];
519
+ }
520
+ async assertTouched(service, match) {
521
+ const entry = await this.receipt(service);
522
+ const need = match?.minRequests ?? 1;
523
+ const matched = match ? entry.entries.filter((r) => (match.method === void 0 || r.method.toUpperCase() === match.method.toUpperCase()) && (match.path === void 0 || r.path.includes(match.path))) : entry.entries;
524
+ if (matched.length < need) {
525
+ const what = match ? `matching ${match.method ?? "ANY"} ${match.path ?? "*"} (${matched.length}/${need})` : "any intercepted requests";
526
+ throw new VerisUntouchedError(
527
+ `service '${service}' saw no ${what} \u2014 the code under test never reached it (a green run that skipped its dependency looks identical to a working one)`,
528
+ service,
529
+ { verisSandboxId: this.ctx.twinId }
530
+ );
531
+ }
532
+ }
533
+ async getDataPlaneEnv() {
534
+ return dataPlaneEnv(await this.services());
535
+ }
536
+ /** The CA trust vars injected at create, for callers building their own env. */
537
+ getTrustEnv() {
538
+ return vendoredTrustEnv();
539
+ }
540
+ async deliverTo(target, opts = {}) {
541
+ const url = typeof target === "number" ? (await this.ctx.sandbox.getPreviewLink(target)).url : target;
542
+ await this.ctx.controlPlane.updateSandbox(
543
+ this.ctx.environmentId,
544
+ this.ctx.twinId,
545
+ { client_base_url: url }
546
+ );
547
+ if (url !== null && opts.probe !== false) await this.probeDelivery(url);
548
+ return url;
549
+ }
550
+ /** Ask each service to re-probe the registered destination; throw if none can reach it. */
551
+ async probeDelivery(url) {
552
+ const services = (await this.services()).filter((s) => isHttpUrl(s.control_url));
553
+ if (!services.length) return;
554
+ const probes = await Promise.all(services.map(async (svc) => {
555
+ try {
556
+ const res = await fetch(`${svc.control_url}/veris/client/probe`, { method: "POST" });
557
+ return res.ok ? await res.json() : null;
558
+ } catch {
559
+ return null;
560
+ }
561
+ }));
562
+ if (!probes.some((p) => p?.answered)) {
563
+ throw new VerisError(
564
+ `no service could reach ${url} \u2014 is your app listening on that port inside the Daytona sandbox?`,
565
+ { phase: "receipt", verisSandboxId: this.ctx.twinId, responseBody: probes }
566
+ );
567
+ }
568
+ }
569
+ };
570
+
571
+ // src/version.ts
572
+ var SDK_VERSION = true ? "0.1.0" : "0.0.0-dev";
573
+
574
+ // src/daytona.ts
575
+ var LABEL = {
576
+ twinId: "veris_twin_id",
577
+ envId: "veris_env_id",
578
+ apiBase: "veris_api_base",
579
+ mode: "veris_mode",
580
+ egress: "veris_egress",
581
+ ownsTwin: "veris_owns_twin",
582
+ canaryHost: "veris_canary_host"
583
+ };
584
+ var VERIS_LABEL_KEYS = Object.values(LABEL);
585
+ function isVerisSandbox(sbx) {
586
+ return typeof sbx.verisSandboxId === "string";
587
+ }
588
+ var Daytona = class extends BaseDaytona {
589
+ verisDefaults;
590
+ constructor(config) {
591
+ super(config);
592
+ this.verisDefaults = config?.veris ?? {};
593
+ }
594
+ async create(params, options) {
595
+ const v = { ...this.verisDefaults, ...params?.veris ?? {} };
596
+ const rest = stripVeris(params);
597
+ if (v.disabled) return this.baseCreate(rest, options);
598
+ const coords = resolveCoordinates(v);
599
+ const controlPlane = new ControlPlane({
600
+ apiKey: coords.apiKey,
601
+ apiBase: coords.apiBase,
602
+ sdkVersion: SDK_VERSION
603
+ });
604
+ const egress = v.egress ?? "strict";
605
+ const ttlMinutes = v.ttlMinutes ?? 60;
606
+ const ownsTwin = !v.attachSandboxId;
607
+ const twin = await this.provisionTwin(controlPlane, v, coords, ttlMinutes);
608
+ const cleanupTwin = async () => {
609
+ if (ownsTwin) await controlPlane.deleteTwin(twin.environment_id, twin.id).catch(() => {
610
+ });
611
+ };
612
+ let sandbox;
613
+ let credential;
614
+ try {
615
+ credential = await controlPlane.mintEgressCredential(twin.environment_id, twin.id);
616
+ if (!credential) {
617
+ throw new VerisGatewayNotOfferedError(
618
+ "this Veris control plane does not offer egress credentials, so there is no gateway for the sandbox to route through",
619
+ { phase: "credential-mint", verisSandboxId: twin.id }
620
+ );
621
+ }
622
+ if (!credential.connect_address && !credential.http_proxy_url) {
623
+ throw new VerisGatewayNotOfferedError(
624
+ 'the Veris gateway offers SOCKS5 but no HTTP CONNECT endpoint, and Daytona accepts only http/https outbound proxies ("Unsupported outbound proxy scheme"). Upgrade the control plane to one that returns connect_address.',
625
+ { phase: "credential-mint", verisSandboxId: twin.id }
626
+ );
627
+ }
628
+ const services = twin.services?.length ? twin.services : await controlPlane.services(twin.id);
629
+ const proxyUrl = gatewayProxyUrl(credential);
630
+ const network = buildNetwork({
631
+ services,
632
+ mode: egress,
633
+ // The gateway has to be reachable or nothing is: taken from the URL we
634
+ // are actually going to use, so the two can never disagree.
635
+ gatewayHosts: [new URL(proxyUrl).hostname, credential.canary_host].filter(Boolean),
636
+ allowOut: v.allowOut,
637
+ allowRegistries: v.allowRegistries
638
+ });
639
+ const verisManaged = {
640
+ ...v.installCa !== false ? sanitizeTrustEnv(void 0) : {},
641
+ ...v.dataPlaneEnv !== false ? dataPlaneEnv(services) : {},
642
+ VERIS_SANDBOX_ID: twin.id
643
+ };
644
+ const createParams = {
645
+ ...rest,
646
+ envVars: { ...rest.envVars ?? {}, ...verisManaged },
647
+ labels: {
648
+ ...reserveLabels(rest.labels),
649
+ [LABEL.twinId]: twin.id,
650
+ [LABEL.envId]: twin.environment_id,
651
+ [LABEL.apiBase]: coords.apiBase,
652
+ [LABEL.egress]: egress,
653
+ [LABEL.ownsTwin]: String(ownsTwin),
654
+ [LABEL.mode]: "gateway",
655
+ [LABEL.canaryHost]: credential.canary_host
656
+ },
657
+ ...network,
658
+ // 3. Where Daytona forwards everything the allowlist permits. Chained,
659
+ // not advisory: an unreachable gateway makes allowed traffic 502
660
+ // rather than quietly going direct.
661
+ outboundProxyUrl: proxyUrl,
662
+ ttlMinutes: rest.ttlMinutes ?? ttlMinutes
663
+ };
664
+ sandbox = await this.baseCreate(createParams, options);
665
+ } catch (cause) {
666
+ await cleanupTwin();
667
+ if (cause instanceof VerisError) throw cause;
668
+ throw new VerisError("Daytona sandbox create failed", {
669
+ phase: "sandbox-create",
670
+ verisSandboxId: twin.id,
671
+ cause
672
+ });
673
+ }
674
+ try {
675
+ await installCa(sandbox, credential.ca_pem);
676
+ await probeCanary(sandbox, credential.canary_host, twin.id);
677
+ } catch (err) {
678
+ await sandbox.delete().catch(() => {
679
+ });
680
+ await cleanupTwin();
681
+ throw err;
682
+ }
683
+ return this.attach(sandbox, {
684
+ controlPlane,
685
+ environmentId: twin.environment_id,
686
+ twinId: twin.id,
687
+ egress,
688
+ ownsTwin,
689
+ canaryHost: credential.canary_host
690
+ });
691
+ }
692
+ /**
693
+ * Rehydrate the Veris surface on an existing sandbox.
694
+ *
695
+ * Not an optimisation — a necessity. The OpenCode plugin reconnects to a
696
+ * sandbox with get() on every resumed session and deletes through get() too,
697
+ * so a get() that returned a bare Sandbox would mean no receipts after any
698
+ * restart and a leaked twin on every delete.
699
+ */
700
+ async get(sandboxIdOrName) {
701
+ const sandbox = await super.get(sandboxIdOrName);
702
+ return this.rehydrate(sandbox);
703
+ }
704
+ /** Same rehydration for the sandboxes a list() streams. */
705
+ list(query) {
706
+ const inner = super.list(query);
707
+ const rehydrate = (s) => this.rehydrate(s);
708
+ return (async function* () {
709
+ for await (const sandbox of inner) yield rehydrate(sandbox);
710
+ })();
711
+ }
712
+ /**
713
+ * Attach the Veris surface to a sandbox whose labels say it has a twin.
714
+ * A sandbox without our labels is passed through untouched — callers can use
715
+ * this client for ordinary Daytona work.
716
+ */
717
+ rehydrate(sandbox) {
718
+ const labels = sandbox.labels ?? {};
719
+ const twinId = labels[LABEL.twinId];
720
+ if (!twinId) return sandbox;
721
+ const apiKey = this.verisDefaults.apiKey ?? process.env.VERIS_API_KEY;
722
+ if (!apiKey) return sandbox;
723
+ const trustedBase = this.verisDefaults.apiBase ?? process.env.VERIS_API_BASE;
724
+ const labelBase = labels[LABEL.apiBase];
725
+ if (trustedBase && labelBase && labelBase !== trustedBase) {
726
+ throw new VerisError(
727
+ `sandbox ${sandbox.id} labels name a different Veris control plane (${labelBase}) than your configuration (${trustedBase}) \u2014 refusing to send the API key to an unverified host`,
728
+ { phase: "attach" }
729
+ );
730
+ }
731
+ const apiBase = trustedBase ?? labelBase ?? "https://svc.api.veris.ai";
732
+ return this.attach(sandbox, {
733
+ controlPlane: new ControlPlane({ apiKey, apiBase, sdkVersion: SDK_VERSION }),
734
+ environmentId: labels[LABEL.envId] ?? "",
735
+ twinId,
736
+ // Re-minted below when a receipt is actually asked for; the label only
737
+ // has to survive the reconnect.
738
+ canaryHost: labels[LABEL.canaryHost] ?? "",
739
+ egress: labels[LABEL.egress] ?? "strict",
740
+ ownsTwin: labels[LABEL.ownsTwin] !== "false"
741
+ });
742
+ }
743
+ async provisionTwin(controlPlane, v, coords, ttlMinutes) {
744
+ if (v.attachSandboxId) {
745
+ const existing = await controlPlane.getTwin(v.attachSandboxId);
746
+ if (!existing) {
747
+ throw new VerisError(`attach target ${v.attachSandboxId} not found`, {
748
+ phase: "twin-provision",
749
+ verisSandboxId: v.attachSandboxId
750
+ });
751
+ }
752
+ return existing.status === "ready" ? existing : controlPlane.waitReady(v.attachSandboxId, 24e4);
753
+ }
754
+ if (!coords.environmentId) {
755
+ throw new MissingCredentialsError(
756
+ "no Veris environment: set VERIS_ENVIRONMENT_ID, or pass veris.environmentId",
757
+ { phase: "credentials" }
758
+ );
759
+ }
760
+ const created = await controlPlane.createTwin(coords.environmentId, { ttlMinutes });
761
+ try {
762
+ return await controlPlane.waitReady(created.id, 24e4);
763
+ } catch (e) {
764
+ await controlPlane.deleteTwin(coords.environmentId, created.id).catch(() => {
765
+ });
766
+ throw e;
767
+ }
768
+ }
769
+ /**
770
+ * Hang the Veris surface off the instance, and wrap delete() so teardown is
771
+ * automatic.
772
+ *
773
+ * Wrapping rather than asking callers to remember is the point: the OpenCode
774
+ * plugin's existing `sandbox.delete()` then removes the twin too, with no
775
+ * change to the plugin at all. A twin outlives its sandbox otherwise, until
776
+ * its TTL reaps it — invisible until the bill arrives.
777
+ */
778
+ attach(sandbox, ctx) {
779
+ if (isVerisSandbox(sandbox)) return sandbox;
780
+ const veris = new VerisApiImpl({ ...ctx, sandbox });
781
+ const originalDelete = sandbox.delete.bind(sandbox);
782
+ Object.defineProperties(sandbox, {
783
+ veris: { value: veris, enumerable: true, configurable: true },
784
+ verisSandboxId: { value: ctx.twinId, enumerable: true, configurable: true },
785
+ delete: {
786
+ configurable: true,
787
+ value: async (timeout, wait) => {
788
+ if (ctx.ownsTwin) {
789
+ await ctx.controlPlane.deleteTwin(ctx.environmentId, ctx.twinId).catch(() => {
790
+ });
791
+ }
792
+ return originalDelete(timeout, wait);
793
+ }
794
+ }
795
+ });
796
+ return sandbox;
797
+ }
798
+ /** super.create through the overload the params actually match. */
799
+ baseCreate(params, options) {
800
+ return super.create.call(this, params, options);
801
+ }
802
+ };
803
+ var daytona_default = Daytona;
804
+ function resolveCoordinates(v) {
805
+ const apiKey = v.apiKey ?? process.env.VERIS_API_KEY;
806
+ if (!apiKey) {
807
+ throw new MissingCredentialsError(
808
+ "no Veris API key: set VERIS_API_KEY in your environment, or pass veris.apiKey. Get one at https://studio.veris.ai",
809
+ { phase: "credentials" }
810
+ );
811
+ }
812
+ return {
813
+ apiKey,
814
+ environmentId: v.environmentId ?? process.env.VERIS_ENVIRONMENT_ID,
815
+ apiBase: (v.apiBase ?? process.env.VERIS_API_BASE ?? "https://svc.api.veris.ai").replace(/\/$/, "")
816
+ };
817
+ }
818
+ function stripVeris(params) {
819
+ const { veris: _veris, ...rest } = params ?? {};
820
+ return rest;
821
+ }
822
+ function reserveLabels(labels) {
823
+ const out = {};
824
+ for (const [k, val] of Object.entries(labels ?? {})) {
825
+ if (!VERIS_LABEL_KEYS.includes(k)) out[k] = val;
826
+ }
827
+ return out;
828
+ }
829
+ export {
830
+ CA_CERT_PATH,
831
+ ControlPlane,
832
+ DEFAULT_REGISTRY_HOSTS,
833
+ Daytona,
834
+ MissingCredentialsError,
835
+ ReceiptIntegrityError,
836
+ SDK_VERSION,
837
+ SYSTEM_BUNDLE,
838
+ SnapshotUnsupportedError,
839
+ TwinExpiredError,
840
+ UnsupportedOperationError,
841
+ VERIS_BUNDLE,
842
+ VERIS_CA_FILE,
843
+ VerisError,
844
+ VerisGatewayNotOfferedError,
845
+ VerisGatewayUnreachableError,
846
+ VerisUntouchedError,
847
+ dataPlaneHosts,
848
+ daytona_default as default,
849
+ gatewayProxyUrl,
850
+ isVerisSandbox,
851
+ twinHosts,
852
+ vendorHosts,
853
+ vendoredTrustEnv
854
+ };
855
+ //# sourceMappingURL=index.js.map