npm package: @heycanary/sdk
Description: Lightweight observability SDK for AI agents. Trace, monitor, and debug your agents in production.
Author: Athena Made
Latest version: 0.1.1

# @heycanary/sdk

Lightweight AI agent tracing and observability SDK. Instrument your AI agent calls with zero dependencies and minimal overhead.

## Features

- 🪶 **Lightweight** - < 5KB minified, zero dependencies
- ⚡ **Non-blocking** - Batched, async trace submission never slows down your agents
- 🔒 **Type-safe** - Full TypeScript support with generics
- 🛡️ **Resilient** - Graceful fallback with in-memory queuing when API is down
- 🎯 **Simple** - Dead simple API inspired by Sentry and PostHog

## Installation

```bash
npm install @heycanary/sdk
```

## Quick Start

```typescript
import { init, wrap } from '@heycanary/sdk';

// Initialize once at app startup
init({ 
  apiKey: 'cnry_...',
  projectId: 'my-app' 
});

// Wrap any async function to trace it
const agent = wrap(async (prompt: string) => {
  return await openai.chat.completions.create({
    model: 'gpt-4',
    messages: [{ role: 'user', content: prompt }]
  });
}, { name: 'my-agent' });

// Use it normally - traces are captured automatically
const result = await agent('Hello world');
```

## API Reference

### `init(config)`

Initialize the SDK with your API key and project ID. Call this once at application startup.

```typescript
init({
  apiKey: 'cnry_...',           // Required: Your Canary API key
  projectId: 'my-app',           // Required: Project identifier
  endpoint?: string,             // Optional: Custom API endpoint
  flushInterval?: number,        // Optional: Batch flush interval (default: 5000ms)
  maxQueueSize?: number          // Optional: Max traces in memory (default: 1000)
});
```

### `wrap(fn, options)`

Wrap an async function to automatically trace its execution. Captures:
- Start time, end time, duration
- Input arguments
- Output/result
- Errors and stack traces
- Token usage (if available in response)
- Model name (if available in response)

```typescript
const wrapped = wrap(
  async (input) => {
    // Your agent logic
    return result;
  },
  {
    name: 'agent-name',           // Required: Trace name
    metadata?: {                  // Optional: Custom metadata
      version: '1.0',
      environment: 'production'
    }
  }
);
```

The wrapped function has the same type signature as the original, preserving full type safety.

### `trace(name, metadata?)`

Manually create a trace span for custom events.

```typescript
trace('user-feedback', {
  userId: '123',
  rating: 5,
  comment: 'Great response!'
});
```

### `shutdown()`

Flush all pending traces and shutdown the SDK. Useful for serverless environments or before process exit.

```typescript
await shutdown();
```

## Usage Examples

### OpenAI Integration

```typescript
import OpenAI from 'openai';
import { init, wrap } from '@heycanary/sdk';

init({ apiKey: process.env.CANARY_API_KEY, projectId: 'my-app' });

const openai = new OpenAI();

const chat = wrap(
  async (messages) => {
    return await openai.chat.completions.create({
      model: 'gpt-4',
      messages
    });
  },
  { name: 'openai-chat' }
);

// Automatically traces token usage, duration, etc.
const response = await chat([
  { role: 'user', content: 'Hello!' }
]);
```

### Anthropic Integration

```typescript
import Anthropic from '@anthropic-ai/sdk';
import { init, wrap } from '@heycanary/sdk';

init({ apiKey: process.env.CANARY_API_KEY, projectId: 'my-app' });

const anthropic = new Anthropic();

const claude = wrap(
  async (prompt) => {
    return await anthropic.messages.create({
      model: 'claude-3-opus-20240229',
      max_tokens: 1024,
      messages: [{ role: 'user', content: prompt }]
    });
  },
  { name: 'claude-chat', metadata: { model: 'opus' } }
);

const response = await claude('Explain quantum computing');
```

### Custom Agent with Manual Tracing

```typescript
import { init, wrap, trace } from '@heycanary/sdk';

init({ apiKey: process.env.CANARY_API_KEY, projectId: 'my-app' });

async function customAgent(query: string) {
  // Manual trace for preprocessing
  trace('preprocess', { queryLength: query.length });
  
  const processed = query.toLowerCase();
  
  // Wrapped LLM call
  const llmCall = wrap(
    async (input) => {
      return await someLLM.generate(input);
    },
    { name: 'llm-generation' }
  );
  
  const result = await llmCall(processed);
  
  // Manual trace for postprocessing
  trace('postprocess', { resultLength: result.length });
  
  return result;
}
```

### Error Handling

```typescript
const agent = wrap(
  async (prompt) => {
    const response = await openai.chat.completions.create({
      model: 'gpt-4',
      messages: [{ role: 'user', content: prompt }]
    });
    
    if (!response.choices[0]) {
      throw new Error('No response from model');
    }
    
    return response;
  },
  { name: 'safe-agent' }
);

try {
  const result = await agent('Hello');
} catch (error) {
  // Error is captured in trace and re-thrown
  console.error('Agent failed:', error);
}
```

### Serverless Usage

```typescript
import { init, wrap, shutdown } from '@heycanary/sdk';

// Lambda handler example
export const handler = async (event) => {
  init({ apiKey: process.env.CANARY_API_KEY, projectId: 'lambda-app' });
  
  const agent = wrap(
    async (input) => {
      // Your agent logic
      return result;
    },
    { name: 'lambda-agent' }
  );
  
  const result = await agent(event.body);
  
  // Ensure traces are flushed before function exits
  await shutdown();
  
  return { statusCode: 200, body: JSON.stringify(result) };
};
```

## What Gets Traced?

Each trace captures:

- **Timing**: Start time, end time, duration in milliseconds
- **Input/Output**: Function arguments and return values
- **Errors**: Error messages and stack traces
- **Metadata**: Custom metadata you provide
- **Token Usage**: Automatically extracted from OpenAI/Anthropic responses
- **Model Name**: Automatically extracted when available
- **Project ID**: Links trace to your project

## Performance

- **Non-blocking**: Traces are batched and sent asynchronously
- **Minimal overhead**: ~0.1ms per trace
- **Automatic batching**: Flushes every 5 seconds or when queue reaches 100
- **Smart queueing**: Max 1000 traces in memory, drops oldest on overflow
- **Retry logic**: Failed sends are re-queued (max 500 retries)

## License

MIT © Athena Made