@tangle-network/sandbox 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,826 +0,0 @@
1
- import { t as SandboxInstance } from "./sandbox-ksXTNlo-.js";
2
- //#region src/tangle/abi.ts
3
- /**
4
- * Tangle Contract ABI Definitions
5
- *
6
- * Viem-compatible ABI for the ITangleJobs precompile, the AgentSandbox
7
- * blueprint contract views/events, and ABI parameter definitions for
8
- * encoding/decoding blueprint job inputs and outputs.
9
- */
10
- const ITangleJobsAbi = [
11
- {
12
- type: "function",
13
- name: "submitJob",
14
- inputs: [
15
- {
16
- name: "serviceId",
17
- type: "uint64"
18
- },
19
- {
20
- name: "jobIndex",
21
- type: "uint8"
22
- },
23
- {
24
- name: "inputs",
25
- type: "bytes"
26
- }
27
- ],
28
- outputs: [{
29
- name: "callId",
30
- type: "uint64"
31
- }],
32
- stateMutability: "payable"
33
- },
34
- {
35
- type: "function",
36
- name: "getJobCall",
37
- inputs: [{
38
- name: "serviceId",
39
- type: "uint64"
40
- }, {
41
- name: "callId",
42
- type: "uint64"
43
- }],
44
- outputs: [{
45
- name: "",
46
- type: "tuple",
47
- components: [
48
- {
49
- name: "jobIndex",
50
- type: "uint8"
51
- },
52
- {
53
- name: "caller",
54
- type: "address"
55
- },
56
- {
57
- name: "createdAt",
58
- type: "uint64"
59
- },
60
- {
61
- name: "resultCount",
62
- type: "uint32"
63
- },
64
- {
65
- name: "payment",
66
- type: "uint256"
67
- },
68
- {
69
- name: "completed",
70
- type: "bool"
71
- }
72
- ]
73
- }],
74
- stateMutability: "view"
75
- },
76
- {
77
- type: "event",
78
- name: "JobSubmitted",
79
- inputs: [
80
- {
81
- name: "serviceId",
82
- type: "uint64",
83
- indexed: true
84
- },
85
- {
86
- name: "callId",
87
- type: "uint64",
88
- indexed: true
89
- },
90
- {
91
- name: "jobIndex",
92
- type: "uint8",
93
- indexed: true
94
- },
95
- {
96
- name: "caller",
97
- type: "address",
98
- indexed: false
99
- },
100
- {
101
- name: "inputs",
102
- type: "bytes",
103
- indexed: false
104
- }
105
- ]
106
- },
107
- {
108
- type: "event",
109
- name: "JobResultSubmitted",
110
- inputs: [
111
- {
112
- name: "serviceId",
113
- type: "uint64",
114
- indexed: true
115
- },
116
- {
117
- name: "callId",
118
- type: "uint64",
119
- indexed: true
120
- },
121
- {
122
- name: "operator",
123
- type: "address",
124
- indexed: true
125
- },
126
- {
127
- name: "result",
128
- type: "bytes",
129
- indexed: false
130
- }
131
- ]
132
- }
133
- ];
134
- const SandboxCreateParamTypes = [
135
- {
136
- name: "name",
137
- type: "string"
138
- },
139
- {
140
- name: "image",
141
- type: "string"
142
- },
143
- {
144
- name: "stack",
145
- type: "string"
146
- },
147
- {
148
- name: "agent_identifier",
149
- type: "string"
150
- },
151
- {
152
- name: "env_json",
153
- type: "string"
154
- },
155
- {
156
- name: "metadata_json",
157
- type: "string"
158
- },
159
- {
160
- name: "ssh_enabled",
161
- type: "bool"
162
- },
163
- {
164
- name: "ssh_public_keys",
165
- type: "string[]"
166
- },
167
- {
168
- name: "web_terminal_enabled",
169
- type: "bool"
170
- },
171
- {
172
- name: "max_lifetime_seconds",
173
- type: "uint64"
174
- },
175
- {
176
- name: "idle_timeout_seconds",
177
- type: "uint64"
178
- },
179
- {
180
- name: "cpu_cores",
181
- type: "uint64"
182
- },
183
- {
184
- name: "memory_mb",
185
- type: "uint64"
186
- },
187
- {
188
- name: "disk_gb",
189
- type: "uint64"
190
- },
191
- {
192
- name: "tee_required",
193
- type: "bool"
194
- },
195
- {
196
- name: "tee_type",
197
- type: "uint8"
198
- },
199
- {
200
- name: "attestation_nonce",
201
- type: "string"
202
- }
203
- ];
204
- const SandboxIdParamTypes = [{
205
- name: "sandbox_id",
206
- type: "string"
207
- }];
208
- const SandboxCreateResponseParamTypes = [{
209
- name: "sandboxId",
210
- type: "string"
211
- }, {
212
- name: "json",
213
- type: "string"
214
- }];
215
- const JsonResponseParamTypes = [{
216
- name: "json",
217
- type: "string"
218
- }];
219
- const AgentSandboxBlueprintAbi = [
220
- {
221
- type: "function",
222
- name: "getAvailableCapacity",
223
- inputs: [],
224
- outputs: [{
225
- name: "available",
226
- type: "uint32"
227
- }],
228
- stateMutability: "view"
229
- },
230
- {
231
- type: "function",
232
- name: "getServiceStats",
233
- inputs: [],
234
- outputs: [{
235
- name: "totalSandboxes",
236
- type: "uint32"
237
- }, {
238
- name: "totalCapacity",
239
- type: "uint32"
240
- }],
241
- stateMutability: "view"
242
- },
243
- {
244
- type: "function",
245
- name: "getOperatorLoad",
246
- inputs: [{
247
- name: "operator",
248
- type: "address"
249
- }],
250
- outputs: [{
251
- name: "active",
252
- type: "uint32"
253
- }, {
254
- name: "max",
255
- type: "uint32"
256
- }],
257
- stateMutability: "view"
258
- },
259
- {
260
- type: "function",
261
- name: "getSandboxOperator",
262
- inputs: [{
263
- name: "sandboxId",
264
- type: "string"
265
- }],
266
- outputs: [{
267
- name: "",
268
- type: "address"
269
- }],
270
- stateMutability: "view"
271
- },
272
- {
273
- type: "function",
274
- name: "isSandboxActive",
275
- inputs: [{
276
- name: "sandboxId",
277
- type: "string"
278
- }],
279
- outputs: [{
280
- name: "",
281
- type: "bool"
282
- }],
283
- stateMutability: "view"
284
- },
285
- {
286
- type: "event",
287
- name: "OperatorAssigned",
288
- inputs: [
289
- {
290
- name: "serviceId",
291
- type: "uint64",
292
- indexed: true
293
- },
294
- {
295
- name: "callId",
296
- type: "uint64",
297
- indexed: true
298
- },
299
- {
300
- name: "operator",
301
- type: "address",
302
- indexed: true
303
- }
304
- ]
305
- },
306
- {
307
- type: "event",
308
- name: "OperatorRouted",
309
- inputs: [
310
- {
311
- name: "serviceId",
312
- type: "uint64",
313
- indexed: true
314
- },
315
- {
316
- name: "callId",
317
- type: "uint64",
318
- indexed: true
319
- },
320
- {
321
- name: "operator",
322
- type: "address",
323
- indexed: true
324
- }
325
- ]
326
- },
327
- {
328
- type: "event",
329
- name: "SandboxCreated",
330
- inputs: [{
331
- name: "sandboxHash",
332
- type: "bytes32",
333
- indexed: true
334
- }, {
335
- name: "operator",
336
- type: "address",
337
- indexed: true
338
- }]
339
- },
340
- {
341
- type: "event",
342
- name: "SandboxDeleted",
343
- inputs: [{
344
- name: "sandboxHash",
345
- type: "bytes32",
346
- indexed: true
347
- }, {
348
- name: "operator",
349
- type: "address",
350
- indexed: true
351
- }]
352
- }
353
- ];
354
- //#endregion
355
- //#region src/tangle/types.ts
356
- /**
357
- * Tangle Sandbox Client types and constants.
358
- */
359
- /** Tangle mainnet chain ID. */
360
- const TANGLE_CHAIN_ID = 5845;
361
- /** Tangle mainnet RPC endpoint. */
362
- const TANGLE_MAINNET_RPC = "https://rpc.tangle.tools";
363
- /** ITangleJobs precompile contract address. */
364
- const TANGLE_JOBS_CONTRACT = "0x0000000000000000000000000000000000000808";
365
- /** Job indices matching the blueprint's job IDs. */
366
- const JOB_SANDBOX_CREATE = 0;
367
- const JOB_SANDBOX_DELETE = 1;
368
- //#endregion
369
- //#region src/tangle/chain-client.ts
370
- function confidentialFromOptions(options) {
371
- if (options.confidential) return options.confidential;
372
- const profile = options.backend?.profile;
373
- if (typeof profile === "object" && profile?.confidential) return profile.confidential;
374
- }
375
- function teeTypeId(tee) {
376
- switch (tee) {
377
- case "tdx":
378
- case "phala-dstack": return 1;
379
- case "nitro": return 2;
380
- case "sev-snp": return 3;
381
- case "any":
382
- case void 0: return 0;
383
- default: throw new Error(`Unsupported TEE type for Tangle blueprint: ${tee}`);
384
- }
385
- }
386
- function teeCreateValues(options) {
387
- const confidential = confidentialFromOptions(options);
388
- const teeRequired = Boolean(confidential?.tee);
389
- return [
390
- teeRequired,
391
- teeRequired ? teeTypeId(confidential?.tee) : 0,
392
- confidential?.attestationNonce ?? ""
393
- ];
394
- }
395
- var TangleChainClient = class {
396
- config;
397
- publicClient = null;
398
- walletClient = null;
399
- account = null;
400
- constructor(config) {
401
- this.config = config;
402
- }
403
- async ensureInitialized() {
404
- if (this.publicClient && this.walletClient) return {
405
- publicClient: this.publicClient,
406
- walletClient: this.walletClient
407
- };
408
- let viem;
409
- try {
410
- viem = await import("viem");
411
- } catch {
412
- throw new Error("viem is required for TangleSandboxClient. Install it: npm install viem");
413
- }
414
- const rpcUrl = this.config.rpcUrl ?? "https://rpc.tangle.tools";
415
- const chain = {
416
- id: TANGLE_CHAIN_ID,
417
- name: "Tangle",
418
- nativeCurrency: {
419
- name: "TNT",
420
- symbol: "TNT",
421
- decimals: 18
422
- },
423
- rpcUrls: { default: { http: [rpcUrl] } }
424
- };
425
- const transport = viem.http(rpcUrl);
426
- this.publicClient = viem.createPublicClient({
427
- chain,
428
- transport
429
- });
430
- if (this.config.privateKey) {
431
- const { privateKeyToAccount } = await import("viem/accounts");
432
- this.account = privateKeyToAccount(this.config.privateKey);
433
- this.walletClient = viem.createWalletClient({
434
- account: this.account,
435
- chain,
436
- transport
437
- });
438
- } else if (this.config.wallet) {
439
- const wallet = this.config.wallet;
440
- this.walletClient = wallet;
441
- this.account = wallet.account ?? null;
442
- } else throw new Error("TangleSandboxClient requires either privateKey or wallet");
443
- return {
444
- publicClient: this.publicClient,
445
- walletClient: this.walletClient
446
- };
447
- }
448
- encodeSandboxCreateInputs(options) {
449
- return {
450
- types: SandboxCreateParamTypes,
451
- values: [
452
- options.name ?? "",
453
- options.image ?? "",
454
- "",
455
- "",
456
- options.env ? JSON.stringify(options.env) : "",
457
- options.metadata ? JSON.stringify(options.metadata) : "",
458
- options.sshEnabled ?? false,
459
- options.sshPublicKeys ?? (options.sshPublicKey ? [options.sshPublicKey] : []),
460
- options.webTerminalEnabled ?? false,
461
- BigInt(options.maxLifetimeSeconds ?? 0),
462
- BigInt(options.idleTimeoutSeconds ?? 0),
463
- BigInt(options.resources?.cpuCores ?? 0),
464
- BigInt(options.resources?.memoryMB ?? 0),
465
- BigInt(options.resources?.diskGB ?? 0),
466
- ...teeCreateValues(options)
467
- ]
468
- };
469
- }
470
- encodeSandboxIdInputs(sandboxId) {
471
- return {
472
- types: SandboxIdParamTypes,
473
- values: [sandboxId]
474
- };
475
- }
476
- async submitJobAndWait(jobIndex, encoded) {
477
- const { publicClient, walletClient } = await this.ensureInitialized();
478
- const viem = await import("viem");
479
- const contractAddress = this.config.contractAddress ?? "0x0000000000000000000000000000000000000808";
480
- const serviceId = this.config.serviceId;
481
- const inputs = viem.encodeAbiParameters(encoded.types, encoded.values);
482
- const hash = await walletClient.writeContract({
483
- address: contractAddress,
484
- abi: ITangleJobsAbi,
485
- functionName: "submitJob",
486
- args: [
487
- serviceId,
488
- jobIndex,
489
- inputs
490
- ]
491
- });
492
- const receipt = await publicClient.waitForTransactionReceipt({ hash });
493
- const jobSubmittedEvent = receipt.logs.find((log) => {
494
- try {
495
- return viem.decodeEventLog({
496
- abi: ITangleJobsAbi,
497
- data: log.data,
498
- topics: log.topics
499
- }).eventName === "JobSubmitted";
500
- } catch {
501
- return false;
502
- }
503
- });
504
- if (!jobSubmittedEvent) throw new Error("JobSubmitted event not found in transaction receipt");
505
- const callId = viem.decodeEventLog({
506
- abi: ITangleJobsAbi,
507
- data: jobSubmittedEvent.data,
508
- topics: jobSubmittedEvent.topics
509
- }).args.callId;
510
- const timeoutMs = this.config.jobTimeoutMs ?? 144e5;
511
- const pollIntervalMs = this.config.pollIntervalMs ?? 5e3;
512
- const deadline = Date.now() + timeoutMs;
513
- while (Date.now() < deadline) {
514
- await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
515
- const jobCall = await publicClient.readContract({
516
- address: contractAddress,
517
- abi: ITangleJobsAbi,
518
- functionName: "getJobCall",
519
- args: [serviceId, callId]
520
- });
521
- if (jobCall.completed) return {
522
- result: jobCall,
523
- callId,
524
- blockNumber: receipt.blockNumber
525
- };
526
- }
527
- throw new Error(`Job ${jobIndex} (callId=${callId}) did not complete within ${timeoutMs}ms`);
528
- }
529
- async getJobResult(callId, fromBlock) {
530
- const { publicClient } = await this.ensureInitialized();
531
- const viem = await import("viem");
532
- const contractAddress = this.config.contractAddress ?? "0x0000000000000000000000000000000000000808";
533
- const logs = await publicClient.getLogs({
534
- address: contractAddress,
535
- event: viem.parseAbiItem("event JobResultSubmitted(uint64 indexed serviceId, uint64 indexed callId, address indexed operator, bytes result)"),
536
- args: {
537
- serviceId: this.config.serviceId,
538
- callId
539
- },
540
- fromBlock: fromBlock ?? "earliest"
541
- });
542
- if (logs.length === 0) return null;
543
- return logs[0].args.result ?? null;
544
- }
545
- async getAvailableCapacity() {
546
- const { publicClient } = await this.ensureInitialized();
547
- if (!this.config.blueprintContractAddress) throw new Error("blueprintContractAddress is required for getAvailableCapacity");
548
- const result = await publicClient.readContract({
549
- address: this.config.blueprintContractAddress,
550
- abi: AgentSandboxBlueprintAbi,
551
- functionName: "getAvailableCapacity"
552
- });
553
- return Number(result);
554
- }
555
- async getServiceStats() {
556
- const { publicClient } = await this.ensureInitialized();
557
- if (!this.config.blueprintContractAddress) throw new Error("blueprintContractAddress is required for getServiceStats");
558
- const [totalSandboxes, totalCapacity] = await publicClient.readContract({
559
- address: this.config.blueprintContractAddress,
560
- abi: AgentSandboxBlueprintAbi,
561
- functionName: "getServiceStats"
562
- });
563
- return {
564
- totalSandboxes: Number(totalSandboxes),
565
- totalCapacity: Number(totalCapacity)
566
- };
567
- }
568
- getResponseParamTypes(jobIndex) {
569
- if (jobIndex === 0) return SandboxCreateResponseParamTypes;
570
- return JsonResponseParamTypes;
571
- }
572
- };
573
- //#endregion
574
- //#region src/tangle/client.ts
575
- function randomHexToken(bytes) {
576
- if (typeof globalThis.crypto === "undefined" || typeof globalThis.crypto.getRandomValues !== "function") throw new Error("randomHexToken: globalThis.crypto.getRandomValues is unavailable; this runtime cannot generate cryptographically secure nonces. Run on a host with WebCrypto support (Node 20+, browsers, Workers).");
577
- const buf = new Uint8Array(bytes);
578
- globalThis.crypto.getRandomValues(buf);
579
- return Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
580
- }
581
- function attestationNonceFromOptions(options) {
582
- const direct = options.confidential?.attestationNonce;
583
- if (direct) return direct;
584
- const profile = options.backend?.profile;
585
- if (typeof profile === "object") return profile.confidential?.attestationNonce;
586
- }
587
- function withAttestationNonce(options) {
588
- if (options.confidential?.attestationRefresh && !options.confidential.attestationNonce) return {
589
- ...options,
590
- confidential: {
591
- ...options.confidential,
592
- attestationNonce: randomHexToken(32)
593
- }
594
- };
595
- const profile = options.backend?.profile;
596
- if (options.backend && typeof profile === "object" && profile.confidential?.attestationRefresh && !profile.confidential.attestationNonce) return {
597
- ...options,
598
- backend: {
599
- ...options.backend,
600
- profile: {
601
- ...profile,
602
- confidential: {
603
- ...profile.confidential,
604
- attestationNonce: randomHexToken(32)
605
- }
606
- }
607
- }
608
- };
609
- return options;
610
- }
611
- var TangleSandboxClient = class {
612
- chain;
613
- sandboxes = /* @__PURE__ */ new Map();
614
- operatorApiUrl;
615
- operatorApiToken;
616
- constructor(config) {
617
- if (!config.privateKey && !config.wallet) throw new Error("TangleSandboxClient requires either privateKey or wallet");
618
- this.chain = new TangleChainClient(config);
619
- this.operatorApiUrl = config.operatorApiUrl?.replace(/\/+$/, "");
620
- this.operatorApiToken = config.operatorApiToken;
621
- }
622
- /**
623
- * Create a sandbox via on-chain job submission.
624
- */
625
- async create(options) {
626
- const createOptions = withAttestationNonce(options ?? {});
627
- const encoded = this.chain.encodeSandboxCreateInputs(createOptions);
628
- const { result: _result, callId, blockNumber } = await this.chain.submitJobAndWait(0, encoded);
629
- const resultBytes = await this.chain.getJobResult(callId, blockNumber);
630
- let sandboxId;
631
- let responseJson = {};
632
- if (resultBytes) try {
633
- const decoded = (await import("viem")).decodeAbiParameters(this.chain.getResponseParamTypes(0), resultBytes);
634
- sandboxId = decoded[0];
635
- try {
636
- responseJson = JSON.parse(decoded[1]);
637
- } catch {}
638
- } catch {
639
- throw new Error("Failed to decode create job result");
640
- }
641
- else throw new Error("No result returned from create job");
642
- const runtimeUrl = responseJson.runtimeUrl ?? responseJson.sidecarUrl ?? responseJson.sidecar_url ?? "";
643
- const runtimeToken = responseJson.token ?? responseJson.runtimeToken ?? responseJson.sidecarToken ?? "";
644
- if (runtimeUrl && !runtimeToken) throw new Error("Create job returned a runtime URL without an auth token");
645
- const entry = {
646
- id: sandboxId,
647
- runtimeUrl,
648
- runtimeToken,
649
- status: "running",
650
- createdAt: /* @__PURE__ */ new Date(),
651
- teeAttestationJson: responseJson.teeAttestationJson,
652
- teePublicKeyJson: responseJson.teePublicKeyJson,
653
- attestationNonce: attestationNonceFromOptions(createOptions)
654
- };
655
- this.sandboxes.set(sandboxId, entry);
656
- const info = {
657
- id: sandboxId,
658
- name: options?.name,
659
- status: "running",
660
- connection: runtimeUrl ? {
661
- runtimeUrl,
662
- authToken: runtimeToken
663
- } : void 0,
664
- metadata: {
665
- ...createOptions.metadata ?? {},
666
- ...entry.teeAttestationJson ? { teeAttestationJson: entry.teeAttestationJson } : {},
667
- ...entry.teePublicKeyJson ? { teePublicKeyJson: entry.teePublicKeyJson } : {},
668
- ...entry.attestationNonce ? { attestationNonce: entry.attestationNonce } : {}
669
- },
670
- createdAt: entry.createdAt
671
- };
672
- return new SandboxInstance(this, info);
673
- }
674
- /**
675
- * Route interception for SandboxInstance lifecycle calls.
676
- *
677
- * SandboxInstance calls this.client.fetch() for:
678
- * - GET /v1/sandboxes/:id → return from local tracking
679
- * - POST /v1/sandboxes/:id/stop → unsupported until blueprint exposes stop
680
- * - POST /v1/sandboxes/:id/resume → unsupported until blueprint exposes resume
681
- * - DELETE /v1/sandboxes/:id → on-chain JOB_SANDBOX_DELETE
682
- */
683
- async fetch(path, options) {
684
- const method = options?.method ?? "GET";
685
- const runtimeMatch = path.match(/^\/v1\/sandboxes\/([^/]+)\/runtime(\/.*)$/);
686
- if (runtimeMatch) {
687
- const id = decodeURIComponent(runtimeMatch[1]);
688
- const runtimePath = runtimeMatch[2];
689
- return this.fetchRuntime(id, runtimePath, options);
690
- }
691
- const getMatch = path.match(/^\/v1\/sandboxes\/([^/]+)$/);
692
- if (getMatch && method === "GET") {
693
- const id = decodeURIComponent(getMatch[1]);
694
- const entry = this.sandboxes.get(id);
695
- if (!entry) return new Response(JSON.stringify({ error: "Not found" }), { status: 404 });
696
- return new Response(JSON.stringify({
697
- id: entry.id,
698
- status: entry.status,
699
- connection: entry.runtimeUrl ? {
700
- runtimeUrl: entry.runtimeUrl,
701
- authToken: entry.runtimeToken
702
- } : void 0,
703
- metadata: {
704
- ...entry.teeAttestationJson ? { teeAttestationJson: entry.teeAttestationJson } : {},
705
- ...entry.teePublicKeyJson ? { teePublicKeyJson: entry.teePublicKeyJson } : {},
706
- ...entry.attestationNonce ? { attestationNonce: entry.attestationNonce } : {}
707
- },
708
- createdAt: entry.createdAt.toISOString()
709
- }), {
710
- status: 200,
711
- headers: { "Content-Type": "application/json" }
712
- });
713
- }
714
- const attestationMatch = path.match(/^\/v1\/sandboxes\/([^/]+)\/tee\/attestation$/);
715
- if (attestationMatch && method === "GET") {
716
- const id = decodeURIComponent(attestationMatch[1]);
717
- if (this.operatorApiUrl) return this.fetchOperatorApi(`/api/sandboxes/${encodeURIComponent(id)}/tee/attestation`, options);
718
- const response = await this.fetchRuntime(id, "/tee/attestation", {
719
- ...options,
720
- method: "GET"
721
- });
722
- if (!response.ok) return response;
723
- const attestation = await response.json();
724
- return new Response(JSON.stringify({
725
- sandbox_id: id,
726
- attestation
727
- }), {
728
- status: 200,
729
- headers: { "Content-Type": "application/json" }
730
- });
731
- }
732
- if (attestationMatch && method === "POST") {
733
- if (this.operatorApiUrl) {
734
- const id = decodeURIComponent(attestationMatch[1]);
735
- return this.fetchOperatorApi(`/api/sandboxes/${encodeURIComponent(id)}/tee/attestation`, options);
736
- }
737
- return new Response(JSON.stringify({ error: "Nonce-bound TEE attestation requires the operator API and is not available through the direct sidecar Tangle client" }), {
738
- status: 501,
739
- headers: { "Content-Type": "application/json" }
740
- });
741
- }
742
- const publicKeyMatch = path.match(/^\/v1\/sandboxes\/([^/]+)\/tee\/public-key$/);
743
- if (publicKeyMatch && method === "GET") {
744
- const id = decodeURIComponent(publicKeyMatch[1]);
745
- if (this.operatorApiUrl) return this.fetchOperatorApi(`/api/sandboxes/${encodeURIComponent(id)}/tee/public-key`, options);
746
- const response = await this.fetchRuntime(id, "/tee/public-key", {
747
- ...options,
748
- method: "GET"
749
- });
750
- if (!response.ok) return response;
751
- const publicKey = await response.json();
752
- return new Response(JSON.stringify({
753
- sandbox_id: id,
754
- public_key: publicKey
755
- }), {
756
- status: 200,
757
- headers: { "Content-Type": "application/json" }
758
- });
759
- }
760
- if (path.match(/^\/v1\/sandboxes\/([^/]+)\/stop$/) && method === "POST") return new Response(JSON.stringify({ error: "Sandbox stop is not exposed by the current Tangle blueprint" }), {
761
- status: 501,
762
- headers: { "Content-Type": "application/json" }
763
- });
764
- if (path.match(/^\/v1\/sandboxes\/([^/]+)\/resume$/) && method === "POST") return new Response(JSON.stringify({ error: "Sandbox resume is not exposed by the current Tangle blueprint" }), {
765
- status: 501,
766
- headers: { "Content-Type": "application/json" }
767
- });
768
- const deleteMatch = path.match(/^\/v1\/sandboxes\/([^/]+)$/);
769
- if (deleteMatch && method === "DELETE") {
770
- const id = decodeURIComponent(deleteMatch[1]);
771
- const encoded = this.chain.encodeSandboxIdInputs(id);
772
- await this.chain.submitJobAndWait(1, encoded);
773
- const entry = this.sandboxes.get(id);
774
- if (entry) entry.status = "deleted";
775
- this.sandboxes.delete(id);
776
- return new Response(null, { status: 204 });
777
- }
778
- throw new Error(`Operation not supported via on-chain client: ${method} ${path}`);
779
- }
780
- async fetchRuntime(sandboxId, runtimePath, options) {
781
- const entry = this.sandboxes.get(sandboxId);
782
- if (!entry?.runtimeUrl) return new Response(JSON.stringify({ error: "Sandbox runtime unavailable" }), {
783
- status: 503,
784
- headers: { "Content-Type": "application/json" }
785
- });
786
- const headers = new Headers(options?.headers);
787
- headers.set("Authorization", `Bearer ${entry.runtimeToken}`);
788
- const isFormDataBody = typeof FormData !== "undefined" && options?.body instanceof FormData;
789
- if (!headers.has("Content-Type") && !isFormDataBody) headers.set("Content-Type", "application/json");
790
- const method = options?.method ?? "GET";
791
- return await fetch(`${entry.runtimeUrl.replace(/\/$/, "")}${runtimePath}`, {
792
- ...options,
793
- method,
794
- headers
795
- });
796
- }
797
- /**
798
- * Get available capacity from the blueprint contract.
799
- * Requires blueprintContractAddress in config.
800
- */
801
- async getAvailableCapacity() {
802
- return this.chain.getAvailableCapacity();
803
- }
804
- /**
805
- * Get service stats from the blueprint contract.
806
- * Requires blueprintContractAddress in config.
807
- */
808
- async getServiceStats() {
809
- return this.chain.getServiceStats();
810
- }
811
- async fetchOperatorApi(path, options) {
812
- if (!this.operatorApiUrl) return new Response(JSON.stringify({ error: "Operator API URL is not configured" }), {
813
- status: 501,
814
- headers: { "Content-Type": "application/json" }
815
- });
816
- const headers = new Headers(options?.headers);
817
- if (this.operatorApiToken && !headers.has("Authorization")) headers.set("Authorization", `Bearer ${this.operatorApiToken}`);
818
- if (options?.body && !headers.has("Content-Type")) headers.set("Content-Type", "application/json");
819
- return fetch(`${this.operatorApiUrl}${path}`, {
820
- ...options,
821
- headers
822
- });
823
- }
824
- };
825
- //#endregion
826
- export { TANGLE_JOBS_CONTRACT as a, ITangleJobsAbi as c, SandboxCreateResponseParamTypes as d, SandboxIdParamTypes as f, TANGLE_CHAIN_ID as i, JsonResponseParamTypes as l, JOB_SANDBOX_CREATE as n, TANGLE_MAINNET_RPC as o, JOB_SANDBOX_DELETE as r, AgentSandboxBlueprintAbi as s, TangleSandboxClient as t, SandboxCreateParamTypes as u };