Skip to content

TypeScript SDK

The TypeScript SDK is the path of least resistance when you control the agent’s source. Install, call init(), and every LLM call your code makes routes through Lupid’s enforcement pipeline transparently.

Terminal window
npm install @lupid/agentum-sdk
# or
pnpm add @lupid/agentum-sdk
  1. Get an API key for your agent. From the dashboard’s Admin → API Keys page, or from the CLI:

    Terminal window
    agentum admin api-keys mint \
    --email "support-bot@yourco.com" \
    --role admin

    The response includes a plaintext_key — copy it now, it’s only shown once.

  2. Set environment variables for the agent process:

    Terminal window
    export LUPID_BASE_URL=http://localhost:3000 # or your dashboard URL
    export LUPID_API_KEY=ak_... # the plaintext key from step 1
  3. Initialise the SDK at agent startup:

    import { Lupid } from "@lupid/agentum-sdk";
    const lupid = await Lupid.init({
    baseUrl: process.env.LUPID_BASE_URL!,
    apiKey: process.env.LUPID_API_KEY!,
    agentName: "support-bot",
    declaredTools: ["search_kb", "create_ticket"],
    });

    init() does three things:

    • Resolves your agentName to the agent’s UUID (registers the agent if it doesn’t exist).
    • Installs fetch interceptors for the LLM hosts in the registry: OpenAI, Anthropic, Cohere, Google (Gemini / generativelanguage.googleapis.com), Azure OpenAI (*.openai.azure.com), plus the OpenAI-compatible providers Together, Mistral, Groq, DeepSeek, X.AI, Perplexity, OpenRouter, Fireworks, and Anyscale. Any fetch() to one of those goes through Lupid’s policy engine before it leaves.
    • Returns a handle you can use for explicit policy checks (see below).

That’s it. From this point on, your existing LLM call code runs unchanged but each call is policy-checked and audited.

The fetch interceptors cover the transport. For policy decisions on app-level tool dispatch (e.g. a LangChain agent picking create_ticket), call the gate directly:

const decision = await lupid.evaluateToolCall("create_ticket", {
customerId: "c_123",
priority: "urgent",
});
if (decision.allowed) {
await createTicket(/* ... */);
} else {
// decision.rule_id and decision.reason are populated
console.warn(`Lupid blocked create_ticket: ${decision.reason}`);
}

evaluateToolCall returns allow, deny, or require_hitl. On require_hitl, the call parks in the approval queue and the promise resolves when an operator clicks through (or after the timeout you set).

The SDK handles streaming LLM responses transparently. Server-Sent Event chunks pass through; the DLP-on-response check fires at the SSE boundary. If a chunk contains a PII/secret hit above the block threshold, the stream is cut and the agent receives a clean termination with the audit row pointing to the offending chunk’s hash.

Most calls inherit the agent context from init(). To override per-request:

await lupid.evaluateToolCall("create_ticket", { ... }, {
sessionId: "user-session-42", // bind the call to a user session
userId: "u_alice", // bind to a specific end-user
trustLevel: "low", // override the default trust level
});

The audit row records these on the event, so you can query “every action this user caused this agent to take” later.

The SDK throws LupidPolicyError (for deny), LupidHitlError (for require_hitl that timed out), and LupidNetworkError (for transport failures). Catch them specifically:

import { Lupid, LupidPolicyError } from "@lupid/agentum-sdk";
try {
await lupid.evaluateToolCall("delete_user", { id: 42 });
} catch (e) {
if (e instanceof LupidPolicyError) {
// Policy denied — log and show user-friendly message
return reply.status(403).send({ error: "not authorised" });
}
throw e; // unknown error, let it propagate
}

Right after init(), log a deliberate denied tool call and check the dashboard’s audit page (/audit). You should see a tool_call event with outcome=deny within a second. If you don’t, double-check LUPID_BASE_URL and that the API key hasn’t been revoked.