Let users choose the metadata generation model (#3215)
Expose Automatic and Manual metadata model selection in Host Settings, preserve existing configured fallbacks, and keep structured generation resilient through discovered candidates. Includes Playwright coverage, translations, public documentation, and settings-row alignment polish.
This commit is contained in:
@@ -18,8 +18,17 @@ class ProviderSnapshots {
|
||||
}
|
||||
|
||||
describe("resolveStructuredGenerationProviders", () => {
|
||||
test("uses explicit configured provider models without refreshing provider snapshots", async () => {
|
||||
const snapshots = new ProviderSnapshots([]);
|
||||
test("tries the configured model before dynamically discovered fallbacks", async () => {
|
||||
const snapshots = new ProviderSnapshots([
|
||||
{
|
||||
provider: "work-claude",
|
||||
status: READY,
|
||||
enabled: true,
|
||||
models: [
|
||||
{ provider: "work-claude", id: "claude-haiku-2026", label: "Haiku", isDefault: true },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const providers = await resolveStructuredGenerationProviders({
|
||||
cwd: "/tmp/repo",
|
||||
@@ -31,8 +40,11 @@ describe("resolveStructuredGenerationProviders", () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(providers).toEqual([{ provider: "mock", model: "ten-second-stream" }]);
|
||||
expect(snapshots.calls).toEqual([]);
|
||||
expect(providers).toEqual([
|
||||
{ provider: "mock", model: "ten-second-stream" },
|
||||
{ provider: "work-claude", model: "claude-haiku-2026" },
|
||||
]);
|
||||
expect(snapshots.calls).toEqual([{ cwd: "/tmp/repo", wait: true }]);
|
||||
});
|
||||
|
||||
test("falls back to dynamic defaults and current selection when no provider is configured", async () => {
|
||||
@@ -155,7 +167,7 @@ describe("resolveStructuredGenerationProviders", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test("uses explicit configured provider models as-is instead of waiting to normalize aliases", async () => {
|
||||
test("normalizes configured nested-provider aliases from the provider snapshot", async () => {
|
||||
const snapshots = new ProviderSnapshots([
|
||||
{
|
||||
provider: "opencode",
|
||||
@@ -186,8 +198,8 @@ describe("resolveStructuredGenerationProviders", () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(providers).toEqual([{ provider: "plexus", model: "small-fast" }]);
|
||||
expect(snapshots.calls).toEqual([]);
|
||||
expect(providers).toEqual([{ provider: "opencode", model: "plexus/small-fast" }]);
|
||||
expect(snapshots.calls).toEqual([{ cwd: "/tmp/repo", wait: true }]);
|
||||
});
|
||||
|
||||
test("keeps explicit candidates when provider snapshots are in error state", async () => {
|
||||
@@ -215,7 +227,10 @@ describe("resolveStructuredGenerationProviders", () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(providers).toEqual([{ provider: "current-provider", model: "configured-model" }]);
|
||||
expect(snapshots.calls).toEqual([]);
|
||||
expect(providers).toEqual([
|
||||
{ provider: "current-provider", model: "configured-model" },
|
||||
{ provider: "current-provider", model: "selected-model", thinkingOptionId: "medium" },
|
||||
]);
|
||||
expect(snapshots.calls).toEqual([{ cwd: "/tmp/repo", wait: true }]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -44,22 +44,6 @@ export async function resolveStructuredGenerationProviders(
|
||||
options: ResolveStructuredGenerationProvidersOptions,
|
||||
): Promise<StructuredGenerationProvider[]> {
|
||||
const configuredProviders = readConfiguredProviders(options.daemonConfig);
|
||||
if (configuredProviders.length > 0) {
|
||||
const explicitProviders = resolveExplicitConfiguredProviders(configuredProviders);
|
||||
if (explicitProviders.length === configuredProviders.length) {
|
||||
return dedupeProviders(explicitProviders);
|
||||
}
|
||||
|
||||
const providerEntries = await options.providerSnapshotManager.listProviders({
|
||||
cwd: options.cwd,
|
||||
wait: false,
|
||||
});
|
||||
const providers = resolveConfiguredProviders(configuredProviders, providerEntries);
|
||||
if (providers.length > 0) {
|
||||
return dedupeProviders(providers);
|
||||
}
|
||||
}
|
||||
|
||||
const providerEntries = await options.providerSnapshotManager.listProviders({
|
||||
cwd: options.cwd,
|
||||
wait: true,
|
||||
@@ -100,42 +84,6 @@ export async function resolveStructuredGenerationProviders(
|
||||
return dedupeProviders(providers);
|
||||
}
|
||||
|
||||
function resolveConfiguredProviders(
|
||||
configuredProviders: readonly { provider: string; model?: string; thinkingOptionId?: string }[],
|
||||
providerEntries: readonly ProviderSnapshotEntry[],
|
||||
): StructuredGenerationProvider[] {
|
||||
const enabledEntries = providerEntries.filter((entry) => entry.enabled);
|
||||
const modelEntries = enabledEntries.filter((entry) => (entry.models?.length ?? 0) > 0);
|
||||
const entriesByProvider = new Map(enabledEntries.map((entry) => [entry.provider, entry]));
|
||||
const providers: StructuredGenerationProvider[] = [];
|
||||
for (const configured of configuredProviders) {
|
||||
const resolved = resolveConfiguredCandidate(configured, modelEntries, entriesByProvider);
|
||||
if (resolved) {
|
||||
providers.push(resolved);
|
||||
}
|
||||
}
|
||||
return providers;
|
||||
}
|
||||
|
||||
function resolveExplicitConfiguredProviders(
|
||||
configuredProviders: readonly { provider: string; model?: string; thinkingOptionId?: string }[],
|
||||
): StructuredGenerationProvider[] {
|
||||
const providers: StructuredGenerationProvider[] = [];
|
||||
for (const configured of configuredProviders) {
|
||||
const provider = configured.provider.trim();
|
||||
const model = configured.model?.trim();
|
||||
if (!provider || !model) {
|
||||
continue;
|
||||
}
|
||||
providers.push({
|
||||
provider,
|
||||
model,
|
||||
...(configured.thinkingOptionId ? { thinkingOptionId: configured.thinkingOptionId } : {}),
|
||||
});
|
||||
}
|
||||
return providers;
|
||||
}
|
||||
|
||||
function resolveCurrentSelection(
|
||||
selection: ResolveStructuredGenerationProvidersOptions["currentSelection"],
|
||||
readyEntries: readonly ProviderSnapshotEntry[],
|
||||
|
||||
Reference in New Issue
Block a user