@tangle-network/tcloud 0.4.5 → 0.4.8
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/README.md +6 -5
- package/dist/attestation.cjs +4 -0
- package/dist/attestation.d.cts +1 -1
- package/dist/attestation.d.ts +1 -1
- package/dist/attestation.js +4 -0
- package/dist/{chunk-MB4VK4MM.js → chunk-27OTTDMZ.js} +27 -3
- package/dist/{chunk-M5K3EFNP.js → chunk-CVWEKCQ3.js} +150 -15
- package/dist/{chunk-DBIT227N.js → chunk-DRCOPW7D.js} +8 -1
- package/dist/{chunk-4ZUVGKVH.js → chunk-YWN4JOCW.js} +1 -1
- package/dist/cli.cjs +298 -19
- package/dist/cli.js +126 -6
- package/dist/{client-DkQugNHH.d.cts → client-CF-tpVsi.d.cts} +80 -9
- package/dist/{client-DkQugNHH.d.ts → client-CF-tpVsi.d.ts} +80 -9
- package/dist/index.cjs +180 -17
- package/dist/index.d.cts +19 -7
- package/dist/index.d.ts +19 -7
- package/dist/index.js +8 -4
- package/dist/instance.cjs +150 -15
- package/dist/instance.d.cts +2 -1
- package/dist/instance.d.ts +2 -1
- package/dist/instance.js +1 -1
- package/dist/sandbox.cjs +8 -1
- package/dist/sandbox.d.cts +20 -0
- package/dist/sandbox.d.ts +20 -0
- package/dist/sandbox.js +1 -1
- package/dist/shielded.cjs +150 -15
- package/dist/shielded.d.cts +2 -1
- package/dist/shielded.d.ts +2 -1
- package/dist/shielded.js +2 -2
- package/package.json +7 -2
package/dist/instance.cjs
CHANGED
|
@@ -263,6 +263,7 @@ var PrivateRouter = class {
|
|
|
263
263
|
|
|
264
264
|
// src/client.ts
|
|
265
265
|
var ROTATING_MARKER = "__tcloudRotating";
|
|
266
|
+
var DIRECT_CLI_BRIDGE_MARKER = "__tcloudDirectCliBridge";
|
|
266
267
|
var DEFAULT_BASE_URL = "https://router.tangle.tools/v1";
|
|
267
268
|
var SDK_VERSION = "0.4.0";
|
|
268
269
|
async function proxiedFetch(privacy, url, init, streaming) {
|
|
@@ -312,6 +313,43 @@ var DEFAULT_RETRY = {
|
|
|
312
313
|
};
|
|
313
314
|
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
314
315
|
var DEFAULT_PLATFORM_URL = "https://id.tangle.tools";
|
|
316
|
+
var PROTECTED_PROVIDER_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
317
|
+
"model",
|
|
318
|
+
"messages",
|
|
319
|
+
"temperature",
|
|
320
|
+
"max_tokens",
|
|
321
|
+
"maxTokens",
|
|
322
|
+
"stream",
|
|
323
|
+
"stop",
|
|
324
|
+
"top_p",
|
|
325
|
+
"topP",
|
|
326
|
+
"frequency_penalty",
|
|
327
|
+
"frequencyPenalty",
|
|
328
|
+
"presence_penalty",
|
|
329
|
+
"presencePenalty",
|
|
330
|
+
"response_format",
|
|
331
|
+
"responseFormat",
|
|
332
|
+
"tools",
|
|
333
|
+
"tool_choice",
|
|
334
|
+
"toolChoice",
|
|
335
|
+
"gateway",
|
|
336
|
+
"bridge",
|
|
337
|
+
"agent_profile",
|
|
338
|
+
"agentProfile",
|
|
339
|
+
"session_id",
|
|
340
|
+
"sessionId"
|
|
341
|
+
]);
|
|
342
|
+
function sanitizeProviderOptions(providerOptions) {
|
|
343
|
+
if (!providerOptions) return {};
|
|
344
|
+
for (const key of Object.keys(providerOptions)) {
|
|
345
|
+
if (PROTECTED_PROVIDER_OPTION_KEYS.has(key)) {
|
|
346
|
+
throw new Error(
|
|
347
|
+
`providerOptions cannot override protected chat field "${key}"; use the typed ChatOptions field instead`
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return providerOptions;
|
|
352
|
+
}
|
|
315
353
|
var TCloudClient = class _TCloudClient {
|
|
316
354
|
baseURL;
|
|
317
355
|
platformURL;
|
|
@@ -352,13 +390,21 @@ var TCloudClient = class _TCloudClient {
|
|
|
352
390
|
* const reply = await client.ask('explain X', 'claude-code/sonnet')
|
|
353
391
|
* ```
|
|
354
392
|
*
|
|
355
|
-
* For session-resumable agentic dispatches (
|
|
356
|
-
*
|
|
357
|
-
*
|
|
393
|
+
* For session-resumable agentic dispatches, use `client.bridge(...)` on
|
|
394
|
+
* the returned direct client. `resume` is serialized to cli-bridge's
|
|
395
|
+
* `session_id` body field and the model wire format stays
|
|
396
|
+
* `<harness>/<model>` without the router-only `bridge/` prefix.
|
|
358
397
|
*/
|
|
359
398
|
static fromCliBridge(opts) {
|
|
360
399
|
const baseURL = opts.url.replace(/\/+$/, "") + "/v1";
|
|
361
|
-
|
|
400
|
+
const client = new _TCloudClient({ ...opts.config ?? {}, apiKey: opts.bearer, baseURL });
|
|
401
|
+
Object.defineProperty(client, DIRECT_CLI_BRIDGE_MARKER, {
|
|
402
|
+
value: true,
|
|
403
|
+
enumerable: false,
|
|
404
|
+
configurable: false,
|
|
405
|
+
writable: false
|
|
406
|
+
});
|
|
407
|
+
return client;
|
|
362
408
|
}
|
|
363
409
|
/**
|
|
364
410
|
* Build a client that rotates which operator serves each call. Mirrors
|
|
@@ -370,7 +416,7 @@ var TCloudClient = class _TCloudClient {
|
|
|
370
416
|
*
|
|
371
417
|
* ```ts
|
|
372
418
|
* const tcloud = TCloudClient.rotating({
|
|
373
|
-
* apiKey: process.env.
|
|
419
|
+
* apiKey: process.env.TANGLE_API_KEY,
|
|
374
420
|
* routing: { strategy: 'min-exposure' },
|
|
375
421
|
* })
|
|
376
422
|
* await tcloud.ask('hello')
|
|
@@ -411,7 +457,7 @@ var TCloudClient = class _TCloudClient {
|
|
|
411
457
|
constructor(config = {}) {
|
|
412
458
|
this.baseURL = (config.baseURL || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
413
459
|
this.platformURL = (config.platformURL || DEFAULT_PLATFORM_URL).replace(/\/$/, "");
|
|
414
|
-
this.apiKey = config.apiKey || process.env.TCLOUD_API_KEY;
|
|
460
|
+
this.apiKey = config.apiKey || process.env.TANGLE_API_KEY || process.env.TCLOUD_API_KEY;
|
|
415
461
|
this.model = config.model || "gpt-4o-mini";
|
|
416
462
|
this.privacy = config.privacy;
|
|
417
463
|
this.limits = config.limits;
|
|
@@ -620,8 +666,8 @@ var TCloudClient = class _TCloudClient {
|
|
|
620
666
|
delete headers["Authorization"];
|
|
621
667
|
}
|
|
622
668
|
}
|
|
623
|
-
if (bridge) {
|
|
624
|
-
headers["X-Bridge-Unlock"] = bridge.unlock;
|
|
669
|
+
if (bridge && !this._isDirectCliBridge()) {
|
|
670
|
+
headers["X-Bridge-Unlock"] = bridge.unlock ?? "";
|
|
625
671
|
if (bridge.resume) headers["X-Resume"] = bridge.resume;
|
|
626
672
|
if (bridge.bridgeUrl) headers["X-Bridge-Url"] = bridge.bridgeUrl;
|
|
627
673
|
if (bridge.bridgeBearer) headers["X-Bridge-Bearer"] = bridge.bridgeBearer;
|
|
@@ -634,13 +680,24 @@ var TCloudClient = class _TCloudClient {
|
|
|
634
680
|
*/
|
|
635
681
|
_effectiveModel(options) {
|
|
636
682
|
if (options.bridge) {
|
|
683
|
+
if (this._isDirectCliBridge()) {
|
|
684
|
+
return options.bridge.model ? `${options.bridge.harness}/${options.bridge.model}` : options.bridge.harness;
|
|
685
|
+
}
|
|
637
686
|
return options.bridge.model ? `bridge/${options.bridge.harness}/${options.bridge.model}` : `bridge/${options.bridge.harness}`;
|
|
638
687
|
}
|
|
639
688
|
return options.model || this.model;
|
|
640
689
|
}
|
|
641
690
|
/** Build the chat completions request body */
|
|
642
691
|
_chatBody(options, stream) {
|
|
692
|
+
const providerOptions = sanitizeProviderOptions(options.providerOptions);
|
|
693
|
+
const sandboxBody = {};
|
|
694
|
+
if (options.sandbox?.agentProfile) sandboxBody.agent_profile = options.sandbox.agentProfile;
|
|
695
|
+
if (options.sandbox?.sessionId) sandboxBody.session_id = options.sandbox.sessionId;
|
|
696
|
+
if (this._isDirectCliBridge() && options.bridge?.resume && !sandboxBody.session_id) {
|
|
697
|
+
sandboxBody.session_id = options.bridge.resume;
|
|
698
|
+
}
|
|
643
699
|
return JSON.stringify({
|
|
700
|
+
...providerOptions,
|
|
644
701
|
model: this._effectiveModel(options),
|
|
645
702
|
messages: options.messages,
|
|
646
703
|
temperature: options.temperature,
|
|
@@ -654,7 +711,7 @@ var TCloudClient = class _TCloudClient {
|
|
|
654
711
|
tools: options.tools,
|
|
655
712
|
tool_choice: options.toolChoice,
|
|
656
713
|
...options.gateway ? { gateway: options.gateway } : {},
|
|
657
|
-
...
|
|
714
|
+
...sandboxBody
|
|
658
715
|
});
|
|
659
716
|
}
|
|
660
717
|
/** Chat completion (non-streaming) */
|
|
@@ -735,7 +792,13 @@ var TCloudClient = class _TCloudClient {
|
|
|
735
792
|
"TCloudClient.rotating() cannot dispatch sandbox-harness sessions.\nSandbox sessions bind to a single operator; rotation is meaningful only for\nstateless calls. Use TCloudClient.shielded() + AgentProfile.confidential.tee\nfor privacy-preserving sandbox execution instead."
|
|
736
793
|
);
|
|
737
794
|
}
|
|
738
|
-
|
|
795
|
+
if (!this._isDirectCliBridge() && cfg.unlock == null) {
|
|
796
|
+
throw new Error("Bridge unlock is required for router-mediated bridge sessions");
|
|
797
|
+
}
|
|
798
|
+
return new BridgeSession(this, cfg, this._isDirectCliBridge());
|
|
799
|
+
}
|
|
800
|
+
_isDirectCliBridge() {
|
|
801
|
+
return this[DIRECT_CLI_BRIDGE_MARKER] === true;
|
|
739
802
|
}
|
|
740
803
|
/**
|
|
741
804
|
* Rotation stats — populated only on clients created via
|
|
@@ -901,6 +964,49 @@ var TCloudClient = class _TCloudClient {
|
|
|
901
964
|
})
|
|
902
965
|
});
|
|
903
966
|
}
|
|
967
|
+
/**
|
|
968
|
+
* Edit / inpaint / variate an existing image with a text prompt.
|
|
969
|
+
* Sibling to `imageGenerate`; routes to `/v1/images/edits` via
|
|
970
|
+
* multipart/form-data per the OpenAI spec.
|
|
971
|
+
*
|
|
972
|
+
* Reference image attachments may be passed as `Blob`, `ArrayBuffer`,
|
|
973
|
+
* or `{data: base64, mediaType, filename?}`. Multi-image composition
|
|
974
|
+
* (e.g. gpt-image-2 with two reference frames + a prompt that fuses
|
|
975
|
+
* them) is supported by passing an array; for legacy models only the
|
|
976
|
+
* first image is honored upstream.
|
|
977
|
+
*/
|
|
978
|
+
async imagesEdit(options) {
|
|
979
|
+
const formData = new FormData();
|
|
980
|
+
formData.append("prompt", options.prompt);
|
|
981
|
+
formData.append("model", options.model || "gpt-image-2");
|
|
982
|
+
if (options.n != null) formData.append("n", String(options.n));
|
|
983
|
+
if (options.size) formData.append("size", options.size);
|
|
984
|
+
if (options.quality) formData.append("quality", options.quality);
|
|
985
|
+
if (options.response_format) formData.append("response_format", options.response_format);
|
|
986
|
+
if (options.mask) formData.append("mask", toEditBlob(options.mask, "mask.png"), "mask.png");
|
|
987
|
+
const images = Array.isArray(options.image) ? options.image : [options.image];
|
|
988
|
+
if (images.length === 0) {
|
|
989
|
+
throw new TCloudError(400, "imagesEdit requires at least one image attachment");
|
|
990
|
+
}
|
|
991
|
+
images.forEach((img, idx) => {
|
|
992
|
+
const blob = toEditBlob(img, `image-${idx + 1}.png`);
|
|
993
|
+
formData.append("image[]", blob, blobFilename(img, `image-${idx + 1}.png`));
|
|
994
|
+
});
|
|
995
|
+
const headers = { ...this.headers };
|
|
996
|
+
delete headers["Content-Type"];
|
|
997
|
+
this.checkLimits();
|
|
998
|
+
const res = await proxiedFetch(this.privacy, `${this.baseURL}/images/edits`, {
|
|
999
|
+
method: "POST",
|
|
1000
|
+
headers,
|
|
1001
|
+
body: formData
|
|
1002
|
+
}, false);
|
|
1003
|
+
if (!res.ok) {
|
|
1004
|
+
const err = await res.json().catch(() => ({ error: res.statusText }));
|
|
1005
|
+
throw new TCloudError(res.status, err.error?.message || err.error || err.message || res.statusText);
|
|
1006
|
+
}
|
|
1007
|
+
this._requestCount++;
|
|
1008
|
+
return res.json();
|
|
1009
|
+
}
|
|
904
1010
|
/** Rerank documents by relevance to a query */
|
|
905
1011
|
async rerank(options) {
|
|
906
1012
|
return this._request(`${this.baseURL}/rerank`, {
|
|
@@ -992,7 +1098,7 @@ var TCloudClient = class _TCloudClient {
|
|
|
992
1098
|
}
|
|
993
1099
|
/** Get video generation status */
|
|
994
1100
|
async videoStatus(id) {
|
|
995
|
-
return this._fetch(`${this.baseURL}/video
|
|
1101
|
+
return this._fetch(`${this.baseURL}/video/${encodeURIComponent(id)}`);
|
|
996
1102
|
}
|
|
997
1103
|
/** Generate an avatar video (lip-synced talking head from audio + face image).
|
|
998
1104
|
* Returns 202 with a job_id for async polling via avatarJobStatus(). */
|
|
@@ -1252,12 +1358,14 @@ var ALL_TIERS = [
|
|
|
1252
1358
|
{ name: "max-gpu-tee", cpu: 64, ramGb: 256, gpu: 4, tee: true }
|
|
1253
1359
|
];
|
|
1254
1360
|
var BridgeSession = class _BridgeSession {
|
|
1255
|
-
constructor(client, cfg) {
|
|
1361
|
+
constructor(client, cfg, direct = false) {
|
|
1256
1362
|
this.client = client;
|
|
1257
1363
|
this.cfg = cfg;
|
|
1364
|
+
this.direct = direct;
|
|
1258
1365
|
}
|
|
1259
1366
|
client;
|
|
1260
1367
|
cfg;
|
|
1368
|
+
direct;
|
|
1261
1369
|
/** Full chat completion (non-streaming). */
|
|
1262
1370
|
async chat(options) {
|
|
1263
1371
|
return this.client.chat({ ...options, bridge: this.cfg });
|
|
@@ -1291,15 +1399,16 @@ var BridgeSession = class _BridgeSession {
|
|
|
1291
1399
|
}
|
|
1292
1400
|
/** Clone with a new resume id — same harness, different logical conversation. */
|
|
1293
1401
|
withResume(resume) {
|
|
1294
|
-
return new _BridgeSession(this.client, { ...this.cfg, resume });
|
|
1402
|
+
return new _BridgeSession(this.client, { ...this.cfg, resume }, this.direct);
|
|
1295
1403
|
}
|
|
1296
1404
|
/** Clone with a different model inside the same harness. */
|
|
1297
1405
|
withModel(model) {
|
|
1298
|
-
return new _BridgeSession(this.client, { ...this.cfg, model });
|
|
1406
|
+
return new _BridgeSession(this.client, { ...this.cfg, model }, this.direct);
|
|
1299
1407
|
}
|
|
1300
1408
|
/** The effective model id that will land on the router (`bridge/<harness>/<model>`). */
|
|
1301
1409
|
get model() {
|
|
1302
|
-
|
|
1410
|
+
const prefix = this.direct ? "" : "bridge/";
|
|
1411
|
+
return this.cfg.model ? `${prefix}${this.cfg.harness}/${this.cfg.model}` : `${prefix}${this.cfg.harness}`;
|
|
1303
1412
|
}
|
|
1304
1413
|
/** The resume id currently bound to this session, if any. */
|
|
1305
1414
|
get resume() {
|
|
@@ -1321,6 +1430,32 @@ function selectTiers(all, n) {
|
|
|
1321
1430
|
function formatPrice(pricePerToken) {
|
|
1322
1431
|
return `$${(pricePerToken * 1e3).toFixed(6)}/1K tokens`;
|
|
1323
1432
|
}
|
|
1433
|
+
function toEditBlob(input, defaultFilename) {
|
|
1434
|
+
if (input instanceof Blob) return input;
|
|
1435
|
+
if (input instanceof ArrayBuffer) return new Blob([input], { type: "image/png" });
|
|
1436
|
+
const binary = base64ToUint8Array(input.data);
|
|
1437
|
+
const copy = new Uint8Array(binary.byteLength);
|
|
1438
|
+
copy.set(binary);
|
|
1439
|
+
return new Blob([copy.buffer], { type: input.mediaType || "image/png" });
|
|
1440
|
+
void defaultFilename;
|
|
1441
|
+
}
|
|
1442
|
+
function blobFilename(input, fallback) {
|
|
1443
|
+
if (typeof input === "object" && !(input instanceof Blob) && !(input instanceof ArrayBuffer)) {
|
|
1444
|
+
if (input.filename) return input.filename;
|
|
1445
|
+
}
|
|
1446
|
+
return fallback;
|
|
1447
|
+
}
|
|
1448
|
+
function base64ToUint8Array(b64) {
|
|
1449
|
+
const m = /^data:[^;,]*(?:;[^,]*)?,(.+)$/.exec(b64);
|
|
1450
|
+
const raw = m ? m[1] : b64;
|
|
1451
|
+
const bin = typeof atob === "function" ? atob(raw) : (
|
|
1452
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1453
|
+
globalThis.Buffer.from(raw, "base64").toString("binary")
|
|
1454
|
+
);
|
|
1455
|
+
const out = new Uint8Array(bin.length);
|
|
1456
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
1457
|
+
return out;
|
|
1458
|
+
}
|
|
1324
1459
|
var TCloudError = class extends Error {
|
|
1325
1460
|
constructor(status, message) {
|
|
1326
1461
|
super(message);
|
package/dist/instance.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { a as TCloudConfig, T as TCloudClient } from './client-
|
|
1
|
+
import { a as TCloudConfig, T as TCloudClient } from './client-CF-tpVsi.cjs';
|
|
2
|
+
import '@tangle-network/sandbox';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Instance — programmatic harness for spinning up a local Tangle dev environment.
|
package/dist/instance.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { a as TCloudConfig, T as TCloudClient } from './client-
|
|
1
|
+
import { a as TCloudConfig, T as TCloudClient } from './client-CF-tpVsi.js';
|
|
2
|
+
import '@tangle-network/sandbox';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Instance — programmatic harness for spinning up a local Tangle dev environment.
|
package/dist/instance.js
CHANGED
package/dist/sandbox.cjs
CHANGED
|
@@ -276,7 +276,14 @@ function buildSandboxCreateOptions(options) {
|
|
|
276
276
|
memoryMB: options.memoryMb,
|
|
277
277
|
diskGB: options.diskGb
|
|
278
278
|
} : void 0,
|
|
279
|
-
backend:
|
|
279
|
+
// backend: merge `backend` (type string) with `agentProfile` (carried
|
|
280
|
+
// as `backend.profile` on the underlying SDK). Either may be set
|
|
281
|
+
// independently; setting both produces `{ type, profile }`. Setting
|
|
282
|
+
// neither leaves `backend` undefined so the SDK applies its default.
|
|
283
|
+
backend: options.backend || options.agentProfile ? {
|
|
284
|
+
...options.backend ? { type: options.backend } : {},
|
|
285
|
+
...options.agentProfile ? { profile: options.agentProfile } : {}
|
|
286
|
+
} : void 0,
|
|
280
287
|
confidential: options.tee ? {
|
|
281
288
|
tee: options.tee,
|
|
282
289
|
sealed: options.sealed || void 0,
|
package/dist/sandbox.d.cts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AgentProfile } from '@tangle-network/sandbox';
|
|
1
2
|
import { AsyncAttestationPolicy, AttestationVerificationResult } from '@tangle-network/tcloud-attestation';
|
|
2
3
|
|
|
3
4
|
type TCloudSandboxTee = 'any' | 'tdx' | 'nitro' | 'sev-snp' | 'phala-dstack' | 'gcp' | 'azure' | (string & {});
|
|
@@ -12,6 +13,25 @@ interface TCloudSandboxCreateOptions {
|
|
|
12
13
|
gitUrl?: string;
|
|
13
14
|
gitRef?: string;
|
|
14
15
|
backend?: 'opencode' | 'claude-code' | 'codex' | 'amp' | (string & {});
|
|
16
|
+
/**
|
|
17
|
+
* Declarative agent profile rendered into `backend.profile` on the
|
|
18
|
+
* underlying `@tangle-network/sandbox` `CreateSandboxOptions`. The
|
|
19
|
+
* profile carries the harness's prompt, tool allowlist, MCP servers
|
|
20
|
+
* to attach, and subagents available for dispatch — everything a
|
|
21
|
+
* coding-agent backend needs to specialize.
|
|
22
|
+
*
|
|
23
|
+
* When set alongside `backend` (string), `backend.type` becomes the
|
|
24
|
+
* type and the profile rides as `backend.profile`. When `backend` is
|
|
25
|
+
* omitted, the SDK applies its default type and consumes the profile
|
|
26
|
+
* as-is.
|
|
27
|
+
*
|
|
28
|
+
* Confidential execution: if `agentProfile.confidential` is set AND
|
|
29
|
+
* `tee` is also requested at the top level, the top-level `tee`
|
|
30
|
+
* wins (it's the explicit op-level requirement). Set one or the
|
|
31
|
+
* other, not both. The SDK fails closed when the operator cannot
|
|
32
|
+
* satisfy the requested TEE.
|
|
33
|
+
*/
|
|
34
|
+
agentProfile?: AgentProfile;
|
|
15
35
|
tee?: TCloudSandboxTee;
|
|
16
36
|
sealed?: boolean;
|
|
17
37
|
attestationNonce?: string | 'auto';
|
package/dist/sandbox.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AgentProfile } from '@tangle-network/sandbox';
|
|
1
2
|
import { AsyncAttestationPolicy, AttestationVerificationResult } from '@tangle-network/tcloud-attestation';
|
|
2
3
|
|
|
3
4
|
type TCloudSandboxTee = 'any' | 'tdx' | 'nitro' | 'sev-snp' | 'phala-dstack' | 'gcp' | 'azure' | (string & {});
|
|
@@ -12,6 +13,25 @@ interface TCloudSandboxCreateOptions {
|
|
|
12
13
|
gitUrl?: string;
|
|
13
14
|
gitRef?: string;
|
|
14
15
|
backend?: 'opencode' | 'claude-code' | 'codex' | 'amp' | (string & {});
|
|
16
|
+
/**
|
|
17
|
+
* Declarative agent profile rendered into `backend.profile` on the
|
|
18
|
+
* underlying `@tangle-network/sandbox` `CreateSandboxOptions`. The
|
|
19
|
+
* profile carries the harness's prompt, tool allowlist, MCP servers
|
|
20
|
+
* to attach, and subagents available for dispatch — everything a
|
|
21
|
+
* coding-agent backend needs to specialize.
|
|
22
|
+
*
|
|
23
|
+
* When set alongside `backend` (string), `backend.type` becomes the
|
|
24
|
+
* type and the profile rides as `backend.profile`. When `backend` is
|
|
25
|
+
* omitted, the SDK applies its default type and consumes the profile
|
|
26
|
+
* as-is.
|
|
27
|
+
*
|
|
28
|
+
* Confidential execution: if `agentProfile.confidential` is set AND
|
|
29
|
+
* `tee` is also requested at the top level, the top-level `tee`
|
|
30
|
+
* wins (it's the explicit op-level requirement). Set one or the
|
|
31
|
+
* other, not both. The SDK fails closed when the operator cannot
|
|
32
|
+
* satisfy the requested TEE.
|
|
33
|
+
*/
|
|
34
|
+
agentProfile?: AgentProfile;
|
|
15
35
|
tee?: TCloudSandboxTee;
|
|
16
36
|
sealed?: boolean;
|
|
17
37
|
attestationNonce?: string | 'auto';
|
package/dist/sandbox.js
CHANGED
package/dist/shielded.cjs
CHANGED
|
@@ -261,6 +261,7 @@ var PrivateRouter = class {
|
|
|
261
261
|
|
|
262
262
|
// src/client.ts
|
|
263
263
|
var ROTATING_MARKER = "__tcloudRotating";
|
|
264
|
+
var DIRECT_CLI_BRIDGE_MARKER = "__tcloudDirectCliBridge";
|
|
264
265
|
var DEFAULT_BASE_URL = "https://router.tangle.tools/v1";
|
|
265
266
|
var SDK_VERSION = "0.4.0";
|
|
266
267
|
async function proxiedFetch(privacy, url, init, streaming) {
|
|
@@ -310,6 +311,43 @@ var DEFAULT_RETRY = {
|
|
|
310
311
|
};
|
|
311
312
|
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
312
313
|
var DEFAULT_PLATFORM_URL = "https://id.tangle.tools";
|
|
314
|
+
var PROTECTED_PROVIDER_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
315
|
+
"model",
|
|
316
|
+
"messages",
|
|
317
|
+
"temperature",
|
|
318
|
+
"max_tokens",
|
|
319
|
+
"maxTokens",
|
|
320
|
+
"stream",
|
|
321
|
+
"stop",
|
|
322
|
+
"top_p",
|
|
323
|
+
"topP",
|
|
324
|
+
"frequency_penalty",
|
|
325
|
+
"frequencyPenalty",
|
|
326
|
+
"presence_penalty",
|
|
327
|
+
"presencePenalty",
|
|
328
|
+
"response_format",
|
|
329
|
+
"responseFormat",
|
|
330
|
+
"tools",
|
|
331
|
+
"tool_choice",
|
|
332
|
+
"toolChoice",
|
|
333
|
+
"gateway",
|
|
334
|
+
"bridge",
|
|
335
|
+
"agent_profile",
|
|
336
|
+
"agentProfile",
|
|
337
|
+
"session_id",
|
|
338
|
+
"sessionId"
|
|
339
|
+
]);
|
|
340
|
+
function sanitizeProviderOptions(providerOptions) {
|
|
341
|
+
if (!providerOptions) return {};
|
|
342
|
+
for (const key of Object.keys(providerOptions)) {
|
|
343
|
+
if (PROTECTED_PROVIDER_OPTION_KEYS.has(key)) {
|
|
344
|
+
throw new Error(
|
|
345
|
+
`providerOptions cannot override protected chat field "${key}"; use the typed ChatOptions field instead`
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return providerOptions;
|
|
350
|
+
}
|
|
313
351
|
var TCloudClient = class _TCloudClient {
|
|
314
352
|
baseURL;
|
|
315
353
|
platformURL;
|
|
@@ -350,13 +388,21 @@ var TCloudClient = class _TCloudClient {
|
|
|
350
388
|
* const reply = await client.ask('explain X', 'claude-code/sonnet')
|
|
351
389
|
* ```
|
|
352
390
|
*
|
|
353
|
-
* For session-resumable agentic dispatches (
|
|
354
|
-
*
|
|
355
|
-
*
|
|
391
|
+
* For session-resumable agentic dispatches, use `client.bridge(...)` on
|
|
392
|
+
* the returned direct client. `resume` is serialized to cli-bridge's
|
|
393
|
+
* `session_id` body field and the model wire format stays
|
|
394
|
+
* `<harness>/<model>` without the router-only `bridge/` prefix.
|
|
356
395
|
*/
|
|
357
396
|
static fromCliBridge(opts) {
|
|
358
397
|
const baseURL = opts.url.replace(/\/+$/, "") + "/v1";
|
|
359
|
-
|
|
398
|
+
const client = new _TCloudClient({ ...opts.config ?? {}, apiKey: opts.bearer, baseURL });
|
|
399
|
+
Object.defineProperty(client, DIRECT_CLI_BRIDGE_MARKER, {
|
|
400
|
+
value: true,
|
|
401
|
+
enumerable: false,
|
|
402
|
+
configurable: false,
|
|
403
|
+
writable: false
|
|
404
|
+
});
|
|
405
|
+
return client;
|
|
360
406
|
}
|
|
361
407
|
/**
|
|
362
408
|
* Build a client that rotates which operator serves each call. Mirrors
|
|
@@ -368,7 +414,7 @@ var TCloudClient = class _TCloudClient {
|
|
|
368
414
|
*
|
|
369
415
|
* ```ts
|
|
370
416
|
* const tcloud = TCloudClient.rotating({
|
|
371
|
-
* apiKey: process.env.
|
|
417
|
+
* apiKey: process.env.TANGLE_API_KEY,
|
|
372
418
|
* routing: { strategy: 'min-exposure' },
|
|
373
419
|
* })
|
|
374
420
|
* await tcloud.ask('hello')
|
|
@@ -409,7 +455,7 @@ var TCloudClient = class _TCloudClient {
|
|
|
409
455
|
constructor(config = {}) {
|
|
410
456
|
this.baseURL = (config.baseURL || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
411
457
|
this.platformURL = (config.platformURL || DEFAULT_PLATFORM_URL).replace(/\/$/, "");
|
|
412
|
-
this.apiKey = config.apiKey || process.env.TCLOUD_API_KEY;
|
|
458
|
+
this.apiKey = config.apiKey || process.env.TANGLE_API_KEY || process.env.TCLOUD_API_KEY;
|
|
413
459
|
this.model = config.model || "gpt-4o-mini";
|
|
414
460
|
this.privacy = config.privacy;
|
|
415
461
|
this.limits = config.limits;
|
|
@@ -618,8 +664,8 @@ var TCloudClient = class _TCloudClient {
|
|
|
618
664
|
delete headers["Authorization"];
|
|
619
665
|
}
|
|
620
666
|
}
|
|
621
|
-
if (bridge) {
|
|
622
|
-
headers["X-Bridge-Unlock"] = bridge.unlock;
|
|
667
|
+
if (bridge && !this._isDirectCliBridge()) {
|
|
668
|
+
headers["X-Bridge-Unlock"] = bridge.unlock ?? "";
|
|
623
669
|
if (bridge.resume) headers["X-Resume"] = bridge.resume;
|
|
624
670
|
if (bridge.bridgeUrl) headers["X-Bridge-Url"] = bridge.bridgeUrl;
|
|
625
671
|
if (bridge.bridgeBearer) headers["X-Bridge-Bearer"] = bridge.bridgeBearer;
|
|
@@ -632,13 +678,24 @@ var TCloudClient = class _TCloudClient {
|
|
|
632
678
|
*/
|
|
633
679
|
_effectiveModel(options) {
|
|
634
680
|
if (options.bridge) {
|
|
681
|
+
if (this._isDirectCliBridge()) {
|
|
682
|
+
return options.bridge.model ? `${options.bridge.harness}/${options.bridge.model}` : options.bridge.harness;
|
|
683
|
+
}
|
|
635
684
|
return options.bridge.model ? `bridge/${options.bridge.harness}/${options.bridge.model}` : `bridge/${options.bridge.harness}`;
|
|
636
685
|
}
|
|
637
686
|
return options.model || this.model;
|
|
638
687
|
}
|
|
639
688
|
/** Build the chat completions request body */
|
|
640
689
|
_chatBody(options, stream) {
|
|
690
|
+
const providerOptions = sanitizeProviderOptions(options.providerOptions);
|
|
691
|
+
const sandboxBody = {};
|
|
692
|
+
if (options.sandbox?.agentProfile) sandboxBody.agent_profile = options.sandbox.agentProfile;
|
|
693
|
+
if (options.sandbox?.sessionId) sandboxBody.session_id = options.sandbox.sessionId;
|
|
694
|
+
if (this._isDirectCliBridge() && options.bridge?.resume && !sandboxBody.session_id) {
|
|
695
|
+
sandboxBody.session_id = options.bridge.resume;
|
|
696
|
+
}
|
|
641
697
|
return JSON.stringify({
|
|
698
|
+
...providerOptions,
|
|
642
699
|
model: this._effectiveModel(options),
|
|
643
700
|
messages: options.messages,
|
|
644
701
|
temperature: options.temperature,
|
|
@@ -652,7 +709,7 @@ var TCloudClient = class _TCloudClient {
|
|
|
652
709
|
tools: options.tools,
|
|
653
710
|
tool_choice: options.toolChoice,
|
|
654
711
|
...options.gateway ? { gateway: options.gateway } : {},
|
|
655
|
-
...
|
|
712
|
+
...sandboxBody
|
|
656
713
|
});
|
|
657
714
|
}
|
|
658
715
|
/** Chat completion (non-streaming) */
|
|
@@ -733,7 +790,13 @@ var TCloudClient = class _TCloudClient {
|
|
|
733
790
|
"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
791
|
);
|
|
735
792
|
}
|
|
736
|
-
|
|
793
|
+
if (!this._isDirectCliBridge() && cfg.unlock == null) {
|
|
794
|
+
throw new Error("Bridge unlock is required for router-mediated bridge sessions");
|
|
795
|
+
}
|
|
796
|
+
return new BridgeSession(this, cfg, this._isDirectCliBridge());
|
|
797
|
+
}
|
|
798
|
+
_isDirectCliBridge() {
|
|
799
|
+
return this[DIRECT_CLI_BRIDGE_MARKER] === true;
|
|
737
800
|
}
|
|
738
801
|
/**
|
|
739
802
|
* Rotation stats — populated only on clients created via
|
|
@@ -899,6 +962,49 @@ var TCloudClient = class _TCloudClient {
|
|
|
899
962
|
})
|
|
900
963
|
});
|
|
901
964
|
}
|
|
965
|
+
/**
|
|
966
|
+
* Edit / inpaint / variate an existing image with a text prompt.
|
|
967
|
+
* Sibling to `imageGenerate`; routes to `/v1/images/edits` via
|
|
968
|
+
* multipart/form-data per the OpenAI spec.
|
|
969
|
+
*
|
|
970
|
+
* Reference image attachments may be passed as `Blob`, `ArrayBuffer`,
|
|
971
|
+
* or `{data: base64, mediaType, filename?}`. Multi-image composition
|
|
972
|
+
* (e.g. gpt-image-2 with two reference frames + a prompt that fuses
|
|
973
|
+
* them) is supported by passing an array; for legacy models only the
|
|
974
|
+
* first image is honored upstream.
|
|
975
|
+
*/
|
|
976
|
+
async imagesEdit(options) {
|
|
977
|
+
const formData = new FormData();
|
|
978
|
+
formData.append("prompt", options.prompt);
|
|
979
|
+
formData.append("model", options.model || "gpt-image-2");
|
|
980
|
+
if (options.n != null) formData.append("n", String(options.n));
|
|
981
|
+
if (options.size) formData.append("size", options.size);
|
|
982
|
+
if (options.quality) formData.append("quality", options.quality);
|
|
983
|
+
if (options.response_format) formData.append("response_format", options.response_format);
|
|
984
|
+
if (options.mask) formData.append("mask", toEditBlob(options.mask, "mask.png"), "mask.png");
|
|
985
|
+
const images = Array.isArray(options.image) ? options.image : [options.image];
|
|
986
|
+
if (images.length === 0) {
|
|
987
|
+
throw new TCloudError(400, "imagesEdit requires at least one image attachment");
|
|
988
|
+
}
|
|
989
|
+
images.forEach((img, idx) => {
|
|
990
|
+
const blob = toEditBlob(img, `image-${idx + 1}.png`);
|
|
991
|
+
formData.append("image[]", blob, blobFilename(img, `image-${idx + 1}.png`));
|
|
992
|
+
});
|
|
993
|
+
const headers = { ...this.headers };
|
|
994
|
+
delete headers["Content-Type"];
|
|
995
|
+
this.checkLimits();
|
|
996
|
+
const res = await proxiedFetch(this.privacy, `${this.baseURL}/images/edits`, {
|
|
997
|
+
method: "POST",
|
|
998
|
+
headers,
|
|
999
|
+
body: formData
|
|
1000
|
+
}, false);
|
|
1001
|
+
if (!res.ok) {
|
|
1002
|
+
const err = await res.json().catch(() => ({ error: res.statusText }));
|
|
1003
|
+
throw new TCloudError(res.status, err.error?.message || err.error || err.message || res.statusText);
|
|
1004
|
+
}
|
|
1005
|
+
this._requestCount++;
|
|
1006
|
+
return res.json();
|
|
1007
|
+
}
|
|
902
1008
|
/** Rerank documents by relevance to a query */
|
|
903
1009
|
async rerank(options) {
|
|
904
1010
|
return this._request(`${this.baseURL}/rerank`, {
|
|
@@ -990,7 +1096,7 @@ var TCloudClient = class _TCloudClient {
|
|
|
990
1096
|
}
|
|
991
1097
|
/** Get video generation status */
|
|
992
1098
|
async videoStatus(id) {
|
|
993
|
-
return this._fetch(`${this.baseURL}/video
|
|
1099
|
+
return this._fetch(`${this.baseURL}/video/${encodeURIComponent(id)}`);
|
|
994
1100
|
}
|
|
995
1101
|
/** Generate an avatar video (lip-synced talking head from audio + face image).
|
|
996
1102
|
* Returns 202 with a job_id for async polling via avatarJobStatus(). */
|
|
@@ -1250,12 +1356,14 @@ var ALL_TIERS = [
|
|
|
1250
1356
|
{ name: "max-gpu-tee", cpu: 64, ramGb: 256, gpu: 4, tee: true }
|
|
1251
1357
|
];
|
|
1252
1358
|
var BridgeSession = class _BridgeSession {
|
|
1253
|
-
constructor(client, cfg) {
|
|
1359
|
+
constructor(client, cfg, direct = false) {
|
|
1254
1360
|
this.client = client;
|
|
1255
1361
|
this.cfg = cfg;
|
|
1362
|
+
this.direct = direct;
|
|
1256
1363
|
}
|
|
1257
1364
|
client;
|
|
1258
1365
|
cfg;
|
|
1366
|
+
direct;
|
|
1259
1367
|
/** Full chat completion (non-streaming). */
|
|
1260
1368
|
async chat(options) {
|
|
1261
1369
|
return this.client.chat({ ...options, bridge: this.cfg });
|
|
@@ -1289,15 +1397,16 @@ var BridgeSession = class _BridgeSession {
|
|
|
1289
1397
|
}
|
|
1290
1398
|
/** Clone with a new resume id — same harness, different logical conversation. */
|
|
1291
1399
|
withResume(resume) {
|
|
1292
|
-
return new _BridgeSession(this.client, { ...this.cfg, resume });
|
|
1400
|
+
return new _BridgeSession(this.client, { ...this.cfg, resume }, this.direct);
|
|
1293
1401
|
}
|
|
1294
1402
|
/** Clone with a different model inside the same harness. */
|
|
1295
1403
|
withModel(model) {
|
|
1296
|
-
return new _BridgeSession(this.client, { ...this.cfg, model });
|
|
1404
|
+
return new _BridgeSession(this.client, { ...this.cfg, model }, this.direct);
|
|
1297
1405
|
}
|
|
1298
1406
|
/** The effective model id that will land on the router (`bridge/<harness>/<model>`). */
|
|
1299
1407
|
get model() {
|
|
1300
|
-
|
|
1408
|
+
const prefix = this.direct ? "" : "bridge/";
|
|
1409
|
+
return this.cfg.model ? `${prefix}${this.cfg.harness}/${this.cfg.model}` : `${prefix}${this.cfg.harness}`;
|
|
1301
1410
|
}
|
|
1302
1411
|
/** The resume id currently bound to this session, if any. */
|
|
1303
1412
|
get resume() {
|
|
@@ -1319,6 +1428,32 @@ function selectTiers(all, n) {
|
|
|
1319
1428
|
function formatPrice(pricePerToken) {
|
|
1320
1429
|
return `$${(pricePerToken * 1e3).toFixed(6)}/1K tokens`;
|
|
1321
1430
|
}
|
|
1431
|
+
function toEditBlob(input, defaultFilename) {
|
|
1432
|
+
if (input instanceof Blob) return input;
|
|
1433
|
+
if (input instanceof ArrayBuffer) return new Blob([input], { type: "image/png" });
|
|
1434
|
+
const binary = base64ToUint8Array(input.data);
|
|
1435
|
+
const copy = new Uint8Array(binary.byteLength);
|
|
1436
|
+
copy.set(binary);
|
|
1437
|
+
return new Blob([copy.buffer], { type: input.mediaType || "image/png" });
|
|
1438
|
+
void defaultFilename;
|
|
1439
|
+
}
|
|
1440
|
+
function blobFilename(input, fallback) {
|
|
1441
|
+
if (typeof input === "object" && !(input instanceof Blob) && !(input instanceof ArrayBuffer)) {
|
|
1442
|
+
if (input.filename) return input.filename;
|
|
1443
|
+
}
|
|
1444
|
+
return fallback;
|
|
1445
|
+
}
|
|
1446
|
+
function base64ToUint8Array(b64) {
|
|
1447
|
+
const m = /^data:[^;,]*(?:;[^,]*)?,(.+)$/.exec(b64);
|
|
1448
|
+
const raw = m ? m[1] : b64;
|
|
1449
|
+
const bin = typeof atob === "function" ? atob(raw) : (
|
|
1450
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1451
|
+
globalThis.Buffer.from(raw, "base64").toString("binary")
|
|
1452
|
+
);
|
|
1453
|
+
const out = new Uint8Array(bin.length);
|
|
1454
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
1455
|
+
return out;
|
|
1456
|
+
}
|
|
1322
1457
|
var TCloudError = class extends Error {
|
|
1323
1458
|
constructor(status, message) {
|
|
1324
1459
|
super(message);
|