@lore-hex/trusted-router
v0.8.0
Published
JavaScript SDK and CLI for TrustedRouter — typed errors, retries, failover, and attestation verification.
Downloads
709
Maintainers
Readme
TrustedRouter JavaScript SDK and CLI
OpenAI-compatible JS/TS client for TrustedRouter — the hosted, attested LLM router that lets you point one OpenAI-shaped client at every provider (Anthropic, OpenAI, Google Vertex, Gemini, DeepSeek, Mistral, Cerebras) and prove the prompt path doesn't log.
- Inference API:
https://api.trustedrouter.com/v1 - Control API:
https://trustedrouter.com/v1 - Trust release:
https://trust.trustedrouter.com - Source:
https://github.com/Lore-Hex/trusted-router-js - License: Apache-2.0
npm install @lore-hex/trusted-routerRuns on Node 20+, Deno, Bun, and modern browsers — no native deps. The attestation verifier uses the WebCrypto SubtleCrypto API.
Command-line interface
The same npm package includes the official trustedrouter CLI. Install it
globally, invoke it through npx, or keep it as a project dependency:
npm install --global @lore-hex/trusted-router
export TRUSTEDROUTER_API_KEY="sk-tr-v1-..."
trustedrouter chat "Explain confidential computing in one paragraph"
printf '%s\n' 'Summarize this input' | trustedrouter chat - --stream
trustedrouter models --json
trustedrouter providers --json
trustedrouter regions --json
trustedrouter trust --json
trustedrouter attest --verify --json
trustedrouter attest --session --json
# No global install:
npx --yes @lore-hex/trusted-router models --jsonchat accepts a positional prompt, an explicit -, or piped stdin when the
prompt is omitted. Stdin must be valid UTF-8, is capped at 8 MiB, and preserves
its leading/trailing whitespace. It defaults to trustedrouter/auto; use
--model and --max-tokens to override that. --stream --json emits one
compact JSONL chat.delta record per non-empty text delta followed by
chat.done. Compact JSON and JSONL recursively sort object keys so byte output
is deterministic.
Every non-streaming command success in --json mode has the stable shape
{"command":"...","data":...,"ok":true}. Errors go only to stderr as
{"error":{"message":"...","type":"..."},"ok":false}, with
status_code and request_id when available. Exit codes are stable by family:
0 success, 1 API/runtime failure, 2 usage/input error, and 3
authentication/permission failure.
attest --verify checks the document signature and workload identity against
the published trust release. attest --session additionally binds the check
to the live connection's nonce and TLS exporter and runs a same-socket
follow-up challenge.
The CLI reads TRUSTEDROUTER_API_KEY (or the legacy TR_API_KEY) and never
accepts a bearer as a command-line argument, where it could leak through shell
history or the process list. Optional deployment overrides are
TRUSTEDROUTER_BASE_URL, TRUSTEDROUTER_CONTROL_BASE_URL, and
TRUSTEDROUTER_WORKSPACE_ID; TR_BASE_URL remains a legacy base-URL alias.
Explicit --help output is always plain text, even alongside --json. Run
trustedrouter --help or trustedrouter chat --help for the complete command
reference. Invoking the CLI without a command is a usage error (exit 2).
Quick start
import { TrustedRouter, AUTO_MODEL } from "@lore-hex/trusted-router";
const client = new TrustedRouter({ apiKey: "sk-tr-v1-..." });
const resp = await client.chatCompletions({
model: AUTO_MODEL, // "trustedrouter/auto" — multi-provider failover
messages: [{ role: "user", content: "hello" }],
});
console.log(resp.choices[0].message.content);chatCompletions(...) defaults to AUTO_MODEL when model is omitted.
Routing, privacy, and orchestration
The package exports stable aliases for normal routing (AUTO_MODEL,
FAST_MODEL), privacy (ZDR_MODEL, E2E_MODEL, CONFIDENTIAL_MODEL,
EU_MODEL, US_MODEL), and named orchestration models including
SOCRATES_MODEL, PROMETHEUS_MODEL, ZEUS_MODEL, and ATHENA_MODEL.
Use ProviderPreferences when privacy or US provider jurisdiction must remain
a hard routing requirement with an explicit model. Use EU_MODEL for the
EU-focused routing pool:
import { ProviderPreferences } from "@lore-hex/trusted-router";
const response = await client.chatCompletions({
model: "z-ai/glm-5.2",
messages: [{ role: "user", content: "Review this contract." }],
provider: ProviderPreferences.confidential(),
});All five atomic orchestration primitives have typed builders with matching
wire formats across the official SDKs: fusionTool (Synth), advisorTool,
selectorTool, mapReduceTool, and subagentTool.
Cost allocation tags
Attach up to 50 AWS-style string tags to any inference request. Tags remain outside model prompts and provider payloads and appear in TrustedRouter generation and activity metadata.
const response = await client.chatCompletions({
model: "trustedrouter/zdr",
messages: [{ role: "user", content: "Summarize this contract." }],
tags: {
environment: "production",
team: "legal",
"cost-center": "legal-01",
},
user: "user_123",
session_id: "matter_456",
});The same tags, user, session_id, and trace fields work with Responses,
Messages, and Embeddings. Request tags override API key default tags with the
same key.
Node TLS session verification
G6 TLS session pinning uses Node TLS sockets, so import it from the Node-only session subpath rather than the browser-safe package root:
import {
verifyGatewaySession,
fetchAttestationAgain,
} from "@lore-hex/trusted-router/session";fetchAttestationAgain(session) re-fetches and verifies the document over the
already-pinned socket and returns a verified GatewayAttestation. This is a
return-type change from the earlier raw Uint8Array; an HTTP 200 alone is no
longer exposed as though it were a trusted follow-up.
Fusion
Fan a request across a panel of models and let a judge model pick or synthesize
one answer. fusion(...) returns the same OpenAI-shape chat.completion as
chatCompletions. FUSION_FREEDOM_PANEL / FUSION_FREEDOM_FALLBACK_JUDGES are
the recommended most-permissive configuration.
import {
TrustedRouter,
FUSION_FREEDOM_PANEL,
FUSION_FREEDOM_FALLBACK_JUDGES,
} from "@lore-hex/trusted-router";
const client = new TrustedRouter({ apiKey: "sk-tr-v1-..." });
const resp = await client.fusion({
messages: [{ role: "user", content: "explain how mRNA vaccines work" }],
analysisModels: FUSION_FREEDOM_PANEL, // the panel
// omit selectionStrategy to use synthesize_non_refusals
fallbackJudges: FUSION_FREEDOM_FALLBACK_JUDGES, // tried in order if a judge refuses/fails
});
console.log(resp.choices[0].message.content);Or attach fusionTool(...) to any chat call yourself. preset: "quality" or
"budget" picks a built-in panel.
Browser sign-in / delegated keys
Browser apps should not ask users to paste a full TrustedRouter key. Use the OAuth/PKCE delegation flow to mint a limited inference key for your app, then store that delegated key in browser storage.
import { TrustedRouter } from "@lore-hex/trusted-router";
const tr = new TrustedRouter();
// Sign-in button handler.
const auth = await tr.createOAuthAuthorization({
callbackUrl: `${location.origin}/auth/callback`,
keyLabel: "Lore Web",
limit: "5",
usageLimitType: "monthly",
});
sessionStorage.setItem("tr_oauth", JSON.stringify({
state: auth.state,
codeVerifier: auth.codeVerifier,
}));
location.assign(auth.url);On the callback page:
const params = new URLSearchParams(location.search);
const saved = JSON.parse(sessionStorage.getItem("tr_oauth") || "{}");
if (params.get("state") !== saved.state) throw new Error("OAuth state mismatch");
const { key } = await new TrustedRouter().exchangeOAuthKey({
code: params.get("code"),
codeVerifier: saved.codeVerifier,
});
localStorage.setItem("tr_delegated_key", key);createOAuthAuthorization(...) generates an RFC7636 S256 PKCE verifier and
challenge locally. exchangeOAuthKey(...) posts only the one-time code and
verifier, and deliberately omits any existing bearer key.
Sign in with TrustedRouter
For browser SPAs, BrowserOAuthFlow (from @lore-hex/trusted-router/oauth)
wraps the lower-level helpers above: initiate(...) builds the authorize URL
and stashes { state, codeVerifier } in sessionStorage, and
handleCallback(...) validates state, exchanges the code, and returns the
delegated key + verified identity. Then client.userInfo() reads the
signed-in user.
import { TrustedRouter } from "@lore-hex/trusted-router";
import { BrowserOAuthFlow } from "@lore-hex/trusted-router/oauth";
const flow = new BrowserOAuthFlow(`${location.origin}/auth/callback`, {
client: new TrustedRouter(),
});
// sign-in button:
const { url } = await flow.initiate({ keyLabel: "My App", limit: "5" });
location.assign(url);
// on /auth/callback (reads location.search; throws on state mismatch):
const { key, identity } = await flow.handleCallback();
localStorage.setItem("tr_delegated_key", key);
// later:
const { data } = await new TrustedRouter({ apiKey: key }).userInfo();Full flow, endpoints, and security notes: Sign in with TrustedRouter.
Streaming
for await (const token of client.chatCompletionsText({
messages: [{ role: "user", content: "Write a haiku" }],
})) {
process.stdout.write(token);
}chatCompletionsChunks(...) yields the raw OpenAI chat.completion.chunk
objects (with finish_reason, model, id) when you need more than just
the text delta. chatCompletionsRawStream(...) yields the underlying SSE
bytes — useful if you're writing an HTTP relay that doesn't want to parse.
Inference endpoint and failover
Inference calls default to DEFAULT_API_BASE_URL. The default client probes
the published US Central, US East, and Europe gateways in parallel on its first
inference request, pins the lowest-latency healthy region, and keeps the other
regions plus the global apex as idempotent failover targets. Reuse one client to
retain region affinity and connection pooling, reuse DNS results, and improve
prompt-cache locality. Set regionalAffinity: false to use only the global endpoint. A
custom baseUrl is never probed or rewritten. A custom fetch defaults
affinity off; opt in with regionalAffinity: true when its transport can reach
the public regional hosts. Pass baseUrl only for a custom inference endpoint
(e.g. a self-hosted gateway). Metadata,
OAuth, billing, credits, activity, and broadcast helpers use the control plane
at DEFAULT_CONTROL_BASE_URL; override it with controlBaseUrl only when you
need a custom control endpoint.
Alias domains
The regions above all live under one name on one DNS provider, and the domain sits above every cloud behind it. A zone that stops answering, a registrar lock, or a resolver handing out a stale record takes the API down no matter how many regions are healthy.
ALIAS_API_BASE_URLS — api.allyrouter.com and api.uptimerouter.com — are
exact aliases of the primary, on separate domains served by separate DNS
providers, resolving to the same attested enclaves. They sit at the end of the
candidate list, after the regional gateways, so a healthy deployment never
touches them. Nothing to configure; it is on by default.
Failover changes host only on connection failures and on 502, 503, or
504. A 500 means a server received and processed the request. You are not
charged twice for it — authorization is idempotent per Idempotency-Key and
settlement happens once — but the work would run a second time, so the answer
could differ and TrustedRouter pays the provider again. A 500 is retried on the
same host.
Aliases are used only for the default baseUrl. A custom one — a private
deployment, a test server, a regional pin — is never rewritten. Set
regionalFailover: false to keep every attempt on a single host.
Typed errors
Every HTTP failure throws a typed subclass of TrustedRouterError so callers
can discriminate without inspecting status codes:
import {
TrustedRouter, RateLimitError, AuthenticationError,
BadRequestError, EndpointNotSupportedError, InternalError,
} from "@lore-hex/trusted-router";
try {
await client.chatCompletions({ messages: [...] });
} catch (err) {
if (err instanceof RateLimitError) {
await sleep((err.retryAfter ?? 5) * 1000);
} else if (err instanceof AuthenticationError) {
refreshKey();
} else if (err instanceof BadRequestError) {
console.warn("bad request:", err.message);
} else if (err instanceof EndpointNotSupportedError) {
disableOptionalFeature();
} else if (err instanceof InternalError) {
// auto-retried; still failing
} else {
throw err;
}
}All subclasses inherit TrustedRouterError.
Every error also carries layer, source, provider, and requestId when
the server supplies them, so retry logic can distinguish router failures from
provider failures without parsing human-readable messages.
Automatic retries
By default the client retries 429 and 5xx responses up to 2 times
with exponential backoff + jitter (capped at 30s, honors Retry-After).
Disable with maxRetries: 0:
const client = new TrustedRouter({ apiKey: "...", maxRetries: 0 });Typed inference and control-plane mutation methods mint one idempotency key at
the logical call boundary and reuse it unchanged for every retry. The generic
client.request(...) and client.rawRequest(...) escape hatches deliberately
do not guess whether an arbitrary write is idempotent: pass idempotencyKey
explicitly if an unsafe method such as POST should be replayed after an
ambiguous transport failure or an ordinary retryable status. A failure known
to happen before any bytes were sent remains safe to retry without a key.
Regional failover applies only to inference routes and preserves the same idempotency key when it advances to another healthy gateway. Control-plane calls retry on the configured control host.
Per-call extras
Every chat method (and request() for ad-hoc paths) accepts:
| Option | Purpose |
|---|---|
| apiKey | Override the instance bearer for this call only (threadsafe) |
| extraHeaders | Object of headers to merge in (trace IDs, custom routing) |
| workspaceId | Sets X-TrustedRouter-Workspace for workspace-scoped management calls |
| idempotencyKey | Supplies the replay key; typed mutations auto-mint one when omitted, while generic request() does not |
| timeout | Per-call timeout in milliseconds (uses AbortController) |
await client.billingCheckout({
amount: 25,
paymentMethod: "stablecoin",
idempotencyKey: `checkout-${userId}-${orderId}`, // never double-charge
});Attestation verification (the differentiator)
Every TrustedRouter response is generated inside a Google Confidential Space
workload. The gateway's /attestation endpoint mints a Google-signed JWT
that commits to the workload image digest, image reference, your nonce, and
the TLS leaf cert SHA-256. Verifying it proves the prompt path you're about
to use is the exact build the trust page advertises:
import { TrustedRouter } from "@lore-hex/trusted-router";
import {
verifyGatewayAttestation, policyFromTrustRelease,
} from "@lore-hex/trusted-router/attestation";
const client = new TrustedRouter({ apiKey: "sk-tr-v1-..." });
const policy = await policyFromTrustRelease(); // pulls live trust release
const nonce = crypto.randomUUID().replace(/-/g, "");
const jwt = await client.attestation(); // raw JWT bytes (Uint8Array)
const attestation = await verifyGatewayAttestation(jwt, {
policy,
nonceHex: nonce,
// Optionally pass the live TLS cert DER bytes for extra binding.
});
console.log("verified gateway:", attestation.imageDigest);verifyGatewayAttestation() throws AttestationVerificationError on any
failure — bad signature, expired JWT, wrong issuer, audience mismatch,
image_digest mismatch, image_reference mismatch, missing nonce echo, or
TLS cert mismatch. Never returns falsey for a failed verification.
Bring your own fetch
Pass fetchImpl for custom transports (proxies, retries you manage,
observability hooks):
const client = new TrustedRouter({
apiKey: "...",
fetchImpl: async (url, init) => myInstrumentedFetch(url, init),
});Other endpoints
client.models(); // OpenAI-shape catalog via the control plane
client.providers(); // provider list via the control plane
client.regions(); // deployed regions via the control plane
client.credits({ workspaceId: "ws_..." }); // prepaid balance via the control plane
client.activity({ since: "2026-01-01", limit: 50 }); // control-plane activity
client.messages({ // Anthropic shape, preserves system + content blocks
model: "anthropic/claude-3-5-sonnet",
messages: [{ role: "user", content: "hi" }],
maxTokens: 512,
});
client.billingCheckout({ amount: 25, paymentMethod: "stablecoin", idempotencyKey: "..." });client.embeddings(...) uses the attested inference plane. Embedding model
catalog routes such as /embeddings/models are control-plane metadata.
For routes the SDK doesn't wrap, drop down to client.request(...):
await client.request("GET", "/some/new/route", {
extraHeaders: { "x-trace": "abc" },
});Contributing
npm install
npm run check # syntax check
npm test # node --testCI runs lint + tests on every push to main and PR.
