@tangle-network/tcloud 0.4.2 → 0.4.3
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/attestation.cjs +46 -0
- package/dist/attestation.d.cts +1 -0
- package/dist/attestation.d.ts +1 -0
- package/dist/attestation.js +23 -0
- package/dist/{chunk-A7AEPV2G.js → chunk-4ZUVGKVH.js} +1 -1
- package/dist/chunk-DBIT227N.js +324 -0
- package/dist/{chunk-B22AH4JH.js → chunk-M5K3EFNP.js} +87 -0
- package/dist/{chunk-577KIKFA.js → chunk-MB4VK4MM.js} +38 -3
- package/dist/cli.cjs +370 -1
- package/dist/cli.js +73 -4
- package/dist/{client-D7_hFedn.d.ts → client-DkQugNHH.d.cts} +67 -1
- package/dist/{client-D7_hFedn.d.cts → client-DkQugNHH.d.ts} +67 -1
- package/dist/index.cjs +444 -2
- package/dist/index.d.cts +18 -3
- package/dist/index.d.ts +18 -3
- package/dist/index.js +33 -5
- package/dist/instance.cjs +87 -0
- package/dist/instance.d.cts +1 -1
- package/dist/instance.d.ts +1 -1
- package/dist/instance.js +1 -1
- package/dist/sandbox.cjs +350 -0
- package/dist/sandbox.d.cts +90 -0
- package/dist/sandbox.d.ts +90 -0
- package/dist/sandbox.js +16 -0
- package/dist/shielded.cjs +87 -0
- package/dist/shielded.d.cts +1 -1
- package/dist/shielded.d.ts +1 -1
- package/dist/shielded.js +2 -2
- package/package.json +32 -10
|
@@ -0,0 +1,46 @@
|
|
|
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/attestation.ts
|
|
21
|
+
var attestation_exports = {};
|
|
22
|
+
__export(attestation_exports, {
|
|
23
|
+
assertAttestation: () => import_tcloud_attestation.assertAttestation,
|
|
24
|
+
createSevSnpHardwareVerifier: () => import_tcloud_attestation.createSevSnpHardwareVerifier,
|
|
25
|
+
createTdxHardwareVerifier: () => import_tcloud_attestation.createTdxHardwareVerifier,
|
|
26
|
+
normalizeTeeType: () => import_tcloud_attestation.normalizeTeeType,
|
|
27
|
+
parseAttestation: () => import_tcloud_attestation.parseAttestation,
|
|
28
|
+
parseSevSnpReport: () => import_tcloud_attestation.parseSevSnpReport,
|
|
29
|
+
toHex: () => import_tcloud_attestation.toHex,
|
|
30
|
+
verifyAttestation: () => import_tcloud_attestation.verifyAttestation,
|
|
31
|
+
verifyAttestationAsync: () => import_tcloud_attestation.verifyAttestationAsync
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(attestation_exports);
|
|
34
|
+
var import_tcloud_attestation = require("@tangle-network/tcloud-attestation");
|
|
35
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
36
|
+
0 && (module.exports = {
|
|
37
|
+
assertAttestation,
|
|
38
|
+
createSevSnpHardwareVerifier,
|
|
39
|
+
createTdxHardwareVerifier,
|
|
40
|
+
normalizeTeeType,
|
|
41
|
+
parseAttestation,
|
|
42
|
+
parseSevSnpReport,
|
|
43
|
+
toHex,
|
|
44
|
+
verifyAttestation,
|
|
45
|
+
verifyAttestationAsync
|
|
46
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
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';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
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';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// src/attestation.ts
|
|
2
|
+
import {
|
|
3
|
+
assertAttestation,
|
|
4
|
+
createSevSnpHardwareVerifier,
|
|
5
|
+
createTdxHardwareVerifier,
|
|
6
|
+
normalizeTeeType,
|
|
7
|
+
parseSevSnpReport,
|
|
8
|
+
parseAttestation,
|
|
9
|
+
toHex,
|
|
10
|
+
verifyAttestation,
|
|
11
|
+
verifyAttestationAsync
|
|
12
|
+
} from "@tangle-network/tcloud-attestation";
|
|
13
|
+
export {
|
|
14
|
+
assertAttestation,
|
|
15
|
+
createSevSnpHardwareVerifier,
|
|
16
|
+
createTdxHardwareVerifier,
|
|
17
|
+
normalizeTeeType,
|
|
18
|
+
parseAttestation,
|
|
19
|
+
parseSevSnpReport,
|
|
20
|
+
toHex,
|
|
21
|
+
verifyAttestation,
|
|
22
|
+
verifyAttestationAsync
|
|
23
|
+
};
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
// src/sandbox.ts
|
|
2
|
+
import { Sandbox } from "@tangle-network/sandbox";
|
|
3
|
+
import { createHash, randomBytes } from "crypto";
|
|
4
|
+
import {
|
|
5
|
+
normalizeTeeType,
|
|
6
|
+
verifyAttestationAsync
|
|
7
|
+
} from "@tangle-network/tcloud-attestation";
|
|
8
|
+
var DEFAULT_SANDBOX_URL = "https://sandbox.tangle.tools";
|
|
9
|
+
var TCloudSandbox = class {
|
|
10
|
+
client;
|
|
11
|
+
apiKey;
|
|
12
|
+
baseUrl;
|
|
13
|
+
timeoutMs;
|
|
14
|
+
constructor(config) {
|
|
15
|
+
this.apiKey = config.apiKey;
|
|
16
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_SANDBOX_URL).replace(/\/+$/, "");
|
|
17
|
+
this.timeoutMs = config.timeoutMs;
|
|
18
|
+
this.client = new Sandbox({
|
|
19
|
+
apiKey: this.apiKey,
|
|
20
|
+
baseUrl: this.baseUrl,
|
|
21
|
+
timeoutMs: config.timeoutMs
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
async create(options) {
|
|
25
|
+
const effectiveVerify = shouldVerifyAttestation(options);
|
|
26
|
+
const attestationPolicy = buildAttestationPolicy(options);
|
|
27
|
+
const createOptions = buildSandboxCreateOptions({
|
|
28
|
+
...options,
|
|
29
|
+
verify: effectiveVerify
|
|
30
|
+
});
|
|
31
|
+
const sandbox = await this.client.create(createOptions);
|
|
32
|
+
this.attachTeeApiFallbacks(sandbox);
|
|
33
|
+
let attestation = attestationFromMetadata(sandbox.metadata);
|
|
34
|
+
if ((effectiveVerify || createOptions.confidential?.attestationNonce) && !attestation) {
|
|
35
|
+
const getTeeAttestation = sandbox.getTeeAttestation;
|
|
36
|
+
if (typeof getTeeAttestation !== "function") {
|
|
37
|
+
throw new Error("Installed @tangle-network/sandbox does not expose TEE attestation fetching");
|
|
38
|
+
}
|
|
39
|
+
attestation = (await getTeeAttestation.call(
|
|
40
|
+
sandbox,
|
|
41
|
+
createOptions.confidential?.attestationNonce ? { attestationNonce: createOptions.confidential.attestationNonce } : void 0
|
|
42
|
+
)).attestation;
|
|
43
|
+
}
|
|
44
|
+
if (effectiveVerify && !attestation) {
|
|
45
|
+
throw new Error("TEE attestation verification requested but no evidence was returned");
|
|
46
|
+
}
|
|
47
|
+
const verification = effectiveVerify ? await verifyAttestationAsync(attestation, {
|
|
48
|
+
...attestationPolicy,
|
|
49
|
+
expectedNonce: createOptions.confidential?.attestationNonce
|
|
50
|
+
}) : void 0;
|
|
51
|
+
if (verification && !verification.valid) {
|
|
52
|
+
throw new Error(`TEE attestation verification failed: ${verification.errors.join("; ")}`);
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
sandbox,
|
|
56
|
+
attestation,
|
|
57
|
+
verification,
|
|
58
|
+
attestationNonce: createOptions.confidential?.attestationNonce,
|
|
59
|
+
attestationStatus: {
|
|
60
|
+
requested: Boolean(options.tee),
|
|
61
|
+
evidenceReturned: Boolean(attestation),
|
|
62
|
+
verified: Boolean(verification?.valid),
|
|
63
|
+
nonceBound: Boolean(createOptions.confidential?.attestationNonce && verification?.valid),
|
|
64
|
+
errors: verification?.errors ?? []
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
attachTeeApiFallbacks(sandbox) {
|
|
69
|
+
if (typeof sandbox !== "object" || sandbox === null) return;
|
|
70
|
+
const target = sandbox;
|
|
71
|
+
if (typeof target.id !== "string" || target.id.length === 0) return;
|
|
72
|
+
if (typeof target.getTeeAttestation !== "function") {
|
|
73
|
+
target.getTeeAttestation = (options) => this.fetchTeeAttestation(target.id, options?.attestationNonce);
|
|
74
|
+
}
|
|
75
|
+
if (typeof target.getTeePublicKey !== "function") {
|
|
76
|
+
target.getTeePublicKey = () => this.fetchTeePublicKey(target.id);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async fetchTeeAttestation(sandboxId, attestationNonce) {
|
|
80
|
+
const response = await this.fetchSandboxApi(
|
|
81
|
+
`/v1/sandboxes/${encodeURIComponent(sandboxId)}/tee/attestation`,
|
|
82
|
+
{
|
|
83
|
+
method: attestationNonce ? "POST" : "GET",
|
|
84
|
+
body: attestationNonce ? JSON.stringify({ attestation_nonce: attestationNonce }) : void 0
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
const data = await response.json();
|
|
88
|
+
if (attestationNonce && !data.attestationNonce) {
|
|
89
|
+
data.attestationNonce = attestationNonce;
|
|
90
|
+
}
|
|
91
|
+
return data;
|
|
92
|
+
}
|
|
93
|
+
async fetchTeePublicKey(sandboxId) {
|
|
94
|
+
const response = await this.fetchSandboxApi(
|
|
95
|
+
`/v1/sandboxes/${encodeURIComponent(sandboxId)}/tee/public-key`,
|
|
96
|
+
{ method: "GET" }
|
|
97
|
+
);
|
|
98
|
+
return response.json();
|
|
99
|
+
}
|
|
100
|
+
async fetchSandboxApi(path, options) {
|
|
101
|
+
const headers = new Headers(options.headers);
|
|
102
|
+
headers.set("Authorization", `Bearer ${this.apiKey}`);
|
|
103
|
+
if (options.body && !headers.has("Content-Type")) {
|
|
104
|
+
headers.set("Content-Type", "application/json");
|
|
105
|
+
}
|
|
106
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
107
|
+
...options,
|
|
108
|
+
headers,
|
|
109
|
+
signal: options.signal ?? (this.timeoutMs ? AbortSignal.timeout(this.timeoutMs) : void 0)
|
|
110
|
+
});
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
const body = await response.text();
|
|
113
|
+
throw new Error(`Sandbox API ${path} failed with HTTP ${response.status}: ${body}`);
|
|
114
|
+
}
|
|
115
|
+
return response;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
function createTeeAttestationChallenge(context) {
|
|
119
|
+
const randomHex = generateAttestationNonce(32);
|
|
120
|
+
if (context == null) {
|
|
121
|
+
return { nonce: randomHex, randomHex };
|
|
122
|
+
}
|
|
123
|
+
const contextHashHex = createHash("sha256").update(typeof context === "string" ? Buffer.from(context) : Buffer.from(context)).digest("hex");
|
|
124
|
+
return {
|
|
125
|
+
nonce: `${randomHex}${contextHashHex}`,
|
|
126
|
+
randomHex,
|
|
127
|
+
contextHashHex
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function startTeeAttestationHeartbeat(sandbox, options = {}) {
|
|
131
|
+
const getTeeAttestation = sandbox?.getTeeAttestation;
|
|
132
|
+
if (typeof getTeeAttestation !== "function") {
|
|
133
|
+
throw new Error("Sandbox does not expose TEE attestation fetching");
|
|
134
|
+
}
|
|
135
|
+
const intervalMs = options.intervalMs ?? 6e4;
|
|
136
|
+
const sessionId = options.sessionId ?? generateAttestationNonce(16);
|
|
137
|
+
const policy = options.tee ? buildAttestationPolicy({ tee: options.tee, attestationPolicy: options.attestationPolicy }) : options.attestationPolicy ?? {};
|
|
138
|
+
let stopped = false;
|
|
139
|
+
let failures = 0;
|
|
140
|
+
let sequence = 0;
|
|
141
|
+
let latest;
|
|
142
|
+
let timer;
|
|
143
|
+
let resolveDone;
|
|
144
|
+
const done = new Promise((resolve) => {
|
|
145
|
+
resolveDone = resolve;
|
|
146
|
+
});
|
|
147
|
+
const stop = () => {
|
|
148
|
+
if (stopped) return;
|
|
149
|
+
stopped = true;
|
|
150
|
+
if (timer) clearTimeout(timer);
|
|
151
|
+
resolveDone?.();
|
|
152
|
+
};
|
|
153
|
+
const ping = async (context) => {
|
|
154
|
+
if (stopped || options.signal?.aborted) {
|
|
155
|
+
throw new Error("TEE attestation heartbeat is stopped");
|
|
156
|
+
}
|
|
157
|
+
const nextSequence = sequence + 1;
|
|
158
|
+
const boundContext = context ?? `${sessionId}:${nextSequence}`;
|
|
159
|
+
const challenge = createTeeAttestationChallenge(boundContext);
|
|
160
|
+
const response = await getTeeAttestation.call(sandbox, {
|
|
161
|
+
attestationNonce: challenge.nonce
|
|
162
|
+
});
|
|
163
|
+
const attestation = response?.attestation;
|
|
164
|
+
if (!attestation) {
|
|
165
|
+
throw new Error("TEE attestation heartbeat returned no evidence");
|
|
166
|
+
}
|
|
167
|
+
const verification = await verifyAttestationAsync(attestation, {
|
|
168
|
+
...policy,
|
|
169
|
+
expectedNonce: challenge.nonce
|
|
170
|
+
});
|
|
171
|
+
if (!verification.valid) {
|
|
172
|
+
throw new Error(`TEE attestation heartbeat verification failed: ${verification.errors.join("; ")}`);
|
|
173
|
+
}
|
|
174
|
+
const sample = {
|
|
175
|
+
sequence: nextSequence,
|
|
176
|
+
nonce: challenge.nonce,
|
|
177
|
+
context: boundContext,
|
|
178
|
+
attestation,
|
|
179
|
+
verification,
|
|
180
|
+
checkedAt: /* @__PURE__ */ new Date()
|
|
181
|
+
};
|
|
182
|
+
sequence = nextSequence;
|
|
183
|
+
latest = sample;
|
|
184
|
+
options.onSuccess?.(sample);
|
|
185
|
+
return sample;
|
|
186
|
+
};
|
|
187
|
+
const schedule = () => {
|
|
188
|
+
if (stopped || options.signal?.aborted) {
|
|
189
|
+
stop();
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
timer = setTimeout(() => {
|
|
193
|
+
void loop();
|
|
194
|
+
}, intervalMs);
|
|
195
|
+
};
|
|
196
|
+
const loop = async () => {
|
|
197
|
+
try {
|
|
198
|
+
await ping();
|
|
199
|
+
schedule();
|
|
200
|
+
} catch (error) {
|
|
201
|
+
failures += 1;
|
|
202
|
+
options.onFailure?.(error);
|
|
203
|
+
if (options.continueOnFailure) {
|
|
204
|
+
schedule();
|
|
205
|
+
} else {
|
|
206
|
+
stop();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
options.signal?.addEventListener("abort", stop, { once: true });
|
|
211
|
+
if (options.immediate === false) {
|
|
212
|
+
schedule();
|
|
213
|
+
} else {
|
|
214
|
+
void loop();
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
stop,
|
|
218
|
+
ping,
|
|
219
|
+
get stopped() {
|
|
220
|
+
return stopped;
|
|
221
|
+
},
|
|
222
|
+
get failures() {
|
|
223
|
+
return failures;
|
|
224
|
+
},
|
|
225
|
+
get latest() {
|
|
226
|
+
return latest;
|
|
227
|
+
},
|
|
228
|
+
done
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function buildSandboxCreateOptions(options) {
|
|
232
|
+
if (!options.tee && (options.sealed || options.attestationNonce || options.verify)) {
|
|
233
|
+
throw new Error("TEE options require a tee value");
|
|
234
|
+
}
|
|
235
|
+
const shouldGenerateNonce = options.attestationNonce === "auto" || shouldVerifyAttestation(options) && !options.attestationNonce;
|
|
236
|
+
const attestationNonce = shouldGenerateNonce ? generateAttestationNonce() : options.attestationNonce;
|
|
237
|
+
if (attestationNonce) {
|
|
238
|
+
validateAttestationNonce(attestationNonce);
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
name: options.name,
|
|
242
|
+
environment: options.environment ?? options.image,
|
|
243
|
+
sshEnabled: options.ssh || void 0,
|
|
244
|
+
git: options.gitUrl ? {
|
|
245
|
+
url: options.gitUrl,
|
|
246
|
+
ref: options.gitRef
|
|
247
|
+
} : void 0,
|
|
248
|
+
resources: options.cpu || options.memoryMb || options.diskGb ? {
|
|
249
|
+
cpuCores: options.cpu,
|
|
250
|
+
memoryMB: options.memoryMb,
|
|
251
|
+
diskGB: options.diskGb
|
|
252
|
+
} : void 0,
|
|
253
|
+
backend: options.backend ? { type: options.backend } : void 0,
|
|
254
|
+
confidential: options.tee ? {
|
|
255
|
+
tee: options.tee,
|
|
256
|
+
sealed: options.sealed || void 0,
|
|
257
|
+
attestationNonce,
|
|
258
|
+
attestationRefresh: Boolean(attestationNonce)
|
|
259
|
+
} : void 0
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function shouldVerifyAttestation(options) {
|
|
263
|
+
return Boolean(options.tee || options.verify);
|
|
264
|
+
}
|
|
265
|
+
function buildAttestationPolicy(options) {
|
|
266
|
+
if (!options.tee || options.tee === "any") return options.attestationPolicy ?? {};
|
|
267
|
+
const requestedTypes = acceptedAttestationTypesForTee(options.tee);
|
|
268
|
+
const acceptedTeeTypes = options.attestationPolicy?.acceptedTeeTypes;
|
|
269
|
+
if (acceptedTeeTypes?.length && !acceptedTeeTypes.some((type) => requestedTypes.includes(type))) {
|
|
270
|
+
throw new Error(
|
|
271
|
+
`TEE attestation policy does not accept requested TEE type ${options.tee}`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
...options.attestationPolicy,
|
|
276
|
+
acceptedTeeTypes: acceptedTeeTypes?.length ? acceptedTeeTypes.filter((type) => requestedTypes.includes(type)) : requestedTypes
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
function acceptedAttestationTypesForTee(tee) {
|
|
280
|
+
switch (tee) {
|
|
281
|
+
case "phala-dstack":
|
|
282
|
+
return ["tdx", "phala-dstack"];
|
|
283
|
+
case "gcp":
|
|
284
|
+
return ["tdx", "sev-snp", "gcp"];
|
|
285
|
+
case "azure":
|
|
286
|
+
return ["sev-snp", "azure"];
|
|
287
|
+
default:
|
|
288
|
+
return [normalizeTeeType(tee)];
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
function validateAttestationNonce(value) {
|
|
292
|
+
const normalized = value.trim().toLowerCase().replace(/^0x/, "");
|
|
293
|
+
if (!/^[0-9a-f]+$/.test(normalized)) {
|
|
294
|
+
throw new Error("attestation nonce must be hex");
|
|
295
|
+
}
|
|
296
|
+
if (normalized.length % 2 !== 0) {
|
|
297
|
+
throw new Error("attestation nonce must have even hex length");
|
|
298
|
+
}
|
|
299
|
+
const bytes = normalized.length / 2;
|
|
300
|
+
if (bytes < 32 || bytes > 64) {
|
|
301
|
+
throw new Error(`attestation nonce must be 32-64 bytes, got ${bytes}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function generateAttestationNonce(bytes = 32) {
|
|
305
|
+
return Array.from(randomBytes(bytes)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
306
|
+
}
|
|
307
|
+
function attestationFromMetadata(metadata) {
|
|
308
|
+
const raw = metadata?.teeAttestationJson;
|
|
309
|
+
if (typeof raw !== "string" || raw.trim() === "") return void 0;
|
|
310
|
+
try {
|
|
311
|
+
return JSON.parse(raw);
|
|
312
|
+
} catch {
|
|
313
|
+
return void 0;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export {
|
|
318
|
+
TCloudSandbox,
|
|
319
|
+
createTeeAttestationChallenge,
|
|
320
|
+
startTeeAttestationHeartbeat,
|
|
321
|
+
buildSandboxCreateOptions,
|
|
322
|
+
shouldVerifyAttestation,
|
|
323
|
+
generateAttestationNonce
|
|
324
|
+
};
|
|
@@ -10,6 +10,8 @@ var PrivateRouter = class {
|
|
|
10
10
|
usage = /* @__PURE__ */ new Map();
|
|
11
11
|
currentIndex = 0;
|
|
12
12
|
totalRequests = 0;
|
|
13
|
+
/** Slug of the most-recently-selected operator. Populated on each selectOperator() hit. */
|
|
14
|
+
_lastSelectedSlug = null;
|
|
13
15
|
constructor(config = {}) {
|
|
14
16
|
this.config = {
|
|
15
17
|
strategy: config.strategy || "round-robin",
|
|
@@ -155,6 +157,11 @@ var PrivateRouter = class {
|
|
|
155
157
|
requestCount: (existing?.requestCount || 0) + 1,
|
|
156
158
|
lastUsedAt: Date.now()
|
|
157
159
|
});
|
|
160
|
+
this._lastSelectedSlug = op.slug;
|
|
161
|
+
}
|
|
162
|
+
/** Slug of the most-recently-selected operator (null before the first call). */
|
|
163
|
+
get lastSelectedSlug() {
|
|
164
|
+
return this._lastSelectedSlug;
|
|
158
165
|
}
|
|
159
166
|
getLastUsedOperator() {
|
|
160
167
|
let latest = null;
|
|
@@ -212,6 +219,7 @@ var PrivateRouter = class {
|
|
|
212
219
|
};
|
|
213
220
|
|
|
214
221
|
// src/client.ts
|
|
222
|
+
var ROTATING_MARKER = "__tcloudRotating";
|
|
215
223
|
var DEFAULT_BASE_URL = "https://router.tangle.tools/v1";
|
|
216
224
|
var SDK_VERSION = "0.4.0";
|
|
217
225
|
async function proxiedFetch(privacy, url, init, streaming) {
|
|
@@ -309,6 +317,54 @@ var TCloudClient = class _TCloudClient {
|
|
|
309
317
|
const baseURL = opts.url.replace(/\/+$/, "") + "/v1";
|
|
310
318
|
return new _TCloudClient({ ...opts.config ?? {}, apiKey: opts.bearer, baseURL });
|
|
311
319
|
}
|
|
320
|
+
/**
|
|
321
|
+
* Build a client that rotates which operator serves each call. Mirrors
|
|
322
|
+
* {@link TCloudClient.shielded} in shape: returns a standard `TCloudClient`
|
|
323
|
+
* that behaves identically for the OpenAI-compatible surface but
|
|
324
|
+
* dispatches each chat/completions/embeddings request through a
|
|
325
|
+
* {@link PrivateRouter} — different operator per call per the chosen
|
|
326
|
+
* strategy.
|
|
327
|
+
*
|
|
328
|
+
* ```ts
|
|
329
|
+
* const tcloud = TCloudClient.rotating({
|
|
330
|
+
* apiKey: process.env.TCLOUD_API_KEY,
|
|
331
|
+
* routing: { strategy: 'min-exposure' },
|
|
332
|
+
* })
|
|
333
|
+
* await tcloud.ask('hello')
|
|
334
|
+
* tcloud.getRotationStats() // { callsByOperator: { ... }, currentOperator: '…' }
|
|
335
|
+
* ```
|
|
336
|
+
*
|
|
337
|
+
* Rotation is meaningful only for stateless calls. Sandbox-harness
|
|
338
|
+
* sessions bind to a single operator for the lifetime of the session;
|
|
339
|
+
* `rotating()` clients refuse to dispatch them — see {@link bridge}.
|
|
340
|
+
*/
|
|
341
|
+
static rotating(config = {}) {
|
|
342
|
+
const routing = config.routing ?? {};
|
|
343
|
+
const strategy = routing.strategy ?? "min-exposure";
|
|
344
|
+
const { routing: _omit, ...base } = config;
|
|
345
|
+
const client = new _TCloudClient(base);
|
|
346
|
+
const router = new PrivateRouter({
|
|
347
|
+
strategy,
|
|
348
|
+
minOperators: routing.minOperators ?? 1,
|
|
349
|
+
maxRequestsPerOperator: routing.maxRequestsPerOperator,
|
|
350
|
+
excludeOperators: routing.excludeOperators,
|
|
351
|
+
preferRegions: routing.preferRegions
|
|
352
|
+
});
|
|
353
|
+
if (routing.pool && routing.pool.length > 0) {
|
|
354
|
+
router.setOperators(routing.pool);
|
|
355
|
+
client._cachedOperators = routing.pool;
|
|
356
|
+
client._operatorsCachedAt = Date.now();
|
|
357
|
+
}
|
|
358
|
+
;
|
|
359
|
+
client.privateRouter = router;
|
|
360
|
+
Object.defineProperty(client, ROTATING_MARKER, {
|
|
361
|
+
value: true,
|
|
362
|
+
enumerable: false,
|
|
363
|
+
configurable: false,
|
|
364
|
+
writable: false
|
|
365
|
+
});
|
|
366
|
+
return client;
|
|
367
|
+
}
|
|
312
368
|
constructor(config = {}) {
|
|
313
369
|
this.baseURL = (config.baseURL || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
314
370
|
this.platformURL = (config.platformURL || DEFAULT_PLATFORM_URL).replace(/\/$/, "");
|
|
@@ -624,10 +680,38 @@ var TCloudClient = class _TCloudClient {
|
|
|
624
680
|
*
|
|
625
681
|
* Sessions persist across process restarts — use the same `resume` id
|
|
626
682
|
* to land on the same CLI conversation (context intact, no replay tax).
|
|
683
|
+
*
|
|
684
|
+
* Guard: clients built via {@link TCloudClient.rotating} cannot dispatch
|
|
685
|
+
* sandbox-harness sessions (rotation rotates per call; a sandbox session
|
|
686
|
+
* binds to one operator). Attempting `bridge({ harness: 'sandbox' })` on
|
|
687
|
+
* a rotating client throws.
|
|
627
688
|
*/
|
|
628
689
|
bridge(cfg) {
|
|
690
|
+
if (cfg.harness === "sandbox" && this[ROTATING_MARKER] === true) {
|
|
691
|
+
throw new Error(
|
|
692
|
+
"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."
|
|
693
|
+
);
|
|
694
|
+
}
|
|
629
695
|
return new BridgeSession(this, cfg);
|
|
630
696
|
}
|
|
697
|
+
/**
|
|
698
|
+
* Rotation stats — populated only on clients created via
|
|
699
|
+
* {@link TCloudClient.rotating}. Non-rotating clients return an empty
|
|
700
|
+
* counter and `currentOperator: null`.
|
|
701
|
+
*/
|
|
702
|
+
getRotationStats() {
|
|
703
|
+
if (!this.privateRouter) {
|
|
704
|
+
return { callsByOperator: {}, currentOperator: null };
|
|
705
|
+
}
|
|
706
|
+
const callsByOperator = {};
|
|
707
|
+
for (const row of this.privateRouter.getStats().operatorBreakdown) {
|
|
708
|
+
callsByOperator[row.slug] = row.requests;
|
|
709
|
+
}
|
|
710
|
+
return {
|
|
711
|
+
callsByOperator,
|
|
712
|
+
currentOperator: this.privateRouter.lastSelectedSlug
|
|
713
|
+
};
|
|
714
|
+
}
|
|
631
715
|
/** Convenience: send a single message and get the text response */
|
|
632
716
|
async ask(message, modelOrOptions) {
|
|
633
717
|
const options = typeof modelOrOptions === "string" ? { model: modelOrOptions } : modelOrOptions;
|
|
@@ -1129,6 +1213,8 @@ var BridgeSession = class _BridgeSession {
|
|
|
1129
1213
|
this.client = client;
|
|
1130
1214
|
this.cfg = cfg;
|
|
1131
1215
|
}
|
|
1216
|
+
client;
|
|
1217
|
+
cfg;
|
|
1132
1218
|
/** Full chat completion (non-streaming). */
|
|
1133
1219
|
async chat(options) {
|
|
1134
1220
|
return this.client.chat({ ...options, bridge: this.cfg });
|
|
@@ -1198,6 +1284,7 @@ var TCloudError = class extends Error {
|
|
|
1198
1284
|
this.status = status;
|
|
1199
1285
|
this.name = "TCloudError";
|
|
1200
1286
|
}
|
|
1287
|
+
status;
|
|
1201
1288
|
};
|
|
1202
1289
|
|
|
1203
1290
|
export {
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createShieldedClient,
|
|
3
3
|
generateWallet
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-4ZUVGKVH.js";
|
|
5
5
|
import {
|
|
6
6
|
TCloudClient
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-M5K3EFNP.js";
|
|
8
8
|
|
|
9
9
|
// src/index.ts
|
|
10
|
+
import {
|
|
11
|
+
assertAttestation,
|
|
12
|
+
createSevSnpHardwareVerifier,
|
|
13
|
+
createTdxHardwareVerifier,
|
|
14
|
+
normalizeTeeType,
|
|
15
|
+
parseAttestation,
|
|
16
|
+
parseSevSnpReport,
|
|
17
|
+
toHex,
|
|
18
|
+
verifyAttestation,
|
|
19
|
+
verifyAttestationAsync
|
|
20
|
+
} from "@tangle-network/tcloud-attestation";
|
|
10
21
|
var TCloud = class _TCloud extends TCloudClient {
|
|
11
22
|
constructor(config) {
|
|
12
23
|
super(config);
|
|
@@ -35,6 +46,21 @@ var TCloud = class _TCloud extends TCloudClient {
|
|
|
35
46
|
static shielded(config) {
|
|
36
47
|
return createShieldedClient(config);
|
|
37
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Create a client that rotates operators per call.
|
|
51
|
+
* See {@link TCloudClient.rotating} for semantics.
|
|
52
|
+
*
|
|
53
|
+
* ```ts
|
|
54
|
+
* const client = TCloud.rotating({
|
|
55
|
+
* apiKey: process.env.TCLOUD_API_KEY,
|
|
56
|
+
* routing: { strategy: 'min-exposure' },
|
|
57
|
+
* })
|
|
58
|
+
* const stats = client.getRotationStats()
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
static rotating(config) {
|
|
62
|
+
return TCloudClient.rotating(config);
|
|
63
|
+
}
|
|
38
64
|
/**
|
|
39
65
|
* Generate a new ephemeral wallet (without creating a client).
|
|
40
66
|
*
|
|
@@ -47,5 +73,14 @@ var TCloud = class _TCloud extends TCloudClient {
|
|
|
47
73
|
};
|
|
48
74
|
|
|
49
75
|
export {
|
|
50
|
-
TCloud
|
|
76
|
+
TCloud,
|
|
77
|
+
assertAttestation,
|
|
78
|
+
createSevSnpHardwareVerifier,
|
|
79
|
+
createTdxHardwareVerifier,
|
|
80
|
+
normalizeTeeType,
|
|
81
|
+
parseAttestation,
|
|
82
|
+
parseSevSnpReport,
|
|
83
|
+
toHex,
|
|
84
|
+
verifyAttestation,
|
|
85
|
+
verifyAttestationAsync
|
|
51
86
|
};
|