// ---------------------------------------------------------------------------
// concurrency.ts -- cooperative semaphore for limiting concurrent LLM calls
// Shared module: imported by both eval-server/ and commands/eval/
// ---------------------------------------------------------------------------
export const DEFAULT_CONCURRENCY = 3;
export class Semaphore {
    limit;
    running = 0;
    queue = [];
    constructor(limit = DEFAULT_CONCURRENCY) {
        this.limit = limit;
        if (limit < 1)
            throw new Error("Semaphore limit must be >= 1");
    }
    async acquire() {
        if (this.running < this.limit) {
            this.running++;
            return;
        }
        return new Promise((resolve) => this.queue.push(() => {
            this.running++;
            resolve();
        }));
    }
    release() {
        if (this.running <= 0)
            return; // idempotent — no underflow
        this.running--;
        const next = this.queue.shift();
        if (next)
            next();
    }
    get available() {
        return Math.max(0, this.limit - this.running);
    }
    get pending() {
        return this.queue.length;
    }
    get active() {
        return this.running;
    }
}
// Per-skill semaphore registry — ensures one semaphore per skill
const registry = new Map();
export function getSkillSemaphore(skillKey, limit = DEFAULT_CONCURRENCY) {
    let sem = registry.get(skillKey);
    if (!sem) {
        sem = new Semaphore(limit);
        registry.set(skillKey, sem);
    }
    return sem;
}
//# sourceMappingURL=concurrency.js.map