// engine/llm-security/gpai-detector.ts — SPEC-535
// Detects foundation model SDK usage in projects for EU AI Act Article 53-55 compliance.
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
/** Map of known LLM SDK package names to provider labels. */
const SDK_PROVIDER_MAP = {
    '@anthropic-ai/sdk': 'Anthropic',
    anthropic: 'Anthropic',
    openai: 'OpenAI',
    '@openai': 'OpenAI',
    '@google/generative-ai': 'Google',
    '@google-ai/generativelanguage': 'Google',
    'google-generativeai': 'Google',
    langchain: 'LangChain',
    'langchain-anthropic': 'Anthropic',
    'langchain-openai': 'OpenAI',
    '@langchain/anthropic': 'Anthropic',
    '@langchain/openai': 'OpenAI',
    '@langchain': 'LangChain',
    llamaindex: 'LlamaIndex',
    'llama-index': 'LlamaIndex',
    llama_index: 'LlamaIndex',
    '@vercel/ai': 'Vercel AI SDK',
    ai: 'Vercel AI SDK',
    'groq-sdk': 'Groq',
    mistralai: 'Mistral AI',
    cohere: 'Cohere',
};
/** Check a package name against the known SDK map. */
function matchSdk(packageName) {
    const trimmed = packageName.trim();
    // Direct match
    if (SDK_PROVIDER_MAP[trimmed] !== undefined) {
        return trimmed;
    }
    // Prefix match for scoped packages (e.g. @langchain/*)
    for (const sdk of Object.keys(SDK_PROVIDER_MAP)) {
        if (sdk.endsWith('/') && trimmed.startsWith(sdk)) {
            return sdk;
        }
        if (sdk === '@langchain' && trimmed.startsWith('@langchain/')) {
            return sdk;
        }
        if (sdk === '@openai' && trimmed.startsWith('@openai/')) {
            return sdk;
        }
    }
    return null;
}
/** Parse package.json and extract matched SDKs. */
async function scanPackageJson(filePath) {
    const content = await readFile(filePath, 'utf-8');
    let parsed;
    try {
        parsed = JSON.parse(content);
    }
    catch {
        return [];
    }
    if (typeof parsed !== 'object' || parsed === null) {
        return [];
    }
    const pkg = parsed;
    const matched = [];
    for (const section of ['dependencies', 'devDependencies', 'peerDependencies']) {
        const deps = pkg[section];
        if (typeof deps === 'object' && deps !== null) {
            for (const name of Object.keys(deps)) {
                const sdk = matchSdk(name);
                if (sdk !== null && !matched.includes(sdk)) {
                    matched.push(sdk);
                }
            }
        }
    }
    return matched;
}
/** Parse a plain-text dependency file (requirements.txt, Gemfile, etc.) line by line. */
async function scanTextFile(filePath) {
    const content = await readFile(filePath, 'utf-8');
    const matched = [];
    for (const rawLine of content.split('\n')) {
        // Strip version specifiers: package==1.0 → package
        const line = rawLine.trim().split(/[=<>!;\s[]/)[0] ?? '';
        if (line.length === 0 || line.startsWith('#')) {
            continue;
        }
        const sdk = matchSdk(line);
        if (sdk !== null && !matched.includes(sdk)) {
            matched.push(sdk);
        }
    }
    return matched;
}
/** Parse pyproject.toml — extract package names from [tool.poetry.dependencies] section. */
async function scanPyprojectToml(filePath) {
    const content = await readFile(filePath, 'utf-8');
    const matched = [];
    let inDepsSection = false;
    for (const rawLine of content.split('\n')) {
        const line = rawLine.trim();
        if (line.startsWith('[')) {
            inDepsSection =
                line === '[tool.poetry.dependencies]' ||
                    line === '[project.dependencies]' ||
                    line === '[tool.poetry.dev-dependencies]';
            continue;
        }
        if (!inDepsSection) {
            continue;
        }
        // key = "value" or key = { ... }
        const eqIdx = line.indexOf('=');
        if (eqIdx === -1) {
            continue;
        }
        const pkgName = line.slice(0, eqIdx).trim();
        const sdk = matchSdk(pkgName);
        if (sdk !== null && !matched.includes(sdk)) {
            matched.push(sdk);
        }
    }
    return matched;
}
/** Parse go.mod — extract module names from require block. */
async function scanGoMod(filePath) {
    const content = await readFile(filePath, 'utf-8');
    const matched = [];
    let inRequire = false;
    for (const rawLine of content.split('\n')) {
        const line = rawLine.trim();
        if (line.startsWith('require (')) {
            inRequire = true;
            continue;
        }
        if (inRequire && line === ')') {
            inRequire = false;
            continue;
        }
        if (line.startsWith('require ') || inRequire) {
            const parts = line
                .replace(/^require\s+/, '')
                .trim()
                .split(/\s+/);
            const modulePath = parts[0] ?? '';
            // Extract last segment of Go module path (e.g. github.com/openai/openai-go → openai-go)
            const segments = modulePath.split('/');
            for (const seg of segments) {
                const sdk = matchSdk(seg);
                if (sdk !== null && !matched.includes(sdk)) {
                    matched.push(sdk);
                }
            }
        }
    }
    return matched;
}
/** Parse Cargo.toml — extract package names from [dependencies] section. */
async function scanCargoToml(filePath) {
    const content = await readFile(filePath, 'utf-8');
    const matched = [];
    let inDeps = false;
    for (const rawLine of content.split('\n')) {
        const line = rawLine.trim();
        if (line.startsWith('[')) {
            inDeps = line === '[dependencies]' || line === '[dev-dependencies]';
            continue;
        }
        if (!inDeps) {
            continue;
        }
        const eqIdx = line.indexOf('=');
        if (eqIdx === -1) {
            continue;
        }
        const pkgName = line.slice(0, eqIdx).trim();
        const sdk = matchSdk(pkgName);
        if (sdk !== null && !matched.includes(sdk)) {
            matched.push(sdk);
        }
    }
    return matched;
}
/** Attempt to read one dependency file and return matched SDKs; silently ignores errors. */
async function tryScanFile(filePath, scanner) {
    try {
        return await scanner(filePath);
    }
    catch {
        return [];
    }
}
/**
 * Detect GPAI (foundation model) SDK usage in a project directory.
 * Scans common dependency manifests across languages; best-effort — never throws.
 */
export async function detectGpaiUsage(projectPath) {
    const scanTasks = [
        tryScanFile(join(projectPath, 'package.json'), scanPackageJson),
        tryScanFile(join(projectPath, 'requirements.txt'), scanTextFile),
        tryScanFile(join(projectPath, 'pyproject.toml'), scanPyprojectToml),
        tryScanFile(join(projectPath, 'go.mod'), scanGoMod),
        tryScanFile(join(projectPath, 'Cargo.toml'), scanCargoToml),
        tryScanFile(join(projectPath, 'Gemfile'), scanTextFile),
    ];
    // Scan .gemspec files (best-effort glob via readdir)
    const results = await Promise.all(scanTasks);
    const allSdks = [];
    for (const sdks of results) {
        for (const sdk of sdks) {
            if (!allSdks.includes(sdk)) {
                allSdks.push(sdk);
            }
        }
    }
    const providers = [
        ...new Set(allSdks.map((sdk) => SDK_PROVIDER_MAP[sdk]).filter((p) => typeof p === 'string')),
    ];
    return {
        usesFoundationModels: allSdks.length > 0,
        detectedSdks: allSdks,
        providers,
    };
}
//# sourceMappingURL=gpai-detector.js.map