<!--
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 %}Providers - AISBF Dashboard{% endblock %}

{% block content %}
<script id="providers-bootstrap" type="application/json">{{ providers_data | tojson }}</script>
<h2 style="margin-bottom: 30px;">Providers Configuration</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);">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()">
            <option value="openai">OpenAI</option>
            <option value="google">Google</option>
            <option value="anthropic">Anthropic</option>
            <option value="ollama">Ollama</option>
            <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 (OpenAI OAuth2)</option>
            <option value="coderai">CoderAI</option>
            <option value="runpod">RunPod</option>
        </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 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>
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();
}

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 formatRunpodTimestamp(value) {
    if (value == null || value === '') return 'Never';
    const date = new Date(Number(value) * 1000);
    if (Number.isNaN(date.getTime())) return 'Unknown';
    return date.toLocaleString();
}

function runpodStatusBadgeColor(status) {
    const normalized = String(status || '').toLowerCase();
    if (['ready', 'running', 'healthy', 'active'].includes(normalized)) return '#22c55e';
    if (['starting', 'pending', 'provisioning'].includes(normalized)) return '#f59e0b';
    if (['stopped', 'exited', 'terminated', 'idle'].includes(normalized)) return '#94a3b8';
    return '#f87171';
}

function renderRunpodRuntimeStatus(providerKey, payload, errorMessage) {
    const container = document.getElementById(`runpod-runtime-status-${providerKey}`);
    if (!container) return;

    if (errorMessage) {
        container.innerHTML = `<div style="padding:12px; border-radius:6px; background:rgba(248,113,113,0.08); border:1px solid rgba(248,113,113,0.35); color:#fca5a5;">${escHtmlAttr(errorMessage)}</div>`;
        return;
    }

    const status = payload || {};
    const catalog = status.catalog || {};
    const badgeColor = runpodStatusBadgeColor(status.status);
    const models = Array.isArray(catalog.models) ? catalog.models : [];
    const modelPreview = models.slice(0, 6).map(model => `<code style="background:var(--bg-page);padding:2px 6px;border-radius:4px;">${escHtmlAttr(model.id || model.name || 'unknown')}</code>`).join(' ');

    container.innerHTML = `
        <div style="padding:14px; border-radius:6px; background: var(--bg-page); border:1px solid var(--color-border); display:flex; flex-direction:column; gap:10px;">
            <div style="display:flex; justify-content:space-between; gap:10px; align-items:center; flex-wrap:wrap;">
                <strong style="color:var(--color-text);">RunPod Runtime Status</strong>
                <span style="display:inline-flex; align-items:center; gap:8px; background:rgba(15,23,42,0.25); border:1px solid var(--color-border); border-radius:999px; padding:4px 10px; color:var(--color-text);">
                    <span style="width:10px; height:10px; border-radius:50%; background:${badgeColor}; display:inline-block;"></span>
                    ${escHtmlAttr(status.status || 'unknown')}
                </span>
            </div>
            <div style="display:grid; grid-template-columns:repeat(auto-fit, minmax(220px, 1fr)); gap:10px; font-size:13px; color:var(--color-text); line-height:1.5;">
                <div><strong>Mode:</strong> ${escHtmlAttr(status.mode || 'unknown')}</div>
                <div><strong>Wrapper:</strong> ${escHtmlAttr(status.wrapper_mode || 'n/a')}</div>
                <div><strong>Resource:</strong> ${escHtmlAttr(status.resource_kind || 'n/a')} ${status.resource_id ? `(${escHtmlAttr(status.resource_id)})` : ''}</div>
                <div><strong>Endpoint:</strong> ${escHtmlAttr(status.endpoint_url || 'Unknown')}</div>
                <div><strong>Catalog Count:</strong> ${escHtmlAttr(String(catalog.item_count ?? models.length ?? 0))}</div>
                <div><strong>Catalog Source:</strong> ${escHtmlAttr(catalog.source || 'unknown')}</div>
                <div><strong>Catalog Refreshed:</strong> ${escHtmlAttr(formatRunpodTimestamp(catalog.refreshed_at))}</div>
                <div><strong>Last Used:</strong> ${escHtmlAttr(formatRunpodTimestamp(status.last_used_at))}</div>
            </div>
            ${catalog.refresh_error ? `<div style="padding:10px; border-radius:6px; background:rgba(245,158,11,0.1); border:1px solid rgba(245,158,11,0.35); color:#fcd34d;"><strong>Last Refresh Error:</strong> ${escHtmlAttr(catalog.refresh_error)}</div>` : ''}
            ${modelPreview ? `<div style="display:flex; flex-wrap:wrap; gap:6px; align-items:center;"><strong style="color:var(--color-text);">Catalog Models:</strong> ${modelPreview}</div>` : ''}
        </div>
    `;
}

async function loadRunpodRuntimeStatus(providerKey) {
    const container = document.getElementById(`runpod-runtime-status-${providerKey}`);
    if (!container) return;
    container.innerHTML = `<div style="color: var(--color-muted);">Loading RunPod runtime status...</div>`;

    try {
        const result = await apiCall('GET', `${BASE_PATH}/dashboard/providers/${encodeURIComponent(providerKey)}/runpod-status`);
        if (!result.success) throw new Error(result.error || 'Failed to load RunPod status');
        renderRunpodRuntimeStatus(providerKey, result.status, null);
    } catch (error) {
        renderRunpodRuntimeStatus(providerKey, null, `Unable to load RunPod status: ${error.message}`);
    }
}

async function refreshRunpodCatalog(providerKey) {
    const actionStatus = document.getElementById(`runpod-runtime-action-${providerKey}`);
    if (actionStatus) {
        actionStatus.style.color = 'var(--color-muted)';
        actionStatus.textContent = 'Refreshing public catalog...';
    }

    try {
        const result = await apiCall('POST', `${BASE_PATH}/dashboard/providers/${encodeURIComponent(providerKey)}/runpod-refresh`, {});
        if (!result.success) throw new Error(result.error || 'Failed to refresh catalog');
        renderRunpodRuntimeStatus(providerKey, result.status, null);
        if (actionStatus) {
            actionStatus.style.color = '#4ade80';
            actionStatus.textContent = `Public catalog refreshed (${result.catalog_count} models).`;
        }
    } catch (error) {
        if (actionStatus) {
            actionStatus.style.color = '#f87171';
            actionStatus.textContent = `Refresh failed: ${error.message}`;
        }
    }
}

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('; ');
}

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();
}

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 };
let providersData = {{ providers_data | tojson }};
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 providerMasterOrder = Object.keys(providersData);
let _providerDS = null;

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

// ===== Provider Usage Indicators =====
const _usageCache = {};  // provider_id -> {data, ts}
const USAGE_POLL_INTERVAL = 15000;  // 15 seconds

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

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) {
    // Update collapsed badge
    const badge = document.getElementById(`usage-badge-${key}`);
    if (badge) {
        const cached = _usageCache[key];
        if (cached && cached.data) badge.innerHTML = _renderUsageCompact(cached.data);
    }
    // Update expanded panel
    const full = document.getElementById(`usage-full-${key}`);
    if (full) {
        const cached = _usageCache[key];
        if (cached && cached.data) full.innerHTML = _renderUsageFull(cached.data);
    }
}

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

// Start polling
setInterval(refreshAllUsage, USAGE_POLL_INTERVAL);

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);
}

// 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 (.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});

    // Cross-page sentinel zones (before + after list)
    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 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>` : '';

            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>
                        ${compactBadge}
                    </div>
                    <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);

    // Pagination controls
    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>`;
        }
    }

    // Init / re-attach DragSort
    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];
    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';
    const isRunpodProvider = provider.type === 'runpod';
    
    // 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',
            creds_file: '',
            sqlite_db: '',
            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 = {
            credentials_file: `~/.aisbf/claude_${key}_credentials.json`
        };
    }

    // Initialize kilo_config if this is a kilocode provider and doesn't have it
    if (isKiloProvider && !provider.kilo_config) {
        provider.kilo_config = {
            credentials_file: `~/.aisbf/kilo_${key}_credentials.json`,
            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 = {
            credentials_file: `~/.aisbf/qwen_${key}_credentials.json`,
            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 = {
            credentials_file: `~/.aisbf/codex_${key}_credentials.json`,
            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: ''
        };
    }
    if (isRunpodProvider && !provider.runpod_config) {
        provider.runpod_config = {
            mode: 'pod',
            wrapper_mode: 'openai',
            account_name: key,
            management_api: 'auto',
            idle_shutdown_ms: 900000,
            startup_poll_interval_ms: 3000,
            startup_timeout_ms: 300000,
            pod_id: '',
            template_id: '',
            endpoint_id: '',
            serverless_template_id: '',
            public_endpoint_protocol_default: 'auto',
            public_models: {}
        };
    }
    
    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 runpodConfig = provider.runpod_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);">${window.i18n.t('providers.kiro_opt1')}</h5>
                <div class="form-group">
                    <label>${window.i18n.t('providers.credentials_file')}</label>
                    <input type="text" value="${kiroConfig.creds_file || ''}" onchange="updateKiroConfig('${key}', 'creds_file', this.value)" placeholder="~/.config/Code/User/globalStorage/amazon.q/credentials.json">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">Path to Kiro IDE credentials JSON file</small>
                </div>
                
                <h5 style="margin: 20px 0 10px 0; color: var(--color-link);">${window.i18n.t('providers.kiro_opt2')}</h5>
                <div class="form-group">
                    <label>${window.i18n.t('providers.kiro_sqlite_path')}</label>
                    <input type="text" value="${kiroConfig.sqlite_db || ''}" onchange="updateKiroConfig('${key}', 'sqlite_db', this.value)" placeholder="~/.local/share/kiro-cli/data.sqlite3">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">Path to kiro-cli SQLite database</small>
                </div>
                
                <h5 style="margin: 20px 0 10px 0; color: var(--color-link);">${window.i18n.t('providers.kiro_opt3')}</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 class="form-group">
                    <label>${window.i18n.t('providers.credentials_file')}</label>
                    <input type="text" value="${kiloConfig.credentials_file || '~/.kilo_credentials.json'}" onchange="updateKiloConfig('${key}', 'credentials_file', this.value)" placeholder="~/.kilo_credentials.json">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">${window.i18n.t('providers.credentials_file_desc')}</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
        // Determine extension state: local clients don't need it; remote clients need it installed
        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 class="form-group">
                    <label>${window.i18n.t('providers.credentials_file')}</label>
                    <input type="text" value="${claudeConfig.credentials_file || '~/.claude_credentials.json'}" onchange="updateClaudeConfig('${key}', 'credentials_file', this.value)" placeholder="~/.claude_credentials.json">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">${window.i18n.t('providers.credentials_file_desc')}</small>
                </div>

                <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: 8px;">
                        The claude CLI was detected at startup. When enabled, requests are piped
                        through the local claude binary instead of the HTTP API.
                    </small>
                    <div style="background: #3a2a00; border: 1px solid #f59e0b; border-radius: 6px; padding: 10px 14px; margin-bottom: 14px;">
                        <span style="color: #f59e0b; font-weight: 600;">⚠ Experimental:</span>
                        <span style="color: #fcd34d; font-size: 0.85em;">
                            CLI mode is experimental. Tool calling (function calling) does not yet work reliably —
                            the CLI subprocess may refuse or mishandle tool definitions. Use with simple
                            (non-tool) requests only until this is resolved.
                        </span>
                    </div>

                    <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 are 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. Path saved in
                            <code style="background:var(--bg-panel);padding:1px 4px;border-radius:3px;">claude_config.cli_credentials_file</code>.
                        </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 used by AISBF to match the right CoderAI client session.</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;">Generated and stored directly in this provider configuration for this owner. 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, 1, ' 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 (isRunpodProvider) {
        const publicModels = runpodConfig.public_models || {};
        const overrideRows = Object.entries(publicModels).map(([modelId, modelConfig]) => `
            <div style="display:grid; grid-template-columns:minmax(220px, 1fr) minmax(180px, 220px); gap:10px; margin-bottom:10px; align-items:center;">
                <input type="text" value="${escHtmlAttr(modelId)}" onchange="renameRunpodPublicModelOverride('${key}', '${escHtmlAttr(modelId)}', this.value)" placeholder="model-slug">
                <select onchange="updateRunpodPublicModelProtocol('${key}', '${escHtmlAttr(modelId)}', this.value)">
                    <option value="auto" ${(modelConfig.protocol || 'auto') === 'auto' ? 'selected' : ''}>Auto detect</option>
                    <option value="runpod_public" ${modelConfig.protocol === 'runpod_public' ? 'selected' : ''}>RunPod public</option>
                    <option value="openai" ${modelConfig.protocol === 'openai' ? 'selected' : ''}>OpenAI</option>
                    <option value="ollama" ${modelConfig.protocol === 'ollama' ? 'selected' : ''}>Ollama</option>
                    <option value="coderai" ${modelConfig.protocol === 'coderai' ? 'selected' : ''}>CoderAI</option>
                </select>
            </div>
        `).join('');

        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);">RunPod Runtime Configuration</h4>
                <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
                    RunPod providers use the REST management API to start pods, resolve serverless endpoints, and cache discovered public models across AISBF instances.
                </small>

                <div class="form-group">
                    <label>Account Label</label>
                    <input type="text" value="${runpodConfig.account_name || key}" onchange="updateRunpodConfig('${key}', 'account_name', this.value)" placeholder="personal-runpod">
                </div>

                <div class="form-group">
                    <label>Management API Key</label>
                    <input type="password" value="${provider.api_key || ''}" onchange="updateProvider('${key}', 'api_key', this.value)" placeholder="RunPod API key">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">Used for RunPod REST management calls and public endpoint requests.</small>
                </div>

                <div class="form-group">
                    <label>Mode</label>
                    <select onchange="updateRunpodMode('${key}', this.value)">
                        <option value="pod" ${runpodConfig.mode === 'pod' ? 'selected' : ''}>Pod-backed</option>
                        <option value="serverless_template" ${runpodConfig.mode === 'serverless_template' ? 'selected' : ''}>Serverless template</option>
                        <option value="public" ${runpodConfig.mode === 'public' ? 'selected' : ''}>Public catalog</option>
                    </select>
                </div>

                <div id="runpod-non-public-${key}" style="display:${runpodConfig.mode === 'public' ? 'none' : 'block'};">
                    <div class="form-group">
                        <label>Wrapper Mode</label>
                        <select onchange="updateRunpodConfig('${key}', 'wrapper_mode', this.value)">
                            <option value="openai" ${(runpodConfig.wrapper_mode || 'openai') === 'openai' ? 'selected' : ''}>OpenAI</option>
                            <option value="ollama" ${runpodConfig.wrapper_mode === 'ollama' ? 'selected' : ''}>Ollama</option>
                            <option value="coderai" ${runpodConfig.wrapper_mode === 'coderai' ? 'selected' : ''}>CoderAI</option>
                        </select>
                    </div>

                    <div class="form-group" style="display:${runpodConfig.mode === 'pod' ? 'block' : 'none'};" id="runpod-pod-id-group-${key}">
                        <label>Pod ID</label>
                        <input type="text" value="${runpodConfig.pod_id || ''}" onchange="updateRunpodConfig('${key}', 'pod_id', this.value)" placeholder="abc123">
                    </div>

                    <div class="form-group" style="display:${runpodConfig.mode === 'serverless_template' ? 'block' : 'none'};" id="runpod-endpoint-id-group-${key}">
                        <label>Endpoint ID</label>
                        <input type="text" value="${runpodConfig.endpoint_id || ''}" onchange="updateRunpodConfig('${key}', 'endpoint_id', this.value)" placeholder="endpoint id (optional if template set)">
                    </div>

                    <div class="form-group">
                        <label>Template ID</label>
                        <input type="text" value="${runpodConfig.template_id || ''}" onchange="updateRunpodConfig('${key}', 'template_id', this.value)" placeholder="tmpl_xyz">
                    </div>

                    <div class="form-group" style="display:${runpodConfig.mode === 'serverless_template' ? 'block' : 'none'};" id="runpod-serverless-template-group-${key}">
                        <label>Serverless Template ID</label>
                        <input type="text" value="${runpodConfig.serverless_template_id || ''}" onchange="updateRunpodConfig('${key}', 'serverless_template_id', this.value)" placeholder="serverless template id">
                    </div>

                    <div class="form-group">
                        <label>Idle Shutdown (ms)</label>
                        <input type="number" value="${runpodConfig.idle_shutdown_ms || 900000}" onchange="updateRunpodNumberConfig('${key}', 'idle_shutdown_ms', this.value)">
                    </div>

                    <div class="form-group">
                        <label>Startup Poll Interval (ms)</label>
                        <input type="number" value="${runpodConfig.startup_poll_interval_ms || 3000}" onchange="updateRunpodNumberConfig('${key}', 'startup_poll_interval_ms', this.value)">
                    </div>

                    <div class="form-group">
                        <label>Startup Timeout (ms)</label>
                        <input type="number" value="${runpodConfig.startup_timeout_ms || 300000}" onchange="updateRunpodNumberConfig('${key}', 'startup_timeout_ms', this.value)">
                    </div>
                </div>

                <div id="runpod-public-${key}" style="display:${runpodConfig.mode === 'public' ? 'block' : 'none'};">
                    <div class="form-group">
                        <label>Default Public Protocol</label>
                        <select onchange="updateRunpodConfig('${key}', 'public_endpoint_protocol_default', this.value)">
                            <option value="auto" ${(runpodConfig.public_endpoint_protocol_default || 'auto') === 'auto' ? 'selected' : ''}>Auto detect</option>
                            <option value="runpod_public" ${runpodConfig.public_endpoint_protocol_default === 'runpod_public' ? 'selected' : ''}>RunPod public</option>
                            <option value="openai" ${runpodConfig.public_endpoint_protocol_default === 'openai' ? 'selected' : ''}>OpenAI</option>
                            <option value="ollama" ${runpodConfig.public_endpoint_protocol_default === 'ollama' ? 'selected' : ''}>Ollama</option>
                            <option value="coderai" ${runpodConfig.public_endpoint_protocol_default === 'coderai' ? 'selected' : ''}>CoderAI</option>
                        </select>
                    </div>

                    <div class="form-group">
                        <label>Per-model Protocol Overrides</label>
                        <div style="background: var(--bg-page); border: 1px solid var(--color-border); border-radius: 6px; padding: 12px;">
                            ${overrideRows || '<small style="color: var(--color-muted); display:block;">No overrides configured.</small>'}
                            <button type="button" class="btn btn-secondary" style="margin-top:10px;" onclick="addRunpodPublicModelOverride('${key}')">Add Override</button>
                        </div>
                    </div>
                </div>

                <div class="form-group" style="margin-top: 20px;">
                    <div style="display:flex; justify-content:space-between; gap:10px; align-items:center; flex-wrap:wrap; margin-bottom:10px;">
                        <label style="margin:0;">RunPod Runtime Status</label>
                        <div style="display:flex; gap:8px; flex-wrap:wrap;">
                            <button type="button" class="btn btn-secondary" onclick="loadRunpodRuntimeStatus('${safeKey}')">Refresh Status</button>
                            ${runpodConfig.mode === 'public' ? `<button type="button" class="btn" onclick="refreshRunpodCatalog('${safeKey}')">Refresh Public Catalog</button>` : ''}
                        </div>
                    </div>
                    <div id="runpod-runtime-status-${key}" style="margin-top:10px;"></div>
                    <div id="runpod-runtime-action-${key}" style="margin-top:8px; font-size:13px; color: var(--color-muted);"></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 class="form-group">
                    <label>${window.i18n.t('providers.credentials_file')}</label>
                    <input type="text" value="${qwenConfig.credentials_file || '~/.aisbf/qwen_credentials.json'}" onchange="updateQwenConfig('${key}', 'credentials_file', this.value)" placeholder="~/.aisbf/qwen_credentials.json">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">${window.i18n.t('providers.credentials_file_desc')}</small>
                </div>

                <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 class="form-group">
                    <label>${window.i18n.t('providers.credentials_file')}</label>
                    <input type="text" value="${codexConfig.credentials_file || '~/.aisbf/codex_credentials.json'}" onchange="updateCodexConfig('${key}', 'credentials_file', this.value)" placeholder="~/.aisbf/codex_credentials.json">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">${window.i18n.t('providers.credentials_file_desc')}</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>
                <option value="google" ${provider.type === 'google' ? 'selected' : ''}>Google</option>
                <option value="openai" ${provider.type === 'openai' ? 'selected' : ''}>OpenAI</option>
                <option value="anthropic" ${provider.type === 'anthropic' ? 'selected' : ''}>Anthropic</option>
                <option value="ollama" ${provider.type === 'ollama' ? 'selected' : ''}>Ollama</option>
                <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</option>
            </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>
        </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;">${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; flex-wrap: wrap;">
            <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 (isRunpodProvider) {
        setTimeout(() => loadRunpodRuntimeStatus(key), 0);
    }

    // Fetch full usage data for codex providers when panel is expanded
    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 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',
        'runpod': 'RunPod provider. Uses the RunPod REST management API for pod lifecycle, serverless endpoints, and public endpoint catalogs. Default endpoint: https://rest.runpod.io/v1'
    };

    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';
    }
}

// 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,
                credentials_file: providersData[key].claude_config?.credentials_file || `~/.aisbf/claude_${key}_credentials.json`
            })
        });
        
        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,
                credentials_file: providersData[key].qwen_config?.credentials_file || `~/.aisbf/qwen_${key}_credentials.json`
            })
        });
        
        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,
                credentials_file: providersData[key].codex_config?.credentials_file || `~/.aisbf/codex_${key}_credentials.json`,
                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,
                credentials_file: providersData[key].kilo_config?.credentials_file || `~/.aisbf/kilo_${key}_credentials.json`
            })
        });
        
        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', `${BASE_PATH}/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', `${BASE_PATH}/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 updateRunpodConfig(key, field, value) {
    if (!providersData[key].runpod_config) {
        providersData[key].runpod_config = {};
    }
    providersData[key].runpod_config[field] = value;
}

function updateRunpodNumberConfig(key, field, value) {
    if (!providersData[key].runpod_config) {
        providersData[key].runpod_config = {};
    }
    providersData[key].runpod_config[field] = value === '' ? null : parseInt(value, 10);
}

function updateRunpodMode(key, value) {
    if (!providersData[key].runpod_config) {
        providersData[key].runpod_config = {};
    }
    providersData[key].runpod_config.mode = value;
    renderProviderDetails(key);
}

function addRunpodPublicModelOverride(key) {
    if (!providersData[key].runpod_config) providersData[key].runpod_config = {};
    if (!providersData[key].runpod_config.public_models) providersData[key].runpod_config.public_models = {};
    let candidate = 'model-slug';
    let counter = 1;
    while (providersData[key].runpod_config.public_models[candidate]) {
        counter += 1;
        candidate = `model-slug-${counter}`;
    }
    providersData[key].runpod_config.public_models[candidate] = { protocol: 'auto', capabilities: [] };
    renderProviderDetails(key);
}

function renameRunpodPublicModelOverride(key, previousModelId, nextModelId) {
    if (!providersData[key].runpod_config || !providersData[key].runpod_config.public_models) return;
    const current = providersData[key].runpod_config.public_models[previousModelId];
    if (!current) return;
    const trimmed = String(nextModelId || '').trim();
    if (!trimmed || trimmed === previousModelId) return;
    delete providersData[key].runpod_config.public_models[previousModelId];
    providersData[key].runpod_config.public_models[trimmed] = current;
    renderProviderDetails(key);
}

function updateRunpodPublicModelProtocol(key, modelId, protocol) {
    if (!providersData[key].runpod_config) providersData[key].runpod_config = {};
    if (!providersData[key].runpod_config.public_models) providersData[key].runpod_config.public_models = {};
    if (!providersData[key].runpod_config.public_models[modelId]) {
        providersData[key].runpod_config.public_models[modelId] = { capabilities: [] };
    }
    providersData[key].runpod_config.public_models[modelId].protocol = protocol;
}

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', `${BASE_PATH}/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;
    }
    if (value === 'runpod' && !providersData[key].runpod_config) {
        providersData[key].runpod_config = {
            mode: 'pod',
            wrapper_mode: 'openai',
            account_name: key,
            management_api: 'auto',
            idle_shutdown_ms: 900000,
            startup_poll_interval_ms: 3000,
            startup_timeout_ms: 300000,
            pod_id: '',
            template_id: '',
            endpoint_id: '',
            serverless_template_id: '',
            public_endpoint_protocol_default: 'auto',
            public_models: {}
        };
    }
    // 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',
            creds_file: '',
            sqlite_db: '',
            refresh_token: '',
            profile_arn: '',
            client_id: '',
            client_secret: ''
        };
    } else if (type === 'claude') {
        providersData[key].claude_config = {
            credentials_file: `~/.aisbf/claude_${key}_credentials.json`
        };
    } else if (type === 'kilocode') {
        providersData[key].kilo_config = {
            credentials_file: `~/.aisbf/kilo_${key}_credentials.json`,
            api_base: 'https://api.kilo.ai/api/gateway'
        };
    } else if (type === 'qwen') {
        providersData[key].qwen_config = {
            credentials_file: `~/.aisbf/qwen_${key}_credentials.json`,
            api_key: '',
            region: 'china-beijing',
            workspace_id: 'Default Workspace'
        };
    } else if (type === 'codex') {
        providersData[key].codex_config = {
            credentials_file: `~/.aisbf/codex_${key}_credentials.json`,
            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'
        };
    } else if (type === 'runpod') {
        providersData[key].runpod_config = {
            mode: 'pod',
            wrapper_mode: 'openai',
            account_name: key,
            management_api: 'auto',
            idle_shutdown_ms: 900000,
            startup_poll_interval_ms: 3000,
            startup_timeout_ms: 300000,
            pod_id: '',
            template_id: '',
            endpoint_id: '',
            serverless_template_id: '',
            public_endpoint_protocol_default: 'auto',
            public_models: {}
        };
    }

    // Immediately persist the new provider
    try {
        const result = await apiCall('POST', `${BASE_PATH}/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 = {
        'openai': 'https://api.openai.com/v1',
        'google': 'https://generativelanguage.googleapis.com/v1beta',
        'anthropic': 'https://api.anthropic.com/v1',
        'ollama': 'http://localhost:11434/api',
        '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',
        'runpod': 'https://rest.runpod.io/v1'
    };
    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) {
            sessionStorage.setItem('providers_page', currentProviderPage);
            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');
    }
}

// Defer until i18n is ready so translated strings are available
function _doInitialRender() {
    const savedPage = sessionStorage.getItem('providers_page');
    if (savedPage !== null) {
        currentProviderPage = parseInt(savedPage) || 0;
        sessionStorage.removeItem('providers_page');
    }
    renderProvidersList();
    // Kick off initial usage fetch for all usage-supporting providers
    refreshAllUsage().then(() => renderProvidersList());
}
if (window.i18n) { _doInitialRender(); } else { document.addEventListener('i18n:ready', _doInitialRender, {once: true}); }

</script>
{% endblock %}
