Adapters

Agent protocol adapters for parsing and intercepting agent frames.


@securitylayerai/adapters defines the AgentAdapter interface and provides implementations for different agent protocols. The proxy uses adapters to parse agent communication frames into typed security events.

AgentAdapter interface

Every adapter implements this contract:

interface AgentAdapter {
  // Parse client→agent frames into typed events
  parseInboundFrame(raw: Buffer): InboundEvent;

  // Parse agent→world frames into typed actions
  parseOutboundFrame(raw: Buffer): OutboundAction[];

  // Create a deny response to inject back to the agent
  injectDenyResponse(
    action: OutboundAction,
    reason: string,
    toolCallId?: string,
  ): Buffer;

  // Get session metadata (channel, trust level, active skill)
  getSessionMetadata(sessionId: string): SessionInfo | null;

  // Extract tool calls from an agent response frame
  extractToolCalls(frame: Buffer): ToolCall[];

  // Wrap an approved action for re-injection to the agent
  wrapReplayAction(action: OutboundAction): Buffer;
}

Included adapters

OpenClaw adapter

For OpenClaw's WebSocket protocol with typed frame types (req:send, req:agent, res:agent, event:chat, etc.).

import { createOpenClawAdapter } from "@securitylayerai/adapters";

const adapter = createOpenClawAdapter({
  gatewayUrl: "ws://localhost:18789",
});

Handles OpenClaw-specific frame types:

Frame typeDirectionDescription
req:sendClient → AgentSend message request
req:agentClient → AgentAgent instruction
res:agentAgent → ClientAgent response with tool calls
event:chatAgent → ClientChat message event
event:skills.installAgent → ClientSkill installation event

Generic adapter

For simple JSON-based tool-call agents that don't use a specific protocol.

import { createGenericAdapter } from "@securitylayerai/adapters";

const adapter = createGenericAdapter({
  parseToolCall: (data) => ({
    id: data.id,
    name: data.tool,
    input: data.params,
  }),
});

Types

InboundEvent

Events flowing from client to agent:

interface InboundEvent {
  type: InboundEventType;
  sessionId: string;
  sender?: string;
  channel?: string;
  data: Record<string, unknown>;
}

type InboundEventType =
  | "message"
  | "agent_instruction"
  | "config_change"
  | "cron_create"
  | "skill_install"
  | "chat";

OutboundAction

Actions the agent wants to take:

interface OutboundAction {
  tool: string;
  params: Record<string, unknown>;
  requiredCapability: string;
  toolCallId?: string;
}

FrameParseError

Thrown when a frame can't be parsed. The proxy catches this to handle malformed frames gracefully without crashing.

class FrameParseError extends Error {
  constructor(message: string);
}

Creating a custom adapter

To support a new agent protocol, implement the AgentAdapter interface:

import type { AgentAdapter } from "@securitylayerai/adapters";

function createMyAdapter(config: MyConfig): AgentAdapter {
  return {
    parseInboundFrame(raw) {
      const data = JSON.parse(raw.toString());
      return {
        type: "message",
        sessionId: data.session,
        data: data.payload,
      };
    },

    parseOutboundFrame(raw) {
      const data = JSON.parse(raw.toString());
      return data.toolCalls.map((tc: any) => ({
        tool: tc.name,
        params: tc.arguments,
        requiredCapability: mapToolToCapability(tc.name),
        toolCallId: tc.id,
      }));
    },

    injectDenyResponse(action, reason, toolCallId) {
      return Buffer.from(JSON.stringify({
        type: "error",
        toolCallId,
        error: reason,
      }));
    },

    // ... remaining methods
  };
}

Package structure

packages/adapters/
├── src/
│   ├── index.ts          # Barrel exports
│   ├── interface.ts      # AgentAdapter interface
│   ├── types.ts          # InboundEvent, OutboundAction, etc.
│   ├── openclaw.ts       # OpenClaw adapter
│   └── generic.ts        # Generic JSON adapter
└── test/
    ├── openclaw.test.ts
    └── generic.test.ts

See also

  • Proxy — Uses adapters to intercept agent frames
  • Security pipeline — What happens after frames are parsed

On this page