CAIN-42 CAIN Studio

Developer documentation

TypeScript SDK

Last reviewed 31 August 2026

All docs

TypeScript and JavaScript#

There is no npm package yet. The decision API is one HTTP call, so the helper below is the whole integration: copy it into your project. Node 18+ (global fetch), Deno, Bun and edge runtimes. No dependencies.

// cain.ts
const CAIN_URL = process.env.CAIN_BASE_URL ?? "https://cainstudio.online";

export class CainRefused extends Error {
  constructor(message: string, readonly decision?: any) { super(message); }
}

/** Ask CAIN whether a tool call may run. Resolves with the decision; never throws. */
export async function decide(tool: string, payload: object, agentId = "my-agent", runId?: string) {
  try {
    const r = await fetch(`${CAIN_URL}/fabric/decisions`, {
      method: "POST",
      headers: { "X-API-Key": process.env.CAIN_API_KEY ?? "", "content-type": "application/json" },
      body: JSON.stringify({ path: `/tools/${tool}`, payload, agent_id: agentId, chain_id: runId }),
      signal: AbortSignal.timeout(5000),
    });
    return await r.json();
  } catch (e) {
    return { verdict: "ERROR", blocked: true, detail: String(e) };   // fail closed
  }
}

/** The only permit: an ALLOWED* verdict that is not blocked. */
export const allowed = (d: any) =>
  d?.blocked === false && String(d?.verdict ?? "").startsWith("ALLOWED");

/** Wrap a tool: it runs only if CAIN allows this call with these arguments. */
export function guard<A extends object, R>(tool: string, fn: (args: A) => Promise<R>, agentId?: string) {
  return async (args: A): Promise<R> => {
    const d = await decide(tool, args, agentId);
    if (!allowed(d)) throw new CainRefused(`${tool}: ${d.verdict ?? d.detail ?? "no verdict"} (decision ${d.decision_id ?? "-"})`, d);
    return fn(args);
  };
}
import { guard } from "./cain";

export const sendEmail = guard("send_email", async ({ to, subject }: { to: string; subject: string }) => {
  // unchanged
});

A new agent has no trust history, so its first calls come back REQUIRE_APPROVAL: approve them in the console, or add a tool rule. The verdicts, the decision record and the approval flow are the same as in the Python SDK and the HTTP quickstart. The full API is in openapi.json.