npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@premai/pcci-sdk-ts

v1.0.20

Published

End-to-end encrypted OpenAI-compatible client with file upload and tools support, using XWing (ML-KEM768 + X25519) hybrid post-quantum encryption.

Readme

RVENC Client

End-to-end encrypted OpenAI-compatible client with file upload and tools support, using XWing (ML-KEM768 + X25519) hybrid post-quantum encryption.

Installation

npm install

Quick Start

import createRvencClient from "@premai/pcci-sdk-ts";

// Create client (encryption keys auto-generated)
const client = await createRvencClient({
  apiKey: "your-api-key"
});

// Chat completion
const response = await client.chat.completions.create({
  model: "openai/gpt-oss-120b",
  messages: [{ role: "user", content: "Hello!" }],
});

console.log(response.choices[0].message.content);

OpenAI-Compatible API Server

Environment Variables:

  • ENCLAVE_URL
  • PROXY_URL
  • CLIENT_KEK
  • HOST (optional)
  • PORT (optional)

Run as a standalone server with automatic DEK store management per API key:

# ways to run the termination proxy
## run command from local/remote NPM package
bunx -p @premai/pcci-sdk-ts pcci-proxy
npx -p @premai/pcci-sdk-ts pcci-proxy

## install globally
## make sure to have the "global bin dir" in your PATH
## `npm config get prefix`, for pure node runtime
npm i -g @premai/pcci-sdk-ts
bun i -g @premai/pcci-sdk-ts

# Server runs on http://localhost:3000

Use with any OpenAI-compatible client:

curl http://localhost:3000/v1/chat/completions \
  -H "Authorization: Bearer your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-oss-120b",
    "messages": [
      {"role": "user", "content": "Hello!"}
    ],
    "stream": false
  }'

Or use the OpenAI SDK in Node.js:

import OpenAI from "openai";

const client = new OpenAI({
  apiKey: "your-api-key",
  baseURL: "http://localhost:3000/v1",
});

const stream = await client.chat.completions.create({
  model: "openai/gpt-oss-120b",
  messages: [{ role: "user", content: "Count to 10" }],
  stream: true,
});

for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content || "");
}

The server caches clients in memory per API key for better performance.

Features

  • Chat Completions - OpenAI-compatible API with streaming support
  • Models - List available models
  • File Management - Upload, list, get, and delete encrypted files
  • File Upload - Client-side encrypted file uploads with per-file DEKs
  • Tools - Image generation, transcription, RAG search, and more
  • End-to-end Encryption - Post-quantum cryptography (XWing)
  • KEK Architecture - Key Encryption Key wraps all file DEKs
  • RAG Support - Encrypted document retrieval with persistent RAG DEK
  • TypeScript - Full type safety

Chat Completions

Non-streaming

const response = await client.chat.completions.create({
  model: "openai/gpt-oss-120b",
  messages: [{ role: "user", content: "Hello!" }],
});

console.log(response.choices[0].message.content);

Streaming

const stream = await client.chat.completions.create({
  model: "openai/gpt-oss-120b",
  messages: [{ role: "user", content: "Count to 10" }],
  stream: true,
});

for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content || "");
}

Vision

Use vision models to analyze images:

const response = await client.chat.completions.create({
  model: "OpenGVLab/InternVL3-38B",
  messages: [{
    role: "user",
    content: [
      { type: "text", text: "What is in this image?" },
      {
        type: "image_url",
        image_url: {
          url: "https://fastly.picsum.photos/id/237/200/300.jpg?hmac=TmmQSbShHz9CdQm0NkEjx1Dyh_Y984R9LpNrpvH2D_U",
        },
      },
    ],
  }],
});

console.log(response.choices[0].message.content);

Audio

Transcriptions

Transcribe audio files to text:

import createRvencClient from "@premai/pcci-sdk-ts";
import fs from "fs";

const client = await createRvencClient({
  apiKey: "your-api-key"
});

const transcription = await client.audio.transcriptions.create({
  file: fs.createReadStream('./audio.wav'),
  model: 'openai/whisper-large-v3',
});

console.log(transcription.text);

Translations

Translate audio files to English:

const translation = await client.audio.translations.create({
  file: fs.createReadStream('./audio.mp3'),
  model: 'openai/whisper-large-v3',
});

console.log(translation.text);

Note: openai/whisper-large-v3 model supports non-streaming only.

Models

List available models

const client = await createRvencClient({
  apiKey: "your-api-key",
});

const models = await client.models.list({
  type: "CHAT",
});

File Upload

First Time Setup

import createRvencClient, { serializeDEKStore } from "@premai/pcci-sdk-ts";
import fs from "fs";

// Create client (DEK store auto-generated)
const client = await createRvencClient({
  apiKey: "your-api-key",
  clientKEK: "your-kek" // You can pass clientKEK here or as an env variable CLIENT_KEK
});

// Save DEK store for future use
const serializedStore = serializeDEKStore(client.dekStore);
fs.writeFileSync("./dek-store.json", serializedStore);

console.log("✅ DEK store saved. Keep this file secure!");

Subsequent Usage

import createRvencClient, { deserializeDEKStore } from "@premai/pcci-sdk-ts";
import fs from "fs";

// Load existing DEK store
const serializedStore = fs.readFileSync("./dek-store.json", "utf-8");
const dekStore = deserializeDEKStore(serializedStore);

// Create client with existing DEK store
const client = await createRvencClient({
  apiKey: "your-api-key",
  clientKEK: "your-kek" // You can pass clientKEK here or as an env variable CLIENT_KEK
  dekStore,
});

Upload Files

import fs from "fs";
import { serializeDEKStore } from "@premai/pcci-sdk-ts";

const fileContent = fs.readFileSync("./document.pdf");
const result = await client.files.upload({
  file: new Uint8Array(fileContent),
  fileName: "document.pdf",
  mimeType: "application/pdf", // optional - auto-detected
});

console.log("Uploaded:", result.id);

// ⚠️ IMPORTANT: Save dekStore after upload to persist file DEKs
const serialized = serializeDEKStore(client.dekStore);
fs.writeFileSync("./dek-store.json", serialized);
console.log("✅ DEK store updated with file encryption keys");

Upload Files with RAG Indexing

To enable RAG (Retrieval-Augmented Generation) indexing for a file, set ragIndex: true:

const result = await client.files.upload({
  file: new Uint8Array(fileContent),
  fileName: "document.pdf",
  mimeType: "application/pdf",
  ragIndex: true, // Enable RAG indexing
});

// Save dekStore to persist both file DEK and RAG DEK
const serialized = serializeDEKStore(client.dekStore);
fs.writeFileSync("./dek-store.json", serialized);

RAG Indexing Notes:

  • When ragIndex: true, additional encryption keys are generated for RAG operations
  • A shared ragDEK is created (or reused from dekStore) for all RAG-indexed files
  • The file DEK is encrypted with the RAG DEK for retrieval operations
  • RAG DEK is automatically persisted in dekStore.ragDEK

Important Notes:

  • Each file is encrypted with a unique random DEK
  • The DEK is wrapped with your clientKEK and stored in dekStore.fileDEKs
  • You must save the dekStore after uploading to persist file DEKs
  • File DEKs are required for tools to process uploaded files

List Files

Retrieve a paginated list of your encrypted files with optional filtering:

// List all files (default: 20 per page)
const result = await client.files.list();
console.log(result.files);        // Array of FileMetadata
console.log(result.pagination);   // { total, page, limit, pages }

// List with pagination
const page2 = await client.files.list({
  limit: 50,
  offset: 50,
});

// Filter by type
const images = await client.files.list({
  type: 'image',  // 'image' | 'document' | 'video' | 'audio' | 'archive' | 'general'
});

// Search by name
const searchResults = await client.files.list({
  search: 'report',
});

// Filter by date range (must be ISO8601 format)
const recentFiles = await client.files.list({
  from: '2024-01-01T00:00:00Z',
  to: '2024-12-31T23:59:59Z',
});

// Or with timezone offset
const specificRange = await client.files.list({
  from: '2024-01-01T00:00:00+00:00',
  to: '2024-12-31T23:59:59+00:00',
});

// Combine filters
const filtered = await client.files.list({
  limit: 100,
  offset: 0,
  type: 'document',
  search: 'quarterly',
  from: '2024-01-01',
  to: '2024-12-31',
});

Get File Details

Retrieve detailed information about a specific file:

// Get file metadata
const file = await client.files.get({ id: 'file-id-123' });
console.log(file.original_name);
console.log(file.file_size);
console.log(file.mime_type);
console.log(file.created_at);

// Get file with signed download URL
const fileWithUrl = await client.files.get({ 
  id: 'file-id-123',
  url: true  // Include signed download URL
});
console.log(fileWithUrl.url);  // Temporary signed URL for download

Delete File

Delete an encrypted file:

// Delete a file
await client.files.delete({
  id: 'file-id-123'
});

// With error handling
try {
  await client.files.delete({
    id: 'file-id-123'
  });
  console.log('File deleted successfully');
} catch (error) {
  console.error('Delete failed:', error.message);
}

Index Files for RAG

Index existing files for RAG (Retrieval-Augmented Generation) search. This allows you to add files to your RAG index that were uploaded without ragIndex: true:

// First, list your files to get their IDs and paths
const filesList = await client.files.list();

// Select files to index
const filesToIndex = filesList.files
  .filter(f => f.type === 'document')
  .map(f => ({
    fileId: f.id,
    filePath: f.file_path,
  }));

// Index the files for RAG (uses DEKs from dekStore)
const indexResult = await client.files.index({
  files: filesToIndex
});

// Check results
indexResult.data.results.forEach(result => {
  if (result.success) {
    console.log(`✓ File ${result.file_id}: ${result.rag_status}`);
  } else {
    console.error(`✗ File ${result.file_id} failed: ${result.error}`);
  }
});

Flexible DEK Management:

You can provide custom DEKs or let the function use DEKs from dekStore:

// Option 1: Use DEKs from dekStore (default)
await client.files.index({
  files: [
    { fileId: 'file-id-1', filePath: 's3/path/file1.enc' },
    { fileId: 'file-id-2', filePath: 's3/path/file2.enc' },
  ]
});

// Option 2: Provide custom ragDEK
await client.files.index({
  files: [
    { fileId: 'file-id-1', filePath: 's3/path/file1.enc' },
  ],
  ragDEK: customRagDEK  // Override dekStore ragDEK
});

// Option 3: Provide custom fileDEK for specific files
await client.files.index({
  files: [
    { 
      fileId: 'file-id-1', 
      filePath: 's3/path/file1.enc',
      fileDEK: customFileDEK  // Custom DEK for this file
    },
    { 
      fileId: 'file-id-2', 
      filePath: 's3/path/file2.enc' 
      // Uses dekStore for this file
    },
  ],
  ragDEK: customRagDEK  // Single ragDEK for all files
});

Important Notes:

  • ragDEK: Checks options.ragDEK first, then dekStore.ragDEK. Throws error if not found.
  • fileDEK: For each file, checks file.fileDEK first, then dekStore.fileDEKs. Throws error if not found.
  • To initialize RAG DEK in dekStore, upload at least one file with ragIndex: true
  • Once indexed, files can be searched using client.tools.searchRag()

Example Workflow:

// Step 1: Upload first file with RAG to initialize RAG DEK
const firstFile = await client.files.upload({
  file: new Uint8Array(fs.readFileSync('./doc1.pdf')),
  fileName: 'doc1.pdf',
  ragIndex: true,  // Creates RAG DEK in dekStore
});

// Save dekStore to persist RAG DEK
fs.writeFileSync('./dek-store.json', serializeDEKStore(client.dekStore));

// Step 2: Upload other files without RAG indexing (faster)
const file2 = await client.files.upload({
  file: new Uint8Array(fs.readFileSync('./doc2.pdf')),
  fileName: 'doc2.pdf',
  ragIndex: false,  // Skip RAG indexing during upload
});

// Step 3: Later, index the files for RAG
const result = await client.files.index({
  files: [
    { fileId: file2.id, filePath: file2.file_path },
  ]
});

console.log('Indexed:', result.data.results[0].rag_status); // "running"

Delete Files from RAG Index

Remove files from the RAG index without deleting the actual files:

// Delete specific files from RAG index
const deleteResult = await client.files.deleteIndex({
  fileIds: ['file-id-1', 'file-id-2', 'file-id-3']
});

// Check results
deleteResult.data.results.forEach(result => {
  if (result.success) {
    console.log(`✓ File ${result.file_id} removed from index`);
  } else {
    console.error(`✗ File ${result.file_id} failed: ${result.error}`);
  }
});

With Custom RAG DEK:

// Use custom ragDEK instead of dekStore
await client.files.deleteIndex({
  fileIds: ['file-id-1', 'file-id-2'],
  ragDEK: customRagDEK  // Optional: override dekStore ragDEK
});

Important Notes:

  • This removes files from the RAG search index only
  • The actual encrypted files remain in storage
  • ragDEK: Checks options.ragDEK first, then dekStore.ragDEK. Throws error if not found.
  • Use client.files.delete() to completely delete files

File Upload Options

interface FileUploadOptions {
  file: Uint8Array;        // File content as bytes
  fileName: string;        // Name of the file
  mimeType?: string;       // Optional MIME type (auto-detected from extension)
  ragIndex?: boolean;      // Optional: Enable RAG indexing for this file
}

interface ListFilesOptions {
  limit?: number;          // Max files to return (default: 20)
  offset?: number;         // Skip N files for pagination (default: 0)
  search?: string;         // Search term to filter by name
  from?: string;           // Minimum date filter (ISO8601: YYYY-MM-DDTHH:mm:ss+HH:mm or Z)
  to?: string;             // Maximum date filter (ISO8601: YYYY-MM-DDTHH:mm:ss+HH:mm or Z)
}

interface GetFileOptions {
  id: string;              // File ID (required)
  url?: boolean;           // Include signed download URL (default: false)
}

interface DeleteFileOptions {
  id: string;              // File ID (required)
}

interface IndexFileInput {
  fileId: string;          // File ID (required)
  filePath: string;        // S3/R2 path to encrypted file (required)
  fileDEK?: Uint8Array;    // Optional: custom file DEK (falls back to dekStore)
}

interface IndexFilesOptions {
  files: IndexFileInput[]; // Array of files to index (required)
  ragDEK?: Uint8Array;     // Optional: custom RAG DEK for all files (falls back to dekStore)
}

interface DeleteIndexOptions {
  fileIds: string[];       // Array of file IDs to remove from index (required)
  ragDEK?: Uint8Array;     // Optional: custom RAG DEK (falls back to dekStore)
}

Tools

All tools are encrypted end-to-end. Files are automatically downloaded and decrypted.

File-Producing Tools

Generate files that are automatically decrypted and returned:

// Generate an image
const image = await client.tools.generateImage({prompt: "sunset over mountains"});
console.log(image.fileName); // "generated_image.png"
console.log(image.content);  // Uint8Array - save or use directly
fs.writeFileSync(image.fileName, image.content);

// Generate audio from text
const audio = await client.tools.audioGenerateFromText({text: "Hello, world!"});
fs.writeFileSync(audio.fileName, audio.content);

// Create a custom file
const file = await client.tools.createFileForUser(
  {
    fileName: 'test_file',
    fileExtension: 'txt',
    fileContent: 'This is the content of the test file.',
    mimeType: 'text/plain'
  }
);
fs.writeFileSync(file.fileName, file.content);

File-Processing Tools

Process uploaded files and get results:

// Upload a file first
const upload = await client.files.upload({
  file: new Uint8Array(fs.readFileSync("./image.jpg")),
  fileName: "image.jpg",
});

// Describe and caption image
const description = await client.tools.imageDescribeAndCaption({fileId: upload.id});
console.log(description);

// Extract PDF content
const pdfUpload = await client.files.upload({
  file: new Uint8Array(fs.readFileSync("./doc.pdf")),
  fileName: "doc.pdf",
});
const pdfContent = await client.tools.getPDFContent({fileId: pdfUpload.id});
console.log(pdfContent);

// Transcribe audio
const audioUpload = await client.files.upload({
  file: new Uint8Array(fs.readFileSync("./audio.mp3")),
  fileName: "audio.mp3",
});
const transcript = await client.tools.transcribeAudioToText({fileId: audioUpload.id});
console.log(transcript);

// Video description
const videoUpload = await client.files.upload({
  file: new Uint8Array(fs.readFileSync("./video.mp4")),
  fileName: "video.mp4",
});
const videoDesc = await client.tools.videoDescribeAndCaption({fileId: videoUpload.id});
console.log(videoDesc);

Simple Tools

Tools that don't require file handling:

// Get current time
const time = await client.tools.getTime({timezone: 'America/New_York'});
console.log(time);

// Web search
const searchResults = await client.tools.webSearchTool({query: 'latest AI news'});
console.log(searchResults);

// Web page scraper
const pageContent = await client.tools.webPageScraperTool({url: 'https://example.com', renderJs: false});
console.log(pageContent);

// RAG search across your uploaded files
const ragResults = await client.tools.searchRag({
  query: 'test query',
});
console.log(ragResults);

Available Tools

File-Producing Tools

  • generateImage(params: { prompt: string }) - Generate images from text
  • audioGenerateFromText(params: { text: string }) - Text-to-speech
  • createFileForUser(params: { fileName: string, fileExtension: string, fileContent: string, mimeType: string }) - Create custom files

File-Processing Tools

  • imageDescribeAndCaption(params: { fileId: string }) - Describe images
  • imageDescribeAndCaptionFallback(params: { fileId: string }) - Alternative image description
  • videoDescribeAndCaption(params: { fileId: string }) - Describe videos
  • getPDFContent(params: { fileId: string }) - Extract PDF text
  • getTextDocumentContent(params: { fileId: string }) - Extract document text
  • transcribeAudioToText(params: { fileId: string, language?: string }) - Audio transcription
  • transcribeAudioWithDiarization(params: { fileId: string, language?: string }) - Transcription with speakers
  • audioDiarization(params: { fileId: string }) - Identify speakers
  • getFileContentOCR(params: { fileId: string }) - OCR on images
  • getSpreadsheetContent(params: { fileId: string }) - Extract spreadsheet data
  • getDataFileContent(params: { fileId: string }) - Extract data file content
  • getPowerPointContent(params: { fileId: string, slideNumbers?: number[] }) - Extract presentation content

Simple Tools

  • getTime(params: { timezone: string }) - Get current time
  • webSearchTool(params: { query: string, country?: string, searchLang?: string }) - Web search
  • webPageScraperTool(params: { url: string, renderJs?: boolean }) - Scrape web pages

RAG Tools

  • searchRag(params: { query: string }) - Search indexed documents with optional file filtering

Configuration Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | apiKey | string | required | Authorization token | | encryptionKeys | EncryptionKeys | auto-generated | Pre-generated ML-KEM keys | | dekStore | DEKStore | auto-generated | DEKs for files and RAG | | requestTimeoutMs | number | 30000 | Request timeout in milliseconds | | maxBufferSize | number | 10485760 | Max SSE buffer size (10MB) |

DEK Store Management

The DEK store contains encryption keys for all file and RAG operations:

interface DEKStore {
  fileDEKs?: Map<string, Uint8Array>;        // Per-file DEKs (fileId -> DEK)
  ragDEK?: Uint8Array;                        // RAG operations DEK
  ragVersion?: string;                        // RAG version identifier
}

Initialize New DEK Store

import { initializeDEKStore, serializeDEKStore } from "@premai/pcci-sdk-ts";

// Create new DEK store with KEK from environment variable
const dekStore = initializeDEKStore();

// Save to secure storage
const serialized = serializeDEKStore(dekStore);
fs.writeFileSync("dek-store.json", serialized);

Serialize for Storage

import { serializeDEKStore } from "@premai/pcci-sdk-ts";

const serialized = serializeDEKStore(client.dekStore);
fs.writeFileSync("dek-store.json", serialized);

Deserialize from Storage

import { deserializeDEKStore } from "@premai/pcci-sdk-ts";

const serialized = fs.readFileSync("dek-store.json", "utf-8");
const dekStore = deserializeDEKStore(serialized);

Important: Persist After File Operations

// After uploading files
const upload = await client.files.upload({...});

// ⚠️ MUST save to persist file DEKs
const serialized = serializeDEKStore(client.dekStore);
fs.writeFileSync("dek-store.json", serialized);

// After using RAG tools
await client.tools.searchRag({query: "..."});

// ⚠️ Save if ragDEK was auto-generated
const serialized = serializeDEKStore(client.dekStore);
fs.writeFileSync("dek-store.json", serialized);

How It Works

Chat Completions

  1. Key Exchange: Fetches enclave public key → creates XWing encapsulation → shared secret
  2. Request Encryption: Encrypts inference params with XChaCha20-Poly1305
  3. Encrypted Transport: Sends { cipherText, encryptedInference, nonce } to proxy
  4. Response Decryption: Decrypts response (streaming SSE or JSON) using shared secret

File Upload

  1. DEK Generation: Generates random 32-byte DEK for this file
  2. File Encryption: Encrypts file content with XChaCha20-Poly1305 + managed nonce
  3. Metadata Encryption: Encrypts filename and MIME type with same DEK
  4. DEK Wrapping: Wraps DEK with clientKEK using AES-KWP
  5. Upload: Sends encrypted data with wrapped_dek and kid
  6. Storage: Stores file DEK in dekStore.fileDEKs[fileId] for later use

With RAG Indexing (ragIndex: true)

  1. RAG DEK: Retrieves existing ragDEK from dekStore or generates new 32-byte key
  2. File DEK Encryption: Encrypts file DEK with RAG DEK (XChaCha20-Poly1305)
  3. RAG DEK Encryption: Encrypts RAG DEK with clientKEK (XChaCha20-Poly1305)
  4. Additional Payload: Includes encrypted_file_dek, encrypted_rag_dek, file_nonce, rag_dek_nonce, cipher_text
  5. RAG Storage: Persists ragDEK in dekStore.ragDEK for reuse across files

Tools

File-Processing Tools

  1. Retrieve fileDEK: Gets stored DEK from dekStore.fileDEKs[fileId]
  2. Encrypt fileDEK: Encrypts fileDEK with shared secret (per-request nonce)
  3. Send Request: Sends encryptedFileDEK + fileDEKNonce to enclave
  4. Enclave Decrypts: Enclave decrypts fileDEK, then decrypts file from S3
  5. Response: Returns encrypted result, SDK decrypts with shared secret

File-Producing Tools

  1. Generate DEK: Creates random DEK for output file
  2. Encrypt Request: Encrypts tool parameters with shared secret
  3. Enclave Executes: Generates file, encrypts with provided DEK
  4. Download: SDK downloads and decrypts file using the DEK
  5. Response: Returns DecryptedFile with content as Uint8Array

RAG Tools

  1. Retrieve fileDEKs: Gets DEKs for all specified files
  2. Encrypt DEKs: Encrypts each fileDEK with shared secret (unique nonces)
  3. Encrypt ragDEK: Encrypts persistent ragDEK with shared secret
  4. Send Request: Sends encryptedFileDEKs[] + ragDEK + ragVersion
  5. Enclave Processing: Decrypts files, performs RAG search
  6. Response: Returns encrypted results

Error Handling

try {
  const client = await createRvencClient({
    apiKey: "your-api-key",
    clientKEK: "your-kek" // You can pass clientKEK here or as an env variable CLIENT_KEK
  });
  
  const image = await client.tools.generateImage("mountain landscape");
  fs.writeFileSync(image.fileName, image.content);
  
} catch (error) {
  if (error.message.includes("timeout")) {
    console.error("Request timed out");
  } else {
    console.error("Error:", error.message);
  }
}

Security Notes

  • ⚠️ Use environment variables for API keys & client KEK
  • ⚠️ Always persist dekStore after file uploads - file DEKs must be saved
  • ⚠️ Backup your dekStore - losing it means losing access to uploaded files
  • ✅ All encryption happens client-side (zero-knowledge)
  • ✅ Files are encrypted with unique DEKs before upload
  • ✅ KEK-based architecture - KEK wraps all file DEKs
  • ✅ Tool responses are automatically decrypted
  • ✅ Post-quantum secure (XWing: ML-KEM768 + X25519)

Encryption Architecture

clientKEK (32 bytes)
    ├── wraps fileDEK₁ → file_1.pdf
    ├── wraps fileDEK₂ → file_2.jpg
    ├── wraps fileDEK₃ → file_3.mp3
    └── wraps fileDEKₙ → file_n.txt

ragDEK (32 bytes) → encrypts RAG index

Each file gets unique DEK, all wrapped by KEK

Best Practices

  1. Initialize Once: Create dekStore once, reuse across sessions
  2. Persist After Uploads: Save dekStore immediately after each file upload
  3. Secure Storage: Store dek-store.json in secure, encrypted location
  4. Regular Backups: Backup dekStore to prevent data loss
  5. Rotation: Generate new dekStore periodically and re-upload files
  6. Error Handling: Always check if fileDEK exists before using tools

TypeScript Types

import type {
  RvencClient,
  RvencClientOptions,
  DEKStore,
  EncryptionKeys,
  FileUploadOptions,
  UploadedFile,
  DecryptedFile,
  ToolsClient,
  ListFilesOptions,
  ListFilesResponse,
  GetFileOptions,
  DeleteFileOptions,
  DeleteFileResponse,
  IndexFileInput,
  IndexFilesOptions,
  IndexFilesResponse,
  DeleteIndexOptions,
  DeleteIndexResponse,
  FileMetadata,
  PaginationInfo,
} from "@premai/pcci-sdk-ts";