npm package: @pillar-ai/server
Description: Pillar backend SDK — register server-side tools and handle tool calls
Latest version: 0.2.4

# @pillar-ai/server

Backend SDK for [Pillar](https://trypillar.com) — register server-side tools and handle tool calls from Pillar Cloud.

## Install

```bash
npm install @pillar-ai/server
```

Optional peer dependencies for Zod schema support:

```bash
npm install zod zod-to-json-schema
```

## Quick Start

```typescript
import { Pillar, defineTool } from '@pillar-ai/server';
import { z } from 'zod';

const pillar = new Pillar({
  secret: process.env.PILLAR_SECRET!,
  endpointUrl: 'https://api.myapp.com/pillar',
});

const lookupCustomer = defineTool({
  name: 'lookup_customer',
  description: 'Look up a customer by email address',
  input: z.object({
    email: z.string().email().describe('Customer email address'),
  }),
  execute: async ({ email }, ctx) => {
    const customer = await db.findByEmail(email);
    return { name: customer.name, plan: customer.plan };
  },
});

await pillar.registerTools([lookupCustomer]);
```

## Framework Integration

### Express

```typescript
import express from 'express';
const app = express();
app.use(express.json());
app.post('/pillar', pillar.expressHandler());
app.listen(3000);
```

### Hono

```typescript
import { Hono } from 'hono';
const app = new Hono();
app.post('/pillar', pillar.honoHandler());
```

### Next.js (App Router)

```typescript
// app/api/pillar/route.ts
export const POST = pillar.nextHandler();
```

### Fastify

```typescript
import Fastify from 'fastify';
const fastify = Fastify();
fastify.post('/pillar', pillar.fastifyHandler());
fastify.listen({ port: 3000 });
```

### Standalone

```typescript
pillar.serve({ port: 8787 });
```

## Plain JSON Schema (No Zod)

```typescript
const lookupCustomer = defineTool({
  name: 'lookup_customer',
  description: 'Look up a customer by email',
  inputSchema: {
    type: 'object',
    properties: {
      email: { type: 'string', description: 'Customer email' },
    },
    required: ['email'],
  },
  execute: async ({ email }: { email: string }, ctx) => {
    return { name: 'Jane' };
  },
});
```

## API Reference

### `Pillar`

```typescript
new Pillar({
  secret: string,           // Required. Your Pillar secret (used for auth and signature verification).
  endpointUrl?: string,     // URL where Pillar Cloud sends tool calls.
  autoRegister?: boolean,   // Auto-register tools on first call (default: true).
  baseUrl?: string,         // Pillar API base URL (default: https://api.trypillar.com).
  logger?: Logger,          // Custom logger.
})
```

### `defineTool`

```typescript
defineTool({
  name: string,                    // Unique tool name.
  description: string,             // Human-readable description.
  input?: ZodSchema,               // Zod schema (optional).
  inputSchema?: JsonSchema,        // Plain JSON Schema (optional).
  outputSchema?: JsonSchema,       // Output schema (optional).
  guidance?: string,               // Agent instructions.
  timeoutMs?: number,              // Timeout in ms (default: 30000).
  channelCompatibility?: string[], // Channels (default: all).
  execute: (input, ctx) => any,    // Handler function.
})
```

### `ToolContext`

```typescript
interface ToolContext {
  caller: CallerInfo;
  conversationId: string;
  callId: string;
  productId?: string;
  isIdentified: boolean;
  confirmed: boolean; // true when re-executing after user confirmation
}

interface CallerInfo {
  channel: 'web' | 'slack' | 'discord' | 'email' | 'api';
  channelUserId?: string;
  externalUserId?: string;
  email?: string;
  displayName?: string;
}
```

## Confirmation responses

When a tool needs user confirmation before performing an action, return a `ConfirmationResponse` from `execute`. Pillar renders a channel-appropriate confirmation UI (Block Kit buttons in Slack, structured JSON in the copilot) and re-calls `execute` with `ctx.confirmed = true` after the user confirms.

```typescript
import { defineTool, type ConfirmationResponse } from '@pillar-ai/server';

const createPlan = defineTool({
  name: 'create_plan',
  description: 'Create a new plan',
  input: z.object({ name: z.string(), price: z.number() }),
  execute: async (input, ctx): Promise<ConfirmationResponse | { created: boolean }> => {
    if (!ctx.confirmed) {
      return {
        confirmation_required: true,
        title: 'Create plan',
        message: `Create "${input.name}" at $${input.price}/mo?`,
        details: {
          'Plan name': input.name,
          'Price': `$${input.price}/mo`,
        },
        confirm_payload: input,
      };
    }

    await db.createPlan(input);
    return { created: true };
  },
});
```

### `ConfirmationResponse`

| Field | Required | Description |
|---|---|---|
| `confirmation_required` | yes | Must be `true`. |
| `title` | no | Short headline. Defaults to the tool name. |
| `message` | no | One-line description shown before the buttons. |
| `details` | no | Key-value pairs rendered as a summary card. |
| `confirm_payload` | no | Opaque data passed back to `execute` on confirm. Defaults to the original arguments. |