npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@runlayer/hooks-sdk

v0.2.0

Published

Runlayer Hooks TypeScript SDK

Readme

Runlayer Hooks TypeScript SDK

First-party Hooks TypeScript SDK for wiring agent runtimes into Runlayer governance, telemetry, and hook enforcement.

Phase 1 supports Claude Agent SDK hooks plus structural adapters for common TypeScript agent tool APIs:

  • lifecycle telemetry for session, prompt, and stop events
  • pre-tool enforcement with argument rewriting
  • post-tool output scanning and blocking
  • failed tool telemetry
  • direct MCP source enforcement
  • preflight session emission for ingestion checks
  • transcript-bearing Stop emission for completed assistant messages
  • Vercel AI SDK tool wrappers
  • OpenAI Agents SDK function tool wrappers
  • Google ADK FunctionTool option wrappers

Copy Prompt

Read the Runlayer SDK docs in https://docs.runlayer.com/runlayer-hooks-typescript-sdk and update my agent code to integrate with Runlayer's hook enforcement and telemetry pipeline

Install

pnpm add @runlayer/hooks-sdk
npm install @runlayer/hooks-sdk

Install the framework package you use separately, such as @anthropic-ai/claude-agent-sdk, ai, @openai/agents, or @google/adk.

Latest verified TypeScript framework versions as of 2026-06-17:

| Surface | Package | Version | | ------------------------------- | -------------------------------- | --------- | | Claude Agent SDK hooks | @anthropic-ai/claude-agent-sdk | 0.3.172 | | OpenAI Agents SDK tools | @openai/agents | 0.11.6 | | Vercel AI SDK tools | ai | 6.0.200 | | Vercel OpenAI provider examples | @ai-sdk/openai | 3.0.69 | | Google ADK tools | @google/adk | 1.2.0 | | LangChain MCP examples | langchain | 1.4.4 | | LangChain OpenAI examples | @langchain/openai | 1.4.7 | | LangChain core types | @langchain/core | 1.1.48 |

Configure

export RUNLAYER_BASE_URL="https://your-runlayer-instance.com"
export RUNLAYER_API_KEY="rl_..."

For cloud installations that authenticate with a shared organization API key (rl_org_...), also name the Runlayer user the deployment runs as so sessions and enforcement are attributed to it:

export RUNLAYER_API_KEY="rl_org_..."
export RUNLAYER_USER_EMAIL="[email protected]"

The identity can be a service user provisioned just for the deployment; events sent before that user exists in the workspace are buffered and replayed once it does. The organization key must carry the AI Watch scan role. userEmail is also available as a RunlayerClient constructor option; it applies only to API key auth (agent accounts express the acting user via subjectToken).

For agent account authentication, set client credentials instead:

export RUNLAYER_BASE_URL="https://your-runlayer-instance.com"
export RUNLAYER_AGENT_CLIENT_ID="client_..."
export RUNLAYER_AGENT_CLIENT_SECRET="..."

The SDK exchanges these credentials for a Runlayer bearer token with client_credentials, then caches and refreshes that token internally. Do not configure or pass a pre-minted bearer token.

For OBO agent tokens, also set:

export RUNLAYER_AGENT_SUBJECT_TOKEN="[email protected]"
export RUNLAYER_AGENT_SUBJECT_TOKEN_TYPE="urn:runlayer:token-type:user-email"

RunlayerClient.fromEnv() defaults to client: "typescript-sdk" so phase 1 events are attributed to the first-party Hooks TypeScript SDK in Runlayer. Enable the TypeScript SDK session monitoring client in Runlayer workspace settings before testing: Settings → General → TypeScript SDK.

Optional runtime controls:

| Variable | Purpose | | ---------------------------------------- | ---------------------------------------------------------------------------------------------------- | | RUNLAYER_HOOK_TIMEOUT_MS | Hook request timeout. Defaults to 10000. | | RUNLAYER_HOOK_MAX_TOOL_OUTPUT_BYTES | Maximum serialized tool output sent to Runlayer. Defaults to 65536. | | RUNLAYER_HOOK_ENFORCEMENT_FAILURE_MODE | Defaults to closed; set to open only if tool calls should continue when Runlayer is unreachable. | | RUNLAYER_ALLOW_INSECURE_TRANSPORT=1 | Allow non-HTTPS RUNLAYER_BASE_URL for local development. |

Enforcement calls fail closed by default. Lifecycle telemetry is best-effort unless a helper documents strict behavior. Tool output is capped before upload, and failed tool details include bounded stdout/stderr/output fields with truncation metadata.

The SDK skips pre-tool and post-tool enforcement for Runlayer's own MCP server names (runlayer, runlayer-plugin, and onelayer) and for MCP calls whose toolUrl points at a Runlayer proxy URL. Third-party MCP tools stay enforced by default. Use shouldEnforceTool(...) or toolEnforcement when building a custom TypeScript adapter. Additional ignoredMcpServerNames are added to the default Runlayer self-MCP skip list; set skipRunlayerSelfMcp: false only if you need to enforce those self-MCP names. If you call shouldEnforceTool(...) directly with absolute self-hosted Runlayer MCP proxy URLs, pass runlayerBaseUrl; RunlayerClient does this automatically.

Claude Agent SDK Hooks

import { query } from "@anthropic-ai/claude-agent-sdk";
import {
  claudeAgentSdkAssistantMessageToTranscriptLine,
  createClaudeAgentSdkHooks,
  emitClaudeAgentSdkTranscriptStop,
  RunlayerClient,
} from "@runlayer/hooks-sdk/claude-agent-sdk";

const runlayer = RunlayerClient.fromEnv({
  clientVersion: "my-agent/1.0.0",
});

const transcriptLines: string[] = [];
let sessionId: string | undefined;

for await (const message of query({
  prompt: "Inspect this workspace",
  options: {
    hooks: createClaudeAgentSdkHooks(runlayer, { includeStop: false }),
    thinking: { type: "adaptive" },
  },
})) {
  if (message.type === "system" && message.subtype === "init") {
    sessionId = message.session_id;
  }

  if (message.type === "assistant") {
    transcriptLines.push(claudeAgentSdkAssistantMessageToTranscriptLine(message.message));
  }
}

if (sessionId) {
  await emitClaudeAgentSdkTranscriptStop(runlayer, {
    model: "claude-agent-sdk",
    sessionId,
    transcriptLines,
  });
}

Use includeStop: false when you emit a transcript-bearing Stop manually. That lets Runlayer extract assistant thinking/reasoning blocks from the transcript instead of receiving a bare lifecycle stop.

Preflight

Save this as runlayer-preflight.mjs:

import { RunlayerClient, sendRunlayerPreflight } from "@runlayer/hooks-sdk";

const result = await sendRunlayerPreflight(RunlayerClient.fromEnv(), {
  prompt: "Runlayer ingestion preflight",
});

console.log(result.sessionUrl);

Run preflight before the first real model call:

node runlayer-preflight.mjs

The result includes sessionUrl, runlayerSessionId, sessionId, and externalSessionId. Use sessionUrl or runlayerSessionId to find the session in Runlayer. If the workspace has not enabled SDK session monitoring, preflight throws RunlayerPreflightError with SDK session monitoring is not enabled for this workspace.

Troubleshooting: sessions not appearing

Lifecycle hooks send events best-effort and do not throw on their own. If the server accepts an event but does not record it, it responds with status: "ignored" and a reason. The client logs a one-time console.warn for the actionable reasons so a misconfiguration is not silent:

  • client_not_enabled — the SDK session-monitoring client is off for this workspace. Enable the client (and any required SDK toggle) in Runlayer settings. Note this is a separate switch from tool enforcement, so both must be on to get full session + tool telemetry.
  • actor_unresolved — the request authenticated but Runlayer could not map it to a user/agent. With an organization API key, set RUNLAYER_USER_EMAIL (or the userEmail client option) to the Runlayer user the deployment runs as; events are buffered until that user exists in the workspace. Otherwise use a personal API key (RUNLAYER_API_KEY) or agent-account credentials (RUNLAYER_AGENT_CLIENT_ID / RUNLAYER_AGENT_CLIENT_SECRET).

Set RUNLAYER_HOOK_DEBUG=1 to also log transient ignore reasons and network failures. Run sendRunlayerPreflight() for an explicit pass/fail check.

Tool Wrapping

Use runTool when you are invoking tools yourself rather than through a Claude Agent SDK hook.

const output = await runlayer.runTool({
  execute: async (toolInput) => {
    return runLocalTool(toolInput);
  },
  sessionId: "session-id",
  toolInput: { command: "cat README.md" },
  toolName: "Bash",
  toolType: "shell",
});

Pass toolUrl when wrapping MCP tools manually. Runlayer proxy URLs such as /api/v1/proxy/<id>/mcp, /api/v1/proxy/plugins/<id>/mcp, /api/v1/proxy/skills/<id>/mcp, and /api/v1/proxy/agent-account/<id>/mcp are skipped automatically because the proxy already runs MCP policy and scanner enforcement.

Vercel AI SDK Tools

Wrap a Vercel AI SDK tool set before passing it to generateText, streamText, or a ToolLoopAgent.

import { streamText, tool } from "ai";
import { withRunlayerVercelAiTools, RunlayerClient } from "@runlayer/hooks-sdk/vercel-ai-sdk";

const runlayer = RunlayerClient.fromEnv({
  clientVersion: "my-agent/1.0.0",
});

const tools = withRunlayerVercelAiTools(
  {
    getWeather: tool({
      description: "Get weather for a city",
      inputSchema: {
        type: "object",
        properties: { city: { type: "string" } },
        required: ["city"],
      },
      execute: async ({ city }) => ({ forecast: `sunny in ${city}` }),
    }),
  },
  {
    client: runlayer,
    sessionId: "session-id",
  },
);

await streamText({
  model,
  prompt: "Check the weather in Paris",
  tools,
});

The adapter preserves the Vercel execution options and forwards toolCallId to Runlayer as toolUseId. It supports concrete or promise-like tool outputs; AsyncIterable streaming tool outputs are not wrapped by this adapter.

OpenAI Agents SDK Tools

Wrap function tool options before passing them to OpenAI Agents SDK's tool(...) helper.

import { tool } from "@openai/agents";
import {
  withRunlayerOpenAIAgentsTool,
  RunlayerClient,
} from "@runlayer/hooks-sdk/openai-agents-sdk";

const runlayer = RunlayerClient.fromEnv({
  clientVersion: "my-agent/1.0.0",
});

const lookupTicket = tool(
  withRunlayerOpenAIAgentsTool(
    {
      name: "lookup_ticket",
      description: "Look up a support ticket",
      parameters: {
        type: "object",
        properties: { ticketId: { type: "string" } },
        required: ["ticketId"],
      },
      execute: async ({ ticketId }) => `ticket:${ticketId}`,
    },
    {
      client: runlayer,
      sessionId: "session-id",
    },
  ),
);

The adapter preserves context and details arguments and reads common toolCall IDs from details.

Google ADK Tools

Wrap Google ADK FunctionTool options before constructing the tool.

import { FunctionTool } from "@google/adk";
import { withRunlayerGoogleAdkTool, RunlayerClient } from "@runlayer/hooks-sdk/google-adk";

const runlayer = RunlayerClient.fromEnv({
  clientVersion: "my-agent/1.0.0",
});

const searchTickets = new FunctionTool(
  withRunlayerGoogleAdkTool(
    {
      name: "search_tickets",
      description: "Search support tickets",
      parameters: {
        type: "object",
        properties: { query: { type: "string" } },
        required: ["query"],
      },
      execute: async ({ query }) => ({ query }),
    },
    {
      client: runlayer,
      sessionId: "session-id",
    },
  ),
);

Direct MCP Source Enforcement

When the deployment exposes direct MCP source validation, configure the endpoint path and call enforceMcpSource before executing a direct MCP tool.

const runlayer = RunlayerClient.fromEnv({
  directMcpSourceEnforcementPath: "/api/v1/hooks/direct-mcp-source",
});

await runlayer.enforceMcpSource({
  generationId: "generation-id",
  sessionId: "session-id",
  toolName: "mcp__github__list_repos",
  url: "https://tenant.runlayer.com/api/v1/proxy/server-id/mcp",
});