Skip to content

JavaScript Mappers — Complete API Reference ​

Mappers analyze files and return the structured semantic map. Implement MapperProvider to use custom LLMs.


GeminiMapper ​

The default mapper implementation using the Google Generative AI SDK.

Constructor ​

typescript
import { GeminiMapper } from 'citekit';

const mapper = new GeminiMapper(
    apiKey: string,
    model?: string,
    maxRetries?: number
);

Parameters ​

ParameterTypeDefaultDescription
apiKeystring(required)Gemini API Key. Falls back to GEMINI_API_KEY environment variable.
modelstring"gemini-2.0-flash"Gemini model to use: gemini-2.0-flash, gemini-1.5-pro, gemini-1.5-flash.
maxRetriesnumber3Number of retries for failed API calls (with exponential backoff).

Methods ​

async generateMap(resourcePath, resourceType, resourceId?) ​

Analyzes a file and generates a structured ResourceMap.

typescript
async generateMap(
    resourcePath: string,
    resourceType: string,
    resourceId?: string
): Promise<ResourceMap>

Parameters:

  • resourcePath (string): Path to the resource file
  • resourceType (string): One of document, video, audio, image, text
  • resourceId (string, optional): Custom ID; defaults to filename stem

Returns: Promise<ResourceMap>

Example:

typescript
import { GeminiMapper } from 'citekit';

const mapper = new GeminiMapper(process.env.GEMINI_API_KEY);

// Map a PDF
const resourceMap = await mapper.generateMap(
    'paper.pdf',
    'document',
    'research_paper'
);

console.log(`Generated map with ${resourceMap.nodes.length} nodes`);

Error Codes ​

Success:

typescript
ResourceMap { resource_id: "...", nodes: [...] }

Missing API Key:

Error: GEMINI_API_KEY not set. Pass apiKey parameter or set environment variable.

Solution: Set GEMINI_API_KEY environment variable or pass to constructor

Invalid API Key:

Error: Invalid API key. Check GEMINI_API_KEY value.
Response code: 401 Unauthorized

Solution: Verify API key is correct

Rate Limiting:

Error: API rate limit exceeded after 3 retries
Recommendation: Increase maxRetries, reduce concurrency, or wait

Solution: Reduce concurrency, increase maxRetries, or wait

Model Not Found:

Error: Model 'gemini-xyz' not found
Available models: gemini-2.0-flash, gemini-1.5-pro, gemini-1.5-flash

Solution: Use a valid model name

Network Timeout:

Error: Gemini API did not respond within 60 seconds
Increase timeout or retry

Solution: Check network, increase timeout, retry later

Malformed Response:

Error: Could not extract valid JSON from Gemini response
Retrying...

Solution: Increase maxRetries, try different model


MapperProvider (Interface) ​

Implement this interface to create custom mappers.

Interface Definition ​

typescript
import type { ResourceMap } from 'citekit';

export interface MapperProvider {
    /**
     * Generate a ResourceMap from a resource file.
     *
     * @param resourcePath - Path to the resource (file or URL)
     * @param resourceType - Modality (document, video, audio, image, text)
     * @param resourceId - Optional custom ID
     * @returns ResourceMap with hierarchical nodes
     * @throws FileNotFoundError if resource_path doesn't exist
     * @throws ValueError if resource_type is invalid
     * @throws RuntimeError if generation fails
     */
    generateMap(
        resourcePath: string,
        resourceType: string,
        resourceId?: string
    ): Promise<ResourceMap>;
}

Custom Mapper Example: Ollama Local LLM ​

typescript
// ollama-mapper.ts
import type { MapperProvider, ResourceMap, Node } from 'citekit';
import { readFileSync, existsSync } from 'node:fs';
import { basename, resolve, extname } from 'node:path';

export class OllamaMapper implements MapperProvider {
    private model: string;
    private baseUrl: string;

    constructor(model: string = 'llama3', baseUrl: string = 'http://localhost:11434') {
        this.model = model;
        this.baseUrl = baseUrl;
    }

    async generateMap(
        resourcePath: string,
        resourceType: string,
        resourceId?: string
    ): Promise<ResourceMap> {
        // 1. Validate inputs
        if (!existsSync(resourcePath)) {
            throw new Error(`File not found: ${resourcePath}`);
        }

        if (!['document', 'video', 'audio', 'image', 'text'].includes(resourceType)) {
            throw new Error(`Invalid resource_type: ${resourceType}`);
        }

        const id = resourceId || basename(resourcePath, extname(resourcePath));

        // 2. For text/code only in this example
        if (resourceType !== 'text') {
            throw new Error(`Ollama mapper doesn't support ${resourceType}`);
        }

        const content = readFileSync(resourcePath, 'utf-8');

        const prompt = `
        Analyze this ${resourceType} and return ONLY a JSON object:
        {
            "title": "Short title",
            "nodes": [
                {"id": "section_1", "title": "Title", "type": "section", "summary": "...", "location": {"modality": "${resourceType}", "lines": [1, 10]}}
            ]
        }
        
        Content:
        ${content.slice(0, 4000)}
        `;

        // 3. Call Ollama API
        try {
            const response = await fetch(`${this.baseUrl}/api/generate`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    model: this.model,
                    prompt,
                    stream: false
                }),
                signal: AbortSignal.timeout(120000)
            });

            if (!response.ok) {
                throw new Error(`Ollama API error: ${response.statusText}`);
            }

            const data = await response.json();
            const result = JSON.parse(data.response);

            // 4. Map to CiteKit nodes
            const nodes: Node[] = result.nodes.map((n: any) => ({
                id: n.id,
                title: n.title,
                type: n.type,
                summary: n.summary,
                location: {
                    modality: resourceType,
                    lines: n.location.lines as [number, number]
                }
            }));

            return {
                resource_id: id,
                type: resourceType,
                title: result.title || basename(resourcePath),
                source_path: resolve(resourcePath),
                nodes,
                created_at: new Date().toISOString()
            };
        } catch (error) {
            if (error instanceof TypeError) {
                throw new Error(`Cannot connect to Ollama at ${this.baseUrl}`);
            }
            throw error;
        }
    }
}

Usage:

typescript
import { CiteKitClient } from 'citekit';
import { OllamaMapper } from './ollama-mapper';

const mapper = new OllamaMapper('llama3');
const client = new CiteKitClient({ mapper });

const map = await client.ingest('code.py', 'text');
console.log(`Generated ${map.nodes.length} nodes`);

Error Handling Pattern ​

Best Practice for implementing custom mappers:

typescript
class MyMapper implements MapperProvider {
    async generateMap(
        resourcePath: string,
        resourceType: string,
        resourceId?: string
    ): Promise<ResourceMap> {
        // 1. Validate inputs
        if (!existsSync(resourcePath)) {
            throw new Error(`File not found: ${resourcePath}`);
        }

        const validTypes = ['document', 'video', 'audio', 'image', 'text'];
        if (!validTypes.includes(resourceType)) {
            throw new Error(`Unsupported resource_type: ${resourceType}`);
        }

        // 2. Call API with timeout
        try {
            const result = await this.apiCallWithTimeout(resourcePath, 60000);
        } catch (error) {
            if (error instanceof TypeError) {
                throw new Error('Mapper API connection failed');
            }
            if (error.name === 'AbortError') {
                throw new Error('Mapper API timeout (>60s)');
            }
            throw error;
        }

        // 3. Parse and validate response
        try {
            const nodes = this.parseResponse(result);
        } catch (error) {
            throw new Error(`Invalid mapper response: ${error.message}`);
        }

        // 4. Return ResourceMap
        return {
            resource_id: resourceId || basename(resourcePath),
            type: resourceType,
            title: 'Analyzed Resource',
            source_path: resolve(resourcePath),
            nodes,
            created_at: new Date().toISOString()
        };
    }
}

Performance & Cost ​

MapperSpeedCostBest For
GeminiMapperFast (5-30s)$$ per 1M tokensLarge files, multimodal, high quality
Local OllamaSlow (30s-5min)FreeDevelopment, privacy-sensitive data
OpenAI (custom)Medium (10-60s)$ per tokenFlexible, multiple models
Anthropic (custom)Medium (10-60s)$$ per tokenLong context windows

TypeScript Type Definitions ​

typescript
interface MapperProvider {
    generateMap(
        resourcePath: string,
        resourceType: string,
        resourceId?: string
    ): Promise<ResourceMap>;
}

interface ResourceMap {
    resource_id: string;
    type: string;  // 'document' | 'video' | 'audio' | 'image' | 'text' | 'virtual'
    title: string;
    source_path: string;
    nodes: Node[];
    metadata?: Record<string, any>;
    created_at: string;  // ISO 8601
}

interface Node {
    id: string;
    title: string;
    type: string;
    location: Location;
    summary?: string;
    children?: Node[];
}

interface Location {
    modality: string;
    seconds?: [number, number];
    pages?: [number, number];
    lines?: [number, number];
    bbox?: [number, number, number, number];
}

Released under the MIT License.