- 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
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -442,6 +442,16 @@ async function loadModelsDevData(): Promise<Model[]> {
|
||||
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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<TApi extends Api>(
|
||||
|
||||
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" },
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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<void> {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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<string, typeof discoveredServers>();
|
||||
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)}`);
|
||||
|
||||
Reference in New Issue
Block a user