feat(gateway): global tier provider/model defaults with catalog-backed options
This commit is contained in:
@@ -695,6 +695,12 @@ function updateAssistantHealth(configData) {
|
||||
const modelTier = configData?.agents?.primary_tier ?? 'default';
|
||||
const delegation = configData?.agents?.delegation ?? {};
|
||||
const backgroundModels = configData?.agents?.background_models ?? {};
|
||||
const tiers = configData?.models ?? {};
|
||||
const modelCatalog = configData?.__modelCatalog ?? [];
|
||||
const providerList = modelCatalog.length > 0
|
||||
? modelCatalog.map((entry) => entry.provider)
|
||||
: ['anthropic', 'openai', 'gemini', 'openrouter', 'github', 'xai', 'ollama', 'llamacpp', 'bedrock', 'zhipuai', 'minimax', 'moonshot', 'synthetic'];
|
||||
const modelOptionsByProvider = Object.fromEntries(modelCatalog.map((entry) => [entry.provider, entry.models ?? []]));
|
||||
const checklistRows = [
|
||||
{ label: 'Set briefing output channel + peer', done: Boolean(briefingOutput?.channel && briefingOutput?.peer) },
|
||||
{ label: 'Enable assistant behavior profile', done: playbookLikeReady },
|
||||
@@ -710,6 +716,18 @@ function updateAssistantHealth(configData) {
|
||||
const tierOption = (selected) => ['fast', 'default', 'complex', 'local']
|
||||
.map((tier) => `<option value="${tier}" ${selected === tier ? 'selected' : ''}>${tier}</option>`)
|
||||
.join('');
|
||||
const providerOption = (selected) => providerList
|
||||
.map((provider) => `<option value="${provider}" ${selected === provider ? 'selected' : ''}>${provider}</option>`)
|
||||
.join('');
|
||||
const modelDataList = (id, provider, selected) => {
|
||||
const options = modelOptionsByProvider[provider] ?? [];
|
||||
return `
|
||||
<input id="${id}" list="${id}-list" value="${escapeHtml(selected ?? '')}" class="bg-zinc-950 text-zinc-50 border border-zinc-800 rounded-md px-2 py-1.5 text-sm focus:border-blue-500 outline-none" />
|
||||
<datalist id="${id}-list">
|
||||
${options.map((model) => `<option value="${escapeHtml(model)}"></option>`).join('')}
|
||||
</datalist>
|
||||
`;
|
||||
};
|
||||
const taskRows = [
|
||||
{ key: 'compaction', label: 'Compaction' },
|
||||
{ key: 'memory_extraction', label: 'Memory extraction' },
|
||||
@@ -767,6 +785,31 @@ function updateAssistantHealth(configData) {
|
||||
</div>
|
||||
<div class="mt-4 p-4 border border-zinc-800 rounded-lg bg-zinc-900">
|
||||
<div class="text-sm font-semibold text-zinc-50 mb-3">Model Tier Defaults</div>
|
||||
<div class="text-sm text-zinc-500 mb-3">Tier provider/model definitions</div>
|
||||
<div class="space-y-3 mb-4">
|
||||
${['default', 'fast', 'complex', 'local'].map((tier) => {
|
||||
const cfg = tiers?.[tier] ?? {};
|
||||
const provider = cfg.provider ?? tiers?.default?.provider ?? 'openai';
|
||||
const model = cfg.model ?? '';
|
||||
return `
|
||||
<div class="p-3 border border-zinc-800 rounded-md bg-zinc-950/60">
|
||||
<div class="text-sm text-zinc-50 mb-2">${escapeHtml(tier)} tier</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||
<label class="flex flex-col gap-1">
|
||||
<span class="text-xs text-zinc-500">Provider</span>
|
||||
<select id="assist-tier-${tier}-provider" class="bg-zinc-950 text-zinc-50 border border-zinc-800 rounded-md px-2 py-1.5 text-sm focus:border-blue-500 outline-none">
|
||||
${providerOption(provider)}
|
||||
</select>
|
||||
</label>
|
||||
<label class="flex flex-col gap-1">
|
||||
<span class="text-xs text-zinc-500">Model</span>
|
||||
${modelDataList(`assist-tier-${tier}-model`, provider, model)}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('')}
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 mb-4">
|
||||
<label class="flex flex-col gap-1.5">
|
||||
<span class="text-sm text-zinc-400">Primary tier</span>
|
||||
@@ -797,11 +840,13 @@ function updateAssistantHealth(configData) {
|
||||
</label>
|
||||
<label class="flex flex-col gap-1">
|
||||
<span class="text-xs text-zinc-500">Provider</span>
|
||||
<input id="assist-bg-${task.key}-provider" type="text" value="${escapeHtml(background?.provider ?? '')}" placeholder="openai" class="bg-zinc-950 text-zinc-50 border border-zinc-800 rounded-md px-2 py-1.5 text-sm focus:border-blue-500 outline-none" />
|
||||
<select id="assist-bg-${task.key}-provider" class="bg-zinc-950 text-zinc-50 border border-zinc-800 rounded-md px-2 py-1.5 text-sm focus:border-blue-500 outline-none">
|
||||
${providerOption(background?.provider ?? tiers?.default?.provider ?? 'openai')}
|
||||
</select>
|
||||
</label>
|
||||
<label class="flex flex-col gap-1">
|
||||
<span class="text-xs text-zinc-500">Model</span>
|
||||
<input id="assist-bg-${task.key}-model" type="text" value="${escapeHtml(background?.model ?? '')}" placeholder="gpt-4o-mini" class="bg-zinc-950 text-zinc-50 border border-zinc-800 rounded-md px-2 py-1.5 text-sm focus:border-blue-500 outline-none" />
|
||||
${modelDataList(`assist-bg-${task.key}-model`, background?.provider ?? tiers?.default?.provider ?? 'openai', background?.model ?? '')}
|
||||
</label>
|
||||
<label class="flex flex-col gap-1">
|
||||
<span class="text-xs text-zinc-500">Fallback tier</span>
|
||||
@@ -866,6 +911,32 @@ function updateAssistantHealth(configData) {
|
||||
${renderAssistantSaveState()}
|
||||
`;
|
||||
|
||||
const updateModelOptions = (inputId, provider) => {
|
||||
const input = el.querySelector(`#${inputId}`);
|
||||
const list = el.querySelector(`#${inputId}-list`);
|
||||
if (!input || !list) {return;}
|
||||
const options = modelOptionsByProvider[provider] ?? [];
|
||||
list.innerHTML = options.map((model) => `<option value="${escapeHtml(model)}"></option>`).join('');
|
||||
};
|
||||
|
||||
const tierRows = ['default', 'fast', 'complex', 'local'];
|
||||
for (const tier of tierRows) {
|
||||
const providerSelect = el.querySelector(`#assist-tier-${tier}-provider`);
|
||||
if (!providerSelect) {continue;}
|
||||
providerSelect.addEventListener('change', () => {
|
||||
updateModelOptions(`assist-tier-${tier}-model`, providerSelect.value);
|
||||
});
|
||||
}
|
||||
|
||||
const taskRowsForModels = ['compaction', 'memory_extraction', 'classification', 'tool_summarisation', 'complex_reasoning'];
|
||||
for (const task of taskRowsForModels) {
|
||||
const providerSelect = el.querySelector(`#assist-bg-${task}-provider`);
|
||||
if (!providerSelect) {continue;}
|
||||
providerSelect.addEventListener('change', () => {
|
||||
updateModelOptions(`assist-bg-${task}-model`, providerSelect.value);
|
||||
});
|
||||
}
|
||||
|
||||
const statusEl = el.querySelector('#ops-assistant-status');
|
||||
const buttons = el.querySelectorAll('.assistant-action-btn');
|
||||
buttons.forEach((button) => {
|
||||
@@ -929,6 +1000,20 @@ function updateAssistantHealth(configData) {
|
||||
patches = {
|
||||
'agents.primary_tier': (el.querySelector('#assist-primary-tier')?.value ?? 'default'),
|
||||
};
|
||||
const tiers = ['default', 'fast', 'complex', 'local'];
|
||||
for (const tier of tiers) {
|
||||
const provider = (el.querySelector(`#assist-tier-${tier}-provider`)?.value ?? '').trim();
|
||||
const model = (el.querySelector(`#assist-tier-${tier}-model`)?.value ?? '').trim();
|
||||
if (!provider || (!model && tier !== 'default')) {
|
||||
continue;
|
||||
}
|
||||
if (provider) {
|
||||
patches[`models.${tier}.provider`] = provider;
|
||||
}
|
||||
if (model) {
|
||||
patches[`models.${tier}.model`] = model;
|
||||
}
|
||||
}
|
||||
for (const task of tasks) {
|
||||
const delegationTier = el.querySelector(`#assist-delegation-${task}`)?.value ?? 'fast';
|
||||
const enabled = Boolean(el.querySelector(`#assist-bg-${task}-enabled`)?.checked);
|
||||
@@ -1042,22 +1127,29 @@ async function fetchFast(client) {
|
||||
}
|
||||
|
||||
async function fetchSlow(client) {
|
||||
const [health, services, sessionAnalytics, contextUsage, config] = await Promise.allSettled([
|
||||
const [health, services, sessionAnalytics, contextUsage, config, modelCatalog] = await Promise.allSettled([
|
||||
client.call('system.health'),
|
||||
client.call('system.services'),
|
||||
client.call('system.sessionAnalytics', { days: 14, topLimit: 5 }),
|
||||
client.call('system.contextUsage'),
|
||||
client.call('config.get'),
|
||||
client.call('system.modelCatalog'),
|
||||
]);
|
||||
|
||||
const unwrap = (result) => (result.status === 'fulfilled' ? result.value : null);
|
||||
|
||||
const configValue = unwrap(config);
|
||||
const modelCatalogValue = unwrap(modelCatalog);
|
||||
if (configValue && typeof configValue === 'object') {
|
||||
configValue.__modelCatalog = Array.isArray(modelCatalogValue?.providers) ? modelCatalogValue.providers : [];
|
||||
}
|
||||
|
||||
return {
|
||||
health: unwrap(health),
|
||||
services: unwrap(services),
|
||||
sessionAnalytics: unwrap(sessionAnalytics),
|
||||
contextUsage: unwrap(contextUsage),
|
||||
config: unwrap(config),
|
||||
config: configValue,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user