npm package: @reallyartificial/approval-protocol-langchain
Description: LangChain integration for Approval Protocol — wrap any tool with human-in-the-loop approval.
Author: josharsh
Latest version: 0.1.1

# @reallyartificial/approval-protocol-langchain

**LangChain integration for Approval Protocol — wrap any tool with human-in-the-loop approval.**

One function call. Full lifecycle. Drop-in replacement.

---

## Install

```bash
npm install @reallyartificial/approval-protocol-langchain @reallyartificial/approval-protocol-core
```

## Quickstart

```typescript
import { DynamicStructuredTool } from "@langchain/core/tools";
import { ApprovalClient } from "@reallyartificial/approval-protocol-core";
import { approvalTool } from "@reallyartificial/approval-protocol-langchain";
import { z } from "zod";

// Your existing LangChain tool
const sendEmail = new DynamicStructuredTool({
  name: "send_email",
  description: "Send an email to a customer",
  schema: z.object({
    to: z.string(),
    body: z.string(),
  }),
  func: async ({ to, body }) => {
    // actual send logic
    return `Email sent to ${to}`;
  },
});

// Wrap it with Approval Protocol
const client = new ApprovalClient("http://localhost:4000");

const safeSendEmail = approvalTool(sendEmail, {
  client,
  agent: "support-agent",
  risk: { reversible: false, blast_radius: "external" },
  undo: { type: "manual", description: "Contact recipient to disregard" },
});

// Use safeSendEmail anywhere you'd use the original tool.
// It's a drop-in replacement — same name, description, and schema.
```

## What It Does

`approvalTool` wraps a LangChain `StructuredTool` with the full Approval Protocol lifecycle:

```
invoke(input)
  -> ap/request        (ask for approval)
  -> waitForDecision   (poll until human decides)
  -> tool.invoke()     (run the original tool)
  -> ap/confirm        (report result + undo info)
  -> return result
```

The wrapped tool is a standard `DynamicStructuredTool` — it works in any LangChain agent (ReAct, OpenAI Functions, custom chains, etc.) with no changes to your agent code.

## API

### `approvalTool(tool, config)`

Returns a new `DynamicStructuredTool` with the same name, description, and schema.

**Parameters:**

| Param | Type | Description |
|-------|------|-------------|
| `tool` | `StructuredToolInterface` | The LangChain tool to wrap |
| `config` | `ApprovalToolConfig` | Configuration (see below) |

**`ApprovalToolConfig`:**

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `client` | `ApprovalClient` | Yes | — | AP client pointed at your server |
| `agent` | `string` | Yes | — | Agent identifier for AP requests |
| `risk` | `ApRiskMetadata` | No | — | Risk metadata attached to every request |
| `undo` | `ApUndoMetadata` | No | — | Undo instructions sent on confirm |
| `timeout` | `number` | No | `300` | Seconds to wait for human decision |
| `pollInterval` | `number` | No | `1000` | Milliseconds between status polls |

### Error Classes

**`ApprovalDeniedError`** — Thrown when the human denies the request (or it's auto-denied by policy).

```typescript
import { ApprovalDeniedError } from "@reallyartificial/approval-protocol-langchain";

try {
  await safeSendEmail.invoke({ to: "user@example.com", body: "Hello" });
} catch (err) {
  if (err instanceof ApprovalDeniedError) {
    console.log(`Denied: ${err.reason}`);  // "Too risky"
    console.log(err.requestId);             // "req_abc123"
  }
}
```

**`ApprovalDryRunResult`** — Thrown when the policy specifies `dry_run` execution mode. The tool is not executed.

```typescript
import { ApprovalDryRunResult } from "@reallyartificial/approval-protocol-langchain";

try {
  await wrappedTool.invoke(input);
} catch (err) {
  if (err instanceof ApprovalDryRunResult) {
    console.log(`Dry run for ${err.toolName}, request ${err.requestId}`);
  }
}
```

## Behavior by Status

| AP Response | What Happens |
|-------------|-------------|
| `auto_approved` | Tool executes immediately, result confirmed |
| `pending` → `approved` | Waits for human, then executes and confirms |
| `pending` → `denied` | Throws `ApprovalDeniedError` |
| `pending` → `expired` | Throws `Error` (timeout) |
| `auto_denied` | Throws `ApprovalDeniedError` |
| `dry_run` mode | Confirms without executing, throws `ApprovalDryRunResult` |
| Tool throws | Confirms failure to AP, then re-throws original error |

## Peer Dependencies

| Package | Version |
|---------|---------|
| `@langchain/core` | `>= 0.3.0` |
| `@reallyartificial/approval-protocol-core` | `>= 0.1.0` |

## Related

- **[@reallyartificial/approval-protocol-core](https://www.npmjs.com/package/@reallyartificial/approval-protocol-core)** — The core protocol: server, client, policies, channels.
- **[PROTOCOL.md](https://github.com/ReallyArtificial/ApprovalProtocol/blob/main/PROTOCOL.md)** — Full protocol specification.

## License

MIT