"use strict";
// backend/src/services/providerManager/providerService.ts
// SPDX-License-Identifier: AGPL-3.0-or-later
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProviderService = void 0;
const uuid_1 = require("../../utils/uuid");
const providerStore_1 = require("./providerStore");
const runtimeCapabilities_1 = require("./runtimeCapabilities");
const SENSITIVE_FIELDS = [
    'apiKey',
    'claudeApiKey',
    'claudeAuthToken',
    'openaiApiKey',
    'awsBearerToken',
    'awsAccessKeyId',
    'awsSecretAccessKey',
    'awsSessionToken',
];
function maskValue(value) {
    if (value.length <= 8)
        return '****';
    return `****${value.slice(-4)}`;
}
function maskConnection(conn) {
    const masked = { ...conn };
    for (const field of SENSITIVE_FIELDS) {
        const val = masked[field];
        if (typeof val === 'string' && val)
            masked[field] = maskValue(val);
    }
    return masked;
}
function isSensitiveCustomKey(key) {
    return /(?:api[_-]?key|auth(?:orization)?|bearer|token|secret|password|access[_-]?key|session[_-]?token|credential)/i
        .test(key);
}
function maskSensitiveRecordValues(record) {
    if (!record)
        return undefined;
    const masked = { ...record };
    for (const [key, value] of Object.entries(masked)) {
        if (value && isSensitiveCustomKey(key))
            masked[key] = maskValue(value);
    }
    return masked;
}
function maskCustom(custom) {
    if (!custom)
        return undefined;
    return {
        ...custom,
        headers: maskSensitiveRecordValues(custom.headers),
        envOverrides: maskSensitiveRecordValues(custom.envOverrides),
    };
}
function maskProvider(p) {
    return {
        ...p,
        connection: maskConnection(p.connection),
        custom: maskCustom(p.custom),
    };
}
class ProviderService {
    constructor(filePath) {
        this.store = new providerStore_1.ProviderStore(filePath);
        this.store.load();
    }
    list(scope) {
        return this.store.getAll(scope).map(maskProvider);
    }
    get(id, scope) {
        const p = this.store.get(id, scope);
        return p ? maskProvider(p) : undefined;
    }
    getRaw(id, scope) {
        return this.store.get(id, scope);
    }
    create(input, scope) {
        if (!input.name?.trim())
            throw new Error('Provider name is required');
        if (!input.type)
            throw new Error('Provider type is required');
        if (!ProviderService.VALID_TYPES.includes(input.type)) {
            throw new Error(`Invalid provider type: ${input.type}. Must be one of: ${ProviderService.VALID_TYPES.join(', ')}`);
        }
        if (!input.models?.primary || !input.models?.light) {
            throw new Error('models.primary and models.light are required');
        }
        this.assertRuntimeSupported(input.type, input.connection.agentRuntime);
        const now = new Date().toISOString();
        const provider = {
            id: (0, uuid_1.uuidv4)(),
            name: input.name.trim(),
            category: input.category,
            type: input.type,
            isActive: false,
            createdAt: now,
            updatedAt: now,
            models: input.models,
            connection: input.connection,
            ...(input.tuning ? { tuning: input.tuning } : {}),
            ...(input.custom ? { custom: input.custom } : {}),
        };
        this.store.set(provider, scope);
        return provider;
    }
    update(id, input, scope) {
        const existing = this.store.get(id, scope);
        if (!existing)
            throw new Error(`Provider not found: ${id}`);
        const updated = {
            ...existing,
            updatedAt: new Date().toISOString(),
        };
        if (input.name !== undefined)
            updated.name = input.name.trim();
        if (input.models)
            updated.models = { ...existing.models, ...input.models };
        if (input.connection) {
            const merged = { ...existing.connection };
            for (const [key, val] of Object.entries(input.connection)) {
                if (val !== undefined && !String(val).startsWith('****')) {
                    merged[key] = val;
                }
            }
            if (input.connection.agentRuntime !== undefined) {
                this.assertRuntimeSupported(existing.type, merged.agentRuntime);
            }
            updated.connection = merged;
        }
        if (input.tuning !== undefined)
            updated.tuning = input.tuning ?? undefined;
        if (input.custom !== undefined)
            updated.custom = input.custom ?? undefined;
        this.store.set(updated, scope);
        return updated;
    }
    delete(id, scope) {
        const existing = this.store.get(id, scope);
        if (!existing)
            throw new Error(`Provider not found: ${id}`);
        if (existing.isActive)
            throw new Error('Cannot delete the active provider. Deactivate or switch first.');
        this.store.delete(id, scope);
    }
    rotateSecret(id, scope) {
        const existing = this.store.get(id, scope);
        if (!existing)
            throw new Error(`Provider not found: ${id}`);
        const version = this.store.rotateSecret(id, scope);
        if (version === undefined) {
            throw new Error('Secret rotation is only available for the enterprise provider store');
        }
        return version;
    }
    activate(id, scope) {
        const target = this.store.get(id, scope);
        if (!target)
            throw new Error(`Provider not found: ${id}`);
        const current = this.store.getActivePeer(id, scope);
        if (current && current.id !== id) {
            this.store.set({ ...current, isActive: false, updatedAt: new Date().toISOString() }, scope);
        }
        this.store.set({ ...target, isActive: true, updatedAt: new Date().toISOString() }, scope);
    }
    deactivateAll(scope) {
        const current = this.store.getActiveWriteScope(scope);
        if (current) {
            this.store.set({ ...current, isActive: false, updatedAt: new Date().toISOString() }, scope);
        }
    }
    switchAgentRuntime(id, runtime, scope) {
        if (runtime !== 'claude-agent-sdk' && runtime !== 'openai-agents-sdk') {
            throw new Error(`Invalid agent runtime: ${runtime}`);
        }
        return this.update(id, { connection: { agentRuntime: runtime } }, scope);
    }
    getEffectiveEnv(scope) {
        const active = this.store.getActive(scope);
        if (!active)
            return null;
        return this.toEnvVars(active);
    }
    getEnvForProvider(id, scope) {
        const provider = this.store.get(id, scope);
        if (!provider)
            return null;
        return this.toEnvVars(provider);
    }
    getRawEffectiveProvider(scope) {
        return this.store.getActive(scope);
    }
    getRawProvider(id, scope) {
        return this.store.get(id, scope);
    }
    resolveAgentRuntime(provider) {
        return (0, runtimeCapabilities_1.resolveProviderAgentRuntime)(provider);
    }
    supportsAgentRuntime(provider, runtime) {
        return (0, runtimeCapabilities_1.supportsAgentRuntimeType)(provider.type, runtime);
    }
    assertRuntimeSupported(type, runtime) {
        (0, runtimeCapabilities_1.assertAgentRuntimeSupported)(type, runtime);
    }
    resolveOpenAIProtocol(provider) {
        if (provider?.connection.openaiProtocol)
            return provider.connection.openaiProtocol;
        if (provider?.type === 'openai')
            return 'responses';
        return 'chat_completions';
    }
    getClaudeBaseUrl(provider, defaultBaseUrl) {
        return provider.connection.claudeBaseUrl || provider.connection.baseUrl || defaultBaseUrl;
    }
    applyClaudeAuth(env, provider) {
        if (provider.connection.claudeApiKey)
            env.ANTHROPIC_API_KEY = provider.connection.claudeApiKey;
        if (provider.connection.claudeAuthToken)
            env.ANTHROPIC_AUTH_TOKEN = provider.connection.claudeAuthToken;
        if (!provider.connection.claudeApiKey && !provider.connection.claudeAuthToken && provider.connection.apiKey) {
            if ((0, runtimeCapabilities_1.sharedKeyShouldUseClaudeAuthToken)(provider.type)) {
                env.ANTHROPIC_AUTH_TOKEN = provider.connection.apiKey;
            }
            else {
                env.ANTHROPIC_API_KEY = provider.connection.apiKey;
            }
        }
    }
    getOpenAIBaseUrl(provider, defaultBaseUrl) {
        return provider.connection.openaiBaseUrl || provider.connection.baseUrl || defaultBaseUrl;
    }
    getOpenAIApiKey(provider) {
        return provider.connection.openaiApiKey || provider.connection.apiKey;
    }
    applyOpenAIConnection(env, provider, defaultBaseUrl) {
        const baseUrl = this.getOpenAIBaseUrl(provider, defaultBaseUrl);
        const apiKey = this.getOpenAIApiKey(provider);
        if (baseUrl)
            env.OPENAI_BASE_URL = baseUrl;
        if (apiKey)
            env.OPENAI_API_KEY = apiKey;
        env.OPENAI_AGENTS_PROTOCOL = this.resolveOpenAIProtocol(provider);
    }
    toEnvVars(provider) {
        const env = {};
        const runtime = this.resolveAgentRuntime(provider);
        switch (provider.type) {
            case 'anthropic':
                this.applyClaudeAuth(env, provider);
                {
                    const baseUrl = this.getClaudeBaseUrl(provider);
                    if (baseUrl)
                        env.ANTHROPIC_BASE_URL = baseUrl;
                }
                break;
            case 'bedrock':
                if (provider.connection.useBedrock !== false) {
                    env.CLAUDE_CODE_USE_BEDROCK = '1';
                }
                if (provider.connection.awsRegion)
                    env.AWS_REGION = provider.connection.awsRegion;
                if (provider.connection.baseUrl)
                    env.ANTHROPIC_BEDROCK_BASE_URL = provider.connection.baseUrl;
                this.applyClaudeAuth(env, provider);
                if (provider.connection.awsBearerToken)
                    env.AWS_BEARER_TOKEN_BEDROCK = provider.connection.awsBearerToken;
                if (provider.connection.awsAccessKeyId)
                    env.AWS_ACCESS_KEY_ID = provider.connection.awsAccessKeyId;
                if (provider.connection.awsSecretAccessKey)
                    env.AWS_SECRET_ACCESS_KEY = provider.connection.awsSecretAccessKey;
                if (provider.connection.awsSessionToken)
                    env.AWS_SESSION_TOKEN = provider.connection.awsSessionToken;
                if (provider.connection.awsProfile)
                    env.AWS_PROFILE = provider.connection.awsProfile;
                break;
            case 'vertex':
                env.CLAUDE_CODE_USE_VERTEX = '1';
                if (provider.connection.gcpProjectId)
                    env.ANTHROPIC_VERTEX_PROJECT_ID = provider.connection.gcpProjectId;
                if (provider.connection.gcpRegion)
                    env.CLOUD_ML_REGION = provider.connection.gcpRegion;
                break;
            case 'deepseek':
            case 'glm':
            case 'qwen':
            case 'qwen_coding':
            case 'kimi_code':
            case 'kimi':
            case 'doubao':
            case 'minimax':
            case 'xiaomi':
            case 'tencent_token_plan':
            case 'tencent_coding_plan':
            case 'hunyuan':
            case 'qianfan':
            case 'stepfun':
            case 'siliconflow':
            case 'huawei':
                if (!(0, runtimeCapabilities_1.isDualSurfaceProviderType)(provider.type)) {
                    break;
                }
                if (runtime === 'openai-agents-sdk') {
                    env.SMARTPERFETTO_AGENT_RUNTIME = 'openai-agents-sdk';
                    this.applyOpenAIConnection(env, provider);
                }
                else {
                    const baseUrl = this.getClaudeBaseUrl(provider);
                    if (baseUrl)
                        env.ANTHROPIC_BASE_URL = baseUrl;
                    this.applyClaudeAuth(env, provider);
                }
                break;
            case 'openai':
                env.SMARTPERFETTO_AGENT_RUNTIME = 'openai-agents-sdk';
                this.applyOpenAIConnection(env, provider, 'https://api.openai.com/v1');
                break;
            case 'ollama':
                env.SMARTPERFETTO_AGENT_RUNTIME = 'openai-agents-sdk';
                this.applyOpenAIConnection(env, provider, 'http://localhost:11434/v1');
                env.OPENAI_API_KEY ?? (env.OPENAI_API_KEY = 'ollama');
                break;
            case 'custom':
                env.SMARTPERFETTO_AGENT_RUNTIME = runtime;
                if (runtime === 'openai-agents-sdk') {
                    this.applyOpenAIConnection(env, provider);
                }
                else {
                    this.applyClaudeAuth(env, provider);
                    const baseUrl = this.getClaudeBaseUrl(provider);
                    if (baseUrl)
                        env.ANTHROPIC_BASE_URL = baseUrl;
                }
                if (provider.custom?.envOverrides)
                    Object.assign(env, provider.custom.envOverrides);
                break;
        }
        env.SMARTPERFETTO_AGENT_RUNTIME = runtime;
        if (runtime === 'openai-agents-sdk') {
            env.OPENAI_MODEL = provider.models.primary;
            env.OPENAI_LIGHT_MODEL = provider.models.light;
        }
        else {
            env.CLAUDE_MODEL = provider.models.primary;
            env.CLAUDE_LIGHT_MODEL = provider.models.light;
            if (provider.models.subAgent)
                env.CLAUDE_SUB_AGENT_MODEL = provider.models.subAgent;
        }
        if (runtime === 'openai-agents-sdk') {
            if (provider.tuning?.maxTurns)
                env.OPENAI_MAX_TURNS = String(provider.tuning.maxTurns);
            if (provider.tuning?.fullPerTurnMs)
                env.OPENAI_FULL_PER_TURN_MS = String(provider.tuning.fullPerTurnMs);
            if (provider.tuning?.quickPerTurnMs)
                env.OPENAI_QUICK_PER_TURN_MS = String(provider.tuning.quickPerTurnMs);
            if (provider.tuning?.classifierTimeoutMs)
                env.OPENAI_CLASSIFIER_TIMEOUT_MS = String(provider.tuning.classifierTimeoutMs);
        }
        else {
            if (provider.tuning?.maxTurns)
                env.CLAUDE_MAX_TURNS = String(provider.tuning.maxTurns);
            if (provider.tuning?.effort)
                env.CLAUDE_EFFORT = provider.tuning.effort;
            if (provider.tuning?.maxBudgetUsd)
                env.CLAUDE_MAX_BUDGET_USD = String(provider.tuning.maxBudgetUsd);
            if (provider.tuning?.fullPerTurnMs)
                env.CLAUDE_FULL_PER_TURN_MS = String(provider.tuning.fullPerTurnMs);
            if (provider.tuning?.quickPerTurnMs)
                env.CLAUDE_QUICK_PER_TURN_MS = String(provider.tuning.quickPerTurnMs);
            if (provider.tuning?.verifierTimeoutMs)
                env.CLAUDE_VERIFIER_TIMEOUT_MS = String(provider.tuning.verifierTimeoutMs);
            if (provider.tuning?.classifierTimeoutMs)
                env.CLAUDE_CLASSIFIER_TIMEOUT_MS = String(provider.tuning.classifierTimeoutMs);
            if (provider.tuning?.enableSubAgents !== undefined)
                env.CLAUDE_ENABLE_SUB_AGENTS = String(provider.tuning.enableSubAgents);
            if (provider.tuning?.enableVerification !== undefined)
                env.CLAUDE_ENABLE_VERIFICATION = String(provider.tuning.enableVerification);
        }
        return env;
    }
}
exports.ProviderService = ProviderService;
ProviderService.VALID_TYPES = [
    'anthropic',
    'bedrock',
    'vertex',
    ...runtimeCapabilities_1.DUAL_SURFACE_PROVIDER_TYPES,
    'openai',
    'ollama',
    'custom',
];
//# sourceMappingURL=providerService.js.map