> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rehydra.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# LLM Proxy

> Proxy middleware API reference for OpenAI and Anthropic

Transparent PII anonymization and rehydration for LLM API calls. Available in Node.js and Bun only.

## Import

```typescript theme={null}
import {
  wrapLLMClient,
  createRehydraFetch,
  createRehydraProxy,
  createRehydraProxyServer,
  SSEParser,
  OpenAIProvider,
  AnthropicProvider,
  detectProvider,
} from 'rehydra/proxy';
```

***

## wrapLLMClient()

Wraps an OpenAI or Anthropic SDK client for automatic anonymization/rehydration.

### Signature

```typescript theme={null}
function wrapLLMClient<T>(
  client: T,
  config: Omit<RehydraFetchConfig, 'provider'>
): T
```

### Parameters

<ParamField path="client" type="T" required>
  An OpenAI or Anthropic SDK client instance
</ParamField>

<ParamField path="config" type="RehydraFetchConfig">
  Configuration (see [RehydraFetchConfig](#rehydrafetchconfig))
</ParamField>

### Returns

The same client type `T`, with fetch intercepted for anonymization.

### Example

```typescript theme={null}
import OpenAI from 'openai';
import { wrapLLMClient } from 'rehydra/proxy';
import { InMemoryKeyProvider, SQLitePIIStorageProvider } from 'rehydra';

const storage = new SQLitePIIStorageProvider('./pii.db');
await storage.initialize();

const client = wrapLLMClient(new OpenAI(), {
  anonymizer: { ner: { mode: 'quantized' } },
  keyProvider: new InMemoryKeyProvider(),
  piiStorageProvider: storage,
  getSessionId: () => 'session-1',
});
```

***

## createRehydraFetch()

Creates a drop-in `fetch` replacement that anonymizes requests and rehydrates responses.

### Signature

```typescript theme={null}
function createRehydraFetch(config: RehydraFetchConfig): typeof globalThis.fetch
```

### Parameters

<ParamField path="config" type="RehydraFetchConfig" required>
  Configuration (see [RehydraFetchConfig](#rehydrafetchconfig))
</ParamField>

### Returns

A `fetch`-compatible function.

### Example

```typescript theme={null}
import { createRehydraFetch } from 'rehydra/proxy';
import { InMemoryKeyProvider, SQLitePIIStorageProvider } from 'rehydra';

const storage = new SQLitePIIStorageProvider('./pii.db');
await storage.initialize();

const fetch = createRehydraFetch({
  anonymizer: { ner: { mode: 'quantized' } },
  keyProvider: new InMemoryKeyProvider(),
  piiStorageProvider: storage,
  provider: 'openai',  // or 'anthropic', or auto-detected
  getSessionId: () => 'session-1',
});

const response = await fetch('https://api.openai.com/v1/chat/completions', {
  method: 'POST',
  body: JSON.stringify({ model: 'gpt-4', messages: [...] }),
});
```

***

## createRehydraProxy()

Creates an HTTP proxy middleware using the Web Request/Response API.

### Signature

```typescript theme={null}
function createRehydraProxy(
  config: RehydraProxyConfig
): (request: Request) => Promise<Response>
```

### Parameters

<ParamField path="config" type="RehydraProxyConfig" required>
  Configuration (see [RehydraProxyConfig](#rehydraproxyconfig))
</ParamField>

### Returns

A function `(request: Request) => Promise<Response>` compatible with Bun.serve, Hono, and other frameworks using Web APIs.

### Example

```typescript theme={null}
import { createRehydraProxy } from 'rehydra/proxy';
import { InMemoryKeyProvider, SQLitePIIStorageProvider } from 'rehydra';

const storage = new SQLitePIIStorageProvider('./pii.db');
await storage.initialize();

const proxy = createRehydraProxy({
  upstream: 'https://api.openai.com',
  anonymizer: { ner: { mode: 'quantized' } },
  keyProvider: new InMemoryKeyProvider(),
  piiStorageProvider: storage,
  getSessionId: () => 'session-1',
});

Bun.serve({ port: 8080, fetch: proxy });
```

***

## createRehydraProxyServer()

Creates and starts a standalone HTTP proxy server.

### Signature

```typescript theme={null}
function createRehydraProxyServer(
  config: RehydraProxyServerConfig
): Promise<RehydraProxyServer>
```

### Parameters

<ParamField path="config" type="RehydraProxyServerConfig" required>
  Extends [RehydraProxyConfig](#rehydraproxyconfig) with:

  <Expandable title="Additional properties">
    <ParamField path="port" type="number" default="8080">
      Port to listen on
    </ParamField>

    <ParamField path="host" type="string" default="'127.0.0.1'">
      Host to bind to
    </ParamField>
  </Expandable>
</ParamField>

### Returns

```typescript theme={null}
interface RehydraProxyServer {
  server: http.Server;
  port: number;
  host: string;
  close(): Promise<void>;
}
```

### Example

```typescript theme={null}
import { createRehydraProxyServer } from 'rehydra/proxy';
import { InMemoryKeyProvider, SQLitePIIStorageProvider } from 'rehydra';

const storage = new SQLitePIIStorageProvider('./pii.db');
await storage.initialize();

const server = await createRehydraProxyServer({
  upstream: 'https://api.openai.com',
  port: 8080,
  anonymizer: { ner: { mode: 'quantized' } },
  keyProvider: new InMemoryKeyProvider(),
  piiStorageProvider: storage,
  getSessionId: () => 'session-1',
});

// Point clients at http://localhost:8080/v1
await server.close();
```

***

## Configuration Types

### RehydraFetchConfig

```typescript theme={null}
interface RehydraFetchConfig {
  // Encryption key provider (required)
  keyProvider: KeyProvider;

  // Storage for session persistence (required)
  piiStorageProvider: PIIStorageProvider;

  // Anonymizer configuration (includes tagFormat)
  anonymizer?: AnonymizerConfig;

  // Policy override
  policy?: Partial<AnonymizationPolicy>;

  // Provider hint (auto-detected if omitted)
  provider?: 'openai' | 'anthropic' | 'auto';

  // Derive session ID from request
  getSessionId?: (request: Request) => string | Promise<string>;

  // Handle streaming responses (default: true)
  handleStreaming?: boolean;

  // Locale hint
  locale?: string;

  // Tool execution callback (non-streaming only)
  // Arguments are rehydrated before calling; return value is anonymized
  onToolCall?: (
    name: string,
    args: Record<string, unknown>,
    toolCallId: string,
  ) => unknown | Promise<unknown>;

  // Maximum tool execution rounds (default: 10)
  maxToolRounds?: number;

  // PII system instruction: string to override, false to disable, undefined for default
  systemInstruction?: string | false;
}
```

### RehydraProxyConfig

Extends `RehydraFetchConfig` with:

```typescript theme={null}
interface RehydraProxyConfig extends RehydraFetchConfig {
  // Upstream LLM API URL (e.g., 'https://api.openai.com')
  upstream: string;

  // Headers to forward to upstream
  // Default: ['authorization', 'content-type', 'x-api-key',
  //   'anthropic-version', 'openai-organization', 'openai-project']
  forwardHeaders?: string[];

  // Strip this prefix from the request path before forwarding
  stripPrefix?: string;

  // API key to inject into upstream requests
  // Adds Authorization header (OpenAI) or x-api-key header (Anthropic)
  apiKey?: string;
}
```

***

## Providers

### detectProvider()

Auto-detect the LLM provider from request URL and headers.

```typescript theme={null}
function detectProvider(
  url: string,
  headers: Headers,
  hint?: string
): LLMContentProvider
```

### LLMContentProvider Interface

```typescript theme={null}
interface LLMContentProvider {
  readonly name: string;

  // Request detection
  matchesRequest(url: string, headers: Headers): boolean;
  isStreamingRequest(body: unknown): boolean;

  // Request handling
  extractRequestText(body: unknown): string[];
  rebuildRequestBody(body: unknown, anonymizedTexts: string[]): unknown;

  // Non-streaming response handling
  extractResponseText(body: unknown): string[];
  rebuildResponseBody(body: unknown, rehydratedTexts: string[]): unknown;

  // Streaming content handling
  extractSSEDelta(data: unknown): string | null;
  rebuildSSEDelta(data: unknown, rehydratedText: string): unknown;

  // Tool/function call handling (non-streaming)
  extractResponseToolCalls?(body: unknown): string[];
  rebuildResponseToolCalls?(body: unknown, rehydratedArgs: string[]): unknown;

  // Tool/function call handling (streaming)
  extractSSEToolCallDeltas?(data: unknown): ToolCallDelta[] | null;
  rebuildSSEToolCallDeltas?(data: unknown, rehydratedArgs: Map<number, string>): unknown;
  extractSSEToolCallStop?(data: unknown): number | null;
}

interface ToolCallDelta {
  index: number;
  arguments: string;
}

interface SSEEvent {
  event: string;   // defaults to 'message'
  data: string;
}
```

### Built-in Providers

| Provider            | Matches                                                            |
| ------------------- | ------------------------------------------------------------------ |
| `OpenAIProvider`    | `api.openai.com` URL or `Bearer sk-` auth header                   |
| `AnthropicProvider` | `api.anthropic.com` URL or `x-api-key`/`anthropic-version` headers |

***

## SSEParser

Parser for Server-Sent Events streams.

```typescript theme={null}
const parser = new SSEParser();

// Parse SSE text
const events = parser.parse(chunk);
// [{ event: 'message', data: '...' }, ...]

// Check for stream end
import { isSSEDone } from 'rehydra/proxy';
if (isSSEDone(event.data)) { /* stream complete */ }

// Serialize back to SSE format
import { serializeSSEEvent } from 'rehydra/proxy';
const text = serializeSSEEvent({ event: 'message', data: '...' });
```

## Related

* [LLM Proxy Guide](/guides/llm-proxy) - Usage guide and examples
* [Streaming Guide](/guides/streaming) - Stream-level anonymization
