feat(insights): model selector with favorites, provider filtering, and configured-providers-only API

- Wire up CustomModelDropdown in InsightsView with favorites support:
  favoriteProviders, favoriteModels, and toggle callbacks
- Auto-promote resolved planning provider as implicit favorite when
  no explicit favorites are set
- Filter /models API to only return providers with credentials in
  Fusion's own auth stores (primary + legacy .pi + models.json),
  excluding supplemental stores (Codex CLI, Claude Code, env vars)
  that surface unconfigured providers
- Still allow enabled extension providers (Claude CLI, Droid CLI,
  llama.cpp) when their settings toggles are on

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Timothy Laurent
2026-05-06 17:45:18 -07:00
parent 65d15f77b5
commit 86712d4966
6 changed files with 125 additions and 2 deletions

View File

@@ -1,7 +1,67 @@
import { existsSync, readFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import { resolvePlanningSettingsModel } from "@fusion/core";
import { ApiError } from "../api-error.js";
import type { ApiRouteRegistrar } from "./types.js";
/**
* Read provider names from Fusion's own auth stores (primary + legacy .pi).
* These represent providers the user has explicitly configured in Fusion,
* as opposed to supplemental credentials inherited from Codex CLI,
* Claude Code, or environment variables.
*/
function getConfiguredProviderNames(): Set<string> {
const home = process.env.HOME || process.env.USERPROFILE || homedir();
const providers = new Set<string>();
// Fusion primary + legacy .pi auth files
const authPaths = [
join(home, ".fusion", "agent", "auth.json"),
join(home, ".pi", "agent", "auth.json"),
join(home, ".pi", "auth.json"),
];
for (const authPath of authPaths) {
if (!existsSync(authPath)) continue;
try {
const parsed = JSON.parse(readFileSync(authPath, "utf-8")) as Record<string, unknown>;
for (const key of Object.keys(parsed)) {
providers.add(key);
}
} catch {
// Ignore invalid auth files
}
}
// Check models.json for providers with inline API keys
const modelsPaths = [
join(home, ".fusion", "agent", "models.json"),
join(home, ".pi", "agent", "models.json"),
join(home, ".pi", "models.json"),
];
for (const modelsPath of modelsPaths) {
if (!existsSync(modelsPath)) continue;
try {
const parsed = JSON.parse(readFileSync(modelsPath, "utf-8")) as {
providers?: Record<string, { apiKey?: string }>;
};
const provs = parsed?.providers;
if (provs) {
for (const [providerId, config] of Object.entries(provs)) {
if (config.apiKey) {
providers.add(providerId);
}
}
}
} catch {
// Ignore invalid models.json
}
}
return providers;
}
export const registerModelRoutes: ApiRouteRegistrar = (ctx) => {
const { router, options, store, runtimeLogger } = ctx;
@@ -93,6 +153,20 @@ export const registerModelRoutes: ApiRouteRegistrar = (ctx) => {
models = models.filter((m) => m.provider !== "cursor-cli");
}
// Filter to only providers the user has explicitly configured in Fusion.
// getAvailable() checks supplemental credential stores (Codex CLI,
// Claude Code, env vars) which surface providers the user may not
// have set up in Fusion. We restrict to providers with credentials
// in Fusion's own auth stores (primary + legacy .pi + models.json),
// plus any providers enabled via settings toggles (Claude CLI, etc.).
const configuredProviders = getConfiguredProviderNames();
if (useClaudeCli) configuredProviders.add("pi-claude-cli");
if (useDroidCli) configuredProviders.add("droid-cli");
if (useLlamaCpp) configuredProviders.add("llama-server");
if (configuredProviders.size > 0) {
models = models.filter((m) => configuredProviders.has(m.provider));
}
res.json({
models,
favoriteProviders,