coder(providers): fix empty picker (loading-state) + config model overrides + current Claude models
Fix: getProviderSnapshot returned synchronous installed:false 'loading' entries on a cache miss (v2.5.5/Phase 2), which AgentComposerBar filters out — with the Phase 5 client poll not yet built, a single fetch stranded on 'loading' and the picker showed no providers. It now awaits the build and returns terminal entries; the sync loading-return is deferred until Phase 5. Builds stay fast via the tier-2 cold-probe skip. Feature: wire the v2.3 config schema's models/additionalModels — buildResolvedRegistry carries them onto ResolvedProviderDef (models replace, additionalModels merge) and provider-snapshot applies them to every ready model list, so /data/coder-providers.json can edit any provider's models with no code change. Claude staticModels bumped from the stale 2-entry list to opus/sonnet/haiku latest-aliases + pinned claude-opus-4-8 / claude-sonnet-4-6 / claude-haiku-4-5-20251001 (passed verbatim to claude --model). +2 tests (109 total). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -85,6 +85,16 @@ async function buildProviderEntry(
|
||||
const label = agentRow?.label ?? resolved.configLabel ?? resolved.label;
|
||||
const descr = resolved.configDescription ? { description: resolved.configDescription } : {};
|
||||
|
||||
// v2.3: config `models` REPLACES the discovered/static list; `additionalModels`
|
||||
// MERGES on top. Applied to every ready/installed model list below.
|
||||
const withConfigModels = (m: ProviderModel[]): ProviderModel[] => {
|
||||
let out = resolved.configModels && resolved.configModels.length > 0 ? resolved.configModels : m;
|
||||
if (resolved.configAdditionalModels && resolved.configAdditionalModels.length > 0) {
|
||||
out = mergeModels(out, resolved.configAdditionalModels);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
// ACP built-ins fall back to PTY transport when the installed binary lacks ACP.
|
||||
let transport = resolved.transport;
|
||||
if (agentRow && resolved.transport === 'acp' && !agentRow.supports_acp) {
|
||||
@@ -104,7 +114,7 @@ async function buildProviderEntry(
|
||||
if (isNative) {
|
||||
return {
|
||||
name, label: resolved.label, transport, status: 'ready',
|
||||
enabled: true, installed: true, models: llamaModels, modes: [],
|
||||
enabled: true, installed: true, models: withConfigModels(llamaModels), modes: [],
|
||||
defaultModeId: null, commands: manifestCommands,
|
||||
};
|
||||
}
|
||||
@@ -137,7 +147,7 @@ async function buildProviderEntry(
|
||||
if (name === 'claude') {
|
||||
return {
|
||||
name, label, transport, status: 'ready', enabled: true, installed: true,
|
||||
models: attachClaudeThinking(models), modes: fallbackModes, defaultModeId,
|
||||
models: attachClaudeThinking(withConfigModels(models)), modes: fallbackModes, defaultModeId,
|
||||
commands: manifestCommands,
|
||||
};
|
||||
}
|
||||
@@ -165,7 +175,7 @@ async function buildProviderEntry(
|
||||
}
|
||||
return {
|
||||
name, label, transport, status: 'ready', enabled: true, installed: true,
|
||||
models: skipModels, modes: fallbackModes, defaultModeId, commands: manifestCommands,
|
||||
models: withConfigModels(skipModels), modes: fallbackModes, defaultModeId, commands: manifestCommands,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -188,7 +198,7 @@ async function buildProviderEntry(
|
||||
name, label, transport,
|
||||
status: probe.ok ? 'ready' : 'error',
|
||||
enabled: true, installed: true,
|
||||
models: probeModels,
|
||||
models: withConfigModels(probeModels),
|
||||
modes: probe.modes.length > 0 ? probe.modes : fallbackModes,
|
||||
defaultModeId: probe.defaultModeId ?? defaultModeId,
|
||||
commands: mergeCommands(manifestCommands, probe.commands),
|
||||
@@ -203,27 +213,10 @@ async function buildProviderEntry(
|
||||
}
|
||||
return {
|
||||
name, label, transport, status: 'ready', enabled: true, installed: true,
|
||||
models, modes: fallbackModes, defaultModeId, commands: manifestCommands,
|
||||
models: withConfigModels(models), modes: fallbackModes, defaultModeId, commands: manifestCommands,
|
||||
};
|
||||
}
|
||||
|
||||
/** Synchronous placeholder entries for a cache-miss while the build runs (§4.4). */
|
||||
function loadingEntries(): ProviderSnapshotEntry[] {
|
||||
return [...getResolvedRegistry().values()].map((r) => ({
|
||||
name: r.id,
|
||||
label: r.configLabel ?? r.label,
|
||||
...(r.configDescription ? { description: r.configDescription } : {}),
|
||||
transport: r.transport,
|
||||
status: r.enabled ? ('loading' as const) : ('unavailable' as const),
|
||||
enabled: r.enabled,
|
||||
installed: false,
|
||||
models: [],
|
||||
modes: getManifestModes(r.id),
|
||||
defaultModeId: getManifestDefaultModeId(r.id),
|
||||
commands: getManifestCommands(r.id),
|
||||
}));
|
||||
}
|
||||
|
||||
const snapshotCache = new Map<string, { at: number; entries: ProviderSnapshotEntry[] }>();
|
||||
const snapshotInflight = new Map<string, Promise<ProviderSnapshotEntry[]>>();
|
||||
const CACHE_TTL_MS = 5 * 60_000;
|
||||
@@ -269,14 +262,13 @@ export async function getProviderSnapshot(
|
||||
});
|
||||
snapshotInflight.set(cacheKey, promise);
|
||||
|
||||
// force → await the full build (cold probes included). Non-force cache miss →
|
||||
// return loading entries synchronously; the build settles in the background
|
||||
// and the next call returns it via cache / inflight (§4.4; client polls).
|
||||
if (force) return promise;
|
||||
promise.catch(() => {
|
||||
/* settled errors surface on the next call that awaits inflight/rebuilds */
|
||||
});
|
||||
return loadingEntries();
|
||||
// Await the build (force or cache-miss) and return terminal entries. The sync
|
||||
// `loading` return (design §4.4) is DEFERRED until Phase 5 ships the client
|
||||
// poll that resolves it: without that poll, a single fetch lands on
|
||||
// installed:false `loading` entries, which AgentComposerBar filters out
|
||||
// (`e.installed && ...`) → empty picker. Builds stay fast via the tier-2 skip
|
||||
// once available_agents.models is warm.
|
||||
return promise;
|
||||
}
|
||||
|
||||
export function clearProviderSnapshotCache(): void {
|
||||
|
||||
Reference in New Issue
Block a user