const state = {
token: '', csrf: '', actor: {}, permissions: new Set(), overview: {},
tenants: [], projects: [], keys: [], providers: [], models: [], billingAccounts: [], ledger: [],
usage: [], usagePaging: {cursor:'',nextCursor:'',history:[]}, usageSummary: [], usageDaily: [], usageAnalytics: {models:[],providers:[],keys:[]}, limits: [], users: [], audit: [], orders: [], refunds: [], disputes: [], invoices: [], sessions: [],
developerConfig: {base_url:'',endpoints:{}}, developerModels: [], preferences: {},
autoTopUp: {}, billingProfile: {},
mfa: {totp_enabled:false,passkeys:[]}, pendingMFA: null, authConfig: {}, playgroundKey: '', playgroundController: null, detailModel: null, detailUsage: null
};
const $ = (selector) => document.querySelector(selector);
const $$ = (selector) => [...document.querySelectorAll(selector)];
const can = (permission) => state.permissions.has(permission);
function esc(value) { return String(value ?? '').replace(/[&<>'"]/g, (char) => ({ '&':'&', '<':'<', '>':'>', "'":''', '"':'"' }[char])); }
function date(value) { return value ? new Date(value).toLocaleString() : '—'; }
function shortID(value) { const text = String(value || ''); return text ? `${text.slice(0, 10)}${text.length > 10 ? '…' : ''}` : '—'; }
function percent(part, total) { return total ? `${Math.round((part / total) * 100)}%` : '—'; }
function toast(message, error = false) { const node = $('#toast'); node.textContent = message; node.className = `toast visible ${error ? 'error' : ''}`; setTimeout(() => { node.className = 'toast'; }, 3200); }
async function api(path, options = {}) {
const method = (options.method || 'GET').toUpperCase();
const headers = { ...(options.body ? {'Content-Type':'application/json'} : {}), ...(options.headers || {}) };
if (state.token) headers.Authorization = `Bearer ${state.token}`;
if (!['GET','HEAD','OPTIONS'].includes(method) && state.csrf && !path.startsWith('/auth/login') && !path.startsWith('/auth/register')) headers['X-CSRF-Token'] = state.csrf;
const response = await fetch(`./api${path}`, { ...options, method, credentials:'same-origin', headers });
const payload = await response.json().catch(() => ({}));
if (!response.ok) { const error = new Error(payload?.error?.message || `Request failed (${response.status})`); error.status=response.status; error.type=payload?.error?.type; throw error; }
return payload;
}
function setConnected(connected) {
$('#auth-screen').classList.toggle('hidden', connected);
$('#console-app').classList.toggle('hidden', !connected);
if (!connected) { state.playgroundController?.abort();state.playgroundController=null;state.playgroundKey=''; const key=$('#playground-key');if(key)key.value=''; return; }
$('#connection-state').textContent = state.actor.role?.replaceAll('_', ' ') || 'connected';
$('#actor-label').textContent = state.actor.display_name || state.actor.email || 'Operator';
}
function formJSON(form) {
const data = new FormData(form);
// Password-manager username hints are intentionally decoys. They must not
// be sent to strict JSON endpoints that only accept the documented fields.
data.delete('username');
return Object.fromEntries(data.entries());
}
function selectOptions(items, valueKey, labelKey, empty = 'Select…') { return `${empty} ${items.map(item => `${esc(item[labelKey])} `).join('')}`; }
function decimalToScaled(value, digits) {
const match = String(value).trim().match(/^(-?)(\d+)(?:\.(\d+))?$/); if (!match) throw new Error('Enter a valid decimal amount');
const fraction = match[3] || ''; if (fraction.length > digits && /[1-9]/.test(fraction.slice(digits))) throw new Error(`Use at most ${digits} decimal places`);
const scale = 10n ** BigInt(digits); const absolute = BigInt(match[2]) * scale + BigInt((fraction.slice(0, digits) + '0'.repeat(digits)).slice(0, digits) || '0');
const result = Number(match[1] ? -absolute : absolute); if (!Number.isSafeInteger(result)) throw new Error('Amount is too large'); return result;
}
function scaledToDecimal(value, digits) { const number = BigInt(value || 0); const whole = number / (10n ** BigInt(digits)); const fraction = String(number % (10n ** BigInt(digits))).padStart(digits, '0').replace(/0+$/, ''); return fraction ? `${whole}.${fraction}` : String(whole); }
function currencyDigits(currency) { return ['bif','clp','djf','gnf','jpy','kmf','krw','mga','pyg','rwf','ugx','vnd','vuv','xaf','xof','xpf'].includes(currency) ? 0 : ['bhd','jod','kwd','omr','tnd'].includes(currency) ? 3 : 2; }
function money(micros, currency = state.overview.billing_currency || 'usd') { return new Intl.NumberFormat(undefined, { style:'currency', currency:currency.toUpperCase(), minimumFractionDigits:2, maximumFractionDigits:6 }).format(Number(micros || 0) / 1_000_000); }
function integer(value) { return new Intl.NumberFormat().format(Number(value || 0)); }
function chartHeightClass(value, maximum) { return `chart-height-${Math.max(1, Math.min(20, Math.ceil(Number(value || 0) / Math.max(1, Number(maximum || 0)) * 20)))}`; }
function emptyRow(span) { return `
No records yet `; }
function showSecret(title, value) { $('#secret-title').textContent = title; $('#created-secret').textContent = value; $('#secret-dialog').showModal(); }
function cookie(name) { const prefix=`${encodeURIComponent(name)}=`; const value=document.cookie.split('; ').find(item=>item.startsWith(prefix)); return value ? decodeURIComponent(value.slice(prefix.length)) : ''; }
function authError(message='', success=false) { const node=$('#auth-error');node.textContent=message;node.classList.toggle('success',Boolean(message)&&success); }
function showAuthPane(id) {
$$('.auth-pane').forEach(node => node.classList.toggle('active', node.id === id));
$$('.auth-tab').forEach(node => node.classList.toggle('active', node.dataset.authPane === id));
}
function base64URLBytes(value) {
const normalized=String(value).replaceAll('-','+').replaceAll('_','/');
const padded=normalized+'='.repeat((4-normalized.length%4)%4);
return Uint8Array.from(atob(padded),char=>char.charCodeAt(0));
}
function bytesBase64URL(value) {
if (value == null) return null;
const bytes=new Uint8Array(value); let text=''; bytes.forEach(byte=>{text+=String.fromCharCode(byte);});
return btoa(text).replaceAll('+','-').replaceAll('/','_').replace(/=+$/,'');
}
function browserPublicKey(options) {
const value=structuredClone(options.publicKey||options);
value.challenge=base64URLBytes(value.challenge);
if(value.user?.id)value.user.id=base64URLBytes(value.user.id);
if(value.excludeCredentials)value.excludeCredentials=value.excludeCredentials.map(item=>({...item,id:base64URLBytes(item.id)}));
if(value.allowCredentials)value.allowCredentials=value.allowCredentials.map(item=>({...item,id:base64URLBytes(item.id)}));
return value;
}
function browserCredential(value) {
const response={clientDataJSON:bytesBase64URL(value.response.clientDataJSON)};
for(const key of ['attestationObject','authenticatorData','signature','userHandle']){
if(value.response[key]!=null)response[key]=bytesBase64URL(value.response[key]);
}
if(typeof value.response.getTransports==='function')response.transports=value.response.getTransports();
return {id:value.id,rawId:bytesBase64URL(value.rawId),type:value.type,response,
authenticatorAttachment:value.authenticatorAttachment,clientExtensionResults:value.getClientExtensionResults()};
}
async function createPasskey(options) {
const credential=await navigator.credentials.create({publicKey:browserPublicKey(options)});
if(!credential)throw new Error('Passkey creation was cancelled');
return browserCredential(credential);
}
async function getPasskey(options) {
const credential=await navigator.credentials.get({publicKey:browserPublicKey(options)});
if(!credential)throw new Error('Passkey authentication was cancelled');
return browserCredential(credential);
}
async function permitted(permission, path) { if (!can(permission)) return []; return api(path); }
function defaultUsageQuery() { const to=new Date();const from=new Date(to.getTime()-29*86400000);return new URLSearchParams({from:from.toISOString().slice(0,10),to:to.toISOString().slice(0,10)}).toString(); }
async function loadAll(knownSession = null) {
try {
const session = knownSession || await api('/me'); state.actor = session.actor || {}; state.permissions = new Set(session.permissions || []);
state.overview = await api('/overview');
const usageQuery=defaultUsageQuery();
const results = await Promise.all([
permitted('tenants.read','/tenants'), permitted('projects.read','/projects'), permitted('keys.read','/keys'),
permitted('platform.read','/providers'), permitted('platform.read','/models'), permitted('overview.read','/developer/config'),
permitted('overview.read','/developer/models'), can('preferences.read') ? api('/developer/preferences') : {}, permitted('usage.read',`/usage?${usageQuery}&limit=50`),
permitted('usage.read','/usage/summary'), permitted('limits.read','/limits'), permitted('users.read','/users'),
permitted('audit.read','/audit'), state.overview.billing_enabled ? permitted('billing.read','/billing/accounts') : [],
state.overview.billing_enabled ? permitted('billing.read','/billing/ledger') : [],
state.actor.id ? api('/auth/mfa') : {totp_enabled:false,passkeys:[]},
state.actor.id ? api('/auth/sessions') : [],
state.overview.billing_enabled && can('billing.read') ? api('/billing/orders') : [],
state.overview.billing_enabled ? permitted('billing.read','/billing/refunds') : [],
state.overview.billing_enabled ? permitted('billing.read','/billing/disputes') : [],
state.overview.billing_enabled ? permitted('billing.read','/billing/invoices') : [],
permitted('usage.read',`/usage/daily?${usageQuery}`), permitted('usage.read',`/usage/analytics?${usageQuery}`),
state.overview.billing_enabled && state.actor.tenant_id && can('billing.read') ? api('/billing/auto-topup') : {},
state.overview.billing_enabled && state.actor.tenant_id && can('billing.read') ? api('/billing/profile') : {}
]);
[state.tenants,state.projects,state.keys,state.providers,state.models,state.developerConfig,state.developerModels,state.preferences,state.usage,state.usageSummary,state.limits,state.users,state.audit,state.billingAccounts,state.ledger,state.mfa,state.sessions,state.orders,state.refunds,state.disputes,state.invoices,state.usageDaily,state.usageAnalytics,state.autoTopUp,state.billingProfile] = results;
const usagePage=state.usage;state.usage=Array.isArray(usagePage)?usagePage:(usagePage?.data||[]);state.usagePaging={cursor:'',nextCursor:usagePage?.next_cursor||'',history:[]};
renderAll(); setConnected(true); return true;
} catch (error) { setConnected(false); if (error.status !== 401) toast(error.message, true); return false; }
}
function applyPermissions() {
$$('[data-permission]').forEach(node => node.classList.toggle('hidden', !can(node.dataset.permission)));
$('#billing-tab').classList.toggle('hidden', !state.overview.billing_enabled || !can('billing.read'));
$('#topup-form').classList.toggle('hidden', !state.overview.stripe_enabled || !can('billing.topup'));
$('#billing-portal').classList.toggle('hidden', !state.overview.stripe_enabled || !can('billing.topup'));
$('#account-tab').classList.toggle('hidden', !state.actor.id);
$('#developer-preferences-form').classList.toggle('hidden', !state.actor.tenant_id || !can('preferences.read'));
$('#billing-preferences-form').classList.toggle('hidden', !state.actor.tenant_id || !state.overview.billing_enabled || !can('billing.read'));
$('#auto-topup-panel').classList.toggle('hidden', !state.actor.tenant_id || !state.overview.billing_enabled || !can('billing.read'));
$('#billing-profile-panel').classList.toggle('hidden', !state.actor.tenant_id || !state.overview.billing_enabled || !can('billing.read'));
const active = $('.tab.active'); if (active?.classList.contains('hidden')) $('.tab[data-section="quickstart"]').click();
}
function renderAll() {
applyPermissions(); renderOverview(); renderQuickstart(); renderPreferences(); renderCatalog(); renderTenants(); renderProjects(); renderKeys(); renderProviders(); renderModels(); renderBilling();
renderUsage(); renderLimits(); renderUsers(); renderAudit(); renderRouteEditor(); renderSecurity();
}
function renderOverview() {
const data = state.overview; const propagation = !data.redis_configured ? 'PG polling' : data.redis_connected ? 'Redis live' : 'PG fallback';
const now = new Date(); const current = state.usageSummary.filter(item => { const period = new Date(item.period_start); return period.getUTCMonth() === now.getUTCMonth() && period.getUTCFullYear() === now.getUTCFullYear(); });
const totals = current.reduce((acc,item) => { acc.requests += item.request_count; acc.success += item.successful_requests; acc.tokens += item.total_tokens; acc.cost += item.cost_micros; return acc; }, {requests:0,success:0,tokens:0,cost:0});
const items = [['Requests', integer(totals.requests), 'this month'], ['Success rate', percent(totals.success,totals.requests), 'completed requests'], ['Tokens', integer(totals.tokens), 'this month'], ['Spend', money(totals.cost), 'metered cost'], ['Projects', data.projects, 'active workspaces'], ['Propagation', propagation, `generation ${data.runtime_generation}`]];
$('#metrics').innerHTML = items.map(([label,value,sub]) => `${label} ${esc(value)} ${esc(sub)} `).join('');
$('#overview-usage-body').innerHTML = current.map(item => `${esc(item.project_name)} ${integer(item.request_count)} ${percent(item.successful_requests,item.request_count)} ${integer(item.total_tokens)} ${money(item.cost_micros)} ${money(item.uncollected_micros)} `).join('') || emptyRow(6);
}
function goTo(section) { const node=$(`.tab[data-section="${section}"]`); if(node){ node.click(); window.scrollTo({top:0,behavior:'smooth'}); } }
function latestAvailableBalance() { return state.billingAccounts.find(item => !state.actor.tenant_id || item.tenant_id===state.actor.tenant_id)?.available_micros || 0; }
function inferenceBaseURL() { return String(state.developerConfig.base_url||window.location.origin).replace(/\/$/,''); }
function developerEndpoint(name) { const defaults={chat_completions:'/v1/chat/completions',responses:'/v1/responses',embeddings:'/v1/embeddings',messages:'/anthropic/v1/messages',models:'/v1/models'};const path=state.developerConfig.endpoints?.[name]||defaults[name]||'';return `${inferenceBaseURL()}${path}`; }
function modelIsUnavailable(model) { return model?.health_status==='unavailable'; }
function apiKeyDisplay(item) { return `${item?.key_prefix||''}${item?.key_suffix||''}`; }
function developerModelOption(item) { const unavailable=modelIsUnavailable(item);return `${esc(item.display_name||item.public_id)}${unavailable?' (unavailable)':''} `; }
function providerHealthSummary(item) {
const total=Number(item.provider_count||0);const available=Number(item.available_provider_count||0);const status=item.health_status||'online';
if(status==='unavailable')return {status,label:'Unavailable',detail:'No provider is currently accepting traffic'};
if(status==='degraded')return {status,label:'Degraded',detail:`${available} of ${total} providers accepting traffic`};
return {status:'online',label:'Online',detail:total?`${available} of ${total} providers accepting traffic`:'Routing is available'};
}
function renderQuickstart() {
const balance=latestAvailableBalance();
const balanceText=state.overview.billing_enabled && can('billing.read') ? `Available balance ${money(balance)}` : state.overview.billing_enabled ? 'Balance managed by billing admin' : 'Prepaid billing disabled';
$('#quickstart-balance').textContent=balanceText;
const hasKey=state.keys.some(item=>item.status==='active');
const hasUsage=state.usage.some(item=>item.success);
const funded=!state.overview.billing_enabled || !can('billing.read') || balance>0;
const steps=[
{title:'Workspace ready',body:'Your account and default project are ready.',done:Boolean(state.actor.tenant_id||state.actor.bootstrap),action:'projects'},
{title:'Add balance',body:state.overview.billing_enabled?(!can('billing.read')?'Billing is managed by workspace billing members.':funded?'Funds are available for inference.':'Add funds before the first billable request.'):'Billing is not enabled.',done:funded,action:can('billing.read')?'billing':'team'},
{title:'Create an API key',body:hasKey?'An active key can call the gateway.':'Create a key and copy its secret once.',done:hasKey,action:hasKey?'keys':'',target:hasKey?'':'starter-key-form'},
{title:'Make a request',body:hasUsage?'Your first successful request is recorded.':'Run the example below and watch it appear here.',done:hasUsage,action:hasUsage?'usage':'',target:hasUsage?'':'playground-form'}
];
$('#onboarding').innerHTML=steps.map(step=>`${step.done?'✓ ':''}${esc(step.title)} ${esc(step.body)} ${step.done?'Complete':'Open step →'} `).join('');
const signals=[['Models available',integer(state.developerModels.length)],['Active API keys',integer(state.keys.filter(item=>item.status==='active').length)],['This month',money(state.usageSummary.reduce((sum,item)=>sum+Number(item.cost_micros||0),0))],['Auto top-up',state.autoTopUp.enabled?'Enabled':state.autoTopUp.payment_method_configured?'Ready':'Not configured'],['Fallback model',state.preferences.fallback_model||'Not configured'],['Runtime',state.overview.redis_connected?'Live propagation':'PG fallback']];
$('#quickstart-signals').innerHTML=signals.map(([label,value])=>`${esc(label)} ${esc(value)}
`).join('');
const recent=state.usage.slice(0,5);
$('#quickstart-usage-body').innerHTML=recent.map(item=>`${date(item.started_at)} ${esc(item.public_model)} ${item.status_code} ${integer(item.total_tokens)} ${money(item.charged_micros||item.cost_micros)} ${integer(item.duration_ms)} ms `).join('')||emptyRow(6);
const modelSelect=$('#quickstart-model'); const previous=modelSelect.value||state.preferences.default_model;
modelSelect.innerHTML=state.developerModels.map(developerModelOption).join('')||'No available model ';
if(state.developerModels.some(item=>item.public_id===previous&&!modelIsUnavailable(item)))modelSelect.value=previous;
else{const firstAvailable=state.developerModels.find(item=>!modelIsUnavailable(item));if(firstAvailable)modelSelect.value=firstAvailable.public_id;}
const playgroundModel=$('#playground-model'); const playgroundPrevious=playgroundModel.value||modelSelect.value;
playgroundModel.innerHTML=modelSelect.innerHTML;
if(state.developerModels.some(item=>item.public_id===playgroundPrevious&&!modelIsUnavailable(item)))playgroundModel.value=playgroundPrevious;
if(state.playgroundKey)$('#playground-key').value=state.playgroundKey;
$('#playground-endpoint').textContent=inferenceBaseURL();
syncQuickstartProtocols(); syncPlaygroundProtocols(); renderQuickstartCode(); renderDeveloperAccess();
}
function renderDeveloperAccess() {
const form=$('#starter-key-form');const projects=state.projects.filter(item=>item.status==='active'&&(!state.actor.tenant_id||item.tenant_id===state.actor.tenant_id));const select=$('#starter-project');const previous=select.value;
select.innerHTML=projects.map(item=>`${esc(item.name)} `).join('')||'No active project ';
if(projects.some(item=>item.id===previous))select.value=previous;else{const preferred=projects.find(item=>item.slug==='default')||projects[0];if(preferred)select.value=preferred.id;}
const model=selectedDeveloperModel();$('#starter-model-label').textContent=model.public_id||'No model';
form.classList.toggle('hidden',!state.actor.tenant_id||!can('keys.write'));
$('#starter-key-submit').disabled=!select.value||!model.public_id||modelIsUnavailable(model);
const rows=[['OpenAI SDK base',`${inferenceBaseURL()}/v1`],['Anthropic SDK base',`${inferenceBaseURL()}/anthropic`],['Chat Completions',developerEndpoint('chat_completions')],['Responses',developerEndpoint('responses')],['Embeddings',developerEndpoint('embeddings')],['Anthropic Messages',developerEndpoint('messages')],['Models',developerEndpoint('models')]];
$('#endpoint-list').innerHTML=rows.map(([label,value],index)=>`${esc(label)} ${esc(value)}Copy
`).join('');
$('#endpoint-list').dataset.values=JSON.stringify(rows.map(([,value])=>value));
$('#endpoint-env').textContent=`export AIGW_API_KEY="your-key"\nexport OPENAI_BASE_URL="${inferenceBaseURL()}/v1"\nexport ANTHROPIC_BASE_URL="${inferenceBaseURL()}/anthropic"`;
}
function renderPreferences() {
const preferences=state.preferences||{}; const modelOptions=state.developerModels.map(item=>`${esc(item.display_name||item.public_id)} `).join('');
const defaultSelect=$('#preference-default-model'); const fallbackSelect=$('#preference-fallback-model');
defaultSelect.innerHTML=`First available model ${modelOptions}`;
fallbackSelect.innerHTML=`No workspace fallback ${modelOptions}`;
if(state.developerModels.some(item=>item.public_id===preferences.default_model))defaultSelect.value=preferences.default_model;
if(state.developerModels.some(item=>item.public_id===preferences.fallback_model))fallbackSelect.value=preferences.fallback_model;
const canWriteDeveloper=can('developer.preferences.write'); defaultSelect.disabled=!canWriteDeveloper; fallbackSelect.disabled=!canWriteDeveloper;
$('#low-balance-enabled').checked=preferences.low_balance_enabled!==false;
$('#low-balance-threshold').value=scaledToDecimal(preferences.low_balance_threshold_micros||0,6);
$('#spend-anomaly-enabled').checked=preferences.spend_anomaly_enabled!==false;
$('#spend-anomaly-multiplier').value=Number(preferences.spend_anomaly_multiplier||3);
$('#spend-anomaly-minimum').value=scaledToDecimal(preferences.spend_anomaly_min_micros||0,6);
const canWriteBilling=can('billing.preferences.write');for(const id of ['low-balance-enabled','low-balance-threshold','spend-anomaly-enabled','spend-anomaly-multiplier','spend-anomaly-minimum'])$(`#${id}`).disabled=!canWriteBilling;
$('#balance-alert-status').textContent=state.authConfig.email_delivery_enabled?'Verified billing members receive at most one alert of each type per day. Anomaly spend compares today with the previous seven-day daily average.':'These preferences are saved now and activate when SMTP delivery is configured.';
}
function selectedDeveloperModel() { return state.developerModels.find(item=>item.public_id===$('#quickstart-model')?.value) || state.developerModels.find(item=>!modelIsUnavailable(item)) || state.developerModels[0] || {}; }
function syncQuickstartProtocols() {
const model=selectedDeveloperModel(); const select=$('#quickstart-protocol'); const previous=select.value;
select.innerHTML=(model.supported_wire_apis||[]).map(apiName=>`${esc(modelProtocolLabel(apiName))} `).join('')||'OpenAI Chat Completions ';
if((model.supported_wire_apis||[]).includes(previous))select.value=previous;
syncProviderSelect('#quickstart-provider',model,select.value);
}
function selectedPlaygroundModel() { return state.developerModels.find(item=>item.public_id===$('#playground-model')?.value) || state.developerModels.find(item=>!modelIsUnavailable(item)) || state.developerModels[0] || {}; }
function syncPlaygroundProtocols() {
const model=selectedPlaygroundModel(); const select=$('#playground-protocol'); const previous=select.value;
select.innerHTML=(model.supported_wire_apis||[]).map(apiName=>`${esc(modelProtocolLabel(apiName))} `).join('')||'OpenAI Chat Completions ';
if((model.supported_wire_apis||[]).includes(previous))select.value=previous;
syncProviderSelect('#playground-provider',model,select.value);
const embeddings=select.value==='embeddings';const maxField=$('#playground-max-output-field');
maxField.classList.toggle('hidden',embeddings);$('#playground-max-output').required=!embeddings;
const max=Number(model.max_output_tokens||4096);$('#playground-max-output').max=String(max>0?max:4096);
if(Number($('#playground-max-output').value)>max&&max>0)$('#playground-max-output').value=String(max);
}
function syncProviderSelect(selector,model,wire) {
const select=$(selector);const previous=select.value;const providers=(model.providers||[]).filter(item=>item.wire_api===wire);
select.innerHTML=`Automatic routing ${providers.map(item=>`${esc(item.name||item.slug)} (${esc(item.slug)})${item.state==='open'?' — unavailable':''} `).join('')}`;
if(providers.some(item=>item.slug===previous&&item.state!=='open'))select.value=previous;
}
function modelSelector(model,providerSelector) { const provider=$(providerSelector)?.value||'';const publicID=model.public_id||'model-id';return provider?`${publicID}:${provider}`:publicID; }
function quickstartEndpoint(wire) { return wire==='messages'?developerEndpoint('messages'):wire==='responses'?developerEndpoint('responses'):wire==='embeddings'?developerEndpoint('embeddings'):developerEndpoint('chat_completions'); }
function renderQuickstartCode() {
const model=selectedDeveloperModel(); const selectedModel=modelSelector(model,'#quickstart-provider'); const wire=$('#quickstart-protocol').value||'chat_completions'; const language=$('#quickstart-language').value||'curl'; const endpoint=quickstartEndpoint(wire); const key='$AIGW_API_KEY';
const base=String(state.developerConfig.base_url||window.location.origin).replace(/\/$/,'');let code='';
if(language==='curl') {
const headers=wire==='messages'?`-H "x-api-key: ${key}"\n -H "anthropic-version: 2023-06-01"`:`-H "Authorization: Bearer ${key}"`;
const body=wire==='messages'?`{"model":"${selectedModel}","max_tokens":256,"messages":[{"role":"user","content":"Say hello in one sentence."}]}`:wire==='responses'?`{"model":"${selectedModel}","input":"Say hello in one sentence."}`:wire==='embeddings'?`{"model":"${selectedModel}","input":"AIGW semantic search"}`:`{"model":"${selectedModel}","messages":[{"role":"user","content":"Say hello in one sentence."}],"stream":false}`;
code=`export AIGW_API_KEY="your-key"\ncurl ${endpoint} \\\n ${headers} \\\n -H "Content-Type: application/json" \\\n -d '${body}'`;
} else if(language==='python') {
if(wire==='messages')code=`import os\nfrom anthropic import Anthropic\n\nclient = Anthropic(api_key=os.environ["AIGW_API_KEY"], base_url="${base}/anthropic")\nmessage = client.messages.create(model="${selectedModel}", max_tokens=256, messages=[{"role":"user", "content":"Say hello in one sentence."}])\nprint(message.content[0].text)`;
else if(wire==='responses')code=`import os\nfrom openai import OpenAI\n\nclient = OpenAI(api_key=os.environ["AIGW_API_KEY"], base_url="${base}/v1")\nresponse = client.responses.create(model="${selectedModel}", input="Say hello in one sentence.")\nprint(response.output_text)`;
else if(wire==='embeddings')code=`import os\nfrom openai import OpenAI\n\nclient = OpenAI(api_key=os.environ["AIGW_API_KEY"], base_url="${base}/v1")\nresponse = client.embeddings.create(model="${selectedModel}", input="AIGW semantic search")\nprint(len(response.data[0].embedding))`;
else code=`import os\nfrom openai import OpenAI\n\nclient = OpenAI(api_key=os.environ["AIGW_API_KEY"], base_url="${base}/v1")\nresponse = client.chat.completions.create(model="${selectedModel}", messages=[{"role":"user", "content":"Say hello in one sentence."}])\nprint(response.choices[0].message.content)`;
} else {
if(wire==='messages')code=`import Anthropic from "@anthropic-ai/sdk";\n\nconst client = new Anthropic({ apiKey: process.env.AIGW_API_KEY, baseURL: "${base}/anthropic" });\nconst message = await client.messages.create({ model: "${selectedModel}", max_tokens: 256, messages: [{ role: "user", content: "Say hello in one sentence." }] });\nconsole.log(message.content[0].text);`;
else if(wire==='responses')code=`import OpenAI from "openai";\n\nconst client = new OpenAI({ apiKey: process.env.AIGW_API_KEY, baseURL: "${base}/v1" });\nconst response = await client.responses.create({ model: "${selectedModel}", input: "Say hello in one sentence." });\nconsole.log(response.output_text);`;
else if(wire==='embeddings')code=`import OpenAI from "openai";\n\nconst client = new OpenAI({ apiKey: process.env.AIGW_API_KEY, baseURL: "${base}/v1" });\nconst response = await client.embeddings.create({ model: "${selectedModel}", input: "AIGW semantic search" });\nconsole.log(response.data[0].embedding.length);`;
else code=`import OpenAI from "openai";\n\nconst client = new OpenAI({ apiKey: process.env.AIGW_API_KEY, baseURL: "${base}/v1" });\nconst response = await client.chat.completions.create({ model: "${selectedModel}", messages: [{ role: "user", content: "Say hello in one sentence." }] });\nconsole.log(response.choices[0].message.content);`;
}
$('#quickstart-code-block code').textContent=code;
}
function playgroundBody(wire,model,prompt,maxOutput) {
if(wire==='messages')return {model,max_tokens:maxOutput,messages:[{role:'user',content:prompt}]};
if(wire==='responses')return {model,input:prompt,max_output_tokens:maxOutput,stream:false};
if(wire==='embeddings')return {model,input:prompt};
return {model,messages:[{role:'user',content:prompt}],max_tokens:maxOutput,stream:false};
}
function playgroundTokenCount(payload) {
const usage=payload?.usage;if(!usage)return 0;
return Number(usage.total_tokens ?? (Number(usage.input_tokens||usage.prompt_tokens||0)+Number(usage.output_tokens||usage.completion_tokens||0)));
}
function playgroundDiagnosis(status,network=false) {
if(network)return {title:'Gateway unreachable',message:'Verify the inference public URL, TLS certificate, and exact admin Origin allowed by the gateway.',action:'Open API keys',target:'keys'};
if(status===400)return {title:'Request rejected',message:'Check the selected protocol, prompt, and output limit against the model details.',action:'Review model',target:'catalog'};
if(status===401||status===403)return {title:'API key not authorized',message:'Use an active inference key and check its expiry, scopes, model allowlist, and monthly spend cap.',action:'Open API keys',target:'keys'};
if(status===402)return {title:'Balance required',message:'Add funds to the prepaid wallet, then retry this request. Automatic top-up can prevent future interruptions.',action:'Add funds',target:'billing'};
if(status===404)return {title:'Model route unavailable',message:'The model may be unavailable to this key or may not support the selected protocol.',action:'Choose a model',target:'catalog'};
if(status===409)return {title:'Request conflict',message:'Retry with a new request after the in-flight billing reservation or account change completes.',action:'View usage',target:'usage'};
if(status===429)return {title:'Limit reached',message:'Review request, token, concurrency, project, and API key limits before retrying.',action:'Review limits',target:'limits'};
if(status>=500)return {title:'Provider unavailable',message:'The gateway could not complete the upstream request after routing and failover. Keep the request ID for support.',action:'Choose another model',target:'catalog'};
return {title:'Request failed',message:'Review the response body and request ID. The request was not accepted as successful.',action:'View usage',target:'usage'};
}
function hidePlaygroundDiagnostic(){const node=$('#playground-diagnostic');node.classList.add('hidden');$('#playground-diagnostic-action').classList.add('hidden');}
function showPlaygroundDiagnostic(status,network=false) {
const diagnosis=playgroundDiagnosis(status,network);const action=$('#playground-diagnostic-action');
$('#playground-diagnostic-title').textContent=diagnosis.title;$('#playground-diagnostic-message').textContent=diagnosis.message;
action.textContent=diagnosis.action;action.dataset.target=diagnosis.target;action.classList.toggle('hidden',!$(`.tab[data-section="${diagnosis.target}"]`)||$(`.tab[data-section="${diagnosis.target}"]`).classList.contains('hidden'));
$('#playground-diagnostic').classList.remove('hidden');
}
async function runPlayground(event) {
event.preventDefault();
const key=$('#playground-key').value.trim();const model=modelSelector(selectedPlaygroundModel(),'#playground-provider');const wire=$('#playground-protocol').value;const prompt=$('#playground-prompt').value.trim();const maxOutput=Number($('#playground-max-output').value);
if(!key||!model||!prompt||(wire!=='embeddings'&&(!Number.isSafeInteger(maxOutput)||maxOutput<1)))return toast('Complete the API request fields',true);
state.playgroundKey=key;
const endpoint=quickstartEndpoint(wire);const headers={'Content-Type':'application/json'};
if(wire==='messages'){headers['X-API-Key']=key;headers['Anthropic-Version']='2023-06-01';}else headers.Authorization=`Bearer ${key}`;
const result=$('#playground-result');const status=$('#playground-status');const send=$('#playground-send');const stop=$('#playground-stop');
result.classList.remove('hidden');hidePlaygroundDiagnostic();status.className='badge';status.textContent='Sending';$('#playground-request-id').textContent='';$('#playground-duration').textContent='';$('#playground-tokens').textContent='';$('#playground-response').textContent='';
send.disabled=true;stop.classList.remove('hidden');const controller=new AbortController();state.playgroundController=controller;const started=performance.now();
try {
const response=await fetch(endpoint,{method:'POST',mode:'cors',credentials:'omit',cache:'no-store',headers,body:JSON.stringify(playgroundBody(wire,model,prompt,maxOutput)),signal:controller.signal});
const raw=await response.text();let payload;try{payload=raw?JSON.parse(raw):{};}catch{payload=raw;}
const requestID=response.headers.get('X-AIGW-Request-ID')||payload?.error?.request_id||'';const duration=Math.round(performance.now()-started);const tokens=playgroundTokenCount(payload);
status.className=`badge ${response.ok?'active':'suspended'}`;status.textContent=`${response.status} ${response.ok?'OK':'Error'}`;
$('#playground-request-id').textContent=requestID?`Request ${requestID}`:'';$('#playground-duration').textContent=`${integer(duration)} ms`;$('#playground-tokens').textContent=tokens?`${integer(tokens)} tokens`:'';
$('#playground-response').textContent=typeof payload==='string'?payload:JSON.stringify(payload,null,2);
if(!response.ok){showPlaygroundDiagnostic(response.status);toast(payload?.error?.message||`Request failed (${response.status})`,true);}else{hidePlaygroundDiagnostic();toast('Request completed');setTimeout(()=>loadAll(),900);}
} catch(error) {
const stopped=error.name==='AbortError';status.className='badge suspended';status.textContent=stopped?'Stopped':'Network error';$('#playground-duration').textContent=`${integer(Math.round(performance.now()-started))} ms`;$('#playground-response').textContent=stopped?'Request cancelled.':error.message;if(stopped)hidePlaygroundDiagnostic();else showPlaygroundDiagnostic(0,true);toast(stopped?'Request stopped':error.message,true);
} finally {
if(state.playgroundController===controller)state.playgroundController=null;send.disabled=false;stop.classList.add('hidden');
}
}
function renderCatalog() {
const ownerSelect=$('#catalog-owner');const ownerValue=ownerSelect?.value||'';const owners=[...new Set(state.developerModels.map(item=>String(item.owned_by||'').trim()).filter(Boolean))].sort((a,b)=>a.localeCompare(b));
if(ownerSelect){ownerSelect.innerHTML=`All developers ${owners.map(owner=>`${esc(owner)} `).join('')}`;if(owners.includes(ownerValue))ownerSelect.value=ownerValue;}
const search=String($('#catalog-search')?.value||'').trim().toLowerCase(); const protocol=$('#catalog-protocol')?.value||''; const input=$('#catalog-input')?.value||'';const owner=ownerSelect?.value||'';const sort=$('#catalog-sort')?.value||'newest';
const models=state.developerModels.filter(item=>{const hay=[item.public_id,item.display_name,item.owned_by,item.description,...(item.capabilities||[])].join(' ').toLowerCase();return (!search||hay.includes(search))&&(!protocol||(item.supported_wire_apis||[]).includes(protocol))&&(!input||(item.input_modalities||[]).includes(input))&&(!owner||item.owned_by===owner);});
const compareText=(a,b)=>String(a.display_name||a.public_id).localeCompare(String(b.display_name||b.public_id));
models.sort((a,b)=>sort==='name'?compareText(a,b):sort==='input_price'?Number(a.input_price_micros_per_million||0)-Number(b.input_price_micros_per_million||0)||compareText(a,b):sort==='output_price'?Number(a.output_price_micros_per_million||0)-Number(b.output_price_micros_per_million||0)||compareText(a,b):sort==='context'?Number(b.context_window||0)-Number(a.context_window||0)||compareText(a,b):new Date(b.released_at||0)-new Date(a.released_at||0)||compareText(a,b));
$('#catalog-count').textContent=`${models.length} of ${state.developerModels.length} models`;
$('#catalog-grid').innerHTML=models.map(item=>{const health=providerHealthSummary(item);return `${esc(item.owned_by||'MODEL')} ${esc(item.display_name||item.public_id)} ${esc(item.public_id)}
${esc(health.label)} ${esc(health.detail)}
${esc(item.description||'No description provided.')}
${(item.supported_wire_apis||[]).map(apiName=>`${esc(apiName)} `).join('')}${(item.input_modalities||[]).map(modality=>`${esc(modality)} input `).join('')}
${money(item.input_price_micros_per_million,item.price_currency)} in · ${money(item.output_price_micros_per_million,item.price_currency)} out / 1M tokens
${integer(item.context_window)} context · ${integer(item.max_output_tokens)} max output Details Use this model
`;}).join('')||'No models match these filters.
';
}
function modelProtocolLabel(value) { return value==='chat_completions'?'OpenAI Chat Completions':value==='responses'?'OpenAI Responses':value==='embeddings'?'OpenAI Embeddings':value==='messages'?'Anthropic Messages':value; }
function showModelDetails(publicID) {
const model=state.developerModels.find(item=>item.public_id===publicID);if(!model)return;
state.detailModel=model;$('#model-dialog-title').textContent=model.display_name||model.public_id;$('#model-dialog-id').textContent=model.public_id;
const lifecycle=model.lifecycle||'active';const status=lifecycle==='retired'?'retired':lifecycle==='deprecated'?'deprecated':lifecycle==='preview'?'preview':'available';
const health=providerHealthSummary(model);const rows=[['Status',status],['Runtime',health.label],['Providers',health.detail],['Owner',model.owned_by||'—'],['Protocols',(model.supported_wire_apis||[]).map(modelProtocolLabel).join(', ')||'—'],['Input',(model.input_modalities||[]).join(', ')||'—'],['Output',(model.output_modalities||[]).join(', ')||'—'],['Context',`${integer(model.context_window)} tokens`],['Max output',`${integer(model.max_output_tokens)} tokens`],['Released',date(model.released_at)],['Capabilities',(model.capabilities||[]).join(', ')||'—'],['Aliases',(model.aliases||[]).join(', ')||'—']];
$('#model-detail-grid').innerHTML=rows.map(([label,value])=>`${esc(label)} ${esc(value)}
`).join('');
const providers=model.providers||[];$('#model-provider-health').classList.toggle('hidden',providers.length===0);
const activeProbeCount=providers.reduce((sum,item)=>sum+Number(item.active_probes||0),0);
const sharedCount=providers.reduce((sum,item)=>sum+Number(item.shared_attempts||0)+Number(item.shared_ttft_samples||0),0);
$('#model-provider-health .section-heading small').textContent=sharedCount>0?'Measured from requests shared across gateway instances.':activeProbeCount>0?'Measured from requests and authenticated availability probes on this gateway instance.':'Measured from requests handled by this gateway instance.';
$('#model-provider-health-body').innerHTML=providers.map(item=>{const measured=Number(item.recent_samples||0)>0;const availability=measured?`${Number(item.availability_percent||0).toFixed(1)}%`:'Not measured';const ttft=Number(item.ttft_ewma_ms||0)>0?`${integer(item.ttft_ewma_ms)} ms`:'Not measured';const latency=Number(item.header_latency_ewma_ms||0)>0?`${integer(item.header_latency_ewma_ms)} ms`:'Not measured';const retry=item.circuit_open_until?`Retry ${date(item.circuit_open_until)}`:'';const probes=Number(item.active_probes||0)>0?` · ${integer(item.active_probes)} probes`:'';const shared=Number(item.shared_attempts||0)>0?` · ${integer(item.shared_attempts)} shared`:'';return `${esc(item.name||item.slug)} ${esc(item.slug)} · ${esc(modelProtocolLabel(item.wire_api)||item.protocol||'')} ${esc(item.state==='open'?'Circuit open':item.state)} ${retry?`${esc(retry)} `:''}${esc(availability)} ${esc(ttft)} ${esc(latency)} ${integer(item.attempts||0)}${esc(shared)}${esc(probes)} `;}).join('');
$('#model-dialog-playground').disabled=modelIsUnavailable(model);$('#estimate-input').value='1000';$('#estimate-output').value='500';$('#estimate-cache-read').value='0';$('#estimate-cache-write').value='0';renderModelEstimate();$('#model-dialog').showModal();
}
function renderModelEstimate() {
const model=state.detailModel;if(!model)return;const input=Math.max(0,Number($('#estimate-input').value||0));const output=Math.max(0,Number($('#estimate-output').value||0));const cacheRead=Math.max(0,Number($('#estimate-cache-read').value||0));const cacheWrite=Math.max(0,Number($('#estimate-cache-write').value||0));const micros=(input*Number(model.input_price_micros_per_million||0)+output*Number(model.output_price_micros_per_million||0)+cacheRead*Number(model.cache_read_price_micros_per_million||0)+cacheWrite*Number(model.cache_write_price_micros_per_million||0))/1_000_000;$('#model-estimate').textContent=`Estimated cost ${money(Math.round(micros),model.price_currency||state.overview.billing_currency||'usd')}`;
}
function useModelInPlayground(publicID) { const model=state.developerModels.find(item=>item.public_id===publicID);if(!model||modelIsUnavailable(model)){toast('This model has no provider currently accepting traffic',true);return;}$('#quickstart-model').value=publicID;$('#playground-model').value=publicID;syncQuickstartProtocols();syncPlaygroundProtocols();$('#quickstart-provider').value='';$('#playground-provider').value='';renderQuickstartCode();if($('#model-dialog').open)$('#model-dialog').close();goTo('quickstart'); }
function renderTenants() {
$('#tenants-body').innerHTML = state.tenants.map(item => `${esc(item.name)} ${esc(item.slug)}${esc(item.status)} ${date(item.created_at)} `).join('') || emptyRow(4);
['project-tenant','key-tenant','topup-tenant','adjustment-tenant','user-tenant'].forEach(id => { const node=$(`#${id}`); if (node) node.innerHTML=selectOptions(state.tenants,'id','name', state.actor.tenant_id ? 'Current tenant' : 'Select tenant…'); });
if (state.actor.tenant_id) ['project-tenant','key-tenant','topup-tenant','adjustment-tenant','user-tenant'].forEach(id => { const node=$(`#${id}`); if (node) node.value=state.actor.tenant_id; });
}
function renderProjects() { $('#projects-body').innerHTML = state.projects.map(item => `${esc(item.name)} ${shortID(item.tenant_id)}${esc(item.slug)} ${esc(item.status)} `).join('') || emptyRow(4); renderKeyProjects(); }
function renderKeyProjects() { const tenant=$('#key-tenant').value;const node=$('#key-project');const selected=node.value;const projects=state.projects.filter(item=>!tenant||item.tenant_id===tenant);node.innerHTML=selectOptions(projects,'id','name');if(projects.some(item=>item.id===selected))node.value=selected;else if(projects.length===1)node.value=projects[0].id; }
function renderKeys() {
const picker=$('#key-models');const selected=new Set([...picker.selectedOptions].map(option=>option.value));picker.innerHTML=state.developerModels.map(item=>`${esc(item.display_name||item.public_id)} (${esc(item.public_id)}) `).join('');[...picker.options].forEach(option=>{option.selected=selected.has(option.value);});
$('#keys-body').innerHTML = state.keys.map(item => {
const models=item.allowed_models||[];const expires=item.expires_at?date(item.expires_at):'Never';const tags=(item.tags||[]).map(tag=>`${esc(tag)} `).join('');
const spent=Number(item.current_month_spend_micros||0),reserved=Number(item.current_month_reserved_micros||0),cap=Number(item.monthly_spend_micros||0),remaining=Math.max(0,cap-spent-reserved);
const daySpent=Number(item.current_day_spend_micros||0),dayReserved=Number(item.current_day_reserved_micros||0),dayCap=Number(item.daily_spend_micros||0),dayRemaining=Math.max(0,dayCap-daySpent-dayReserved);
const effectiveStatus=item.status==='active'&&item.expires_at&&new Date(item.expires_at)<=new Date()?'expired':item.status;
const actions=can('keys.write')&&item.status!=='revoked'?`${item.status==='active'?`Disable `:`Enable `} Rotate Revoke `:'';
return `${esc(item.name)} ${tags?`${tags} `:''}${esc(apiKeyDisplay(item))}${shortID(item.project_id)}${(item.scopes||[]).map(scope=>`${esc(scope)} `).join('')}${models.length?`${integer(models.length)} selected model${models.length===1?'':'s'}`:'All visible models'} ${money(spent)} this month ${integer(item.current_month_requests)} requests · ${money(reserved)} reserved ${money(daySpent)} today · ${integer(item.current_day_requests)} requests ${cap?`${money(remaining)} monthly left`:'Monthly unlimited'}${dayCap?`${money(dayRemaining)} daily left`:'Daily unlimited'} ${item.requests_per_minute?`${integer(item.requests_per_minute)} RPM`:'RPM unlimited'} · ${item.tokens_per_minute?`${integer(item.tokens_per_minute)} TPM`:'TPM unlimited'} · Expires: ${esc(expires)} ${esc(effectiveStatus)} Last used: ${esc(date(item.last_used_at))} ${actions} `;
}).join('') || emptyRow(8);
}
function renderProviders() { $('#providers-body').innerHTML = state.providers.map(item => `${esc(item.name)} ${esc(item.slug)}${esc(item.protocol)} ${esc(item.wire_api)} ${esc(item.base_url)} ${integer(item.route_count)} ${item.enabled?'enabled':'disabled'} ${can('platform.write')?`${item.enabled?'Disable':'Enable'} `:''} `).join('') || emptyRow(6); }
function renderModels() { $('#models-body').innerHTML = state.models.map(item => `${esc(item.public_id)} ${esc(item.display_name||'')} · ${integer(item.context_window)} ctx · ${esc((item.input_modalities||[]).join('+'))} → ${esc((item.output_modalities||[]).join('+'))} ${esc(item.owned_by||'—')}v${item.price_version||1} ${esc(item.price_currency||'usd')} · in ${money(item.input_price_micros_per_million,item.price_currency)}/1M · out ${money(item.output_price_micros_per_million,item.price_currency)}/1M ${(item.routes||[]).map(route=>`${esc(route.provider_name||route.provider_id).slice(0,24)} → ${esc(route.upstream_model)} p${route.priority} / w${route.weight} `).join('')}
${esc(item.lifecycle||'active')} ${can('platform.write')?`${item.enabled?'Disable':'Enable'} `:''} `).join('') || emptyRow(5); }
function renderBilling() {
$('#billing-currency').textContent=(state.overview.billing_currency||'').toUpperCase();
$('#billing-accounts-body').innerHTML=state.billingAccounts.map(item=>`${esc(item.tenant_name)} ${shortID(item.tenant_id)}${money(item.balance_micros,item.currency)} ${money(item.reserved_micros,item.currency)} ${money(item.available_micros,item.currency)} ${date(item.updated_at)} `).join('')||emptyRow(5);
$('#billing-ledger-body').innerHTML=state.ledger.map(item=>{const requestReference=item.kind==='usage'&&item.source_type==='request'&&item.source_id;const reference=requestReference?`${shortID(item.source_id)} `:`${shortID(item.source_id)}`;return `${date(item.created_at)} ${shortID(item.tenant_id)}${esc(item.kind)} ${money(item.amount_micros,item.currency)} ${money(item.balance_after_micros,item.currency)} ${reference} `;}).join('')||emptyRow(6);
const orders=state.orders||[];$('#billing-orders-body').innerHTML=orders.map(item=>`${date(item.created_at)}${item.trigger_type==='auto'?'automatic ':''} ${money(item.amount_micros,item.currency)} ${esc(item.status)} ${esc(item.reconciliation_status||'unknown')} ${item.invoice_url?`Invoice `:''} ${item.invoice_pdf_url?`PDF `:''} ${item.receipt_url?`Receipt `:''} ${item.trigger_type!=='auto'&&['failed','expired'].includes(item.status)?`Retry `:''}${can('billing.adjust')&&item.status==='pending'&&item.reconciliation_status==='missing'?` Resolve `:''}${can('billing.adjust')&&item.status==='paid'&&item.reconciliation_status==='missing'&&!item.stripe_payment_intent_id?` Reverse `:''}${can('billing.adjust')&&['paid','partially_refunded'].includes(item.status)?` Refund `:''} `).join('')||emptyRow(6);
$('#refunds-body').innerHTML=(state.refunds||[]).map(item=>`${date(item.created_at)} ${shortID(item.topup_order_id)}${money(item.amount_micros,item.currency)} ${esc(item.status)} ${esc(item.last_error||'—')} `).join('')||emptyRow(5);
$('#disputes-body').innerHTML=(state.disputes||[]).map(item=>`${date(item.updated_at)} ${money(item.amount_micros,item.currency)} ${esc(item.status)} ${esc(item.reason)} ${date(item.due_by)} `).join('')||emptyRow(5);
renderBillingProfile();
renderAutoTopUp();
}
function renderBillingProfile(){
if(!state.actor.tenant_id)return;const item=state.billingProfile||{};const form=$('#billing-profile-form');
for(const name of ['legal_name','billing_email','address_line1','address_line2','city','region','postal_code','country'])form.elements[name].value=item[name]||'';
const labels={not_configured:'Not configured',checkout_managed:'Stripe managed',disabled:'Saved locally',pending:'Syncing',synced:'Stripe synced',failed:'Sync needed'};const status=$('#billing-profile-status');const label=labels[item.stripe_sync_status]||'Not configured';status.textContent=label;status.className=`badge ${item.stripe_sync_status==='synced'?'active':item.stripe_sync_status==='failed'?'suspended':''}`;
$('#billing-profile-error').textContent=item.stripe_sync_error||'';form.querySelector('button[type=submit]').disabled=!can('billing.topup');
$('#billing-portal').disabled=!item.stripe_customer_configured;
}
function renderAutoTopUp(){
const item=state.autoTopUp||{};if(!state.actor.tenant_id)return;
const currency=item.currency||state.overview.billing_currency||'usd';const digits=currencyDigits(currency);
$('#auto-topup-enabled').checked=Boolean(item.enabled);$('#auto-topup-threshold').value=scaledToDecimal(item.threshold_micros||0,6);$('#auto-topup-amount').value=scaledToDecimal(item.topup_amount_minor||0,digits);
const status=$('#auto-topup-status');status.textContent=String(item.status||'not_configured').replaceAll('_',' ');status.className=`badge ${item.enabled&&item.status==='ready'?'active':item.status==='action_required'||item.status==='failed'?'suspended':''}`;
$('#auto-topup-payment-method').textContent=item.payment_method_configured?`${item.payment_method_brand||item.payment_method_type||'payment method'} •••• ${item.payment_method_last4||''}${item.payment_method_exp_month?` · ${String(item.payment_method_exp_month).padStart(2,'0')}/${item.payment_method_exp_year}`:''}`:'Not saved';
$('#auto-topup-error').textContent=item.last_error||'';
const writable=can('billing.topup');$$('#auto-topup-form input').forEach(node=>node.disabled=!writable);$('#auto-topup-form button[type=submit]').disabled=!writable;$('#auto-topup-payment-setup').disabled=!writable||!item.stripe_enabled;
$('#auto-topup-payment-setup').textContent=item.payment_method_configured?'Replace payment method':'Save payment method';
}
function renderSecurity() {
const totp=Boolean(state.mfa?.totp_enabled);
$$('#account [autocomplete="username"]').forEach(node=>{node.value=state.actor.email||'';});
$('#totp-action').textContent=totp?'Disable TOTP':'Set up TOTP';
$('#totp-disable-code').classList.toggle('hidden',!totp);
$('#totp-disable-code input').required=totp;
$('#sessions-body').innerHTML=(state.sessions||[]).map(item=>`${esc(item.current?'Current device':'Browser session')} ${esc(item.user_agent||'Unknown client')} ${esc(item.remote_ip||'—')}${esc(item.auth_method)} ${date(item.last_seen_at)} Sign out `).join('')||emptyRow(5);
$('#passkeys-body').innerHTML=(state.mfa?.passkeys||[]).map(item=>`${esc(item.name)} ${date(item.created_at)} ${date(item.last_used_at)} Delete `).join('')||emptyRow(4);
$('#orders-body').innerHTML=(state.orders||[]).map(item=>`${date(item.created_at)} ${money(item.amount_micros,item.currency)} ${esc(item.status)} ${shortID(item.id)} `).join('')||emptyRow(4);
}
function renderUsage() {
const platformDiagnostics=!state.actor.tenant_id&&can('platform.read');
const project=$('#usage-project'),key=$('#usage-key'),model=$('#usage-model'),provider=$('#usage-provider');
const projectValue=project.value,keyValue=key.value,modelValue=model.value,providerValue=provider.value;
const providers=new Map();if(platformDiagnostics)state.developerModels.forEach(item=>(item.providers||[]).forEach(route=>providers.set(route.slug,route.name||route.slug)));
project.innerHTML=`All projects ${state.projects.map(item=>`${esc(item.name)} `).join('')}`;
key.innerHTML=`All API keys ${state.keys.map(item=>`${esc(item.name)} (${esc(apiKeyDisplay(item))}) `).join('')}`;
model.innerHTML=`All models ${state.developerModels.map(item=>`${esc(item.display_name||item.public_id)} `).join('')}`;
provider.innerHTML=`All providers ${[...providers].sort((a,b)=>a[1].localeCompare(b[1])).map(([slug,name])=>`${esc(name)} (${esc(slug)}) `).join('')}`;
if(projectValue)project.value=projectValue;if(keyValue)key.value=keyValue;if(modelValue)model.value=modelValue;if(providerValue&&platformDiagnostics)provider.value=providerValue;
$('#usage-provider-filter').classList.toggle('hidden',!platformDiagnostics);$('#usage-provider-panel').classList.toggle('hidden',!platformDiagnostics);
if(!$('#usage-from').value){const query=new URLSearchParams(defaultUsageQuery());$('#usage-from').value=query.get('from');$('#usage-to').value=query.get('to');}
const points=state.usageDaily||[];const totals=points.reduce((acc,item)=>{acc.requests+=Number(item.request_count||0);acc.success+=Number(item.successful_requests||0);acc.tokens+=Number(item.total_tokens||0);acc.charged+=Number(item.charged_micros||0);acc.duration+=Number(item.average_duration_ms||0)*Number(item.request_count||0);acc.p50=Math.max(acc.p50,Number(item.p50_duration_ms||0));acc.p95=Math.max(acc.p95,Number(item.p95_duration_ms||0));acc.ttftP95=Math.max(acc.ttftP95,Number(item.p95_ttft_ms||0));return acc;},{requests:0,success:0,tokens:0,charged:0,duration:0,p50:0,p95:0,ttftP95:0});
const metrics=[['Requests',integer(totals.requests),'selected range'],['Success rate',percent(totals.success,totals.requests),'completed requests'],['Tokens',integer(totals.tokens),'input and output'],['Charged',money(totals.charged),'wallet debit'],['Average latency',totals.requests?`${integer(Math.round(totals.duration/totals.requests))} ms`:'—','request weighted'],['P50 latency',totals.p50?`${integer(totals.p50)} ms`:'—','highest daily median'],['P95 latency',totals.p95?`${integer(totals.p95)} ms`:'—','highest daily P95'],['P95 TTFT',totals.ttftP95?`${integer(totals.ttftP95)} ms`:'—','highest daily P95']];$('#usage-metrics').innerHTML=metrics.map(([label,value,sub])=>`${label} ${esc(value)} ${esc(sub)} `).join('');
const maxRequests=Math.max(1,...points.map(item=>Number(item.request_count||0)));$('#usage-chart').innerHTML=points.length?`${points.map(item=>`
${new Date(item.day).toLocaleDateString(undefined,{month:'short',day:'numeric'})} `).join('')}
`:'No usage in this range
';
$('#usage-summary-body').innerHTML=state.usageSummary.map(item=>`${new Date(item.period_start).toLocaleDateString(undefined,{year:'numeric',month:'short'})} ${esc(item.project_name)} ${integer(item.request_count)} ${percent(item.successful_requests,item.request_count)} ${integer(item.input_tokens)} ${integer(item.output_tokens)} ${money(item.cost_micros)} `).join('')||emptyRow(7);
const analytics=Array.isArray(state.usageAnalytics)?{models:[],providers:[],keys:[]}:state.usageAnalytics||{models:[],providers:[],keys:[]};
const changeLabel=item=>item.charge_change_percent==null?(Number(item.previous_charged_micros||0)===0&&Number(item.charged_micros||0)>0?'New':'—'):`${Number(item.charge_change_percent)>=0?'+':''}${Number(item.charge_change_percent).toFixed(1)}%`;
const changeClass=item=>item.charge_change_percent==null?(Number(item.charged_micros||0)>0?'positive':''):Number(item.charge_change_percent)>0?'money-negative':'positive';
const cacheRate=item=>{const denominator=Number(item.input_tokens||0)+Number(item.cache_read_input_tokens||0)+Number(item.cache_creation_input_tokens||0);return denominator?percent(item.cache_read_input_tokens,denominator):'—';};
const latencyPair=item=>`${integer(item.p50_duration_ms)} / ${integer(item.p95_duration_ms)} ms`;
const ttftPair=item=>item.p95_ttft_ms?`${integer(item.p50_ttft_ms)} / ${integer(item.p95_ttft_ms)} ms`:'—';
$('#usage-model-analytics-body').innerHTML=(analytics.models||[]).map(item=>`${esc(item.public_model)} ${platformDiagnostics?`${integer(item.provider_count)} provider${Number(item.provider_count)===1?'':'s'} `:''}${integer(item.request_count)} ${percent(item.successful_requests,item.request_count)} ${integer(item.total_tokens)} ${money(item.charged_micros)} ${changeLabel(item)} ${latencyPair(item)} ${ttftPair(item)} ${integer(item.missing_usage_requests)} `).join('')||emptyRow(9);
$('#usage-key-analytics-body').innerHTML=(analytics.keys||[]).map(item=>`${esc(item.key_name)} ${shortID(item.key_id)} ${integer(item.request_count)} ${percent(item.successful_requests,item.request_count)} ${integer(item.model_count)} ${integer(item.total_tokens)} ${money(item.charged_micros)} ${latencyPair(item)} ${ttftPair(item)} `).join('')||emptyRow(8);
$('#usage-provider-analytics-body').innerHTML=(analytics.providers||[]).map(item=>`${esc(item.provider_name)} ${esc(item.wire_api||'unknown')} ${integer(item.request_count)} ${percent(item.successful_requests,item.request_count)} ${integer(item.model_count)} ${cacheRate(item)} ${money(item.charged_micros)} ${changeLabel(item)} ${latencyPair(item)} ${ttftPair(item)} `).join('')||emptyRow(9);
$('#usage-events-body').innerHTML=state.usage.map((item,index)=>`${date(item.started_at)} ${shortID(item.request_id)}${esc(item.protocol)}${item.attempts>1?` · ${item.attempts} attempts`:''} ${esc(item.project_name||shortID(item.project_id))}${esc(item.key_name||shortID(item.key_id))} ${esc(item.public_model)}${platformDiagnostics&&item.provider_name?`${esc(item.provider_name)} `:''} ${item.status_code} ${item.error_type?`${esc(item.error_type)} `:''}${esc(item.metering_status||'')} ${integer(item.total_tokens)} ${money(item.charged_micros)}${item.uncollected_micros?`${money(item.uncollected_micros)} uncollected `:''} ${integer(item.duration_ms)} msTTFT ${item.ttft_ms?`${integer(item.ttft_ms)} ms`:'—'} Details `).join('')||emptyRow(9);
$('#usage-page-label').textContent=`Page ${state.usagePaging.history.length+1}`;$('#usage-page-prev').disabled=state.usagePaging.history.length===0;$('#usage-page-next').disabled=!state.usagePaging.nextCursor;
}
function usageDiagnostic(item) {
const diagnostic={request_id:item.request_id,started_at:item.started_at,status_code:item.status_code,success:Boolean(item.success),error_type:item.error_type||'',project:{id:item.project_id,name:item.project_name||''},api_key:{id:item.key_id,name:item.key_name||''},model:{public_id:item.public_model},transport:{protocol:item.protocol,stream:Boolean(item.stream),attempts:Number(item.attempts||0),duration_ms:Number(item.duration_ms||0),ttft_ms:Number(item.ttft_ms||0)},usage:{input_tokens:Number(item.input_tokens||0),output_tokens:Number(item.output_tokens||0),total_tokens:Number(item.total_tokens||0),cache_read_input_tokens:Number(item.cache_read_input_tokens||0),cache_creation_input_tokens:Number(item.cache_creation_input_tokens||0),reported:Boolean(item.usage_reported)},billing:{cost_micros:Number(item.cost_micros||0),charged_micros:Number(item.charged_micros||0),uncollected_micros:Number(item.uncollected_micros||0),metering_status:item.metering_status||''}};
if(!state.actor.tenant_id&&can('platform.read'))diagnostic.model.route={provider:item.provider_name||item.provider_id||'',upstream_id:item.upstream_model||''};return diagnostic;
}
function openUsageDetails(item) {
if(!item)return;state.detailUsage=item;$('#usage-dialog-title').textContent=item.public_model||'Request details';$('#usage-dialog-id').textContent=item.request_id;
const throughput=item.duration_ms>0&&item.output_tokens>0?`${(Number(item.output_tokens)*1000/Number(item.duration_ms)).toFixed(1)} output tokens/s`:'—';const rows=[['Time',date(item.started_at)],['Status',`${item.status_code} · ${item.success?'success':'error'}`],['Project',item.project_name||shortID(item.project_id)],['API key',item.key_name||shortID(item.key_id)],['Protocol',`${item.protocol}${item.stream?' · stream':''}`],['Public model',item.public_model],['Attempts',integer(item.attempts)],['Latency',`${integer(item.duration_ms)} ms`],['TTFT',item.ttft_ms?`${integer(item.ttft_ms)} ms`:'—'],['Throughput',throughput],['Tokens',`${integer(item.input_tokens)} in · ${integer(item.output_tokens)} out`],['Cache',`${integer(item.cache_read_input_tokens)} read · ${integer(item.cache_creation_input_tokens)} write`],['Charged',money(item.charged_micros)],['Metering',item.metering_status||'—'],['Error',item.error_type||'—']];
if(!state.actor.tenant_id&&can('platform.read'))rows.splice(6,0,['Provider',item.provider_name||item.provider_id||'—'],['Upstream model',item.upstream_model||'—']);
$('#usage-detail-grid').innerHTML=rows.map(([label,value])=>`${esc(label)} ${esc(value)}
`).join('');$('#usage-diagnostic').textContent=JSON.stringify(usageDiagnostic(item),null,2);const releaseForm=$('#release-reservation-form');releaseForm.classList.toggle('hidden',!can('billing.adjust')||item.metering_status!=='missing');releaseForm.reset();$('#usage-dialog').showModal();
}
function showUsageDetails(index) { openUsageDetails(state.usage[Number(index)]); }
async function showLedgerRequest(requestID,tenantID) {
const params=new URLSearchParams({request_id:requestID,limit:'1'});if(!state.actor.tenant_id&&tenantID)params.set('tenant_id',tenantID);
const page=await api(`/usage?${params}`);const items=Array.isArray(page)?page:(page?.data||[]);if(!items.length)throw new Error('The request record is no longer available');openUsageDetails(items[0]);
}
function renderLimits() {
const existing=new Map(state.limits.map(item=>[item.project_id,item]));
$('#limits-body').innerHTML=state.projects.map(project=>{const item=existing.get(project.id)||{};return `${esc(project.name)} ${shortID(project.id)}${can('limits.write')?'Save ':''} `}).join('')||emptyRow(6);
}
function renderUsers() {
const roles = state.actor.tenant_id ? [['tenant_admin','Tenant admin'],['tenant_billing','Billing'],['tenant_developer','Developer'],['tenant_viewer','Viewer']] : [['platform_admin','Platform admin'],['platform_viewer','Platform viewer'],['tenant_admin','Tenant admin'],['tenant_billing','Billing'],['tenant_developer','Developer'],['tenant_viewer','Viewer']];
$('#user-role').innerHTML=roles.map(([value,label])=>`${label} `).join('');
$('#users-body').innerHTML=state.users.map(item=>`${esc(item.display_name)} ${esc(item.email)} ${esc(item.role.replaceAll('_',' '))} ${shortID(item.tenant_id)}${item.email_verified?'verified ':'pending '} ${(item.mfa_methods||[]).map(method=>`${esc(method)} `).join('')} ${date(item.last_used_at)} ${esc(item.status)} ${item.status!=='revoked'&&can('users.write')?`Revoke `:''} `).join('')||emptyRow(7);
}
function renderAudit() { $('#audit-body').innerHTML=state.audit.map(item=>`${date(item.created_at)} ${esc(item.actor_role.replaceAll('_',' '))} ${esc(item.action)} ${esc(item.method)}${item.status_code} ${shortID(item.request_id)}${esc(item.remote_ip||'—')} `).join('')||emptyRow(7); }
function renderRouteEditor() { const current=$('#route-editor');if(!current.children.length&&can('platform.write'))addRoute();$$('.route-provider').forEach(select=>{const selected=select.value;select.innerHTML=selectOptions(state.providers.filter(item=>item.enabled),'id','name','Provider…');select.value=selected;}); }
function addRoute() { const wrapper=document.createElement('div');wrapper.className='route-row';wrapper.innerHTML='× ';$('#route-editor').appendChild(wrapper);renderRouteEditor(); }
document.addEventListener('click',async(event)=>{
const authTab=event.target.closest('.auth-tab');if(authTab){$$('.auth-tab').forEach(node=>node.classList.toggle('active',node===authTab));$$('.auth-pane').forEach(node=>node.classList.toggle('active',node.id===authTab.dataset.authPane));authError();return;}
const tab=event.target.closest('.tab');if(tab){$$('.tab').forEach(node=>node.classList.toggle('active',node===tab));$$('.section').forEach(node=>node.classList.toggle('active',node.id===tab.dataset.section));return;}
const goto=event.target.closest('[data-goto]');if(goto){goTo(goto.dataset.goto);return;}
const scroll=event.target.closest('[data-scroll-to]');if(scroll){document.getElementById(scroll.dataset.scrollTo)?.scrollIntoView({behavior:'smooth',block:'start'});return;}
const endpointCopy=event.target.closest('[data-copy-endpoint]');if(endpointCopy){try{const values=JSON.parse($('#endpoint-list').dataset.values||'[]');await navigator.clipboard.writeText(values[Number(endpointCopy.dataset.copyEndpoint)]||'');toast('Endpoint copied');}catch(error){toast('Copy failed',true);}return;}
const details=event.target.closest('[data-model-details]');if(details){showModelDetails(details.dataset.modelDetails);return;}
const useModel=event.target.closest('[data-use-model]');if(useModel){useModelInPlayground(useModel.dataset.useModel);return;}
const usageDetails=event.target.closest('[data-usage-details]');if(usageDetails){showUsageDetails(usageDetails.dataset.usageDetails);return;}
const ledgerRequest=event.target.closest('[data-ledger-request]');if(ledgerRequest){try{await showLedgerRequest(ledgerRequest.dataset.ledgerRequest,ledgerRequest.dataset.ledgerTenant);}catch(error){toast(error.message,true);}return;}
if(event.target.id==='reload'){try{await api('/reload',{method:'POST',body:'{}'});await loadAll();toast('Snapshot reloaded');}catch(error){toast(error.message,true);}}
if(event.target.id==='add-route')addRoute();if(event.target.closest('.remove-route'))event.target.closest('.route-row').remove();
const disableKey=event.target.closest('[data-disable-key]');if(disableKey&&confirm('Disable this API key immediately?')){try{await api(`/keys/${disableKey.dataset.disableKey}/disable`,{method:'POST',body:'{}'});await loadAll();toast('API key disabled');}catch(error){toast(error.message,true);}}
const enableKey=event.target.closest('[data-enable-key]');if(enableKey){try{await api(`/keys/${enableKey.dataset.enableKey}/enable`,{method:'POST',body:'{}'});await loadAll();toast('API key enabled');}catch(error){toast(error.message,true);}}
const rotateKey=event.target.closest('[data-rotate-key]');if(rotateKey&&confirm('Rotate this API key? The current secret will stop working immediately.')){try{const result=await api(`/keys/${rotateKey.dataset.rotateKey}/rotate`,{method:'POST',body:'{}'});state.playgroundKey=result.key;showSecret('API key rotated',result.key);await loadAll();toast(result.runtime_sync_status==='applied'?'API key rotated':'API key rotated; runtime reload is pending');}catch(error){toast(error.message,true);}}
const revokeKey=event.target.closest('[data-revoke-key]');if(revokeKey&&confirm('Permanently revoke this API key?')){try{await api(`/keys/${revokeKey.dataset.revokeKey}/revoke`,{method:'POST',body:'{}'});await loadAll();toast('API key revoked');}catch(error){toast(error.message,true);}}
const revokeUser=event.target.closest('[data-revoke-user]');if(revokeUser&&confirm('Revoke this console credential?')){try{await api(`/users/${revokeUser.dataset.revokeUser}/revoke`,{method:'POST',body:'{}'});await loadAll();toast('Console credential revoked');}catch(error){toast(error.message,true);}}
const provider=event.target.closest('[data-toggle-provider]');if(provider){try{await api(`/providers/${provider.dataset.toggleProvider}/toggle`,{method:'POST',body:JSON.stringify({enabled:provider.dataset.enabled==='true'})});await loadAll();toast('Provider updated');}catch(error){toast(error.message,true);}}
const model=event.target.closest('[data-toggle-model]');if(model){try{await api(`/models/${model.dataset.toggleModel}/toggle`,{method:'POST',body:JSON.stringify({enabled:model.dataset.enabled==='true'})});await loadAll();toast('Model updated');}catch(error){toast(error.message,true);}}
const session=event.target.closest('[data-revoke-session]');if(session&&confirm('Sign out this device?')){try{const result=await api(`/auth/sessions/${session.dataset.revokeSession}/revoke`,{method:'POST',body:'{}'});if(result.current){state.csrf='';state.actor={};setConnected(false);}else{await loadAll();toast('Device signed out');}}catch(error){toast(error.message,true);}}
const passkey=event.target.closest('[data-delete-passkey]');if(passkey&&confirm('Delete this passkey?')){const password=$('#passkey-form [name=current_password]').value;if(!password){toast('Enter your current password in the Passkey panel',true);}else{try{await api(`/auth/passkeys/${passkey.dataset.deletePasskey}/delete`,{method:'POST',body:JSON.stringify({current_password:password})});await loadAll();toast('Passkey deleted');}catch(error){toast(error.message,true);}}}
const save=event.target.closest('.save-limit');if(save){const row=save.closest('[data-limit-project]');try{await api(`/limits/${row.dataset.limitProject}`,{method:'POST',body:JSON.stringify({requests_per_minute:Number(row.querySelector('.limit-rpm').value),tokens_per_minute:Number(row.querySelector('.limit-tpm').value),concurrent_requests:Number(row.querySelector('.limit-concurrency').value),monthly_spend_micros:decimalToScaled(row.querySelector('.limit-spend').value,6)})});await loadAll();toast('Project limits updated');}catch(error){toast(error.message,true);}}
if(event.target.id==='billing-portal'){try{const result=await api('/billing/portal-sessions',{method:'POST',body:'{}'});window.location.assign(result.url);}catch(error){toast(error.message,true);}}
if(event.target.id==='billing-export'){try{const headers={};if(state.token)headers.Authorization=`Bearer ${state.token}`;const response=await fetch('./api/billing/export.csv',{credentials:'same-origin',headers});if(!response.ok){const payload=await response.json().catch(()=>({}));throw new Error(payload?.error?.message||`Export failed (${response.status})`);}const blob=await response.blob();const url=URL.createObjectURL(blob);const link=document.createElement('a');link.href=url;link.download=`aigw-ledger-${new Date().toISOString().slice(0,10)}.csv`;document.body.appendChild(link);link.click();link.remove();URL.revokeObjectURL(url);toast('Financial export downloaded');}catch(error){toast(error.message,true);}}
if(event.target.id==='billing-reconcile'){try{const result=await api('/billing/reconcile',{method:'POST',body:'{}'});toast(`Reconciliation ${result.status}: ${result.mismatch_count} mismatches`,result.mismatch_count>0);await loadAll();}catch(error){toast(error.message,true);}}
const retryOrder=event.target.closest('[data-retry-order]');if(retryOrder){try{const result=await api(`/billing/orders/${retryOrder.dataset.retryOrder}/retry`,{method:'POST',body:'{}'});window.location.assign(result.url);}catch(error){toast(error.message,true);}}
const resolveOrder=event.target.closest('[data-resolve-order]');if(resolveOrder){const reason=prompt('Resolution reason for this missing Stripe Session');if(reason){try{await api(`/billing/orders/${resolveOrder.dataset.resolveOrder}/resolve-missing?tenant_id=${encodeURIComponent(resolveOrder.dataset.orderTenant)}`,{method:'POST',body:JSON.stringify({reason})});await loadAll();toast('Missing top-up resolved');}catch(error){toast(error.message,true);}}}
const reverseOrder=event.target.closest('[data-reverse-order]');if(reverseOrder){const reason=prompt('Reason for reversing this unverified local credit');if(reason&&confirm('Post an equal negative ledger entry for this credit?')){try{await api(`/billing/orders/${reverseOrder.dataset.reverseOrder}/reverse-missing-credit?tenant_id=${encodeURIComponent(reverseOrder.dataset.orderTenant)}`,{method:'POST',body:JSON.stringify({reason})});await loadAll();toast('Unverified credit reversed');}catch(error){toast(error.message,true);}}}
const refundOrder=event.target.closest('[data-refund-order]');if(refundOrder){const amount=prompt('Refund amount in account currency');if(amount){try{const digits=currencyDigits(state.overview.billing_currency||'usd');await api(`/billing/orders/${refundOrder.dataset.refundOrder}/refund?tenant_id=${encodeURIComponent(state.orders.find(item=>item.id===refundOrder.dataset.refundOrder)?.tenant_id||'')}`,{method:'POST',body:JSON.stringify({amount_minor:decimalToScaled(amount,digits),reason:'requested_by_customer'})});await loadAll();toast('Refund queued');}catch(error){toast(error.message,true);}}}
});
$('#key-tenant').addEventListener('change',renderKeyProjects);
async function completeBrowserLogin(result,form){state.csrf=result.csrf_token||cookie('aigw_csrf');state.pendingMFA=null;if(form)form.reset();await loadAll(result);}
$('#login-pane').addEventListener('submit',async(event)=>{event.preventDefault();authError();try{state.token='';state.csrf='';const result=await api('/auth/login',{method:'POST',body:JSON.stringify(formJSON(event.target))});if(result.mfa_required){state.pendingMFA=result;$('#mfa-passkey').classList.toggle('hidden',!result.methods.includes('passkey'));showAuthPane('mfa-pane');}else await completeBrowserLogin(result,event.target);}catch(error){authError(error.message);}});
$('#register-pane').addEventListener('submit',async(event)=>{event.preventDefault();authError();try{state.token='';state.csrf='';await api('/auth/register',{method:'POST',body:JSON.stringify(formJSON(event.target))});event.target.reset();showAuthPane('login-pane');authError('Verification email sent.',true);}catch(error){authError(error.message);}});
$('#mfa-pane').addEventListener('submit',async(event)=>{event.preventDefault();authError();if(!state.pendingMFA)return showAuthPane('login-pane');try{const result=await api('/auth/mfa/totp',{method:'POST',body:JSON.stringify({challenge_token:state.pendingMFA.challenge_token,code:formJSON(event.target).code})});await completeBrowserLogin(result,event.target);}catch(error){authError(error.message);}});
$('#reset-request-pane').addEventListener('submit',async(event)=>{event.preventDefault();authError();try{await api('/auth/password-reset/request',{method:'POST',body:JSON.stringify(formJSON(event.target))});event.target.reset();showAuthPane('login-pane');authError('Password reset email sent if the account exists.',true);}catch(error){authError(error.message);}});
$('#reset-complete-pane').addEventListener('submit',async(event)=>{event.preventDefault();authError();try{await api('/auth/password-reset/complete',{method:'POST',body:JSON.stringify(formJSON(event.target))});history.replaceState({},'',location.pathname);event.target.reset();showAuthPane('login-pane');authError('Password updated. Sign in again.',true);}catch(error){authError(error.message);}});
$('#invite-pane').addEventListener('submit',async(event)=>{event.preventDefault();authError();try{const result=await api('/auth/invite/accept',{method:'POST',body:JSON.stringify(formJSON(event.target))});history.replaceState({},'',location.pathname);await completeBrowserLogin(result,event.target);}catch(error){authError(error.message);}});
$('#passkey-login').addEventListener('click',async()=>{authError();if(!state.authConfig.passkeys_enabled)return authError('Passkeys are not configured.');const email=$('#login-pane [name=email]').value;if(!email)return authError('Enter your email first.');try{const begin=await api('/auth/passkey/login/options',{method:'POST',body:JSON.stringify({email})});const credential=await getPasskey(begin.options);const result=await api('/auth/passkey/login',{method:'POST',body:JSON.stringify({challenge_token:begin.challenge_token,credential})});await completeBrowserLogin(result,$('#login-pane'));}catch(error){authError(error.message);}});
$('#mfa-passkey').addEventListener('click',async()=>{authError();if(!state.pendingMFA)return showAuthPane('login-pane');try{const begin=await api('/auth/mfa/passkey/login/options',{method:'POST',body:JSON.stringify({challenge_token:state.pendingMFA.challenge_token})});const credential=await getPasskey(begin.options);const result=await api('/auth/mfa/passkey/login',{method:'POST',body:JSON.stringify({challenge_token:begin.challenge_token,credential})});await completeBrowserLogin(result,$('#mfa-pane'));}catch(error){authError(error.message);}});
$('#bootstrap-pane').addEventListener('submit',async(event)=>{event.preventDefault();authError();state.csrf='';state.token=formJSON(event.target).token.trim();if(!await loadAll()){state.token='';authError('Bootstrap token is invalid');}event.target.reset();});
$('#sign-out').addEventListener('click',async()=>{try{if(!state.token)await api('/auth/logout',{method:'POST',body:'{}'});}catch(error){if(error.status!==401)toast(error.message,true);}state.token='';state.csrf='';state.actor={};state.permissions=new Set();setConnected(false);});
$('#tenant-form').addEventListener('submit',async(event)=>{event.preventDefault();try{await api('/tenants',{method:'POST',body:JSON.stringify(formJSON(event.target))});event.target.reset();await loadAll();toast('Tenant created');}catch(error){toast(error.message,true);}});
$('#project-form').addEventListener('submit',async(event)=>{event.preventDefault();try{await api('/projects',{method:'POST',body:JSON.stringify(formJSON(event.target))});event.target.reset();await loadAll();toast('Project created');}catch(error){toast(error.message,true);}});
$('#key-form').addEventListener('submit',async(event)=>{event.preventDefault();try{const form=event.target;const data=formJSON(form);data.scopes=data.scopes.split(',').map(value=>value.trim()).filter(Boolean);data.tags=data.tags.split(',').map(value=>value.trim()).filter(Boolean);data.allowed_models=[...$('#key-models').selectedOptions].map(option=>option.value);data.monthly_spend_micros=data.monthly_spend.trim()?decimalToScaled(data.monthly_spend,6):0;data.daily_spend_micros=data.daily_spend.trim()?decimalToScaled(data.daily_spend,6):0;data.requests_per_minute=Number(data.requests_per_minute||0);data.tokens_per_minute=Number(data.tokens_per_minute||0);delete data.monthly_spend;delete data.daily_spend;data.expires_at=data.expires_at?new Date(data.expires_at).toISOString():null;const result=await api('/keys',{method:'POST',body:JSON.stringify(data)});state.playgroundKey=result.key;form.reset();showSecret('API key created',result.key);await loadAll();goTo('quickstart');if(result.runtime_sync_status!=='applied')toast('API key created; runtime reload is pending');}catch(error){toast(error.message,true);}});
$('#starter-key-form').addEventListener('submit',async(event)=>{event.preventDefault();const button=$('#starter-key-submit');try{const projectID=$('#starter-project').value;const name=$('#starter-key-name').value.trim();const model=selectedDeveloperModel();if(!projectID||!name||!model.public_id)throw new Error('An active project and model are required');button.disabled=true;const result=await api('/keys',{method:'POST',body:JSON.stringify({tenant_id:state.actor.tenant_id,project_id:projectID,name,scopes:['inference'],tags:['quickstart'],allowed_models:[model.public_id],monthly_spend_micros:0,daily_spend_micros:0,requests_per_minute:0,tokens_per_minute:0,expires_at:null})});state.playgroundKey=result.key;await loadAll();$('#playground-key').value=result.key;showSecret('Starter API key created',result.key);toast(result.runtime_sync_status==='applied'?'Starter key is ready in the Playground':'Starter key created; runtime reload is pending');}catch(error){toast(error.message,true);}finally{button.disabled=false;}});
function syncProviderWireAPI(){const form=$('#provider-form');const wire=form.elements.wire_api;const protocol=form.elements.protocol.value;wire.innerHTML=protocol==='anthropic'?'Messages ':'Chat Completions Responses Embeddings ';}
$('#provider-form [name=protocol]').addEventListener('change',syncProviderWireAPI);
$('#provider-form').addEventListener('submit',async(event)=>{event.preventDefault();try{await api('/providers',{method:'POST',body:JSON.stringify(formJSON(event.target))});event.target.reset();syncProviderWireAPI();await loadAll();toast('Provider added');}catch(error){toast(error.message,true);}});
$('#model-form').addEventListener('submit',async(event)=>{event.preventDefault();try{const data=formJSON(event.target);data.input_price_micros_per_million=decimalToScaled(data.input_price,6);data.output_price_micros_per_million=decimalToScaled(data.output_price,6);data.cache_read_price_micros_per_million=decimalToScaled(data.cache_read_price,6);data.cache_write_price_micros_per_million=decimalToScaled(data.cache_write_price,6);delete data.input_price;delete data.output_price;delete data.cache_read_price;delete data.cache_write_price;for(const field of ['capabilities','input_modalities','output_modalities','regions','aliases','allowed_tenant_ids','allowed_key_ids'])data[field]=String(data[field]||'').split(',').map(value=>value.trim()).filter(Boolean);data.context_window=Number(data.context_window||0);data.max_output_tokens=Number(data.max_output_tokens||0);data.price_currency=state.overview.billing_currency||'usd';data.routes=$$('.route-row').map(row=>({provider_id:row.querySelector('.route-provider').value,upstream_model:row.querySelector('.route-upstream').value,priority:Number(row.querySelector('.route-priority').value),weight:Number(row.querySelector('.route-weight').value)}));await api('/models',{method:'POST',body:JSON.stringify(data)});event.target.reset();$('#route-editor').innerHTML='';renderRouteEditor();await loadAll();toast('Model created');}catch(error){toast(error.message,true);}});
$('#topup-form').addEventListener('submit',async(event)=>{event.preventDefault();try{const data=formJSON(event.target);const digits=currencyDigits(state.overview.billing_currency||'usd');const result=await api('/billing/checkout-sessions',{method:'POST',body:JSON.stringify({tenant_id:data.tenant_id,amount_minor:decimalToScaled(data.amount,digits)})});window.location.assign(result.url);}catch(error){toast(error.message,true);}});
$('#billing-profile-form').addEventListener('submit',async(event)=>{event.preventDefault();try{const data=formJSON(event.target);data.country=String(data.country||'').toUpperCase();state.billingProfile=await api('/billing/profile',{method:'PUT',body:JSON.stringify(data)});renderBillingProfile();toast(state.billingProfile.stripe_sync_status==='synced'?'Invoice details saved and synced':'Invoice details saved');}catch(error){try{state.billingProfile=await api('/billing/profile');renderBillingProfile();}catch{}toast(error.message,true);}});
$('#auto-topup-form').addEventListener('submit',async(event)=>{event.preventDefault();try{const currency=state.autoTopUp.currency||state.overview.billing_currency||'usd';const result=await api('/billing/auto-topup',{method:'PUT',body:JSON.stringify({enabled:$('#auto-topup-enabled').checked,threshold_micros:decimalToScaled($('#auto-topup-threshold').value,6),topup_amount_minor:decimalToScaled($('#auto-topup-amount').value,currencyDigits(currency))})});state.autoTopUp=result;renderAutoTopUp();renderQuickstart();toast(result.enabled?'Automatic top-up enabled':'Automatic top-up settings saved');}catch(error){toast(error.message,true);}});
$('#auto-topup-payment-setup').addEventListener('click',async()=>{try{const result=await api('/billing/auto-topup/setup-sessions',{method:'POST',body:'{}'});window.location.assign(result.url);}catch(error){toast(error.message,true);}});
$('#developer-preferences-form').addEventListener('submit',async(event)=>{event.preventDefault();try{const data=formJSON(event.target);await api('/developer/preferences',{method:'PUT',body:JSON.stringify({default_model:data.default_model||'',fallback_model:data.fallback_model||''})});await loadAll();toast('API defaults saved');}catch(error){toast(error.message,true);}});
$('#billing-preferences-form').addEventListener('submit',async(event)=>{event.preventDefault();try{await api('/developer/preferences/billing',{method:'PUT',body:JSON.stringify({low_balance_enabled:$('#low-balance-enabled').checked,low_balance_threshold_micros:decimalToScaled($('#low-balance-threshold').value,6),spend_anomaly_enabled:$('#spend-anomaly-enabled').checked,spend_anomaly_multiplier:Number($('#spend-anomaly-multiplier').value),spend_anomaly_min_micros:decimalToScaled($('#spend-anomaly-minimum').value,6)})});await loadAll();toast('Billing alerts saved');}catch(error){toast(error.message,true);}});
$('#adjustment-form').addEventListener('submit',async(event)=>{event.preventDefault();try{const data=formJSON(event.target);await api('/billing/adjustments',{method:'POST',body:JSON.stringify({tenant_id:data.tenant_id,amount_micros:decimalToScaled(data.amount,6),description:data.description})});event.target.reset();await loadAll();toast('Balance adjusted');}catch(error){toast(error.message,true);}});
$('#user-form').addEventListener('submit',async(event)=>{event.preventDefault();try{await api('/users',{method:'POST',body:JSON.stringify(formJSON(event.target))});event.target.reset();await loadAll();toast('Invitation sent');}catch(error){toast(error.message,true);}});
$('#password-form').addEventListener('submit',async(event)=>{event.preventDefault();try{const data=formJSON(event.target);await api('/auth/password',{method:'POST',body:JSON.stringify({current_password:data.current_password,new_password:data.new_password})});event.target.reset();state.csrf='';state.actor={};state.permissions=new Set();setConnected(false);authError('Password changed. Sign in again.',true);}catch(error){toast(error.message,true);}});
$('#totp-begin-form').addEventListener('submit',async(event)=>{event.preventDefault();try{const data=formJSON(event.target);if(state.mfa.totp_enabled){await api('/auth/mfa/totp/disable',{method:'POST',body:JSON.stringify(data)});event.target.reset();await loadAll();toast('TOTP disabled');return;}const result=await api('/auth/mfa/totp/begin',{method:'POST',body:JSON.stringify({current_password:data.current_password})});$('#totp-qr').src=result.qr_code;$('#totp-secret').textContent=result.secret;$('#totp-confirm-form').classList.remove('hidden');toast('Authenticator enrollment started');}catch(error){toast(error.message,true);}});
$('#totp-confirm-form').addEventListener('submit',async(event)=>{event.preventDefault();try{const result=await api('/auth/mfa/totp/confirm',{method:'POST',body:JSON.stringify(formJSON(event.target))});event.target.reset();$('#totp-confirm-form').classList.add('hidden');showSecret('Recovery codes',result.recovery_codes.join('\n'));await loadAll();}catch(error){toast(error.message,true);}});
$('#passkey-form').addEventListener('submit',async(event)=>{event.preventDefault();try{if(!state.authConfig.passkeys_enabled)throw new Error('Passkeys are not configured');const data=formJSON(event.target);const begin=await api('/auth/mfa/passkey/options',{method:'POST',body:JSON.stringify({current_password:data.current_password})});const credential=await createPasskey(begin.options);await api('/auth/mfa/passkey',{method:'POST',body:JSON.stringify({challenge_token:begin.challenge_token,name:data.name,credential})});event.target.reset();await loadAll();toast('Passkey added');}catch(error){toast(error.message,true);}});
$('#revoke-other-sessions').addEventListener('click',async()=>{if(!confirm('Sign out every other device?'))return;try{await api('/auth/sessions/revoke-others',{method:'POST',body:'{}'});await loadAll();toast('Other devices signed out');}catch(error){toast(error.message,true);}});
$('#close-dialog').addEventListener('click',()=>$('#secret-dialog').close());$('#copy-secret').addEventListener('click',async()=>{await navigator.clipboard.writeText($('#created-secret').textContent);toast('Credential copied');});
$('#close-model-dialog').addEventListener('click',()=>$('#model-dialog').close());$('#model-dialog-close').addEventListener('click',()=>$('#model-dialog').close());$('#model-dialog-playground').addEventListener('click',()=>{if(state.detailModel)useModelInPlayground(state.detailModel.public_id);});$('#copy-model-id').addEventListener('click',async()=>{await navigator.clipboard.writeText($('#model-dialog-id').textContent);toast('Model ID copied');});
$('#close-usage-dialog').addEventListener('click',()=>$('#usage-dialog').close());$('#usage-dialog-close').addEventListener('click',()=>$('#usage-dialog').close());$('#copy-request-id').addEventListener('click',async()=>{await navigator.clipboard.writeText(state.detailUsage?.request_id||'');toast('Request ID copied');});$('#copy-request-diagnostic').addEventListener('click',async()=>{await navigator.clipboard.writeText($('#usage-diagnostic').textContent);toast('Diagnostic copied');});
$('#release-reservation-form').addEventListener('submit',async(event)=>{event.preventDefault();const item=state.detailUsage;if(!item)return;const reason=event.target.elements.reason.value.trim();if(!reason)return;const params=new URLSearchParams({tenant_id:item.tenant_id});try{await api(`/billing/reservations/${encodeURIComponent(item.request_id)}/release?${params}`,{method:'POST',body:JSON.stringify({reason})});$('#usage-dialog').close();await loadAll();toast('Reservation hold released');}catch(error){toast(error.message,true);}});
$('#model-estimate-form').addEventListener('submit',event=>event.preventDefault());
['estimate-input','estimate-output','estimate-cache-read','estimate-cache-write'].forEach(id=>$('#'+id).addEventListener('input',renderModelEstimate));
$('#quickstart-model').addEventListener('change',()=>{syncQuickstartProtocols();renderQuickstartCode();renderDeveloperAccess();});
$('#quickstart-protocol').addEventListener('change',()=>{syncProviderSelect('#quickstart-provider',selectedDeveloperModel(),$('#quickstart-protocol').value);renderQuickstartCode();});
$('#quickstart-provider').addEventListener('change',renderQuickstartCode);
$('#quickstart-language').addEventListener('change',renderQuickstartCode);
$('#playground-model').addEventListener('change',syncPlaygroundProtocols);
$('#playground-protocol').addEventListener('change',syncPlaygroundProtocols);
$('#playground-form').addEventListener('submit',runPlayground);
$('#playground-stop').addEventListener('click',()=>state.playgroundController?.abort());
$('#playground-diagnostic-action').addEventListener('click',event=>goTo(event.currentTarget.dataset.target));
$('#catalog-search').addEventListener('input',renderCatalog);$('#catalog-protocol').addEventListener('change',renderCatalog);$('#catalog-input').addEventListener('change',renderCatalog);$('#catalog-owner').addEventListener('change',renderCatalog);$('#catalog-sort').addEventListener('change',renderCatalog);
$('#copy-quickstart').addEventListener('click',async()=>{try{await navigator.clipboard.writeText($('#quickstart-code-block code').textContent);toast('Example copied');}catch(error){toast('Copy failed; select the example manually',true);}});
$('#copy-endpoint-env').addEventListener('click',async()=>{try{await navigator.clipboard.writeText($('#endpoint-env').textContent);toast('Environment copied');}catch(error){toast('Copy failed; select the environment manually',true);}});
function usageFilterParams(){const params=new URLSearchParams(new FormData($('#usage-filter-form')));for(const [key,value] of [...params.entries()])if(!String(value).trim())params.delete(key);if(state.actor.tenant_id)params.delete('provider');return params;}
async function loadUsageFilters({cursor='',history=[],refreshAggregates=true}={}){
const params=usageFilterParams(),pageParams=new URLSearchParams(params);pageParams.set('limit','50');if(cursor)pageParams.set('cursor',cursor);
try{
const pagePromise=api(`/usage?${pageParams}`);let page;
if(refreshAggregates){[page,state.usageDaily,state.usageAnalytics]=await Promise.all([pagePromise,api(`/usage/daily${params.size?`?${params}`:''}`),api(`/usage/analytics${params.size?`?${params}`:''}`)]);}else page=await pagePromise;
state.usage=Array.isArray(page)?page:(page.data||[]);state.usagePaging={cursor,nextCursor:page.next_cursor||'',history};renderUsage();
}catch(error){toast(error.message,true);}
}
$('#usage-filter-form').addEventListener('submit',async(event)=>{event.preventDefault();await loadUsageFilters();});
$('#usage-filter-reset').addEventListener('click',async()=>{const form=$('#usage-filter-form');form.reset();const query=new URLSearchParams(defaultUsageQuery());$('#usage-from').value=query.get('from');$('#usage-to').value=query.get('to');await loadUsageFilters();});
$('#usage-page-next').addEventListener('click',async()=>{if(!state.usagePaging.nextCursor)return;await loadUsageFilters({cursor:state.usagePaging.nextCursor,history:[...state.usagePaging.history,state.usagePaging.cursor],refreshAggregates:false});});
$('#usage-page-prev').addEventListener('click',async()=>{if(!state.usagePaging.history.length)return;const history=state.usagePaging.history.slice(0,-1);await loadUsageFilters({cursor:state.usagePaging.history.at(-1)||'',history,refreshAggregates:false});});
async function pollTopUp(orderID){for(let attempt=0;attempt<20;attempt++){const order=await api(`/billing/orders/${encodeURIComponent(orderID)}`);if(order.status==='paid'){await loadAll();toast('Balance credited');return;}if(['failed','expired'].includes(order.status)){toast(`Top-up ${order.status}`,true);return;}await new Promise(resolve=>setTimeout(resolve,1500));}toast('Payment is still processing');}
async function pollAutoTopUpSetup(){for(let attempt=0;attempt<20;attempt++){const settings=await api('/billing/auto-topup');if(settings.payment_method_configured){state.autoTopUp=settings;renderBilling();renderQuickstart();toast('Payment method saved; review the threshold and enable automatic top-up');return;}await new Promise(resolve=>setTimeout(resolve,1500));}toast('Payment method setup is still processing',true);}
async function start(){try{state.authConfig=await api('/auth/config');$('#register-tab').classList.toggle('hidden',!state.authConfig.registration_enabled);if(!state.authConfig.registration_enabled&&$('#register-tab').classList.contains('active'))showAuthPane('login-pane');const params=new URLSearchParams(location.search);const action=params.get('action');const token=params.get('token');if(params.get('auth')==='register'&&state.authConfig.registration_enabled)showAuthPane('register-pane');if(action==='reset-password'&&token){$('#reset-token').value=token;showAuthPane('reset-complete-pane');setConnected(false);return;}if(action==='accept-invite'&&token){$('#invite-token').value=token;showAuthPane('invite-pane');setConnected(false);return;}state.csrf=cookie('aigw_csrf');if(action==='verify-email'&&token){const result=await api('/auth/email/verify',{method:'POST',body:JSON.stringify({token})});history.replaceState({},'',location.pathname);await completeBrowserLogin(result);return;}const session=await api('/auth/session');if(session.authenticated){await loadAll(session);const orderID=params.get('order_id');if(params.get('topup')==='success'&&orderID){history.replaceState({},'',location.pathname);pollTopUp(orderID).catch(error=>toast(error.message,true));}else if(params.get('topup')==='cancel'){history.replaceState({},'',location.pathname);toast('Top-up cancelled');}else if(params.get('autotopup')==='setup'){history.replaceState({},'',location.pathname);pollAutoTopUpSetup().catch(error=>toast(error.message,true));}else if(params.get('autotopup')==='cancel'){history.replaceState({},'',location.pathname);toast('Payment method setup cancelled');}}else setConnected(false);}catch(error){setConnected(false);authError(error.message);}}
start();