new flow for Pi provider/model settings
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
import { PI_CONFIG_DIR } from '../../data-path';
|
||||
import { logger } from './logger';
|
||||
import type { ModelInfo } from './types';
|
||||
|
||||
const CACHE_TTL_MS = 60_000;
|
||||
|
||||
let cachedModels: ModelInfo[] | null = null;
|
||||
let cacheTimestamp = 0;
|
||||
|
||||
export function invalidateModelCache(): void {
|
||||
cachedModels = null;
|
||||
cacheTimestamp = 0;
|
||||
}
|
||||
|
||||
const parseSize = (s?: string): number => {
|
||||
if (!s) return 128000;
|
||||
const match = s.match(/^([\d.]+)([KMG])?$/i);
|
||||
if (!match) return 128000;
|
||||
const num = parseFloat(match[1]!);
|
||||
const unit = (match[2] ?? '').toUpperCase();
|
||||
if (unit === 'K') return Math.round(num * 1000);
|
||||
if (unit === 'M') return Math.round(num * 1000000);
|
||||
if (unit === 'G') return Math.round(num * 1000000000);
|
||||
return Math.round(num);
|
||||
};
|
||||
|
||||
export async function listPiModels(envKeys: Record<string, string>): Promise<ModelInfo[]> {
|
||||
if (cachedModels && Date.now() - cacheTimestamp < CACHE_TTL_MS) {
|
||||
return cachedModels;
|
||||
}
|
||||
|
||||
try {
|
||||
const proc = Bun.spawn(['pi', '--list-models'], {
|
||||
stdout: 'pipe',
|
||||
stderr: 'pipe',
|
||||
env: { ...process.env, ...envKeys, PI_CODING_AGENT_DIR: PI_CONFIG_DIR },
|
||||
});
|
||||
|
||||
const output = await new Response(proc.stdout).text();
|
||||
await proc.exited;
|
||||
|
||||
if (proc.exitCode !== 0) {
|
||||
const stderr = await new Response(proc.stderr).text();
|
||||
logger.error('pi --list-models failed', { exitCode: proc.exitCode, stderr: stderr.trim() });
|
||||
return [];
|
||||
}
|
||||
|
||||
const lines = output.trim().split('\n');
|
||||
if (lines.length < 2) return [];
|
||||
|
||||
// Parse fixed-width table: provider, model, context, max-out, thinking, images
|
||||
const header = lines[0]!;
|
||||
const colStarts = [
|
||||
header.indexOf('provider'),
|
||||
header.indexOf('model'),
|
||||
header.indexOf('context'),
|
||||
header.indexOf('max-out'),
|
||||
header.indexOf('thinking'),
|
||||
header.indexOf('images'),
|
||||
];
|
||||
|
||||
const extractCol = (line: string, colIdx: number): string => {
|
||||
const start = colStarts[colIdx]!;
|
||||
const end = colIdx < colStarts.length - 1 ? colStarts[colIdx + 1]! : line.length;
|
||||
return line.slice(start, end).trim();
|
||||
};
|
||||
|
||||
const models: ModelInfo[] = [];
|
||||
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
const line = lines[i]!;
|
||||
if (!line.trim()) continue;
|
||||
|
||||
const provider = extractCol(line, 0);
|
||||
const model = extractCol(line, 1);
|
||||
const context = extractCol(line, 2);
|
||||
const maxOut = extractCol(line, 3);
|
||||
const thinking = extractCol(line, 4);
|
||||
const images = extractCol(line, 5);
|
||||
|
||||
models.push({
|
||||
id: `${provider}/${model}`,
|
||||
name: model,
|
||||
provider,
|
||||
contextWindow: parseSize(context),
|
||||
maxTokens: parseSize(maxOut),
|
||||
reasoning: thinking === 'yes',
|
||||
images: images === 'yes',
|
||||
});
|
||||
}
|
||||
|
||||
logger.info('pi --list-models returned', { count: models.length });
|
||||
cachedModels = models;
|
||||
cacheTimestamp = Date.now();
|
||||
return models;
|
||||
} catch (err) {
|
||||
logger.error('Failed to run pi --list-models', { error: String(err) });
|
||||
return [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user