@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.
@@ -0,0 +1,90 @@
1
+ import { AsyncAttestationPolicy, AttestationVerificationResult } from '@tangle-network/tcloud-attestation';
2
+
3
+ type TCloudSandboxTee = 'any' | 'tdx' | 'nitro' | 'sev-snp' | 'phala-dstack' | 'gcp' | 'azure' | (string & {});
4
+ interface TCloudSandboxCreateOptions {
5
+ name?: string;
6
+ image?: string;
7
+ environment?: string;
8
+ ssh?: boolean;
9
+ cpu?: number;
10
+ memoryMb?: number;
11
+ diskGb?: number;
12
+ gitUrl?: string;
13
+ gitRef?: string;
14
+ backend?: 'opencode' | 'claude-code' | 'codex' | 'amp' | (string & {});
15
+ tee?: TCloudSandboxTee;
16
+ sealed?: boolean;
17
+ attestationNonce?: string | 'auto';
18
+ verify?: boolean;
19
+ attestationPolicy?: Omit<AsyncAttestationPolicy, 'expectedNonce'>;
20
+ }
21
+ interface TCloudSandboxAttestationStatus {
22
+ requested: boolean;
23
+ evidenceReturned: boolean;
24
+ verified: boolean;
25
+ nonceBound: boolean;
26
+ errors: string[];
27
+ }
28
+ interface TCloudSandboxCreateResult {
29
+ sandbox: unknown;
30
+ attestation?: unknown;
31
+ verification?: AttestationVerificationResult;
32
+ attestationNonce?: string;
33
+ attestationStatus: TCloudSandboxAttestationStatus;
34
+ }
35
+ interface TCloudTeeAttestationChallenge {
36
+ nonce: string;
37
+ randomHex: string;
38
+ contextHashHex?: string;
39
+ }
40
+ interface TCloudTeeAttestationHeartbeatSample {
41
+ sequence: number;
42
+ nonce: string;
43
+ context: string;
44
+ attestation: unknown;
45
+ verification: AttestationVerificationResult;
46
+ checkedAt: Date;
47
+ }
48
+ interface TCloudTeeAttestationHeartbeatOptions {
49
+ tee?: TCloudSandboxTee;
50
+ intervalMs?: number;
51
+ signal?: AbortSignal;
52
+ immediate?: boolean;
53
+ continueOnFailure?: boolean;
54
+ sessionId?: string;
55
+ attestationPolicy?: Omit<AsyncAttestationPolicy, 'expectedNonce'>;
56
+ onSuccess?: (sample: TCloudTeeAttestationHeartbeatSample) => void;
57
+ onFailure?: (error: unknown) => void;
58
+ }
59
+ interface TCloudTeeAttestationHeartbeat {
60
+ stop(): void;
61
+ ping(context?: string): Promise<TCloudTeeAttestationHeartbeatSample>;
62
+ readonly stopped: boolean;
63
+ readonly failures: number;
64
+ readonly latest: TCloudTeeAttestationHeartbeatSample | undefined;
65
+ readonly done: Promise<void>;
66
+ }
67
+ interface TCloudSandboxConfig {
68
+ apiKey: string;
69
+ baseUrl?: string;
70
+ timeoutMs?: number;
71
+ }
72
+ declare class TCloudSandbox {
73
+ private readonly client;
74
+ private readonly apiKey;
75
+ private readonly baseUrl;
76
+ private readonly timeoutMs;
77
+ constructor(config: TCloudSandboxConfig);
78
+ create(options: TCloudSandboxCreateOptions): Promise<TCloudSandboxCreateResult>;
79
+ private attachTeeApiFallbacks;
80
+ private fetchTeeAttestation;
81
+ private fetchTeePublicKey;
82
+ private fetchSandboxApi;
83
+ }
84
+ declare function createTeeAttestationChallenge(context?: string | Uint8Array): TCloudTeeAttestationChallenge;
85
+ declare function startTeeAttestationHeartbeat(sandbox: unknown, options?: TCloudTeeAttestationHeartbeatOptions): TCloudTeeAttestationHeartbeat;
86
+ declare function buildSandboxCreateOptions(options: TCloudSandboxCreateOptions): any;
87
+ declare function shouldVerifyAttestation(options: Pick<TCloudSandboxCreateOptions, 'tee' | 'verify'>): boolean;
88
+ declare function generateAttestationNonce(bytes?: number): string;
89
+
90
+ export { TCloudSandbox, type TCloudSandboxAttestationStatus, type TCloudSandboxConfig, type TCloudSandboxCreateOptions, type TCloudSandboxCreateResult, type TCloudSandboxTee, type TCloudTeeAttestationChallenge, type TCloudTeeAttestationHeartbeat, type TCloudTeeAttestationHeartbeatOptions, type TCloudTeeAttestationHeartbeatSample, buildSandboxCreateOptions, createTeeAttestationChallenge, generateAttestationNonce, shouldVerifyAttestation, startTeeAttestationHeartbeat };
@@ -0,0 +1,90 @@
1
+ import { AsyncAttestationPolicy, AttestationVerificationResult } from '@tangle-network/tcloud-attestation';
2
+
3
+ type TCloudSandboxTee = 'any' | 'tdx' | 'nitro' | 'sev-snp' | 'phala-dstack' | 'gcp' | 'azure' | (string & {});
4
+ interface TCloudSandboxCreateOptions {
5
+ name?: string;
6
+ image?: string;
7
+ environment?: string;
8
+ ssh?: boolean;
9
+ cpu?: number;
10
+ memoryMb?: number;
11
+ diskGb?: number;
12
+ gitUrl?: string;
13
+ gitRef?: string;
14
+ backend?: 'opencode' | 'claude-code' | 'codex' | 'amp' | (string & {});
15
+ tee?: TCloudSandboxTee;
16
+ sealed?: boolean;
17
+ attestationNonce?: string | 'auto';
18
+ verify?: boolean;
19
+ attestationPolicy?: Omit<AsyncAttestationPolicy, 'expectedNonce'>;
20
+ }
21
+ interface TCloudSandboxAttestationStatus {
22
+ requested: boolean;
23
+ evidenceReturned: boolean;
24
+ verified: boolean;
25
+ nonceBound: boolean;
26
+ errors: string[];
27
+ }
28
+ interface TCloudSandboxCreateResult {
29
+ sandbox: unknown;
30
+ attestation?: unknown;
31
+ verification?: AttestationVerificationResult;
32
+ attestationNonce?: string;
33
+ attestationStatus: TCloudSandboxAttestationStatus;
34
+ }
35
+ interface TCloudTeeAttestationChallenge {
36
+ nonce: string;
37
+ randomHex: string;
38
+ contextHashHex?: string;
39
+ }
40
+ interface TCloudTeeAttestationHeartbeatSample {
41
+ sequence: number;
42
+ nonce: string;
43
+ context: string;
44
+ attestation: unknown;
45
+ verification: AttestationVerificationResult;
46
+ checkedAt: Date;
47
+ }
48
+ interface TCloudTeeAttestationHeartbeatOptions {
49
+ tee?: TCloudSandboxTee;
50
+ intervalMs?: number;
51
+ signal?: AbortSignal;
52
+ immediate?: boolean;
53
+ continueOnFailure?: boolean;
54
+ sessionId?: string;
55
+ attestationPolicy?: Omit<AsyncAttestationPolicy, 'expectedNonce'>;
56
+ onSuccess?: (sample: TCloudTeeAttestationHeartbeatSample) => void;
57
+ onFailure?: (error: unknown) => void;
58
+ }
59
+ interface TCloudTeeAttestationHeartbeat {
60
+ stop(): void;
61
+ ping(context?: string): Promise<TCloudTeeAttestationHeartbeatSample>;
62
+ readonly stopped: boolean;
63
+ readonly failures: number;
64
+ readonly latest: TCloudTeeAttestationHeartbeatSample | undefined;
65
+ readonly done: Promise<void>;
66
+ }
67
+ interface TCloudSandboxConfig {
68
+ apiKey: string;
69
+ baseUrl?: string;
70
+ timeoutMs?: number;
71
+ }
72
+ declare class TCloudSandbox {
73
+ private readonly client;
74
+ private readonly apiKey;
75
+ private readonly baseUrl;
76
+ private readonly timeoutMs;
77
+ constructor(config: TCloudSandboxConfig);
78
+ create(options: TCloudSandboxCreateOptions): Promise<TCloudSandboxCreateResult>;
79
+ private attachTeeApiFallbacks;
80
+ private fetchTeeAttestation;
81
+ private fetchTeePublicKey;
82
+ private fetchSandboxApi;
83
+ }
84
+ declare function createTeeAttestationChallenge(context?: string | Uint8Array): TCloudTeeAttestationChallenge;
85
+ declare function startTeeAttestationHeartbeat(sandbox: unknown, options?: TCloudTeeAttestationHeartbeatOptions): TCloudTeeAttestationHeartbeat;
86
+ declare function buildSandboxCreateOptions(options: TCloudSandboxCreateOptions): any;
87
+ declare function shouldVerifyAttestation(options: Pick<TCloudSandboxCreateOptions, 'tee' | 'verify'>): boolean;
88
+ declare function generateAttestationNonce(bytes?: number): string;
89
+
90
+ export { TCloudSandbox, type TCloudSandboxAttestationStatus, type TCloudSandboxConfig, type TCloudSandboxCreateOptions, type TCloudSandboxCreateResult, type TCloudSandboxTee, type TCloudTeeAttestationChallenge, type TCloudTeeAttestationHeartbeat, type TCloudTeeAttestationHeartbeatOptions, type TCloudTeeAttestationHeartbeatSample, buildSandboxCreateOptions, createTeeAttestationChallenge, generateAttestationNonce, shouldVerifyAttestation, startTeeAttestationHeartbeat };
@@ -0,0 +1,16 @@
1
+ import {
2
+ TCloudSandbox,
3
+ buildSandboxCreateOptions,
4
+ createTeeAttestationChallenge,
5
+ generateAttestationNonce,
6
+ shouldVerifyAttestation,
7
+ startTeeAttestationHeartbeat
8
+ } from "./chunk-DBIT227N.js";
9
+ export {
10
+ TCloudSandbox,
11
+ buildSandboxCreateOptions,
12
+ createTeeAttestationChallenge,
13
+ generateAttestationNonce,
14
+ shouldVerifyAttestation,
15
+ startTeeAttestationHeartbeat
16
+ };
package/dist/shielded.cjs CHANGED
@@ -51,6 +51,8 @@ var PrivateRouter = class {
51
51
  usage = /* @__PURE__ */ new Map();
52
52
  currentIndex = 0;
53
53
  totalRequests = 0;
54
+ /** Slug of the most-recently-selected operator. Populated on each selectOperator() hit. */
55
+ _lastSelectedSlug = null;
54
56
  constructor(config = {}) {
55
57
  this.config = {
56
58
  strategy: config.strategy || "round-robin",
@@ -196,6 +198,11 @@ var PrivateRouter = class {
196
198
  requestCount: (existing?.requestCount || 0) + 1,
197
199
  lastUsedAt: Date.now()
198
200
  });
201
+ this._lastSelectedSlug = op.slug;
202
+ }
203
+ /** Slug of the most-recently-selected operator (null before the first call). */
204
+ get lastSelectedSlug() {
205
+ return this._lastSelectedSlug;
199
206
  }
200
207
  getLastUsedOperator() {
201
208
  let latest = null;
@@ -253,6 +260,7 @@ var PrivateRouter = class {
253
260
  };
254
261
 
255
262
  // src/client.ts
263
+ var ROTATING_MARKER = "__tcloudRotating";
256
264
  var DEFAULT_BASE_URL = "https://router.tangle.tools/v1";
257
265
  var SDK_VERSION = "0.4.0";
258
266
  async function proxiedFetch(privacy, url, init, streaming) {
@@ -350,6 +358,54 @@ var TCloudClient = class _TCloudClient {
350
358
  const baseURL = opts.url.replace(/\/+$/, "") + "/v1";
351
359
  return new _TCloudClient({ ...opts.config ?? {}, apiKey: opts.bearer, baseURL });
352
360
  }
361
+ /**
362
+ * Build a client that rotates which operator serves each call. Mirrors
363
+ * {@link TCloudClient.shielded} in shape: returns a standard `TCloudClient`
364
+ * that behaves identically for the OpenAI-compatible surface but
365
+ * dispatches each chat/completions/embeddings request through a
366
+ * {@link PrivateRouter} — different operator per call per the chosen
367
+ * strategy.
368
+ *
369
+ * ```ts
370
+ * const tcloud = TCloudClient.rotating({
371
+ * apiKey: process.env.TCLOUD_API_KEY,
372
+ * routing: { strategy: 'min-exposure' },
373
+ * })
374
+ * await tcloud.ask('hello')
375
+ * tcloud.getRotationStats() // { callsByOperator: { ... }, currentOperator: '…' }
376
+ * ```
377
+ *
378
+ * Rotation is meaningful only for stateless calls. Sandbox-harness
379
+ * sessions bind to a single operator for the lifetime of the session;
380
+ * `rotating()` clients refuse to dispatch them — see {@link bridge}.
381
+ */
382
+ static rotating(config = {}) {
383
+ const routing = config.routing ?? {};
384
+ const strategy = routing.strategy ?? "min-exposure";
385
+ const { routing: _omit, ...base } = config;
386
+ const client = new _TCloudClient(base);
387
+ const router = new PrivateRouter({
388
+ strategy,
389
+ minOperators: routing.minOperators ?? 1,
390
+ maxRequestsPerOperator: routing.maxRequestsPerOperator,
391
+ excludeOperators: routing.excludeOperators,
392
+ preferRegions: routing.preferRegions
393
+ });
394
+ if (routing.pool && routing.pool.length > 0) {
395
+ router.setOperators(routing.pool);
396
+ client._cachedOperators = routing.pool;
397
+ client._operatorsCachedAt = Date.now();
398
+ }
399
+ ;
400
+ client.privateRouter = router;
401
+ Object.defineProperty(client, ROTATING_MARKER, {
402
+ value: true,
403
+ enumerable: false,
404
+ configurable: false,
405
+ writable: false
406
+ });
407
+ return client;
408
+ }
353
409
  constructor(config = {}) {
354
410
  this.baseURL = (config.baseURL || DEFAULT_BASE_URL).replace(/\/$/, "");
355
411
  this.platformURL = (config.platformURL || DEFAULT_PLATFORM_URL).replace(/\/$/, "");
@@ -665,10 +721,38 @@ var TCloudClient = class _TCloudClient {
665
721
  *
666
722
  * Sessions persist across process restarts — use the same `resume` id
667
723
  * to land on the same CLI conversation (context intact, no replay tax).
724
+ *
725
+ * Guard: clients built via {@link TCloudClient.rotating} cannot dispatch
726
+ * sandbox-harness sessions (rotation rotates per call; a sandbox session
727
+ * binds to one operator). Attempting `bridge({ harness: 'sandbox' })` on
728
+ * a rotating client throws.
668
729
  */
669
730
  bridge(cfg) {
731
+ if (cfg.harness === "sandbox" && this[ROTATING_MARKER] === true) {
732
+ throw new Error(
733
+ "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."
734
+ );
735
+ }
670
736
  return new BridgeSession(this, cfg);
671
737
  }
738
+ /**
739
+ * Rotation stats — populated only on clients created via
740
+ * {@link TCloudClient.rotating}. Non-rotating clients return an empty
741
+ * counter and `currentOperator: null`.
742
+ */
743
+ getRotationStats() {
744
+ if (!this.privateRouter) {
745
+ return { callsByOperator: {}, currentOperator: null };
746
+ }
747
+ const callsByOperator = {};
748
+ for (const row of this.privateRouter.getStats().operatorBreakdown) {
749
+ callsByOperator[row.slug] = row.requests;
750
+ }
751
+ return {
752
+ callsByOperator,
753
+ currentOperator: this.privateRouter.lastSelectedSlug
754
+ };
755
+ }
672
756
  /** Convenience: send a single message and get the text response */
673
757
  async ask(message, modelOrOptions) {
674
758
  const options = typeof modelOrOptions === "string" ? { model: modelOrOptions } : modelOrOptions;
@@ -1170,6 +1254,8 @@ var BridgeSession = class _BridgeSession {
1170
1254
  this.client = client;
1171
1255
  this.cfg = cfg;
1172
1256
  }
1257
+ client;
1258
+ cfg;
1173
1259
  /** Full chat completion (non-streaming). */
1174
1260
  async chat(options) {
1175
1261
  return this.client.chat({ ...options, bridge: this.cfg });
@@ -1239,6 +1325,7 @@ var TCloudError = class extends Error {
1239
1325
  this.status = status;
1240
1326
  this.name = "TCloudError";
1241
1327
  }
1328
+ status;
1242
1329
  };
1243
1330
 
1244
1331
  // src/shielded.ts
@@ -1,5 +1,5 @@
1
1
  import { Hex } from 'viem';
2
- import { a as TCloudConfig, T as TCloudClient, H as SpendAuth } from './client-D7_hFedn.cjs';
2
+ import { a as TCloudConfig, T as TCloudClient, N as SpendAuth } from './client-DkQugNHH.cjs';
3
3
 
4
4
  /**
5
5
  * tcloud/shielded — Ephemeral wallet generation, SpendAuth signing, private inference.
@@ -1,5 +1,5 @@
1
1
  import { Hex } from 'viem';
2
- import { a as TCloudConfig, T as TCloudClient, H as SpendAuth } from './client-D7_hFedn.js';
2
+ import { a as TCloudConfig, T as TCloudClient, N as SpendAuth } from './client-DkQugNHH.js';
3
3
 
4
4
  /**
5
5
  * tcloud/shielded — Ephemeral wallet generation, SpendAuth signing, private inference.
package/dist/shielded.js CHANGED
@@ -3,8 +3,8 @@ import {
3
3
  estimateCost,
4
4
  generateWallet,
5
5
  signSpendAuth
6
- } from "./chunk-A7AEPV2G.js";
7
- import "./chunk-B22AH4JH.js";
6
+ } from "./chunk-4ZUVGKVH.js";
7
+ import "./chunk-M5K3EFNP.js";
8
8
  export {
9
9
  createShieldedClient,
10
10
  estimateCost,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/tcloud",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "TypeScript SDK and CLI for Tangle AI Cloud — decentralized LLM inference",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -20,6 +20,26 @@
20
20
  "default": "./dist/index.cjs"
21
21
  }
22
22
  },
23
+ "./attestation": {
24
+ "import": {
25
+ "types": "./dist/attestation.d.ts",
26
+ "default": "./dist/attestation.js"
27
+ },
28
+ "require": {
29
+ "types": "./dist/attestation.d.cts",
30
+ "default": "./dist/attestation.cjs"
31
+ }
32
+ },
33
+ "./sandbox": {
34
+ "import": {
35
+ "types": "./dist/sandbox.d.ts",
36
+ "default": "./dist/sandbox.js"
37
+ },
38
+ "require": {
39
+ "types": "./dist/sandbox.d.cts",
40
+ "default": "./dist/sandbox.cjs"
41
+ }
42
+ },
23
43
  "./shielded": {
24
44
  "import": {
25
45
  "types": "./dist/shielded.d.ts",
@@ -44,26 +64,28 @@
44
64
  }
45
65
  },
46
66
  "scripts": {
47
- "build": "tsup src/index.ts src/cli.ts src/shielded.ts src/instance.ts --format esm,cjs --dts --clean --external socks-proxy-agent",
67
+ "build": "tsup src/index.ts src/cli.ts src/shielded.ts src/instance.ts src/attestation.ts src/sandbox.ts --format esm,cjs --dts --clean --external socks-proxy-agent",
48
68
  "docs": "typedoc src/index.ts --out docs --name tcloud --readme README.md",
49
69
  "test": "vitest run",
70
+ "test:e2e": "vitest run --config vitest.e2e.config.ts",
50
71
  "dev": "tsx src/cli.ts",
51
72
  "prepublishOnly": "npm run build"
52
73
  },
53
74
  "dependencies": {
54
- "@scure/bip32": "^2.0.1",
55
- "@scure/bip39": "^2.0.1",
56
- "@tangle-network/sandbox": "^0.0.3",
57
- "commander": "^13.0.0",
58
- "viem": "^2.0.0"
75
+ "@scure/bip32": "^2.2.0",
76
+ "@scure/bip39": "^2.2.0",
77
+ "@tangle-network/sandbox": "^0.1.2",
78
+ "@tangle-network/tcloud-attestation": "^0.1.0",
79
+ "commander": "^14.0.3",
80
+ "viem": "^2.48.4"
59
81
  },
60
82
  "devDependencies": {
61
83
  "@types/node": "^22.0.0",
62
- "tsup": "^8.0.0",
84
+ "tsup": "^8.5.1",
63
85
  "tsx": "^4.0.0",
64
- "typedoc": "^0.28.18",
86
+ "typedoc": "^0.28.19",
65
87
  "typescript": "^5.0.0",
66
- "vitest": "^4.1.2"
88
+ "vitest": "^4.1.5"
67
89
  },
68
90
  "files": [
69
91
  "dist",