Edictum
TypeScript SDK

TypeScript Adapters

Connect Edictum to TypeScript agent frameworks and check the current Claude Agent SDK compatibility limit.

AI Assistance

Right page if: you want to wire Edictum into a TypeScript AI framework -- Vercel AI, Claude SDK, LangChain.js, or OpenAI Agents. Wrong page if: you are using Python adapters -- see https://docs.edictum.ai/docs/adapters/overview. For server-side features, see https://docs.edictum.ai/docs/typescript/server. Gotcha: each adapter is a separate npm package (`@edictum/vercel-ai`, `@edictum/claude-sdk`, etc.), not extras on the core package like Python. The shipped Claude package does not yet return SDK-native matcher objects or callback signatures; do not wire toSdkHooks() directly into the Claude Agent SDK.

Each TypeScript adapter translates between a framework's hook system and the Edictum pipeline. Policy decisions live in @edictum/core, while adapter translation determines how the framework enforces them. Framework capabilities and adapter limitations therefore matter to the enforcement result.

Common Pattern

Every adapter follows the same setup:

import { Edictum } from '@edictum/core'
import type { Principal } from '@edictum/core'

// 1. Load rulesets (shared across all adapters)
const guard = Edictum.fromYaml('rules.yaml')

All adapters accept the same constructor options:

ParameterTypeDefaultDescription
guardEdictumrequiredThe Edictum instance holding rulesets
sessionIdstringauto UUIDGroups related tool calls for session limit tracking
principalPrincipalnullStatic identity context for audit events
principalResolverFunctionnullDynamic principal resolution per tool call

All adapters expose setPrincipal(principal) to update identity mid-session.

Quick Comparison

FrameworkPackageIntegration MethodReturns
Vercel AI SDK@edictum/vercel-aiasCallbacks()experimental_onToolCallStart / experimental_onToolCallFinish
Claude Agent SDK@edictum/claude-sdktoSdkHooks()Edictum-specific callback arrays; not directly SDK-compatible
LangChain.js@edictum/langchainasMiddleware(){ name, wrapToolCall } middleware object
OpenAI Agents SDK@edictum/openai-agentsasGuardrails(){ inputGuardrail, outputGuardrail }

Vercel AI SDK

Callbacks for generateText and streamText.

Install

pnpm add @edictum/core @edictum/vercel-ai js-yaml

Create adapter

import { Edictum } from '@edictum/core'
import { VercelAIAdapter } from '@edictum/vercel-ai'

const guard = Edictum.fromYaml('rules.yaml')
const adapter = new VercelAIAdapter(guard, {
  sessionId: 'session-01',
  principal: { user_id: 'alice', role: 'analyst' },
})

Wire into agent

import { generateText } from 'ai'
import { openai } from '@ai-sdk/openai'

const result = await generateText({
  model: openai('gpt-4o'),
  tools: { readFile, runCommand },
  prompt: 'Summarize the Q3 report',
  ...adapter.asCallbacks(),
})

Preconditions are fully enforced via experimental_onToolCallStart -- blocked calls throw EdictumDenied before the tool executes. Postconditions fire in experimental_onToolCallFinish as notification-only callbacks. For full postcondition enforcement (redact / block), use guard.run() directly.

The adapter handles both AI SDK v5 (args) and v6 (input) field names automatically.


Claude Agent SDK

Current pre/post tool hook adapter shape and its Claude Agent SDK compatibility limit.

Install

pnpm add @edictum/core @edictum/claude-sdk js-yaml

Create adapter

import { Edictum } from '@edictum/core'
import { ClaudeAgentSDKAdapter } from '@edictum/claude-sdk'

const guard = Edictum.fromYaml('rules.yaml')
const adapter = new ClaudeAgentSDKAdapter(guard, {
  sessionId: 'session-01',
  principal: { user_id: 'alice', role: 'analyst' },
})

toSdkHooks() is not directly compatible with the Claude Agent SDK. The shipped adapter returns bare callback arrays whose callbacks expect one { input } argument. The SDK requires matcher objects and calls each callback as (input, toolUseID, context). Do not pass these arrays directly to the SDK.

The adapter's generated pre-hook emits permissionDecision: 'deny' for an Edictum block, but the current direct wiring does not reliably reach that code. If your application owns the tool callable, use guard.run(). There is currently no supported Edictum integration for Claude Agent SDK built-in tools.

The post-hook emits the deprecated updatedMCPToolOutput field. That field can replace MCP tool output only; it does not replace output from built-in tools such as Bash, Read, or Edit.


LangChain.js

Middleware for LangChain.js ToolNode.

Install

pnpm add @edictum/core @edictum/langchain js-yaml

Create adapter

import { Edictum } from '@edictum/core'
import { LangChainAdapter } from '@edictum/langchain'

const guard = Edictum.fromYaml('rules.yaml')
const adapter = new LangChainAdapter(guard, {
  sessionId: 'session-01',
  principal: { user_id: 'alice', role: 'analyst' },
})

Wire into agent

const middleware = adapter.asMiddleware()
// middleware = { name: "edictum", wrapToolCall: fn }

// Pass to ToolNode or agent as tool_call_middleware
const toolNode = new ToolNode({
  tools: [searchTool, calculatorTool],
  toolCallMiddleware: [middleware],
})

The middleware wraps tool execution -- pre-enforcement runs before the tool, post-enforcement runs after. Blocked calls throw EdictumDenied. Postcondition redaction is fully supported since the middleware controls the return value.

The adapter also provides asToolWrapper() for wrapping individual tool callables:

const wrapper = adapter.asToolWrapper()
const governed = wrapper(myToolFn)
const result = await governed('readFile', { path: 'data.csv' })

OpenAI Agents SDK

Input and output guardrails for the OpenAI Agents SDK.

Install

pnpm add @edictum/core @edictum/openai-agents js-yaml

Create adapter

import { Edictum } from '@edictum/core'
import { OpenAIAgentsAdapter } from '@edictum/openai-agents'

const guard = Edictum.fromYaml('rules.yaml')
const adapter = new OpenAIAgentsAdapter(guard, {
  sessionId: 'session-01',
  principal: { user_id: 'alice', role: 'analyst' },
})

Wire into agent

const { inputGuardrail, outputGuardrail } = adapter.asGuardrails()
// inputGuardrail  — { name: "edictum_input_guardrail", execute: fn }
// outputGuardrail — { name: "edictum_output_guardrail", execute: fn }

// Apply per-tool or per-agent
const agent = createAgent({
  toolInputGuardrails: [inputGuardrail],
  toolOutputGuardrails: [outputGuardrail],
})

Preconditions are enforced via the input guardrail (tripwireTriggered: true on block). With one pending tool call, the native output guardrail enforces postcondition block. Postcondition redact requires the wrapper integration path -- the SDK's output guardrail cannot substitute tool results.

Concurrent tool calls limitation. The output guardrail receives the agent's text output, not per-tool output. When multiple tool calls are in-flight, postcondition evaluation is skipped to avoid misattributing output. Use _pre() / _post() with explicit call IDs for guaranteed postcondition enforcement under concurrent load.


Using guard.run() Directly

If your framework is not listed, use guard.run() directly -- it provides the full pipeline without any adapter:

import { Edictum, EdictumDenied } from '@edictum/core'

const guard = Edictum.fromYaml('rules.yaml')

try {
  const result = await guard.run(
    'readFile',
    { path: '/etc/passwd' },
    async (args) => readFile(String(args.path)),
  )
} catch (e) {
  if (e instanceof EdictumDenied) {
    console.log(`DENIED: ${e.reason}`)
  }
}

Callbacks and Observe Mode

All adapters support lifecycle callbacks and observe mode, configured on the guard:

const guard = Edictum.fromYaml('rules.yaml', {
  mode: 'observe',
  onDeny: (envelope, reason) => console.log(`Would block: ${reason}`),
  onAllow: (envelope) => console.log(`Allowed: ${envelope.toolName}`),
})

In observe mode, blocked calls are logged as CALL_WOULD_DENY audit events but allowed to proceed. See observe mode.

Next Steps

Last updated on

On this page