Fix model selector not showing models with settings.json API keys

Fixes #295
This commit is contained in:
Mario Zechner 2025-12-24 21:23:44 +01:00
parent a96b9201f9
commit ac5f4a77cc
8 changed files with 357 additions and 249 deletions

View file

@ -4,6 +4,7 @@
import type { Api, Model } from "@mariozechner/pi-ai";
import { getAvailableModels } from "../core/model-config.js";
import type { SettingsManager } from "../core/settings-manager.js";
import { fuzzyFilter } from "../utils/fuzzy.js";
/**
@ -24,8 +25,11 @@ function formatTokenCount(count: number): string {
/**
* List available models, optionally filtered by search pattern
*/
export async function listModels(searchPattern?: string): Promise<void> {
const { models, error } = await getAvailableModels();
export async function listModels(searchPattern?: string, settingsManager?: SettingsManager): Promise<void> {
const { models, error } = await getAvailableModels(
undefined,
settingsManager ? (provider) => settingsManager.getApiKey(provider) : undefined,
);
if (error) {
console.error(error);

View file

@ -616,7 +616,9 @@ export class AgentSession {
}
private async _cycleAvailableModel(): Promise<ModelCycleResult | null> {
const { models: availableModels, error } = await getAvailableModels();
const { models: availableModels, error } = await getAvailableModels(undefined, (provider) =>
this.settingsManager.getApiKey(provider),
);
if (error) throw new Error(`Failed to load models: ${error}`);
if (availableModels.length <= 1) return null;
@ -648,7 +650,9 @@ export class AgentSession {
* Get all available models with valid API keys.
*/
async getAvailableModels(): Promise<Model<any>[]> {
const { models, error } = await getAvailableModels();
const { models, error } = await getAvailableModels(undefined, (provider) =>
this.settingsManager.getApiKey(provider),
);
if (error) throw new Error(error);
return models;
}
@ -1330,7 +1334,9 @@ export class AgentSession {
// Restore model if saved
if (sessionContext.model) {
const availableModels = (await getAvailableModels()).models;
const availableModels = (
await getAvailableModels(undefined, (provider) => this.settingsManager.getApiKey(provider))
).models;
const match = availableModels.find(
(m) => m.provider === sessionContext.model!.provider && m.id === sessionContext.model!.modelId,
);

View file

@ -358,9 +358,14 @@ export async function getApiKeyForModel(model: Model<Api>): Promise<string | und
/**
* Get only models that have valid API keys available
* Returns { models, error } - either models array or error message
*
* @param agentDir - Agent config directory
* @param fallbackKeyResolver - Optional function to check for API keys not found by getApiKeyForModel
* (e.g., keys from settings.json)
*/
export async function getAvailableModels(
agentDir: string = getAgentDir(),
fallbackKeyResolver?: (provider: string) => string | undefined,
): Promise<{ models: Model<Api>[]; error: string | null }> {
const { models: allModels, error } = loadAndMergeModels(agentDir);
@ -370,7 +375,11 @@ export async function getAvailableModels(
const availableModels: Model<Api>[] = [];
for (const model of allModels) {
const apiKey = await getApiKeyForModel(model);
let apiKey = await getApiKeyForModel(model);
// Check fallback resolver if primary lookup failed
if (!apiKey && fallbackKeyResolver) {
apiKey = fallbackKeyResolver(model.provider);
}
if (apiKey) {
availableModels.push(model);
}

View file

@ -167,9 +167,15 @@ export function parseModelPattern(pattern: string, availableModels: Model<Api>[]
* Supports models with colons in their IDs (e.g., OpenRouter's model:exacto).
* The algorithm tries to match the full pattern first, then progressively
* strips colon-suffixes to find a match.
*
* @param patterns - Model patterns to resolve
* @param settingsManager - Optional settings manager for API key fallback from settings.json
*/
export async function resolveModelScope(patterns: string[]): Promise<ScopedModel[]> {
const { models: availableModels, error } = await getAvailableModels();
export async function resolveModelScope(patterns: string[], settingsManager?: SettingsManager): Promise<ScopedModel[]> {
const { models: availableModels, error } = await getAvailableModels(
undefined,
settingsManager ? (provider) => settingsManager.getApiKey(provider) : undefined,
);
if (error) {
console.warn(chalk.yellow(`Warning: Error loading models: ${error}`));
@ -269,7 +275,9 @@ export async function findInitialModel(options: {
}
// 4. Try first available model with valid API key
const { models: availableModels, error } = await getAvailableModels();
const { models: availableModels, error } = await getAvailableModels(undefined, (provider) =>
settingsManager.getApiKey(provider),
);
if (error) {
console.error(chalk.red(error));
@ -302,6 +310,7 @@ export async function restoreModelFromSession(
savedModelId: string,
currentModel: Model<Api> | null,
shouldPrintMessages: boolean,
settingsManager?: SettingsManager,
): Promise<{ model: Model<Api> | null; fallbackMessage: string | null }> {
const { model: restoredModel, error } = findModel(savedProvider, savedModelId);
@ -339,7 +348,10 @@ export async function restoreModelFromSession(
}
// Try to find any available model
const { models: availableModels, error: availableError } = await getAvailableModels();
const { models: availableModels, error: availableError } = await getAvailableModels(
undefined,
settingsManager ? (provider) => settingsManager.getApiKey(provider) : undefined,
);
if (availableError) {
console.error(chalk.red(availableError));
process.exit(1);

View file

@ -270,7 +270,8 @@ export async function main(args: string[]) {
if (parsed.listModels !== undefined) {
const searchPattern = typeof parsed.listModels === "string" ? parsed.listModels : undefined;
await listModels(searchPattern);
const settingsManager = SettingsManager.create(process.cwd());
await listModels(searchPattern, settingsManager);
return;
}
@ -305,7 +306,7 @@ export async function main(args: string[]) {
let scopedModels: ScopedModel[] = [];
if (parsed.models && parsed.models.length > 0) {
scopedModels = await resolveModelScope(parsed.models);
scopedModels = await resolveModelScope(parsed.models, settingsManager);
time("resolveModelScope");
}

View file

@ -114,7 +114,10 @@ export class ModelSelectorComponent extends Container {
}));
} else {
// Load available models fresh (includes custom models from models.json)
const { models: availableModels, error } = await getAvailableModels();
// Pass settings manager's key resolver as fallback for settings.json apiKeys
const { models: availableModels, error } = await getAvailableModels(undefined, (provider) =>
this.settingsManager.getApiKey(provider),
);
// If there's an error loading models.json, we'll show it via the "no models" path
// The error will be displayed to the user