Edictum
TypeScript SDK

TypeScript Adapters

Connect Edictum to Vercel AI SDK, Claude Agent SDK, LangChain.js, OpenAI Agents SDK, and OpenClaw in TypeScript.

AI Assistance

Right page if: you want to wire Edictum into a TypeScript AI framework -- Vercel AI, Claude SDK, LangChain.js, OpenAI Agents, or OpenClaw. 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. All adapters share the same constructor pattern and enforce identical contract semantics.

Each TypeScript adapter translates between a framework's hook system and the Edictum pipeline. Adapters are thin wrappers -- all governance logic lives in @edictum/core. Switching frameworks means changing only the adapter wiring, not your contracts.

Common Pattern

Every adapter follows the same setup:

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

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

All adapters accept the same constructor options:

ParameterTypeDefaultDescription
guardEdictumrequiredThe Edictum instance holding contracts
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(){ PreToolUse, PostToolUse } hook arrays
LangChain.js@edictum/langchainasMiddleware(){ name, wrapToolCall } middleware object
OpenAI Agents SDK@edictum/openai-agentsasGuardrails(){ inputGuardrail, outputGuardrail }
OpenClaw@edictum/openclawcreateEdictumPlugin()OpenClaw plugin with before_tool_call / after_tool_call hooks

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('contracts.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 -- denied calls throw EdictumDenied before the tool executes. Postconditions fire in experimental_onToolCallFinish as notification-only callbacks. For full postcondition enforcement (redact/deny), use guard.run() directly.

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


Claude Agent SDK

Pre/post tool use hooks for the Claude Agent SDK.

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('contracts.yaml')
const adapter = new ClaudeAgentSDKAdapter(guard, {
  sessionId: 'session-01',
  principal: { user_id: 'alice', role: 'analyst' },
})

Wire into agent

const hooks = adapter.toSdkHooks()
// hooks.PreToolUse  — array of pre-tool-use hook callbacks
// hooks.PostToolUse — array of post-tool-use hook callbacks

// Pass to Claude Agent SDK configuration
const agent = createAgent({
  hooks: {
    PreToolUse: hooks.PreToolUse,
    PostToolUse: hooks.PostToolUse,
  },
})

Preconditions are fully enforced -- denied calls return permissionDecision: 'deny'. For postconditions, the adapter sets updatedMCPToolOutput in the PostToolUse response when redaction applies. Full postcondition enforcement depends on the Claude Agent SDK honoring this field.


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('contracts.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-governance runs before the tool, post-governance runs after. Denied 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('contracts.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 fully enforced via the input guardrail (tripwireTriggered: true on deny). Postcondition deny is enforced natively. 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.


OpenClaw

Plugin integration for OpenClaw agents.

Install

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

Create adapter

import { Edictum } from '@edictum/core'
import { createEdictumPlugin } from '@edictum/openclaw'

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

const plugin = createEdictumPlugin(guard, {
  sessionId: 'session-01',
  principal: { user_id: 'alice', role: 'analyst' },
})

Wire into agent

// Register with OpenClaw
api.use(plugin)

Or use the adapter class directly for more control:

import { EdictumOpenClawAdapter } from '@edictum/openclaw'

const adapter = new EdictumOpenClawAdapter(guard, {
  sessionId: 'session-01',
  principal: { user_id: 'alice', role: 'analyst' },
  onDeny: (envelope, reason) => console.log(`Denied: ${reason}`),
})

api.on('before_tool_call', (event, ctx) => adapter.handleBeforeToolCall(event, ctx))
api.on('after_tool_call', (event, ctx) => adapter.handleAfterToolCall(event, ctx))

Preconditions are fully enforced -- denied calls return { block: true, blockReason: '...' }. The after_tool_call hook is fire-and-forget in OpenClaw, so postcondition redaction is logged via audit but cannot modify the tool result in the response stream.

The OpenClaw adapter's principalResolver receives the OpenClaw ToolHookContext as a third argument, giving access to agentId, sessionKey, and sessionId for richer principal resolution.


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('contracts.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('contracts.yaml', {
  mode: 'observe',
  onDeny: (envelope, reason) => console.log(`Would deny: ${reason}`),
  onAllow: (envelope) => console.log(`Allowed: ${envelope.toolName}`),
})

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

Next Steps

Last updated on

On this page