diff --git a/CHANGELOG.md b/CHANGELOG.md index 6287f6a59..dc541a620 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.2.2 - 2026-07-25 + +### Fixed + +- Claude 5 models now use the correct context windows. + ## 0.2.1 - 2026-07-24 ### Added diff --git a/package-lock.json b/package-lock.json index 3ef7ac8bc..458b1be37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "paseo", - "version": "0.2.1", + "version": "0.2.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "paseo", - "version": "0.2.1", + "version": "0.2.2", "hasInstallScript": true, "license": "AGPL-3.0-or-later", "workspaces": [ @@ -35211,7 +35211,7 @@ }, "packages/app": { "name": "@getpaseo/app", - "version": "0.2.1", + "version": "0.2.2", "dependencies": { "@codemirror/commands": "6.10.4", "@codemirror/language": "6.12.4", @@ -36236,12 +36236,12 @@ }, "packages/cli": { "name": "@getpaseo/cli", - "version": "0.2.1", + "version": "0.2.2", "dependencies": { "@clack/prompts": "^1.0.0", - "@getpaseo/client": "0.2.1", - "@getpaseo/protocol": "0.2.1", - "@getpaseo/server": "0.2.1", + "@getpaseo/client": "0.2.2", + "@getpaseo/protocol": "0.2.2", + "@getpaseo/server": "0.2.2", "chalk": "^5.3.0", "commander": "^12.0.0", "mime-types": "^2.1.35", @@ -36488,10 +36488,10 @@ }, "packages/client": { "name": "@getpaseo/client", - "version": "0.2.1", + "version": "0.2.2", "dependencies": { - "@getpaseo/protocol": "0.2.1", - "@getpaseo/relay": "0.2.1", + "@getpaseo/protocol": "0.2.2", + "@getpaseo/relay": "0.2.2", "zod": "^4.4.3" }, "devDependencies": { @@ -36502,7 +36502,7 @@ }, "packages/desktop": { "name": "@getpaseo/desktop", - "version": "0.2.1", + "version": "0.2.2", "license": "AGPL-3.0-or-later", "dependencies": { "@getpaseo/cli": "*", @@ -36745,7 +36745,7 @@ }, "packages/expo-two-way-audio": { "name": "@getpaseo/expo-two-way-audio", - "version": "0.2.1", + "version": "0.2.2", "license": "MIT", "devDependencies": { "@types/jest": "^29.5.14", @@ -37641,7 +37641,7 @@ }, "packages/highlight": { "name": "@getpaseo/highlight", - "version": "0.2.1", + "version": "0.2.2", "dependencies": { "@codemirror/language": "6.12.4", "@codemirror/legacy-modes": "^6.5.3", @@ -37873,7 +37873,7 @@ }, "packages/protocol": { "name": "@getpaseo/protocol", - "version": "0.2.1", + "version": "0.2.2", "dependencies": { "zod": "^4.4.3" }, @@ -37886,7 +37886,7 @@ }, "packages/relay": { "name": "@getpaseo/relay", - "version": "0.2.1", + "version": "0.2.2", "dependencies": { "base64-js": "^1.5.1", "tweetnacl": "^1.0.3", @@ -38104,15 +38104,15 @@ }, "packages/server": { "name": "@getpaseo/server", - "version": "0.2.1", + "version": "0.2.2", "dependencies": { "@agentclientprotocol/sdk": "^0.17.1", - "@anthropic-ai/claude-agent-sdk": "^0.3.214", + "@anthropic-ai/claude-agent-sdk": "^0.3.220", "@anthropic-ai/sdk": "^0.104.2", - "@getpaseo/client": "0.2.1", - "@getpaseo/highlight": "0.2.1", - "@getpaseo/protocol": "0.2.1", - "@getpaseo/relay": "0.2.1", + "@getpaseo/client": "0.2.2", + "@getpaseo/highlight": "0.2.2", + "@getpaseo/protocol": "0.2.2", + "@getpaseo/relay": "0.2.2", "@isaacs/ttlcache": "^2.1.4", "@modelcontextprotocol/sdk": "^1.20.1", "@opencode-ai/sdk": "1.14.46", @@ -38156,22 +38156,22 @@ } }, "packages/server/node_modules/@anthropic-ai/claude-agent-sdk": { - "version": "0.3.214", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.214.tgz", - "integrity": "sha512-wt5ImwhU+p259Zt4K/Q9v5xVi6ruxYO5+KFICyJxnjs/QFEClAeSRqhcXx1J8jgGfatPW1faw09hkm66680UjA==", + "version": "0.3.220", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.220.tgz", + "integrity": "sha512-glc7SdwPkOkLw8oxwLo9PKTdLJGqW/PIR4urWXFoRtX9YllwozsEVc5Tc1+EvLSkfrsxPJqQWqOgpjUOQXf1oA==", "license": "SEE LICENSE IN README.md", "engines": { "node": ">=18.0.0" }, "optionalDependencies": { - "@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.214", - "@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.214", - "@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.214", - "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.214", - "@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.214", - "@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.214", - "@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.214", - "@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.214" + "@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.220", + "@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.220", + "@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.220", + "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.220", + "@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.220", + "@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.220", + "@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.220", + "@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.220" }, "peerDependencies": { "@anthropic-ai/sdk": ">=0.93.0", @@ -38180,9 +38180,9 @@ } }, "packages/server/node_modules/@anthropic-ai/claude-agent-sdk/node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64": { - "version": "0.3.214", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.214.tgz", - "integrity": "sha512-vAAOeVtlXs3p7MpFVfvfu9ja32eaKtB+MDZnPxCbdzxJk5nofCHlNsHa1UJwXHOsP9lOnFYNIccYztsZ4DNaJA==", + "version": "0.3.220", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.220.tgz", + "integrity": "sha512-7VxlbEosK7DODiOnsjoVd0DSJzbnaPrM2jelMHI0y8zx1UnLS3WC6EFUXbvy74F2sXqEznh2tzn7EKWInaRN6Q==", "cpu": [ "arm64" ], @@ -38193,9 +38193,9 @@ ] }, "packages/server/node_modules/@anthropic-ai/claude-agent-sdk/node_modules/@anthropic-ai/claude-agent-sdk-darwin-x64": { - "version": "0.3.214", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.214.tgz", - "integrity": "sha512-NTbV8U2yucxCWqEiDC7L0MUehNmd8x3Op8OGzLZRhMF3xmQBy2DxpXiNZgSwwKWNDC3HdgKmJM5ZBbT7XrF/0g==", + "version": "0.3.220", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.220.tgz", + "integrity": "sha512-X9RwDsSmbF6ultKZroaip+DL8WRgC64gHbrAwrRlAFSPNZV7zmJyP2ur8rW7KrxqmtuehdMMkw8+SAC/6hD2PA==", "cpu": [ "x64" ], @@ -38206,9 +38206,9 @@ ] }, "packages/server/node_modules/@anthropic-ai/claude-agent-sdk/node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64": { - "version": "0.3.214", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.214.tgz", - "integrity": "sha512-KBCf+BlusG0ZcgvpjjHwv1kh+6WiR8vJbPjpR2udwkrtcQgKLN+24l+FeaQEzO2TL+ExCmM1KC4tNPvnPpy+tw==", + "version": "0.3.220", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.220.tgz", + "integrity": "sha512-WkROPwWskqhKR9XgnmseHQ6rLi9zM9qt57IWoToIjL/eXOqDWipp7JXZ1L5ud+LrA42dunHPZfBwD/vXZ+A7LA==", "cpu": [ "arm64" ], @@ -38219,9 +38219,9 @@ ] }, "packages/server/node_modules/@anthropic-ai/claude-agent-sdk/node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64-musl": { - "version": "0.3.214", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.214.tgz", - "integrity": "sha512-i06wQRsmevE7spY1ryYfs+NP+xdZ1FwAyTjDaF0k/xG+cgtzZYghTFUemdYF3GVwgWgpcava9GiCFDT3DoHI6w==", + "version": "0.3.220", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.220.tgz", + "integrity": "sha512-OHoZOZ8Cf2TBr6oXIXPwyvUxj9jrq2w8E4poA8dMpacXszcPSPiCQCMuuOh4aWJzfeJE1+TtWxhKMVb2csXyZQ==", "cpu": [ "arm64" ], @@ -38232,9 +38232,9 @@ ] }, "packages/server/node_modules/@anthropic-ai/claude-agent-sdk/node_modules/@anthropic-ai/claude-agent-sdk-linux-x64": { - "version": "0.3.214", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.214.tgz", - "integrity": "sha512-vqadSceJkBKHaTUszxI35uTuECGWtsHWK/mOwIJ4b9DUtYYySz6EJEk4gHg4ccutisJ/oRVCpFXHIKFb+osrKw==", + "version": "0.3.220", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.220.tgz", + "integrity": "sha512-tkTJFnpR9VifvWX2fmkCAPkT6+8Wk/gVu8B5jsVekKZPiZoWRHmMXO30BnZn+f0TZhgYP+82PSX3S8crH1kn+w==", "cpu": [ "x64" ], @@ -38245,9 +38245,9 @@ ] }, "packages/server/node_modules/@anthropic-ai/claude-agent-sdk/node_modules/@anthropic-ai/claude-agent-sdk-linux-x64-musl": { - "version": "0.3.214", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.214.tgz", - "integrity": "sha512-948RstHDhs0E69h+2dKYWIAL1kcwEme4zvtgNUwP8XpKXzWOhrTKmd6MAokHn25zwfuSx5d+HE0XHfFH6R4hLg==", + "version": "0.3.220", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.220.tgz", + "integrity": "sha512-K+FWj+LcGhC1Z7wqeWoLxm1iemcba5xKpLLFVwYm4V6HyMx3ruYd/2r2TiQtjT+JWeNFWIys0ScHiItR6vWAiA==", "cpu": [ "x64" ], @@ -38258,9 +38258,9 @@ ] }, "packages/server/node_modules/@anthropic-ai/claude-agent-sdk/node_modules/@anthropic-ai/claude-agent-sdk-win32-arm64": { - "version": "0.3.214", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.214.tgz", - "integrity": "sha512-CEKlPFCPv+ee79utQMEDSsgeo2f27ulhNHpjrKIt1jXz5G04J7lAWN/QwvPDv9XaLBkWpyfqZWiPXlRcwuaO/w==", + "version": "0.3.220", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.220.tgz", + "integrity": "sha512-rIwgq0UwQExWl6KrHUyC4w5KwpL9l6nd95aUTx6RitexaAuEw//xtfTVLnuE4hDDQZFkzEwpdKc3nxDWoGcUbA==", "cpu": [ "arm64" ], @@ -38271,9 +38271,9 @@ ] }, "packages/server/node_modules/@anthropic-ai/claude-agent-sdk/node_modules/@anthropic-ai/claude-agent-sdk-win32-x64": { - "version": "0.3.214", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.214.tgz", - "integrity": "sha512-cJMJfFoR9IWBZWnTtt9PnEm89hGOsZjoDNjOCEOF3+x+g/ANIXAjMJTcaQYv2HKh6MylWZ0AkxBASI+twkukaQ==", + "version": "0.3.220", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.220.tgz", + "integrity": "sha512-MuOuXhbr66HlGaWXD2f3w0k2PsvmnbkwcUZ0dAe2poFLdl72GC2dapwwOBefxm9QmoNqk9+jmv/dSKGOVWyvLw==", "cpu": [ "x64" ], @@ -38649,7 +38649,7 @@ }, "packages/website": { "name": "@getpaseo/website", - "version": "0.2.1", + "version": "0.2.2", "dependencies": { "@cloudflare/vite-plugin": "^1.29.1", "@cloudflare/workers-types": "^4.20260317.1", diff --git a/package.json b/package.json index 812039714..2dfe2723d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "paseo", - "version": "0.2.1", + "version": "0.2.2", "private": true, "description": "Paseo: voice-controlled development environment for local AI coding agents", "keywords": [ diff --git a/packages/app/package.json b/packages/app/package.json index 2df5e0a57..ffa7d475d 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@getpaseo/app", - "version": "0.2.1", + "version": "0.2.2", "private": true, "main": "index.ts", "scripts": { diff --git a/packages/cli/package.json b/packages/cli/package.json index 96ba2e6cd..c3ab5b8db 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@getpaseo/cli", - "version": "0.2.1", + "version": "0.2.2", "description": "Paseo CLI - control your AI coding agents from the command line", "bin": { "paseo": "bin/paseo" @@ -28,9 +28,9 @@ }, "dependencies": { "@clack/prompts": "^1.0.0", - "@getpaseo/client": "0.2.1", - "@getpaseo/protocol": "0.2.1", - "@getpaseo/server": "0.2.1", + "@getpaseo/client": "0.2.2", + "@getpaseo/protocol": "0.2.2", + "@getpaseo/server": "0.2.2", "chalk": "^5.3.0", "commander": "^12.0.0", "mime-types": "^2.1.35", diff --git a/packages/cli/tests/15-provider.test.ts b/packages/cli/tests/15-provider.test.ts index 8cb3b4730..ec07fe862 100644 --- a/packages/cli/tests/15-provider.test.ts +++ b/packages/cli/tests/15-provider.test.ts @@ -107,6 +107,38 @@ const EXPECTED_CLAUDE_MODELS = [ }, ] as const; +const EXPECTED_CLAUDE_CONTEXT_MODELS = [ + { + id: "claude-opus-5[1m]", + model: "Opus 5 1M", + descriptionFragment: "1M context window", + }, + { + id: "claude-opus-5", + model: "Opus 5", + descriptionFragment: "200K context window", + }, + { + id: "claude-fable-5[1m]", + model: "Fable 5 1M", + descriptionFragment: "1M context window", + }, + { + id: "claude-sonnet-5[1m]", + model: "Sonnet 5 1M", + descriptionFragment: "1M context window", + }, +] as const; + +const EXPECTED_CLAUDE_CATALOG_MODELS = [ + ...new Map( + [...EXPECTED_CLAUDE_MODELS, ...EXPECTED_CLAUDE_CONTEXT_MODELS].map((model) => [ + model.id, + model, + ]), + ).values(), +]; + let claudeModelIdsFromJson: string[] = []; let claudeModelsFromJson: ProviderModel[] = []; @@ -139,18 +171,18 @@ async function runProviderModelsJson(provider: string): Promise function assertClaudeModels(data: ProviderModel[]): void { assert.strictEqual( data.length, - EXPECTED_CLAUDE_MODELS.length, + EXPECTED_CLAUDE_CATALOG_MODELS.length, "claude output should match the current catalog size", ); const byId = new Map(data.map((model) => [model.id, model])); const ids = [...byId.keys()].sort(); - const expectedIds = EXPECTED_CLAUDE_MODELS.map((model) => model.id).sort(); + const expectedIds = EXPECTED_CLAUDE_CATALOG_MODELS.map((model) => model.id).sort(); assert.strictEqual(byId.size, data.length, "claude model IDs should be unique"); assert.deepStrictEqual(ids, expectedIds, "claude IDs should match the current catalog"); - for (const expectedModel of EXPECTED_CLAUDE_MODELS) { + for (const expectedModel of EXPECTED_CLAUDE_CATALOG_MODELS) { const actualModel = byId.get(expectedModel.id); assert(actualModel, `claude output should include ${expectedModel.id}`); assert.strictEqual( @@ -388,7 +420,7 @@ try { const lines = result.stdout.trim().split("\n").filter(Boolean); assert.strictEqual( lines.length, - EXPECTED_CLAUDE_MODELS.length, + EXPECTED_CLAUDE_CATALOG_MODELS.length, "should have one line per Claude catalog model", ); assert.deepStrictEqual( @@ -398,7 +430,7 @@ try { ); assert.deepStrictEqual( [...lines].sort(), - EXPECTED_CLAUDE_MODELS.map((model) => model.id).sort(), + EXPECTED_CLAUDE_CATALOG_MODELS.map((model) => model.id).sort(), "--quiet should print the current Claude catalog IDs", ); assert( diff --git a/packages/client/package.json b/packages/client/package.json index 672adae3a..4988a509b 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@getpaseo/client", - "version": "0.2.1", + "version": "0.2.2", "description": "Paseo client SDK package", "files": [ "dist", @@ -35,8 +35,8 @@ "test": "vitest run" }, "dependencies": { - "@getpaseo/protocol": "0.2.1", - "@getpaseo/relay": "0.2.1", + "@getpaseo/protocol": "0.2.2", + "@getpaseo/relay": "0.2.2", "zod": "^4.4.3" }, "devDependencies": { diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 600bb8ab3..8fd412ef8 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@getpaseo/desktop", - "version": "0.2.1", + "version": "0.2.2", "private": true, "description": "Paseo desktop app (Electron wrapper)", "homepage": "https://paseo.sh", diff --git a/packages/expo-two-way-audio/package.json b/packages/expo-two-way-audio/package.json index 12a461c73..2974d5db0 100644 --- a/packages/expo-two-way-audio/package.json +++ b/packages/expo-two-way-audio/package.json @@ -1,6 +1,6 @@ { "name": "@getpaseo/expo-two-way-audio", - "version": "0.2.1", + "version": "0.2.2", "description": "Native module for two way audio streaming", "keywords": [ "ExpoTwoWayAudio", diff --git a/packages/highlight/package.json b/packages/highlight/package.json index a899c1476..cf8eab550 100644 --- a/packages/highlight/package.json +++ b/packages/highlight/package.json @@ -1,6 +1,6 @@ { "name": "@getpaseo/highlight", - "version": "0.2.1", + "version": "0.2.2", "files": [ "dist", "!dist/**/*.map" diff --git a/packages/protocol/package.json b/packages/protocol/package.json index e60ef71c0..ba5e23232 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@getpaseo/protocol", - "version": "0.2.1", + "version": "0.2.2", "description": "Paseo shared protocol schemas and wire types", "files": [ "dist", diff --git a/packages/relay/package.json b/packages/relay/package.json index f2cac7da2..a156443d5 100644 --- a/packages/relay/package.json +++ b/packages/relay/package.json @@ -1,6 +1,6 @@ { "name": "@getpaseo/relay", - "version": "0.2.1", + "version": "0.2.2", "description": "Paseo relay for bridging daemon and client connections", "files": [ "dist", diff --git a/packages/server/package.json b/packages/server/package.json index 61d3e3ea8..9e5f9186c 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@getpaseo/server", - "version": "0.2.1", + "version": "0.2.2", "description": "Paseo backend server", "files": [ "dist/server", @@ -65,12 +65,12 @@ }, "dependencies": { "@agentclientprotocol/sdk": "^0.17.1", - "@anthropic-ai/claude-agent-sdk": "^0.3.214", + "@anthropic-ai/claude-agent-sdk": "^0.3.220", "@anthropic-ai/sdk": "^0.104.2", - "@getpaseo/client": "0.2.1", - "@getpaseo/highlight": "0.2.1", - "@getpaseo/protocol": "0.2.1", - "@getpaseo/relay": "0.2.1", + "@getpaseo/client": "0.2.2", + "@getpaseo/highlight": "0.2.2", + "@getpaseo/protocol": "0.2.2", + "@getpaseo/relay": "0.2.2", "@isaacs/ttlcache": "^2.1.4", "@modelcontextprotocol/sdk": "^1.20.1", "@opencode-ai/sdk": "1.14.46", diff --git a/packages/server/src/server/agent/providers/claude/agent.test.ts b/packages/server/src/server/agent/providers/claude/agent.test.ts index 60c6cc16e..e773cd735 100644 --- a/packages/server/src/server/agent/providers/claude/agent.test.ts +++ b/packages/server/src/server/agent/providers/claude/agent.test.ts @@ -11,6 +11,7 @@ import { convertClaudeHistoryEntry, normalizeClaudeAskUserQuestionRequestInput, normalizeClaudeAskUserQuestionUpdatedInput, + resolveClaudeCodeVersion, toClaudeSdkMcpConfig, } from "./agent.js"; import { claudeProjectDirSync } from "./project-dir.js"; @@ -405,6 +406,7 @@ describe("ClaudeAgentClient.fetchCatalog", () => { const client = new ClaudeAgentClient({ logger, resolveBinary: async () => "/test/claude/bin", + resolveVersion: async () => "2.1.219", configDir: emptyConfigDir, }); const { models } = await client.fetchCatalog({ @@ -414,11 +416,14 @@ describe("ClaudeAgentClient.fetchCatalog", () => { }); expect(models.map((m) => m.id)).toEqual([ + "claude-opus-5[1m]", "claude-opus-5", + "claude-fable-5[1m]", "claude-fable-5", "claude-opus-4-8[1m]", "claude-opus-4-8", "claude-sonnet-5", + "claude-sonnet-5[1m]", "claude-opus-4-7[1m]", "claude-opus-4-7", "claude-opus-4-6[1m]", @@ -434,7 +439,30 @@ describe("ClaudeAgentClient.fetchCatalog", () => { } const defaultModel = models.find((m) => m.isDefault); - expect(defaultModel?.id).toBe("claude-opus-5"); + expect(defaultModel?.id).toBe("claude-opus-5[1m]"); + } finally { + await fs.rm(emptyConfigDir, { recursive: true, force: true }); + } + }); + + test("preserves the catalog when Claude Code version detection fails", async () => { + const emptyConfigDir = await fs.mkdtemp(path.join(os.tmpdir(), "paseo-claude-models-empty-")); + try { + const client = new ClaudeAgentClient({ + logger, + resolveVersion: async () => { + throw new Error("unrecognized version output"); + }, + configDir: emptyConfigDir, + }); + const { models } = await client.fetchCatalog({ + scope: "workspace", + cwd: "/tmp/claude-models", + force: false, + }); + + expect(models.find((model) => model.isDefault)?.id).toBe("claude-opus-5[1m]"); + expect(models.map((model) => model.id)).toContain("claude-fable-5[1m]"); } finally { await fs.rm(emptyConfigDir, { recursive: true, force: true }); } @@ -446,6 +474,7 @@ describe("ClaudeAgentClient.fetchCatalog", () => { const client = new ClaudeAgentClient({ logger, resolveBinary: async () => "/test/claude/bin", + resolveVersion: async () => "2.1.219", configDir: emptyConfigDir, }); const { models } = await client.fetchCatalog({ @@ -475,6 +504,10 @@ describe("ClaudeAgentClient.fetchCatalog", () => { describe("ClaudeAgentClient binary resolution", () => { const logger = createTestLogger(); + test("resolves the installed Claude Code version", async () => { + await expect(resolveClaudeCodeVersion()).resolves.toMatch(/^\d+\.\d+\.\d+$/); + }); + test("loads user, project, and local Claude settings", async () => { const queryReturn = vi.fn(); queryReturn.mockResolvedValue(undefined); @@ -2083,10 +2116,10 @@ describe("ClaudeAgentSession context window usage", () => { } }); - test("native 1M Claude models seed active context window usage from the catalog", async () => { + test("selected 1M Claude models seed active context window usage from the catalog", async () => { const session = await createSessionForTurns( [[createInitMessage(), createMessageStartEvent(), createSuccessResult()]], - { model: "claude-sonnet-5" }, + { model: "claude-sonnet-5[1m]" }, ); try { diff --git a/packages/server/src/server/agent/providers/claude/agent.ts b/packages/server/src/server/agent/providers/claude/agent.ts index 9b0429fff..463540825 100644 --- a/packages/server/src/server/agent/providers/claude/agent.ts +++ b/packages/server/src/server/agent/providers/claude/agent.ts @@ -37,6 +37,7 @@ import { import { CLAUDE_DISABLED_THINKING_OPTION_ID, CLAUDE_ULTRACODE_THINKING_OPTION_ID, + parseClaudeCodeVersion, resolveClaudeDisabledThinkingForModel, } from "./model-manifest.js"; import { parsePartialJsonObject } from "./partial-json.js"; @@ -363,6 +364,7 @@ interface ClaudeAgentClientOptions { runtimeSettings?: ProviderRuntimeSettings; queryFactory?: ClaudeQueryFactory; resolveBinary?: () => Promise; + resolveVersion?: () => Promise; configDir?: string; } @@ -1439,6 +1441,7 @@ export class ClaudeAgentClient implements AgentClient { private readonly runtimeSettings?: ProviderRuntimeSettings; private readonly queryFactory?: ClaudeQueryFactory; private readonly resolveBinary: () => Promise; + private readonly resolveVersion: () => Promise; private readonly configDir?: string; constructor(options: ClaudeAgentClientOptions) { @@ -1447,6 +1450,8 @@ export class ClaudeAgentClient implements AgentClient { this.runtimeSettings = options.runtimeSettings; this.queryFactory = options.queryFactory; this.resolveBinary = options.resolveBinary ?? (() => resolveClaudeBinary(this.runtimeSettings)); + this.resolveVersion = + options.resolveVersion ?? (() => resolveClaudeCodeVersion(this.runtimeSettings)); this.configDir = options.configDir; } @@ -1498,7 +1503,17 @@ export class ClaudeAgentClient implements AgentClient { async fetchCatalog(_options: FetchCatalogOptions): Promise { // Claude exposes a global catalog here; cwd/force are intentionally irrelevant. - const models = await getClaudeModelsWithSettings(this.logger, this.configDir); + let claudeCodeVersion: string | undefined; + try { + claudeCodeVersion = await this.resolveVersion(); + } catch (error) { + this.logger.warn({ err: error }, "Failed to resolve Claude Code version for model catalog"); + } + const models = await getClaudeModelsWithSettings( + this.logger, + this.configDir, + claudeCodeVersion, + ); const modes = detectIneligibleAutoModeTransport( createProviderEnv({ baseEnv: process.env, runtimeSettings: this.runtimeSettings }), ) @@ -1620,6 +1635,29 @@ async function resolveClaudeBinary(runtimeSettings?: ProviderRuntimeSettings): P ); } +export async function resolveClaudeCodeVersion( + runtimeSettings?: ProviderRuntimeSettings, +): Promise { + const launch = await resolveProviderLaunch({ + commandConfig: runtimeSettings?.command, + defaultBinary: "claude", + }); + const availability = await checkProviderLaunchAvailable(launch); + if (!availability.available) { + throw new Error("Claude binary not found while resolving Claude Code version"); + } + const executable = availability.resolvedPath ?? launch.command; + const { stdout, stderr } = await execCommand(executable, [...launch.args, "--version"], { + ...createProviderEnvSpec({ runtimeSettings }), + timeout: 5_000, + }); + const version = parseClaudeCodeVersion(`${stdout}\n${stderr}`); + if (!version) { + throw new Error("Unable to parse Claude Code version from --version output"); + } + return version.join("."); +} + async function resolveClaudeAuth( launch: ResolvedProviderLaunch, availability: { resolvedPath: string | null }, diff --git a/packages/server/src/server/agent/providers/claude/model-manifest.ts b/packages/server/src/server/agent/providers/claude/model-manifest.ts index b569f0da1..ec9efdb1f 100644 --- a/packages/server/src/server/agent/providers/claude/model-manifest.ts +++ b/packages/server/src/server/agent/providers/claude/model-manifest.ts @@ -6,7 +6,8 @@ interface ClaudeModelManifestEntry { id: string; label: string; description: string; - isDefault?: boolean; + defaultPriority?: number; + minimumClaudeCodeVersion?: string; contextWindowMaxTokens?: number; effortLevels?: readonly ClaudeEffortLevel[]; supportsThinkingDisabled?: boolean; @@ -31,19 +32,38 @@ export const CLAUDE_ULTRACODE_THINKING_OPTION_ID = "ultracode"; export const CLAUDE_MODEL_MANIFEST = [ { - id: "claude-opus-5", - label: "Opus 5", - description: "Opus 5 · Latest release", - isDefault: true, + id: "claude-opus-5[1m]", + label: "Opus 5 1M", + description: "Opus 5 with 1M context window", + defaultPriority: 2, + minimumClaudeCodeVersion: "2.1.219", contextWindowMaxTokens: 1_000_000, effortLevels: CLAUDE_EFFORT_LEVELS.xhigh, supportsThinkingDisabled: true, }, + { + id: "claude-opus-5", + label: "Opus 5", + description: "Opus 5 · 200K context window", + minimumClaudeCodeVersion: "2.1.219", + contextWindowMaxTokens: 200_000, + effortLevels: CLAUDE_EFFORT_LEVELS.xhigh, + supportsThinkingDisabled: true, + }, + { + id: "claude-fable-5[1m]", + label: "Fable 5 1M", + description: "Fable 5 with 1M context window", + minimumClaudeCodeVersion: "2.1.169", + contextWindowMaxTokens: 1_000_000, + effortLevels: CLAUDE_EFFORT_LEVELS.xhigh, + }, { id: "claude-fable-5", label: "Fable 5", description: "Fable 5 · Most powerful model", - contextWindowMaxTokens: 1_000_000, + minimumClaudeCodeVersion: "2.1.169", + contextWindowMaxTokens: 200_000, effortLevels: CLAUDE_EFFORT_LEVELS.xhigh, }, { @@ -59,6 +79,7 @@ export const CLAUDE_MODEL_MANIFEST = [ id: "claude-opus-4-8", label: "Opus 4.8", description: "Opus 4.8 · Previous release", + defaultPriority: 1, contextWindowMaxTokens: 200_000, effortLevels: CLAUDE_EFFORT_LEVELS.xhigh, supportsThinkingDisabled: true, @@ -68,6 +89,14 @@ export const CLAUDE_MODEL_MANIFEST = [ id: "claude-sonnet-5", label: "Sonnet 5", description: "Sonnet 5 · Best for everyday tasks", + contextWindowMaxTokens: 200_000, + effortLevels: CLAUDE_EFFORT_LEVELS.xhigh, + supportsThinkingDisabled: true, + }, + { + id: "claude-sonnet-5[1m]", + label: "Sonnet 5 1M", + description: "Sonnet 5 with 1M context window", contextWindowMaxTokens: 1_000_000, effortLevels: CLAUDE_EFFORT_LEVELS.xhigh, supportsThinkingDisabled: true, @@ -155,31 +184,73 @@ function buildThinkingOptions( return options; } -export function getClaudeManifestModels(): AgentModelDefinition[] { - return CLAUDE_MODEL_MANIFEST.map((model) => { +export function getClaudeManifestModels(claudeCodeVersion?: string): AgentModelDefinition[] { + const availableModels: readonly ClaudeModelManifestEntry[] = CLAUDE_MODEL_MANIFEST.filter( + (model) => isModelAvailableInClaudeCode(model, claudeCodeVersion), + ); + const defaultModel = availableModels.reduce( + (selected, candidate) => + (candidate.defaultPriority ?? 0) > (selected?.defaultPriority ?? 0) ? candidate : selected, + undefined, + ); + + return availableModels.map((model) => { const thinkingOptions = buildThinkingOptions( - "effortLevels" in model ? model.effortLevels : undefined, - "supportsThinkingDisabled" in model && model.supportsThinkingDisabled, + model.effortLevels, + model.supportsThinkingDisabled === true, ); - return { + const definition: AgentModelDefinition = { provider: "claude", id: model.id, label: model.label, description: model.description, - ...("isDefault" in model && model.isDefault ? { isDefault: true } : {}), - ...(model.contextWindowMaxTokens !== undefined - ? { contextWindowMaxTokens: model.contextWindowMaxTokens } - : {}), - ...(thinkingOptions - ? { - thinkingOptions, - defaultThinkingOptionId: "effortLevels" in model ? model.effortLevels?.[0] : undefined, - } - : {}), }; + if (model === defaultModel) { + definition.isDefault = true; + } + if (model.contextWindowMaxTokens !== undefined) { + definition.contextWindowMaxTokens = model.contextWindowMaxTokens; + } + if (thinkingOptions) { + definition.thinkingOptions = thinkingOptions; + definition.defaultThinkingOptionId = model.effortLevels?.[0]; + } + return definition; }); } +function isModelAvailableInClaudeCode( + model: ClaudeModelManifestEntry, + claudeCodeVersion: string | undefined, +): boolean { + if (!model.minimumClaudeCodeVersion || claudeCodeVersion === undefined) { + return true; + } + return compareVersions(claudeCodeVersion, model.minimumClaudeCodeVersion) >= 0; +} + +function compareVersions(left: string, right: string): number { + const leftParts = parseClaudeCodeVersion(left); + const rightParts = parseClaudeCodeVersion(right); + if (!leftParts || !rightParts) { + return -1; + } + for (let index = 0; index < leftParts.length; index += 1) { + const difference = leftParts[index] - rightParts[index]; + if (difference !== 0) { + return difference; + } + } + return 0; +} + +export function parseClaudeCodeVersion(value: string): [number, number, number] | null { + const match = + value.match(/\b(\d+)\.(\d+)\.(\d+)\s+\(Claude Code\)/i) ?? + value.match(/\b(\d+)\.(\d+)\.(\d+)\b/); + return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null; +} + export interface ClaudeDisabledThinkingResolution { supported: boolean; fallbackThinkingOptionId: string | undefined; @@ -236,7 +307,7 @@ export function normalizeClaudeManifestModelId(value: string | null | undefined) } const singleSegmentMatch = trimmed.match( - /^(?:claude[-_ ])?(fable|opus|sonnet|haiku)[-_ ]+(\d+)(\[1m\])?(?:[-_ ]+\d{8})?$/i, + /^(?:claude[-_ ])?(fable|opus|sonnet|haiku)[-_ ]+(\d+)(?:\[1m\])?(?:[-_ ]+\d{8})?(?:\[1m\])?$/i, ); if (singleSegmentMatch) { return normalizeSingleSegmentClaudeModelId( @@ -247,7 +318,7 @@ export function normalizeClaudeManifestModelId(value: string | null | undefined) } const runtimeMatch = trimmed.match( - /^(?:claude[-_ ])?(opus|sonnet|haiku)[-_ ]+(\d+)[-.](\d+)(\[1m\])?(?:[-_ ]+\d{8})?$/i, + /^(?:claude[-_ ])?(opus|sonnet|haiku)[-_ ]+(\d+)[-.](\d+)(?:\[1m\])?(?:[-_ ]+\d{8})?(?:\[1m\])?$/i, ); if (!runtimeMatch) { return null; @@ -257,7 +328,7 @@ export function normalizeClaudeManifestModelId(value: string | null | undefined) runtimeMatch[1], runtimeMatch[2], runtimeMatch[3], - Boolean(runtimeMatch[4]), + trimmed.toLowerCase().includes("[1m]"), ); } @@ -284,7 +355,7 @@ export function normalizeClaudeRuntimeModelId(value: string | null | undefined): const normalizedModelId = normalizeSingleSegmentClaudeModelId( singleSegmentMatch[1], singleSegmentMatch[2], - Boolean(singleSegmentMatch[3]), + trimmed.toLowerCase().includes("[1m]"), ); if (normalizedModelId) { return normalizedModelId; @@ -302,7 +373,7 @@ export function normalizeClaudeRuntimeModelId(value: string | null | undefined): runtimeMatch[1], runtimeMatch[2], runtimeMatch[3], - Boolean(runtimeMatch[4]), + trimmed.toLowerCase().includes("[1m]"), ); } diff --git a/packages/server/src/server/agent/providers/claude/models.test.ts b/packages/server/src/server/agent/providers/claude/models.test.ts index 5b069dce1..c3e62b1dc 100644 --- a/packages/server/src/server/agent/providers/claude/models.test.ts +++ b/packages/server/src/server/agent/providers/claude/models.test.ts @@ -10,6 +10,7 @@ import { CLAUDE_ULTRACODE_THINKING_OPTION_ID, claudeManifestModelSupportsFastMode, normalizeClaudeManifestModelId, + parseClaudeCodeVersion, resolveClaudeDisabledThinkingForModel, } from "./model-manifest.js"; import { findClaudeModel, getClaudeModels, normalizeClaudeRuntimeModelId } from "./models.js"; @@ -38,15 +39,25 @@ async function createClaudeConfigDirWithRawSettings(settings: string): Promise claudeCodeVersion, + }); +} + describe("getClaudeModels", () => { it("returns all claude models", () => { const models = getClaudeModels(); expect(models.map((m) => m.id)).toEqual([ + "claude-opus-5[1m]", "claude-opus-5", + "claude-fable-5[1m]", "claude-fable-5", "claude-opus-4-8[1m]", "claude-opus-4-8", "claude-sonnet-5", + "claude-sonnet-5[1m]", "claude-opus-4-7[1m]", "claude-opus-4-7", "claude-opus-4-6[1m]", @@ -61,7 +72,7 @@ describe("getClaudeModels", () => { const models = getClaudeModels(); const defaults = models.filter((m) => m.isDefault); expect(defaults).toHaveLength(1); - expect(defaults[0].id).toBe("claude-opus-5"); + expect(defaults[0].id).toBe("claude-opus-5[1m]"); }); it("defines context window sizes in the catalog", () => { @@ -71,11 +82,14 @@ describe("getClaudeModels", () => { expect(contextWindows).toEqual( new Map([ - ["claude-opus-5", 1_000_000], - ["claude-fable-5", 1_000_000], + ["claude-opus-5[1m]", 1_000_000], + ["claude-opus-5", 200_000], + ["claude-fable-5[1m]", 1_000_000], + ["claude-fable-5", 200_000], ["claude-opus-4-8[1m]", 1_000_000], ["claude-opus-4-8", 200_000], - ["claude-sonnet-5", 1_000_000], + ["claude-sonnet-5", 200_000], + ["claude-sonnet-5[1m]", 1_000_000], ["claude-opus-4-7[1m]", 1_000_000], ["claude-opus-4-7", 200_000], ["claude-opus-4-6[1m]", 1_000_000], @@ -87,6 +101,20 @@ describe("getClaudeModels", () => { ); }); + it("filters models by their minimum Claude Code version", () => { + const oldVersionModels = getClaudeModels("2.1.218"); + expect(oldVersionModels.map((model) => model.id)).not.toContain("claude-opus-5[1m]"); + expect(oldVersionModels.map((model) => model.id)).not.toContain("claude-opus-5"); + expect(oldVersionModels.find((model) => model.isDefault)?.id).toBe("claude-opus-4-8"); + expect(getClaudeModels("2.1.219").map((model) => model.id)).toContain("claude-opus-5[1m]"); + expect(getClaudeModels("2.1.219").map((model) => model.id)).toContain("claude-opus-5"); + + expect(getClaudeModels("2.1.168").map((model) => model.id)).not.toContain("claude-fable-5[1m]"); + expect(getClaudeModels("2.1.168").map((model) => model.id)).not.toContain("claude-fable-5"); + expect(getClaudeModels("2.1.169").map((model) => model.id)).toContain("claude-fable-5[1m]"); + expect(getClaudeModels("2.1.169").map((model) => model.id)).toContain("claude-fable-5"); + }); + it("derives thinking options from model effort capabilities", () => { const models = new Map(getClaudeModels().map((model) => [model.id, model])); @@ -115,6 +143,9 @@ describe("getClaudeModels", () => { ?.label, ).toBe("Ultra Code"); expect(models.get("claude-sonnet-5")?.defaultThinkingOptionId).toBe("low"); + expect(models.get("claude-sonnet-5[1m]")?.thinkingOptions).toEqual( + models.get("claude-sonnet-5")?.thinkingOptions, + ); expect(models.get("claude-opus-4-7")?.thinkingOptions?.map((option) => option.id)).toEqual([ CLAUDE_DISABLED_THINKING_OPTION_ID, @@ -135,6 +166,9 @@ describe("getClaudeModels", () => { expect(models.get("claude-fable-5")?.thinkingOptions?.map((option) => option.id)).not.toContain( CLAUDE_DISABLED_THINKING_OPTION_ID, ); + expect(models.get("claude-fable-5[1m]")?.thinkingOptions).toEqual( + models.get("claude-fable-5")?.thinkingOptions, + ); expect(models.get("claude-haiku-4-5")?.thinkingOptions).toBeUndefined(); }); @@ -142,6 +176,7 @@ describe("getClaudeModels", () => { ["claude-opus-5", true, "low"], ["claude-opus-5-20260724", true, "low"], ["claude-sonnet-5", true, "low"], + ["claude-sonnet-5[1m]", true, "low"], ["claude-sonnet-5-20260101", true, "low"], ["claude-fable-5", false, "low"], ["claude-haiku-4-5", false, undefined], @@ -175,7 +210,7 @@ describe("ClaudeAgentClient.fetchCatalog", () => { }, }); vi.stubEnv("CLAUDE_CONFIG_DIR", configDir); - const client = new ClaudeAgentClient({ logger: createTestLogger() }); + const client = createCatalogClient(); const { models } = await client.fetchCatalog({ scope: "workspace", @@ -228,7 +263,7 @@ describe("ClaudeAgentClient.fetchCatalog", () => { const configDir = await fs.mkdtemp(path.join(os.tmpdir(), "paseo-claude-models-")); createdClaudeConfigDirs.push(configDir); vi.stubEnv("CLAUDE_CONFIG_DIR", configDir); - const client = new ClaudeAgentClient({ logger: createTestLogger() }); + const client = createCatalogClient(); const { models } = await client.fetchCatalog({ scope: "workspace", @@ -242,7 +277,7 @@ describe("ClaudeAgentClient.fetchCatalog", () => { it("falls back to hardcoded models when settings.json is malformed", async () => { const configDir = await createClaudeConfigDirWithRawSettings("{ nope"); vi.stubEnv("CLAUDE_CONFIG_DIR", configDir); - const client = new ClaudeAgentClient({ logger: createTestLogger() }); + const client = createCatalogClient(); const { models } = await client.fetchCatalog({ scope: "workspace", @@ -262,7 +297,7 @@ describe("ClaudeAgentClient.fetchCatalog", () => { }, }); vi.stubEnv("CLAUDE_CONFIG_DIR", configDir); - const client = new ClaudeAgentClient({ logger: createTestLogger() }); + const client = createCatalogClient(); const { models } = await client.fetchCatalog({ scope: "workspace", @@ -282,7 +317,7 @@ describe("ClaudeAgentClient.fetchCatalog", () => { }, }); vi.stubEnv("CLAUDE_CONFIG_DIR", configDir); - const client = new ClaudeAgentClient({ logger: createTestLogger() }); + const client = createCatalogClient(); const { models } = await client.fetchCatalog({ scope: "workspace", @@ -295,13 +330,29 @@ describe("ClaudeAgentClient.fetchCatalog", () => { "glm-5.1", ]); }); + + it("omits models that require a newer Claude Code version", async () => { + const client = createCatalogClient("2.1.218"); + + const { models } = await client.fetchCatalog({ + scope: "workspace", + cwd: os.tmpdir(), + force: true, + }); + + expect(models.map((model) => model.id)).not.toContain("claude-opus-5[1m]"); + expect(models.map((model) => model.id)).not.toContain("claude-opus-5"); + }); }); describe("normalizeClaudeRuntimeModelId", () => { it("returns exact match for known model IDs", () => { + expect(normalizeClaudeRuntimeModelId("claude-opus-5[1m]")).toBe("claude-opus-5[1m]"); expect(normalizeClaudeRuntimeModelId("claude-opus-5")).toBe("claude-opus-5"); expect(normalizeClaudeRuntimeModelId("claude-fable-5")).toBe("claude-fable-5"); + expect(normalizeClaudeRuntimeModelId("claude-fable-5[1m]")).toBe("claude-fable-5[1m]"); expect(normalizeClaudeRuntimeModelId("claude-sonnet-5")).toBe("claude-sonnet-5"); + expect(normalizeClaudeRuntimeModelId("claude-sonnet-5[1m]")).toBe("claude-sonnet-5[1m]"); expect(normalizeClaudeRuntimeModelId("claude-opus-4-6")).toBe("claude-opus-4-6"); expect(normalizeClaudeRuntimeModelId("claude-opus-4-6[1m]")).toBe("claude-opus-4-6[1m]"); expect(normalizeClaudeRuntimeModelId("claude-sonnet-4-6")).toBe("claude-sonnet-4-6"); @@ -315,9 +366,17 @@ describe("normalizeClaudeRuntimeModelId", () => { expect(normalizeClaudeRuntimeModelId("claude-opus-4-6-20260101")).toBe("claude-opus-4-6"); expect(normalizeClaudeRuntimeModelId("claude-sonnet-4-6-20260101")).toBe("claude-sonnet-4-6"); expect(normalizeClaudeRuntimeModelId("claude-haiku-4-5-20251001")).toBe("claude-haiku-4-5"); + expect(normalizeClaudeRuntimeModelId("claude-opus-5-20260724[1m]")).toBe("claude-opus-5[1m]"); + expect(normalizeClaudeRuntimeModelId("claude-fable-5-20260301[1m]")).toBe("claude-fable-5[1m]"); + expect(normalizeClaudeRuntimeModelId("claude-sonnet-5-20260101[1m]")).toBe( + "claude-sonnet-5[1m]", + ); }); it("preserves [1m] suffix from runtime model strings", () => { + expect(normalizeClaudeRuntimeModelId("claude-opus-5[1m]")).toBe("claude-opus-5[1m]"); + expect(normalizeClaudeRuntimeModelId("claude-fable-5[1m]")).toBe("claude-fable-5[1m]"); + expect(normalizeClaudeRuntimeModelId("claude-sonnet-5[1m]")).toBe("claude-sonnet-5[1m]"); expect(normalizeClaudeRuntimeModelId("claude-opus-4-6[1m]")).toBe("claude-opus-4-6[1m]"); }); @@ -346,6 +405,12 @@ describe("normalizeClaudeRuntimeModelId", () => { }); }); +describe("parseClaudeCodeVersion", () => { + it("prefers the Claude Code version over a wrapper banner", () => { + expect(parseClaudeCodeVersion("wrapper 1.0.0\n2.1.219 (Claude Code)")).toEqual([2, 1, 219]); + }); +}); + describe("findClaudeModel", () => { it("resolves runtime model IDs to catalog entries", () => { expect(findClaudeModel("claude-sonnet-5-20260101")?.id).toBe("claude-sonnet-5"); diff --git a/packages/server/src/server/agent/providers/claude/models.ts b/packages/server/src/server/agent/providers/claude/models.ts index 0e82cb018..48bf90088 100644 --- a/packages/server/src/server/agent/providers/claude/models.ts +++ b/packages/server/src/server/agent/providers/claude/models.ts @@ -17,8 +17,8 @@ const CLAUDE_SETTINGS_MODEL_ENV_KEYS = [ "ANTHROPIC_DEFAULT_HAIKU_MODEL", ] as const; -export function getClaudeModels(): AgentModelDefinition[] { - return getClaudeManifestModels(); +export function getClaudeModels(claudeCodeVersion?: string): AgentModelDefinition[] { + return getClaudeManifestModels(claudeCodeVersion); } export function findClaudeModel( @@ -34,8 +34,9 @@ export function findClaudeModel( export async function getClaudeModelsWithSettings( logger: Logger, configDir?: string, + claudeCodeVersion?: string, ): Promise { - const hardcodedModels = getClaudeModels(); + const hardcodedModels = getClaudeModels(claudeCodeVersion); const settingsModels = await readClaudeSettingsModels(logger, configDir); if (settingsModels.length === 0) { return hardcodedModels; diff --git a/packages/website/package.json b/packages/website/package.json index c7b3d3b8d..7574ece0f 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -1,6 +1,6 @@ { "name": "@getpaseo/website", - "version": "0.2.1", + "version": "0.2.2", "private": true, "type": "module", "scripts": {