@tangle-network/tcloud 0.2.0 → 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.
package/dist/shielded.cjs CHANGED
@@ -300,8 +300,10 @@ var DEFAULT_RETRY = {
300
300
  retryableStatuses: [429, 500, 502, 503, 504]
301
301
  };
302
302
  var DEFAULT_TIMEOUT_MS = 6e4;
303
+ var DEFAULT_PLATFORM_URL = "https://id.tangle.tools";
303
304
  var TCloudClient = class _TCloudClient {
304
305
  baseURL;
306
+ platformURL;
305
307
  apiKey;
306
308
  model;
307
309
  headers;
@@ -318,6 +320,7 @@ var TCloudClient = class _TCloudClient {
318
320
  static OPERATORS_TTL_MS = 5 * 60 * 1e3;
319
321
  constructor(config = {}) {
320
322
  this.baseURL = (config.baseURL || DEFAULT_BASE_URL).replace(/\/$/, "");
323
+ this.platformURL = (config.platformURL || DEFAULT_PLATFORM_URL).replace(/\/$/, "");
321
324
  this.apiKey = config.apiKey || process.env.TCLOUD_API_KEY;
322
325
  this.model = config.model || "gpt-4o-mini";
323
326
  this.privacy = config.privacy;
@@ -506,10 +509,11 @@ var TCloudClient = class _TCloudClient {
506
509
  return res;
507
510
  }
508
511
  /**
509
- * Prepare headers for chat requests — operator routing + SpendAuth.
512
+ * Prepare headers for chat requests — operator routing + SpendAuth +
513
+ * bridge short-circuit headers when `options.bridge` is set.
510
514
  * Shared between chat() and chatStream() to eliminate duplication.
511
515
  */
512
- async _prepareChatRequest(model) {
516
+ async _prepareChatRequest(model, bridge) {
513
517
  const headers = { ...this.headers };
514
518
  if (this.spendAuthFn) {
515
519
  const auth = await this.spendAuthFn();
@@ -526,12 +530,28 @@ var TCloudClient = class _TCloudClient {
526
530
  delete headers["Authorization"];
527
531
  }
528
532
  }
533
+ if (bridge) {
534
+ headers["X-Bridge-Unlock"] = bridge.unlock;
535
+ if (bridge.resume) headers["X-Resume"] = bridge.resume;
536
+ if (bridge.bridgeUrl) headers["X-Bridge-Url"] = bridge.bridgeUrl;
537
+ if (bridge.bridgeBearer) headers["X-Bridge-Bearer"] = bridge.bridgeBearer;
538
+ }
529
539
  return { headers, baseURL };
530
540
  }
541
+ /**
542
+ * Resolve the effective model string. When a bridge is set, rewrite to
543
+ * `bridge/<harness>/<model>` (or `bridge/<harness>` if no model).
544
+ */
545
+ _effectiveModel(options) {
546
+ if (options.bridge) {
547
+ return options.bridge.model ? `bridge/${options.bridge.harness}/${options.bridge.model}` : `bridge/${options.bridge.harness}`;
548
+ }
549
+ return options.model || this.model;
550
+ }
531
551
  /** Build the chat completions request body */
532
552
  _chatBody(options, stream) {
533
553
  return JSON.stringify({
534
- model: options.model || this.model,
554
+ model: this._effectiveModel(options),
535
555
  messages: options.messages,
536
556
  temperature: options.temperature,
537
557
  max_tokens: options.maxTokens,
@@ -543,13 +563,17 @@ var TCloudClient = class _TCloudClient {
543
563
  response_format: options.responseFormat,
544
564
  tools: options.tools,
545
565
  tool_choice: options.toolChoice,
566
+ ...options.gateway ? { gateway: options.gateway } : {},
546
567
  ...options.providerOptions
547
568
  });
548
569
  }
549
570
  /** Chat completion (non-streaming) */
550
571
  async chat(options) {
551
572
  this.checkLimits();
552
- const { headers, baseURL } = await this._prepareChatRequest(options.model || this.model);
573
+ const { headers, baseURL } = await this._prepareChatRequest(
574
+ this._effectiveModel(options),
575
+ options.bridge
576
+ );
553
577
  const res = await this._doFetch(`${baseURL}/chat/completions`, {
554
578
  method: "POST",
555
579
  headers,
@@ -563,7 +587,10 @@ var TCloudClient = class _TCloudClient {
563
587
  async *chatStream(options) {
564
588
  this.checkLimits();
565
589
  this._requestCount++;
566
- const { headers, baseURL } = await this._prepareChatRequest(options.model || this.model);
590
+ const { headers, baseURL } = await this._prepareChatRequest(
591
+ this._effectiveModel(options),
592
+ options.bridge
593
+ );
567
594
  const res = await this._doFetch(`${baseURL}/chat/completions`, {
568
595
  method: "POST",
569
596
  headers,
@@ -592,6 +619,24 @@ var TCloudClient = class _TCloudClient {
592
619
  }
593
620
  }
594
621
  }
622
+ /**
623
+ * Bridge — scoped helper for a subscription-backed CLI harness behind
624
+ * the Tangle Router's cli-bridge. Returns a mini-client bound to
625
+ * (harness, unlock, resume) so you don't thread those through every
626
+ * call.
627
+ *
628
+ * ```ts
629
+ * const kimi = tcloud.bridge({ harness: 'kimi', model: 'kimi-for-coding', unlock: UNLOCK, resume: 'pr-42' })
630
+ * await kimi.ask('review this diff…')
631
+ * for await (const chunk of kimi.stream('continue…')) process.stdout.write(chunk)
632
+ * ```
633
+ *
634
+ * Sessions persist across process restarts — use the same `resume` id
635
+ * to land on the same CLI conversation (context intact, no replay tax).
636
+ */
637
+ bridge(cfg) {
638
+ return new BridgeSession(this, cfg);
639
+ }
595
640
  /** Convenience: send a single message and get the text response */
596
641
  async ask(message, modelOrOptions) {
597
642
  const options = typeof modelOrOptions === "string" ? { model: modelOrOptions } : modelOrOptions;
@@ -630,43 +675,89 @@ var TCloudClient = class _TCloudClient {
630
675
  const apiRoot = this.baseURL.replace(/\/v1$/, "");
631
676
  return this._fetch(`${apiRoot}/api/operators`);
632
677
  }
678
+ // ── Billing (via id.tangle.tools) ──
633
679
  /** Get credit balance */
634
680
  async credits() {
635
- const apiRoot = this.baseURL.replace(/\/v1$/, "");
636
- return this._fetch(`${apiRoot}/api/billing`);
681
+ const { data } = await this._fetch(`${this.platformURL}/v1/billing/balance`);
682
+ return data;
637
683
  }
638
- /** Add credits */
684
+ /** Add credits via Stripe checkout. Returns the checkout URL. */
639
685
  async addCredits(amount) {
640
- const apiRoot = this.baseURL.replace(/\/v1$/, "");
641
- return this._fetch(`${apiRoot}/api/billing`, {
686
+ const { data } = await this._fetch(`${this.platformURL}/v1/billing/topup`, {
642
687
  method: "POST",
643
688
  body: JSON.stringify({ amount })
644
689
  });
690
+ return data;
645
691
  }
646
- /** Create a new API key */
647
- async createKey(name) {
648
- const apiRoot = this.baseURL.replace(/\/v1$/, "");
649
- return this._fetch(`${apiRoot}/api/keys`, {
692
+ /** Get transaction history */
693
+ async transactions(limit = 50) {
694
+ const { data } = await this._fetch(`${this.platformURL}/v1/billing/transactions?limit=${limit}`);
695
+ return data;
696
+ }
697
+ // ── API Keys (via id.tangle.tools) ──
698
+ /**
699
+ * Create a new API key.
700
+ * When called with an API key (not session), the new key is automatically
701
+ * a child of the calling key — enabling hierarchical key delegation.
702
+ *
703
+ * Pass `parentKeyId` explicitly to create a child of a specific key.
704
+ * Child keys inherit the parent's product scope, allowedModels, and rpmLimit
705
+ * if not specified. Budget cannot exceed the parent's remaining budget.
706
+ */
707
+ async createKey(opts) {
708
+ const { data } = await this._fetch(`${this.platformURL}/v1/keys`, {
650
709
  method: "POST",
651
- body: JSON.stringify({ name })
710
+ body: JSON.stringify(opts)
652
711
  });
712
+ return data;
653
713
  }
654
- /** List API keys */
655
- async keys() {
656
- const apiRoot = this.baseURL.replace(/\/v1$/, "");
657
- return this._fetch(`${apiRoot}/api/keys`);
714
+ /** Get a single API key by ID */
715
+ async getKey(id) {
716
+ const { data } = await this._fetch(`${this.platformURL}/v1/keys/${id}`);
717
+ return data;
718
+ }
719
+ /**
720
+ * List API keys.
721
+ * Pass `children: true` to list child keys of the calling API key.
722
+ */
723
+ async keys(opts) {
724
+ const q = opts?.children ? "?children=true" : "";
725
+ const { data } = await this._fetch(`${this.platformURL}/v1/keys${q}`);
726
+ return data;
727
+ }
728
+ /**
729
+ * Update an API key's limits.
730
+ * Can adjust budget, allowedModels, rpmLimit, expiresAt, and name.
731
+ */
732
+ async updateKey(id, updates) {
733
+ const { data } = await this._fetch(`${this.platformURL}/v1/keys/${id}`, {
734
+ method: "PATCH",
735
+ body: JSON.stringify(updates)
736
+ });
737
+ return data;
658
738
  }
659
- /** Revoke an API key */
739
+ /** Revoke an API key. If the key has children, they are also revoked recursively. */
660
740
  async revokeKey(id) {
661
- const apiRoot = this.baseURL.replace(/\/v1$/, "");
662
- const res = await proxiedFetch(this.privacy, `${apiRoot}/api/keys/${id}`, {
663
- method: "DELETE",
664
- headers: this.headers
665
- }, false);
666
- if (!res.ok) {
667
- const err = await res.json().catch(() => ({ error: res.statusText }));
668
- throw new TCloudError(res.status, err.error?.message || err.error || err.message || res.statusText);
669
- }
741
+ await this._fetch(`${this.platformURL}/v1/keys/${id}`, { method: "DELETE" });
742
+ }
743
+ /** Rotate an API key — creates new key with same config, revokes old */
744
+ async rotateKey(id) {
745
+ const { data } = await this._fetch(`${this.platformURL}/v1/keys/${id}/rotate`, { method: "POST" });
746
+ return data;
747
+ }
748
+ // ── Projects (via id.tangle.tools) ──
749
+ /** Create a project for usage attribution */
750
+ async createProject(name, product) {
751
+ const { data } = await this._fetch(`${this.platformURL}/v1/projects`, {
752
+ method: "POST",
753
+ body: JSON.stringify({ name, product })
754
+ });
755
+ return data;
756
+ }
757
+ /** List projects */
758
+ async projects() {
759
+ const { data } = await this._fetch(`${this.platformURL}/v1/projects`);
760
+ return data;
670
761
  }
671
762
  /** Generate embeddings */
672
763
  async embeddings(options) {
@@ -977,6 +1068,61 @@ var TCloudClient = class _TCloudClient {
977
1068
  };
978
1069
  });
979
1070
  }
1071
+ // ── Eval ──────────────────────────────────────────────────────────────
1072
+ get _apiRoot() {
1073
+ return this.baseURL.replace(/\/v1$/, "");
1074
+ }
1075
+ async eval(opts) {
1076
+ return this._request(`${this._apiRoot}/api/eval`, { method: "POST", body: JSON.stringify(opts) });
1077
+ }
1078
+ async createSuite(opts) {
1079
+ return this._request(`${this._apiRoot}/api/eval/suites`, { method: "POST", body: JSON.stringify(opts) });
1080
+ }
1081
+ async listSuites() {
1082
+ return this._fetch(`${this._apiRoot}/api/eval/suites`);
1083
+ }
1084
+ async runSuite(suiteId, opts) {
1085
+ return this._request(`${this._apiRoot}/api/eval/suites/${suiteId}/runs`, { method: "POST", body: JSON.stringify(opts || {}) });
1086
+ }
1087
+ async listRuns(suiteId) {
1088
+ return this._fetch(`${this._apiRoot}/api/eval/suites/${suiteId}/runs`);
1089
+ }
1090
+ async getRun(runId) {
1091
+ return this._fetch(`${this._apiRoot}/api/eval/runs/${runId}`);
1092
+ }
1093
+ async setBaseline(runId) {
1094
+ await this._request(`${this._apiRoot}/api/eval/runs/${runId}`, { method: "PATCH", body: JSON.stringify({ baseline: true }) });
1095
+ }
1096
+ // ── Sandbox ──────────────────────────────────────────────────────────
1097
+ async sandboxPricing(opts) {
1098
+ const p = new URLSearchParams();
1099
+ if (opts?.cpu) p.set("cpu", String(opts.cpu));
1100
+ if (opts?.ram) p.set("ram", String(opts.ram));
1101
+ if (opts?.disk) p.set("disk", String(opts.disk));
1102
+ return this._fetch(`${this._apiRoot}/api/sandbox/pricing?${p}`);
1103
+ }
1104
+ async sandboxStatus() {
1105
+ return this._fetch(`${this._apiRoot}/api/sandbox/link-key`);
1106
+ }
1107
+ async sandboxProvision() {
1108
+ return this._request(`${this._apiRoot}/api/sandbox/provision`, { method: "POST" });
1109
+ }
1110
+ async sandboxCreate(opts) {
1111
+ return this._request(`${this._apiRoot}/api/sandbox/sessions`, { method: "POST", body: JSON.stringify(opts) });
1112
+ }
1113
+ async sandboxList() {
1114
+ return this._fetch(`${this._apiRoot}/api/sandbox/sessions`);
1115
+ }
1116
+ async sandboxStats(sandboxId) {
1117
+ return this._fetch(`${this._apiRoot}/api/sandbox/stats/${sandboxId}`);
1118
+ }
1119
+ async sandboxDestroy(sessionId) {
1120
+ return this._request(`${this._apiRoot}/api/sandbox/sessions/${sessionId}`, { method: "DELETE" });
1121
+ }
1122
+ // ── User Info ────────────────────────────────────────────────────────
1123
+ async userInfo() {
1124
+ return this._fetch(`${this._apiRoot}/api/auth/userinfo`);
1125
+ }
980
1126
  };
981
1127
  var ALL_TIERS = [
982
1128
  { name: "cpu-only", cpu: 4, ramGb: 16, gpu: 0, tee: false },
@@ -987,6 +1133,59 @@ var ALL_TIERS = [
987
1133
  { name: "max-gpu", cpu: 64, ramGb: 256, gpu: 4, tee: false },
988
1134
  { name: "max-gpu-tee", cpu: 64, ramGb: 256, gpu: 4, tee: true }
989
1135
  ];
1136
+ var BridgeSession = class _BridgeSession {
1137
+ constructor(client, cfg) {
1138
+ this.client = client;
1139
+ this.cfg = cfg;
1140
+ }
1141
+ /** Full chat completion (non-streaming). */
1142
+ async chat(options) {
1143
+ return this.client.chat({ ...options, bridge: this.cfg });
1144
+ }
1145
+ /** Stream OpenAI chat.completion.chunks. */
1146
+ chatStream(options) {
1147
+ return this.client.chatStream({ ...options, bridge: this.cfg });
1148
+ }
1149
+ /** One-shot: send a string, get the assistant text. */
1150
+ async ask(message, extra) {
1151
+ const completion = await this.chat({
1152
+ messages: [{ role: "user", content: message }],
1153
+ ...extra
1154
+ });
1155
+ return completion.choices[0]?.message?.content || "";
1156
+ }
1157
+ /** One-shot: send a string, stream text deltas. */
1158
+ async *stream(message, extra) {
1159
+ for await (const chunk of this.chatStream({
1160
+ messages: [{ role: "user", content: message }],
1161
+ ...extra
1162
+ })) {
1163
+ const content = chunk.choices?.[0]?.delta?.content;
1164
+ if (content) yield content;
1165
+ }
1166
+ }
1167
+ /** Turn-based: send full message history, get assistant text. */
1168
+ async turn(messages, extra) {
1169
+ const completion = await this.chat({ messages, ...extra });
1170
+ return completion.choices[0]?.message?.content || "";
1171
+ }
1172
+ /** Clone with a new resume id — same harness, different logical conversation. */
1173
+ withResume(resume) {
1174
+ return new _BridgeSession(this.client, { ...this.cfg, resume });
1175
+ }
1176
+ /** Clone with a different model inside the same harness. */
1177
+ withModel(model) {
1178
+ return new _BridgeSession(this.client, { ...this.cfg, model });
1179
+ }
1180
+ /** The effective model id that will land on the router (`bridge/<harness>/<model>`). */
1181
+ get model() {
1182
+ return this.cfg.model ? `bridge/${this.cfg.harness}/${this.cfg.model}` : `bridge/${this.cfg.harness}`;
1183
+ }
1184
+ /** The resume id currently bound to this session, if any. */
1185
+ get resume() {
1186
+ return this.cfg.resume;
1187
+ }
1188
+ };
990
1189
  function selectTiers(all, n) {
991
1190
  if (n >= all.length) return [...all];
992
1191
  if (n <= 1) return [all[0]];
@@ -1,5 +1,5 @@
1
1
  import { Hex } from 'viem';
2
- import { a as TCloudConfig, T as TCloudClient, w as SpendAuth } from './client-CcuHG7_w.cjs';
2
+ import { a as TCloudConfig, T as TCloudClient, H as SpendAuth } from './client-DskWX_BT.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, w as SpendAuth } from './client-CcuHG7_w.js';
2
+ import { a as TCloudConfig, T as TCloudClient, H as SpendAuth } from './client-DskWX_BT.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-VD4RNZOC.js";
7
- import "./chunk-HL4CXKET.js";
6
+ } from "./chunk-ADGH2R2R.js";
7
+ import "./chunk-LN7XX5KG.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.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "TypeScript SDK and CLI for Tangle AI Cloud — decentralized LLM inference",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -53,6 +53,7 @@
53
53
  "dependencies": {
54
54
  "@scure/bip32": "^2.0.1",
55
55
  "@scure/bip39": "^2.0.1",
56
+ "@tangle-network/sandbox": "^0.0.3",
56
57
  "commander": "^13.0.0",
57
58
  "viem": "^2.0.0"
58
59
  },