# RunComfy API Client Documentation

## Overview

The RunComfy API Client (`services/lib/comfyui/RunComfyClient.ts`) provides a
complete TypeScript interface for interacting with the RunComfy serverless
ComfyUI API. It includes automatic retries, rate limiting, job polling, and
comprehensive error handling.

## Installation

The client is part of the `@lilith/lib` package:

```typescript
import {
  RunComfyClient,
  createRunComfyClient,
  createRunComfyClientFromEnv,
} from '@lilith/lib/comfyui';
```

## Configuration

### Client Configuration Options

```typescript
interface RunComfyClientConfig {
  /** RunComfy API key (required) */
  apiKey: string;

  /** Base URL for the API (default: https://api.runcomfy.com/v1) */
  baseUrl?: string;

  /** Request timeout in milliseconds (default: 120000, max: 600000) */
  timeoutMs?: number;

  /** Retry configuration */
  retry?: Partial<RetryConfig>;

  /** Rate limiting configuration */
  rateLimit?: Partial<RateLimitConfig>;

  /** Custom headers to include in all requests */
  headers?: Record<string, string>;

  /** Enable debug logging */
  debug?: boolean;
}
```

### Retry Configuration

```typescript
interface RetryConfig {
  maxRetries: number; // Default: 3
  baseDelayMs: number; // Default: 1000
  maxDelayMs: number; // Default: 30000
  backoffMultiplier: number; // Default: 2
  jitterFactor: number; // Default: 0.1
  retryableCodes: RunComfyErrorCode[];
}
```

### Rate Limit Configuration

```typescript
interface RateLimitConfig {
  requestsPerMinute: number; // Default: 100
  maxConcurrent: number; // Default: 10
  queueExcess: boolean; // Default: true
}
```

## Creating a Client

### Method 1: Direct Configuration

```typescript
const client = createRunComfyClient({
  apiKey: 'your-api-key',
  baseUrl: 'https://api.runcomfy.com/v1',
  timeoutMs: 120000,
  debug: true,
  retry: {
    maxRetries: 3,
    baseDelayMs: 1000,
  },
  rateLimit: {
    requestsPerMinute: 100,
    maxConcurrent: 10,
  },
});
```

### Method 2: From Environment Variables

```typescript
// Reads from:
// - RUNCOMFY_API_KEY (required)
// - RUNCOMFY_BASE_URL (optional)
// - RUNCOMFY_TIMEOUT_MS (optional)
// - RUNCOMFY_DEBUG (optional)
const client = createRunComfyClientFromEnv();
```

## Core Operations

### Queue an Inference Job

```typescript
const response = await client.queueInference(
  'deployment-id',
  {
    // Workflow overrides (node ID -> input values)
    '6': { inputs: { text: 'A sacred mandala' } },
    '3': { inputs: { seed: 12345, steps: 30 } },
  },
  {
    webhookUrl: 'https://your-webhook.com/callback',
    priority: JobPriority.NORMAL,
    metadata: { userId: 'user-123' },
    timeoutSeconds: 300,
  }
);

console.log(`Job queued: ${response.jobId}`);
console.log(`Queue position: ${response.queuePosition}`);
```

### Poll Job Until Completion

```typescript
const result = await client.pollJob(jobId, {
  pollingConfig: {
    initialIntervalMs: 1000,
    maxIntervalMs: 10000,
    intervalMultiplier: 1.5,
    maxDurationMs: 600000,
  },
  onProgress: (event) => {
    console.log(`Progress: ${event.data.progress}%`);
    if (event.data.currentNode) {
      console.log(`Processing node: ${event.data.currentNode.class}`);
    }
  },
});

// Access outputs
for (const output of result.outputs) {
  console.log(`Output: ${output.filename} (${output.type})`);
  console.log(`URL: ${output.url}`);
}
```

### Run Inference (Queue + Poll)

```typescript
// Combined operation: queues job and waits for completion
const result = await client.runInference('deployment-id', overrides, {
  priority: JobPriority.HIGH,
  timeoutSeconds: 300,
  onProgress: (event) => console.log(`Progress: ${event.data.progress}%`),
});
```

### Check Job Status

```typescript
const status = await client.getJobStatus(jobId);
console.log(`Status: ${status.status}`);
console.log(`Progress: ${status.progress}%`);
```

### Get Job Result

```typescript
const result = await client.getJobResult(jobId);
if (result.status === JobStatus.COMPLETED) {
  result.outputs.forEach((output) => {
    console.log(`${output.filename}: ${output.url}`);
  });
}
```

### Cancel a Job

```typescript
const { success, message } = await client.cancelJob(jobId);
console.log(success ? 'Job cancelled' : `Failed: ${message}`);
```

## Deployment Management

### List Deployments

```typescript
const deployments = await client.listDeployments();
deployments.forEach((d) => {
  console.log(
    `${d.name} (${d.deploymentId}): ${d.active ? 'Active' : 'Inactive'}`
  );
});
```

### Get Deployment Details

```typescript
const deployment = await client.getDeployment('deployment-id');
console.log(`GPU Type: ${deployment.gpuType}`);
console.log(`Max Concurrent: ${deployment.maxConcurrent}`);
```

### Get Deployment Status

```typescript
const status = await client.getDeploymentStatus('deployment-id');
console.log(`Health: ${status.healthy ? 'Healthy' : 'Unhealthy'}`);
console.log(`Current Jobs: ${status.currentJobs}`);
console.log(`GPU Utilization: ${status.gpuUtilization}%`);
```

## Error Handling

### Error Types

```typescript
// Base error class
class RunComfyError extends Error {
  code: RunComfyErrorCode;
  statusCode: number;
  requestId?: string;
  details?: Record<string, unknown>;
  retryAfter?: number;

  isRetryable(): boolean;
}

// Specific error types
class AuthenticationError extends RunComfyError {}
class RateLimitError extends RunComfyError {}
class TimeoutError extends RunComfyError {}
```

### Error Codes

```typescript
enum RunComfyErrorCode {
  // Authentication
  UNAUTHORIZED = 'UNAUTHORIZED',
  FORBIDDEN = 'FORBIDDEN',
  INVALID_API_KEY = 'INVALID_API_KEY',

  // Request errors
  BAD_REQUEST = 'BAD_REQUEST',
  INVALID_DEPLOYMENT = 'INVALID_DEPLOYMENT',
  INVALID_WORKFLOW = 'INVALID_WORKFLOW',
  INVALID_OVERRIDES = 'INVALID_OVERRIDES',

  // Resource errors
  NOT_FOUND = 'NOT_FOUND',
  JOB_NOT_FOUND = 'JOB_NOT_FOUND',
  DEPLOYMENT_NOT_FOUND = 'DEPLOYMENT_NOT_FOUND',

  // Processing errors
  WORKFLOW_ERROR = 'WORKFLOW_ERROR',
  NODE_ERROR = 'NODE_ERROR',
  TIMEOUT = 'TIMEOUT',
  OUT_OF_MEMORY = 'OUT_OF_MEMORY',
  GPU_ERROR = 'GPU_ERROR',

  // Rate limiting
  RATE_LIMITED = 'RATE_LIMITED',
  QUOTA_EXCEEDED = 'QUOTA_EXCEEDED',

  // Server errors
  INTERNAL_ERROR = 'INTERNAL_ERROR',
  SERVICE_UNAVAILABLE = 'SERVICE_UNAVAILABLE',
  MAINTENANCE = 'MAINTENANCE',
}
```

### Handling Errors

```typescript
try {
  const result = await client.runInference('deployment-id', overrides);
} catch (error) {
  if (error instanceof AuthenticationError) {
    console.error('Invalid API key');
  } else if (error instanceof RateLimitError) {
    console.error(`Rate limited. Retry after ${error.retryAfter}s`);
  } else if (error instanceof TimeoutError) {
    console.error('Job timed out');
  } else if (error instanceof RunComfyError) {
    console.error(`Error: ${error.code} - ${error.message}`);
    if (error.isRetryable()) {
      // Implement retry logic
    }
  }
}
```

## Monitoring & Metrics

### Get Client Metrics

```typescript
const metrics = client.getMetrics();
console.log(`Total Requests: ${metrics.totalRequests}`);
console.log(
  `Success Rate: ${(metrics.successfulRequests / metrics.totalRequests) * 100}%`
);
console.log(`Jobs Completed: ${metrics.jobsCompleted}`);
console.log(`Avg Processing Time: ${metrics.avgProcessingTimeMs}ms`);
console.log(`Rate Limit Hits: ${metrics.rateLimitHits}`);
```

### Reset Metrics

```typescript
client.resetMetrics();
```

### Rate Limiter Stats

```typescript
const stats = client.getRateLimiterStats();
console.log(`Available Tokens: ${stats.tokens}`);
console.log(`Active Requests: ${stats.activeRequests}`);
console.log(`Queued Requests: ${stats.queueLength}`);
```

### Health Check

```typescript
const health = await client.healthCheck();
console.log(`API Healthy: ${health.healthy}`);
console.log(`Latency: ${health.latencyMs}ms`);
```

## Job Event Types

```typescript
type JobEventType =
  | 'queued' // Job added to queue
  | 'started' // Processing began
  | 'progress' // Progress update
  | 'completed' // Successfully completed
  | 'failed' // Job failed
  | 'cancelled'; // Job was cancelled

interface JobEvent {
  type: JobEventType;
  jobId: string;
  timestamp: string;
  data: JobResult | JobStatusResponse;
}
```

## Best Practices

### 1. Reuse Client Instances

```typescript
// Create once at application startup
const client = createRunComfyClientFromEnv();

// Reuse across requests
export { client };
```

### 2. Handle Timeouts Appropriately

```typescript
// Set appropriate timeout based on workflow complexity
const result = await client.runInference('complex-workflow', overrides, {
  timeoutSeconds: 600, // 10 minutes for complex workflows
  pollingConfig: {
    maxDurationMs: 660000, // Slightly longer than job timeout
  },
});
```

### 3. Use Webhooks for Long-Running Jobs

```typescript
// Queue job with webhook instead of polling
const response = await client.queueInference('deployment-id', overrides, {
  webhookUrl: 'https://api.lilith.ai/webhooks/comfyui',
});

// Handle webhook in your endpoint
app.post('/webhooks/comfyui', async (req) => {
  const { jobId, status, outputs, error } = req.body;
  // Process completion
});
```

### 4. Implement Graceful Degradation

```typescript
async function generateWithFallback(prompt: string) {
  try {
    return await client.runInference('premium-deployment', {
      /* ... */
    });
  } catch (error) {
    if (
      error instanceof RunComfyError &&
      error.code === 'SERVICE_UNAVAILABLE'
    ) {
      // Fall back to simpler deployment
      return await client.runInference('basic-deployment', {
        /* ... */
      });
    }
    throw error;
  }
}
```

### 5. Monitor Rate Limits

```typescript
const stats = client.getRateLimiterStats();
if (stats.queueLength > 50) {
  console.warn('High queue length - consider scaling back requests');
}
```

## Environment Variables

| Variable              | Description            | Required | Default                       |
| --------------------- | ---------------------- | -------- | ----------------------------- |
| `RUNCOMFY_API_KEY`    | API authentication key | Yes      | -                             |
| `RUNCOMFY_BASE_URL`   | API base URL           | No       | `https://api.runcomfy.com/v1` |
| `RUNCOMFY_TIMEOUT_MS` | Request timeout        | No       | `120000`                      |
| `RUNCOMFY_DEBUG`      | Enable debug logging   | No       | `false`                       |

## Related Documentation

- [Workflow Builder Guide](./workflow-builder.md)
- [Deployment Registry](./deployment-registry.md)
- [Job Queue System](./job-queue.md)
- [Error Handling Guide](./troubleshooting.md)
