From 412ab9ea00a567ba47f96e5a77e44198e8f47163 Mon Sep 17 00:00:00 2001 From: can1357 Date: Thu, 12 Feb 2026 18:12:31 +0100 Subject: [PATCH] fix(coding-agent,ai): resolve issues #33, #34, #35, #37 - show help instead of crashing on `omp setup` with no args - show runtime-discovered MCP servers in `/mcp list` - remove deprecated Anthropic model entries from models.json - sort models by recency in model selector --- packages/ai/CHANGELOG.md | 7 + packages/ai/README.md | 4 +- packages/ai/scripts/generate-models.ts | 10 ++ packages/ai/src/models.json | 120 ------------------ packages/ai/src/providers/amazon-bedrock.ts | 10 +- packages/ai/src/providers/anthropic.ts | 2 +- packages/ai/test/context-overflow.test.ts | 4 +- packages/ai/test/empty.test.ts | 4 +- packages/ai/test/handoff.test.ts | 14 +- packages/ai/test/image-limits.test.ts | 10 +- packages/ai/test/stream.test.ts | 4 +- packages/ai/test/tokens.test.ts | 4 +- .../ai/test/tool-call-without-result.test.ts | 4 +- packages/ai/test/total-tokens.test.ts | 4 +- packages/ai/test/unicode-surrogate.test.ts | 4 +- packages/coding-agent/CHANGELOG.md | 9 ++ packages/coding-agent/src/commands/setup.ts | 9 +- packages/coding-agent/src/mcp/manager.ts | 9 ++ .../src/modes/components/model-selector.ts | 30 ++++- .../controllers/mcp-command-controller.ts | 49 ++++++- 20 files changed, 151 insertions(+), 160 deletions(-) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index e1f5b7a55..4fb28b630 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -1,6 +1,13 @@ # Changelog ## [Unreleased] +### Removed + +- Removed deprecated Anthropic model entries: `claude-3-5-haiku-latest`, `claude-3-5-haiku-20241022`, `claude-3-7-sonnet-20250219`, `claude-3-7-sonnet-latest`, `claude-3-opus-20240229`, `claude-3-sonnet-20240229` ([#33](https://github.com/can1357/oh-my-pi/issues/33)) + +### Fixed + +- Added deprecation filter in model generation script to prevent re-adding deprecated Anthropic models ([#33](https://github.com/can1357/oh-my-pi/issues/33)) ## [11.14.1] - 2026-02-12 ### Added diff --git a/packages/ai/README.md b/packages/ai/README.md index 5bbe0f29e..d063e840d 100644 --- a/packages/ai/README.md +++ b/packages/ai/README.md @@ -856,7 +856,7 @@ const restored: Context = JSON.parse(localStorage.getItem("conversation")!); restored.messages.push({ role: "user", content: "Tell me more about its type system" }); // Continue with any model -const newModel = getModel("anthropic", "claude-3-5-haiku-20241022"); +const newModel = getModel("anthropic", "claude-haiku-4-5-20251001"); const continuation = await complete(newModel, restored); ``` @@ -870,7 +870,7 @@ The library supports browser environments. You must pass the API key explicitly import { getModel, complete } from "@oh-my-pi/pi-ai"; // API key must be passed explicitly in browser -const model = getModel("anthropic", "claude-3-5-haiku-20241022"); +const model = getModel("anthropic", "claude-haiku-4-5-20251001"); const response = await complete( model, diff --git a/packages/ai/scripts/generate-models.ts b/packages/ai/scripts/generate-models.ts index d89a59f13..6ca64ac60 100644 --- a/packages/ai/scripts/generate-models.ts +++ b/packages/ai/scripts/generate-models.ts @@ -442,6 +442,16 @@ async function loadModelsDevData(): Promise { const m = model as ModelsDevModel; if (m.tool_call !== true) continue; + // Skip deprecated Anthropic models (old naming convention) + if ( + modelId.startsWith("claude-3-5-haiku") || + modelId.startsWith("claude-3-7-sonnet") || + modelId === "claude-3-opus-20240229" || + modelId === "claude-3-sonnet-20240229" + ) { + continue; + } + models.push({ id: modelId, name: m.name || modelId, diff --git a/packages/ai/src/models.json b/packages/ai/src/models.json index 638a75669..4e8f11bd2 100644 --- a/packages/ai/src/models.json +++ b/packages/ai/src/models.json @@ -1452,26 +1452,6 @@ "contextWindow": 200000, "maxTokens": 128000 }, - "claude-3-5-haiku-latest": { - "id": "claude-3-5-haiku-latest", - "name": "Claude Haiku 3.5 (latest)", - "api": "anthropic-messages", - "provider": "anthropic", - "baseUrl": "https://api.anthropic.com", - "reasoning": false, - "input": [ - "text", - "image" - ], - "cost": { - "input": 0.8, - "output": 4, - "cacheRead": 0.08, - "cacheWrite": 1 - }, - "contextWindow": 200000, - "maxTokens": 8192 - }, "claude-opus-4-5": { "id": "claude-opus-4-5", "name": "Claude Opus 4.5 (latest)", @@ -1492,26 +1472,6 @@ "contextWindow": 200000, "maxTokens": 64000 }, - "claude-3-opus-20240229": { - "id": "claude-3-opus-20240229", - "name": "Claude Opus 3", - "api": "anthropic-messages", - "provider": "anthropic", - "baseUrl": "https://api.anthropic.com", - "reasoning": false, - "input": [ - "text", - "image" - ], - "cost": { - "input": 15, - "output": 75, - "cacheRead": 1.5, - "cacheWrite": 18.75 - }, - "contextWindow": 200000, - "maxTokens": 4096 - }, "claude-opus-4-5-20251101": { "id": "claude-opus-4-5-20251101", "name": "Claude Opus 4.5", @@ -1612,26 +1572,6 @@ "contextWindow": 200000, "maxTokens": 32000 }, - "claude-3-5-haiku-20241022": { - "id": "claude-3-5-haiku-20241022", - "name": "Claude Haiku 3.5", - "api": "anthropic-messages", - "provider": "anthropic", - "baseUrl": "https://api.anthropic.com", - "reasoning": false, - "input": [ - "text", - "image" - ], - "cost": { - "input": 0.8, - "output": 4, - "cacheRead": 0.08, - "cacheWrite": 1 - }, - "contextWindow": 200000, - "maxTokens": 8192 - }, "claude-3-haiku-20240307": { "id": "claude-3-haiku-20240307", "name": "Claude Haiku 3", @@ -1652,46 +1592,6 @@ "contextWindow": 200000, "maxTokens": 4096 }, - "claude-3-7-sonnet-20250219": { - "id": "claude-3-7-sonnet-20250219", - "name": "Claude Sonnet 3.7", - "api": "anthropic-messages", - "provider": "anthropic", - "baseUrl": "https://api.anthropic.com", - "reasoning": true, - "input": [ - "text", - "image" - ], - "cost": { - "input": 3, - "output": 15, - "cacheRead": 0.3, - "cacheWrite": 3.75 - }, - "contextWindow": 200000, - "maxTokens": 64000 - }, - "claude-3-7-sonnet-latest": { - "id": "claude-3-7-sonnet-latest", - "name": "Claude Sonnet 3.7 (latest)", - "api": "anthropic-messages", - "provider": "anthropic", - "baseUrl": "https://api.anthropic.com", - "reasoning": true, - "input": [ - "text", - "image" - ], - "cost": { - "input": 3, - "output": 15, - "cacheRead": 0.3, - "cacheWrite": 3.75 - }, - "contextWindow": 200000, - "maxTokens": 64000 - }, "claude-sonnet-4-0": { "id": "claude-sonnet-4-0", "name": "Claude Sonnet 4 (latest)", @@ -1732,26 +1632,6 @@ "contextWindow": 200000, "maxTokens": 32000 }, - "claude-3-sonnet-20240229": { - "id": "claude-3-sonnet-20240229", - "name": "Claude Sonnet 3", - "api": "anthropic-messages", - "provider": "anthropic", - "baseUrl": "https://api.anthropic.com", - "reasoning": false, - "input": [ - "text", - "image" - ], - "cost": { - "input": 3, - "output": 15, - "cacheRead": 0.3, - "cacheWrite": 0.3 - }, - "contextWindow": 200000, - "maxTokens": 4096 - }, "claude-haiku-4-5-20251001": { "id": "claude-haiku-4-5-20251001", "name": "Claude Haiku 4.5", diff --git a/packages/ai/src/providers/amazon-bedrock.ts b/packages/ai/src/providers/amazon-bedrock.ts index ac97d4362..14951c0b8 100644 --- a/packages/ai/src/providers/amazon-bedrock.ts +++ b/packages/ai/src/providers/amazon-bedrock.ts @@ -331,17 +331,17 @@ function resolveCacheRetention(cacheRetention?: CacheRetention): CacheRetention /** * Check if the model supports prompt caching. - * Supported: Claude 3.5 Haiku, Claude 3.7 Sonnet, Claude 4.x models + * Supported: Claude 3.5 Haiku, Claude 3.7 Sonnet, Claude 4.x+ models, Haiku 4.5+ */ function supportsPromptCaching(model: Model<"bedrock-converse-stream">): boolean { if (model.cost.cacheRead || model.cost.cacheWrite) return true; const id = model.id.toLowerCase(); // Claude 4.x models (opus-4, sonnet-4, haiku-4) if (id.includes("claude") && (id.includes("-4-") || id.includes("-4."))) return true; - // Claude 3.7 Sonnet - if (id.includes("claude-3-7-sonnet")) return true; - // Claude 3.5 Haiku - if (id.includes("claude-3-5-haiku")) return true; + // Claude 3.5 Haiku, Claude 3.7 Sonnet (legacy naming) + if (id.includes("claude-3-7-sonnet") || id.includes("claude-3-5-haiku")) return true; + // Claude Haiku 4.5+ (new naming) + if (id.includes("claude-haiku")) return true; return false; } diff --git a/packages/ai/src/providers/anthropic.ts b/packages/ai/src/providers/anthropic.ts index 524d4b4df..6cb6c67e6 100644 --- a/packages/ai/src/providers/anthropic.ts +++ b/packages/ai/src/providers/anthropic.ts @@ -642,7 +642,7 @@ function buildParams( stream: true, }; - const includeClaudeCodeSystem = !model.id.startsWith("claude-3-5-haiku"); + const includeClaudeCodeSystem = !model.id.includes("haiku"); const systemBlocks = buildAnthropicSystemBlocks(context.systemPrompt, { includeClaudeCodeInstruction: includeClaudeCodeSystem, }); diff --git a/packages/ai/test/context-overflow.test.ts b/packages/ai/test/context-overflow.test.ts index 00c717f05..02026db61 100644 --- a/packages/ai/test/context-overflow.test.ts +++ b/packages/ai/test/context-overflow.test.ts @@ -98,8 +98,8 @@ function logResult(result: OverflowResult) { describe("Context overflow error handling", () => { describe.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))("Anthropic (API Key)", () => { - it("claude-3-5-haiku - should detect overflow via isContextOverflow", async () => { - const model = getModel("anthropic", "claude-3-5-haiku-20241022"); + it("claude-haiku-4-5 - should detect overflow via isContextOverflow", async () => { + const model = getModel("anthropic", "claude-haiku-4-5-20251001"); const result = await testContextOverflow(model, Bun.env.ANTHROPIC_API_KEY!); logResult(result); diff --git a/packages/ai/test/empty.test.ts b/packages/ai/test/empty.test.ts index 981b3b2f1..64fe5744b 100644 --- a/packages/ai/test/empty.test.ts +++ b/packages/ai/test/empty.test.ts @@ -250,7 +250,7 @@ describe("AI Providers Empty Message Tests", () => { }); describe.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))("Anthropic Provider Empty Messages", () => { - const llm = getModel("anthropic", "claude-3-5-haiku-20241022"); + const llm = getModel("anthropic", "claude-haiku-4-5-20251001"); it( "should handle empty content array", @@ -466,7 +466,7 @@ describe("AI Providers Empty Message Tests", () => { }); describe("Anthropic OAuth Provider Empty Messages", () => { - const llm = getModel("anthropic", "claude-3-5-haiku-20241022"); + const llm = getModel("anthropic", "claude-haiku-4-5-20251001"); it.skipIf(!anthropicOAuthToken)( "should handle empty content array", diff --git a/packages/ai/test/handoff.test.ts b/packages/ai/test/handoff.test.ts index ddd8e9fec..070344b5c 100644 --- a/packages/ai/test/handoff.test.ts +++ b/packages/ai/test/handoff.test.ts @@ -41,7 +41,7 @@ const providerContexts = { }, ], provider: "anthropic", - model: "claude-3-5-haiku-latest", + model: "claude-haiku-4-5", usage: { input: 100, output: 50, @@ -368,13 +368,13 @@ async function testProviderHandoff( describe("Cross-Provider Handoff Tests", () => { describe.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))("Anthropic Provider Handoff", () => { - const model = getModel("anthropic", "claude-3-5-haiku-20241022"); + const model = getModel("anthropic", "claude-haiku-4-5-20251001"); it("should handle contexts from all providers", async () => { console.log("\nTesting Anthropic with pre-built contexts:\n"); const contextTests = [ - { label: "Anthropic-style", context: providerContexts.anthropic, sourceModel: "claude-3-5-haiku-20241022" }, + { label: "Anthropic-style", context: providerContexts.anthropic, sourceModel: "claude-haiku-4-5-20251001" }, { label: "Google-style", context: providerContexts.google, sourceModel: "gemini-2.5-flash" }, { label: "OpenAI-Completions", context: providerContexts.openaiCompletions, sourceModel: "gpt-4o-mini" }, { label: "OpenAI-Responses", context: providerContexts.openaiResponses, sourceModel: "gpt-5-mini" }, @@ -410,7 +410,7 @@ describe("Cross-Provider Handoff Tests", () => { console.log("\nTesting Google with pre-built contexts:\n"); const contextTests = [ - { label: "Anthropic-style", context: providerContexts.anthropic, sourceModel: "claude-3-5-haiku-20241022" }, + { label: "Anthropic-style", context: providerContexts.anthropic, sourceModel: "claude-haiku-4-5-20251001" }, { label: "Google-style", context: providerContexts.google, sourceModel: "gemini-2.5-flash" }, { label: "OpenAI-Completions", context: providerContexts.openaiCompletions, sourceModel: "gpt-4o-mini" }, { label: "OpenAI-Responses", context: providerContexts.openaiResponses, sourceModel: "gpt-5-mini" }, @@ -446,7 +446,7 @@ describe("Cross-Provider Handoff Tests", () => { console.log("\nTesting OpenAI Completions with pre-built contexts:\n"); const contextTests = [ - { label: "Anthropic-style", context: providerContexts.anthropic, sourceModel: "claude-3-5-haiku-20241022" }, + { label: "Anthropic-style", context: providerContexts.anthropic, sourceModel: "claude-haiku-4-5-20251001" }, { label: "Google-style", context: providerContexts.google, sourceModel: "gemini-2.5-flash" }, { label: "OpenAI-Completions", context: providerContexts.openaiCompletions, sourceModel: "gpt-4o-mini" }, { label: "OpenAI-Responses", context: providerContexts.openaiResponses, sourceModel: "gpt-5-mini" }, @@ -482,7 +482,7 @@ describe("Cross-Provider Handoff Tests", () => { console.log("\nTesting OpenAI Responses with pre-built contexts:\n"); const contextTests = [ - { label: "Anthropic-style", context: providerContexts.anthropic, sourceModel: "claude-3-5-haiku-20241022" }, + { label: "Anthropic-style", context: providerContexts.anthropic, sourceModel: "claude-haiku-4-5-20251001" }, { label: "Google-style", context: providerContexts.google, sourceModel: "gemini-2.5-flash" }, { label: "OpenAI-Completions", context: providerContexts.openaiCompletions, sourceModel: "gpt-4o-mini" }, { label: "OpenAI-Responses", context: providerContexts.openaiResponses, sourceModel: "gpt-5-mini" }, @@ -518,7 +518,7 @@ describe("Cross-Provider Handoff Tests", () => { console.log("\nTesting Mistral with pre-built contexts:\n"); const contextTests = [ - { label: "Anthropic-style", context: providerContexts.anthropic, sourceModel: "claude-3-5-haiku-20241022" }, + { label: "Anthropic-style", context: providerContexts.anthropic, sourceModel: "claude-haiku-4-5-20251001" }, { label: "Google-style", context: providerContexts.google, sourceModel: "gemini-2.5-flash" }, { label: "OpenAI-Completions", context: providerContexts.openaiCompletions, sourceModel: "gpt-4o-mini" }, { label: "OpenAI-Responses", context: providerContexts.openaiResponses, sourceModel: "gpt-5-mini" }, diff --git a/packages/ai/test/image-limits.test.ts b/packages/ai/test/image-limits.test.ts index d952aff9a..3dd942200 100644 --- a/packages/ai/test/image-limits.test.ts +++ b/packages/ai/test/image-limits.test.ts @@ -14,7 +14,7 @@ * BASIC LIMITS (small images): * | Provider | Model | Max Images | Max Size | Max Dim | * |-------------|--------------------|------------|----------|----------| - * | Anthropic | claude-3-5-haiku | 100 | 5MB | 8000px | + * | Anthropic | claude-haiku-4-5 | 100 | 5MB | 8000px | * | OpenAI | gpt-4o-mini | 500 | ≥25MB | ≥20000px | * | Gemini | gemini-2.5-flash | ~2000* | ≥40MB | 8000px | * | Mistral | pixtral-12b | 8 | ~15MB | 8000px | @@ -275,11 +275,11 @@ describe("Image Limits E2E Tests", () => { }); // ------------------------------------------------------------------------- - // Anthropic (claude-3-5-haiku-20241022) + // Anthropic (claude-haiku-4-5-20251001) // Limits: 100 images, 5MB per image, 8000px max dimension // ------------------------------------------------------------------------- - describe.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))("Anthropic (claude-3-5-haiku-20241022)", () => { - const model = getModel("anthropic", "claude-3-5-haiku-20241022"); + describe.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))("Anthropic (claude-haiku-4-5-20251001)", () => { + const model = getModel("anthropic", "claude-haiku-4-5-20251001"); it("should accept a small number of images (5)", async () => { const result = await testImageCount(model, 5, smallImage); @@ -979,7 +979,7 @@ describe("Image Limits E2E Tests", () => { it.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))( "Anthropic: max ~3MB images before rejection", async () => { - const model = getModel("anthropic", "claude-3-5-haiku-20241022"); + const model = getModel("anthropic", "claude-haiku-4-5-20251001"); const image3mb = await getImageAtSize(3); // 32MB total limit / ~4MB actual = ~8 images const counts = [1, 2, 4, 6, 8, 10, 12]; diff --git a/packages/ai/test/stream.test.ts b/packages/ai/test/stream.test.ts index fb79ac985..e732127d3 100644 --- a/packages/ai/test/stream.test.ts +++ b/packages/ai/test/stream.test.ts @@ -553,8 +553,8 @@ describe("Generate E2E Tests", () => { ); }); - describe.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))("Anthropic Provider (claude-3-5-haiku-20241022)", () => { - const model = getModel("anthropic", "claude-3-5-haiku-20241022"); + describe.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))("Anthropic Provider (claude-haiku-4-5-20251001)", () => { + const model = getModel("anthropic", "claude-haiku-4-5-20251001"); it( "should complete basic text generation", diff --git a/packages/ai/test/tokens.test.ts b/packages/ai/test/tokens.test.ts index 4c246f50d..8046640f4 100644 --- a/packages/ai/test/tokens.test.ts +++ b/packages/ai/test/tokens.test.ts @@ -109,7 +109,7 @@ describe("Token Statistics on Abort", () => { }); describe.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))("Anthropic Provider", () => { - const llm = getModel("anthropic", "claude-3-5-haiku-20241022"); + const llm = getModel("anthropic", "claude-haiku-4-5-20251001"); it( "should include token stats when aborted mid-stream", @@ -181,7 +181,7 @@ describe("Token Statistics on Abort", () => { }); describe("Anthropic OAuth Provider", () => { - const llm = getModel("anthropic", "claude-3-5-haiku-20241022"); + const llm = getModel("anthropic", "claude-haiku-4-5-20251001"); it.skipIf(!anthropicOAuthToken)( "should include token stats when aborted mid-stream", diff --git a/packages/ai/test/tool-call-without-result.test.ts b/packages/ai/test/tool-call-without-result.test.ts index 038aff69e..4a860a02e 100644 --- a/packages/ai/test/tool-call-without-result.test.ts +++ b/packages/ai/test/tool-call-without-result.test.ts @@ -135,7 +135,7 @@ describe("Tool Call Without Result Tests", () => { }); describe.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))("Anthropic Provider", () => { - const model = getModel("anthropic", "claude-3-5-haiku-20241022"); + const model = getModel("anthropic", "claude-haiku-4-5-20251001"); it( "should filter out tool calls without corresponding tool results", @@ -207,7 +207,7 @@ describe("Tool Call Without Result Tests", () => { }); describe("Anthropic OAuth Provider", () => { - const model = getModel("anthropic", "claude-3-5-haiku-20241022"); + const model = getModel("anthropic", "claude-haiku-4-5-20251001"); it.skipIf(!anthropicOAuthToken)( "should filter out tool calls without corresponding tool results", diff --git a/packages/ai/test/total-tokens.test.ts b/packages/ai/test/total-tokens.test.ts index 5882d359b..561b30969 100644 --- a/packages/ai/test/total-tokens.test.ts +++ b/packages/ai/test/total-tokens.test.ts @@ -101,9 +101,9 @@ describe("totalTokens field", () => { describe.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))("Anthropic (API Key)", () => { it( - "claude-3-5-haiku - should return totalTokens equal to sum of components", + "claude-haiku-4-5 - should return totalTokens equal to sum of components", async () => { - const llm = getModel("anthropic", "claude-3-5-haiku-20241022"); + const llm = getModel("anthropic", "claude-haiku-4-5-20251001"); console.log(`\nAnthropic / ${llm.id}:`); const { first, second } = await testTotalTokensWithCache(llm, { apiKey: Bun.env.ANTHROPIC_API_KEY }); diff --git a/packages/ai/test/unicode-surrogate.test.ts b/packages/ai/test/unicode-surrogate.test.ts index 403330c8a..33c74cc6b 100644 --- a/packages/ai/test/unicode-surrogate.test.ts +++ b/packages/ai/test/unicode-surrogate.test.ts @@ -362,7 +362,7 @@ describe("AI Providers Unicode Surrogate Pair Tests", () => { }); describe.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))("Anthropic Provider Unicode Handling", () => { - const llm = getModel("anthropic", "claude-3-5-haiku-20241022"); + const llm = getModel("anthropic", "claude-haiku-4-5-20251001"); it( "should handle emoji in tool results", @@ -390,7 +390,7 @@ describe("AI Providers Unicode Surrogate Pair Tests", () => { }); describe("Anthropic OAuth Provider Unicode Handling", () => { - const llm = getModel("anthropic", "claude-3-5-haiku-20241022"); + const llm = getModel("anthropic", "claude-haiku-4-5-20251001"); it.skipIf(!anthropicOAuthToken)( "should handle emoji in tool results", diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 8b82b1a8f..8369fc1e0 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -1,6 +1,15 @@ # Changelog ## [Unreleased] +### Added + +- Added `getAllServerNames()` method to MCPManager for enumerating all known servers + +### Fixed + +- Fixed `omp setup` crashing with uncaught exception when no component argument provided; now shows help ([#35](https://github.com/can1357/oh-my-pi/issues/35)) +- Fixed `/mcp list` showing "No MCP servers configured" when servers are loaded from discovery sources like `.claude.json`, `.cursor/mcp.json`, `.vscode/mcp.json` ([#34](https://github.com/can1357/oh-my-pi/issues/34)) +- Fixed model selector sorting to show newest models first within each provider instead of alphabetical; `-latest` aliases now appear before dated versions ([#37](https://github.com/can1357/oh-my-pi/issues/37)) ## [11.14.4] - 2026-02-12 diff --git a/packages/coding-agent/src/commands/setup.ts b/packages/coding-agent/src/commands/setup.ts index 5522ebd83..d48ff513b 100644 --- a/packages/coding-agent/src/commands/setup.ts +++ b/packages/coding-agent/src/commands/setup.ts @@ -1,7 +1,7 @@ /** * Install dependencies for optional features. */ -import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli"; +import { Args, Command, Flags, renderCommandHelp } from "@oh-my-pi/pi-utils/cli"; import { runSetupCommand, type SetupCommandArgs, type SetupComponent } from "../cli/setup-cli"; import { initTheme } from "../modes/theme/theme"; @@ -13,7 +13,7 @@ export default class Setup extends Command { static args = { component: Args.string({ description: "Component to install", - required: true, + required: false, options: COMPONENTS, }), }; @@ -25,6 +25,10 @@ export default class Setup extends Command { async run(): Promise { const { args, flags } = await this.parse(Setup); + if (!args.component) { + renderCommandHelp("omp", "setup", Setup); + return; + } const cmd: SetupCommandArgs = { component: args.component as SetupComponent, flags: { @@ -32,7 +36,6 @@ export default class Setup extends Command { check: flags.check, }, }; - await initTheme(); await runSetupCommand(cmd); } diff --git a/packages/coding-agent/src/mcp/manager.ts b/packages/coding-agent/src/mcp/manager.ts index 0796b5061..e94d5c86f 100644 --- a/packages/coding-agent/src/mcp/manager.ts +++ b/packages/coding-agent/src/mcp/manager.ts @@ -343,6 +343,15 @@ export class MCPManager { return Array.from(this.#connections.keys()); } + /** + * Get all known server names (connected, connecting, or discovered). + */ + getAllServerNames(): string[] { + return Array.from( + new Set([...this.#sources.keys(), ...this.#connections.keys(), ...this.#pendingConnections.keys()]), + ); + } + /** * Disconnect from a specific server. */ diff --git a/packages/coding-agent/src/modes/components/model-selector.ts b/packages/coding-agent/src/modes/components/model-selector.ts index cb43544a8..0c15158b7 100644 --- a/packages/coding-agent/src/modes/components/model-selector.ts +++ b/packages/coding-agent/src/modes/components/model-selector.ts @@ -203,6 +203,9 @@ export class ModelSelectorComponent extends Container { return i; }; + const dateRe = /-(\d{8})$/; + const latestRe = /-latest$/; + models.sort((a, b) => { const aKey = `${a.provider}/${a.id}`; const bKey = `${b.provider}/${b.id}`; @@ -216,10 +219,33 @@ export class ModelSelectorComponent extends Container { const bMru = mruIndex.get(bKey) ?? Number.MAX_SAFE_INTEGER; if (aMru !== bMru) return aMru - bMru; - // Finally alphabetical by provider, then id + // By provider, then recency within provider const providerCmp = a.provider.localeCompare(b.provider); if (providerCmp !== 0) return providerCmp; - return a.id.localeCompare(b.id); + + const aIsLatest = latestRe.test(a.id); + const bIsLatest = latestRe.test(b.id); + const aDate = a.id.match(dateRe)?.[1] ?? ""; + const bDate = b.id.match(dateRe)?.[1] ?? ""; + + // Both have dates or latest tags — sort by recency + const aHasRecency = aIsLatest || aDate !== ""; + const bHasRecency = bIsLatest || bDate !== ""; + + // Models with recency info come before those without + if (aHasRecency !== bHasRecency) return aHasRecency ? -1 : 1; + + // If neither has recency info, fall back to alphabetical + if (!aHasRecency) return a.id.localeCompare(b.id); + + // -latest always sorts first within recency group + if (aIsLatest !== bIsLatest) return aIsLatest ? -1 : 1; + + // Both have dates — descending (newest first) + if (aDate && bDate) return bDate.localeCompare(aDate); + + // One has date, other is latest — latest first + return aIsLatest ? -1 : bIsLatest ? 1 : a.id.localeCompare(b.id); }); } diff --git a/packages/coding-agent/src/modes/controllers/mcp-command-controller.ts b/packages/coding-agent/src/modes/controllers/mcp-command-controller.ts index ea6b7e598..f20e959bc 100644 --- a/packages/coding-agent/src/modes/controllers/mcp-command-controller.ts +++ b/packages/coding-agent/src/modes/controllers/mcp-command-controller.ts @@ -4,6 +4,7 @@ * Handles /mcp subcommands for managing MCP servers. */ import { Spacer, Text } from "@oh-my-pi/pi-tui"; +import type { SourceMeta } from "../../capability/types"; import { analyzeAuthError, discoverOAuthEndpoints, MCPManager } from "../../mcp"; import { connectToServer, disconnectServer, listTools } from "../../mcp/client"; import { @@ -765,7 +766,20 @@ export class MCPCommandController { const userServers = Object.keys(userConfig.mcpServers ?? {}); const projectServers = Object.keys(projectConfig.mcpServers ?? {}); - if (userServers.length === 0 && projectServers.length === 0) { + // Collect runtime-discovered servers not in config files + const configServerNames = new Set([...userServers, ...projectServers]); + const discoveredServers: { name: string; source: SourceMeta }[] = []; + if (this.ctx.mcpManager) { + for (const name of this.ctx.mcpManager.getAllServerNames()) { + if (configServerNames.has(name)) continue; + const source = this.ctx.mcpManager.getSource(name); + if (source) { + discoveredServers.push({ name, source }); + } + } + } + + if (userServers.length === 0 && projectServers.length === 0 && discoveredServers.length === 0) { this.#showMessage( [ "", @@ -826,6 +840,39 @@ export class MCPCommandController { lines.push(""); } + // Show discovered servers (from .claude.json, .cursor/mcp.json, .vscode/mcp.json, etc.) + if (discoveredServers.length > 0) { + // Group by source display name + path + const bySource = new Map(); + for (const entry of discoveredServers) { + const key = `${entry.source.providerName}|${entry.source.path}`; + let group = bySource.get(key); + if (!group) { + group = []; + bySource.set(key, group); + } + group.push(entry); + } + + for (const [key, entries] of bySource) { + const sepIdx = key.indexOf("|"); + const providerName = key.slice(0, sepIdx); + const sourcePath = key.slice(sepIdx + 1); + const shortPath = sourcePath.replace(process.env.HOME ?? "", "~"); + lines.push(theme.fg("accent", providerName) + theme.fg("muted", ` (${shortPath}):`)); + for (const { name } of entries) { + const state = this.ctx.mcpManager!.getConnectionStatus(name); + const status = + state === "connected" + ? theme.fg("success", " ● connected") + : state === "connecting" + ? theme.fg("muted", " ◌ connecting") + : theme.fg("muted", " ○ not connected"); + lines.push(` ${theme.fg("accent", name)}${status}`); + } + lines.push(""); + } + } this.#showMessage(lines.join("\n")); } catch (error) { this.ctx.showError(`Failed to list servers: ${error instanceof Error ? error.message : String(error)}`);