@tangle-network/tcloud 0.4.2 → 0.4.4

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.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import { ShieldedWallet, generateWallet } from './shielded.js';
2
2
  export { createShieldedClient, estimateCost, signSpendAuth } from './shielded.js';
3
- import { T as TCloudClient, a as TCloudConfig } from './client-D7_hFedn.js';
4
- export { A as ApiKeyInfo, b as AvatarGenerateRequest, c as AvatarGenerateResponse, d as AvatarJobStatus, e as AvatarResult, B as BatchJobResponse, f as BatchRequest, g as BridgeOptions, h as BridgeSession, C as ChatCompletion, i as ChatCompletionChunk, j as ChatMessage, k as ChatOptions, l as CompletionOptions, m as CompletionResponse, n as CreateKeyOptions, o as CreatedKey, p as CreditBalance, E as EmbeddingOptions, q as EmbeddingResponse, F as FineTuningJob, r as FineTuningJobOptions, G as GatewayOptions, I as ImageGenerateOptions, s as ImageResponse, J as JobEvent, M as Model, O as Operator, t as OperatorInfo, P as PricingTier, u as PrivacyConfig, v as PrivateRouter, w as PrivateRouterConfig, R as RerankOptions, x as RerankResponse, y as RetryConfig, z as RoutingConfig, D as RoutingStrategy, S as ShieldedConfig, H as SpendAuth, K as SpendingLimits, L as TCloudError, N as TierConfig, Q as TranscriptionResponse, U as UpdateKeyOptions, V as VideoGenerateOptions, W as VideoResponse, X as WatchJobOptions } from './client-D7_hFedn.js';
3
+ import { T as TCloudClient, a as TCloudConfig, R as RotatingClientConfig } from './client-DkQugNHH.js';
4
+ export { A as ApiKeyInfo, b as AvatarGenerateRequest, c as AvatarGenerateResponse, d as AvatarJobStatus, e as AvatarResult, B as BatchJobResponse, f as BatchRequest, g as BridgeOptions, h as BridgeSession, C as ChatCompletion, i as ChatCompletionChunk, j as ChatMessage, k as ChatOptions, l as CompletionOptions, m as CompletionResponse, n as CreateKeyOptions, o as CreatedKey, p as CreditBalance, E as EmbeddingOptions, q as EmbeddingResponse, F as FineTuningJob, r as FineTuningJobOptions, G as GatewayOptions, I as ImageGenerateOptions, s as ImageResponse, J as JobEvent, M as Model, O as Operator, t as OperatorInfo, P as PricingTier, u as PrivacyConfig, v as PrivateRouter, w as PrivateRouterConfig, x as RerankOptions, y as RerankResponse, z as RetryConfig, D as RotatingRoutingConfig, H as RotationStats, K as RoutingConfig, L as RoutingStrategy, S as ShieldedConfig, N as SpendAuth, Q as SpendingLimits, U as TCloudError, V as TierConfig, W as TranscriptionResponse, X as UpdateKeyOptions, Y as VideoGenerateOptions, Z as VideoResponse, _ as WatchJobOptions } from './client-DkQugNHH.js';
5
+ export { TCloudSandbox, TCloudSandboxAttestationStatus, TCloudSandboxConfig, TCloudSandboxCreateOptions, TCloudSandboxCreateResult, TCloudSandboxTee, TCloudTeeAttestationChallenge, TCloudTeeAttestationHeartbeat, TCloudTeeAttestationHeartbeatOptions, TCloudTeeAttestationHeartbeatSample, createTeeAttestationChallenge, generateAttestationNonce, startTeeAttestationHeartbeat } from './sandbox.js';
6
+ export { AsyncAttestationPolicy, AsyncHardwareVerifier, AttestationPolicy, AttestationVerificationResult, HardwareVerifier, HardwareVerifierResult, ParsedAttestation, SevSnpReport, SevSnpVerifierOptions, TeeType, assertAttestation, createSevSnpHardwareVerifier, createTdxHardwareVerifier, normalizeTeeType, parseAttestation, parseSevSnpReport, toHex, verifyAttestation, verifyAttestationAsync } from '@tangle-network/tcloud-attestation';
5
7
  export { AgentProfile, AgentProfileCapabilities, AgentProfileMcpServer, AgentProfileModelHints, AgentProfilePermissionValue, AgentProfilePrompt, AgentProfileResources } from '@tangle-network/sandbox';
6
8
  import 'viem';
7
9
 
@@ -36,6 +38,19 @@ declare class TCloud extends TCloudClient {
36
38
  wallet: ShieldedWallet;
37
39
  stopAutoReplenish: () => void;
38
40
  };
41
+ /**
42
+ * Create a client that rotates operators per call.
43
+ * See {@link TCloudClient.rotating} for semantics.
44
+ *
45
+ * ```ts
46
+ * const client = TCloud.rotating({
47
+ * apiKey: process.env.TCLOUD_API_KEY,
48
+ * routing: { strategy: 'min-exposure' },
49
+ * })
50
+ * const stats = client.getRotationStats()
51
+ * ```
52
+ */
53
+ static rotating(config?: RotatingClientConfig): TCloudClient;
39
54
  /**
40
55
  * Generate a new ephemeral wallet (without creating a client).
41
56
  *
@@ -47,4 +62,4 @@ declare class TCloud extends TCloudClient {
47
62
  static generateWallet: typeof generateWallet;
48
63
  }
49
64
 
50
- export { ShieldedWallet, TCloud, TCloudClient, TCloudConfig, generateWallet };
65
+ export { RotatingClientConfig, ShieldedWallet, TCloud, TCloudClient, TCloudConfig, generateWallet };
package/dist/index.js CHANGED
@@ -1,26 +1,54 @@
1
1
  import {
2
- TCloud
3
- } from "./chunk-577KIKFA.js";
2
+ TCloud,
3
+ assertAttestation,
4
+ createSevSnpHardwareVerifier,
5
+ createTdxHardwareVerifier,
6
+ normalizeTeeType,
7
+ parseAttestation,
8
+ parseSevSnpReport,
9
+ toHex,
10
+ verifyAttestation,
11
+ verifyAttestationAsync
12
+ } from "./chunk-MB4VK4MM.js";
13
+ import {
14
+ TCloudSandbox,
15
+ createTeeAttestationChallenge,
16
+ generateAttestationNonce,
17
+ startTeeAttestationHeartbeat
18
+ } from "./chunk-DBIT227N.js";
4
19
  import {
5
20
  createShieldedClient,
6
21
  estimateCost,
7
22
  generateWallet,
8
23
  signSpendAuth
9
- } from "./chunk-A7AEPV2G.js";
24
+ } from "./chunk-4ZUVGKVH.js";
10
25
  import {
11
26
  BridgeSession,
12
27
  PrivateRouter,
13
28
  TCloudClient,
14
29
  TCloudError
15
- } from "./chunk-B22AH4JH.js";
30
+ } from "./chunk-M5K3EFNP.js";
16
31
  export {
17
32
  BridgeSession,
18
33
  PrivateRouter,
19
34
  TCloud,
20
35
  TCloudClient,
21
36
  TCloudError,
37
+ TCloudSandbox,
38
+ assertAttestation,
39
+ createSevSnpHardwareVerifier,
22
40
  createShieldedClient,
41
+ createTdxHardwareVerifier,
42
+ createTeeAttestationChallenge,
23
43
  estimateCost,
44
+ generateAttestationNonce,
24
45
  generateWallet,
25
- signSpendAuth
46
+ normalizeTeeType,
47
+ parseAttestation,
48
+ parseSevSnpReport,
49
+ signSpendAuth,
50
+ startTeeAttestationHeartbeat,
51
+ toHex,
52
+ verifyAttestation,
53
+ verifyAttestationAsync
26
54
  };
package/dist/instance.cjs CHANGED
@@ -53,6 +53,8 @@ var PrivateRouter = class {
53
53
  usage = /* @__PURE__ */ new Map();
54
54
  currentIndex = 0;
55
55
  totalRequests = 0;
56
+ /** Slug of the most-recently-selected operator. Populated on each selectOperator() hit. */
57
+ _lastSelectedSlug = null;
56
58
  constructor(config = {}) {
57
59
  this.config = {
58
60
  strategy: config.strategy || "round-robin",
@@ -198,6 +200,11 @@ var PrivateRouter = class {
198
200
  requestCount: (existing?.requestCount || 0) + 1,
199
201
  lastUsedAt: Date.now()
200
202
  });
203
+ this._lastSelectedSlug = op.slug;
204
+ }
205
+ /** Slug of the most-recently-selected operator (null before the first call). */
206
+ get lastSelectedSlug() {
207
+ return this._lastSelectedSlug;
201
208
  }
202
209
  getLastUsedOperator() {
203
210
  let latest = null;
@@ -255,6 +262,7 @@ var PrivateRouter = class {
255
262
  };
256
263
 
257
264
  // src/client.ts
265
+ var ROTATING_MARKER = "__tcloudRotating";
258
266
  var DEFAULT_BASE_URL = "https://router.tangle.tools/v1";
259
267
  var SDK_VERSION = "0.4.0";
260
268
  async function proxiedFetch(privacy, url, init, streaming) {
@@ -352,6 +360,54 @@ var TCloudClient = class _TCloudClient {
352
360
  const baseURL = opts.url.replace(/\/+$/, "") + "/v1";
353
361
  return new _TCloudClient({ ...opts.config ?? {}, apiKey: opts.bearer, baseURL });
354
362
  }
363
+ /**
364
+ * Build a client that rotates which operator serves each call. Mirrors
365
+ * {@link TCloudClient.shielded} in shape: returns a standard `TCloudClient`
366
+ * that behaves identically for the OpenAI-compatible surface but
367
+ * dispatches each chat/completions/embeddings request through a
368
+ * {@link PrivateRouter} — different operator per call per the chosen
369
+ * strategy.
370
+ *
371
+ * ```ts
372
+ * const tcloud = TCloudClient.rotating({
373
+ * apiKey: process.env.TCLOUD_API_KEY,
374
+ * routing: { strategy: 'min-exposure' },
375
+ * })
376
+ * await tcloud.ask('hello')
377
+ * tcloud.getRotationStats() // { callsByOperator: { ... }, currentOperator: '…' }
378
+ * ```
379
+ *
380
+ * Rotation is meaningful only for stateless calls. Sandbox-harness
381
+ * sessions bind to a single operator for the lifetime of the session;
382
+ * `rotating()` clients refuse to dispatch them — see {@link bridge}.
383
+ */
384
+ static rotating(config = {}) {
385
+ const routing = config.routing ?? {};
386
+ const strategy = routing.strategy ?? "min-exposure";
387
+ const { routing: _omit, ...base } = config;
388
+ const client = new _TCloudClient(base);
389
+ const router = new PrivateRouter({
390
+ strategy,
391
+ minOperators: routing.minOperators ?? 1,
392
+ maxRequestsPerOperator: routing.maxRequestsPerOperator,
393
+ excludeOperators: routing.excludeOperators,
394
+ preferRegions: routing.preferRegions
395
+ });
396
+ if (routing.pool && routing.pool.length > 0) {
397
+ router.setOperators(routing.pool);
398
+ client._cachedOperators = routing.pool;
399
+ client._operatorsCachedAt = Date.now();
400
+ }
401
+ ;
402
+ client.privateRouter = router;
403
+ Object.defineProperty(client, ROTATING_MARKER, {
404
+ value: true,
405
+ enumerable: false,
406
+ configurable: false,
407
+ writable: false
408
+ });
409
+ return client;
410
+ }
355
411
  constructor(config = {}) {
356
412
  this.baseURL = (config.baseURL || DEFAULT_BASE_URL).replace(/\/$/, "");
357
413
  this.platformURL = (config.platformURL || DEFAULT_PLATFORM_URL).replace(/\/$/, "");
@@ -667,10 +723,38 @@ var TCloudClient = class _TCloudClient {
667
723
  *
668
724
  * Sessions persist across process restarts — use the same `resume` id
669
725
  * to land on the same CLI conversation (context intact, no replay tax).
726
+ *
727
+ * Guard: clients built via {@link TCloudClient.rotating} cannot dispatch
728
+ * sandbox-harness sessions (rotation rotates per call; a sandbox session
729
+ * binds to one operator). Attempting `bridge({ harness: 'sandbox' })` on
730
+ * a rotating client throws.
670
731
  */
671
732
  bridge(cfg) {
733
+ if (cfg.harness === "sandbox" && this[ROTATING_MARKER] === true) {
734
+ throw new Error(
735
+ "TCloudClient.rotating() cannot dispatch sandbox-harness sessions.\nSandbox sessions bind to a single operator; rotation is meaningful only for\nstateless calls. Use TCloudClient.shielded() + AgentProfile.confidential.tee\nfor privacy-preserving sandbox execution instead."
736
+ );
737
+ }
672
738
  return new BridgeSession(this, cfg);
673
739
  }
740
+ /**
741
+ * Rotation stats — populated only on clients created via
742
+ * {@link TCloudClient.rotating}. Non-rotating clients return an empty
743
+ * counter and `currentOperator: null`.
744
+ */
745
+ getRotationStats() {
746
+ if (!this.privateRouter) {
747
+ return { callsByOperator: {}, currentOperator: null };
748
+ }
749
+ const callsByOperator = {};
750
+ for (const row of this.privateRouter.getStats().operatorBreakdown) {
751
+ callsByOperator[row.slug] = row.requests;
752
+ }
753
+ return {
754
+ callsByOperator,
755
+ currentOperator: this.privateRouter.lastSelectedSlug
756
+ };
757
+ }
674
758
  /** Convenience: send a single message and get the text response */
675
759
  async ask(message, modelOrOptions) {
676
760
  const options = typeof modelOrOptions === "string" ? { model: modelOrOptions } : modelOrOptions;
@@ -1172,6 +1256,8 @@ var BridgeSession = class _BridgeSession {
1172
1256
  this.client = client;
1173
1257
  this.cfg = cfg;
1174
1258
  }
1259
+ client;
1260
+ cfg;
1175
1261
  /** Full chat completion (non-streaming). */
1176
1262
  async chat(options) {
1177
1263
  return this.client.chat({ ...options, bridge: this.cfg });
@@ -1241,6 +1327,7 @@ var TCloudError = class extends Error {
1241
1327
  this.status = status;
1242
1328
  this.name = "TCloudError";
1243
1329
  }
1330
+ status;
1244
1331
  };
1245
1332
 
1246
1333
  // src/instance.ts
@@ -1,4 +1,4 @@
1
- import { a as TCloudConfig, T as TCloudClient } from './client-D7_hFedn.cjs';
1
+ import { a as TCloudConfig, T as TCloudClient } from './client-DkQugNHH.cjs';
2
2
 
3
3
  /**
4
4
  * Instance — programmatic harness for spinning up a local Tangle dev environment.
@@ -1,4 +1,4 @@
1
- import { a as TCloudConfig, T as TCloudClient } from './client-D7_hFedn.js';
1
+ import { a as TCloudConfig, T as TCloudClient } from './client-DkQugNHH.js';
2
2
 
3
3
  /**
4
4
  * Instance — programmatic harness for spinning up a local Tangle dev environment.
package/dist/instance.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  TCloudClient
3
- } from "./chunk-B22AH4JH.js";
3
+ } from "./chunk-M5K3EFNP.js";
4
4
 
5
5
  // src/instance.ts
6
6
  import { spawn } from "child_process";
@@ -0,0 +1,350 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/sandbox.ts
21
+ var sandbox_exports = {};
22
+ __export(sandbox_exports, {
23
+ TCloudSandbox: () => TCloudSandbox,
24
+ buildSandboxCreateOptions: () => buildSandboxCreateOptions,
25
+ createTeeAttestationChallenge: () => createTeeAttestationChallenge,
26
+ generateAttestationNonce: () => generateAttestationNonce,
27
+ shouldVerifyAttestation: () => shouldVerifyAttestation,
28
+ startTeeAttestationHeartbeat: () => startTeeAttestationHeartbeat
29
+ });
30
+ module.exports = __toCommonJS(sandbox_exports);
31
+ var import_sandbox = require("@tangle-network/sandbox");
32
+ var import_node_crypto = require("crypto");
33
+ var import_tcloud_attestation = require("@tangle-network/tcloud-attestation");
34
+ var DEFAULT_SANDBOX_URL = "https://sandbox.tangle.tools";
35
+ var TCloudSandbox = class {
36
+ client;
37
+ apiKey;
38
+ baseUrl;
39
+ timeoutMs;
40
+ constructor(config) {
41
+ this.apiKey = config.apiKey;
42
+ this.baseUrl = (config.baseUrl ?? DEFAULT_SANDBOX_URL).replace(/\/+$/, "");
43
+ this.timeoutMs = config.timeoutMs;
44
+ this.client = new import_sandbox.Sandbox({
45
+ apiKey: this.apiKey,
46
+ baseUrl: this.baseUrl,
47
+ timeoutMs: config.timeoutMs
48
+ });
49
+ }
50
+ async create(options) {
51
+ const effectiveVerify = shouldVerifyAttestation(options);
52
+ const attestationPolicy = buildAttestationPolicy(options);
53
+ const createOptions = buildSandboxCreateOptions({
54
+ ...options,
55
+ verify: effectiveVerify
56
+ });
57
+ const sandbox = await this.client.create(createOptions);
58
+ this.attachTeeApiFallbacks(sandbox);
59
+ let attestation = attestationFromMetadata(sandbox.metadata);
60
+ if ((effectiveVerify || createOptions.confidential?.attestationNonce) && !attestation) {
61
+ const getTeeAttestation = sandbox.getTeeAttestation;
62
+ if (typeof getTeeAttestation !== "function") {
63
+ throw new Error("Installed @tangle-network/sandbox does not expose TEE attestation fetching");
64
+ }
65
+ attestation = (await getTeeAttestation.call(
66
+ sandbox,
67
+ createOptions.confidential?.attestationNonce ? { attestationNonce: createOptions.confidential.attestationNonce } : void 0
68
+ )).attestation;
69
+ }
70
+ if (effectiveVerify && !attestation) {
71
+ throw new Error("TEE attestation verification requested but no evidence was returned");
72
+ }
73
+ const verification = effectiveVerify ? await (0, import_tcloud_attestation.verifyAttestationAsync)(attestation, {
74
+ ...attestationPolicy,
75
+ expectedNonce: createOptions.confidential?.attestationNonce
76
+ }) : void 0;
77
+ if (verification && !verification.valid) {
78
+ throw new Error(`TEE attestation verification failed: ${verification.errors.join("; ")}`);
79
+ }
80
+ return {
81
+ sandbox,
82
+ attestation,
83
+ verification,
84
+ attestationNonce: createOptions.confidential?.attestationNonce,
85
+ attestationStatus: {
86
+ requested: Boolean(options.tee),
87
+ evidenceReturned: Boolean(attestation),
88
+ verified: Boolean(verification?.valid),
89
+ nonceBound: Boolean(createOptions.confidential?.attestationNonce && verification?.valid),
90
+ errors: verification?.errors ?? []
91
+ }
92
+ };
93
+ }
94
+ attachTeeApiFallbacks(sandbox) {
95
+ if (typeof sandbox !== "object" || sandbox === null) return;
96
+ const target = sandbox;
97
+ if (typeof target.id !== "string" || target.id.length === 0) return;
98
+ if (typeof target.getTeeAttestation !== "function") {
99
+ target.getTeeAttestation = (options) => this.fetchTeeAttestation(target.id, options?.attestationNonce);
100
+ }
101
+ if (typeof target.getTeePublicKey !== "function") {
102
+ target.getTeePublicKey = () => this.fetchTeePublicKey(target.id);
103
+ }
104
+ }
105
+ async fetchTeeAttestation(sandboxId, attestationNonce) {
106
+ const response = await this.fetchSandboxApi(
107
+ `/v1/sandboxes/${encodeURIComponent(sandboxId)}/tee/attestation`,
108
+ {
109
+ method: attestationNonce ? "POST" : "GET",
110
+ body: attestationNonce ? JSON.stringify({ attestation_nonce: attestationNonce }) : void 0
111
+ }
112
+ );
113
+ const data = await response.json();
114
+ if (attestationNonce && !data.attestationNonce) {
115
+ data.attestationNonce = attestationNonce;
116
+ }
117
+ return data;
118
+ }
119
+ async fetchTeePublicKey(sandboxId) {
120
+ const response = await this.fetchSandboxApi(
121
+ `/v1/sandboxes/${encodeURIComponent(sandboxId)}/tee/public-key`,
122
+ { method: "GET" }
123
+ );
124
+ return response.json();
125
+ }
126
+ async fetchSandboxApi(path, options) {
127
+ const headers = new Headers(options.headers);
128
+ headers.set("Authorization", `Bearer ${this.apiKey}`);
129
+ if (options.body && !headers.has("Content-Type")) {
130
+ headers.set("Content-Type", "application/json");
131
+ }
132
+ const response = await fetch(`${this.baseUrl}${path}`, {
133
+ ...options,
134
+ headers,
135
+ signal: options.signal ?? (this.timeoutMs ? AbortSignal.timeout(this.timeoutMs) : void 0)
136
+ });
137
+ if (!response.ok) {
138
+ const body = await response.text();
139
+ throw new Error(`Sandbox API ${path} failed with HTTP ${response.status}: ${body}`);
140
+ }
141
+ return response;
142
+ }
143
+ };
144
+ function createTeeAttestationChallenge(context) {
145
+ const randomHex = generateAttestationNonce(32);
146
+ if (context == null) {
147
+ return { nonce: randomHex, randomHex };
148
+ }
149
+ const contextHashHex = (0, import_node_crypto.createHash)("sha256").update(typeof context === "string" ? Buffer.from(context) : Buffer.from(context)).digest("hex");
150
+ return {
151
+ nonce: `${randomHex}${contextHashHex}`,
152
+ randomHex,
153
+ contextHashHex
154
+ };
155
+ }
156
+ function startTeeAttestationHeartbeat(sandbox, options = {}) {
157
+ const getTeeAttestation = sandbox?.getTeeAttestation;
158
+ if (typeof getTeeAttestation !== "function") {
159
+ throw new Error("Sandbox does not expose TEE attestation fetching");
160
+ }
161
+ const intervalMs = options.intervalMs ?? 6e4;
162
+ const sessionId = options.sessionId ?? generateAttestationNonce(16);
163
+ const policy = options.tee ? buildAttestationPolicy({ tee: options.tee, attestationPolicy: options.attestationPolicy }) : options.attestationPolicy ?? {};
164
+ let stopped = false;
165
+ let failures = 0;
166
+ let sequence = 0;
167
+ let latest;
168
+ let timer;
169
+ let resolveDone;
170
+ const done = new Promise((resolve) => {
171
+ resolveDone = resolve;
172
+ });
173
+ const stop = () => {
174
+ if (stopped) return;
175
+ stopped = true;
176
+ if (timer) clearTimeout(timer);
177
+ resolveDone?.();
178
+ };
179
+ const ping = async (context) => {
180
+ if (stopped || options.signal?.aborted) {
181
+ throw new Error("TEE attestation heartbeat is stopped");
182
+ }
183
+ const nextSequence = sequence + 1;
184
+ const boundContext = context ?? `${sessionId}:${nextSequence}`;
185
+ const challenge = createTeeAttestationChallenge(boundContext);
186
+ const response = await getTeeAttestation.call(sandbox, {
187
+ attestationNonce: challenge.nonce
188
+ });
189
+ const attestation = response?.attestation;
190
+ if (!attestation) {
191
+ throw new Error("TEE attestation heartbeat returned no evidence");
192
+ }
193
+ const verification = await (0, import_tcloud_attestation.verifyAttestationAsync)(attestation, {
194
+ ...policy,
195
+ expectedNonce: challenge.nonce
196
+ });
197
+ if (!verification.valid) {
198
+ throw new Error(`TEE attestation heartbeat verification failed: ${verification.errors.join("; ")}`);
199
+ }
200
+ const sample = {
201
+ sequence: nextSequence,
202
+ nonce: challenge.nonce,
203
+ context: boundContext,
204
+ attestation,
205
+ verification,
206
+ checkedAt: /* @__PURE__ */ new Date()
207
+ };
208
+ sequence = nextSequence;
209
+ latest = sample;
210
+ options.onSuccess?.(sample);
211
+ return sample;
212
+ };
213
+ const schedule = () => {
214
+ if (stopped || options.signal?.aborted) {
215
+ stop();
216
+ return;
217
+ }
218
+ timer = setTimeout(() => {
219
+ void loop();
220
+ }, intervalMs);
221
+ };
222
+ const loop = async () => {
223
+ try {
224
+ await ping();
225
+ schedule();
226
+ } catch (error) {
227
+ failures += 1;
228
+ options.onFailure?.(error);
229
+ if (options.continueOnFailure) {
230
+ schedule();
231
+ } else {
232
+ stop();
233
+ }
234
+ }
235
+ };
236
+ options.signal?.addEventListener("abort", stop, { once: true });
237
+ if (options.immediate === false) {
238
+ schedule();
239
+ } else {
240
+ void loop();
241
+ }
242
+ return {
243
+ stop,
244
+ ping,
245
+ get stopped() {
246
+ return stopped;
247
+ },
248
+ get failures() {
249
+ return failures;
250
+ },
251
+ get latest() {
252
+ return latest;
253
+ },
254
+ done
255
+ };
256
+ }
257
+ function buildSandboxCreateOptions(options) {
258
+ if (!options.tee && (options.sealed || options.attestationNonce || options.verify)) {
259
+ throw new Error("TEE options require a tee value");
260
+ }
261
+ const shouldGenerateNonce = options.attestationNonce === "auto" || shouldVerifyAttestation(options) && !options.attestationNonce;
262
+ const attestationNonce = shouldGenerateNonce ? generateAttestationNonce() : options.attestationNonce;
263
+ if (attestationNonce) {
264
+ validateAttestationNonce(attestationNonce);
265
+ }
266
+ return {
267
+ name: options.name,
268
+ environment: options.environment ?? options.image,
269
+ sshEnabled: options.ssh || void 0,
270
+ git: options.gitUrl ? {
271
+ url: options.gitUrl,
272
+ ref: options.gitRef
273
+ } : void 0,
274
+ resources: options.cpu || options.memoryMb || options.diskGb ? {
275
+ cpuCores: options.cpu,
276
+ memoryMB: options.memoryMb,
277
+ diskGB: options.diskGb
278
+ } : void 0,
279
+ backend: options.backend ? { type: options.backend } : void 0,
280
+ confidential: options.tee ? {
281
+ tee: options.tee,
282
+ sealed: options.sealed || void 0,
283
+ attestationNonce,
284
+ attestationRefresh: Boolean(attestationNonce)
285
+ } : void 0
286
+ };
287
+ }
288
+ function shouldVerifyAttestation(options) {
289
+ return Boolean(options.tee || options.verify);
290
+ }
291
+ function buildAttestationPolicy(options) {
292
+ if (!options.tee || options.tee === "any") return options.attestationPolicy ?? {};
293
+ const requestedTypes = acceptedAttestationTypesForTee(options.tee);
294
+ const acceptedTeeTypes = options.attestationPolicy?.acceptedTeeTypes;
295
+ if (acceptedTeeTypes?.length && !acceptedTeeTypes.some((type) => requestedTypes.includes(type))) {
296
+ throw new Error(
297
+ `TEE attestation policy does not accept requested TEE type ${options.tee}`
298
+ );
299
+ }
300
+ return {
301
+ ...options.attestationPolicy,
302
+ acceptedTeeTypes: acceptedTeeTypes?.length ? acceptedTeeTypes.filter((type) => requestedTypes.includes(type)) : requestedTypes
303
+ };
304
+ }
305
+ function acceptedAttestationTypesForTee(tee) {
306
+ switch (tee) {
307
+ case "phala-dstack":
308
+ return ["tdx", "phala-dstack"];
309
+ case "gcp":
310
+ return ["tdx", "sev-snp", "gcp"];
311
+ case "azure":
312
+ return ["sev-snp", "azure"];
313
+ default:
314
+ return [(0, import_tcloud_attestation.normalizeTeeType)(tee)];
315
+ }
316
+ }
317
+ function validateAttestationNonce(value) {
318
+ const normalized = value.trim().toLowerCase().replace(/^0x/, "");
319
+ if (!/^[0-9a-f]+$/.test(normalized)) {
320
+ throw new Error("attestation nonce must be hex");
321
+ }
322
+ if (normalized.length % 2 !== 0) {
323
+ throw new Error("attestation nonce must have even hex length");
324
+ }
325
+ const bytes = normalized.length / 2;
326
+ if (bytes < 32 || bytes > 64) {
327
+ throw new Error(`attestation nonce must be 32-64 bytes, got ${bytes}`);
328
+ }
329
+ }
330
+ function generateAttestationNonce(bytes = 32) {
331
+ return Array.from((0, import_node_crypto.randomBytes)(bytes)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
332
+ }
333
+ function attestationFromMetadata(metadata) {
334
+ const raw = metadata?.teeAttestationJson;
335
+ if (typeof raw !== "string" || raw.trim() === "") return void 0;
336
+ try {
337
+ return JSON.parse(raw);
338
+ } catch {
339
+ return void 0;
340
+ }
341
+ }
342
+ // Annotate the CommonJS export names for ESM import in node:
343
+ 0 && (module.exports = {
344
+ TCloudSandbox,
345
+ buildSandboxCreateOptions,
346
+ createTeeAttestationChallenge,
347
+ generateAttestationNonce,
348
+ shouldVerifyAttestation,
349
+ startTeeAttestationHeartbeat
350
+ });