<!--
Copyright (C) 2026 Stefy Lanza <stefy@nexlab.net>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
-->
{% extends "base.html" %}

{% block title %}My Providers - AISBF Dashboard{% endblock %}

{% block content %}
<h2 style="margin-bottom: 30px;" data-i18n="user_providers_page.title">My Providers</h2>

{% if success %}
<div class="alert alert-success">{{ success }}</div>
{% endif %}

{% if error %}
<div class="alert alert-error">{{ error }}</div>
{% endif %}

<!-- New Provider Form (hidden by default) -->
<div id="new-provider-form" style="display: none; background: var(--bg-panel); border: 2px solid var(--color-link); border-radius: 8px; padding: 20px; margin-bottom: 20px;">
    <h3 style="margin: 0 0 20px 0; color: var(--color-link);" data-i18n="user_providers_page.add_new">Add New Provider</h3>
    
    <div class="form-group">
        <label data-i18n="providers.provider_key_label">Provider Key (unique identifier, e.g., "gemini", "openai", "kiro")</label>
        <input type="text" id="new-provider-key" placeholder="Enter provider key" style="width: 100%;">
        <small style="color: var(--color-muted); display: block; margin-top: 5px;">This will be used as the provider ID in the configuration and API endpoints</small>
    </div>
    
    <div class="form-group">
        <label data-i18n="providers.provider_type">Provider Type</label>
        <select id="new-provider-type" onchange="updateNewProviderDefaults()">
            <optgroup label="— Generic types —">
                <option value="openai">OpenAI-compatible (generic)</option>
                <option value="google">Google (Gemini API)</option>
                <option value="anthropic">Anthropic (API key)</option>
            </optgroup>
            <optgroup label="— OAuth2 / Special —">
                <option value="kiro">Kiro (Amazon Q Developer)</option>
                <option value="claude">Claude (OAuth2)</option>
                <option value="kilocode">Kilocode (OAuth2)</option>
                <option value="qwen">Qwen (OAuth2)</option>
                <option value="codex">Codex / ChatGPT (OAuth2)</option>
                <option value="coderai">CoderAI Broker</option>
                <option value="runpod">RunPod (Pod platform)</option>
            </optgroup>
            <optgroup label="— Pre-configured API providers —">
                <option value="abliteration">Abliteration AI</option>
                <option value="ai21">AI21 Labs</option>
                <option value="aihubmix">AIHubMix</option>
                <option value="aiml">AI/ML API</option>
                <option value="apertis">Apertis (Stima)</option>
                <option value="assemblyai_llm">AssemblyAI LLM Gateway</option>
                <option value="baseten">Baseten</option>
                <option value="cerebras">Cerebras</option>
                <option value="charity_engine">Charity Engine</option>
                <option value="chutes">Chutes AI</option>
                <option value="clarifai">Clarifai</option>
                <option value="cohere">Cohere</option>
                <option value="codestral">Codestral (Mistral Code)</option>
                <option value="cometapi">CometAPI</option>
                <option value="compactifai">CompactifAI</option>
                <option value="crusoe">Crusoe Cloud</option>
                <option value="dashscope">DashScope (Alibaba / Qwen)</option>
                <option value="deepinfra">DeepInfra</option>
                <option value="deepseek">DeepSeek</option>
                <option value="empower">Empower</option>
                <option value="featherless_ai">Featherless AI</option>
                <option value="fireworks_ai">Fireworks AI</option>
                <option value="friendliai">FriendliAI</option>
                <option value="galadriel">Galadriel</option>
                <option value="github_models">GitHub Models</option>
                <option value="gmi">GMI Serving</option>
                <option value="gradient_ai">Gradient AI</option>
                <option value="groq">Groq</option>
                <option value="helicone">Helicone AI Gateway</option>
                <option value="huggingface">HuggingFace Inference API</option>
                <option value="hyperbolic">Hyperbolic</option>
                <option value="jina_ai">Jina AI</option>
                <option value="lambda_ai">Lambda AI</option>
                <option value="llamagate">LlamaGate</option>
                <option value="maritalk">MariTalk (Maritaca AI)</option>
                <option value="meta_llama">Meta Llama API</option>
                <option value="minimax">MiniMax</option>
                <option value="mistral">Mistral AI</option>
                <option value="moonshot">Moonshot AI (Kimi)</option>
                <option value="morph">Morph</option>
                <option value="nano_gpt">Nano-GPT</option>
                <option value="nebius">Nebius AI Studio</option>
                <option value="nlp_cloud">NLP Cloud</option>
                <option value="novita">Novita AI</option>
                <option value="nscale">NScale</option>
                <option value="nvidia_nim">NVIDIA NIM</option>
                <option value="openrouter">OpenRouter</option>
                <option value="ovhcloud">OVHcloud AI Endpoints</option>
                <option value="perplexity">Perplexity AI</option>
                <option value="poe">Poe API</option>
                <option value="predibase">Predibase</option>
                <option value="publicai">PublicAI</option>
                <option value="sambanova">SambaNova</option>
                <option value="sarvam">Sarvam AI</option>
                <option value="scaleway">Scaleway</option>
                <option value="synthetic">Synthetic AI</option>
                <option value="together_ai">Together AI</option>
                <option value="veniceai">Venice AI</option>
                <option value="vercel_ai_gateway">Vercel AI Gateway</option>
                <option value="volcengine">VolcEngine (ByteDance Ark)</option>
                <option value="voyage">VoyageAI</option>
                <option value="wandb_inference">W&amp;B Inference</option>
                <option value="xai">xAI (Grok)</option>
                <option value="xiaomi_mimo">Xiaomi MiMo</option>
                <option value="zai">ZAI / 01.AI</option>
            </optgroup>
            <optgroup label="— Local / self-hosted —">
                <option value="ollama">Ollama</option>
                <option value="docker_model_runner">Docker Model Runner</option>
                <option value="infinity">Infinity</option>
                <option value="llamafile">Llamafile</option>
                <option value="lm_studio">LM Studio</option>
                <option value="oobabooga">Oobabooga (Text Gen WebUI)</option>
                <option value="tabbyapi">TabbyAPI</option>
                <option value="vllm">vLLM</option>
                <option value="xinference">Xorbits Inference</option>
            </optgroup>
            <optgroup label="— Cloud (configure endpoint) —">
                <option value="azure_openai">Azure OpenAI</option>
                <option value="databricks">Databricks Model Serving</option>
                <option value="heroku">Heroku AI</option>
                <option value="snowflake">Snowflake Cortex</option>
            </optgroup>
        </select>
        <small style="color: var(--color-muted); display: block; margin-top: 5px;">Select the type of provider to configure appropriate settings</small>
    </div>
    
    <div id="new-provider-type-info" style="background: var(--bg-panel); padding: 15px; border-radius: 5px; margin-bottom: 15px; border-left: 3px solid #4a9eff;">
        <p style="margin: 0; color: var(--color-muted);" id="new-provider-type-description">
            OpenAI-compatible provider. Uses standard API key authentication.
        </p>
    </div>

    <div id="new-provider-claude-warning" style="background: var(--alert-info-bg); border: 2px solid var(--alert-info-border); border-radius: 8px; padding: 20px; margin-bottom: 15px; display: none;">
        <h4 style="margin: 0 0 10px 0; color: var(--alert-info-fg);">⚠️ Important Notice for Claude Provider</h4>
        <p style="margin: 0; color: var(--color-text);">
            Claude.ai policies state that unofficial clients are not allowed. By using AISBF as a client in <strong>HTTP API / OAuth2 mode</strong>, you acknowledge that:
            <br><br>• While we do our best to mimic the claude-code CLI, using an unofficial client can lead to account suspension or cancellation
            <br>• Claude.ai detects prompt patterns to identify unofficial client usage, so it may not work with complex agents and prompts
            <br>• You use this software at your own risk and responsibility
            <br>• We are not affiliated with Anthropic and cannot guarantee compatibility or continued functionality
            <br><br>✅ <strong>CLI mode is different:</strong> when "Use Claude CLI mode" is enabled, AISBF proxies requests through the official <code>claude</code> binary using <code>claude -p</code>, which is the intended programmatic use of the official Anthropic CLI and is permitted by Claude's terms of service.
        </p>
    </div>

    <div id="new-provider-extension-prompt" style="display: none; background: #2a1a00; border: 2px solid #f59e0b; border-radius: 8px; padding: 15px; margin-bottom: 15px;">
        <h5 style="margin: 0 0 8px 0; color: #f59e0b;">🧩 Chrome Extension Required for Claude OAuth2</h5>
        <p style="margin: 0 0 10px 0; color: #fcd34d; font-size: 0.9em;">
            Claude's OAuth2 flow redirects your browser to <code style="background:#1a1000;padding:1px 4px;border-radius:3px;">http://localhost:54545/callback</code>
            to deliver the auth token. When you access AISBF remotely, that callback goes to <em>your machine</em>, not the server.
            <br><br>
            The AISBF Chrome Extension intercepts that callback and forwards it to this server automatically.
            Once installed, it self-configures — no setup needed. Reload this page after installing.
        </p>
        <a id="new-provider-extension-download" href="" download
           style="display: inline-block; padding: 8px 16px; background: #2d6a4f; color: #fff; border-radius: 4px; text-decoration: none; font-weight: 600;">
            ⬇️ Download Chrome Extension (.zip)
        </a>
        <small style="color: var(--color-muted); display: block; margin-top: 8px;">
            In Chrome: go to <strong>chrome://extensions</strong>, enable Developer Mode, click "Load unpacked", and select the extracted folder.
        </small>
    </div>

    <div style="display: flex; gap: 10px;">
        <button type="button" class="btn" onclick="confirmAddProvider()">Create Provider</button>
        <button type="button" class="btn btn-secondary" onclick="cancelAddProvider()">Cancel</button>
    </div>
</div>

<div style="margin-bottom: 15px; display: flex; gap: 10px; align-items: center;">
    <input type="text" id="providers-search" placeholder="Search providers by key or name..."
           oninput="providerSearchFilter = this.value; currentProviderPage = 0; renderProvidersList();"
           style="flex:1;padding:8px;border:1px solid var(--color-border);border-radius:3px;background:var(--bg-page);color:var(--color-text);font-size:14px;">
    <span id="providers-count" style="color:var(--color-muted);font-size:13px;white-space:nowrap;"></span>
</div>

<div id="providers-list" style="margin-bottom: 10px;">
    <!-- Provider list will be rendered here -->
</div>

<div id="providers-pagination" style="margin-bottom: 20px;"></div>

<button type="button" class="btn" id="add-provider-btn" onclick="showAddProviderForm()" style="margin-top: 10px;">Add Provider</button>
<div style="display: flex; gap: 10px; margin-top: 20px;">
    <button type="button" class="btn" onclick="saveProviders()">Save Configuration</button>
    <a href="{{ url_for(request, '/dashboard') }}" class="btn btn-secondary">Cancel</a>
</div>

<style>
.provider-item {
    animation: fadeIn 0.3s;
}

.provider-header:hover {
    background: var(--bg-accent);
}

@keyframes fadeIn {
    from { opacity: 0; transform: translateY(-10px); }
    to { opacity: 1; transform: translateY(0); }
}

.form-group {
    margin-bottom: 15px;
}

.form-group label {
    display: block;
    margin-bottom: 5px;
    font-weight: 500;
    color: var(--color-text);
}

.form-group input[type="text"],
.form-group input[type="password"],
.form-group input[type="number"],
.form-group select {
    width: 100%;
    padding: 8px;
    border: 1px solid var(--bg-accent);
    border-radius: 3px;
    font-size: 14px;
    background: var(--bg-page);
    color: var(--color-text);
}

.form-group input[type="checkbox"] {
    margin-right: 5px;
}
</style>
{% endblock %}

{% block extra_js %}
<script src="{{ url_for(request, '/dashboard/static/dragsort.js') }}"></script>
<script>
const CLAUDE_CLI_MODE = {{ 'true' if claude_cli_mode else 'false' }};
const IS_LOCAL_CLIENT = {{ 'true' if is_local_client else 'false' }};
const BASE_PATH = {{ (request.scope.get('root_path', '') or '') | tojson }};
// Marker used by the AISBF Chrome Extension to auto-detect this page and configure itself.
window.AISBF_PROVIDERS_PAGE = { serverUrl: window.location.origin + BASE_PATH };

// Global cache settings cache (pun intended)
let cacheSettings = [];

function formatVramMb(value) {
    if (value == null || value === '') return 'Unknown';
    const mb = Number(value);
    if (!Number.isFinite(mb)) return 'Unknown';
    if (mb >= 1024) return `${(mb / 1024).toFixed(1)} GB`;
    return `${Math.round(mb)} MB`;
}

function formatPerfNumber(value, digits = 1, suffix = '') {
    if (value == null || value === '') return 'Unknown';
    const num = Number(value);
    if (!Number.isFinite(num)) return 'Unknown';
    return `${num.toFixed(digits)}${suffix}`;
}

function renderCoderaiCollapsedStatus(provider) {
    const coderaiConfig = provider?.coderai_config || {};
    if (provider?.type !== 'coderai' || coderaiConfig.broker_mode === false) return '';
    const connected = !!coderaiConfig?.broker_session?.connected;
    const label = connected ? 'Online' : 'Offline';
    const bg = connected ? 'rgba(34,197,94,0.16)' : 'rgba(239,68,68,0.16)';
    const border = connected ? 'rgba(34,197,94,0.45)' : 'rgba(239,68,68,0.45)';
    const color = connected ? '#22c55e' : '#ef4444';
    const statusBadge = `<span style="display:inline-flex;align-items:center;gap:6px;padding:3px 9px;border-radius:999px;font-size:12px;font-weight:600;background:${bg};border:1px solid ${border};color:${color};"><span style="width:8px;height:8px;border-radius:50%;background:${color};display:inline-block;"></span>${label}</span>`;
    if (!connected) return statusBadge;
    const meta = coderaiConfig.broker_session?.metadata || {};
    return statusBadge + renderCoderaiHardwareChip(meta);
}

function renderCoderaiHardwareChip(meta) {
    const chipStyle = 'display:inline-flex;align-items:center;gap:4px;padding:3px 9px;border-radius:999px;font-size:12px;background:rgba(99,102,241,0.12);border:1px solid rgba(99,102,241,0.35);color:#a5b4fc;margin-left:4px;';
    const shortName = (name) => name.replace(/^NVIDIA\s+(GeForce\s+)?/i, '').replace(/^AMD\s+(Radeon\s+RX\s+)?/i, '');
    const gpus = Array.isArray(meta?.gpus) ? meta.gpus : [];
    // Show every GPU coderai reported, one chip per GPU, not just the first.
    if (gpus.length) {
        return gpus.map((gpu, index) => {
            const name = gpu?.name ? shortName(gpu.name) : `GPU ${gpu?.index ?? index}`;
            const parts = [escHtmlAttr(name)];
            if (gpu?.total_vram_mb != null) {
                parts.push(`${formatVramMb(gpu.available_vram_mb)} / ${formatVramMb(gpu.total_vram_mb)}`);
            }
            return `<span style="${chipStyle}">${parts.join(' · ')}</span>`;
        }).join('');
    }
    // No per-GPU detail reported – fall back to an aggregate VRAM chip.
    const totalVram = meta?.total_vram_mb;
    const freeVram = meta?.available_vram_mb;
    if (totalVram == null) return '';
    return `<span style="${chipStyle}">${formatVramMb(freeVram)} / ${formatVramMb(totalVram)}</span>`;
}

function renderGpuSummary(metadata) {
    const gpus = Array.isArray(metadata?.gpus) ? metadata.gpus : [];
    if (!gpus.length) return 'None reported';
    return gpus.map((gpu, index) => {
        const name = gpu?.name || `GPU ${gpu?.index ?? index}`;
        const free = formatVramMb(gpu?.available_vram_mb);
        const total = formatVramMb(gpu?.total_vram_mb);
        return `${name} (${free} free / ${total} total)`;
    }).join('; ');
}

function showToast(message, type) {
    const alertDiv = document.createElement('div');
    alertDiv.style.cssText = `
        position: fixed;
        top: 20px;
        left: 50%;
        transform: translateX(-50%);
        z-index: 10000;
        min-width: 400px;
        max-width: calc(100vw - 16px);
        padding: 20px 30px;
        border-radius: 6px;
        box-shadow: 0 4px 16px rgba(0,0,0,0.4);
        ${type === 'success' ? 'background: linear-gradient(135deg, #27ae60 0%, #2ecc71 100%); color: white; border: 2px solid #27ae60;' : ''}
        ${type === 'danger'  ? 'background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%); color: white; border: 2px solid #e74c3c;' : ''}
        ${type === 'warning' ? 'background: linear-gradient(135deg, #f39c12 0%, #e67e22 100%); color: white; border: 2px solid #f39c12;' : ''}
    `;
    const icon = type === 'success' ? 'fa-check-circle' : type === 'danger' ? 'fa-times-circle' : 'fa-exclamation-triangle';
    alertDiv.innerHTML = `<i class="fas ${icon}" style="font-size: 24px; margin-right: 10px; vertical-align: middle;"></i><span style="vertical-align: middle;">${message}</span>`;
    if (!document.getElementById('alertAnimations')) {
        const style = document.createElement('style');
        style.id = 'alertAnimations';
        style.textContent = `
            @keyframes slideDown { from { opacity:0; transform:translateX(-50%) translateY(-20px); } to { opacity:1; transform:translateX(-50%) translateY(0); } }
            @keyframes slideUp   { from { opacity:1; transform:translateX(-50%) translateY(0); } to { opacity:0; transform:translateX(-50%) translateY(-20px); } }
        `;
        document.head.appendChild(style);
    }
    alertDiv.style.animation = 'slideDown 0.3s ease-out';
    document.body.appendChild(alertDiv);
    setTimeout(() => {
        alertDiv.style.animation = 'slideUp 0.3s ease-out';
        setTimeout(() => alertDiv.remove(), 300);
    }, 4000);
}

function escHtmlAttr(s) {
    return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');
}

function formatBrokerTimestamp(ts) {
    if (!ts) return 'Never';
    const date = new Date(ts * 1000);
    if (Number.isNaN(date.getTime())) return 'Unknown';
    return date.toLocaleString();
}

async function apiCall(method, url, body) {
    const opts = { method, headers: { 'Content-Type': 'application/json' } };
    if (body !== undefined) opts.body = JSON.stringify(body);
    const r = await fetch(url, opts);
    return r.json();
}

let providersData = {};
const STUDIO_CAPABILITY_CHOICES = {{ studio_capability_choices | tojson }};
const STUDIO_ADAPTER_CHOICES = {{ studio_adapter_choices | tojson }};
const STUDIO_ADAPTER_PROFILE_CHOICES = {{ studio_adapter_profile_choices | tojson }};
let expandedProviders = new Set();
let currentProviderPage = 0;
const PROVIDERS_PAGE_SIZE = 10;
let providerSearchFilter = '';
let rawProviders = JSON.parse({{ user_providers_bootstrap_json | safe }});
let providerMasterOrder = [];
let _providerDS = null;

// Convert user providers format to the format expected by the UI
rawProviders.forEach(provider => {
    providersData[provider.provider_id] = provider.config;
});
providerMasterOrder = rawProviders.map(p => p.provider_id);

// Chunk size: 512KB chunks for maximum compatibility with restrictive proxies
const CHUNK_SIZE = 512 * 1024;

// ===== Provider Usage Indicators =====
const _usageCache = {};
const USAGE_POLL_INTERVAL = 15000;

function _providerSupportsUsage(key) {
    const p = providersData[key];
    return p && p.type === 'codex';
}

function isMarketReferenceProvider(key) {
    const provider = providersData[key];
    return !!(provider && provider.market_reference);
}

function _fmtSeconds(s) {
    if (!s || s <= 0) return '0s';
    const h = Math.floor(s/3600), m = Math.floor((s%3600)/60), sec = s%60;
    if (h > 0) return `${h}h${m > 0 ? ' '+m+'m' : ''}`;
    if (m > 0) return `${m}m${sec > 0 ? ' '+sec+'s' : ''}`;
    return `${sec}s`;
}

function _windowLabel(seconds) {
    if (!seconds || seconds <= 0) return 'Window';
    if (seconds < 120) return 'Session';
    const MIN = 60, HOUR = 3600, DAY = 86400, WEEK = 604800, MONTH = 2592000;
    if (seconds % MONTH === 0) return seconds === MONTH ? 'Monthly' : `${seconds/MONTH}mo`;
    if (seconds % WEEK  === 0) return seconds === WEEK  ? 'Weekly'  : `${seconds/WEEK}wk`;
    if (seconds % DAY   === 0) return seconds === DAY   ? 'Daily'   : `${seconds/DAY}d`;
    if (seconds % HOUR  === 0) return seconds === HOUR  ? 'Hourly'  : `${seconds/HOUR}h`;
    if (seconds % MIN   === 0) return `${seconds/MIN}min`;
    if (seconds >= MONTH * 0.9) return `${Math.round(seconds/MONTH)}mo`;
    if (seconds >= WEEK  * 0.9) return 'Weekly';
    if (seconds >= DAY)         return `${Math.round(seconds/DAY)}d`;
    if (seconds >= HOUR)        return `${Math.round(seconds/HOUR)}h`;
    return `${Math.round(seconds/MIN)}min`;
}

function _fmtResetAt(ts) {
    if (!ts) return '';
    const diff = Math.round((new Date(ts * 1000) - Date.now()) / 1000);
    if (diff <= 0) return 'now';
    return 'in ' + _fmtSeconds(diff);
}

function _usageBarHtml(pct, color) {
    const c = color || (pct >= 90 ? '#ef4444' : pct >= 70 ? '#f59e0b' : '#22c55e');
    return `<div style="height:6px;background:var(--color-border);border-radius:3px;overflow:hidden;min-width:60px;flex:1;">
        <div style="height:100%;width:${Math.min(pct,100)}%;background:${c};transition:width 0.4s;"></div>
    </div>`;
}

function _allWindows(rl) {
    const windows = [];
    if (rl.primary_window) windows.push(rl.primary_window);
    if (rl.secondary_window) windows.push(rl.secondary_window);
    if (Array.isArray(rl.additional_rate_limits)) windows.push(...rl.additional_rate_limits);
    return windows;
}

function _renderUsageCompact(usage) {
    if (!usage) return '<span style="color:var(--color-muted);font-size:11px;">No data</span>';
    const parts = [];
    const plan = usage.plan_type ? `<span style="font-size:10px;color:var(--color-muted);text-transform:uppercase;letter-spacing:0.5px;">${escHtmlAttr(usage.plan_type)}</span>` : '';
    const rl = usage.rate_limit;
    if (rl) {
        _allWindows(rl).forEach(w => {
            const pct = w.used_percent || 0;
            let countStr = '';
            if (w.num_requests_used != null && w.num_requests_limit != null) {
                countStr = ` (${w.num_requests_used.toLocaleString()}/${w.num_requests_limit.toLocaleString()})`;
            }
            parts.push(`<div style="display:flex;align-items:center;gap:4px;font-size:11px;">
                <span style="color:var(--color-muted);white-space:nowrap;">${_windowLabel(w.limit_window_seconds)}:</span>
                ${_usageBarHtml(pct)}
                <span style="color:var(--color-text);white-space:nowrap;">${pct}%${countStr}</span>
            </div>`);
        });
        if (rl.limit_reached) parts.push('<span style="color:#ef4444;font-size:11px;font-weight:600;">LIMIT REACHED</span>');
    }
    return `<div style="display:flex;flex-direction:column;gap:3px;min-width:140px;">${plan}${parts.join('')}</div>`;
}

function _renderUsageFull(usage) {
    if (!usage) return '<p style="color:var(--color-muted);font-size:13px;">Usage data unavailable.</p>';
    let html = '';
    if (usage.plan_type) {
        html += `<div style="margin-bottom:10px;"><span style="background:var(--color-link);color:#fff;padding:2px 8px;border-radius:3px;font-size:11px;text-transform:uppercase;letter-spacing:0.5px;">${escHtmlAttr(usage.plan_type)}</span></div>`;
    }
    const rl = usage.rate_limit;
    if (rl) {
        if (rl.limit_reached) html += `<div style="background:#7f1d1d;color:#fca5a5;padding:8px 12px;border-radius:4px;margin-bottom:10px;font-size:13px;">Rate limit reached${usage.rate_limit_reached_type ? ': ' + escHtmlAttr(usage.rate_limit_reached_type) : ''}</div>`;
        const renderWindow = (w) => {
            if (!w) return '';
            const pct = w.used_percent || 0;
            const color = pct >= 90 ? '#ef4444' : pct >= 70 ? '#f59e0b' : '#22c55e';
            const label = _windowLabel(w.limit_window_seconds);
            let countsHtml = '';
            if (w.num_requests_used != null && w.num_requests_limit != null) {
                countsHtml += `<span>Requests: ${w.num_requests_used.toLocaleString()} / ${w.num_requests_limit.toLocaleString()}</span>`;
            }
            if (w.num_tokens_used != null && w.num_tokens_limit != null) {
                countsHtml += `<span>Tokens: ${w.num_tokens_used.toLocaleString()} / ${w.num_tokens_limit.toLocaleString()}</span>`;
            }
            return `<div style="margin-bottom:12px;">
                <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px;">
                    <span style="font-size:13px;font-weight:600;">${label}</span>
                    <span style="font-size:12px;color:${color};font-weight:700;">${pct}%</span>
                </div>
                <div style="height:8px;background:var(--color-border);border-radius:4px;overflow:hidden;margin-bottom:4px;">
                    <div style="height:100%;width:${Math.min(pct,100)}%;background:${color};transition:width 0.4s;border-radius:4px;"></div>
                </div>
                <div style="display:flex;flex-wrap:wrap;gap:6px;font-size:11px;color:var(--color-muted);">
                    <span>Window: ${_fmtSeconds(w.limit_window_seconds)}</span>
                    <span>Resets ${_fmtResetAt(w.reset_at)}</span>
                    ${countsHtml}
                </div>
            </div>`;
        };
        _allWindows(rl).forEach(w => { html += renderWindow(w); });
    }
    const cr = usage.credits;
    if (cr) {
        const bal = cr.balance != null ? escHtmlAttr(String(cr.balance)) : (cr.unlimited ? 'Unlimited' : 'None');
        html += `<div style="font-size:12px;color:var(--color-muted);margin-top:8px;">Credits: ${bal}</div>`;
    }
    return html || '<p style="color:var(--color-muted);font-size:13px;">No usage data available.</p>';
}

async function fetchProviderUsage(key) {
    try {
        const resp = await fetch(`${BASE_PATH}/dashboard/api/provider/${encodeURIComponent(key)}/usage`);
        if (!resp.ok) return null;
        const data = await resp.json();
        if (data.supported === false) return {supported: false};
        _usageCache[key] = {data: data.usage, ts: Date.now()};
        return data.usage;
    } catch(e) { return null; }
}

function updateUsageBadge(key) {
    const badge = document.getElementById(`usage-badge-${key}`);
    if (badge) { const c = _usageCache[key]; if (c && c.data) badge.innerHTML = _renderUsageCompact(c.data); }
    const full = document.getElementById(`usage-full-${key}`);
    if (full) { const c = _usageCache[key]; if (c && c.data) full.innerHTML = _renderUsageFull(c.data); }
}

async function refreshAllUsage() {
    for (const key of Object.keys(providersData)) {
        if (!_providerSupportsUsage(key)) continue;
        const c = _usageCache[key];
        if (c && (Date.now() - c.ts) < USAGE_POLL_INTERVAL - 1000) continue;
        const data = await fetchProviderUsage(key);
        if (data && data !== null) updateUsageBadge(key);
    }
}

setInterval(refreshAllUsage, USAGE_POLL_INTERVAL);

// Generic chunked upload handler for all providers
async function uploadFileChunked(providerKey, fileType, file, configObject) {
    if (!file) return;
    
    // Ensure provider is expanded so status element exists
    if (!expandedProviders.has(providerKey)) {
        toggleProvider(providerKey);
    }
    
    // Map config object names to status element prefixes
    const statusPrefixMap = {
        'kiro_config': 'kiro',
        'kilo_config': 'kilo',
        'claude_config': 'claude',
        'qwen_config': 'qwen',
        'codex_config': 'codex'
    };
    
    const statusPrefix = statusPrefixMap[configObject] || configObject.replace('_config', '');
    const statusEl = document.getElementById(`${statusPrefix}-upload-status-${providerKey}`);
    statusEl.innerHTML = `<div style="color: var(--color-link);">${window.i18n.interpolate(window.i18n.t('providers.uploading_file'), {pct: 0})}</div>`;
    
    try {
        const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
        
        for (let chunkNumber = 1; chunkNumber <= totalChunks; chunkNumber++) {
            const start = (chunkNumber - 1) * CHUNK_SIZE;
            const end = Math.min(start + CHUNK_SIZE, file.size);
            const chunk = file.slice(start, end);
            
            const progress = Math.round((chunkNumber / totalChunks) * 100);
            statusEl.innerHTML = `<div style="color: var(--color-link);">${window.i18n.interpolate(window.i18n.t('providers.uploading_file'), {pct: progress})}</div>`;
            
            const formData = new FormData();
            formData.append('provider_key', providerKey);
            formData.append('file_type', fileType);
            formData.append('file_name', file.name);
            formData.append('total_size', file.size);
            formData.append('chunk_number', chunkNumber);
            formData.append('total_chunks', totalChunks);
            formData.append('file', chunk);
            
            const response = await fetch(BASE_PATH + '/dashboard/providers/upload-auth-file/chunk', {
                method: 'POST',
                body: formData
            });
            
            const result = await response.json();
            
            if (!result.success) {
                throw new Error(result.error || 'Upload failed');
            }
            
            // When upload is complete
            if (result.complete) {
                const savedPath = result.file_path || result.stored_filename || file.name;
                statusEl.innerHTML = `<div style="color: #4ade80; display: flex; align-items: center; gap: 6px;"><span style="font-size: 1.1em;">&#10003;</span> Saved: <code style="background: var(--bg-panel); padding: 1px 6px; border-radius: 3px; font-size: 0.9em;">${savedPath}</code></div>`;
                showToast(`File uploaded successfully: ${savedPath}`, 'success');

                // Update the config with the full file path (not just filename)
                if (!providersData[providerKey][configObject]) {
                    providersData[providerKey][configObject] = {};
                }
                // Use file_path from server response (tilde format: ~/.aisbf/...)
                providersData[providerKey][configObject][fileType] = result.file_path || result.stored_filename;
                setTimeout(() => renderProvidersList(), 2500);
                return;
            }
        }
    } catch (e) {
        statusEl.innerHTML = `<div style="color: #f87171;">${window.i18n.interpolate(window.i18n.t('providers.upload_failed'), {error: e.message})}</div>`;
        showToast(`Upload failed: ${e.message}`, 'danger');
    }
}

// Upload handler for Kiro credential files
async function uploadKiroFile(providerKey, fileType, file) {
    await uploadFileChunked(providerKey, fileType, file, 'kiro_config');
}

// Upload handler for Kilo credential files
async function uploadKiloFile(providerKey, file) {
    await uploadFileChunked(providerKey, 'credentials_file', file, 'kilo_config');
}

// Upload handler for Claude credential files
async function uploadClaudeFile(providerKey, file) {
    await uploadFileChunked(providerKey, 'credentials_file', file, 'claude_config');
}

// Upload handler for Claude CLI credentials file (.credentials.json from ~/.claude/)
async function uploadClaudeCliFile(providerKey, file) {
    if (!file) return;
    if (!expandedProviders.has(providerKey)) toggleProvider(providerKey);
    const statusEl = document.getElementById(`claude-cli-upload-status-${providerKey}`);
    if (!statusEl) return;
    statusEl.innerHTML = `<div style="color: var(--color-link);">${window.i18n.interpolate(window.i18n.t('providers.uploading_cli'), {pct: 0})}</div>`;
    try {
        const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
        for (let chunkNumber = 1; chunkNumber <= totalChunks; chunkNumber++) {
            const start = (chunkNumber - 1) * CHUNK_SIZE;
            const chunk = file.slice(start, Math.min(start + CHUNK_SIZE, file.size));
            const progress = Math.round((chunkNumber / totalChunks) * 100);
            statusEl.innerHTML = `<div style="color: var(--color-link);">${window.i18n.interpolate(window.i18n.t('providers.uploading_cli'), {pct: progress})}</div>`;
            const formData = new FormData();
            formData.append('provider_key', providerKey);
            formData.append('file_type', 'cli_credentials');
            formData.append('file_name', file.name);
            formData.append('total_size', file.size);
            formData.append('chunk_number', chunkNumber);
            formData.append('total_chunks', totalChunks);
            formData.append('file', chunk);
            const response = await fetch(BASE_PATH + '/dashboard/providers/upload-auth-file/chunk', { method: 'POST', body: formData });
            const result = await response.json();
            if (!result.success) throw new Error(result.error || 'Upload failed');
            if (result.complete) {
                statusEl.innerHTML = `<div style="color: #4ade80; display: flex; align-items: center; gap: 6px;"><span style="font-size: 1.1em;">&#10003;</span> ${window.i18n.interpolate(window.i18n.t('providers.cli_creds_saved'), {name: file.name})}</div>`;
                showToast(window.i18n.interpolate(window.i18n.t('providers.cli_creds_saved'), {name: file.name}), 'success');
                return;
            }
        }
    } catch (e) {
        statusEl.innerHTML = `<div style="color: #f87171;">${window.i18n.interpolate(window.i18n.t('providers.upload_failed'), {error: e.message})}</div>`;
        showToast(`Upload failed: ${e.message}`, 'danger');
    }
}

// Upload handler for Qwen credential files
async function uploadQwenFile(providerKey, file) {
    await uploadFileChunked(providerKey, 'credentials_file', file, 'qwen_config');
}

// Upload handler for Codex credential files
async function uploadCodexFile(providerKey, file) {
    await uploadFileChunked(providerKey, 'credentials_file', file, 'codex_config');
}

function _providerFilteredKeys() {
    return providerSearchFilter
        ? providerMasterOrder.filter(k => k.toLowerCase().includes(providerSearchFilter.toLowerCase()) ||
              (providersData[k] && (providersData[k].name || '').toLowerCase().includes(providerSearchFilter.toLowerCase())))
        : providerMasterOrder.filter(k => k in providersData);
}

function renderProvidersList() {
    const container = document.getElementById('providers-list');
    if (!container) return;

    const filteredKeys = _providerFilteredKeys();
    const total = filteredKeys.length;
    const totalPages = Math.max(1, Math.ceil(total / PROVIDERS_PAGE_SIZE));
    if (currentProviderPage >= totalPages) currentProviderPage = totalPages - 1;

    const pageKeys = filteredKeys.slice(currentProviderPage * PROVIDERS_PAGE_SIZE, (currentProviderPage + 1) * PROVIDERS_PAGE_SIZE);

    const countEl = document.getElementById('providers-count');
    if (countEl) countEl.textContent = window.i18n.interpolate(window.i18n.t(total !== 1 ? 'providers.provider_count_plural' : 'providers.provider_count_singular'), {n: total});

    const hasPrev = totalPages > 1 && currentProviderPage > 0;
    const hasNext = totalPages > 1 && currentProviderPage < totalPages - 1;

    container.innerHTML = `<div id="providers-list-page-prev" class="ds-sentinel${hasPrev ? ' ds-visible' : ''}">⬆ Drop here to move to previous page</div>`;

    if (pageKeys.length === 0) {
        container.innerHTML += `<p style="color:var(--color-muted);">${window.i18n.t('providers.no_providers')}</p>`;
    } else {
        pageKeys.forEach(key => {
            const provider = providersData[key];
            const isMarketReference = isMarketReferenceProvider(key);
            const providerItem = document.createElement('div');
            providerItem.className = 'provider-item';
            providerItem.dataset.sortKey = key;
            providerItem.style.cssText = 'border: 1px solid var(--color-border); margin-bottom: 10px; border-radius: 5px; background: var(--bg-page);';

            const isExpanded = expandedProviders.has(key);
            const safeKey = key.replace(/\\/g, '\\\\').replace(/'/g, "\\'");

            const supportsUsage = _providerSupportsUsage(key);
            const cachedUsage = _usageCache[key];
            const compactBadge = supportsUsage ? `<div id="usage-badge-${escHtmlAttr(key)}" onclick="event.stopPropagation();" style="margin-left:8px;">${cachedUsage && cachedUsage.data ? _renderUsageCompact(cachedUsage.data) : '<span style="color:var(--color-muted);font-size:11px;">loading…</span>'}</div>` : '';
            const coderaiCollapsedStatus = renderCoderaiCollapsedStatus(provider);

            providerItem.innerHTML = `
                <div class="provider-header" onclick="toggleProvider('${safeKey}')" style="padding: 15px; cursor: pointer; display: flex; justify-content: space-between; align-items: center; user-select: none; flex-wrap: wrap; gap: 8px;">
                    <div style="display: flex; align-items: center; gap: 10px; flex-wrap: wrap;">
                        <span class="drag-handle" onclick="event.stopPropagation();" title="Drag to reorder">⠿</span>
                        <span style="font-size: 18px;">${isExpanded ? '▼' : '▶'}</span>
                        <strong style="font-size: 16px;">${escHtmlAttr(key)}</strong>
                        <span style="color: var(--color-muted); font-size: 14px;">(${escHtmlAttr(provider.name || key)})</span>
                        ${isMarketReference ? '<span class="badge" data-market-reference="true" style="background: var(--color-link); color: white; padding: 2px 8px; border-radius: 999px; font-size: 12px;">Market-linked</span><span class="badge" style="background: var(--bg-accent); color: var(--color-text); padding: 2px 8px; border-radius: 999px; font-size: 12px;">Read-only</span>' : ''}
                        ${coderaiCollapsedStatus}
                        ${compactBadge}
                    </div>
                    ${isMarketReference ? '' : `<button type="button" class="btn btn-secondary" onclick="event.stopPropagation(); removeProvider('${safeKey}')" style="background: #dc3545; padding: 5px 15px;">${window.i18n.t('providers.remove')}</button>`}
                </div>
                <div id="provider-details-${escHtmlAttr(key)}" style="display: ${isExpanded ? 'block' : 'none'}; padding: 20px; border-top: 1px solid var(--color-border); background: var(--bg-panel);">
                </div>
            `;

            container.appendChild(providerItem);

            if (isExpanded) {
                renderProviderDetails(key);
            }
        });
    }

    const sentinelBot = document.createElement('div');
    sentinelBot.id = 'providers-list-page-next';
    sentinelBot.className = 'ds-sentinel' + (hasNext ? ' ds-visible' : '');
    sentinelBot.textContent = '⬇ Drop here to move to next page';
    container.appendChild(sentinelBot);

    const paginationEl = document.getElementById('providers-pagination');
    if (paginationEl) {
        if (totalPages <= 1) {
            paginationEl.innerHTML = '';
        } else {
            let btns = '';
            btns += `<button class="btn btn-secondary" onclick="goToProviderPage(${currentProviderPage - 1})" ${currentProviderPage === 0 ? 'disabled' : ''} style="padding:5px 12px;">← Prev</button>`;
            const start = Math.max(0, currentProviderPage - 2);
            const end = Math.min(totalPages - 1, currentProviderPage + 2);
            for (let p = start; p <= end; p++) {
                btns += `<button class="btn ${p === currentProviderPage ? '' : 'btn-secondary'}" onclick="goToProviderPage(${p})" style="padding:5px 10px;min-width:36px;">${p + 1}</button>`;
            }
            btns += `<button class="btn btn-secondary" onclick="goToProviderPage(${currentProviderPage + 1})" ${currentProviderPage >= totalPages - 1 ? 'disabled' : ''} style="padding:5px 12px;">Next →</button>`;
            paginationEl.innerHTML = `<div style="display:flex;align-items:center;gap:6px;flex-wrap:wrap;">
                ${btns}
                <span style="color:var(--color-muted);font-size:13px;margin-left:8px;">Page ${currentProviderPage + 1} of ${totalPages}</span>
            </div>`;
        }
    }

    if (!_providerDS) {
        _providerDS = new DragSort({
            containerId: 'providers-list',
            masterOrder: { value: providerMasterOrder },
            onReorder: function(newOrder) {
                providerMasterOrder = newOrder;
                renderProvidersList();
                fetch(BASE_PATH + '/dashboard/api/provider/reorder', {
                    method: 'POST', headers: {'Content-Type':'application/json'},
                    body: JSON.stringify({order: newOrder})
                }).catch(function(){});
            },
            pagination: {
                getCurrentPage: function() { return currentProviderPage; },
                getTotalPages: function() { return Math.max(1, Math.ceil(_providerFilteredKeys().length / PROVIDERS_PAGE_SIZE)); },
                goToPage: function(p) { goToProviderPage(p); },
                pageSize: PROVIDERS_PAGE_SIZE,
                getFilteredKeys: _providerFilteredKeys
            }
        });
    } else {
        _providerDS._opts.masterOrder.value = providerMasterOrder;
        _providerDS.attach();
    }
}

function goToProviderPage(page) {
    currentProviderPage = page;
    expandedProviders.clear();
    renderProvidersList();
    window.scrollTo(0, 0);
}

function toggleProvider(key) {
    if (expandedProviders.has(key)) {
        expandedProviders.delete(key);
    } else {
        expandedProviders.add(key);
        const filteredKeys = _providerFilteredKeys();
        const idx = filteredKeys.indexOf(key);
        if (idx >= 0) currentProviderPage = Math.floor(idx / PROVIDERS_PAGE_SIZE);
    }
    renderProvidersList();
}

function renderProviderDetails(key) {
    const container = document.getElementById(`provider-details-${key}`);
    const provider = providersData[key];
    if (provider && provider.market_reference) {
        container.innerHTML = `
            <div data-market-reference="true" style="border: 1px solid var(--color-border); border-radius: 6px; padding: 16px; background: var(--bg-page);">
                <div style="display:flex; gap:8px; flex-wrap:wrap; margin-bottom:10px;">
                    <span class="badge" data-market-reference="true" style="background: var(--color-link); color: white; padding: 2px 8px; border-radius: 999px; font-size: 12px;">Market-linked</span>
                    <span class="badge" style="background: var(--bg-accent); color: var(--color-text); padding: 2px 8px; border-radius: 999px; font-size: 12px;">Read-only</span>
                </div>
                <p style="margin:0 0 8px 0;"><strong>Source:</strong> ${escHtmlAttr(provider.owner_username || 'Unknown')} / ${escHtmlAttr(provider.source_id || '')}</p>
                <p style="margin:0;"><strong>Availability:</strong> ${escHtmlAttr(provider.availability || 'unavailable')}</p>
            </div>
        `;
        return;
    }
    const safeKey = key.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
    const isKiroProvider = provider.type === 'kiro';
    const isClaudeProvider = provider.type === 'claude';
    const isKiloProvider = provider.type === 'kilocode';
    const isQwenProvider = provider.type === 'qwen';
    const isCodexProvider = provider.type === 'codex';
    const isCoderAIProvider = provider.type === 'coderai';
    
    // Initialize kiro_config if this is a kiro provider and doesn't have it
    if (isKiroProvider && !provider.kiro_config) {
        provider.kiro_config = {
            region: 'us-east-1',
            refresh_token: '',
            profile_arn: '',
            client_id: '',
            client_secret: ''
        };
    }
    
    // Initialize claude_config if this is a claude provider and doesn't have it
    if (isClaudeProvider && !provider.claude_config) {
        provider.claude_config = {};
    }
    
    // Initialize kilo_config if this is a kilocode provider and doesn't have it
    if (isKiloProvider && !provider.kilo_config) {
        provider.kilo_config = {
            api_base: 'https://api.kilo.ai/api/gateway'
        };
    }
    
    // Initialize qwen_config if this is a qwen provider and doesn't have it
    if (isQwenProvider && !provider.qwen_config) {
        provider.qwen_config = {
            api_key: '',
            region: 'china-beijing',
            workspace_id: 'Default Workspace'
        };
    }
    
    // Initialize codex_config if this is a codex provider and doesn't have it
    if (isCodexProvider && !provider.codex_config) {
        provider.codex_config = {
            issuer: 'https://auth.openai.com'
        };
    }
    if (isCoderAIProvider && !provider.coderai_config) {
        provider.coderai_config = {
            transport: 'broker',
            broker_enabled: true,
            broker_mode: true,
            broker_preferred: true,
            discovery_enabled: true,
            client_id: key,
            bridge_path: '/coderai/ws',
            registration_path: '/coderai/register',
            registration_token: ''
        };
    }
    
    const kiroConfig = provider.kiro_config || {};
    const claudeConfig = provider.claude_config || {};
    const kiloConfig = provider.kilo_config || {};
    const qwenConfig = provider.qwen_config || {};
    const codexConfig = provider.codex_config || {};
    const coderaiConfig = provider.coderai_config || {};
    const brokerSession = coderaiConfig.broker_session || {};
    const brokerConnected = !!brokerSession.connected;
    const ownerLabel = brokerSession.owner_user_id == null ? 'Global admin' : `User #${brokerSession.owner_user_id}`;
    const brokerMetadata = brokerSession.metadata || {};
    const performance = brokerSession.performance || {};
    
    // Build authentication fields based on provider type
    let authFieldsHtml = '';
    if (isKiroProvider) {
        // Kiro-specific authentication fields
        authFieldsHtml = `
            <div style="background: var(--bg-panel); padding: 15px; border-radius: 5px; margin-bottom: 15px; border-left: 3px solid #4a9eff;">
                <h4 style="margin: 0 0 15px 0; color: var(--color-link);">${window.i18n.t('providers.kiro_auth_section')}</h4>
                <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
                    Choose one authentication method: Kiro IDE credentials (creds_file), kiro-cli database (sqlite_db), or direct credentials (refresh_token + client_id/secret).
                </small>
                
                <div class="form-group">
                    <label>${window.i18n.t('providers.kiro_aws_region')}</label>
                    <input type="text" value="${kiroConfig.region || 'us-east-1'}" onchange="updateKiroConfig('${key}', 'region', this.value)" placeholder="us-east-1">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">AWS region for Kiro API (default: us-east-1)</small>
                </div>
                
                <h5 style="margin: 20px 0 10px 0; color: var(--color-link);">Direct Credentials</h5>
                <div class="form-group">
                    <label>${window.i18n.t('providers.kiro_refresh_token')}</label>
                    <input type="password" value="${kiroConfig.refresh_token || ''}" onchange="updateKiroConfig('${key}', 'refresh_token', this.value)" placeholder="Enter refresh token">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">Kiro refresh token for direct authentication</small>
                </div>
                
                <div class="form-group">
                    <label>${window.i18n.t('providers.kiro_profile_arn')}</label>
                    <input type="text" value="${kiroConfig.profile_arn || ''}" onchange="updateKiroConfig('${key}', 'profile_arn', this.value)" placeholder="arn:aws:codewhisperer:us-east-1:...">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">AWS CodeWhisperer profile ARN (optional)</small>
                </div>
                
                <div class="form-group">
                    <label>${window.i18n.t('providers.kiro_client_id')}</label>
                    <input type="text" value="${kiroConfig.client_id || ''}" onchange="updateKiroConfig('${key}', 'client_id', this.value)" placeholder="OAuth client ID">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">OAuth client ID for AWS SSO OIDC authentication</small>
                </div>
                
                <div class="form-group">
                    <label>${window.i18n.t('providers.kiro_client_secret')}</label>
                    <input type="password" value="${kiroConfig.client_secret || ''}" onchange="updateKiroConfig('${key}', 'client_secret', this.value)" placeholder="OAuth client secret">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">OAuth client secret for AWS SSO OIDC authentication</small>
                </div>
                
                <h5 style="margin: 20px 0 10px 0; color: var(--color-link);">${window.i18n.t('providers.kiro_opt4')}</h5>
                <div class="form-group">
                    <label>${window.i18n.t('providers.kiro_upload_creds')}</label>
                    <input type="file" id="kiro-creds-file-${key}" accept=".json" onchange="uploadKiroFile('${key}', 'creds_file', this.files[0])">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">Upload Kiro IDE credentials JSON file</small>
                </div>
                <div class="form-group">
                    <label>${window.i18n.t('providers.kiro_upload_sqlite')}</label>
                    <input type="file" id="kiro-db-file-${key}" accept=".sqlite3,.db,.sqlite" onchange="uploadKiroFile('${key}', 'sqlite_db', this.files[0])">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">Upload kiro-cli SQLite database file</small>
                </div>
                <div id="kiro-upload-status-${key}" style="margin-top: 10px;"></div>
            </div>
        `;
    } else if (isKiloProvider) {
        // Kilocode authentication fields - supports both API key and OAuth2
        authFieldsHtml = `
            <div style="background: var(--bg-panel); padding: 15px; border-radius: 5px; margin-bottom: 15px; border-left: 3px solid #4a9eff;">
                <h4 style="margin: 0 0 15px 0; color: var(--color-link);">${window.i18n.t('providers.kilo_auth_section')}</h4>
                <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
                    Choose your authentication method: API Key (recommended for simplicity) or OAuth2 Device Authorization Grant.
                </small>
                
                <h5 style="margin: 20px 0 10px 0; color: var(--color-link);">Option 1: API Key / Auth Token</h5>
                <div class="form-group">
                    <label>${window.i18n.t('providers.api_key')}</label>
                    <input type="password" value="${provider.api_key || ''}" onchange="updateProvider('${key}', 'api_key', this.value)" placeholder="Enter your Kilocode API key">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">If provided, API key authentication will be used instead of OAuth2</small>
                </div>
                
                <h5 style="margin: 20px 0 10px 0; color: var(--color-link);">${window.i18n.t('providers.kilo_opt2')}</h5>
                <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
                    If no API key is provided, OAuth2 will be used automatically.
                </small>
                
                <div class="form-group">
                    <label>${window.i18n.t('providers.api_base')}</label>
                    <input type="text" value="${kiloConfig.api_base || 'https://api.kilo.ai/api/gateway'}" readonly style="background: var(--bg-panel); cursor: not-allowed;" placeholder="https://api.kilo.ai/api/gateway">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">Kilocode API base URL (fixed)</small>
                </div>
                
                <div style="margin-top: 15px;">
                    <button type="button" class="btn" onclick="authenticateKilo('${key}')" style="background: #4a9eff;">
                        🔐 ${window.i18n.t('providers.authenticate_claude').replace('Claude', 'Kilocode OAuth2')}
                    </button>
                    <button type="button" class="btn btn-secondary" onclick="checkKiloAuth('${key}')" style="margin-left: 10px;">
                        ${window.i18n.t('providers.check_status')}
                    </button>
                </div>
                
                <div id="kilo-auth-status-${key}" style="margin-top: 10px; padding: 10px; border-radius: 3px; display: none;">
                    <!-- Auth status will be displayed here -->
                </div>
                
                <h5 style="margin: 20px 0 10px 0; color: var(--color-link);">${window.i18n.t('providers.upload_credentials_title')}</h5>
                <div class="form-group">
                    <label>${window.i18n.t('providers.upload_credentials_label')}</label>
                    <input type="file" id="kilo-creds-file-${key}" accept=".json" onchange="uploadKiloFile('${key}', this.files[0])">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">Upload Kilocode OAuth2 credentials JSON file</small>
                </div>
                <div id="kilo-upload-status-${key}" style="margin-top: 10px;"></div>
            </div>
        `;
    } else if (isClaudeProvider) {
        // Claude OAuth2 authentication fields
        const extensionInstalled = !!window.aisbfOAuth2Extension;
        const oauth2Available = IS_LOCAL_CLIENT || extensionInstalled;

        authFieldsHtml = `
            <div style="background: var(--bg-panel); padding: 15px; border-radius: 5px; margin-bottom: 15px; border-left: 3px solid #4a9eff;">
                <h4 style="margin: 0 0 15px 0; color: var(--color-link);">Claude OAuth2 Authentication</h4>
                <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
                    Claude uses OAuth2 authentication. Click "Authenticate" to start the OAuth2 flow in your browser.
                </small>

                ${!IS_LOCAL_CLIENT && !extensionInstalled ? `
                <div style="background: #2a1a00; border: 2px solid #f59e0b; border-radius: 8px; padding: 15px; margin-bottom: 15px;">
                    <h5 style="margin: 0 0 8px 0; color: #f59e0b;">🧩 Chrome Extension Required</h5>
                    <p style="margin: 0 0 10px 0; color: #fcd34d; font-size: 0.9em;">
                        Claude's OAuth2 flow redirects your browser to <code style="background:#1a1000;padding:1px 4px;border-radius:3px;">http://localhost:54545/callback</code>
                        to deliver the auth token. When you access AISBF remotely, that localhost URL goes to
                        <em>your machine</em>, not the server — so the token never reaches AISBF.
                        <br><br>
                        The AISBF Chrome Extension intercepts that localhost callback in your browser and
                        forwards it to this server, completing the OAuth2 flow transparently.
                        Once installed, it self-configures automatically — no setup needed.
                        Reload this page after installing and the Authenticate button will appear.
                    </p>
                    <a href="${BASE_PATH}/dashboard/extension/download" download
                       style="display: inline-block; padding: 8px 16px; background: #2d6a4f; color: #fff; border-radius: 4px; text-decoration: none; font-weight: 600;">
                        ⬇️ Download Chrome Extension (.zip)
                    </a>
                    <small style="color: var(--color-muted); display: block; margin-top: 8px;">
                        In Chrome: go to <strong>chrome://extensions</strong>, enable Developer Mode, click "Load unpacked", and select the extracted folder.
                    </small>
                </div>
                ` : ''}

                ${oauth2Available ? `
                <div style="margin-top: 15px;">
                    <button type="button" class="btn" onclick="authenticateClaude('${key}')" style="background: #4a9eff;">
                        🔐 ${window.i18n.t('providers.authenticate_claude')}
                    </button>
                    <button type="button" class="btn btn-secondary" onclick="checkClaudeAuth('${key}')" style="margin-left: 10px;">
                        ${window.i18n.t('providers.check_status')}
                    </button>
                </div>

                <div id="claude-auth-status-${key}" style="margin-top: 10px; padding: 10px; border-radius: 3px; display: none;">
                    <!-- Auth status will be displayed here -->
                </div>

                <h5 style="margin: 20px 0 10px 0; color: var(--color-link);">${window.i18n.t('providers.upload_credentials_title')}</h5>
                <div class="form-group">
                    <label>${window.i18n.t('providers.upload_credentials_label')}</label>
                    <input type="file" id="claude-creds-file-${key}" accept=".json" onchange="uploadClaudeFile('${key}', this.files[0])">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">${window.i18n.t('providers.upload_credentials_desc')}</small>
                </div>
                <div id="claude-upload-status-${key}" style="margin-top: 10px;"></div>
                ` : `
                <div id="claude-auth-status-${key}" style="display:none;"></div>
                <div id="claude-upload-status-${key}" style="display:none;"></div>
                `}

                ${CLAUDE_CLI_MODE ? `
                <div style="margin-top: 20px; padding-top: 15px; border-top: 1px solid var(--color-border);">
                    <h5 style="margin: 0 0 8px 0; color: #4ade80;">${window.i18n.t('providers.cli_mode_active')}</h5>
                    <small style="color: #4ade80; display: block; margin-bottom: 14px;">
                        The claude CLI was detected at startup. When enabled, requests are piped
                        through the local claude binary instead of the HTTP API.
                    </small>

                    <div class="form-group" style="margin-bottom: 16px;">
                        <label style="display: flex; align-items: center; gap: 10px; cursor: pointer;">
                            <input type="checkbox"
                                   id="claude-use-cli-mode-${key}"
                                   ${claudeConfig.use_cli_mode ? 'checked' : ''}
                                   onchange="updateClaudeConfig('${key}', 'use_cli_mode', this.checked)"
                                   style="width: 18px; height: 18px; cursor: pointer; accent-color: #4ade80;">
                            <span style="color: var(--color-text); font-weight: 500;">${window.i18n.t('providers.use_cli_mode')}</span>
                        </label>
                        <small style="color: var(--color-muted); display: block; margin-top: 6px; padding-left: 28px;">
                            When checked and you have already authenticated via OAuth2 above,
                            your existing tokens will be used automatically — no separate file upload needed.
                            You can also upload an explicit <code style="background:var(--bg-panel);padding:1px 4px;border-radius:3px;">.credentials.json</code>
                            below to override.
                        </small>
                    </div>

                    <div class="form-group">
                        <label>${window.i18n.t('providers.upload_cli_creds')}</label>
                        <input type="file" id="claude-cli-creds-file-${key}" accept=".json" onchange="uploadClaudeCliFile('${key}', this.files[0])">
                        <small style="color: var(--color-muted); display: block; margin-top: 5px;">
                            Optional — upload a <code style="background:var(--bg-panel);padding:1px 4px;border-radius:3px;">~/.claude/.credentials.json</code>
                            to use a specific account instead of your OAuth2 tokens.
                        </small>
                    </div>
                    <div id="claude-cli-upload-status-${key}" style="margin-top: 10px;"></div>
                </div>
                ` : ''}
            </div>

            <div style="background: var(--alert-info-bg); border: 2px solid var(--alert-info-border); border-radius: 8px; padding: 20px; margin-bottom: 15px;">
                <h4 style="margin: 0 0 10px 0; color: var(--alert-info-fg);">⚠️ Important Notice</h4>
                <p style="margin: 0; color: var(--color-text);">
                    Claude.ai policies state that unofficial clients are not allowed. By using AISBF in <strong>HTTP API / OAuth2 mode</strong>, you acknowledge that:
                    <br><br>• While we do our best to mimic the claude-code CLI, using an unofficial client can lead to account suspension or cancellation
                    <br>• Claude.ai detects prompt patterns to identify unofficial client usage, so it may not work with complex agents and prompts
                    <br>• You use this software at your own risk and responsibility
                    <br>• We are not affiliated with Anthropic and cannot guarantee compatibility or continued functionality
                    <br><br>✅ <strong>CLI mode is different:</strong> when "Use Claude CLI mode" is enabled, AISBF proxies requests through the official <code style="background:#e8e0b0;padding:1px 4px;border-radius:3px;">claude</code> binary using <code style="background:#e8e0b0;padding:1px 4px;border-radius:3px;">claude -p</code>, which is the intended programmatic use of the official Anthropic CLI and is permitted by Claude's terms of service.
                </p>
            </div>
        `;
    } else if (isCoderAIProvider) {
        authFieldsHtml = `
            <div style="background: var(--bg-panel); padding: 15px; border-radius: 5px; margin-bottom: 15px; border-left: 3px solid #4a9eff;">
                <h4 style="margin: 0 0 15px 0; color: var(--color-link);">CoderAI Connection Mode</h4>
                <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
                    In broker mode, CoderAI connects inbound to AISBF and all traffic stays on that registered broker WebSocket. In direct mode, AISBF uses the configured endpoint as a normal OpenAI-compatible API.
                </small>

                <div class="form-group">
                    <label style="display:flex; align-items:center; gap:10px; cursor:pointer;">
                        <input type="checkbox" ${coderaiConfig.broker_mode !== false ? 'checked' : ''} onchange="updateCoderAIBrokerMode('${key}', this.checked)">
                        <span>Use broker mode</span>
                    </label>
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">Recommended for NATed CoderAI instances. Disable this only for directly reachable OpenAI-compatible HTTP endpoints.</small>
                </div>

                <div class="form-group" style="display:${coderaiConfig.broker_mode === false ? 'block' : 'none'};" id="coderai-api-key-group-${key}">
                    <label>API Token</label>
                    <input type="password" value="${provider.api_key || ''}" onchange="updateProvider('${key}', 'api_key', this.value)" placeholder="Enter CoderAI API token for direct HTTP mode">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">Used only when broker mode is disabled, so AISBF can call the endpoint as a normal OpenAI-compatible API.</small>
                </div>

                <div id="coderai-broker-group-${key}" style="display:${coderaiConfig.broker_mode === false ? 'none' : 'block'};">
                <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
                    Generate or rotate this registration token here, then copy it into the matching CoderAI configuration so it can authenticate when connecting to AISBF.
                </small>

                <div class="form-group">
                    <label>Client ID</label>
                    <input type="text" value="${coderaiConfig.client_id || key}" onchange="updateCoderAIConfig('${key}', 'client_id', this.value)" placeholder="workstation-01">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">Stable identifier AISBF uses to bind your provider to one specific client machine.</small>
                </div>

                <div class="form-group">
                    <label>Registration Token</label>
                    <div style="display:flex; gap:10px; align-items:center; flex-wrap:wrap;">
                        <input type="password" value="${coderaiConfig.registration_token || ''}" onchange="updateCoderAIConfig('${key}', 'registration_token', this.value)" placeholder="Provider-scoped token" style="flex:1; min-width:320px;">
                        <button type="button" class="btn btn-secondary" onclick="copyCoderAIToken('${safeKey}')">Copy Token</button>
                        <button type="button" class="btn btn-secondary" onclick="rotateCoderAIToken('${safeKey}')">Rotate Token</button>
                    </div>
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">Stored directly in this provider configuration for your account. Rotating invalidates the old token for future CoderAI broker registrations.</small>
                    <div id="coderai-token-status-${key}" style="margin-top:10px; font-size:13px; color: var(--color-muted);"></div>
                </div>

                <div class="form-group" style="margin-top:20px; padding:14px; border-radius:6px; background:${brokerConnected ? 'rgba(34,197,94,0.08)' : 'rgba(248,113,113,0.08)'}; border:1px solid ${brokerConnected ? 'rgba(34,197,94,0.35)' : 'rgba(248,113,113,0.35)'};">
                    <label>Broker Session Status</label>
                    <div style="font-size:14px; color:var(--color-text); line-height:1.6;">
                        <div><strong>Status:</strong> ${brokerConnected ? 'Connected' : 'Disconnected'}</div>
                        <div><strong>Owner:</strong> ${escHtmlAttr(ownerLabel)}</div>
                        <div><strong>Client ID:</strong> ${escHtmlAttr(brokerSession.client_id || coderaiConfig.client_id || key)}</div>
                        <div><strong>Connection State:</strong> ${escHtmlAttr(brokerSession.connection_state || (brokerConnected ? 'connected' : 'disconnected'))}</div>
                        <div><strong>Last Seen:</strong> ${escHtmlAttr(formatBrokerTimestamp(brokerSession.last_seen))}</div>
                        <div><strong>Connected At:</strong> ${escHtmlAttr(formatBrokerTimestamp(brokerSession.connected_at))}</div>
                        <div><strong>Remote Endpoint:</strong> ${escHtmlAttr(brokerSession.endpoint || 'Unknown')}</div>
                        <div><strong>Transport:</strong> ${escHtmlAttr(brokerSession.transport || 'broker')}</div>
                        <div><strong>GPU Count:</strong> ${escHtmlAttr(String((brokerMetadata.gpu_count ?? (Array.isArray(brokerMetadata.gpus) ? brokerMetadata.gpus.length : 0)) || '0'))}</div>
                        <div><strong>VRAM:</strong> ${escHtmlAttr(formatVramMb(brokerMetadata.available_vram_mb))} free / ${escHtmlAttr(formatVramMb(brokerMetadata.total_vram_mb))} total</div>
                        <div><strong>GPUs:</strong> ${escHtmlAttr(renderGpuSummary(brokerMetadata))}</div>
                        <div><strong>Avg Latency:</strong> ${escHtmlAttr(formatPerfNumber(performance.avg_latency_ms, 1, ' ms'))}</div>
                        <div><strong>Avg Throughput:</strong> ${escHtmlAttr(formatPerfNumber(performance.avg_tokens_per_second, 2, ' tok/s'))}</div>
                        <div><strong>Avg Tokens:</strong> ${escHtmlAttr(formatPerfNumber(performance.avg_total_tokens, 1))}</div>
                        <div><strong>Success Rate:</strong> ${escHtmlAttr(formatPerfNumber((performance.success_rate ?? 0) * 100, 1, '%'))}</div>
                        <div><strong>Samples:</strong> ${escHtmlAttr(String(performance.sample_count ?? 0))} / ${escHtmlAttr(String(performance.window_size ?? 100))}</div>
                        <div><strong>Studio Endpoints:</strong> ${escHtmlAttr((brokerSession.studio_endpoints || []).join(', ') || 'None advertised')}</div>
                    </div>
                </div>
                </div>

                <div id="coderai-direct-group-${key}" style="display:${coderaiConfig.broker_mode === false ? 'block' : 'none'}; margin-top:20px;">
                    <div class="form-group">
                        <label>Direct Transport</label>
                        <select onchange="updateCoderAITransportMode('${key}', this.value)">
                            <option value="http" ${coderaiConfig.transport === 'http' || coderaiConfig.broker_mode !== false ? 'selected' : ''}>HTTP</option>
                            <option value="websocket" ${coderaiConfig.transport === 'websocket' ? 'selected' : ''}>WebSocket</option>
                        </select>
                        <small style="color: var(--color-muted); display: block; margin-top: 5px;">Use direct mode only when AISBF can reach the CoderAI endpoint itself.</small>
                    </div>
                </div>
            </div>
        `;
    } else if (isQwenProvider) {
        // Qwen authentication fields - supports both API key and OAuth2
        authFieldsHtml = `
            <div style="background: #ff6b35; border: 2px solid #d63031; border-radius: 8px; padding: 20px; margin-bottom: 15px;">
                <h4 style="margin: 0 0 10px 0; color: #fff;">⚠️ Qwen OAuth2 Service Discontinued</h4>
                <p style="margin: 0; color: #fff;">
                    As of April 2026, Qwen has completely disabled OAuth2 subscriptions for Qwen Code.
                    OAuth2 tokens are no longer accepted by the DashScope API.
                    <br><br><strong>Please use API Key authentication instead.</strong>
                    <br><br>OAuth2 support is maintained in the code for potential future re-enablement, but it is currently non-functional.
                </p>
            </div>
            
            <div style="background: var(--bg-panel); padding: 15px; border-radius: 5px; margin-bottom: 15px; border-left: 3px solid #4a9eff;">
                <h4 style="margin: 0 0 15px 0; color: var(--color-link);">Qwen Authentication</h4>
                <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
                    Choose your authentication method: API Key (recommended for simplicity) or OAuth2 Device Authorization Grant.
                </small>

                <h5 style="margin: 20px 0 10px 0; color: var(--color-link);">${window.i18n.t('providers.kilo_opt1')}</h5>
                <div class="form-group">
                    <label>${window.i18n.t('providers.api_key')}</label>
                    <input type="password" value="${qwenConfig.api_key || ''}" onchange="updateQwenConfig('${key}', 'api_key', this.value)" placeholder="Enter your Qwen API key">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">If provided, API key authentication will be used instead of OAuth2</small>
                </div>

                <div id="qwen-region-config-${key}" style="display: ${qwenConfig.api_key ? 'block' : 'none'}; margin-top: 15px;">
                    <h5 style="margin: 20px 0 10px 0; color: var(--color-link);">Region Configuration (API Key Mode)</h5>
                    <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
                        Select your preferred region for API key authentication. Different regions have different endpoints.
                    </small>

                    <div class="form-group">
                        <label>Region</label>
                        <select onchange="updateQwenConfig('${key}', 'region', this.value)">
                            <option value="china-beijing" ${qwenConfig.region === 'china-beijing' || !qwenConfig.region ? 'selected' : ''}>China (Beijing) - dashscope.aliyuncs.com</option>
                            <option value="singapore" ${qwenConfig.region === 'singapore' ? 'selected' : ''}>Singapore - dashscope-intl.aliyuncs.com</option>
                            <option value="us-virginia" ${qwenConfig.region === 'us-virginia' ? 'selected' : ''}>US (Virginia) - dashscope-us.aliyuncs.com</option>
                            <option value="china-hongkong" ${qwenConfig.region === 'china-hongkong' ? 'selected' : ''}>China (Hong Kong) - cn-hongkong.dashscope.aliyuncs.com</option>
                            <option value="germany-frankfurt" ${qwenConfig.region === 'germany-frankfurt' ? 'selected' : ''}>Germany (Frankfurt) - Requires Workspace ID</option>
                        </select>
                        <small style="color: var(--color-muted); display: block; margin-top: 5px;">Select the region for your Qwen API endpoint</small>
                    </div>

                    <div id="qwen-workspace-config-${key}" style="display: ${qwenConfig.region === 'germany-frankfurt' ? 'block' : 'none'};">
                        <div class="form-group">
                            <label>${window.i18n.t('providers.workspace_id')}</label>
                            <input type="text" value="${qwenConfig.workspace_id || 'Default Workspace'}" onchange="updateQwenConfig('${key}', 'workspace_id', this.value)" placeholder="Default Workspace">
                            <small style="color: var(--color-muted); display: block; margin-top: 5px;">Workspace ID for Germany region (default: "Default Workspace")</small>
                        </div>
                    </div>
                </div>

                <h5 style="margin: 20px 0 10px 0; color: var(--color-link);">${window.i18n.t('providers.qwen_opt2_discontinued')}</h5>
                <small style="color: #ff6b35; display: block; margin-bottom: 15px;">
                    ⚠️ OAuth2 authentication is no longer functional. Qwen has discontinued this service. Use API Key instead.
                </small>

                <div style="margin-top: 15px;">
                    <button type="button" class="btn" onclick="authenticateQwen('${key}')" style="background: #4a9eff;">
                        🔐 Authenticate with Qwen OAuth2
                    </button>
                    <button type="button" class="btn btn-secondary" onclick="checkQwenAuth('${key}')" style="margin-left: 10px;">
                        ${window.i18n.t('providers.check_status')}
                    </button>
                </div>

                <div id="qwen-auth-status-${key}" style="margin-top: 10px; padding: 10px; border-radius: 3px; display: none;">
                    <!-- Auth status will be displayed here -->
                </div>

                <h5 style="margin: 20px 0 10px 0; color: var(--color-link);">${window.i18n.t('providers.upload_credentials_title')}</h5>
                <div class="form-group">
                    <label>${window.i18n.t('providers.upload_credentials_label')}</label>
                    <input type="file" id="qwen-creds-file-${key}" accept=".json" onchange="uploadQwenFile('${key}', this.files[0])">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">Upload Qwen OAuth2 credentials JSON file</small>
                </div>
                <div id="qwen-upload-status-${key}" style="margin-top: 10px;"></div>
            </div>
        `;
    } else if (isCodexProvider) {
        // Codex OAuth2 authentication fields
        authFieldsHtml = `
            <div style="background: var(--bg-panel); padding: 15px; border-radius: 5px; margin-bottom: 15px; border-left: 3px solid #4a9eff;">
                <h4 style="margin: 0 0 15px 0; color: var(--color-link);">Codex OAuth2 Authentication</h4>
                <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
                    Codex uses OAuth2 Device Authorization Grant (same protocol as OpenAI). Click "Authenticate" to start the OAuth2 flow.
                </small>
                
                <div class="form-group">
                    <label>${window.i18n.t('providers.api_key')} (${window.i18n.t('providers.optional')} - if provided, used instead of OAuth2)</label>
                    <input type="password" value="${provider.api_key || ''}" onchange="updateProvider('${key}', 'api_key', this.value)" placeholder="Enter OpenAI API key (optional)">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">If provided, API key authentication will be used instead of OAuth2</small>
                </div>
                
                <div class="form-group">
                    <label>${window.i18n.t('providers.oauth2_issuer_url')}</label>
                    <input type="text" value="${codexConfig.issuer || 'https://auth.openai.com'}" onchange="updateCodexConfig('${key}', 'issuer', this.value)" placeholder="https://auth.openai.com">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">OAuth2 issuer URL (default: https://auth.openai.com)</small>
                </div>
                
                <div style="margin-top: 15px;">
                    <button type="button" class="btn" onclick="authenticateCodex('${key}')" style="background: #4a9eff;">
                        🔐 Authenticate with Codex (Device Code)
                    </button>
                    <button type="button" class="btn btn-secondary" onclick="checkCodexAuth('${key}')" style="margin-left: 10px;">
                        ${window.i18n.t('providers.check_status')}
                    </button>
                </div>
                
                <div id="codex-auth-status-${key}" style="margin-top: 10px; padding: 10px; border-radius: 3px; display: none;">
                    <!-- Auth status will be displayed here -->
                </div>

                <h5 style="margin: 20px 0 10px 0; color: var(--color-link);">Usage &amp; Quota</h5>
                <div id="usage-full-${key}" style="margin-bottom: 10px;">
                    <p style="color:var(--color-muted);font-size:13px;">Loading usage data…</p>
                </div>
                <button type="button" class="btn btn-secondary" onclick="(async()=>{document.getElementById('usage-full-${key}').innerHTML='<p style=\\'color:var(--color-muted);font-size:13px;\\'>Refreshing…</p>';_usageCache['${key}']={ts:0};const d=await fetchProviderUsage('${key}');document.getElementById('usage-full-${key}').innerHTML=_renderUsageFull(d);})()" style="font-size:12px;padding:4px 10px;">↺ Refresh usage</button>

                <h5 style="margin: 20px 0 10px 0; color: var(--color-link);">${window.i18n.t('providers.upload_credentials_title')}</h5>
                <div class="form-group">
                    <label>${window.i18n.t('providers.upload_credentials_label')}</label>
                    <input type="file" id="codex-creds-file-${key}" accept=".json" onchange="uploadCodexFile('${key}', this.files[0])">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">Upload Codex OAuth2 credentials JSON file</small>
                </div>
                <div id="codex-upload-status-${key}" style="margin-top: 10px;"></div>
            </div>
        `;
    } else {
        // Standard API key authentication fields
        authFieldsHtml = `
            <div class="form-group">
                <label>
                    <input type="checkbox" ${provider.api_key_required ? 'checked' : ''} onchange="updateProvider('${key}', 'api_key_required', this.checked)">
                    API Key Required
                </label>
            </div>
            
            <div class="form-group">
                <label>${window.i18n.t('providers.api_key')}</label>
                <input type="password" value="${provider.api_key || ''}" onchange="updateProvider('${key}', 'api_key', this.value)" placeholder="Enter API key">
            </div>
        `;
    }
    
    container.innerHTML = `
        <div class="form-group">
            <label>${window.i18n.t('providers.provider_name')}</label>
            <input type="text" value="${provider.id}" onchange="updateProvider('${key}', 'id', this.value)" required>
        </div>
        
        <div class="form-group">
            <label>${window.i18n.t('providers.provider_name')}</label>
            <input type="text" value="${provider.name}" onchange="updateProvider('${key}', 'name', this.value)" required>
        </div>
        
        <div class="form-group">
            <label>${window.i18n.t('providers.api_base')}</label>
            <input type="text" value="${provider.endpoint}" onchange="updateProvider('${key}', 'endpoint', this.value)" ${(provider.type === 'kilocode' || provider.type === 'qwen' || provider.type === 'claude' || provider.type === 'codex') ? 'readonly style="background: var(--bg-panel); cursor: not-allowed;"' : ''} required>
            ${isKiroProvider ? '<small style="color: var(--color-muted); display: block; margin-top: 5px;">Typically: https://q.us-east-1.amazonaws.com</small>' : ''}
            ${provider.type === 'kilocode' ? '<small style="color: var(--color-muted); display: block; margin-top: 5px;">Fixed endpoint for Kilocode OAuth2 provider</small>' : ''}
            ${isQwenProvider ? '<small style="color: var(--color-muted); display: block; margin-top: 5px;">Fixed endpoint for Qwen OAuth2 provider (https://dashscope.aliyuncs.com/compatible-mode/v1)</small>' : ''}
            ${isClaudeProvider ? '<small style="color: var(--color-muted); display: block; margin-top: 5px;">Fixed endpoint for Claude OAuth2 provider (https://api.anthropic.com/v1)</small>' : ''}
            ${isCodexProvider ? '<small style="color: var(--color-muted); display: block; margin-top: 5px;">Fixed endpoint for Codex OAuth2 provider (https://api.openai.com/v1)</small>' : ''}
        </div>
        
        <div class="form-group">
            <label>${window.i18n.t('providers.provider_type')}</label>
            <select onchange="updateProviderType('${key}', this.value)" required>
                <optgroup label="Generic types">
                    <option value="openai" ${provider.type === 'openai' ? 'selected' : ''}>OpenAI</option>
                    <option value="google" ${provider.type === 'google' ? 'selected' : ''}>Google</option>
                    <option value="anthropic" ${provider.type === 'anthropic' ? 'selected' : ''}>Anthropic</option>
                </optgroup>
                <optgroup label="OAuth2 / Special">
                    <option value="kiro" ${provider.type === 'kiro' ? 'selected' : ''}>Kiro (Amazon Q Developer)</option>
                    <option value="claude" ${provider.type === 'claude' ? 'selected' : ''}>Claude (OAuth2)</option>
                    <option value="kilocode" ${provider.type === 'kilocode' ? 'selected' : ''}>Kilocode (OAuth2)</option>
                    <option value="qwen" ${provider.type === 'qwen' ? 'selected' : ''}>Qwen (OAuth2)</option>
                    <option value="codex" ${provider.type === 'codex' ? 'selected' : ''}>Codex (OpenAI OAuth2)</option>
                    <option value="coderai" ${provider.type === 'coderai' ? 'selected' : ''}>CoderAI</option>
                    <option value="runpod" ${provider.type === 'runpod' ? 'selected' : ''}>RunPod (Pod platform)</option>
                </optgroup>
                <optgroup label="Pre-configured API providers">
                    <option value="abliteration" ${provider.type === 'abliteration' ? 'selected' : ''}>Abliteration AI</option>
                    <option value="ai21" ${provider.type === 'ai21' ? 'selected' : ''}>AI21</option>
                    <option value="aiml" ${provider.type === 'aiml' ? 'selected' : ''}>AI/ML API</option>
                    <option value="aihubmix" ${provider.type === 'aihubmix' ? 'selected' : ''}>AIHubMix</option>
                    <option value="apertis" ${provider.type === 'apertis' ? 'selected' : ''}>Apertis (Stima)</option>
                    <option value="assemblyai_llm" ${provider.type === 'assemblyai_llm' ? 'selected' : ''}>AssemblyAI LLM</option>
                    <option value="baseten" ${provider.type === 'baseten' ? 'selected' : ''}>Baseten</option>
                    <option value="cerebras" ${provider.type === 'cerebras' ? 'selected' : ''}>Cerebras</option>
                    <option value="charity_engine" ${provider.type === 'charity_engine' ? 'selected' : ''}>Charity Engine</option>
                    <option value="chutes" ${provider.type === 'chutes' ? 'selected' : ''}>Chutes</option>
                    <option value="clarifai" ${provider.type === 'clarifai' ? 'selected' : ''}>Clarifai</option>
                    <option value="cohere" ${provider.type === 'cohere' ? 'selected' : ''}>Cohere</option>
                    <option value="codestral" ${provider.type === 'codestral' ? 'selected' : ''}>Codestral (Mistral)</option>
                    <option value="cometapi" ${provider.type === 'cometapi' ? 'selected' : ''}>CometAPI</option>
                    <option value="compactifai" ${provider.type === 'compactifai' ? 'selected' : ''}>CompactifAI</option>
                    <option value="crusoe" ${provider.type === 'crusoe' ? 'selected' : ''}>Crusoe Cloud</option>
                    <option value="dashscope" ${provider.type === 'dashscope' ? 'selected' : ''}>DashScope (Alibaba)</option>
                    <option value="deepinfra" ${provider.type === 'deepinfra' ? 'selected' : ''}>DeepInfra</option>
                    <option value="deepseek" ${provider.type === 'deepseek' ? 'selected' : ''}>DeepSeek</option>
                    <option value="empower" ${provider.type === 'empower' ? 'selected' : ''}>Empower</option>
                    <option value="featherless_ai" ${provider.type === 'featherless_ai' ? 'selected' : ''}>Featherless AI</option>
                    <option value="fireworks_ai" ${provider.type === 'fireworks_ai' ? 'selected' : ''}>Fireworks AI</option>
                    <option value="friendliai" ${provider.type === 'friendliai' ? 'selected' : ''}>Friendli AI</option>
                    <option value="galadriel" ${provider.type === 'galadriel' ? 'selected' : ''}>Galadriel</option>
                    <option value="github_models" ${provider.type === 'github_models' ? 'selected' : ''}>GitHub Models</option>
                    <option value="gmi" ${provider.type === 'gmi' ? 'selected' : ''}>GMI Serving</option>
                    <option value="gradient_ai" ${provider.type === 'gradient_ai' ? 'selected' : ''}>Gradient AI (DigitalOcean)</option>
                    <option value="groq" ${provider.type === 'groq' ? 'selected' : ''}>Groq</option>
                    <option value="helicone" ${provider.type === 'helicone' ? 'selected' : ''}>Helicone AI Gateway</option>
                    <option value="huggingface" ${provider.type === 'huggingface' ? 'selected' : ''}>HuggingFace Inference</option>
                    <option value="hyperbolic" ${provider.type === 'hyperbolic' ? 'selected' : ''}>Hyperbolic</option>
                    <option value="jina_ai" ${provider.type === 'jina_ai' ? 'selected' : ''}>Jina AI</option>
                    <option value="lambda_ai" ${provider.type === 'lambda_ai' ? 'selected' : ''}>Lambda AI</option>
                    <option value="llamagate" ${provider.type === 'llamagate' ? 'selected' : ''}>LlamaGate</option>
                    <option value="maritalk" ${provider.type === 'maritalk' ? 'selected' : ''}>MariTalk</option>
                    <option value="meta_llama" ${provider.type === 'meta_llama' ? 'selected' : ''}>Meta Llama API</option>
                    <option value="minimax" ${provider.type === 'minimax' ? 'selected' : ''}>MiniMax</option>
                    <option value="mistral" ${provider.type === 'mistral' ? 'selected' : ''}>Mistral AI</option>
                    <option value="moonshot" ${provider.type === 'moonshot' ? 'selected' : ''}>Moonshot AI</option>
                    <option value="morph" ${provider.type === 'morph' ? 'selected' : ''}>Morph LLM</option>
                    <option value="nano_gpt" ${provider.type === 'nano_gpt' ? 'selected' : ''}>NanoGPT</option>
                    <option value="nebius" ${provider.type === 'nebius' ? 'selected' : ''}>Nebius AI Studio</option>
                    <option value="nlp_cloud" ${provider.type === 'nlp_cloud' ? 'selected' : ''}>NLP Cloud</option>
                    <option value="novita" ${provider.type === 'novita' ? 'selected' : ''}>Novita AI</option>
                    <option value="nscale" ${provider.type === 'nscale' ? 'selected' : ''}>NScale</option>
                    <option value="nvidia_nim" ${provider.type === 'nvidia_nim' ? 'selected' : ''}>NVIDIA NIM</option>
                    <option value="openrouter" ${provider.type === 'openrouter' ? 'selected' : ''}>OpenRouter</option>
                    <option value="ovhcloud" ${provider.type === 'ovhcloud' ? 'selected' : ''}>OVHcloud AI</option>
                    <option value="perplexity" ${provider.type === 'perplexity' ? 'selected' : ''}>Perplexity AI</option>
                    <option value="poe" ${provider.type === 'poe' ? 'selected' : ''}>Poe</option>
                    <option value="predibase" ${provider.type === 'predibase' ? 'selected' : ''}>Predibase</option>
                    <option value="publicai" ${provider.type === 'publicai' ? 'selected' : ''}>PublicAI</option>
                    <option value="sambanova" ${provider.type === 'sambanova' ? 'selected' : ''}>SambaNova</option>
                    <option value="sarvam" ${provider.type === 'sarvam' ? 'selected' : ''}>Sarvam AI</option>
                    <option value="scaleway" ${provider.type === 'scaleway' ? 'selected' : ''}>Scaleway</option>
                    <option value="synthetic" ${provider.type === 'synthetic' ? 'selected' : ''}>Synthetic</option>
                    <option value="together_ai" ${provider.type === 'together_ai' ? 'selected' : ''}>Together AI</option>
                    <option value="veniceai" ${provider.type === 'veniceai' ? 'selected' : ''}>Venice AI</option>
                    <option value="vercel_ai_gateway" ${provider.type === 'vercel_ai_gateway' ? 'selected' : ''}>Vercel AI Gateway</option>
                    <option value="volcengine" ${provider.type === 'volcengine' ? 'selected' : ''}>VolcEngine (ByteDance)</option>
                    <option value="voyage" ${provider.type === 'voyage' ? 'selected' : ''}>Voyage AI</option>
                    <option value="wandb_inference" ${provider.type === 'wandb_inference' ? 'selected' : ''}>W&amp;B Inference</option>
                    <option value="xai" ${provider.type === 'xai' ? 'selected' : ''}>xAI (Grok)</option>
                    <option value="xiaomi_mimo" ${provider.type === 'xiaomi_mimo' ? 'selected' : ''}>Xiaomi MiMo</option>
                    <option value="zai" ${provider.type === 'zai' ? 'selected' : ''}>Z.AI</option>
                </optgroup>
                <optgroup label="Local / self-hosted">
                    <option value="ollama" ${provider.type === 'ollama' ? 'selected' : ''}>Ollama</option>
                    <option value="docker_model_runner" ${provider.type === 'docker_model_runner' ? 'selected' : ''}>Docker Model Runner</option>
                    <option value="infinity" ${provider.type === 'infinity' ? 'selected' : ''}>Infinity</option>
                    <option value="llamafile" ${provider.type === 'llamafile' ? 'selected' : ''}>Llamafile</option>
                    <option value="lm_studio" ${provider.type === 'lm_studio' ? 'selected' : ''}>LM Studio</option>
                    <option value="oobabooga" ${provider.type === 'oobabooga' ? 'selected' : ''}>Oobabooga (Text Gen WebUI)</option>
                    <option value="tabbyapi" ${provider.type === 'tabbyapi' ? 'selected' : ''}>TabbyAPI</option>
                    <option value="vllm" ${provider.type === 'vllm' ? 'selected' : ''}>vLLM</option>
                    <option value="xinference" ${provider.type === 'xinference' ? 'selected' : ''}>Xinference</option>
                </optgroup>
                <optgroup label="Cloud (configure endpoint)">
                    <option value="azure_openai" ${provider.type === 'azure_openai' ? 'selected' : ''}>Azure OpenAI</option>
                    <option value="databricks" ${provider.type === 'databricks' ? 'selected' : ''}>Databricks</option>
                    <option value="heroku" ${provider.type === 'heroku' ? 'selected' : ''}>Heroku AI</option>
                    <option value="snowflake" ${provider.type === 'snowflake' ? 'selected' : ''}>Snowflake Cortex</option>
                </optgroup>
            </select>
        </div>
        
        ${authFieldsHtml}
        
        <div style="background: var(--bg-panel); padding: 15px; border-radius: 5px; margin: 15px 0; border-left: 3px solid #4a9eff;">
            <h4 style="margin: 0 0 15px 0; color: var(--color-link);">${window.i18n.t('providers.pricing_section')}</h4>
            
            <div class="form-group">
                <label>
                    <input type="checkbox" ${provider.is_subscription ? 'checked' : ''} onchange="updateProvider('${key}', 'is_subscription', this.checked); togglePricingFields('${key}')">
                    Subscription-Based Provider (Free)
                </label>
                <small style="color: var(--color-muted); display: block; margin-top: 5px;">
                    If checked, this provider is subscription-based and costs will be calculated as $0. Usage is still tracked for analytics.
                </small>
            </div>
            
            <div id="pricing-fields-${key}" style="display: ${provider.is_subscription ? 'none' : 'block'};">
                <div class="form-group">
                    <label>${window.i18n.t('providers.price_prompt')}</label>
                    <input type="number" step="0.01" min="0" value="${provider.price_per_million_prompt || ''}" onchange="updateProvider('${key}', 'price_per_million_prompt', parseFloat(this.value) || null)" placeholder="e.g., 15.00">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">
                        Leave empty to use default pricing. Examples: OpenAI GPT-4: $10, Anthropic Claude: $15, Google Gemini: $1.25
                    </small>
                </div>
                
                <div class="form-group">
                    <label>${window.i18n.t('providers.price_completion')}</label>
                    <input type="number" step="0.01" min="0" value="${provider.price_per_million_completion || ''}" onchange="updateProvider('${key}', 'price_per_million_completion', parseFloat(this.value) || null)" placeholder="e.g., 75.00">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">
                        Leave empty to use default pricing. Examples: OpenAI GPT-4: $30, Anthropic Claude: $75, Google Gemini: $5.00
                    </small>
                </div>

                <div class="form-group">
                    <label>Pro Tier Monthly Cost</label>
                    <input type="number" step="0.01" min="0" value="${provider.premium_reference_monthly_cost || ''}" onchange="updateProvider('${key}', 'premium_reference_monthly_cost', parseFloat(this.value) || null)" placeholder="e.g., 20.00">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">
                        Used for free-tier equivalent savings estimates.
                    </small>
                </div>

                <div class="form-group">
                    <label>Free Tier Limit</label>
                    <input type="number" step="1" min="0" value="${provider.free_tier_limit || ''}" onchange="updateProvider('${key}', 'free_tier_limit', this.value ? parseInt(this.value, 10) : null)" placeholder="e.g., 50">
                </div>

                <div class="form-group">
                    <label>Free Tier Period</label>
                    <select onchange="updateProvider('${key}', 'free_tier_period', this.value || null)">
                        <option value="" ${!provider.free_tier_period ? 'selected' : ''}>Unspecified</option>
                        <option value="day" ${provider.free_tier_period === 'day' ? 'selected' : ''}>Daily</option>
                        <option value="week" ${provider.free_tier_period === 'week' ? 'selected' : ''}>Weekly</option>
                        <option value="month" ${provider.free_tier_period === 'month' ? 'selected' : ''}>Monthly</option>
                    </select>
                </div>

                <div class="form-group">
                    <label>Free Tier Limit Type</label>
                    <select onchange="updateProvider('${key}', 'free_tier_limit_type', this.value || null)">
                        <option value="" ${!provider.free_tier_limit_type ? 'selected' : ''}>Unspecified</option>
                        <option value="requests" ${provider.free_tier_limit_type === 'requests' ? 'selected' : ''}>Requests</option>
                        <option value="tokens" ${provider.free_tier_limit_type === 'tokens' ? 'selected' : ''}>Tokens</option>
                    </select>
                </div>

                <div class="form-group">
                    <label>Pro Tier Requests Daily</label>
                    <input type="number" step="1" min="0" value="${provider.pro_tier_requests_daily || ''}" onchange="updateProvider('${key}', 'pro_tier_requests_daily', this.value ? parseInt(this.value, 10) : null)" placeholder="Optional">
                </div>

                <div class="form-group">
                    <label>Pro Tier Requests Weekly</label>
                    <input type="number" step="1" min="0" value="${provider.pro_tier_requests_weekly || ''}" onchange="updateProvider('${key}', 'pro_tier_requests_weekly', this.value ? parseInt(this.value, 10) : null)" placeholder="Optional">
                </div>

                <div class="form-group">
                    <label>Pro Tier Requests Monthly</label>
                    <input type="number" step="1" min="0" value="${provider.pro_tier_requests_monthly || ''}" onchange="updateProvider('${key}', 'pro_tier_requests_monthly', this.value ? parseInt(this.value, 10) : null)" placeholder="Optional">
                </div>

                <div class="form-group">
                    <label>Pro Tier Tokens Daily</label>
                    <input type="number" step="1" min="0" value="${provider.pro_tier_tokens_daily || ''}" onchange="updateProvider('${key}', 'pro_tier_tokens_daily', this.value ? parseInt(this.value, 10) : null)" placeholder="Optional">
                </div>

                <div class="form-group">
                    <label>Pro Tier Tokens Weekly</label>
                    <input type="number" step="1" min="0" value="${provider.pro_tier_tokens_weekly || ''}" onchange="updateProvider('${key}', 'pro_tier_tokens_weekly', this.value ? parseInt(this.value, 10) : null)" placeholder="Optional">
                </div>

                <div class="form-group">
                    <label>Pro Tier Tokens Monthly</label>
                    <input type="number" step="1" min="0" value="${provider.pro_tier_tokens_monthly || ''}" onchange="updateProvider('${key}', 'pro_tier_tokens_monthly', this.value ? parseInt(this.value, 10) : null)" placeholder="Optional">
                </div>
            </div>
        </div>

        <div class="form-group">
            <label>${window.i18n.t('providers.rate_limit')}</label>
            <input type="number" value="${provider.rate_limit || 0}" onchange="updateProvider('${key}', 'rate_limit', parseFloat(this.value))" step="0.1">
            <small style="color: var(--color-muted); display: block; margin-top: 5px;">Time delay between requests to this provider</small>
        </div>
        
        <div class="form-group">
            <label>${window.i18n.t('providers.default_rate_limit_tpm')}</label>
            <input type="number" value="${provider.default_rate_limit_TPM || ''}" onchange="updateProvider('${key}', 'default_rate_limit_TPM', this.value ? parseInt(this.value) : null)" placeholder="Optional">
            <small style="color: var(--color-muted); display: block; margin-top: 5px;">Default token limit per minute for models in this provider</small>
        </div>
        
        <div class="form-group">
            <label>${window.i18n.t('providers.default_rate_limit_tph')}</label>
            <input type="number" value="${provider.default_rate_limit_TPH || ''}" onchange="updateProvider('${key}', 'default_rate_limit_TPH', this.value ? parseInt(this.value) : null)" placeholder="Optional">
            <small style="color: var(--color-muted); display: block; margin-top: 5px;">Default token limit per hour for models in this provider</small>
        </div>
        
        <div class="form-group">
            <label>${window.i18n.t('providers.default_rate_limit_tpd')}</label>
            <input type="number" value="${provider.default_rate_limit_TPD || ''}" onchange="updateProvider('${key}', 'default_rate_limit_TPD', this.value ? parseInt(this.value) : null)" placeholder="Optional">
            <small style="color: var(--color-muted); display: block; margin-top: 5px;">Default token limit per day for models in this provider</small>
        </div>
        
        <div class="form-group">
            <label>${window.i18n.t('providers.max_tokens')}</label>
            <input type="number" value="${provider.default_max_request_tokens || ''}" onchange="updateProvider('${key}', 'default_max_request_tokens', this.value ? parseInt(this.value) : null)" placeholder="Optional">
        </div>
        
        <div class="form-group">
            <label>${window.i18n.t('providers.context_size')}</label>
            <input type="number" value="${provider.default_context_size || ''}" onchange="updateProvider('${key}', 'default_context_size', this.value ? parseInt(this.value) : null)" placeholder="Optional">
        </div>
        
        <div class="form-group">
            <label>${window.i18n.t('providers.default_condense_context')}</label>
            <input type="number" value="${provider.default_condense_context || ''}" onchange="updateProvider('${key}', 'default_condense_context', this.value ? parseInt(this.value) : null)" placeholder="Optional (e.g., 80)">
        </div>
        
        <div class="form-group">
            <label>${window.i18n.t('providers.default_condense_method')}</label>
            <input type="text" value="${Array.isArray(provider.default_condense_method) ? provider.default_condense_method.join(', ') : (provider.default_condense_method || '')}" onchange="updateProviderCondenseMethod('${key}', this.value)" placeholder="e.g., semantic, conversational">
        </div>
        
        <div class="form-group">
            <label>
                <input type="checkbox" ${provider.nsfw ? 'checked' : ''} onchange="updateProvider('${key}', 'nsfw', this.checked)">
                NSFW
            </label>
        </div>
        
        <div class="form-group">
            <label>
                <input type="checkbox" ${provider.privacy ? 'checked' : ''} onchange="updateProvider('${key}', 'privacy', this.checked)">
                Privacy
            </label>
        </div>

        <h4 style="margin-top: 20px; margin-bottom: 10px;">Feature Overrides</h4>
        <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">Per-provider tri-state overrides. Inherit follows the global default.</small>
        ${triStateSelect('Context Condensation', provider.enable_context_condensation, `updateProviderTriState('${key}', 'enable_context_condensation', this.value)`)}
        ${triStateSelect('Response Cache', provider.enable_response_cache, `updateProviderTriState('${key}', 'enable_response_cache', this.value)`)}
        ${triStateSelect('Prompt Batching', provider.enable_prompt_batching, `updateProviderTriState('${key}', 'enable_prompt_batching', this.value)`)}
        ${triStateSelect('Prompt Security', provider.enable_prompt_security, `updateProviderTriState('${key}', 'enable_prompt_security', this.value)`)}
        ${triStateSelect('Context Lens Analytics', provider.enable_context_lens, `updateProviderTriState('${key}', 'enable_context_lens', this.value)`)}
        ${triStateSelect('Block High-Risk Prompts', provider.block_high_risk_prompts, `updateProviderTriState('${key}', 'block_high_risk_prompts', this.value)`)}
        ${triStateSelect('NSFW Classification', provider.enable_nsfw_classification, `updateProviderTriState('${key}', 'enable_nsfw_classification', this.value)`)}
        ${triStateSelect('Privacy Classification', provider.enable_privacy_classification, `updateProviderTriState('${key}', 'enable_privacy_classification', this.value)`)}

        <h4 style="margin-top: 20px; margin-bottom: 10px;">${window.i18n.t('providers.native_caching_section')}</h4>
        <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
            Provider-native caching features (Anthropic cache_control, Google Context Caching, OpenAI and Kilo-compatible APIs) for cost reduction.
        </small>

        <div class="form-group">
            <label>
                <input type="checkbox" ${provider.enable_native_caching ? 'checked' : ''} onchange="updateProvider('${key}', 'enable_native_caching', this.checked)">
                Enable Native Caching
            </label>
            <small style="color: var(--color-muted); display: block; margin-top: 5px;">Enable provider-native caching for cost reduction (50-70% savings for supported providers)</small>
        </div>

        <div class="form-group">
            <label>${window.i18n.t('providers.cache_ttl')}</label>
            <input type="number" value="${provider.cache_ttl || ''}" onchange="updateProvider('${key}', 'cache_ttl', this.value ? parseInt(this.value) : null)" placeholder="Optional (e.g., 3600)">
            <small style="color: var(--color-muted); display: block; margin-top: 5px;">Cache time-to-live in seconds (Google Context Caching only)</small>
        </div>

        <div class="form-group">
            <label>${window.i18n.t('providers.min_cacheable_tokens')}</label>
            <input type="number" value="${provider.min_cacheable_tokens || 1000}" onchange="updateProvider('${key}', 'min_cacheable_tokens', this.value ? parseInt(this.value) : 1000)" placeholder="1000">
            <small style="color: var(--color-muted); display: block; margin-top: 5px;">Minimum token count for content to be cacheable (default: 1000)</small>
        </div>

        <div class="form-group">
            <label>${window.i18n.t('providers.prompt_cache_key')}</label>
            <input type="text" value="${provider.prompt_cache_key || ''}" onchange="updateProvider('${key}', 'prompt_cache_key', this.value)" placeholder="Optional cache key for load balancer optimization">
            <small style="color: var(--color-muted); display: block; margin-top: 5px;">Optional cache key for OpenAI/Kilo load balancer routing optimization</small>
        </div>

        <h4 style="margin-top: 20px; margin-bottom: 10px;">${window.i18n.t('providers.models_section')}</h4>
        <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
            Configure specific models for this provider, or leave empty to automatically fetch all available models from the provider's API.
        </small>

        <div class="form-group" style="background: var(--bg-panel); padding: 15px; border-radius: 5px; margin-bottom: 15px; border-left: 3px solid #4a9eff;">
            <label>${window.i18n.t('providers.model_filter')}</label>
            <input type="text" value="${provider.model_filter || ''}" onchange="updateProvider('${key}', 'model_filter', this.value)" placeholder="e.g., free, pro, flash">
            <small style="color: var(--color-muted); display: block; margin-top: 5px;">
                When no models are manually configured, only expose models whose ID contains this filter word (case-insensitive wildcard matching).
                <br>Example: "free" will match models like "model-name-free", "free-model", "gpt-4-free-tier", etc.
            </small>
        </div>

        <div id="models-${key}"></div>
        <div style="display: flex; gap: 10px; margin-top: 10px;">
            <button type="button" class="btn" onclick="getModelsFromProvider('${key}')" style="background: #4a9eff;">
                🔄 ${window.i18n.t('providers.fetch_models')}
            </button>
            <button type="button" class="btn btn-secondary" onclick="addModel('${key}')">${window.i18n.t('providers.add_model')}</button>
        </div>
        <div id="get-models-status-${key}" style="margin-top: 10px;"></div>

        <div style="margin-top: 20px; padding-top: 15px; border-top: 1px solid var(--color-border); display: flex; gap: 10px; align-items: center; flex-wrap: wrap;">
            <button type="button" class="btn" onclick="saveProvider('${safeKey}')" style="background: #22c55e;">${window.i18n.t('providers.save_this')}</button>
            <span id="save-status-${key}" style="font-size: 13px; color: var(--color-muted);"></span>
        </div>
    `;

    renderModels(key);

    if (isCodexProvider) {
        const cached = _usageCache[key];
        if (cached && cached.data) {
            const fullEl = document.getElementById(`usage-full-${key}`);
            if (fullEl) fullEl.innerHTML = _renderUsageFull(cached.data);
        } else {
            fetchProviderUsage(key).then(data => {
                const fullEl = document.getElementById(`usage-full-${key}`);
                if (fullEl) fullEl.innerHTML = _renderUsageFull(data);
                updateUsageBadge(key);
            });
        }
    }
}

function renderModels(providerKey) {
    const container = document.getElementById(`models-${providerKey}`);
    const provider = providersData[providerKey];
    
    if (!provider.models || provider.models.length === 0) {
        container.innerHTML = `<p style="color: var(--color-muted);">${window.i18n.t('providers.no_models')}</p>`;
        return;
    }
    
    container.innerHTML = '';
    provider.models.forEach((model, index) => {
        const selectedStudioCaps = Array.isArray(model.studio_capabilities) ? model.studio_capabilities : [];
        const inferredCaps = Array.isArray(model.studio_capabilities) ? model.studio_capabilities : [];
        const studioCapabilityOptions = STUDIO_CAPABILITY_CHOICES.map(cap => {
            const selected = selectedStudioCaps.includes(cap) ? 'selected' : '';
            return `<option value="${escHtmlAttr(cap)}" ${selected}>${escHtmlAttr(cap)}</option>`;
        }).join('');
        const capabilitySource = model.studio_capability_source || 'heuristic';
        const selectedAdapter = model.studio_adapter_override || 'auto';
        const effectiveAdapter = model.studio_adapter || 'auto';
        const adapterOptions = STUDIO_ADAPTER_CHOICES.map(adapter => `<option value="${escHtmlAttr(adapter)}" ${selectedAdapter === adapter ? 'selected' : ''}>${escHtmlAttr(adapter)}</option>`).join('');
        const selectedAdapterProfile = model.studio_adapter_profile_override || 'auto';
        const effectiveAdapterProfile = model.studio_adapter_profile || 'auto';
        const adapterProfileOptions = STUDIO_ADAPTER_PROFILE_CHOICES.map(profile => `<option value="${escHtmlAttr(profile)}" ${selectedAdapterProfile === profile ? 'selected' : ''}>${escHtmlAttr(profile)}</option>`).join('');
        const capabilityNotes = Array.isArray(model.studio_capability_notes) && model.studio_capability_notes.length
            ? `<div style="margin-top:6px;color:var(--color-muted);font-size:12px;">${model.studio_capability_notes.map(note => escHtmlAttr(note)).join(' | ')}</div>`
            : '';
        const modelDiv = document.createElement('div');
        modelDiv.style.cssText = 'border: 1px solid var(--color-border); padding: 15px; margin-bottom: 10px; border-radius: 3px; background: var(--bg-page);';
        
        modelDiv.innerHTML = `
            <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
                <strong>${window.i18n.t('providers.model_label')} ${index + 1}</strong>
                <button type="button" class="btn btn-secondary" onclick="removeModel('${providerKey}', ${index})" style="background: #dc3545; padding: 5px 10px; font-size: 12px;">${window.i18n.t('providers.remove')}</button>
            </div>
            
            <div class="form-group">
                <label>${window.i18n.t('providers.model_name')}</label>
                <input type="text" value="${model.name}" onchange="updateModel('${providerKey}', ${index}, 'name', this.value)" required>
            </div>
            
            <div class="form-group">
                <label>${window.i18n.t('providers.rate_limit')}</label>
                <input type="number" value="${model.rate_limit || 0}" onchange="updateModel('${providerKey}', ${index}, 'rate_limit', parseFloat(this.value))" step="0.1">
            </div>
            
            <div class="form-group">
                <label>${window.i18n.t('providers.max_tokens')}</label>
                <input type="number" value="${model.max_request_tokens || ''}" onchange="updateModel('${providerKey}', ${index}, 'max_request_tokens', this.value ? parseInt(this.value) : null)" placeholder="Optional">
            </div>
            
            <div class="form-group">
                <label>${window.i18n.t('providers.context_size')}</label>
                <input type="number" value="${model.context_size || ''}" onchange="updateModel('${providerKey}', ${index}, 'context_size', this.value ? parseInt(this.value) : null)" placeholder="Optional">
            </div>
            
            <div class="form-group">
                <label>${window.i18n.t('providers.model_rate_limit_tpm')}</label>
                <input type="number" value="${model.rate_limit_TPM || ''}" onchange="updateModel('${providerKey}', ${index}, 'rate_limit_TPM', this.value ? parseInt(this.value) : null)" placeholder="Optional">
            </div>
            
            <div class="form-group">
                <label>${window.i18n.t('providers.model_rate_limit_tph')}</label>
                <input type="number" value="${model.rate_limit_TPH || ''}" onchange="updateModel('${providerKey}', ${index}, 'rate_limit_TPH', this.value ? parseInt(this.value) : null)" placeholder="Optional">
            </div>
            
            <div class="form-group">
                <label>${window.i18n.t('providers.model_rate_limit_tpd')}</label>
                <input type="number" value="${model.rate_limit_TPD || ''}" onchange="updateModel('${providerKey}', ${index}, 'rate_limit_TPD', this.value ? parseInt(this.value) : null)" placeholder="Optional">
            </div>
            
            <div class="form-group">
                <label>${window.i18n.t('providers.model_condense_context')}</label>
                <input type="number" value="${model.condense_context || ''}" onchange="updateModel('${providerKey}', ${index}, 'condense_context', this.value ? parseInt(this.value) : null)" placeholder="Optional (default: 80)">
            </div>
            
            <div class="form-group">
                <label>${window.i18n.t('providers.model_condense_method')}</label>
                <input type="text" value="${Array.isArray(model.condense_method) ? model.condense_method.join(', ') : (model.condense_method || '')}" onchange="updateModelCondenseMethod('${providerKey}', ${index}, this.value)" placeholder="e.g., semantic, conversational">
            </div>
            
            <div class="form-group">
                <label>
                    <input type="checkbox" ${model.nsfw ? 'checked' : ''} onchange="updateModel('${providerKey}', ${index}, 'nsfw', this.checked)">
                    NSFW
                </label>
            </div>
            
            <div class="form-group">
                <label>
                    <input type="checkbox" ${model.privacy ? 'checked' : ''} onchange="updateModel('${providerKey}', ${index}, 'privacy', this.checked)">
                    Privacy
                </label>
            </div>
            
            <div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--color-border);">
                <h5 style="margin: 0 0 10px 0;">Feature Overrides</h5>
                ${triStateSelect('Context Condensation', model.enable_context_condensation, `updateModelTriState('${providerKey}', ${index}, 'enable_context_condensation', this.value)`)}
                ${triStateSelect('Response Cache', model.enable_response_cache, `updateModelTriState('${providerKey}', ${index}, 'enable_response_cache', this.value)`)}
                ${triStateSelect('Prompt Batching', model.enable_prompt_batching, `updateModelTriState('${providerKey}', ${index}, 'enable_prompt_batching', this.value)`)}
                ${triStateSelect('Prompt Security', model.enable_prompt_security, `updateModelTriState('${providerKey}', ${index}, 'enable_prompt_security', this.value)`)}
                ${triStateSelect('Context Lens Analytics', model.enable_context_lens, `updateModelTriState('${providerKey}', ${index}, 'enable_context_lens', this.value)`)}
                ${triStateSelect('Block High-Risk Prompts', model.block_high_risk_prompts, `updateModelTriState('${providerKey}', ${index}, 'block_high_risk_prompts', this.value)`)}
                ${triStateSelect('NSFW Classification', model.enable_nsfw_classification, `updateModelTriState('${providerKey}', ${index}, 'enable_nsfw_classification', this.value)`)}
                ${triStateSelect('Privacy Classification', model.enable_privacy_classification, `updateModelTriState('${providerKey}', ${index}, 'enable_privacy_classification', this.value)`)}
            </div>

            <div class="form-group">
                <label>Studio capabilities</label>
                <select multiple size="8" onchange="updateModelStudioCapabilities('${providerKey}', ${index}, this)" style="width:100%;padding:8px;border:1px solid var(--bg-accent);border-radius:3px;background:var(--bg-page);color:var(--color-text);font-size:14px;">
                    ${studioCapabilityOptions}
                </select>
                <small style="color: var(--color-muted); display:block; margin-top:5px;">Used by Studio to decide which model can power chat, vision, image, audio, video, embeddings, and advanced tools.</small>
                <div style="margin-top:6px;font-size:12px;color:var(--color-muted);">Source: ${escHtmlAttr(capabilitySource)}${model.studio_capability_unknown ? ' · low confidence' : ''}</div>
                <div style="margin-top:6px;font-size:12px;color:var(--color-text);">Current: ${inferredCaps.length ? inferredCaps.map(cap => `<span style=&quot;display:inline-block;margin:2px 4px 0 0;padding:2px 6px;border-radius:999px;background:var(--bg-accent);&quot;>${escHtmlAttr(cap)}</span>`).join('') : '<span style="color:var(--color-muted);">none</span>'}</div>
                ${capabilityNotes}
            </div>

            <div class="form-group">
                <label>Studio adapter override</label>
                <select onchange="updateModelStudioAdapter('${providerKey}', ${index}, this.value)">
                    ${adapterOptions}
                </select>
                <small style="color: var(--color-muted); display:block; margin-top:5px;">Optional manual override for how AISBF adapts Studio workflow payloads for this model. Leave on auto to infer the best adapter from provider type and capabilities.</small>
                <div style="margin-top:6px;font-size:12px;color:var(--color-muted);">Effective adapter: ${escHtmlAttr(effectiveAdapter)}</div>
            </div>

            <div class="form-group">
                <label>Studio adapter profile override</label>
                <select onchange="updateModelStudioAdapterProfile('${providerKey}', ${index}, this.value)">
                    ${adapterProfileOptions}
                </select>
                <small style="color: var(--color-muted); display:block; margin-top:5px;">Optional provider-specific profile layered above adapter family inference. Use this when a specific configured provider behaves differently from the default family mapping.</small>
                <div style="margin-top:6px;font-size:12px;color:var(--color-muted);">Effective profile: ${escHtmlAttr(effectiveAdapterProfile)}</div>
            </div>
        `;
        
        container.appendChild(modelDiv);
    });
}



function showAddProviderForm() {
    document.getElementById('new-provider-form').style.display = 'block';
    document.getElementById('add-provider-btn').style.display = 'none';
    document.getElementById('new-provider-key').value = '';
    document.getElementById('new-provider-type').value = 'openai';
    updateNewProviderDefaults();
    document.getElementById('new-provider-key').focus();
}

function cancelAddProvider() {
    document.getElementById('new-provider-form').style.display = 'none';
    document.getElementById('add-provider-btn').style.display = 'inline-block';
}

function updateNewProviderDefaults() {
    const providerType = document.getElementById('new-provider-type').value;
    const descriptionEl = document.getElementById('new-provider-type-description');

    const descriptions = {
        'openai': 'OpenAI-compatible provider. Uses standard API key authentication. Endpoint: https://api.openai.com/v1',
        'google': 'Google AI provider (Gemini). Uses API key authentication. Endpoint: https://generativelanguage.googleapis.com/v1beta',
        'anthropic': 'Anthropic provider (Claude). Uses API key authentication. Endpoint: https://api.anthropic.com/v1',
        'ollama': 'Ollama local provider. No API key required by default. Endpoint: http://localhost:11434/api',
        'kiro': 'Kiro (Amazon Q Developer) provider. Uses Kiro credentials (IDE, CLI, or direct tokens). Endpoint: https://q.us-east-1.amazonaws.com',
        'claude': 'Claude Code provider. Uses OAuth2 authentication (browser-based login). Endpoint: https://api.anthropic.com/v1',
        'kilocode': 'Kilocode provider. Uses OAuth2 Device Authorization Grant. Endpoint: https://api.kilo.ai/api/gateway',
        'qwen': 'Qwen provider. Uses OAuth2 Device Authorization Grant or API key. Endpoint: https://dashscope.aliyuncs.com/compatible-mode/v1',
        'codex': 'Codex provider. Uses OAuth2 Device Authorization Grant (same protocol as OpenAI). Endpoint: https://api.openai.com/v1',
        'coderai': 'CoderAI provider. In broker mode, CoderAI connects inbound to AISBF using a provider-scoped registration token. In direct mode, AISBF calls it as an OpenAI-compatible endpoint. Default endpoint: http://127.0.0.1:11437',
        // Pre-configured API providers
        'abliteration': 'Abliteration AI — OpenAI-compatible inference API. Endpoint: https://api.abliteration.ai/v1',
        'ai21': 'AI21 Labs — OpenAI-compatible endpoint for Jamba and Jurassic models. Endpoint: https://api.ai21.com/studio/v1',
        'aiml': 'AI/ML API — unified gateway for many models. Endpoint: https://api.aimlapi.com/v1',
        'aihubmix': 'AIHubMix — OpenAI-compatible aggregator. Endpoint: https://aihubmix.com/v1',
        'apertis': 'Apertis (Stima Tech) — OpenAI-compatible inference. Endpoint: https://api.stima.tech/v1',
        'assemblyai_llm': 'AssemblyAI LLM Gateway — OpenAI-compatible endpoint. Endpoint: https://llm-gateway.assemblyai.com/v1',
        'baseten': 'Baseten — deploy and serve ML models. Endpoint: https://inference.baseten.co/v1',
        'cerebras': 'Cerebras — fast inference on Cerebras hardware. Endpoint: https://api.cerebras.ai/v1',
        'charity_engine': 'Charity Engine — distributed compute for AI. Endpoint: https://api.charityengine.services/remotejobs/v2/inference',
        'chutes': 'Chutes — decentralized AI inference. Endpoint: https://llm.chutes.ai/v1',
        'clarifai': 'Clarifai — AI platform with OpenAI-compatible API. Endpoint: https://api.clarifai.com/v2/ext/openai/v1',
        'cohere': 'Cohere — language models via OpenAI compatibility layer. Endpoint: https://api.cohere.ai/compatibility/v1',
        'codestral': 'Codestral — Mistral\'s code-focused model endpoint. Endpoint: https://codestral.mistral.ai/v1',
        'cometapi': 'CometAPI — OpenAI-compatible model aggregator. Endpoint: https://api.cometapi.com/v1',
        'compactifai': 'CompactifAI — efficient model inference. Endpoint: https://api.compactif.ai/v1',
        'crusoe': 'Crusoe Cloud — GPU cloud with managed inference. Endpoint: https://managed-inference-api-proxy.crusoecloud.com/v1',
        'dashscope': 'DashScope (Alibaba Cloud) — Qwen and other models. Endpoint: https://dashscope.aliyuncs.com/compatible-mode/v1',
        'deepinfra': 'DeepInfra — run open-source models at scale. Endpoint: https://api.deepinfra.com/v1/openai',
        'deepseek': 'DeepSeek — reasoning and chat models. Endpoint: https://api.deepseek.com/v1',
        'empower': 'Empower — AI inference platform. Endpoint: https://app.empower.dev/api/v1',
        'featherless_ai': 'Featherless AI — serverless open-source model hosting. Endpoint: https://api.featherless.ai/v1',
        'fireworks_ai': 'Fireworks AI — fast open-source model inference. Endpoint: https://api.fireworks.ai/inference/v1',
        'friendliai': 'Friendli AI — serverless model inference. Endpoint: https://api.friendli.ai/serverless/v1',
        'galadriel': 'Galadriel — decentralized AI inference. Endpoint: https://api.galadriel.com/v1',
        'github_models': 'GitHub Models — run AI models from the GitHub Marketplace. Endpoint: https://models.inference.ai.azure.com',
        'gmi': 'GMI Serving — OpenAI-compatible GPU inference. Endpoint: https://api.gmi-serving.com/v1',
        'gradient_ai': 'Gradient AI / DigitalOcean GenAI — managed inference. Endpoint: https://inference.do-ai.run/v1',
        'groq': 'Groq — ultra-fast LPU inference. Endpoint: https://api.groq.com/openai/v1',
        'helicone': 'Helicone AI Gateway — observability proxy for LLMs. Endpoint: https://ai-gateway.helicone.ai',
        'huggingface': 'HuggingFace Inference — run models from the HuggingFace Hub. Endpoint: https://api-inference.huggingface.co/v1',
        'hyperbolic': 'Hyperbolic — GPU cloud inference platform. Endpoint: https://api.hyperbolic.xyz/v1',
        'jina_ai': 'Jina AI — embeddings and reranking. Endpoint: https://api.jina.ai/v1',
        'lambda_ai': 'Lambda AI — GPU cloud with inference API. Endpoint: https://api.lambda.ai/v1',
        'llamagate': 'LlamaGate — open model inference gateway. Endpoint: https://api.llamagate.dev/v1',
        'maritalk': 'MariTalk — Brazilian Portuguese AI models. Endpoint: https://api.maritalk.com/v1',
        'meta_llama': 'Meta Llama API — official Meta inference endpoint. Endpoint: https://api.llama.com/compat/v1',
        'minimax': 'MiniMax — Chinese AI provider with global models. Endpoint: https://api.minimax.io/v1',
        'mistral': 'Mistral AI — Mistral and Mixtral models. Endpoint: https://api.mistral.ai/v1',
        'moonshot': 'Moonshot AI — long-context Chinese AI provider. Endpoint: https://api.moonshot.ai/v1',
        'morph': 'Morph LLM — OpenAI-compatible inference. Endpoint: https://api.morphllm.com/v1',
        'nano_gpt': 'NanoGPT — pay-per-token model marketplace. Endpoint: https://nano-gpt.com/api/v1',
        'nebius': 'Nebius AI Studio — GPU cloud with inference API. Endpoint: https://api.studio.nebius.ai/v1',
        'nlp_cloud': 'NLP Cloud — production-ready NLP models. Endpoint: https://api.nlpcloud.io/v1',
        'novita': 'Novita AI — affordable GPU inference. Endpoint: https://api.novita.ai/v3/openai',
        'nscale': 'NScale — high-performance GPU inference. Endpoint: https://inference.api.nscale.com/v1',
        'nvidia_nim': 'NVIDIA NIM — NVIDIA-optimized model microservices. Endpoint: https://integrate.api.nvidia.com/v1',
        'openrouter': 'OpenRouter — unified API for 100+ models across providers. Endpoint: https://openrouter.ai/api/v1',
        'ovhcloud': 'OVHcloud AI — European cloud AI endpoint. Endpoint: https://oai.endpoints.kepler.ai.cloud.ovh.net/v1',
        'perplexity': 'Perplexity AI — search-augmented language models. Endpoint: https://api.perplexity.ai',
        'poe': 'Poe — access many AI models through Poe. Endpoint: https://api.poe.com/v1',
        'predibase': 'Predibase — fine-tuned model serving. Endpoint: https://serving.app.predibase.com/v1',
        'publicai': 'PublicAI — open model inference. Endpoint: https://api.publicai.co/v1',
        'runpod': 'RunPod — serverless GPU cloud for AI workloads. Configure endpoint per pod.',
        'sambanova': 'SambaNova — enterprise AI inference platform. Endpoint: https://api.sambanova.ai/v1',
        'sarvam': 'Sarvam AI — Indian language AI models. Endpoint: https://api.sarvam.ai/v1',
        'scaleway': 'Scaleway — European cloud inference API. Endpoint: https://api.scaleway.ai/v1',
        'synthetic': 'Synthetic — AI inference platform. Endpoint: https://api.synthetic.new/openai/v1',
        'together_ai': 'Together AI — collaborative open-source model inference. Endpoint: https://api.together.xyz/v1',
        'veniceai': 'Venice AI — privacy-first AI inference. Endpoint: https://api.venice.ai/api/v1',
        'vercel_ai_gateway': 'Vercel AI Gateway — managed AI gateway from Vercel. Endpoint: https://ai-gateway.vercel.sh/v1',
        'volcengine': 'VolcEngine (ByteDance) — Doubao and other models. Endpoint: https://ark.cn-beijing.volces.com/api/v3',
        'voyage': 'Voyage AI — specialized embedding and reranking models. Endpoint: https://api.voyageai.com/v1',
        'wandb_inference': 'W&B Inference — Weights & Biases model inference. Endpoint: https://api.inference.wandb.ai/v1',
        'xai': 'xAI (Grok) — Grok models by Elon Musk\'s xAI. Endpoint: https://api.x.ai/v1',
        'xiaomi_mimo': 'Xiaomi MiMo — Xiaomi\'s open-source reasoning model. Endpoint: https://api.xiaomimimo.com/v1',
        'zai': 'Z.AI — OpenAI-compatible inference. Endpoint: https://api.z.ai/api/paas/v4',
        // Local / self-hosted
        'docker_model_runner': 'Docker Model Runner — run AI models via Docker Desktop. No API key required. Endpoint: http://localhost:12434/engines/llama.cpp/v1',
        'infinity': 'Infinity — fast embedding and reranking server. No API key required. Endpoint: http://localhost:8000/v1',
        'llamafile': 'Llamafile — self-contained executable LLM server. No API key required. Endpoint: http://localhost:8080/v1',
        'lm_studio': 'LM Studio — local GUI for running LLMs. No API key required. Endpoint: http://localhost:1234/v1',
        'oobabooga': 'Oobabooga (Text Generation WebUI) — local LLM frontend. No API key required. Endpoint: http://localhost:5000/v1',
        'tabbyapi': 'TabbyAPI — local ExLlamaV2-based inference server. No API key required. Endpoint: http://localhost:5000/v1',
        'vllm': 'vLLM — high-throughput local model server. No API key required. Endpoint: http://localhost:8000/v1',
        'xinference': 'Xinference — distributed model inference. No API key required. Endpoint: http://localhost:9997/v1',
        // Cloud (configure endpoint)
        'azure_openai': 'Azure OpenAI — Microsoft Azure-hosted OpenAI models. Requires your Azure resource endpoint (e.g. https://YOUR-RESOURCE.openai.azure.com/openai/v1).',
        'databricks': 'Databricks — AI/BI and model serving on Databricks. Requires your workspace endpoint.',
        'heroku': 'Heroku AI — managed inference on Heroku. Requires your Heroku inference endpoint.',
        'snowflake': 'Snowflake Cortex — AI inference within Snowflake. Requires your Snowflake account endpoint.',
    };

    descriptionEl.textContent = descriptions[providerType] || window.i18n.t('providers.standard_config');

    // Show/hide Claude warning
    const claudeWarningEl = document.getElementById('new-provider-claude-warning');
    if (providerType === 'claude') {
        claudeWarningEl.style.display = 'block';
    } else {
        claudeWarningEl.style.display = 'none';
    }

    // Show extension download prompt when Claude is selected on a non-local client without the extension
    const extensionPromptEl = document.getElementById('new-provider-extension-prompt');
    if (extensionPromptEl) {
        if (providerType === 'claude' && !IS_LOCAL_CLIENT && !window.aisbfOAuth2Extension) {
            const dlLink = document.getElementById('new-provider-extension-download');
            if (dlLink) dlLink.href = BASE_PATH + '/dashboard/extension/download';
            extensionPromptEl.style.display = 'block';
        } else {
            extensionPromptEl.style.display = 'none';
        }
    }
}

function togglePricingFields(key) {
    const provider = providersData[key];
    const pricingFields = document.getElementById(`pricing-fields-${key}`);
    if (pricingFields) {
        pricingFields.style.display = provider.is_subscription ? 'none' : 'block';
    }
}

function triStateValue(value) {
    if (value === true || value === 'enabled') return 'enabled';
    if (value === false || value === 'disabled') return 'disabled';
    return 'inherit';
}

function triStateSelect(label, value, onChange, helpText = '') {
    return `
        <div class="form-group">
            <label>${label}</label>
            <select onchange="${onChange}">
                <option value="inherit" ${triStateValue(value) === 'inherit' ? 'selected' : ''}>Inherit</option>
                <option value="enabled" ${triStateValue(value) === 'enabled' ? 'selected' : ''}>Enabled</option>
                <option value="disabled" ${triStateValue(value) === 'disabled' ? 'selected' : ''}>Disabled</option>
            </select>
            ${helpText ? `<small style="color: var(--color-muted); display:block; margin-top:5px;">${helpText}</small>` : ''}
        </div>
    `;
}

function updateTriState(target, field, value) {
    target[field] = value === 'inherit' ? null : value === 'enabled';
}

function updateProviderTriState(key, field, value) {
    updateTriState(providersData[key], field, value);
}

function updateModelTriState(providerKey, index, field, value) {
    updateTriState(providersData[providerKey].models[index], field, value);
}

// Helper function to format expiration time
function formatExpirationTime(expiresAt) {
    if (!expiresAt) return 'Unknown';
    
    const now = Math.floor(Date.now() / 1000);
    const secondsRemaining = expiresAt - now;
    
    if (secondsRemaining <= 0) {
        return 'Expired';
    }
    
    const days = Math.floor(secondsRemaining / 86400);
    const hours = Math.floor((secondsRemaining % 86400) / 3600);
    const minutes = Math.floor((secondsRemaining % 3600) / 60);
    const seconds = secondsRemaining % 60;
    
    if (days > 0) {
        return `${days} day${days > 1 ? 's' : ''} ${hours} hour${hours !== 1 ? 's' : ''}`;
    } else if (hours > 0) {
        return `${hours} hour${hours > 1 ? 's' : ''} ${minutes} minute${minutes !== 1 ? 's' : ''}`;
    } else if (minutes > 0) {
        return `${minutes} minute${minutes > 1 ? 's' : ''} ${seconds} second${seconds !== 1 ? 's' : ''}`;
    } else {
        return `${seconds} second${seconds > 1 ? 's' : ''}`;
    }
}

// OAuth authentication check functions
async function checkClaudeAuth(providerKey) {
    const statusDiv = document.getElementById(`claude-auth-status-${providerKey}`);
    if (!statusDiv) return;

    statusDiv.style.display = 'block';
    statusDiv.style.background = '#f0f0f0';
    statusDiv.style.color = '#333';
    statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.checking_auth'), {provider: 'Claude'});

    try {
        const response = await fetch(`${BASE_PATH}/dashboard/providers/${providerKey}/auth/check`, {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json',
            }
        });

        const result = await response.json();

        if (response.ok && result.authenticated) {
            const expirationText = result.expires_at ? formatExpirationTime(result.expires_at) : 'Unknown';
            statusDiv.style.background = '#d4edda';
            statusDiv.style.color = '#155724';
            statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.auth_valid'), {provider: 'Claude', expiry: expirationText});
        } else {
            statusDiv.style.background = '#f8d7da';
            statusDiv.style.color = '#721c24';
            statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.auth_failed'), {provider: 'Claude', error: result.error || window.i18n.t('providers.not_authenticated')});
        }
    } catch (error) {
        statusDiv.style.background = '#f8d7da';
        statusDiv.style.color = '#721c24';
        statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.auth_error'), {provider: 'Claude', error: error.message});
    }
}

async function checkQwenAuth(providerKey) {
    const statusDiv = document.getElementById(`qwen-auth-status-${providerKey}`);
    if (!statusDiv) return;

    statusDiv.style.display = 'block';
    statusDiv.style.background = '#f0f0f0';
    statusDiv.style.color = '#333';
    statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.checking_auth'), {provider: 'Qwen'});

    try {
        const response = await fetch(`${BASE_PATH}/dashboard/providers/${providerKey}/auth/check`, {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json',
            }
        });

        const result = await response.json();

        if (response.ok && result.authenticated) {
            const expirationText = result.expires_at ? formatExpirationTime(result.expires_at) : 'Unknown';
            statusDiv.style.background = '#d4edda';
            statusDiv.style.color = '#155724';
            statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.auth_valid'), {provider: 'Qwen', expiry: expirationText});
        } else {
            statusDiv.style.background = '#f8d7da';
            statusDiv.style.color = '#721c24';
            statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.auth_failed'), {provider: 'Qwen', error: result.error || window.i18n.t('providers.not_authenticated')});
        }
    } catch (error) {
        statusDiv.style.background = '#f8d7da';
        statusDiv.style.color = '#721c24';
        statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.auth_error'), {provider: 'Qwen', error: error.message});
    }
}

async function checkCodexAuth(providerKey) {
    const statusDiv = document.getElementById(`codex-auth-status-${providerKey}`);
    if (!statusDiv) return;

    statusDiv.style.display = 'block';
    statusDiv.style.background = '#f0f0f0';
    statusDiv.style.color = '#333';
    statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.checking_auth'), {provider: 'Codex'});

    try {
        const response = await fetch(`${BASE_PATH}/dashboard/providers/${providerKey}/auth/check`, {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json',
            }
        });

        const result = await response.json();

        if (response.ok && result.authenticated) {
            const expirationText = result.expires_at ? formatExpirationTime(result.expires_at) : 'Unknown';
            statusDiv.style.background = '#d4edda';
            statusDiv.style.color = '#155724';
            statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.auth_valid'), {provider: 'Codex', expiry: expirationText});
        } else {
            statusDiv.style.background = '#f8d7da';
            statusDiv.style.color = '#721c24';
            statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.auth_failed'), {provider: 'Codex', error: result.error || window.i18n.t('providers.not_authenticated')});
        }
    } catch (error) {
        statusDiv.style.background = '#f8d7da';
        statusDiv.style.color = '#721c24';
        statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.auth_error'), {provider: 'Codex', error: error.message});
    }
}

async function checkKiloAuth(providerKey) {
    const statusDiv = document.getElementById(`kilo-auth-status-${providerKey}`);
    if (!statusDiv) return;

    statusDiv.style.display = 'block';
    statusDiv.style.background = '#f0f0f0';
    statusDiv.style.color = '#333';
    statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.checking_auth'), {provider: 'Kilocode'});

    try {
        const response = await fetch(`${BASE_PATH}/dashboard/providers/${providerKey}/auth/check`, {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json',
            }
        });

        const result = await response.json();

        if (response.ok && result.authenticated) {
            const expirationText = result.expires_at ? formatExpirationTime(result.expires_at) : 'Unknown';
            statusDiv.style.background = '#d4edda';
            statusDiv.style.color = '#155724';
            statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.auth_valid'), {provider: 'Kilocode', expiry: expirationText});
        } else {
            statusDiv.style.background = '#f8d7da';
            statusDiv.style.color = '#721c24';
            statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.auth_failed'), {provider: 'Kilocode', error: result.error || window.i18n.t('providers.not_authenticated')});
        }
    } catch (error) {
        statusDiv.style.background = '#f8d7da';
        statusDiv.style.color = '#721c24';
        statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.auth_error'), {provider: 'Kilocode', error: error.message});
    }
}

// OAuth authentication functions
async function authenticateClaude(key) {
    const statusEl = document.getElementById(`claude-auth-status-${key}`);
    statusEl.style.display = 'block';
    statusEl.style.background = 'var(--bg-panel)';
    statusEl.style.border = '1px solid #4a9eff';
    statusEl.innerHTML = '<p style="margin: 0; color: var(--color-link);">🔄 Starting OAuth2 authentication flow...</p>';
    
    try {
        const response = await fetch(BASE_PATH + '/dashboard/claude/auth/start', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                provider_key: key
            })
        });
        
        const data = await response.json();
        
        if (!data.success || !data.auth_url) {
            statusEl.style.background = '#402010';
            statusEl.style.border = '1px solid #ff4a4a';
            statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.interpolate(window.i18n.t('providers.auth_start_failed'), {error: data.error || ''})}</p>`;
            return;
        }
        
        statusEl.innerHTML = '<p style="margin: 0; color: var(--color-link);">🔄 Opening authentication window...</p>';
        
        // Open OAuth2 URL in popup window
        const authWindow = window.open(data.auth_url, 'claude-auth', 'width=600,height=700');
        
        statusEl.innerHTML = '<p style="margin: 0; color: var(--color-link);">🔄 Please complete authentication in the popup window...</p>';
        
        // Poll for completion
        let pollCount = 0;
        const maxPolls = 60;
        let isCompleting = false;
        
        await new Promise(resolve => setTimeout(resolve, 8000));
        
        const pollInterval = setInterval(async () => {
            pollCount++;
            
            try {
                const statusResponse = await fetch(BASE_PATH + '/dashboard/claude/auth/callback-status');
                const statusData = await statusResponse.json();
                
                if (statusData.received) {
                    clearInterval(pollInterval);
                    
                    if (authWindow && !authWindow.closed) {
                        authWindow.close();
                    }
                    
                    if (statusData.error) {
                        statusEl.style.background = '#402010';
                        statusEl.style.border = '1px solid #ff4a4a';
                        statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">✗ OAuth2 error: ${statusData.error}</p>`;
                        return;
                    }
                    
                    if (isCompleting) return;
                    isCompleting = true;
                    
                    await new Promise(resolve => setTimeout(resolve, 1000));
                    
                    try {
                        statusEl.innerHTML = '<p style="margin: 0; color: var(--color-link);">🔄 Completing authentication...</p>';
                        
                        const completeResponse = await fetch(BASE_PATH + '/dashboard/claude/auth/complete', {
                            method: 'POST',
                            headers: {
                                'Content-Type': 'application/json',
                            }
                        });
                        
                        const completeData = await completeResponse.json();
                        
                        if (completeData.success) {
                            statusEl.style.background = '#0f4020';
                            statusEl.style.border = '1px solid #4eff9e';
                            statusEl.innerHTML = '<p style="margin: 0; color: #4eff9e;">✓ Authentication successful! Credentials saved.</p>';
                        } else {
                            statusEl.style.background = '#402010';
                            statusEl.style.border = '1px solid #ff4a4a';
                            statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">✗ Authentication incomplete. ${completeData.error || 'Please try again.'}</p>`;
                        }
                    } catch (error) {
                        statusEl.style.background = '#402010';
                        statusEl.style.border = '1px solid #ff4a4a';
                        statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.interpolate(window.i18n.t('providers.auth_error_completing'), {error: error.message})}</p>`;
                    }
                }
            } catch (error) {
                console.error('Error checking callback status:', error);
            }
            
            if (pollCount >= maxPolls) {
                clearInterval(pollInterval);
                if (authWindow && !authWindow.closed) {
                    authWindow.close();
                }
                statusEl.style.background = '#402010';
                statusEl.style.border = '1px solid #ff4a4a';
                statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.t('providers.auth_timeout')}</p>`;
            }
        }, 2000);
        
    } catch (error) {
        statusEl.style.background = '#402010';
        statusEl.style.border = '1px solid #ff4a4a';
        statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.interpolate(window.i18n.t('providers.auth_generic_error'), {error: error.message})}</p>`;
    }
}

async function authenticateQwen(key) {
    const statusEl = document.getElementById(`qwen-auth-status-${key}`);
    statusEl.style.display = 'block';
    statusEl.style.background = 'var(--bg-panel)';
    statusEl.style.border = '1px solid #4a9eff';
    statusEl.innerHTML = '<p style="margin: 0; color: var(--color-link);">🔄 Starting Qwen OAuth2 Device Authorization flow...</p>';
    
    try {
        const response = await fetch(BASE_PATH + '/dashboard/qwen/auth/start', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                provider_key: key
            })
        });
        
        const data = await response.json();
        
        if (!data.success) {
            statusEl.style.background = '#402010';
            statusEl.style.border = '1px solid #ff4a4a';
            statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.interpolate(window.i18n.t('providers.auth_start_failed'), {error: data.error || ''})}</p>`;
            return;
        }
        
        statusEl.style.background = 'var(--bg-panel)';
        statusEl.style.border = '1px solid #4a9eff';
        statusEl.innerHTML = `
            <div style="margin: 0;">
                <p style="margin: 0 0 10px 0; color: var(--color-link); font-weight: bold;">🔐 Qwen Device Authorization</p>
                <p style="margin: 0 0 10px 0; color: var(--color-text);">
                    Please visit: <a href="${data.verification_uri}" target="_blank" style="color: #4eff9e; text-decoration: underline;">${data.verification_uri}</a>
                </p>
                <p style="margin: 0 0 10px 0; color: var(--color-text);">
                    Enter code: <strong style="color: #4eff9e; font-size: 18px; letter-spacing: 2px;">${data.user_code}</strong>
                </p>
                <p style="margin: 0; color: var(--color-muted); font-size: 13px;">
                    Waiting for authorization... (expires in ${Math.floor(data.expires_in / 60)} minutes)
                </p>
            </div>
        `;
        
        try {
            window.open(data.verification_uri, 'qwen-auth', 'width=600,height=700');
        } catch (e) {
            console.error('Could not open auth window:', e);
        }
        
        let pollCount = 0;
        const maxPolls = Math.floor(data.expires_in / data.interval);
        const pollInterval = setInterval(async () => {
            pollCount++;
            
            try {
                const pollResponse = await fetch(BASE_PATH + '/dashboard/qwen/auth/poll', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    }
                });
                
                const pollData = await pollResponse.json();
                
                if (pollData.status === 'completed') {
                    clearInterval(pollInterval);
                    statusEl.style.background = '#0f4020';
                    statusEl.style.border = '1px solid #4eff9e';
                    statusEl.innerHTML = `<p style="margin: 0; color: #4eff9e;">${window.i18n.interpolate(window.i18n.t('providers.auth_success'), {provider: 'Qwen'})}</p>`;
                } else if (pollData.status === 'denied') {
                    clearInterval(pollInterval);
                    statusEl.style.background = '#402010';
                    statusEl.style.border = '1px solid #ff4a4a';
                    statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.t('providers.auth_denied')}</p>`;
                } else if (pollData.status === 'expired') {
                    clearInterval(pollInterval);
                    statusEl.style.background = '#402010';
                    statusEl.style.border = '1px solid #ff4a4a';
                    statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.t('providers.auth_expired')}</p>`;
                }
                
            } catch (error) {
                console.error('Error polling Qwen auth:', error);
            }
            
            if (pollCount >= maxPolls) {
                clearInterval(pollInterval);
                statusEl.style.background = '#402010';
                statusEl.style.border = '1px solid #ff4a4a';
                statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.t('providers.auth_timeout')}</p>`;
            }
        }, data.interval * 1000);
        
    } catch (error) {
        statusEl.style.background = '#402010';
        statusEl.style.border = '1px solid #ff4a4a';
        statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.interpolate(window.i18n.t('providers.auth_generic_error'), {error: error.message})}</p>`;
    }
}

async function pollQwenAuth(providerKey, deviceCode) {
    // This function is no longer needed as polling is handled in authenticateQwen
}

async function authenticateCodex(key) {
    const statusEl = document.getElementById(`codex-auth-status-${key}`);
    statusEl.style.display = 'block';
    statusEl.style.background = 'var(--bg-panel)';
    statusEl.style.border = '1px solid #4a9eff';
    statusEl.innerHTML = '<p style="margin: 0; color: var(--color-link);">🔄 Starting Codex OAuth2 Device Authorization flow...</p>';
    
    try {
        const response = await fetch(BASE_PATH + '/dashboard/codex/auth/start', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                provider_key: key,
                issuer: providersData[key].codex_config?.issuer || 'https://auth.openai.com'
            })
        });
        
        const data = await response.json();
        
        if (!data.success) {
            statusEl.style.background = '#402010';
            statusEl.style.border = '1px solid #ff4a4a';
            statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.interpolate(window.i18n.t('providers.auth_start_failed'), {error: data.error || ''})}</p>`;
            return;
        }
        
        statusEl.style.background = 'var(--bg-panel)';
        statusEl.style.border = '1px solid #4a9eff';
        statusEl.innerHTML = `
            <div style="margin: 0;">
                <p style="margin: 0 0 10px 0; color: var(--color-link); font-weight: bold;">🔐 Codex Device Authorization</p>
                <p style="margin: 0 0 10px 0; color: var(--color-text);">
                    Please visit: <a href="${data.verification_uri}" target="_blank" style="color: #4eff9e; text-decoration: underline;">${data.verification_uri}</a>
                </p>
                <p style="margin: 0 0 10px 0; color: var(--color-text);">
                    Enter code: <strong style="color: #4eff9e; font-size: 18px; letter-spacing: 2px;">${data.user_code}</strong>
                </p>
                <p style="margin: 0; color: var(--color-muted); font-size: 13px;">
                    Waiting for authorization... (expires in ${Math.floor(data.expires_in / 60)} minutes)
                </p>
            </div>
        `;
        
        try {
            window.open(data.verification_uri, 'codex-auth', 'width=600,height=700');
        } catch (e) {
            console.error('Could not open auth window:', e);
        }
        
        let pollCount = 0;
        const maxPolls = Math.floor(data.expires_in / data.interval);
        const pollInterval = setInterval(async () => {
            pollCount++;
            
            try {
                const pollResponse = await fetch(BASE_PATH + '/dashboard/codex/auth/poll', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    }
                });
                
                const pollData = await pollResponse.json();
                
                if (pollData.status === 'approved') {
                    clearInterval(pollInterval);
                    statusEl.style.background = '#0f4020';
                    statusEl.style.border = '1px solid #4eff9e';
                    statusEl.innerHTML = `<p style="margin: 0; color: #4eff9e;">${window.i18n.interpolate(window.i18n.t('providers.auth_success'), {provider: 'Codex'})}</p>`;
                } else if (pollData.status === 'denied') {
                    clearInterval(pollInterval);
                    statusEl.style.background = '#402010';
                    statusEl.style.border = '1px solid #ff4a4a';
                    statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.t('providers.auth_denied')}</p>`;
                } else if (pollData.status === 'expired') {
                    clearInterval(pollInterval);
                    statusEl.style.background = '#402010';
                    statusEl.style.border = '1px solid #ff4a4a';
                    statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.t('providers.auth_expired')}</p>`;
                }
                
            } catch (error) {
                console.error('Error polling Codex auth:', error);
            }
            
            if (pollCount >= maxPolls) {
                clearInterval(pollInterval);
                statusEl.style.background = '#402010';
                statusEl.style.border = '1px solid #ff4a4a';
                statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.t('providers.auth_timeout')}</p>`;
            }
        }, data.interval * 1000);
        
    } catch (error) {
        statusEl.style.background = '#402010';
        statusEl.style.border = '1px solid #ff4a4a';
        statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.interpolate(window.i18n.t('providers.auth_generic_error'), {error: error.message})}</p>`;
    }
}

async function pollCodexAuth(providerKey, deviceCode) {
    // This function is no longer needed as polling is handled in authenticateCodex
}

async function authenticateKilo(key) {
    const statusEl = document.getElementById(`kilo-auth-status-${key}`);
    statusEl.style.display = 'block';
    statusEl.style.background = 'var(--bg-panel)';
    statusEl.style.border = '1px solid #4a9eff';
    statusEl.innerHTML = '<p style="margin: 0; color: var(--color-link);">🔄 Starting Kilocode OAuth2 Device Authorization flow...</p>';
    
    try {
        const response = await fetch(BASE_PATH + '/dashboard/kilo/auth/start', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                provider_key: key
            })
        });
        
        const data = await response.json();
        
        if (!data.success) {
            statusEl.style.background = '#402010';
            statusEl.style.border = '1px solid #ff4a4a';
            statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.interpolate(window.i18n.t('providers.auth_start_failed'), {error: data.error || ''})}</p>`;
            return;
        }
        
        statusEl.style.background = 'var(--bg-panel)';
        statusEl.style.border = '1px solid #4a9eff';
        statusEl.innerHTML = `
            <div style="margin: 0;">
                <p style="margin: 0 0 10px 0; color: var(--color-link); font-weight: bold;">🔐 Kilocode Device Authorization</p>
                <p style="margin: 0 0 10px 0; color: var(--color-text);">
                    Please visit: <a href="${data.verification_uri}" target="_blank" style="color: #4eff9e; text-decoration: underline;">${data.verification_uri}</a>
                </p>
                <p style="margin: 0 0 10px 0; color: var(--color-text);">
                    Enter code: <strong style="color: #4eff9e; font-size: 18px; letter-spacing: 2px;">${data.user_code}</strong>
                </p>
                <p style="margin: 0; color: var(--color-muted); font-size: 13px;">
                    Waiting for authorization... (expires in ${Math.floor(data.expires_in / 60)} minutes)
                </p>
            </div>
        `;
        
        try {
            window.open(data.verification_uri, 'kilo-auth', 'width=600,height=700');
        } catch (e) {
            console.error('Could not open auth window:', e);
        }
        
        let pollCount = 0;
        const maxPolls = Math.floor(data.expires_in / data.interval);
        const pollInterval = setInterval(async () => {
            pollCount++;
            
            try {
                const pollResponse = await fetch(BASE_PATH + '/dashboard/kilo/auth/poll', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    }
                });
                
                const pollData = await pollResponse.json();
                
                if (pollData.status === 'completed') {
                    clearInterval(pollInterval);
                    statusEl.style.background = '#0f4020';
                    statusEl.style.border = '1px solid #4eff9e';
                    statusEl.innerHTML = `<p style="margin: 0; color: #4eff9e;">${window.i18n.interpolate(window.i18n.t('providers.auth_success'), {provider: 'Kilocode'})}</p>`;
                } else if (pollData.status === 'denied') {
                    clearInterval(pollInterval);
                    statusEl.style.background = '#402010';
                    statusEl.style.border = '1px solid #ff4a4a';
                    statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.t('providers.auth_denied')}</p>`;
                } else if (pollData.status === 'expired') {
                    clearInterval(pollInterval);
                    statusEl.style.background = '#402010';
                    statusEl.style.border = '1px solid #ff4a4a';
                    statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.t('providers.auth_expired')}</p>`;
                }
                
            } catch (error) {
                console.error('Error polling Kilo auth:', error);
            }
            
            if (pollCount >= maxPolls) {
                clearInterval(pollInterval);
                statusEl.style.background = '#402010';
                statusEl.style.border = '1px solid #ff4a4a';
                statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.t('providers.auth_timeout')}</p>`;
            }
        }, data.interval * 1000);
        
    } catch (error) {
        statusEl.style.background = '#402010';
        statusEl.style.border = '1px solid #ff4a4a';
        statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.interpolate(window.i18n.t('providers.auth_generic_error'), {error: error.message})}</p>`;
    }
}

async function pollKiloAuth(providerKey, deviceCode) {
    // This function is no longer needed as polling is handled in authenticateKilo
}

async function saveProvider(key) {
    const statusEl = document.getElementById(`save-status-${key}`);
    if (statusEl) statusEl.textContent = window.i18n.t('providers.saving');
    try {
        const result = await apiCall('POST', '/dashboard/api/provider', {
            provider_id: key,
            config: providersData[key]
        });
        if (result.success) {
            if (statusEl) { statusEl.textContent = window.i18n.t('providers.saved'); statusEl.style.color = '#4ade80'; setTimeout(() => { statusEl.textContent = ''; }, 3000); }
        } else {
            if (statusEl) { statusEl.textContent = 'Error: ' + (result.error || 'Unknown'); statusEl.style.color = '#f87171'; }
        }
    } catch (e) {
        if (statusEl) { statusEl.textContent = 'Error: ' + e.message; statusEl.style.color = '#f87171'; }
    }
}

async function removeProvider(key) {
    if (await showDangerConfirm(window.i18n.interpolate(window.i18n.t('providers.remove_provider_confirm'), {key}), window.i18n.t('providers.remove_provider_title'))) {
        try {
            const result = await apiCall('DELETE', '/dashboard/api/provider/' + encodeURIComponent(key));
            if (!result.success) {
                showAlert('Error removing provider: ' + (result.error || 'Unknown'), 'Error', '❌', 'danger');
                return;
            }
        } catch (e) {
            showAlert('Error removing provider: ' + e.message, 'Error', '❌', 'danger');
            return;
        }
        delete providersData[key];
        providerMasterOrder = providerMasterOrder.filter(k => k !== key);
        expandedProviders.delete(key);
        renderProvidersList();
    }
}

function updateProvider(key, field, value) {
    providersData[key][field] = value;
}

function updateKiroConfig(key, field, value) {
    if (!providersData[key].kiro_config) {
        providersData[key].kiro_config = {};
    }
    providersData[key].kiro_config[field] = value;
}

function updateClaudeConfig(key, field, value) {
    if (!providersData[key].claude_config) {
        providersData[key].claude_config = {};
    }
    providersData[key].claude_config[field] = value;
}

function updateKiloConfig(key, field, value) {
    if (!providersData[key].kilo_config) {
        providersData[key].kilo_config = {};
    }
    providersData[key].kilo_config[field] = value;
}

function updateQwenConfig(key, field, value) {
    if (!providersData[key].qwen_config) {
        providersData[key].qwen_config = {};
    }
    providersData[key].qwen_config[field] = value;
}

function updateCodexConfig(key, field, value) {
    if (!providersData[key].codex_config) {
        providersData[key].codex_config = {};
    }
    providersData[key].codex_config[field] = value;
}

function updateCoderAIConfig(key, field, value) {
    if (!providersData[key].coderai_config) {
        providersData[key].coderai_config = {};
    }
    providersData[key].coderai_config[field] = value;
}

function updateCoderAIBrokerMode(key, enabled) {
    if (!providersData[key].coderai_config) {
        providersData[key].coderai_config = {};
    }
    providersData[key].coderai_config.broker_mode = enabled;
    providersData[key].coderai_config.broker_enabled = enabled;
    providersData[key].coderai_config.broker_preferred = enabled;
    providersData[key].coderai_config.transport = enabled ? 'broker' : ((providersData[key].coderai_config.transport === 'websocket') ? 'websocket' : 'http');
    renderProviderDetails(key);
}

function updateCoderAITransportMode(key, value) {
    if (!providersData[key].coderai_config) {
        providersData[key].coderai_config = {};
    }
    providersData[key].coderai_config.transport = value;
    renderProviderDetails(key);
}

async function copyCoderAIToken(key) {
    const statusEl = document.getElementById(`coderai-token-status-${key}`);
    const token = (((providersData[key] || {}).coderai_config || {}).registration_token || '').trim();
    if (!token) {
        if (statusEl) {
            statusEl.textContent = 'No token to copy';
            statusEl.style.color = '#f87171';
        }
        return;
    }
    try {
        await navigator.clipboard.writeText(token);
        if (statusEl) {
            statusEl.textContent = 'Token copied to clipboard';
            statusEl.style.color = '#4ade80';
        }
    } catch (e) {
        if (statusEl) {
            statusEl.textContent = 'Copy failed: ' + e.message;
            statusEl.style.color = '#f87171';
        }
    }
}

async function rotateCoderAIToken(key) {
    const statusEl = document.getElementById(`coderai-token-status-${key}`);
    if (statusEl) {
        statusEl.textContent = 'Rotating token...';
        statusEl.style.color = 'var(--color-muted)';
    }
    try {
        const result = await apiCall('POST', `/dashboard/api/provider/${encodeURIComponent(key)}/coderai-token`, {});
        if (!result.success) throw new Error(result.error || 'Unknown error');
        if (!providersData[key].coderai_config) providersData[key].coderai_config = {};
        providersData[key].coderai_config.registration_token = result.registration_token;
        renderProviderDetails(key);
        const refreshedStatusEl = document.getElementById(`coderai-token-status-${key}`);
        if (refreshedStatusEl) {
            refreshedStatusEl.textContent = 'Token rotated successfully';
            refreshedStatusEl.style.color = '#4ade80';
        }
    } catch (e) {
        if (statusEl) {
            statusEl.textContent = 'Error: ' + e.message;
            statusEl.style.color = '#f87171';
        }
    }
}

function updateProviderType(key, value) {
    providersData[key].type = value;
    const defaultEndpoint = getDefaultEndpoint(value);
    if (defaultEndpoint) {
        providersData[key].endpoint = defaultEndpoint;
    }
    // Re-render to update the configuration fields
    renderProvidersList();
}

function updateProviderCondenseMethod(key, value) {
    const trimmed = value.trim();
    if (!trimmed) {
        providersData[key].default_condense_method = null;
        return;
    }

    // Check if it's a comma-separated list
    if (trimmed.includes(',')) {
        providersData[key].default_condense_method = trimmed.split(',').map(s => s.trim()).filter(s => s);
    } else {
        providersData[key].default_condense_method = trimmed;
    }
}

function addModel(key) {
    if (!providersData[key].models) {
        providersData[key].models = [];
    }

    providersData[key].models.push({
        name: '',
        rate_limit: 0
    });

    renderModels(key);
}

async function removeModel(providerKey, index) {
    if (await showDangerConfirm(window.i18n.t('providers.remove_model_confirm'), window.i18n.t('providers.remove_model_title'))) {
        providersData[providerKey].models.splice(index, 1);
        renderModels(providerKey);
    }
}

async function confirmAddProvider() {
    const key = document.getElementById('new-provider-key').value.trim();
    const type = document.getElementById('new-provider-type').value;

    if (!key) {
        showAlert(window.i18n.t('providers.missing_key'), window.i18n.t('providers.missing_key_title'), '⚠️', 'warn');
        return;
    }

    if (providersData[key]) {
        showAlert(window.i18n.t('providers.duplicate_key'), window.i18n.t('providers.duplicate_key_title'), '⚠️', 'warn');
        return;
    }

    // Create new provider with defaults
    providersData[key] = {
        id: key,
        name: key.charAt(0).toUpperCase() + key.slice(1),
        endpoint: getDefaultEndpoint(type),
        type: type,
        api_key_required: type !== 'ollama' && type !== 'kiro' && type !== 'claude' && type !== 'kilocode' && type !== 'qwen' && type !== 'codex' && type !== 'coderai',
        rate_limit: 0,
        models: []
    };

    // Add type-specific config
    if (type === 'kiro') {
        providersData[key].kiro_config = {
            region: 'us-east-1',
            refresh_token: '',
            profile_arn: '',
            client_id: '',
            client_secret: ''
        };
    } else if (type === 'claude') {
        providersData[key].claude_config = {};
    } else if (type === 'kilocode') {
        providersData[key].kilo_config = {
            api_base: 'https://api.kilo.ai/api/gateway'
        };
    } else if (type === 'qwen') {
        providersData[key].qwen_config = {
            api_key: '',
            region: 'china-beijing',
            workspace_id: 'Default Workspace'
        };
    } else if (type === 'codex') {
        providersData[key].codex_config = {
            issuer: 'https://auth.openai.com'
        };
    } else if (type === 'coderai') {
        providersData[key].coderai_config = {
            transport: 'broker',
            broker_enabled: true,
            broker_mode: true,
            broker_preferred: true,
            discovery_enabled: true,
            client_id: key,
            bridge_path: '/coderai/ws',
            registration_path: '/coderai/register'
        };
    }

    try {
        const result = await apiCall('POST', '/dashboard/api/provider', {
            provider_id: key,
            config: providersData[key]
        });
        if (!result.success) {
            showAlert('Error creating provider: ' + (result.error || 'Unknown'), 'Error', '❌', 'danger');
            delete providersData[key];
            return;
        }
    } catch (e) {
        showAlert('Error creating provider: ' + e.message, 'Error', '❌', 'danger');
        delete providersData[key];
        return;
    }

    if (!providerMasterOrder.includes(key)) providerMasterOrder.push(key);
    expandedProviders.add(key);
    cancelAddProvider();
    renderProvidersList();
}

function getDefaultEndpoint(type) {
    const defaults = {
        // Generic types
        'openai': 'https://api.openai.com/v1',
        'google': 'https://generativelanguage.googleapis.com/v1beta',
        'anthropic': 'https://api.anthropic.com/v1',
        'ollama': 'http://localhost:11434/api',
        // OAuth2 / Special
        'kiro': 'https://q.us-east-1.amazonaws.com',
        'claude': 'https://api.anthropic.com/v1',
        'kilocode': 'https://api.kilo.ai/api/gateway',
        'qwen': 'https://dashscope.aliyuncs.com/compatible-mode/v1',
        'codex': 'https://api.openai.com/v1',
        'coderai': 'http://127.0.0.1:11437',
        // Pre-configured API providers
        'abliteration': 'https://api.abliteration.ai/v1',
        'ai21': 'https://api.ai21.com/studio/v1',
        'aiml': 'https://api.aimlapi.com/v1',
        'aihubmix': 'https://aihubmix.com/v1',
        'apertis': 'https://api.stima.tech/v1',
        'assemblyai_llm': 'https://llm-gateway.assemblyai.com/v1',
        'baseten': 'https://inference.baseten.co/v1',
        'cerebras': 'https://api.cerebras.ai/v1',
        'charity_engine': 'https://api.charityengine.services/remotejobs/v2/inference',
        'chutes': 'https://llm.chutes.ai/v1',
        'clarifai': 'https://api.clarifai.com/v2/ext/openai/v1',
        'cohere': 'https://api.cohere.ai/compatibility/v1',
        'codestral': 'https://codestral.mistral.ai/v1',
        'cometapi': 'https://api.cometapi.com/v1',
        'compactifai': 'https://api.compactif.ai/v1',
        'crusoe': 'https://managed-inference-api-proxy.crusoecloud.com/v1',
        'dashscope': 'https://dashscope.aliyuncs.com/compatible-mode/v1',
        'deepinfra': 'https://api.deepinfra.com/v1/openai',
        'deepseek': 'https://api.deepseek.com/v1',
        'empower': 'https://app.empower.dev/api/v1',
        'featherless_ai': 'https://api.featherless.ai/v1',
        'fireworks_ai': 'https://api.fireworks.ai/inference/v1',
        'friendliai': 'https://api.friendli.ai/serverless/v1',
        'galadriel': 'https://api.galadriel.com/v1',
        'github_models': 'https://models.inference.ai.azure.com',
        'gmi': 'https://api.gmi-serving.com/v1',
        'gradient_ai': 'https://inference.do-ai.run/v1',
        'groq': 'https://api.groq.com/openai/v1',
        'helicone': 'https://ai-gateway.helicone.ai',
        'huggingface': 'https://api-inference.huggingface.co/v1',
        'hyperbolic': 'https://api.hyperbolic.xyz/v1',
        'jina_ai': 'https://api.jina.ai/v1',
        'lambda_ai': 'https://api.lambda.ai/v1',
        'llamagate': 'https://api.llamagate.dev/v1',
        'maritalk': 'https://api.maritalk.com/v1',
        'meta_llama': 'https://api.llama.com/compat/v1',
        'minimax': 'https://api.minimax.io/v1',
        'mistral': 'https://api.mistral.ai/v1',
        'moonshot': 'https://api.moonshot.ai/v1',
        'morph': 'https://api.morphllm.com/v1',
        'nano_gpt': 'https://nano-gpt.com/api/v1',
        'nebius': 'https://api.studio.nebius.ai/v1',
        'nlp_cloud': 'https://api.nlpcloud.io/v1',
        'novita': 'https://api.novita.ai/v3/openai',
        'nscale': 'https://inference.api.nscale.com/v1',
        'nvidia_nim': 'https://integrate.api.nvidia.com/v1',
        'openrouter': 'https://openrouter.ai/api/v1',
        'ovhcloud': 'https://oai.endpoints.kepler.ai.cloud.ovh.net/v1',
        'perplexity': 'https://api.perplexity.ai',
        'poe': 'https://api.poe.com/v1',
        'predibase': 'https://serving.app.predibase.com/v1',
        'publicai': 'https://api.publicai.co/v1',
        'sambanova': 'https://api.sambanova.ai/v1',
        'sarvam': 'https://api.sarvam.ai/v1',
        'scaleway': 'https://api.scaleway.ai/v1',
        'synthetic': 'https://api.synthetic.new/openai/v1',
        'together_ai': 'https://api.together.xyz/v1',
        'veniceai': 'https://api.venice.ai/api/v1',
        'vercel_ai_gateway': 'https://ai-gateway.vercel.sh/v1',
        'volcengine': 'https://ark.cn-beijing.volces.com/api/v3',
        'voyage': 'https://api.voyageai.com/v1',
        'wandb_inference': 'https://api.inference.wandb.ai/v1',
        'xai': 'https://api.x.ai/v1',
        'xiaomi_mimo': 'https://api.xiaomimimo.com/v1',
        'zai': 'https://api.z.ai/api/paas/v4',
        // Local / self-hosted
        'docker_model_runner': 'http://localhost:12434/engines/llama.cpp/v1',
        'infinity': 'http://localhost:8000/v1',
        'llamafile': 'http://localhost:8080/v1',
        'lm_studio': 'http://localhost:1234/v1',
        'oobabooga': 'http://localhost:5000/v1',
        'tabbyapi': 'http://localhost:5000/v1',
        'vllm': 'http://localhost:8000/v1',
        'xinference': 'http://localhost:9997/v1',
        // Cloud (configure endpoint — left empty so user must fill in)
        'azure_openai': '',
        'databricks': '',
        'heroku': '',
        'snowflake': '',
    };
    return defaults[type] || '';
}

async function getModelsFromProvider(key) {
    const statusDiv = document.getElementById(`get-models-status-${key}`);
    if (!statusDiv) return;

    statusDiv.innerHTML = window.i18n.t('providers.fetching_models');

    try {
        const response = await fetch(BASE_PATH + '/dashboard/providers/get-models', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                provider_key: key,
                provider: providersData[key]
            })
        });

        const result = await response.json();

        if (response.ok && result.models) {
            providersData[key].models = result.models;
            renderModels(key);
            statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.models_found'), {n: result.models.length});
        } else {
            statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.models_fetch_error'), {error: result.error || ''});
        }
    } catch (error) {
        statusDiv.innerHTML = `❌ Error: ${error.message}`;
    }
}

function updateModel(providerKey, index, field, value) {
    providersData[providerKey].models[index][field] = value;
}

function updateModelStudioCapabilities(providerKey, index, selectEl) {
    const values = Array.from(selectEl.selectedOptions).map(option => option.value).filter(Boolean);
    providersData[providerKey].models[index].studio_capabilities = values;
    providersData[providerKey].models[index].studio_capability_source = values.length ? 'manual' : 'heuristic';
    providersData[providerKey].models[index].studio_capability_unknown = values.length === 0;
}

function updateModelStudioAdapter(providerKey, index, value) {
    providersData[providerKey].models[index].studio_adapter_override = value === 'auto' ? null : value;
    providersData[providerKey].models[index].studio_adapter = value || 'auto';
}

function updateModelStudioAdapterProfile(providerKey, index, value) {
    providersData[providerKey].models[index].studio_adapter_profile_override = value === 'auto' ? null : value;
    providersData[providerKey].models[index].studio_adapter_profile = value || 'auto';
}

function updateModelCondenseMethod(providerKey, index, value) {
    const trimmed = value.trim();
    if (!trimmed) {
        providersData[providerKey].models[index].condense_method = null;
        return;
    }

    // Check if it's a comma-separated list
    if (trimmed.includes(',')) {
        providersData[providerKey].models[index].condense_method = trimmed.split(',').map(s => s.trim()).filter(s => s);
    } else {
        providersData[providerKey].models[index].condense_method = trimmed;
    }
}

async function saveProviders() {
    try {
        const response = await fetch(BASE_PATH + '/dashboard/providers', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
            },
            body: (function() {
                const orderedData = {};
                providerMasterOrder.forEach(k => { if (providersData[k]) orderedData[k] = providersData[k]; });
                return 'config=' + encodeURIComponent(JSON.stringify(orderedData, null, 2));
            })()
        });

        if (response.ok) {
            window.location.href = `${BASE_PATH}/dashboard/providers?success=1`;
        } else {
            showAlert(window.i18n.t('providers.error_saving'), 'Error', '❌', 'danger');
        }
    } catch (error) {
        showAlert('Error: ' + error.message, 'Error', '❌', 'danger');
    }
}

async function initProviders() {
    renderProvidersList();
    refreshAllUsage().then(() => renderProvidersList());
}

// Defer until i18n is ready so translated strings are available
function _doInitialRender() { initProviders(); }
if (window.i18n) { _doInitialRender(); } else { document.addEventListener('i18n:ready', _doInitialRender, {once: true}); }

</script>
{% endblock %}
