Chio/Docs
LOGIN · JOIN

ReferenceSDKs

TypeScript SDK

Version 0.1.0 of @chio-protocol/sdk for Node.js 22 or newer: invariants, transport, sessions, DPoP proofs, receipt query, and the cognition market.

Source

This page reflects sdks/typescript/chio-ts/ in the Chio source at the pinned commit: the manifest package.json, the root barrel src/index.ts, and the modules it re-exports (src/client/, src/session/, src/transport/, src/invariants/, src/auth/, src/dpop.ts, src/errors.ts, src/receipt_query_client.ts, src/finding.ts, and src/cognition_market.ts). Package names, versions, subpath exports, and declared dependencies render from the sdks dataset, which reads every package.json under sdks/. The worked applications are examples/hello-express and examples/hello-fastify. A TypeScript manifest states what it states; the normative protocol text lives in the Protocol Reference.


Synopsis

One install line and the root entry point.

bash
$ npm install @chio-protocol/sdk@0.1.0
typescript
import { ChioClient } from "@chio-protocol/sdk";

const client = ChioClient.withStaticBearer("https://edge.example.com", token);
const session = await client.initialize();

The package sets "type": "module" and "engines": { "node": ">=22" } in its manifest, so it is ESM only. A CommonJS consumer reaches it through a dynamic import(). Nothing in the package is native: the runtime dependency list in the manifest is empty, and signing, hashing, and verification run on node:crypto.

Entry points

The manifest declares 3 importable subpaths. The root re-exports every module, so an import from the root reaches the same symbols; the two narrow subpaths exist so a bundler can drop the rest.

Import specifierWhat it carries
@chio-protocol/sdkThe root barrel: ChioClient, ChioSession, staticBearerAuth, the JSON-RPC types, the transport functions, every invariant, the error classes, signDpopProof, ReceiptQueryClient, findingBidCeiling, and the cognition-market clients.
@chio-protocol/sdk/invariantsVerification only: canonical JSON, SHA-256, Ed25519, and receipt, capability, and manifest parsing and verification.
@chio-protocol/sdk/transportStreamable HTTP: RPC message parsing, session lifecycle, header builders.

Invariants

The invariants module has no dependency outside node:crypto, and every exported function is synchronous, so there is nothing to await and no network call to make. This is the full export list of src/invariants/index.ts.

ModuleExports
json.tscanonicalizeJson, canonicalizeJsonString, and the types JsonPrimitive and JsonValue.
crypto.tssha256Hex, verifyChioSignature.
hashing.tssha256HexBytes, sha256HexUtf8.
receipt.tsparseReceiptJson, receiptBodyCanonicalJson, receiptSigningBodyCanonicalJson, verifyReceipt, verifyReceiptJson, verifyReceiptWithTrustedSigners, and the types ChioReceipt, ReceiptDecisionKind, ReceiptVerification.
capability.tscapabilityBodyCanonicalJson, capabilitySigningBody, capabilitySigningBodyCanonicalJson, parseCapabilityJson, verifyCapability, verifyCapabilityJson, and the types CapabilityTimeStatus, CapabilityToken, CapabilityVerification, DelegationLink.
manifest.tsparseSignedManifestJson, signedManifestBodyCanonicalJson, verifySignedManifest, verifySignedManifestJson, and the types ManifestVerification, MonetaryAmount, PricingModel, SignedManifest, ToolManifest, ToolPricing.
signing.tsisValidPublicKeyHex, isValidSignatureHex, publicKeyHexMatches, signJsonStringEd25519, signUtf8MessageEd25519, verifyJsonStringSignatureEd25519, verifyUtf8MessageEd25519, and the types CanonicalJsonSignature, Utf8MessageSignature.
errors.tsChioInvariantError, parseJsonText, and the type ChioInvariantErrorCode.

DPoP proof signing is not here. It lives on the root entry point as signDpopProof. See DPoP proofs.

Canonical JSON

typescript
import { canonicalizeJson, canonicalizeJsonString } from "@chio-protocol/sdk/invariants";

// Serialize a value to its RFC 8785 canonical JSON string.
const canonical: string = canonicalizeJson({ b: 2, a: 1 });
// canonical === '{"a":1,"b":2}'

// Canonicalize an existing JSON string.
const str: string = canonicalizeJsonString('{"b":2,"a":1}');
// str === '{"a":1,"b":2}'

Hashing

typescript
import { sha256Hex, sha256HexBytes, sha256HexUtf8 } from "@chio-protocol/sdk/invariants";

sha256Hex("hello world");            // string or Buffer
sha256HexBytes(Buffer.from([1, 2, 3]));
sha256HexUtf8("hello world");

Receipts

verifyReceipt is synchronous and takes an optional list of trusted signer keys. It returns a ReceiptVerification whose fields are snake_case, matching the Rust serde output byte for byte.

typescript
import {
  parseReceiptJson,
  receiptBodyCanonicalJson,
  verifyReceipt,
  verifyReceiptJson,
  verifyReceiptWithTrustedSigners,
} from "@chio-protocol/sdk/invariants";
import type { ChioReceipt, ReceiptVerification } from "@chio-protocol/sdk/invariants";

const receipt: ChioReceipt = parseReceiptJson(jsonString);

// signature_valid, parameter_hash_valid, receipt_id_valid, decision,
// receipt_kind, boundary_class, trust_level, result, authorized,
// signer_key_hex, signer_trusted, ok
const result: ReceiptVerification = verifyReceipt(receipt);

// Same call with the signer allow-list spelled out.
const trusted = verifyReceiptWithTrustedSigners(receipt, [kernelKeyHex]);

const quick: ReceiptVerification = verifyReceiptJson(jsonString);
const body: string = receiptBodyCanonicalJson(receipt);

ok and authorized both require signer_trusted, so a call that passes no trusted signer returns false for both however good the signature is. Branch on signature_valid and parameter_hash_valid for an integrity check, and pass the kernel keys you accept when you want an authorization answer.

Capabilities

typescript
import {
  capabilitySigningBodyCanonicalJson,
  parseCapabilityJson,
  verifyCapability,
  verifyCapabilityJson,
} from "@chio-protocol/sdk/invariants";
import type {
  CapabilityToken,
  CapabilityVerification,
  CapabilityTimeStatus,
} from "@chio-protocol/sdk/invariants";

const cap: CapabilityToken = parseCapabilityJson(jsonString);
const now = Math.floor(Date.now() / 1000);

// The third argument bounds the delegation chain; omit it for no bound.
const status: CapabilityVerification = verifyCapability(cap, now, 4);
// status.signature_valid: boolean
// status.delegation_chain_shape_valid: boolean
// status.time_valid: boolean
// status.time_status: "valid" | "not_yet_valid" | "expired"

Manifests

typescript
import {
  parseSignedManifestJson,
  verifySignedManifest,
  verifySignedManifestJson,
} from "@chio-protocol/sdk/invariants";
import type { SignedManifest, ManifestVerification } from "@chio-protocol/sdk/invariants";

const manifest: SignedManifest = parseSignedManifestJson(jsonString);

// structure_valid, signature_valid, embedded_public_key_valid,
// embedded_public_key_matches_signer
const result: ManifestVerification = verifySignedManifest(manifest);

Ed25519

typescript
import {
  signJsonStringEd25519,
  signUtf8MessageEd25519,
  verifyJsonStringSignatureEd25519,
  verifyUtf8MessageEd25519,
  isValidPublicKeyHex,
  isValidSignatureHex,
  publicKeyHexMatches,
} from "@chio-protocol/sdk/invariants";

const sig = signJsonStringEd25519('{"key":"value"}', privateKeyHex);
const ok = verifyJsonStringSignatureEd25519(
  '{"key":"value"}',
  sig.public_key_hex,
  sig.signature_hex,
);

isValidPublicKeyHex(hex);  // 64 hex chars
isValidSignatureHex(hex);  // 128 hex chars

ChioClient and ChioSession

ChioClient composes transport, authentication, and session management. initialize() runs the MCP handshake against a chio edge and returns a ChioSession. The constructor takes baseUrl, the fields of StaticBearerAuth, and an optional fetchImpl. The base URL is the edge origin: the transport appends /mcp to it on every request. initialize() sends protocol version 2025-11-25 and client info naming the package unless you pass protocolVersion, capabilities, or clientInfo.

typescript
import { ChioClient, staticBearerAuth } from "@chio-protocol/sdk";

// ChioClientOptions extends StaticBearerAuth, so spread the auth fields in.
const client = new ChioClient({
  baseUrl: "https://edge.example.com",
  ...staticBearerAuth(process.env.CHIO_TOKEN!),
});
// Or: ChioClient.withStaticBearer("https://edge.example.com", token)

const session = await client.initialize();

const tools = await session.listTools();
const result = await session.callTool("read_file", { path: "./README.md" });

await session.close();

ChioSession numbers its own JSON-RPC request ids from 2, the handshake having taken 1. Every method below returns the RPC result, except request, sendEnvelope, notification, and setLogLevel, which return the whole RpcExchange, and close, which returns the status and headers of the session delete.

MethodJSON-RPC method
listTools(params?)tools/list
callTool(name, args?)tools/call
listResources(params?)resources/list
readResource(uri)resources/read
subscribeResource(uri)resources/subscribe
unsubscribeResource(uri)resources/unsubscribe
listResourceTemplates(params?)resources/templates/list
listPrompts(params?)prompts/list
getPrompt(name, args?)prompts/get
complete(params)completion/complete
setLogLevel(level)logging/setLevel, sent as a notification
listTasks(params?)tasks/list
getTask(taskId)tasks/get
getTaskResult(taskId)tasks/result
cancelTask(taskId)tasks/cancel
request(method, params?, onMessage?)any method, returning the exchange
requestResult(method, params?, onMessage?)any method, returning the terminal response
notification(method, params?, onMessage?)any notification
sendEnvelope(body, onMessage?)a caller-built JSON-RPC envelope
setMessageHandler(onMessage)replaces the default per-message handler
close()deletes the session over HTTP

A JSON-RPC failure raises an Error carrying the server's message on every result-returning method. request and requestResult hand back the failure envelope instead, so use them when you want to read the error object.

Transport

The transport module implements MCP Streamable HTTP with an explicit session lifecycle. Use it directly to manage sessions, or let ChioClient manage them. Every function appends /mcp to the base URL it is given, so pass the edge origin rather than the endpoint path. The caller supplies whole JSON-RPC envelopes; initializeSession posts the one you hand it, reads MCP-Session-Id off the response, and sends notifications/initialized for you.

typescript
import {
  initializeSession,
  postRpc,
  postNotification,
  deleteSession,
  buildRpcHeaders,
  buildSessionDeleteHeaders,
  parseRpcMessages,
  readRpcMessagesUntilTerminal,
  terminalMessage,
} from "@chio-protocol/sdk/transport";
import type {
  SessionState,
  InitializeSessionResult,
  RpcExchange,
  JsonRpcMessage,
} from "@chio-protocol/sdk/transport";

// InitializeSessionResult extends SessionState, so it carries sessionId and
// protocolVersion alongside both handshake exchanges.
const session: InitializeSessionResult = await initializeSession(
  "https://edge.example.com",
  authToken,
  {
    jsonrpc: "2.0",
    id: 1,
    method: "initialize",
    params: {
      protocolVersion: "2025-11-25",
      capabilities: {},
      clientInfo: { name: "my-app", version: "1.0.0" },
    },
  },
);

const exchange: RpcExchange = await postRpc(
  "https://edge.example.com",
  authToken,
  session.sessionId,
  session.protocolVersion,
  { jsonrpc: "2.0", id: 2, method: "tools/list", params: {} },
);
// exchange.request, exchange.status, exchange.headers, exchange.messages

await deleteSession("https://edge.example.com", authToken, session.sessionId);

DPoP proofs

signDpopProof builds a canonical proof body, hashes the tool arguments with SHA-256, and signs the body with the agent Ed25519 seed. The schema id is chio.dpop_proof.v1, exported as DPOP_SCHEMA, and the kernel checks the same value in verify_dpop_proof. A bad seed raises DpopSignError.

typescript
import { signDpopProof, DPOP_SCHEMA } from "@chio-protocol/sdk";
import type { DpopProof, DpopProofBody } from "@chio-protocol/sdk";

const proof: DpopProof = signDpopProof({
  capabilityId: process.env.CHIO_CAPABILITY_ID!,
  toolServer: "srv-files",
  toolName: "read_file",
  actionArgs: { path: "./workspace/README.md" },
  agentSeedHex: process.env.CHIO_AGENT_SEED_HEX!,
  nonce: "optional-server-nonce",
  issuedAt: 1744537862, // optional, defaults to now
});

// proof.body fields, in the alphabetical order canonical JSON puts them:
//   action_hash, agent_key, capability_id, issued_at, nonce,
//   schema (= DPOP_SCHEMA), tool_name, tool_server
// proof.signature is a hex-encoded Ed25519 signature over the canonical body.

ReceiptQueryClient

ReceiptQueryClient wraps GET /v1/receipts/query and injects the bearer token. The constructor is positional: (baseUrl, authToken, fetchImpl?). Filter fields are camelCase in TypeScript and go on the query string under the same names.

typescript
import { ReceiptQueryClient } from "@chio-protocol/sdk";
import type { ChioReceipt, ReceiptQueryResponse } from "@chio-protocol/sdk";

const client = new ReceiptQueryClient(
  "https://receipts.example.com",
  process.env.RECEIPT_TOKEN!,
);

const page: ReceiptQueryResponse = await client.query({
  capabilityId: "cap_7f3a",
  agentSubject: agentPublicKeyHex,
  toolServer: "srv-files",
  toolName: "read_file",
  outcome: "deny",
  since: 1744500000,        // Unix seconds
  until: 1744600000,
  minCost: 1n,              // bigint, not number
  maxCost: 1000n,
  costCurrency: "USD",      // three uppercase letters; required with a cost bound
  limit: 50,
  cursor: undefined,        // numeric cursor from a prior page
});

console.log(page.totalCount, page.nextCursor, page.receipts.length);

paginate() is an async generator over pages. It drives cursor from each response's nextCursor, stops when the server omits it, and throws QueryError when a cursor fails to advance rather than looping.

typescript
for await (const receipts of client.paginate({ outcome: "deny" })) {
  for (const receipt of receipts) {
    const r: ChioReceipt = receipt;
    console.log(r.id, r.tool_name, r.decision?.verdict);
  }
}

A non-2xx response throws QueryError with the HTTP status on .status; a failed fetch throws TransportError. The wire contract, including the values outcome accepts and the server's pagination limits, is on the Receipt Query API page.

Cognition market

The root entry point also carries the Finding market clients. A buyer searches, verifies a proof bundle, and purchases; a seller packages a verified fix and admits or retracts it; a hosted client speaks the multi-tenant API. Verification is not reimplemented in TypeScript: the buyer shells out to the chio binary and parses its JSON, so the Rust verifier stays the only judge of a bundle.

CognitionMarketBuyer

Constructed from a buyer profile path and an options object (chioBinary, fetch, statusFloorPath, timeoutMs, default 30000). The profile must carry the schema chio.finding.buyer-client.v1. The status floor defaults to the profile path with .status-floor.json appended.

MethodBehavior
search(options)GET /v1/findings/search with topicPrefix, limit (default 20), and an optional cursor. An empty or untrimmed prefix throws before the request.
proof(findingId)GET /v1/findings/{id}/proof, returning the bundle bytes. The id must be lowercase 64-hex, and the response is bounded at 24 MiB.
verifyProof(proof)Pipes the bundle into chio finding verify-bundle --profile <path> --input - --json and returns the Finding id, the bytes, and the verifier report.
verifiedProof(findingId)Fetch then verify, refusing a bundle that names a different Finding.
purchase(verified, options)POST /v1/findings/{id}/purchase with a canonical request built from maxPriceUnits, currency (default USD), and deadlineSecs (default 3600). The terminal is verified before the call returns.
purchaseVerifiedFix(verified, options)The same purchase, returning a PurchasedVerifiedFix: repository, base and candidate revisions, and the patch text. Nothing is written to a workspace.
status(findingId)Runs chio finding status against the profile's feed, operator authorization, service bond, rollback floor, and epoch age bound, then maps non_inclusion to live and inclusion to retracted.
challenge(findingId, signedChallenge)POST /v1/findings/{id}/challenges with caller-signed bytes, bounded at 1 MiB.
challengeEvidenceInvalid(verified, purchased, options?)Re-verifies the purchase terminal, builds the evidence document, and files it with chio finding challenge --class evidence-invalid. Requires a signing seed on the profile.
typescript
import { CognitionMarketBuyer, CognitionMarketError } from "@chio-protocol/sdk";
import type { PurchasedVerifiedFix, VerifiedFindingProof } from "@chio-protocol/sdk";

const buyer = new CognitionMarketBuyer("./buyer-profile.json");

const page = await buyer.search({ topicPrefix: "repo/parser", limit: 20 });

const verified: VerifiedFindingProof = await buyer.verifiedProof(findingId);
const live = await buyer.status(findingId);
if (live.status !== "live") throw new CognitionMarketError("finding is retracted");

const fix: PurchasedVerifiedFix = await buyer.purchaseVerifiedFix(verified, {
  maxPriceUnits: 300,
  currency: "USD",
});
console.log(fix.repository, fix.baseRevision, fix.candidateRevision);

CognitionMarketSeller

Constructed from a credential path carrying the schema chio.finding.seller-client.v1. The default request timeout is 720000 milliseconds, sized for the operator's bounded packaging sandbox.

  • packageVerifiedFix(input) builds the submission identity (repository, base and candidate revisions, tests, topic, price) and derives its requestId as SHA-256 over the domain-separated canonical JSON. Price defaults to 300 and must fall between 1 and the verified-fix sale exposure of 450 units. The repository must be an absolute normalized operator-side path, and a caller-supplied output is refused because the package is operator-owned.
  • admit(packageRequest) posts the package to POST /v1/findings/operator/verified-fixes.
  • retract(findingId) posts chio.finding.voluntary-retraction-request.v1 to POST /v1/findings/operator/retractions, with a request id derived from the retraction domain and the Finding id.

HostedCognitionMarketClient

The multi-tenant transport. The constructor takes endpoint, tenantId, apiKeyId, apiKeySecret, and optional fetch and timeoutMs. The endpoint must be an https: URL with no embedded credentials. Every request carries chio-api-key-id, chio-api-key-secret, chio-request-id, and chio-tenant-id; a request with a body adds idempotency-key.

  • publish(finding, requestId, idempotencyKey): POST /v1/findings/publish.
  • mutate(operation, mutation, requestId): POST /v1/findings/events/{operation}, where the operation is one of listing, delivery, challenge, verified-fix, retraction, and penalty. The mutation carries aggregateId, eventId, a non-negative expectedRevision, and the payload; the event id doubles as the idempotency key.
  • finding(findingId, requestId): GET /v1/findings/{id}.
  • findings(options): GET /v1/findings with an optional after and a limit from 1 through 100.

Every failure on all three clients raises CognitionMarketError: a non-2xx response, an oversized body, a timeout, a profile whose schema does not match, and a verifier answer that names a different Finding. The class does not extend ChioError.

findingBidCeiling

findingBidCeiling computes a buyer-local ceiling with exact integer arithmetic and returns it as a decimal string. It authenticates nothing. What it does is bind one caller-carried estimate to the buyer's own expected source, context, and replay-recipe digests, to the buyer's currency, and to a validity window, then discount the estimate by three basis-point factors and cap the result at the remaining budget. Arithmetic runs in BigInt and rounds down once, after the combined product.

typescript
import { findingBidCeiling, FindingBidCeilingError } from "@chio-protocol/sdk";
import type {
  BuyerFindingEstimate,
  FindingBidCeilingInput,
  FindingBidCeilingPolicy,
} from "@chio-protocol/sdk";

const estimate: BuyerFindingEstimate = {
  units: "1200",
  currency: "USD",
  provenance: "buyer_metering_history_v1",
  sourceSha256: expectedSourceSha256,
  contextSha256: expectedContextSha256,
  replayRecipeSha256: expectedReplayRecipeSha256,
  observedAtUnixMs: "1744500000000",
  validUntilUnixMs: "1744503600000",
};

const policy: FindingBidCeilingPolicy = {
  budgetRemainingUnits: "5000",
  currency: "USD",
  wouldHaveRunBps: "9000",       // 0 to 10000
  siblingRedundancyBps: "1500",  // subtracted from 10000
  guaranteeClassBps: "10000",
};

const input: FindingBidCeilingInput = {
  estimate,
  policy,
  expectedSourceSha256,
  expectedContextSha256,
  expectedReplayRecipeSha256,
  nowUnixMs: "1744501000000",
};

try {
  const ceiling: string = findingBidCeiling(input);
} catch (err) {
  if (err instanceof FindingBidCeilingError) console.error(err.code);
}

FindingBidCeilingError.code is one of invalid_decimal, u64_overflow, basis_points_out_of_range, currency_mismatch, provenance_unsupported, source_substituted, context_substituted, replay_recipe_substituted, digest_malformed, invalid_validity_window, and stale_estimate. Only two provenance values are accepted: buyer_metering_history_v1 and buyer_fresh_metered_quote_v1.

Published packages

Framework integrations, runtime bindings, the WASM guard guest SDK, and the scaffolder ship as separate, independently versioned npm packages rather than subpaths of @chio-protocol/sdk. Every package under sdks/ whose manifest is not marked private is below, with the version and the declared dependencies that manifest carries.

PackageVersionSubpathsDeclared dependencies
@chio-protocol/ai-sdk0.1.0.none
@chio-protocol/ai-sdk-middleware0.1.0.none
@chio-protocol/browser0.1.0., ./pkg/*@chio-protocol/wasm-core
@chio-protocol/deno0.1.0., ./web/*@chio-protocol/wasm-core
@chio-protocol/edge0.1.0., ./web/*@chio-protocol/wasm-core
@chio-protocol/elysia0.1.0.@chio-protocol/node-http
@chio-protocol/express0.1.0.@chio-protocol/node-http
@chio-protocol/fastify0.1.0.@chio-protocol/node-http, fastify-plugin
@chio-protocol/guard-ts0.2.0nonenone
@chio-protocol/mobile0.1.0., ./expo-pluginnone
@chio-protocol/next0.1.0.none
@chio-protocol/node-http0.1.0.none
@chio-protocol/passkey0.1.0.none
@chio-protocol/sdk0.1.0., ./invariants, ./transportnone
@chio-protocol/wasm-core0.1.0.none
@chio-protocol/workers0.1.0., ./bundler/*none
create-chio-app0.1.0.none

The dependency column is the whole basis for two claims often made about these packages. @chio-protocol/elysia, @chio-protocol/express, @chio-protocol/fastify are the sidecar HTTP adapters: each declares @chio-protocol/node-http, the shared interception layer that evaluates a request against a chio sidecar, attaches the signed receipt, and surfaces the deny reason to the handler. No other published package declares it, and none of them declares the in-process core @chio-protocol/sdk. @chio-protocol/browser, @chio-protocol/deno, @chio-protocol/edge declare @chio-protocol/wasm-core, the shared receipt-hex helpers the WASM runtimes build on.

A worked application

examples/hello-express serves the same three routes as the other framework applications under examples/: GET /healthz skipped, GET /hello allowed, and POST /echo refused until the caller presents a capability. It points the middleware at a sidecar URL rather than a config file, and reads the receipt id off the request inside the handler. The file is .mjs, so this is JavaScript; the types are the same.

examples/hello-express/server.mjs:41-60javascript
  if (enableChio) {
    app.use(
      chio({
        sidecarUrl,
        skip: ["/healthz"],
      }),
    );
  }
  app.use(express.json());

  app.get("/healthz", (_req, res) => {
    res.json({ status: "ok" });
  });

  app.get("/hello", (req, res) => {
    res.json({
      message: "hello from express",
      receipt_id: req.chioResult?.receipt.id ?? null,
    });
  });

Fastify and Elysia take the same options through their own registration idiom. Both are worked at examples/hello-fastify and examples/hello-elysia.

examples/hello-fastify/server.mjs:41-46javascript
  if (enableChio) {
    await fastify.register(chio, {
      sidecarUrl,
      skip: ["/healthz"],
    });
  }

On an allow, the interception layer sets X-Chio-Receipt-Id to the receipt id and puts the evaluation result on req.chioResult. On a deny it answers with the verdict's HTTP status and a JSON body of error, message, receipt_id, and suggestion, where error is one of six CHIO_ERROR_CODES values: chio_access_denied, chio_sidecar_unreachable, chio_sidecar_unavailable, chio_evaluation_failed, chio_invalid_receipt, and chio_timeout. A sidecar that answers with a receipt that fails verification is a 502 with chio_invalid_receipt, not a pass.

Error hierarchy

Every SDK-level error extends ChioError, which carries a string code. Catch the base class for broad recovery, or a subclass to branch on failure mode. DpopSignError has code dpop_sign_error, QueryError has query_error plus an optional HTTP status, and TransportError has transport_error.

typescript
import {
  ChioError,
  DpopSignError,
  QueryError,
  TransportError,
} from "@chio-protocol/sdk";

try {
  const page = await client.query({ capabilityId: "cap_abc" });
} catch (err) {
  if (err instanceof QueryError) {
    console.error("query failed", err.status, err.message);
  } else if (err instanceof TransportError) {
    console.error("network failed", err.message);
  } else if (err instanceof ChioError) {
    console.error("chio error", err.code, err.message);
  } else {
    throw err;
  }
}

Two error trees, not one

ChioInvariantError lives under @chio-protocol/sdk/invariants and does not extend ChioError. Its code is one of json, canonical_json, invalid_hex, invalid_public_key, and invalid_signature. CognitionMarketError and FindingBidCeilingError extend neither. Catch all three trees if you call across the layers.

Quickstart

Open a session, list tools, sign a DPoP proof, call a tool, then verify the receipt offline.

typescript
import {
  ChioClient,
  signDpopProof,
  staticBearerAuth,
} from "@chio-protocol/sdk";
import { verifyReceiptJson } from "@chio-protocol/sdk/invariants";

const client = new ChioClient({
  baseUrl: "https://edge.example.com",
  ...staticBearerAuth(process.env.CHIO_TOKEN!),
});

const session = await client.initialize();

const tools = await session.listTools();
console.log("available tools:", tools);

const args = { path: "./README.md" };

// Sign a DPoP proof bound to this capability, tool, and argument hash.
const proof = signDpopProof({
  capabilityId: process.env.CHIO_CAPABILITY_ID!,
  toolServer: "srv-files",
  toolName: "read_file",
  actionArgs: args,
  agentSeedHex: process.env.CHIO_AGENT_SEED_HEX!,
});
console.log("dpop proof:", proof.signature);

const result = await session.callTool("read_file", args);
console.log("result:", result);

await session.close();

// The kernel signs a receipt for the call. Pull it from your receipt store
// (see ReceiptQueryClient) as a JSON string, then verify it offline.
const verification = verifyReceiptJson(receiptJson);
if (!verification.signature_valid || !verification.parameter_hash_valid) {
  throw new Error("receipt failed local verification");
}

Conformance

The TypeScript SDK runs against the cross-language conformance vectors. Canonical JSON output, SHA-256 digests, Ed25519 signatures, receipt verification, capability verification, and manifest verification all produce byte-identical results against the Rust reference. The test command uses the Node type-stripping runner declared in the manifest.

bash
$ cd sdks/typescript/chio-ts && npm test