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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user