TypeScript Adapters
Connect Edictum to Vercel AI SDK, Claude Agent SDK, LangChain.js, OpenAI Agents SDK, and OpenClaw in TypeScript.
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:
| Parameter | Type | Default | Description |
|---|---|---|---|
guard | Edictum | required | The Edictum instance holding contracts |
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() | { PreToolUse, PostToolUse } hook arrays |
| LangChain.js | @edictum/langchain | asMiddleware() | { name, wrapToolCall } middleware object |
| OpenAI Agents SDK | @edictum/openai-agents | asGuardrails() | { inputGuardrail, outputGuardrail } |
| OpenClaw | @edictum/openclaw | createEdictumPlugin() | 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-yamlCreate 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-yamlCreate 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-yamlCreate 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-yamlCreate 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-yamlCreate 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
- TypeScript SDK overview -- installation and core API
- Server SDK -- connect to Edictum Console
- Contract types -- preconditions, postconditions, session, sandbox
- Python adapter comparison -- compare with Python adapters
Last updated on