new flow for Pi provider/model settings
This commit is contained in:
+8
-124
@@ -2,9 +2,8 @@ import type { Context } from 'hono';
|
||||
import { createRouter } from '../../create-router';
|
||||
import * as storage from './storage';
|
||||
import { readApiKeys, readLocalProviders } from '../server-settings/pi-mono';
|
||||
import { getConfiguredProviders, fetchProviderModels } from '../server-settings/provider-registry';
|
||||
import { listPiModels } from './list-models';
|
||||
import { getHomeDir } from '../../data-path';
|
||||
import type { ModelInfo } from './types';
|
||||
import { logger } from './logger';
|
||||
|
||||
/**
|
||||
@@ -15,140 +14,25 @@ export const piRestRouter = createRouter();
|
||||
|
||||
/**
|
||||
* GET /api/pi/models
|
||||
* List available models by running `pi --list-models` with stored API keys
|
||||
* PLUS models from local providers (ollama, lmstudio, etc.)
|
||||
* List available models via `pi --list-models` (with stored API keys + PI_CODING_AGENT_DIR).
|
||||
* Local provider friendly names are resolved from stored local-providers config.
|
||||
*/
|
||||
piRestRouter.get('/pi/models', async (ctx: Context) => {
|
||||
const models: ModelInfo[] = [];
|
||||
const providerNames: Record<string, string> = {};
|
||||
|
||||
// Helper to parse context window sizes
|
||||
const parseSize = (s?: string): number => {
|
||||
if (!s) return 128000;
|
||||
const match = s.match(/^(\d+)([KMG])?$/i);
|
||||
if (!match) return 128000;
|
||||
const num = parseInt(match[1]!, 10);
|
||||
const unit = (match[2] ?? '').toUpperCase();
|
||||
if (unit === 'K') return num * 1000;
|
||||
if (unit === 'M') return num * 1000000;
|
||||
if (unit === 'G') return num * 1000000000;
|
||||
return num;
|
||||
};
|
||||
|
||||
try {
|
||||
// 1. Fetch models from API-based providers (OpenAI, Anthropic, etc.)
|
||||
const storedKeys = await readApiKeys();
|
||||
const configuredProviders = getConfiguredProviders(storedKeys);
|
||||
|
||||
logger.info('Fetching models from API providers', { count: configuredProviders.length });
|
||||
|
||||
for (const { provider, apiKey } of configuredProviders) {
|
||||
try {
|
||||
const providerModels = await fetchProviderModels(provider, apiKey);
|
||||
logger.info('Fetched models from API provider', {
|
||||
provider: provider.name,
|
||||
count: providerModels.length
|
||||
});
|
||||
|
||||
for (const m of providerModels) {
|
||||
models.push({
|
||||
id: `${provider.id}/${m.id}`,
|
||||
name: m.name,
|
||||
provider: provider.id,
|
||||
contextWindow: 128000, // Default
|
||||
maxTokens: 4096, // Default
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('Failed to fetch models from API provider', {
|
||||
provider: provider.name,
|
||||
error: String(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
const models = await listPiModels(storedKeys);
|
||||
|
||||
// 2. Fetch models from local providers (ollama, lmstudio, openai-compatible)
|
||||
// Build providerNames map for officer-local-* providers
|
||||
const providerNames: Record<string, string> = {};
|
||||
const localProviders = await readLocalProviders();
|
||||
logger.info('Local providers loaded', { count: localProviders.length, providers: localProviders.map(p => p.name) });
|
||||
|
||||
for (const lp of localProviders) {
|
||||
try {
|
||||
const base = lp.url.replace(/\/+$/, '');
|
||||
const path = lp.apiType === 'ollama' ? '/api/tags' : '/v1/models';
|
||||
|
||||
const headers: Record<string, string> = {};
|
||||
if (lp.auth?.type === 'api-key') {
|
||||
headers['Authorization'] = `Bearer ${lp.auth.apiKey}`;
|
||||
} else if (lp.auth?.type === 'basic') {
|
||||
headers['Authorization'] = `Basic ${btoa(`${lp.auth.username}:${lp.auth.password}`)}`;
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), 5000);
|
||||
|
||||
const res = await fetch(`${base}${path}`, { headers, signal: controller.signal });
|
||||
clearTimeout(timer);
|
||||
|
||||
if (!res.ok) {
|
||||
logger.error('Failed to fetch models from local provider', { provider: lp.name, status: res.status });
|
||||
continue;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
// Use Pi's provider ID format: officer-local-{uuid}
|
||||
const piProviderId = `officer-local-${lp.id}`;
|
||||
|
||||
// Store friendly provider name for frontend display
|
||||
providerNames[piProviderId] = lp.name;
|
||||
|
||||
// Parse Ollama response
|
||||
if (lp.apiType === 'ollama' && data.models) {
|
||||
logger.info('Adding Ollama models', { provider: lp.name, count: data.models.length });
|
||||
for (const m of data.models) {
|
||||
models.push({
|
||||
id: `${piProviderId}/${m.name}`,
|
||||
name: m.name,
|
||||
provider: piProviderId,
|
||||
contextWindow: 128000, // Default
|
||||
maxTokens: 4096, // Default
|
||||
});
|
||||
}
|
||||
}
|
||||
// Parse OpenAI-compatible response (including LM Studio)
|
||||
else if (data.data) {
|
||||
logger.info('Adding OpenAI-compatible models', { provider: lp.name, count: data.data.length });
|
||||
for (const m of data.data) {
|
||||
models.push({
|
||||
id: `${piProviderId}/${m.id}`,
|
||||
name: m.id,
|
||||
provider: piProviderId,
|
||||
contextWindow: 128000, // Default
|
||||
maxTokens: 4096, // Default
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('Failed to fetch models from local provider', {
|
||||
provider: lp.name,
|
||||
error: String(err)
|
||||
});
|
||||
}
|
||||
providerNames[`officer-local-${lp.id}`] = lp.name;
|
||||
}
|
||||
|
||||
logger.info('Returning models', {
|
||||
totalModels: models.length,
|
||||
providerCount: Object.keys(providerNames).length,
|
||||
providers: models.reduce((acc, m) => {
|
||||
acc[m.provider] = (acc[m.provider] || 0) + 1;
|
||||
return acc;
|
||||
}, {} as Record<string, number>)
|
||||
});
|
||||
|
||||
return ctx.json({ models, providerNames });
|
||||
} catch (err) {
|
||||
logger.error('Failed to list models', { error: String(err) });
|
||||
return ctx.json({ models, providerNames });
|
||||
return ctx.json({ models: [], providerNames: {} });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user