TypeScript Adapters
Connect Edictum to TypeScript agent frameworks and check the current Claude Agent SDK compatibility limit.
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:
| Parameter | Type | Default | Description |
|---|---|---|---|
guard | Edictum | required | The Edictum instance holding rulesets |
sessionId | string | auto UUID | Groups related tool calls for session limit tracking |
principal | Principal | null | Static identity context for audit events |
principalResolver | Function | null | Dynamic principal resolution per tool call |
All adapters expose setPrincipal(principal) to update identity mid-session.
Quick Comparison
| Framework | Package | Integration Method | Returns |
|---|---|---|---|
| Vercel AI SDK | @edictum/vercel-ai | asCallbacks() | experimental_onToolCallStart / experimental_onToolCallFinish |
| Claude Agent SDK | @edictum/claude-sdk | toSdkHooks() | Edictum-specific callback arrays; not directly SDK-compatible |
| LangChain.js | @edictum/langchain | asMiddleware() | { name, wrapToolCall } middleware object |
| OpenAI Agents SDK | @edictum/openai-agents | asGuardrails() | { inputGuardrail, outputGuardrail } |
Vercel AI SDK
Callbacks for generateText and streamText.
Install
pnpm add @edictum/core @edictum/vercel-ai js-yamlCreate 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-yamlCreate 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-yamlCreate 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-yamlCreate 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
- TypeScript SDK overview -- installation and core API
- Server SDK -- connect to the reference stack
- Rule types -- preconditions, postconditions, session, sandbox
- Python adapter comparison -- compare with Python adapters
Last updated on