Polish Feynman harness and stabilize Pi web runtime

This commit is contained in:
Advait Paliwal
2026-03-22 20:20:26 -07:00
parent 7f0def3a4c
commit 46810f97b7
47 changed files with 3178 additions and 869 deletions

273
src/model/commands.ts Normal file
View File

@@ -0,0 +1,273 @@
import { AuthStorage } from "@mariozechner/pi-coding-agent";
import { writeFileSync } from "node:fs";
import { readJson } from "../pi/settings.js";
import { promptChoice, promptText } from "../setup/prompts.js";
import { printInfo, printSection, printSuccess, printWarning } from "../ui/terminal.js";
import {
buildModelStatusSnapshotFromRecords,
chooseRecommendedModel,
getAvailableModelRecords,
getSupportedModelRecords,
type ModelStatusSnapshot,
} from "./catalog.js";
function formatProviderSummaryLine(status: ModelStatusSnapshot["providers"][number]): string {
const state = status.configured ? `${status.availableModels} authenticated` : "not authenticated";
const flags = [
status.current ? "current" : undefined,
status.recommended ? "recommended" : undefined,
].filter(Boolean);
return `${status.label}: ${state}, ${status.supportedModels} supported${flags.length > 0 ? ` (${flags.join(", ")})` : ""}`;
}
function collectModelStatus(settingsPath: string, authPath: string): ModelStatusSnapshot {
return buildModelStatusSnapshotFromRecords(
getSupportedModelRecords(authPath),
getAvailableModelRecords(authPath),
getCurrentModelSpec(settingsPath),
);
}
type OAuthProviderInfo = {
id: string;
name?: string;
usesCallbackServer?: boolean;
};
function getOAuthProviders(authPath: string): OAuthProviderInfo[] {
return AuthStorage.create(authPath).getOAuthProviders() as OAuthProviderInfo[];
}
function resolveOAuthProvider(authPath: string, input: string): OAuthProviderInfo | undefined {
const normalizedInput = input.trim().toLowerCase();
if (!normalizedInput) {
return undefined;
}
return getOAuthProviders(authPath).find((provider) => provider.id.toLowerCase() === normalizedInput);
}
async function selectOAuthProvider(authPath: string, action: "login" | "logout"): Promise<OAuthProviderInfo | undefined> {
const providers = getOAuthProviders(authPath);
if (providers.length === 0) {
printWarning("No Pi OAuth model providers are available.");
return undefined;
}
if (providers.length === 1) {
return providers[0];
}
const choices = providers.map((provider) => `${provider.id}${provider.name ?? provider.id}`);
choices.push("Cancel");
const selection = await promptChoice(`Choose an OAuth provider to ${action}:`, choices, 0);
if (selection >= providers.length) {
return undefined;
}
return providers[selection];
}
function resolveAvailableModelSpec(authPath: string, input: string): string | undefined {
const normalizedInput = input.trim().toLowerCase();
if (!normalizedInput) {
return undefined;
}
const available = getAvailableModelRecords(authPath);
const fullSpecMatch = available.find((model) => `${model.provider}/${model.id}`.toLowerCase() === normalizedInput);
if (fullSpecMatch) {
return `${fullSpecMatch.provider}/${fullSpecMatch.id}`;
}
const exactIdMatches = available.filter((model) => model.id.toLowerCase() === normalizedInput);
if (exactIdMatches.length === 1) {
return `${exactIdMatches[0]!.provider}/${exactIdMatches[0]!.id}`;
}
return undefined;
}
export function getCurrentModelSpec(settingsPath: string): string | undefined {
const settings = readJson(settingsPath);
if (typeof settings.defaultProvider === "string" && typeof settings.defaultModel === "string") {
return `${settings.defaultProvider}/${settings.defaultModel}`;
}
return undefined;
}
export function printModelStatus(settingsPath: string, authPath: string): void {
const status = collectModelStatus(settingsPath, authPath);
printInfo(`Current default model: ${status.current ?? "not set"}`);
printInfo(`Current default valid: ${status.currentValid ? "yes" : "no"}`);
printInfo(`Authenticated models: ${status.availableModels.length}`);
printInfo(`Providers with auth: ${status.providers.filter((provider) => provider.configured).length}`);
printInfo(`Research recommendation: ${status.recommended ?? "none available"}`);
if (status.recommendationReason) {
printInfo(`Recommendation reason: ${status.recommendationReason}`);
}
if (status.providers.length > 0) {
printSection("Providers");
for (const provider of status.providers) {
printInfo(formatProviderSummaryLine(provider));
}
}
if (status.guidance.length > 0) {
printSection("Next Steps");
for (const line of status.guidance) {
printWarning(line);
}
}
}
export function printModelProviders(settingsPath: string, authPath: string): void {
const status = collectModelStatus(settingsPath, authPath);
for (const provider of status.providers) {
printInfo(formatProviderSummaryLine(provider));
}
const oauthProviders = getOAuthProviders(authPath);
if (oauthProviders.length > 0) {
printSection("OAuth Login");
for (const provider of oauthProviders) {
printInfo(`${provider.id}${provider.name ?? provider.id}`);
}
}
if (status.providers.length === 0) {
printWarning("No Pi model providers are visible in the current runtime.");
}
}
export function printModelList(settingsPath: string, authPath: string): void {
const status = collectModelStatus(settingsPath, authPath);
if (status.availableModels.length === 0) {
printWarning("No authenticated Pi models are currently available.");
for (const line of status.guidance) {
printInfo(line);
}
return;
}
let lastProvider: string | undefined;
for (const spec of status.availableModels) {
const [provider] = spec.split("/", 1);
if (provider !== lastProvider) {
lastProvider = provider;
printSection(provider);
}
const markers = [
spec === status.current ? "current" : undefined,
spec === status.recommended ? "recommended" : undefined,
].filter(Boolean);
printInfo(`${spec}${markers.length > 0 ? ` (${markers.join(", ")})` : ""}`);
}
}
export function printModelRecommendation(authPath: string): void {
const recommendation = chooseRecommendedModel(authPath);
if (!recommendation) {
printWarning("No authenticated Pi models are available to recommend.");
printInfo("Run `feynman model login <provider>` or add provider credentials that Pi can see, then rerun this command.");
return;
}
printSuccess(`Recommended model: ${recommendation.spec}`);
printInfo(recommendation.reason);
}
export async function loginModelProvider(authPath: string, providerId?: string): Promise<void> {
const provider = providerId ? resolveOAuthProvider(authPath, providerId) : await selectOAuthProvider(authPath, "login");
if (!provider) {
if (providerId) {
throw new Error(`Unknown OAuth model provider: ${providerId}`);
}
printInfo("Login cancelled.");
return;
}
const authStorage = AuthStorage.create(authPath);
const abortController = new AbortController();
await authStorage.login(provider.id, {
onAuth: (info: { url: string; instructions?: string }) => {
printSection(`Login: ${provider.name ?? provider.id}`);
printInfo(`Open this URL: ${info.url}`);
if (info.instructions) {
printInfo(info.instructions);
}
},
onPrompt: async (prompt: { message: string; placeholder?: string }) => {
return promptText(prompt.message, prompt.placeholder ?? "");
},
onProgress: (message: string) => {
printInfo(message);
},
onManualCodeInput: async () => {
return promptText("Paste redirect URL or auth code");
},
signal: abortController.signal,
});
printSuccess(`Model provider login complete: ${provider.id}`);
}
export async function logoutModelProvider(authPath: string, providerId?: string): Promise<void> {
const provider = providerId ? resolveOAuthProvider(authPath, providerId) : await selectOAuthProvider(authPath, "logout");
if (!provider) {
if (providerId) {
throw new Error(`Unknown OAuth model provider: ${providerId}`);
}
printInfo("Logout cancelled.");
return;
}
AuthStorage.create(authPath).logout(provider.id);
printSuccess(`Model provider logout complete: ${provider.id}`);
}
export function setDefaultModelSpec(settingsPath: string, authPath: string, spec: string): void {
const resolvedSpec = resolveAvailableModelSpec(authPath, spec);
if (!resolvedSpec) {
throw new Error(`Model not available in Pi auth storage: ${spec}. Run \`feynman model list\` first.`);
}
const [provider, ...rest] = resolvedSpec.split("/");
const modelId = rest.join("/");
const settings = readJson(settingsPath);
settings.defaultProvider = provider;
settings.defaultModel = modelId;
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
printSuccess(`Default model set to ${resolvedSpec}`);
}
export async function runModelSetup(settingsPath: string, authPath: string): Promise<void> {
const status = collectModelStatus(settingsPath, authPath);
if (status.availableModels.length === 0) {
printWarning("No Pi models are currently authenticated for Feynman.");
for (const line of status.guidance) {
printInfo(line);
}
printInfo("Tip: run `feynman model login <provider>` if your provider supports Pi OAuth login.");
return;
}
const choices = status.availableModels.map((spec) => {
const markers = [
spec === status.recommended ? "recommended" : undefined,
spec === status.current ? "current" : undefined,
].filter(Boolean);
return `${spec}${markers.length > 0 ? ` (${markers.join(", ")})` : ""}`;
});
choices.push(`Keep current (${status.current ?? "unset"})`);
const defaultIndex = status.current ? Math.max(0, status.availableModels.indexOf(status.current)) : 0;
const selection = await promptChoice("Select your default research model:", choices, defaultIndex >= 0 ? defaultIndex : 0);
if (selection >= status.availableModels.length) {
printInfo("Skipped (keeping current model)");
return;
}
setDefaultModelSpec(settingsPath, authPath, status.availableModels[selection]!);
}