Thoth SDK
sdk v0.1.6 / proxy v0.2.7
SDKs

TypeScript SDK

Full reference for the Thoth TypeScript SDK — ThothConfig, instrument(), wrapAnthropicTools(), wrapOpenAITools(), EnforcementMode, ThothPolicyViolation.

Installation

npm install @atensec/thoth
# or
pnpm add @atensec/thoth
# or
yarn add @atensec/thoth

Node.js 18+ required. The package ships full TypeScript declarations and requires a runtime with fetch + AbortSignal.timeout support.


ThothConfig

interface ThothConfig {
  agentId: string;
  approvedScope: string[];
  tenantId: string;
  userId?: string; // default: "system"
  enforcement?: EnforcementMode; // default: EnforcementMode.PROGRESSIVE
  apiKey?: string; // or set THOTH_API_KEY env var
  apiUrl?: string; // required, or set THOTH_API_URL env var
  stepUpTimeoutMinutes?: number; // default: 15
  stepUpPollIntervalMs?: number; // default: 5000
  sessionIntent?: string; // optional HIPAA minimum-necessary session scope
  policyContext?: Record<string, unknown>; // optional metadata.policy_context payload
  enforcementTraceId?: string; // default: generated session UUID
  environment?: string; // default: "prod"
}

Fields

FieldTypeDefaultDescription
agentIdstringrequiredUnique identifier for this agent
approvedScopestring[]requiredTool names the agent is authorized to call
tenantIdstringrequiredYour Thoth tenant identifier
userIdstring"system"User initiating the request
enforcementEnforcementModePROGRESSIVEHow to handle policy violations
apiKeystringprocess.env.THOTH_API_KEYAPI key for authentication
apiUrlstringprocess.env.THOTH_API_URLRequired tenant API base URL used for both /v1/enforce and /v1/events/batch
stepUpTimeoutMinutesnumber15Max wait time for step-up approval
stepUpPollIntervalMsnumber5000Step-up polling interval in milliseconds
sessionIntentstringunsetSession purpose used for HIPAA minimum-necessary checks
policyContextRecord<string, unknown>unsetTenant policy context sent as metadata.policy_context
enforcementTraceIdstringgenerated session UUIDCorrelation ID propagated to enforcer/services
environmentstring"prod"Environment tag for env-scoped policy resolution

EnforcementMode

enum EnforcementMode {
  OBSERVE = "observe", // log only, never block
  STEP_UP = "step_up", // require human approval
  BLOCK = "block", // immediately reject violations
  PROGRESSIVE = "progressive", // escalating enforcement (default)
}

instrument()

Instrument a generic AI agent with Thoth governance. Wraps all tools with enforce/emit hooks. Returns the same agent object (mutated in-place).

function instrument<T extends object>(agent: T, config: ThothConfig): T;

Agent shape

The agent object must have a tools array where each element has a name: string and run: Function:

const agent = {
  tools: [
    {
      name: "search_docs",
      run: async (query: string) => `results for: ${query}`,
    },
    {
      name: "submit_payment",
      run: async (invoiceId: string) => `payment submitted: ${invoiceId}`,
    },
  ],
};

Example

import {
  instrument,
  EnforcementMode,
  ThothPolicyViolation,
} from "@atensec/thoth";
 
const agent = {
  tools: [
    { name: "search_docs", run: async (q: string) => `docs: ${q}` },
    { name: "submit_payment", run: async (id: string) => `paid: ${id}` },
  ],
};
 
instrument(agent, {
  agentId: "invoice-processor-v2",
  approvedScope: ["search_docs"], // submit_payment is out of scope
  tenantId: "acme-corp",
  apiUrl: "https://enforce.acme-corp.atensecurity.com",
  enforcement: EnforcementMode.PROGRESSIVE,
});
 
// In-scope call — executes normally
const result = await agent.tools[0].run("quarterly report");
 
// Out-of-scope call — throws ThothPolicyViolation in block/step_up mode
try {
  await agent.tools[1].run("INV-001");
} catch (err) {
  if (err instanceof ThothPolicyViolation) {
    console.error(`Blocked: ${err.reason} (violation_id=${err.violationId})`);
  }
}

Async generators

instrument() detects async generator functions automatically and wraps them correctly, yielding all chunks while still emitting pre/post behavioral events:

const streamingTool = {
  name: "stream_response",
  async *run(query: string) {
    for (const chunk of chunks) {
      yield chunk;
    }
  },
};

Failure behavior

Current SDK behavior is fail-closed for policy checks: if /v1/enforce is unreachable or returns a non-OK response, the decision falls back to BLOCK (reason: "enforcer unavailable"), and instrument() will throw ThothPolicyViolation before running the tool.


emitBehavioralEvent()

Emit a single behavioral event directly to the Thoth API. Fire-and-forget — never blocks execution.

import { emitBehavioralEvent } from "@atensec/thoth";
// also available as a named import from the package
 
async function emitBehavioralEvent(
  event: BehavioralEvent,
  apiUrl: string,
  apiKey: string,
): Promise<void>;

The function catches all errors internally and never throws. If the API is unreachable, the error is silently discarded to preserve agent availability.

BehavioralEvent schema

interface BehavioralEvent {
  eventId: string; // UUID — generated automatically by instrument()
  tenantId: string;
  agentId?: string;
  sessionId: string;
  toolName?: string;
  userId: string;
  sourceType: SourceType; // "agent_tool_call" for SDK-wrapped tools
  eventType: EventType; // "TOOL_CALL_PRE" | "TOOL_CALL_POST" | "LLM_INVOCATION"
  content: string; // JSON-serialized arguments or result
  metadata?: Record<string, unknown>;
  approvedScope: string[];
  enforcementMode: EnforcementMode;
  sessionToolCalls: string[]; // tool names called so far in this session
  occurredAt: Date;
}

wrapAnthropicTools()

Wrap tool execution functions for use in an Anthropic Claude agentic loop. Returns a new map of governed callables.

import { wrapAnthropicTools } from "@atensec/thoth/anthropic";
 
function wrapAnthropicTools(
  toolFns: Record<string, AnthropicToolFn>,
  config: ThothConfig,
): Record<string, AnthropicToolFn>;
 
type AnthropicToolFn = (
  input: Record<string, unknown>,
) => unknown | Promise<unknown>;

Example

import Anthropic from "@anthropic-ai/sdk";
import { wrapAnthropicTools } from "@atensec/thoth/anthropic";
 
const client = new Anthropic();
 
const toolFns = {
  search_docs: async (input: Record<string, unknown>) => {
    const query = input.query as string;
    return `docs for: ${query}`;
  },
};
 
const governed = wrapAnthropicTools(toolFns, {
  agentId: "support-bot",
  approvedScope: ["search_docs"],
  tenantId: "acme-corp",
  apiUrl: "https://enforce.acme-corp.atensecurity.com",
});
 
// Standard Anthropic agentic loop
const tools: Anthropic.Tool[] = [
  {
    name: "search_docs",
    description: "Search company documentation",
    input_schema: {
      type: "object" as const,
      properties: { query: { type: "string" } },
      required: ["query"],
    },
  },
];
 
const messages: Anthropic.MessageParam[] = [
  { role: "user", content: "Find docs about expense reports" },
];
 
while (true) {
  const response = await client.messages.create({
    model: "claude-opus-4-5",
    max_tokens: 1024,
    tools,
    messages,
  });
 
  if (response.stop_reason === "end_turn") break;
 
  const toolResults: Anthropic.ToolResultBlockParam[] = [];
  for (const block of response.content) {
    if (block.type === "tool_use") {
      const fn = governed[block.name];
      if (fn) {
        const result = await fn(block.input as Record<string, unknown>);
        toolResults.push({
          type: "tool_result",
          tool_use_id: block.id,
          content: String(result),
        });
      }
    }
  }
 
  messages.push({ role: "assistant", content: response.content });
  messages.push({ role: "user", content: toolResults });
}

wrapOpenAITools()

Wrap tool execution functions for use in an OpenAI tool-calling loop.

import { wrapOpenAITools } from "@atensec/thoth/openai";
 
function wrapOpenAITools(
  toolFns: Record<string, OpenAIToolFn>,
  config: ThothConfig,
): Record<string, OpenAIToolFn>;
 
type OpenAIToolFn = (
  args: Record<string, unknown>,
) => unknown | Promise<unknown>;

Example

import OpenAI from "openai";
import { wrapOpenAITools } from "@atensec/thoth/openai";
 
const openai = new OpenAI();
 
const governed = wrapOpenAITools(
  {
    search_docs: async (args) => `docs for: ${args.query}`,
  },
  {
    agentId: "support-bot",
    approvedScope: ["search_docs"],
    tenantId: "acme-corp",
    apiUrl: "https://enforce.acme-corp.atensecurity.com",
  },
);
 
const response = await openai.chat.completions.create({
  model: "gpt-4o",
  tools: [
    {
      type: "function",
      function: {
        name: "search_docs",
        description: "Search company docs",
        parameters: {
          type: "object",
          properties: { query: { type: "string" } },
        },
      },
    },
  ],
  messages: [{ role: "user", content: "Find docs about expense reports" }],
});
 
for (const toolCall of response.choices[0].message.tool_calls ?? []) {
  const fn = governed[toolCall.function.name];
  if (fn) {
    const args = JSON.parse(toolCall.function.arguments) as Record<
      string,
      unknown
    >;
    const result = await fn(args); // governance runs here
    console.log(result);
  }
}

ThothPolicyViolation

Thrown when the enforcer blocks a tool call.

class ThothPolicyViolation extends Error {
  constructor(
    public readonly toolName: string,
    public readonly reason: string,
    public readonly violationId?: string,
  )
}
import { ThothPolicyViolation } from "@atensec/thoth";
 
try {
  await governedTool(input);
} catch (err) {
  if (err instanceof ThothPolicyViolation) {
    console.error({
      tool: err.toolName,
      reason: err.reason,
      violationId: err.violationId, // correlate with Thoth evidence records
    });
    return { error: "Action blocked by policy" };
  }
  throw err;
}

EnforcementDecision

The enforcer response shape (returned from POST /v1/enforce):

interface EnforcementDecision {
  decision: DecisionType; // "ALLOW" | "BLOCK" | "STEP_UP"
  reason?: string;
  violationId?: string;
  holdToken?: string; // present when decision === "STEP_UP"
}
 
enum DecisionType {
  ALLOW = "ALLOW",
  BLOCK = "BLOCK",
  STEP_UP = "STEP_UP",
}

Environment Variables

VariableDescription
THOTH_API_KEYAPI key for hosted Thoth authentication
THOTH_API_URLRequired tenant API base URL for both policy checks and event ingestion

The TypeScript SDK reads these environment variables automatically in Node.js environments. If config.apiUrl and THOTH_API_URL are both missing, instrument() throws at startup.


TypeScript strict mode

The SDK is compiled with strict: true. All exported types are narrowed — no implicit any. If you use instrument<T>(), the generic T preserves your agent's type through the instrumentation:

interface MyAgent {
  tools: Array<{ name: string; run: (input: string) => Promise<string> }>
  someOtherField: string
}
 
const agent: MyAgent = { tools: [...], someOtherField: "hello" }
const governed = instrument(agent, config)
// governed is still typed as MyAgent
console.log(governed.someOtherField)  // ✓ TypeScript happy

On this page