@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
|
@@ -590,6 +590,8 @@ declare class PrivateRouter {
|
|
|
590
590
|
private usage;
|
|
591
591
|
private currentIndex;
|
|
592
592
|
private totalRequests;
|
|
593
|
+
/** Slug of the most-recently-selected operator. Populated on each selectOperator() hit. */
|
|
594
|
+
private _lastSelectedSlug;
|
|
593
595
|
constructor(config?: Partial<PrivateRouterConfig>);
|
|
594
596
|
/** Set the available operator pool */
|
|
595
597
|
setOperators(operators: OperatorInfo[]): void;
|
|
@@ -614,6 +616,8 @@ declare class PrivateRouter {
|
|
|
614
616
|
private minExposure;
|
|
615
617
|
private latencyAware;
|
|
616
618
|
private recordUsage;
|
|
619
|
+
/** Slug of the most-recently-selected operator (null before the first call). */
|
|
620
|
+
get lastSelectedSlug(): string | null;
|
|
617
621
|
private getLastUsedOperator;
|
|
618
622
|
private peekNextOperator;
|
|
619
623
|
}
|
|
@@ -623,6 +627,35 @@ declare class PrivateRouter {
|
|
|
623
627
|
* Shared between CLI and SDK.
|
|
624
628
|
*/
|
|
625
629
|
|
|
630
|
+
/** Rotation knobs for {@link TCloudClient.rotating}. */
|
|
631
|
+
interface RotatingRoutingConfig {
|
|
632
|
+
/** Router strategy. Defaults to `'min-exposure'`. */
|
|
633
|
+
strategy?: Extract<RoutingStrategy, 'min-exposure' | 'round-robin' | 'random'>;
|
|
634
|
+
/**
|
|
635
|
+
* Pre-seed the operator pool. When omitted the client fetches
|
|
636
|
+
* `/api/operators` on the first call (TTL-cached).
|
|
637
|
+
*/
|
|
638
|
+
pool?: OperatorInfo[];
|
|
639
|
+
/** Minimum distinct operators required before routing proceeds. */
|
|
640
|
+
minOperators?: number;
|
|
641
|
+
/** Max requests per operator before forced rotation. */
|
|
642
|
+
maxRequestsPerOperator?: number;
|
|
643
|
+
/** Exclude specific operator slugs. */
|
|
644
|
+
excludeOperators?: string[];
|
|
645
|
+
/** Prefer specific regions (others kept as fallback). */
|
|
646
|
+
preferRegions?: string[];
|
|
647
|
+
}
|
|
648
|
+
/** Configuration accepted by {@link TCloudClient.rotating}. */
|
|
649
|
+
type RotatingClientConfig = Omit<TCloudConfig, 'routing'> & {
|
|
650
|
+
routing?: RotatingRoutingConfig;
|
|
651
|
+
};
|
|
652
|
+
/** Rotation stats surfaced by {@link TCloudClient.getRotationStats}. */
|
|
653
|
+
interface RotationStats {
|
|
654
|
+
/** Per-operator call counter. */
|
|
655
|
+
callsByOperator: Record<string, number>;
|
|
656
|
+
/** Slug of the most-recently-selected operator, if any. */
|
|
657
|
+
currentOperator: string | null;
|
|
658
|
+
}
|
|
626
659
|
declare class TCloudClient {
|
|
627
660
|
readonly baseURL: string;
|
|
628
661
|
readonly platformURL: string;
|
|
@@ -675,6 +708,28 @@ declare class TCloudClient {
|
|
|
675
708
|
/** Optional config passthrough (timeout, retry, etc). */
|
|
676
709
|
config?: Omit<TCloudConfig, 'apiKey' | 'baseURL'>;
|
|
677
710
|
}): TCloudClient;
|
|
711
|
+
/**
|
|
712
|
+
* Build a client that rotates which operator serves each call. Mirrors
|
|
713
|
+
* {@link TCloudClient.shielded} in shape: returns a standard `TCloudClient`
|
|
714
|
+
* that behaves identically for the OpenAI-compatible surface but
|
|
715
|
+
* dispatches each chat/completions/embeddings request through a
|
|
716
|
+
* {@link PrivateRouter} — different operator per call per the chosen
|
|
717
|
+
* strategy.
|
|
718
|
+
*
|
|
719
|
+
* ```ts
|
|
720
|
+
* const tcloud = TCloudClient.rotating({
|
|
721
|
+
* apiKey: process.env.TCLOUD_API_KEY,
|
|
722
|
+
* routing: { strategy: 'min-exposure' },
|
|
723
|
+
* })
|
|
724
|
+
* await tcloud.ask('hello')
|
|
725
|
+
* tcloud.getRotationStats() // { callsByOperator: { ... }, currentOperator: '…' }
|
|
726
|
+
* ```
|
|
727
|
+
*
|
|
728
|
+
* Rotation is meaningful only for stateless calls. Sandbox-harness
|
|
729
|
+
* sessions bind to a single operator for the lifetime of the session;
|
|
730
|
+
* `rotating()` clients refuse to dispatch them — see {@link bridge}.
|
|
731
|
+
*/
|
|
732
|
+
static rotating(config?: RotatingClientConfig): TCloudClient;
|
|
678
733
|
constructor(config?: TCloudConfig);
|
|
679
734
|
/** Set the SpendAuth signer for private mode */
|
|
680
735
|
setSpendAuthSigner(fn: () => Promise<SpendAuth>): void;
|
|
@@ -754,8 +809,19 @@ declare class TCloudClient {
|
|
|
754
809
|
*
|
|
755
810
|
* Sessions persist across process restarts — use the same `resume` id
|
|
756
811
|
* to land on the same CLI conversation (context intact, no replay tax).
|
|
812
|
+
*
|
|
813
|
+
* Guard: clients built via {@link TCloudClient.rotating} cannot dispatch
|
|
814
|
+
* sandbox-harness sessions (rotation rotates per call; a sandbox session
|
|
815
|
+
* binds to one operator). Attempting `bridge({ harness: 'sandbox' })` on
|
|
816
|
+
* a rotating client throws.
|
|
757
817
|
*/
|
|
758
818
|
bridge(cfg: BridgeOptions): BridgeSession;
|
|
819
|
+
/**
|
|
820
|
+
* Rotation stats — populated only on clients created via
|
|
821
|
+
* {@link TCloudClient.rotating}. Non-rotating clients return an empty
|
|
822
|
+
* counter and `currentOperator: null`.
|
|
823
|
+
*/
|
|
824
|
+
getRotationStats(): RotationStats;
|
|
759
825
|
/** Convenience: send a single message and get the text response */
|
|
760
826
|
ask(message: string, modelOrOptions?: string | Partial<ChatOptions>): Promise<string>;
|
|
761
827
|
/** Convenience: send a single message and get the full completion (with usage) */
|
|
@@ -1149,4 +1215,4 @@ declare class TCloudError extends Error {
|
|
|
1149
1215
|
constructor(status: number, message: string);
|
|
1150
1216
|
}
|
|
1151
1217
|
|
|
1152
|
-
export { type ApiKeyInfo as A, type BatchJobResponse as B, type ChatCompletion as C, type
|
|
1218
|
+
export { type ApiKeyInfo as A, type BatchJobResponse as B, type ChatCompletion as C, type RotatingRoutingConfig as D, type EmbeddingOptions as E, type FineTuningJob as F, type GatewayOptions as G, type RotationStats as H, type ImageGenerateOptions as I, type JobEvent as J, type RoutingConfig as K, type RoutingStrategy as L, type Model as M, type SpendAuth as N, type Operator as O, type PricingTier as P, type SpendingLimits as Q, type RotatingClientConfig as R, type ShieldedConfig as S, TCloudClient as T, TCloudError as U, type TierConfig as V, type TranscriptionResponse as W, type UpdateKeyOptions as X, type VideoGenerateOptions as Y, type VideoResponse as Z, type WatchJobOptions as _, type TCloudConfig as a, type AvatarGenerateRequest as b, type AvatarGenerateResponse as c, type AvatarJobStatus as d, type AvatarResult as e, type BatchRequest as f, type BridgeOptions as g, BridgeSession as h, type ChatCompletionChunk as i, type ChatMessage as j, type ChatOptions as k, type CompletionOptions as l, type CompletionResponse as m, type CreateKeyOptions as n, type CreatedKey as o, type CreditBalance as p, type EmbeddingResponse as q, type FineTuningJobOptions as r, type ImageResponse as s, type OperatorInfo as t, type PrivacyConfig as u, PrivateRouter as v, type PrivateRouterConfig as w, type RerankOptions as x, type RerankResponse as y, type RetryConfig as z };
|
package/dist/index.cjs
CHANGED
|
@@ -35,10 +35,23 @@ __export(index_exports, {
|
|
|
35
35
|
TCloud: () => TCloud,
|
|
36
36
|
TCloudClient: () => TCloudClient,
|
|
37
37
|
TCloudError: () => TCloudError,
|
|
38
|
+
TCloudSandbox: () => TCloudSandbox,
|
|
39
|
+
assertAttestation: () => import_tcloud_attestation2.assertAttestation,
|
|
40
|
+
createSevSnpHardwareVerifier: () => import_tcloud_attestation2.createSevSnpHardwareVerifier,
|
|
38
41
|
createShieldedClient: () => createShieldedClient,
|
|
42
|
+
createTdxHardwareVerifier: () => import_tcloud_attestation2.createTdxHardwareVerifier,
|
|
43
|
+
createTeeAttestationChallenge: () => createTeeAttestationChallenge,
|
|
39
44
|
estimateCost: () => estimateCost,
|
|
45
|
+
generateAttestationNonce: () => generateAttestationNonce,
|
|
40
46
|
generateWallet: () => generateWallet,
|
|
41
|
-
|
|
47
|
+
normalizeTeeType: () => import_tcloud_attestation2.normalizeTeeType,
|
|
48
|
+
parseAttestation: () => import_tcloud_attestation2.parseAttestation,
|
|
49
|
+
parseSevSnpReport: () => import_tcloud_attestation2.parseSevSnpReport,
|
|
50
|
+
signSpendAuth: () => signSpendAuth,
|
|
51
|
+
startTeeAttestationHeartbeat: () => startTeeAttestationHeartbeat,
|
|
52
|
+
toHex: () => import_tcloud_attestation2.toHex,
|
|
53
|
+
verifyAttestation: () => import_tcloud_attestation2.verifyAttestation,
|
|
54
|
+
verifyAttestationAsync: () => import_tcloud_attestation2.verifyAttestationAsync
|
|
42
55
|
});
|
|
43
56
|
module.exports = __toCommonJS(index_exports);
|
|
44
57
|
|
|
@@ -54,6 +67,8 @@ var PrivateRouter = class {
|
|
|
54
67
|
usage = /* @__PURE__ */ new Map();
|
|
55
68
|
currentIndex = 0;
|
|
56
69
|
totalRequests = 0;
|
|
70
|
+
/** Slug of the most-recently-selected operator. Populated on each selectOperator() hit. */
|
|
71
|
+
_lastSelectedSlug = null;
|
|
57
72
|
constructor(config = {}) {
|
|
58
73
|
this.config = {
|
|
59
74
|
strategy: config.strategy || "round-robin",
|
|
@@ -199,6 +214,11 @@ var PrivateRouter = class {
|
|
|
199
214
|
requestCount: (existing?.requestCount || 0) + 1,
|
|
200
215
|
lastUsedAt: Date.now()
|
|
201
216
|
});
|
|
217
|
+
this._lastSelectedSlug = op.slug;
|
|
218
|
+
}
|
|
219
|
+
/** Slug of the most-recently-selected operator (null before the first call). */
|
|
220
|
+
get lastSelectedSlug() {
|
|
221
|
+
return this._lastSelectedSlug;
|
|
202
222
|
}
|
|
203
223
|
getLastUsedOperator() {
|
|
204
224
|
let latest = null;
|
|
@@ -256,6 +276,7 @@ var PrivateRouter = class {
|
|
|
256
276
|
};
|
|
257
277
|
|
|
258
278
|
// src/client.ts
|
|
279
|
+
var ROTATING_MARKER = "__tcloudRotating";
|
|
259
280
|
var DEFAULT_BASE_URL = "https://router.tangle.tools/v1";
|
|
260
281
|
var SDK_VERSION = "0.4.0";
|
|
261
282
|
async function proxiedFetch(privacy, url, init, streaming) {
|
|
@@ -353,6 +374,54 @@ var TCloudClient = class _TCloudClient {
|
|
|
353
374
|
const baseURL = opts.url.replace(/\/+$/, "") + "/v1";
|
|
354
375
|
return new _TCloudClient({ ...opts.config ?? {}, apiKey: opts.bearer, baseURL });
|
|
355
376
|
}
|
|
377
|
+
/**
|
|
378
|
+
* Build a client that rotates which operator serves each call. Mirrors
|
|
379
|
+
* {@link TCloudClient.shielded} in shape: returns a standard `TCloudClient`
|
|
380
|
+
* that behaves identically for the OpenAI-compatible surface but
|
|
381
|
+
* dispatches each chat/completions/embeddings request through a
|
|
382
|
+
* {@link PrivateRouter} — different operator per call per the chosen
|
|
383
|
+
* strategy.
|
|
384
|
+
*
|
|
385
|
+
* ```ts
|
|
386
|
+
* const tcloud = TCloudClient.rotating({
|
|
387
|
+
* apiKey: process.env.TCLOUD_API_KEY,
|
|
388
|
+
* routing: { strategy: 'min-exposure' },
|
|
389
|
+
* })
|
|
390
|
+
* await tcloud.ask('hello')
|
|
391
|
+
* tcloud.getRotationStats() // { callsByOperator: { ... }, currentOperator: '…' }
|
|
392
|
+
* ```
|
|
393
|
+
*
|
|
394
|
+
* Rotation is meaningful only for stateless calls. Sandbox-harness
|
|
395
|
+
* sessions bind to a single operator for the lifetime of the session;
|
|
396
|
+
* `rotating()` clients refuse to dispatch them — see {@link bridge}.
|
|
397
|
+
*/
|
|
398
|
+
static rotating(config = {}) {
|
|
399
|
+
const routing = config.routing ?? {};
|
|
400
|
+
const strategy = routing.strategy ?? "min-exposure";
|
|
401
|
+
const { routing: _omit, ...base } = config;
|
|
402
|
+
const client = new _TCloudClient(base);
|
|
403
|
+
const router = new PrivateRouter({
|
|
404
|
+
strategy,
|
|
405
|
+
minOperators: routing.minOperators ?? 1,
|
|
406
|
+
maxRequestsPerOperator: routing.maxRequestsPerOperator,
|
|
407
|
+
excludeOperators: routing.excludeOperators,
|
|
408
|
+
preferRegions: routing.preferRegions
|
|
409
|
+
});
|
|
410
|
+
if (routing.pool && routing.pool.length > 0) {
|
|
411
|
+
router.setOperators(routing.pool);
|
|
412
|
+
client._cachedOperators = routing.pool;
|
|
413
|
+
client._operatorsCachedAt = Date.now();
|
|
414
|
+
}
|
|
415
|
+
;
|
|
416
|
+
client.privateRouter = router;
|
|
417
|
+
Object.defineProperty(client, ROTATING_MARKER, {
|
|
418
|
+
value: true,
|
|
419
|
+
enumerable: false,
|
|
420
|
+
configurable: false,
|
|
421
|
+
writable: false
|
|
422
|
+
});
|
|
423
|
+
return client;
|
|
424
|
+
}
|
|
356
425
|
constructor(config = {}) {
|
|
357
426
|
this.baseURL = (config.baseURL || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
358
427
|
this.platformURL = (config.platformURL || DEFAULT_PLATFORM_URL).replace(/\/$/, "");
|
|
@@ -668,10 +737,38 @@ var TCloudClient = class _TCloudClient {
|
|
|
668
737
|
*
|
|
669
738
|
* Sessions persist across process restarts — use the same `resume` id
|
|
670
739
|
* to land on the same CLI conversation (context intact, no replay tax).
|
|
740
|
+
*
|
|
741
|
+
* Guard: clients built via {@link TCloudClient.rotating} cannot dispatch
|
|
742
|
+
* sandbox-harness sessions (rotation rotates per call; a sandbox session
|
|
743
|
+
* binds to one operator). Attempting `bridge({ harness: 'sandbox' })` on
|
|
744
|
+
* a rotating client throws.
|
|
671
745
|
*/
|
|
672
746
|
bridge(cfg) {
|
|
747
|
+
if (cfg.harness === "sandbox" && this[ROTATING_MARKER] === true) {
|
|
748
|
+
throw new Error(
|
|
749
|
+
"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."
|
|
750
|
+
);
|
|
751
|
+
}
|
|
673
752
|
return new BridgeSession(this, cfg);
|
|
674
753
|
}
|
|
754
|
+
/**
|
|
755
|
+
* Rotation stats — populated only on clients created via
|
|
756
|
+
* {@link TCloudClient.rotating}. Non-rotating clients return an empty
|
|
757
|
+
* counter and `currentOperator: null`.
|
|
758
|
+
*/
|
|
759
|
+
getRotationStats() {
|
|
760
|
+
if (!this.privateRouter) {
|
|
761
|
+
return { callsByOperator: {}, currentOperator: null };
|
|
762
|
+
}
|
|
763
|
+
const callsByOperator = {};
|
|
764
|
+
for (const row of this.privateRouter.getStats().operatorBreakdown) {
|
|
765
|
+
callsByOperator[row.slug] = row.requests;
|
|
766
|
+
}
|
|
767
|
+
return {
|
|
768
|
+
callsByOperator,
|
|
769
|
+
currentOperator: this.privateRouter.lastSelectedSlug
|
|
770
|
+
};
|
|
771
|
+
}
|
|
675
772
|
/** Convenience: send a single message and get the text response */
|
|
676
773
|
async ask(message, modelOrOptions) {
|
|
677
774
|
const options = typeof modelOrOptions === "string" ? { model: modelOrOptions } : modelOrOptions;
|
|
@@ -1173,6 +1270,8 @@ var BridgeSession = class _BridgeSession {
|
|
|
1173
1270
|
this.client = client;
|
|
1174
1271
|
this.cfg = cfg;
|
|
1175
1272
|
}
|
|
1273
|
+
client;
|
|
1274
|
+
cfg;
|
|
1176
1275
|
/** Full chat completion (non-streaming). */
|
|
1177
1276
|
async chat(options) {
|
|
1178
1277
|
return this.client.chat({ ...options, bridge: this.cfg });
|
|
@@ -1242,6 +1341,7 @@ var TCloudError = class extends Error {
|
|
|
1242
1341
|
this.status = status;
|
|
1243
1342
|
this.name = "TCloudError";
|
|
1244
1343
|
}
|
|
1344
|
+
status;
|
|
1245
1345
|
};
|
|
1246
1346
|
|
|
1247
1347
|
// src/shielded.ts
|
|
@@ -1497,7 +1597,321 @@ async function replenishDirect(fundingKey, tokenAddress, amount, commitment, spe
|
|
|
1497
1597
|
}
|
|
1498
1598
|
}
|
|
1499
1599
|
|
|
1600
|
+
// src/sandbox.ts
|
|
1601
|
+
var import_sandbox = require("@tangle-network/sandbox");
|
|
1602
|
+
var import_node_crypto = require("crypto");
|
|
1603
|
+
var import_tcloud_attestation = require("@tangle-network/tcloud-attestation");
|
|
1604
|
+
var DEFAULT_SANDBOX_URL = "https://sandbox.tangle.tools";
|
|
1605
|
+
var TCloudSandbox = class {
|
|
1606
|
+
client;
|
|
1607
|
+
apiKey;
|
|
1608
|
+
baseUrl;
|
|
1609
|
+
timeoutMs;
|
|
1610
|
+
constructor(config) {
|
|
1611
|
+
this.apiKey = config.apiKey;
|
|
1612
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_SANDBOX_URL).replace(/\/+$/, "");
|
|
1613
|
+
this.timeoutMs = config.timeoutMs;
|
|
1614
|
+
this.client = new import_sandbox.Sandbox({
|
|
1615
|
+
apiKey: this.apiKey,
|
|
1616
|
+
baseUrl: this.baseUrl,
|
|
1617
|
+
timeoutMs: config.timeoutMs
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
async create(options) {
|
|
1621
|
+
const effectiveVerify = shouldVerifyAttestation(options);
|
|
1622
|
+
const attestationPolicy = buildAttestationPolicy(options);
|
|
1623
|
+
const createOptions = buildSandboxCreateOptions({
|
|
1624
|
+
...options,
|
|
1625
|
+
verify: effectiveVerify
|
|
1626
|
+
});
|
|
1627
|
+
const sandbox = await this.client.create(createOptions);
|
|
1628
|
+
this.attachTeeApiFallbacks(sandbox);
|
|
1629
|
+
let attestation = attestationFromMetadata(sandbox.metadata);
|
|
1630
|
+
if ((effectiveVerify || createOptions.confidential?.attestationNonce) && !attestation) {
|
|
1631
|
+
const getTeeAttestation = sandbox.getTeeAttestation;
|
|
1632
|
+
if (typeof getTeeAttestation !== "function") {
|
|
1633
|
+
throw new Error("Installed @tangle-network/sandbox does not expose TEE attestation fetching");
|
|
1634
|
+
}
|
|
1635
|
+
attestation = (await getTeeAttestation.call(
|
|
1636
|
+
sandbox,
|
|
1637
|
+
createOptions.confidential?.attestationNonce ? { attestationNonce: createOptions.confidential.attestationNonce } : void 0
|
|
1638
|
+
)).attestation;
|
|
1639
|
+
}
|
|
1640
|
+
if (effectiveVerify && !attestation) {
|
|
1641
|
+
throw new Error("TEE attestation verification requested but no evidence was returned");
|
|
1642
|
+
}
|
|
1643
|
+
const verification = effectiveVerify ? await (0, import_tcloud_attestation.verifyAttestationAsync)(attestation, {
|
|
1644
|
+
...attestationPolicy,
|
|
1645
|
+
expectedNonce: createOptions.confidential?.attestationNonce
|
|
1646
|
+
}) : void 0;
|
|
1647
|
+
if (verification && !verification.valid) {
|
|
1648
|
+
throw new Error(`TEE attestation verification failed: ${verification.errors.join("; ")}`);
|
|
1649
|
+
}
|
|
1650
|
+
return {
|
|
1651
|
+
sandbox,
|
|
1652
|
+
attestation,
|
|
1653
|
+
verification,
|
|
1654
|
+
attestationNonce: createOptions.confidential?.attestationNonce,
|
|
1655
|
+
attestationStatus: {
|
|
1656
|
+
requested: Boolean(options.tee),
|
|
1657
|
+
evidenceReturned: Boolean(attestation),
|
|
1658
|
+
verified: Boolean(verification?.valid),
|
|
1659
|
+
nonceBound: Boolean(createOptions.confidential?.attestationNonce && verification?.valid),
|
|
1660
|
+
errors: verification?.errors ?? []
|
|
1661
|
+
}
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
attachTeeApiFallbacks(sandbox) {
|
|
1665
|
+
if (typeof sandbox !== "object" || sandbox === null) return;
|
|
1666
|
+
const target = sandbox;
|
|
1667
|
+
if (typeof target.id !== "string" || target.id.length === 0) return;
|
|
1668
|
+
if (typeof target.getTeeAttestation !== "function") {
|
|
1669
|
+
target.getTeeAttestation = (options) => this.fetchTeeAttestation(target.id, options?.attestationNonce);
|
|
1670
|
+
}
|
|
1671
|
+
if (typeof target.getTeePublicKey !== "function") {
|
|
1672
|
+
target.getTeePublicKey = () => this.fetchTeePublicKey(target.id);
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
async fetchTeeAttestation(sandboxId, attestationNonce) {
|
|
1676
|
+
const response = await this.fetchSandboxApi(
|
|
1677
|
+
`/v1/sandboxes/${encodeURIComponent(sandboxId)}/tee/attestation`,
|
|
1678
|
+
{
|
|
1679
|
+
method: attestationNonce ? "POST" : "GET",
|
|
1680
|
+
body: attestationNonce ? JSON.stringify({ attestation_nonce: attestationNonce }) : void 0
|
|
1681
|
+
}
|
|
1682
|
+
);
|
|
1683
|
+
const data = await response.json();
|
|
1684
|
+
if (attestationNonce && !data.attestationNonce) {
|
|
1685
|
+
data.attestationNonce = attestationNonce;
|
|
1686
|
+
}
|
|
1687
|
+
return data;
|
|
1688
|
+
}
|
|
1689
|
+
async fetchTeePublicKey(sandboxId) {
|
|
1690
|
+
const response = await this.fetchSandboxApi(
|
|
1691
|
+
`/v1/sandboxes/${encodeURIComponent(sandboxId)}/tee/public-key`,
|
|
1692
|
+
{ method: "GET" }
|
|
1693
|
+
);
|
|
1694
|
+
return response.json();
|
|
1695
|
+
}
|
|
1696
|
+
async fetchSandboxApi(path, options) {
|
|
1697
|
+
const headers = new Headers(options.headers);
|
|
1698
|
+
headers.set("Authorization", `Bearer ${this.apiKey}`);
|
|
1699
|
+
if (options.body && !headers.has("Content-Type")) {
|
|
1700
|
+
headers.set("Content-Type", "application/json");
|
|
1701
|
+
}
|
|
1702
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
1703
|
+
...options,
|
|
1704
|
+
headers,
|
|
1705
|
+
signal: options.signal ?? (this.timeoutMs ? AbortSignal.timeout(this.timeoutMs) : void 0)
|
|
1706
|
+
});
|
|
1707
|
+
if (!response.ok) {
|
|
1708
|
+
const body = await response.text();
|
|
1709
|
+
throw new Error(`Sandbox API ${path} failed with HTTP ${response.status}: ${body}`);
|
|
1710
|
+
}
|
|
1711
|
+
return response;
|
|
1712
|
+
}
|
|
1713
|
+
};
|
|
1714
|
+
function createTeeAttestationChallenge(context) {
|
|
1715
|
+
const randomHex = generateAttestationNonce(32);
|
|
1716
|
+
if (context == null) {
|
|
1717
|
+
return { nonce: randomHex, randomHex };
|
|
1718
|
+
}
|
|
1719
|
+
const contextHashHex = (0, import_node_crypto.createHash)("sha256").update(typeof context === "string" ? Buffer.from(context) : Buffer.from(context)).digest("hex");
|
|
1720
|
+
return {
|
|
1721
|
+
nonce: `${randomHex}${contextHashHex}`,
|
|
1722
|
+
randomHex,
|
|
1723
|
+
contextHashHex
|
|
1724
|
+
};
|
|
1725
|
+
}
|
|
1726
|
+
function startTeeAttestationHeartbeat(sandbox, options = {}) {
|
|
1727
|
+
const getTeeAttestation = sandbox?.getTeeAttestation;
|
|
1728
|
+
if (typeof getTeeAttestation !== "function") {
|
|
1729
|
+
throw new Error("Sandbox does not expose TEE attestation fetching");
|
|
1730
|
+
}
|
|
1731
|
+
const intervalMs = options.intervalMs ?? 6e4;
|
|
1732
|
+
const sessionId = options.sessionId ?? generateAttestationNonce(16);
|
|
1733
|
+
const policy = options.tee ? buildAttestationPolicy({ tee: options.tee, attestationPolicy: options.attestationPolicy }) : options.attestationPolicy ?? {};
|
|
1734
|
+
let stopped = false;
|
|
1735
|
+
let failures = 0;
|
|
1736
|
+
let sequence = 0;
|
|
1737
|
+
let latest;
|
|
1738
|
+
let timer;
|
|
1739
|
+
let resolveDone;
|
|
1740
|
+
const done = new Promise((resolve) => {
|
|
1741
|
+
resolveDone = resolve;
|
|
1742
|
+
});
|
|
1743
|
+
const stop = () => {
|
|
1744
|
+
if (stopped) return;
|
|
1745
|
+
stopped = true;
|
|
1746
|
+
if (timer) clearTimeout(timer);
|
|
1747
|
+
resolveDone?.();
|
|
1748
|
+
};
|
|
1749
|
+
const ping = async (context) => {
|
|
1750
|
+
if (stopped || options.signal?.aborted) {
|
|
1751
|
+
throw new Error("TEE attestation heartbeat is stopped");
|
|
1752
|
+
}
|
|
1753
|
+
const nextSequence = sequence + 1;
|
|
1754
|
+
const boundContext = context ?? `${sessionId}:${nextSequence}`;
|
|
1755
|
+
const challenge = createTeeAttestationChallenge(boundContext);
|
|
1756
|
+
const response = await getTeeAttestation.call(sandbox, {
|
|
1757
|
+
attestationNonce: challenge.nonce
|
|
1758
|
+
});
|
|
1759
|
+
const attestation = response?.attestation;
|
|
1760
|
+
if (!attestation) {
|
|
1761
|
+
throw new Error("TEE attestation heartbeat returned no evidence");
|
|
1762
|
+
}
|
|
1763
|
+
const verification = await (0, import_tcloud_attestation.verifyAttestationAsync)(attestation, {
|
|
1764
|
+
...policy,
|
|
1765
|
+
expectedNonce: challenge.nonce
|
|
1766
|
+
});
|
|
1767
|
+
if (!verification.valid) {
|
|
1768
|
+
throw new Error(`TEE attestation heartbeat verification failed: ${verification.errors.join("; ")}`);
|
|
1769
|
+
}
|
|
1770
|
+
const sample = {
|
|
1771
|
+
sequence: nextSequence,
|
|
1772
|
+
nonce: challenge.nonce,
|
|
1773
|
+
context: boundContext,
|
|
1774
|
+
attestation,
|
|
1775
|
+
verification,
|
|
1776
|
+
checkedAt: /* @__PURE__ */ new Date()
|
|
1777
|
+
};
|
|
1778
|
+
sequence = nextSequence;
|
|
1779
|
+
latest = sample;
|
|
1780
|
+
options.onSuccess?.(sample);
|
|
1781
|
+
return sample;
|
|
1782
|
+
};
|
|
1783
|
+
const schedule = () => {
|
|
1784
|
+
if (stopped || options.signal?.aborted) {
|
|
1785
|
+
stop();
|
|
1786
|
+
return;
|
|
1787
|
+
}
|
|
1788
|
+
timer = setTimeout(() => {
|
|
1789
|
+
void loop();
|
|
1790
|
+
}, intervalMs);
|
|
1791
|
+
};
|
|
1792
|
+
const loop = async () => {
|
|
1793
|
+
try {
|
|
1794
|
+
await ping();
|
|
1795
|
+
schedule();
|
|
1796
|
+
} catch (error) {
|
|
1797
|
+
failures += 1;
|
|
1798
|
+
options.onFailure?.(error);
|
|
1799
|
+
if (options.continueOnFailure) {
|
|
1800
|
+
schedule();
|
|
1801
|
+
} else {
|
|
1802
|
+
stop();
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
};
|
|
1806
|
+
options.signal?.addEventListener("abort", stop, { once: true });
|
|
1807
|
+
if (options.immediate === false) {
|
|
1808
|
+
schedule();
|
|
1809
|
+
} else {
|
|
1810
|
+
void loop();
|
|
1811
|
+
}
|
|
1812
|
+
return {
|
|
1813
|
+
stop,
|
|
1814
|
+
ping,
|
|
1815
|
+
get stopped() {
|
|
1816
|
+
return stopped;
|
|
1817
|
+
},
|
|
1818
|
+
get failures() {
|
|
1819
|
+
return failures;
|
|
1820
|
+
},
|
|
1821
|
+
get latest() {
|
|
1822
|
+
return latest;
|
|
1823
|
+
},
|
|
1824
|
+
done
|
|
1825
|
+
};
|
|
1826
|
+
}
|
|
1827
|
+
function buildSandboxCreateOptions(options) {
|
|
1828
|
+
if (!options.tee && (options.sealed || options.attestationNonce || options.verify)) {
|
|
1829
|
+
throw new Error("TEE options require a tee value");
|
|
1830
|
+
}
|
|
1831
|
+
const shouldGenerateNonce = options.attestationNonce === "auto" || shouldVerifyAttestation(options) && !options.attestationNonce;
|
|
1832
|
+
const attestationNonce = shouldGenerateNonce ? generateAttestationNonce() : options.attestationNonce;
|
|
1833
|
+
if (attestationNonce) {
|
|
1834
|
+
validateAttestationNonce(attestationNonce);
|
|
1835
|
+
}
|
|
1836
|
+
return {
|
|
1837
|
+
name: options.name,
|
|
1838
|
+
environment: options.environment ?? options.image,
|
|
1839
|
+
sshEnabled: options.ssh || void 0,
|
|
1840
|
+
git: options.gitUrl ? {
|
|
1841
|
+
url: options.gitUrl,
|
|
1842
|
+
ref: options.gitRef
|
|
1843
|
+
} : void 0,
|
|
1844
|
+
resources: options.cpu || options.memoryMb || options.diskGb ? {
|
|
1845
|
+
cpuCores: options.cpu,
|
|
1846
|
+
memoryMB: options.memoryMb,
|
|
1847
|
+
diskGB: options.diskGb
|
|
1848
|
+
} : void 0,
|
|
1849
|
+
backend: options.backend ? { type: options.backend } : void 0,
|
|
1850
|
+
confidential: options.tee ? {
|
|
1851
|
+
tee: options.tee,
|
|
1852
|
+
sealed: options.sealed || void 0,
|
|
1853
|
+
attestationNonce,
|
|
1854
|
+
attestationRefresh: Boolean(attestationNonce)
|
|
1855
|
+
} : void 0
|
|
1856
|
+
};
|
|
1857
|
+
}
|
|
1858
|
+
function shouldVerifyAttestation(options) {
|
|
1859
|
+
return Boolean(options.tee || options.verify);
|
|
1860
|
+
}
|
|
1861
|
+
function buildAttestationPolicy(options) {
|
|
1862
|
+
if (!options.tee || options.tee === "any") return options.attestationPolicy ?? {};
|
|
1863
|
+
const requestedTypes = acceptedAttestationTypesForTee(options.tee);
|
|
1864
|
+
const acceptedTeeTypes = options.attestationPolicy?.acceptedTeeTypes;
|
|
1865
|
+
if (acceptedTeeTypes?.length && !acceptedTeeTypes.some((type) => requestedTypes.includes(type))) {
|
|
1866
|
+
throw new Error(
|
|
1867
|
+
`TEE attestation policy does not accept requested TEE type ${options.tee}`
|
|
1868
|
+
);
|
|
1869
|
+
}
|
|
1870
|
+
return {
|
|
1871
|
+
...options.attestationPolicy,
|
|
1872
|
+
acceptedTeeTypes: acceptedTeeTypes?.length ? acceptedTeeTypes.filter((type) => requestedTypes.includes(type)) : requestedTypes
|
|
1873
|
+
};
|
|
1874
|
+
}
|
|
1875
|
+
function acceptedAttestationTypesForTee(tee) {
|
|
1876
|
+
switch (tee) {
|
|
1877
|
+
case "phala-dstack":
|
|
1878
|
+
return ["tdx", "phala-dstack"];
|
|
1879
|
+
case "gcp":
|
|
1880
|
+
return ["tdx", "sev-snp", "gcp"];
|
|
1881
|
+
case "azure":
|
|
1882
|
+
return ["sev-snp", "azure"];
|
|
1883
|
+
default:
|
|
1884
|
+
return [(0, import_tcloud_attestation.normalizeTeeType)(tee)];
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
function validateAttestationNonce(value) {
|
|
1888
|
+
const normalized = value.trim().toLowerCase().replace(/^0x/, "");
|
|
1889
|
+
if (!/^[0-9a-f]+$/.test(normalized)) {
|
|
1890
|
+
throw new Error("attestation nonce must be hex");
|
|
1891
|
+
}
|
|
1892
|
+
if (normalized.length % 2 !== 0) {
|
|
1893
|
+
throw new Error("attestation nonce must have even hex length");
|
|
1894
|
+
}
|
|
1895
|
+
const bytes = normalized.length / 2;
|
|
1896
|
+
if (bytes < 32 || bytes > 64) {
|
|
1897
|
+
throw new Error(`attestation nonce must be 32-64 bytes, got ${bytes}`);
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
function generateAttestationNonce(bytes = 32) {
|
|
1901
|
+
return Array.from((0, import_node_crypto.randomBytes)(bytes)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1902
|
+
}
|
|
1903
|
+
function attestationFromMetadata(metadata) {
|
|
1904
|
+
const raw = metadata?.teeAttestationJson;
|
|
1905
|
+
if (typeof raw !== "string" || raw.trim() === "") return void 0;
|
|
1906
|
+
try {
|
|
1907
|
+
return JSON.parse(raw);
|
|
1908
|
+
} catch {
|
|
1909
|
+
return void 0;
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1500
1913
|
// src/index.ts
|
|
1914
|
+
var import_tcloud_attestation2 = require("@tangle-network/tcloud-attestation");
|
|
1501
1915
|
var TCloud = class _TCloud extends TCloudClient {
|
|
1502
1916
|
constructor(config) {
|
|
1503
1917
|
super(config);
|
|
@@ -1526,6 +1940,21 @@ var TCloud = class _TCloud extends TCloudClient {
|
|
|
1526
1940
|
static shielded(config) {
|
|
1527
1941
|
return createShieldedClient(config);
|
|
1528
1942
|
}
|
|
1943
|
+
/**
|
|
1944
|
+
* Create a client that rotates operators per call.
|
|
1945
|
+
* See {@link TCloudClient.rotating} for semantics.
|
|
1946
|
+
*
|
|
1947
|
+
* ```ts
|
|
1948
|
+
* const client = TCloud.rotating({
|
|
1949
|
+
* apiKey: process.env.TCLOUD_API_KEY,
|
|
1950
|
+
* routing: { strategy: 'min-exposure' },
|
|
1951
|
+
* })
|
|
1952
|
+
* const stats = client.getRotationStats()
|
|
1953
|
+
* ```
|
|
1954
|
+
*/
|
|
1955
|
+
static rotating(config) {
|
|
1956
|
+
return TCloudClient.rotating(config);
|
|
1957
|
+
}
|
|
1529
1958
|
/**
|
|
1530
1959
|
* Generate a new ephemeral wallet (without creating a client).
|
|
1531
1960
|
*
|
|
@@ -1543,8 +1972,21 @@ var TCloud = class _TCloud extends TCloudClient {
|
|
|
1543
1972
|
TCloud,
|
|
1544
1973
|
TCloudClient,
|
|
1545
1974
|
TCloudError,
|
|
1975
|
+
TCloudSandbox,
|
|
1976
|
+
assertAttestation,
|
|
1977
|
+
createSevSnpHardwareVerifier,
|
|
1546
1978
|
createShieldedClient,
|
|
1979
|
+
createTdxHardwareVerifier,
|
|
1980
|
+
createTeeAttestationChallenge,
|
|
1547
1981
|
estimateCost,
|
|
1982
|
+
generateAttestationNonce,
|
|
1548
1983
|
generateWallet,
|
|
1549
|
-
|
|
1984
|
+
normalizeTeeType,
|
|
1985
|
+
parseAttestation,
|
|
1986
|
+
parseSevSnpReport,
|
|
1987
|
+
signSpendAuth,
|
|
1988
|
+
startTeeAttestationHeartbeat,
|
|
1989
|
+
toHex,
|
|
1990
|
+
verifyAttestation,
|
|
1991
|
+
verifyAttestationAsync
|
|
1550
1992
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { ShieldedWallet, generateWallet } from './shielded.cjs';
|
|
2
2
|
export { createShieldedClient, estimateCost, signSpendAuth } from './shielded.cjs';
|
|
3
|
-
import { T as TCloudClient, a as TCloudConfig } from './client-
|
|
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,
|
|
3
|
+
import { T as TCloudClient, a as TCloudConfig, R as RotatingClientConfig } from './client-DkQugNHH.cjs';
|
|
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.cjs';
|
|
5
|
+
export { TCloudSandbox, TCloudSandboxAttestationStatus, TCloudSandboxConfig, TCloudSandboxCreateOptions, TCloudSandboxCreateResult, TCloudSandboxTee, TCloudTeeAttestationChallenge, TCloudTeeAttestationHeartbeat, TCloudTeeAttestationHeartbeatOptions, TCloudTeeAttestationHeartbeatSample, createTeeAttestationChallenge, generateAttestationNonce, startTeeAttestationHeartbeat } from './sandbox.cjs';
|
|
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 };
|