From 4bb1823a205c7e900765e73b8de6f3eb97d939fd Mon Sep 17 00:00:00 2001 From: Advait Paliwal Date: Fri, 20 Mar 2026 11:29:21 -0700 Subject: [PATCH] Auto-select default model and clean package conflicts --- .pi/settings.json | 1 - package-lock.json | 1 + src/index.ts | 97 +++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 86 insertions(+), 13 deletions(-) diff --git a/.pi/settings.json b/.pi/settings.json index 20c9e23..cc25bc4 100644 --- a/.pi/settings.json +++ b/.pi/settings.json @@ -4,7 +4,6 @@ "npm:pi-docparser", "npm:pi-web-access", "npm:pi-markdown-preview", - "npm:@kaiserlich-dev/pi-session-search", "npm:@aliou/pi-processes", "npm:pi-wandb", "npm:pi-zotero" diff --git a/package-lock.json b/package-lock.json index bd6f987..2dfebab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "@companion-ai/feynman", "version": "0.1.0", + "hasInstallScript": true, "dependencies": { "@companion-ai/alpha-hub": "^0.1.1", "@mariozechner/pi-ai": "^0.56.1", diff --git a/src/index.ts b/src/index.ts index 07040c2..41682f0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -101,6 +101,80 @@ function patchEmbeddedPiBranding(piPackageRoot: string): void { } } +function choosePreferredModel( + availableModels: Array<{ provider: string; id: string }>, +): { provider: string; id: string } | undefined { + const preferences = [ + { provider: "anthropic", id: "claude-opus-4-6" }, + { provider: "anthropic", id: "claude-opus-4-5" }, + { provider: "anthropic", id: "claude-sonnet-4-5" }, + { provider: "openai", id: "gpt-5.4" }, + { provider: "openai", id: "gpt-5" }, + ]; + + for (const preferred of preferences) { + const match = availableModels.find( + (model) => model.provider === preferred.provider && model.id === preferred.id, + ); + if (match) { + return match; + } + } + + return availableModels[0]; +} + +function normalizeFeynmanSettings( + settingsPath: string, + bundledSettingsPath: string, + defaultThinkingLevel: ThinkingLevel, + authPath: string, +): void { + let settings: Record = {}; + + if (existsSync(settingsPath)) { + try { + settings = JSON.parse(readFileSync(settingsPath, "utf8")); + } catch { + settings = {}; + } + } + else if (existsSync(bundledSettingsPath)) { + try { + settings = JSON.parse(readFileSync(bundledSettingsPath, "utf8")); + } catch { + settings = {}; + } + } + + if (Array.isArray(settings.packages)) { + settings.packages = settings.packages.filter( + (entry) => entry !== "npm:@kaiserlich-dev/pi-session-search", + ); + } + + if (!settings.defaultThinkingLevel) { + settings.defaultThinkingLevel = defaultThinkingLevel; + } + + const authStorage = AuthStorage.create(authPath); + const modelRegistry = new ModelRegistry(authStorage); + const availableModels = modelRegistry.getAvailable().map((model) => ({ + provider: model.provider, + id: model.id, + })); + + if ((!settings.defaultProvider || !settings.defaultModel) && availableModels.length > 0) { + const preferredModel = choosePreferredModel(availableModels); + if (preferredModel) { + settings.defaultProvider = preferredModel.provider; + settings.defaultModel = preferredModel.id; + } + } + + writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8"); +} + async function main(): Promise { const here = dirname(fileURLToPath(import.meta.url)); const appRoot = resolve(here, ".."); @@ -131,8 +205,18 @@ async function main(): Promise { return; } + const workingDir = resolve(values.cwd ?? process.cwd()); + const sessionDir = resolve(values["session-dir"] ?? resolve(homedir(), ".feynman", "sessions")); + mkdirSync(sessionDir, { recursive: true }); + mkdirSync(feynmanAgentDir, { recursive: true }); + const feynmanSettingsPath = resolve(feynmanAgentDir, "settings.json"); + const feynmanAuthPath = resolve(feynmanAgentDir, "auth.json"); + const thinkingLevel = normalizeThinkingLevel(values.thinking ?? process.env.FEYNMAN_THINKING) ?? "medium"; + normalizeFeynmanSettings(feynmanSettingsPath, bundledSettingsPath, thinkingLevel, feynmanAuthPath); + if (values["alpha-login"]) { const result = await loginAlpha(); + normalizeFeynmanSettings(feynmanSettingsPath, bundledSettingsPath, thinkingLevel, feynmanAuthPath); const name = (result.userInfo && typeof result.userInfo === "object" && @@ -160,25 +244,14 @@ async function main(): Promise { return; } - const workingDir = resolve(values.cwd ?? process.cwd()); - const sessionDir = resolve(values["session-dir"] ?? resolve(homedir(), ".feynman", "sessions")); - mkdirSync(sessionDir, { recursive: true }); - mkdirSync(feynmanAgentDir, { recursive: true }); - const feynmanSettingsPath = resolve(feynmanAgentDir, "settings.json"); - if (!existsSync(feynmanSettingsPath) && existsSync(bundledSettingsPath)) { - writeFileSync(feynmanSettingsPath, readFileSync(bundledSettingsPath, "utf8"), "utf8"); - } - const explicitModelSpec = values.model ?? process.env.FEYNMAN_MODEL; if (explicitModelSpec) { - const modelRegistry = new ModelRegistry(AuthStorage.create()); + const modelRegistry = new ModelRegistry(AuthStorage.create(feynmanAuthPath)); const explicitModel = parseModelSpec(explicitModelSpec, modelRegistry); if (!explicitModel) { throw new Error(`Unknown model: ${explicitModelSpec}`); } } - - const thinkingLevel = normalizeThinkingLevel(values.thinking ?? process.env.FEYNMAN_THINKING) ?? "medium"; const oneShotPrompt = values.prompt; const initialPrompt = oneShotPrompt ?? (positionals.length > 0 ? positionals.join(" ") : undefined);