UNPKG

@tangle-network/tcloud

Version:

TypeScript SDK and CLI for Tangle Router, Sandbox, model routing, and agent service calls

575 lines (445 loc) • 19.7 kB
# tcloud TypeScript SDK and CLI for [Tangle Router](https://router.tangle.tools): model routing, sandbox-backed agent calls, operator routing, and anonymous payments via ShieldedCredits. Zero framework dependencies. Pure `fetch` + SSE. Works in Node.js, Deno, Bun, and edge runtimes. ## Table of Contents - [Installation](#installation) - [SDK](#sdk) - [Quick Start](#quick-start) - [Model Selection](#model-selection) - [Decisions (OpenAI wire)](#decisions-openai-wire) - [System One Decisions (fleet wire)](#system-one-decisions-fleet-wire) - [Key Scope and Allow-Lists](#key-scope-and-allow-lists) - [Budget-Derived Child Keys](#budget-derived-child-keys) - [Streaming](#streaming) - [Full Chat Completion](#full-chat-completion) - [Private Inference](#private-inference) - [Embeddings](#embeddings) - [Web Search](#web-search) - [Video & Avatar Generation](#video--avatar-generation) - [Async Jobs](#async-jobs) - [Operator Routing](#operator-routing) - [Models and Operators](#models-and-operators) - [API Key Management](#api-key-management) - [Cost Estimation](#cost-estimation) - [Spending Limits](#spending-limits) - [CLI](#cli) - [Authentication](#authentication) - [Chat](#chat) - [Browse](#browse) - [Credits and Keys](#credits-and-keys) - [Wallet Management](#wallet-management) - [Configuration](#configuration) - [OpenAI SDK Compatibility](#openai-sdk-compatibility) - [Vercel AI SDK Compatibility](#vercel-ai-sdk-compatibility) - [Examples](#examples) - [License](#license) ## Installation ```bash npm install @tangle-network/tcloud ``` Or run the CLI directly: ```bash npx @tangle-network/tcloud chat "What is Tangle?" ``` Do not install unscoped `tcloud`. It is unrelated to Tangle. ## SDK ### Quick Start ```ts import { TCloud } from '@tangle-network/tcloud' // Set model at client creation — explicit and consistent. const client = new TCloud({ apiKey: 'sk-tan-...', model: 'gpt-4o-mini', }) const answer = await client.ask('What is Tangle Network?') // string console.log(answer) ``` ### Quick Start — direct cli-bridge (subscription-backed coding harness) When you have your own [cli-bridge](https://github.com/drewstone/cli-bridge) running (locally or on your own VPS), point the SDK straight at it. cli-bridge is OpenAI-compatible (`/v1/chat/completions`) so chat / ask / askStream work unchanged — and your CLI subscriptions on the bridge box pay for the LLM tokens directly (no router, no per-token billing). ```bash # 1. clone + run cli-bridge (one terminal) gh repo clone drewstone/cli-bridge && cd cli-bridge echo "BRIDGE_BEARER=$(openssl rand -hex 32)" >> .env.local echo "BRIDGE_BACKENDS=claude,passthrough" >> .env.local export $(grep -v '^#' .env.local | xargs) && pnpm exec tsx src/server.ts # 2. authenticate your harness CLI claude /login # or: kimi login, codex login, opencode auth login ``` ```ts import { TCloudClient } from '@tangle-network/tcloud' const client = TCloudClient.fromCliBridge({ url: 'http://127.0.0.1:3344', // or any reachable URL bearer: process.env.CLI_BRIDGE_BEARER!, // value of BRIDGE_BEARER from .env.local }) // model id = `<harness>/<model>` — no `bridge/` prefix in direct mode const reply = await client.ask('reply with OK', 'claude-code/sonnet') // streaming + full chat work the same for await (const chunk of client.askStream('write a haiku', 'kimi-code/kimi-for-coding')) { process.stdout.write(chunk) } ``` See [`examples/14-direct-cli-bridge.ts`](./examples/14-direct-cli-bridge.ts) for the full pattern. Direct cli-bridge clients also support `client.bridge({ harness, model, resume })`; in direct mode the SDK sends `<harness>/<model>` and maps `resume` to cli-bridge's `session_id`. ### Model Selection Model is set at client creation. Override per-request when needed: ```ts // Default model for all requests const client = new TCloud({ apiKey: '...', model: 'gpt-4o-mini' }) await client.ask('Hello') // returns string // Full control per-request (OpenAI-compatible) const completion = await client.chat({ model: 'claude-sonnet-4-6', messages: [{ role: 'user', content: 'Hello' }], temperature: 0.5, maxTokens: 100, }) // returns ChatCompletion { id, model, choices: [{ index, message, finish_reason }], usage? } // Get full response with usage stats const full = await client.askFull('Hello') // returns ChatCompletion console.log(full.model, full.usage?.total_tokens) // Search available models const llamas = await client.searchModels('llama') // returns Model[] ``` ### Decisions (OpenAI wire) Structured judgment over an input — up to 200 typed questions in one request, answers in order, billed input-only at the row's reviewed rate. The request passes through Router verbatim, so OpenAI SDK field semantics (prompt caching, safety identifiers, item inputs, per-question refusals) are preserved, and every call carries Router's idempotent replay and cost receipts. ```ts const { decision, receipts } = await client.decisions.create({ model: 'gpt-6-luna', input: 'The package arrived with a broken screen.', questions: [ { type: 'predicate', name: 'damaged', instructions: 'Does the customer report a damaged item?' }, { type: 'choice', name: 'action', instructions: 'Choose the first response.', choices: [{ value: 'refund' }, { value: 'replacement' }] }, { type: 'score', name: 'severity', instructions: 'Rate the severity.', levels: [{ label: 'low' }, { label: 'high' }] }, ], idempotencyKey: 'case-123', // identical retry replays the settled outcome, never re-charged maxCostUsd: 0.01, // per-call ceiling; the router refuses above it }) // decision.answers[0] -> { type: 'predicate', name: 'damaged', probability: 0.95 } // receipts.costUsd / providerCostUsd / generationId / replayed ``` ### System One Decisions (fleet wire) The fleet's decision wire for judges and policy machinery: `state` + named `noul`/`score`/`choice` questions, answers keyed by name, strict validation, calibrated probability profiles. (`@tangle-network/agent-eval/systemone/protocol` owns the canonical contract; these types mirror it dependency-free.) ```ts const { result, receipts } = await client.systemone({ model: 'openai/gpt-6-luna', state: 'The filing cites 37 precedents; 31 support the motion.', questions: { strength: { type: 'score', instructions: 'How strong is the motion?', criteria: ['weak', 'moderate', 'strong'] }, outcome: { type: 'choice', instructions: 'Predict the ruling.', criteria: { granted: null, denied: null } }, }, }) // result.answers.strength -> { type: 'score', score: 2.03, confidence: 0.71, legend: {...}, probabilities: {...} } ``` ### Key Scope and Allow-Lists A scoped key's allow-list is routing information, not a wall — query it before the first refusal, resolve within it, or parse it from the refusal itself: ```ts const info = await client.keyInfo() // { allowedModels, limitRemaining, rateLimitRequests, expiresAt, ... } const allowed = await client.allowedModels() // cached 5 min; { refresh: true } forces a re-read const resolution = await client.resolveModel('gpt-6-luna', { fallbacks: ['glm-5.3', 'gpt-5.5'] }) // { status: 'allowed' } | { status: 'rerouted', model, blockedModel } | { status: 'refused', allowedModels } // Pure utilities (no client): parseModelNotAllowed(refusalMessage), resolveModelWithinAllowList(...), // formatModelNotAllowedRefusal({ requested, allowedModels }) — never a silent downgrade. ``` ### Budget-Derived Child Keys The platform RESERVES a child key's budget against its parent's REMAINING — a flat default silently breaks every parent that holds less. Pass the parent's remaining and the ask derives within it (never above remaining, never below the platform minimum): ```ts await client.createKey({ name: 'workspace-child', budgetUsd: 25, // the configured ask parentBudgetRemainingUsd: 5, // -> asks $5, not $25 (derives, floors to cents, $0.50 min) allowedModels: ['glm-5.3'], }) // deriveChildBudgetUsd({ requestedUsd, parentRemainingUsd }) is exported for manual use; // an exhausted parent throws ParentBudgetExhaustedError before any mint. ``` ### Streaming ```ts for await (const chunk of client.askStream('Explain zero-knowledge proofs')) { process.stdout.write(chunk) } // With model override for await (const chunk of client.askStream('Hello', 'claude-sonnet-4-6')) { process.stdout.write(chunk) } ``` ### Full Chat Completion OpenAI-compatible request/response format: ```ts const completion = await client.chat({ model: 'meta-llama/llama-4-maverick', messages: [ { role: 'system', content: 'You are a helpful assistant.' }, { role: 'user', content: 'Hello' }, ], temperature: 0.7, maxTokens: 1024, }) // returns ChatCompletion console.log(completion.choices[0].message.content) // completion.usage => { prompt_tokens, completion_tokens, total_tokens } ``` ### Private Inference Anonymous inference with no API key. Uses EIP-712 SpendAuth signatures — the operator verifies payment without learning your identity. ```ts import { TCloud } from '@tangle-network/tcloud' const client = TCloud.shielded() const answer = await client.ask('Hello from the shadows') ``` Under the hood: generates an ephemeral wallet, signs a SpendAuth payload, and sends it as an `X-Payment-Signature` header. The operator validates the cryptographic proof and serves inference without knowing who you are. ### Embeddings ```ts const response = await client.embeddings({ model: 'text-embedding-3-small', input: 'What is Tangle?', }) // returns EmbeddingResponse // EmbeddingResponse: { object, data: [{ object, embedding: number[], index }], model, usage } console.log(response.data[0].embedding.length) // 1536 ``` ### Web Search ```ts const results = await client.search({ query: 'latest Tangle docs', provider: 'exa', maxResults: 5, }) console.log(results.data[0].title) console.log(results.citations) ``` Use search inside chat with either the typed gateway shorthand or OpenRouter-compatible plugins: ```ts await client.ask('What changed in Tangle this week?', { webSearch: { provider: 'exa', maxResults: 5, searchRecency: 'week' }, }) await client.chat({ messages: [{ role: 'user', content: 'Summarize recent AI video model releases.' }], plugins: [{ id: 'web', engine: 'parallel', max_results: 5 }], }) ``` ### Video & Avatar Generation ```ts // Generate a video from a text prompt const video = await client.videoGenerate({ prompt: 'A sunset over mountains', duration: 5, }) // returns VideoResponse { id, status, url?, error? } // Generate a talking-head avatar video const avatar = await client.avatarGenerate({ audio_url: 'https://example.com/narration.mp3', image_url: 'https://example.com/face.jpg', }) // returns AvatarGenerateResponse { job_id, status, result?, error? } ``` ### Async Jobs Avatar and video generation are asynchronous. Use `watchJob()` for real-time SSE streaming of job progress, or poll with `avatarJobStatus()`. ```ts // Submit an avatar job const job = await client.avatarGenerate({ audio_url: 'https://...', image_url: 'https://...', }) console.log(job.job_id) // 'job-abc123' console.log(job.status) // 'queued' // Watch until complete (SSE streaming) const result = await client.watchJob(job.job_id, { onEvent: (e) => console.log(`${e.status} ${e.progress ?? ''}%`), }) // returns JobEvent { status, progress?, result?, error?, timestamp } console.log(result.result) // { video_url: 'https://...' } // Or just poll const status = await client.avatarJobStatus(job.job_id) // returns AvatarJobStatus { job_id, status, result?, error? } ``` ### Operator Routing Route requests to specific operators or use strategy-based selection: ```ts const client = new TCloud({ apiKey: 'sk-tan-...', routing: { prefer: 'operator-slug', // specific operator strategy: 'lowest-latency', // or 'lowest-price', 'highest-reputation' region: 'us-east', // geographic preference }, }) ``` The gateway selects the best operator based on a composite score (reputation 40%, latency 30%, price 30%) and returns the selection in response headers: ```ts // After a request, check which operator served it: // X-Tangle-Operator: <slug> // X-Tangle-Price-Input: <per-token> // X-Tangle-Price-Output: <per-token> ``` ### Models and Operators ```ts // List all available models const models = await client.models() // returns Model[] // Model: { id, name, context_length, pricing: { prompt, completion }, _provider? } models.forEach(m => console.log(m.id, m._provider)) // Search models by name, provider, or capability const llamas = await client.searchModels('llama') // returns Model[] const anthropic = await client.searchModels('anthropic') // List active operators with stats const { operators, stats } = await client.operators() // returns { operators: Operator[], stats: any } // Operator: { id, slug, name, status, endpointUrl, reputationScore, avgLatencyMs, models, ... } console.log(`${stats.activeOperators} operators serving ${stats.totalModels} models`) // Check credit balance const credits = await client.credits() // returns CreditBalance // CreditBalance: { balance: number, transactions: [{ id, amount, type, description, createdAt }] } console.log(`Balance: $${credits.balance}`) ``` ### API Key Management Create, list, and revoke API keys programmatically: ```ts // Create a new key const { key, id } = await client.createKey('my-app') console.log(key) // sk-tan-... (shown once, store it) // List all keys const keys = await client.keys() keys.forEach(k => console.log(k.name, k.prefix, k.lastUsedAt)) // Revoke a key await client.revokeKey(id) ``` ### Cost Estimation Preview cost before sending a request: ```ts const cost = await client.estimateCost({ model: 'gpt-4o', inputTokens: 1000, outputTokens: 500, }) // returns { inputCost: number, outputCost: number, total: number } console.log(`Estimated: $${cost.total.toFixed(6)}`) // { inputCost: 0.005, outputCost: 0.0075, total: 0.0125 } ``` ### Spending Limits Prevent runaway costs with per-request and total budget caps: ```ts const client = new TCloud({ apiKey: 'sk-tan-...', model: 'gpt-4o-mini', limits: { maxCostPerRequest: 0.01, // $0.01 max per request maxTotalSpend: 1.00, // $1.00 lifetime budget maxRequests: 100, // hard request cap onLimitWarning: (info) => { console.warn(`${info.type} at ${info.current}/${info.limit}`) }, }, }) // Requests that would exceed limits are blocked with TCloudError (429) await client.ask('Hello') // Check metering const { totalSpent, requestCount } = client.usage ``` ## CLI ### Authentication ```bash tcloud auth login # Browser-based device flow tcloud auth set-key sk-tan-... # Set key directly tcloud auth status # Check current auth ``` ### Chat ```bash npx @tangle-network/tcloud chat "Explain zero-knowledge proofs" npx @tangle-network/tcloud chat -m meta-llama/llama-4-maverick "Hello" npx @tangle-network/tcloud chat --private "Anonymous request" # ShieldedCredits mode npx @tangle-network/tcloud chat # Interactive mode ``` ### Browse ```bash tcloud models # List available models tcloud models -s llama # Search models tcloud search "Tangle docs" --provider exa --max-results 5 tcloud operators # List active operators ``` ### Credits and Keys ```bash tcloud credits balance # Check balance tcloud credits add 10 # Add credits tcloud keys create my-app # Create a new API key tcloud keys list # List keys ``` ### Decisions and Key Scope ```bash tcloud decide "The package arrived with a broken screen." -p "Does the customer report a damaged item?" tcloud decide "Customer wants a refund for two dead units" --choice action=refund,replacement,deny tcloud systemone "The filing cites 37 precedents." -n strength="How strong is the motion?" tcloud key-info ``` ### Wallet Management For private inference, manage ephemeral wallets: ```bash tcloud wallet generate # Create wallet tcloud wallet generate -l "research" # With label tcloud wallet list # List wallets ``` The CLI generates random ephemeral keys and stores them in `~/.tcloud/wallets.json` with owner-only permissions. Treat this file as sensitive. ## Configuration Config stored in `~/.tcloud/config.json`: ```bash tcloud config --api-url https://router.tangle.tools tcloud config --model gpt-4o-mini ``` Environment variables: - `TANGLE_API_KEY` — API key (primary). One key for router + sandbox + all Tangle products. - `TCLOUD_API_KEY` — Deprecated alias, still honored for backwards compatibility. - `TANGLE_ROUTER_URL` — Override CLI router URL. - `TCLOUD_API_URL` — Deprecated CLI router URL alias. ## OpenAI SDK Compatibility tcloud's API is OpenAI-compatible. You can use the OpenAI SDK directly: ```ts import OpenAI from 'openai' const client = new OpenAI({ apiKey: 'sk-tan-...', baseURL: 'https://router.tangle.tools/v1', }) const completion = await client.chat.completions.create({ model: 'meta-llama/llama-4-maverick', messages: [{ role: 'user', content: 'Hello' }], }) ``` ## Vercel AI SDK Compatibility Use with the Vercel AI SDK's OpenAI provider: ```ts import { createOpenAI } from '@ai-sdk/openai' import { generateText } from 'ai' const tangle = createOpenAI({ apiKey: 'sk-tan-...', baseURL: 'https://router.tangle.tools/v1', }) const { text } = await generateText({ model: tangle('meta-llama/llama-4-maverick'), prompt: 'What is decentralized AI?', }) ``` ## Examples See the [`examples/`](./examples/) directory — each is a self-contained script: | # | Example | What it shows | |---|---------|---------------| | 01 | [Quick Start](./examples/01-quick-start.ts) | Minimum viable setup | | 02 | [Model Selection](./examples/02-model-selection.ts) | Three ways to pick a model, search, browse | | 03 | [Streaming](./examples/03-streaming.ts) | Real-time SSE output, text + chunk modes | | 04 | [Private Inference](./examples/04-private-inference.ts) | ShieldedCredits, ephemeral wallets, auto-replenish | | 05 | [Operator Routing](./examples/05-operator-routing.ts) | Prefer operator, strategy, region, list operators | | 06 | [Cost & Usage](./examples/06-cost-and-usage.ts) | Estimate cost, track tokens, check balance | | 07 | [API Keys](./examples/07-api-keys.ts) | Create, list, revoke keys programmatically | | 08 | [OpenAI SDK](./examples/08-openai-compat.ts) | Drop-in replacement via baseURL | | 09 | [Vercel AI SDK](./examples/09-vercel-ai-sdk.ts) | generateText + streamText with Tangle | | 10 | [Spending Limits](./examples/10-spending-limits.ts) | Budget caps, request limits, warning callbacks | | 11 | [Inference Strategies](./examples/11-inference-strategies.ts) | Min-exposure + operator rotation patterns | | 12 | [Bridge Sessions](./examples/12-bridge-sessions.ts) | Persistent bridge sessions for harness routing | | 13 | [Bridge Chat App](./examples/13-bridge-chat-app.ts) | Multi-turn bridge application | | 14 | [Direct cli-bridge](./examples/14-direct-cli-bridge.ts) | Local cli-bridge, no router | | 15 | [Sandbox Agents](./examples/15-sandbox-agents.ts) | Sandbox agents + inline AgentProfile | | 16 | [Tangle Intelligence Hook](./examples/16-tangle-intelligence-hook.ts) | Trace inference into hosted intelligence (one OTLP block) | Run any example: ```bash TANGLE_API_KEY=sk-tan-... npx tsx examples/01-quick-start.ts ``` ## License [Apache-2.0](./LICENSE)