diff --git a/.github/workflows/template-tauri-build-windows-x64.yml b/.github/workflows/template-tauri-build-windows-x64.yml
index ed00ef90f..963bb144c 100644
--- a/.github/workflows/template-tauri-build-windows-x64.yml
+++ b/.github/workflows/template-tauri-build-windows-x64.yml
@@ -234,8 +234,6 @@ jobs:
# Upload for tauri updater
aws s3 cp ./${{ steps.metadata.outputs.FILE_NAME }} s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/${{ steps.metadata.outputs.FILE_NAME }}
aws s3 cp ./${{ steps.metadata.outputs.FILE_NAME }}.sig s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/${{ steps.metadata.outputs.FILE_NAME }}.sig
-
- aws s3 cp ./src-tauri/target/release/bundle/msi/${{ steps.metadata.outputs.MSI_FILE_NAME }} s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/${{ steps.metadata.outputs.MSI_FILE_NAME }}
env:
AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }}
diff --git a/docs/src/pages/changelog/2025-10-02-jan-projects.mdx b/docs/src/pages/changelog/2025-10-02-jan-projects.mdx
index 63f29194d..851e26403 100644
--- a/docs/src/pages/changelog/2025-10-02-jan-projects.mdx
+++ b/docs/src/pages/changelog/2025-10-02-jan-projects.mdx
@@ -1,6 +1,6 @@
---
title: "Jan v0.7.0: Jan Projects"
-version: v0.7.0
+version: 0.7.0
description: "Jan v0.7.0 introduces Projects, model renaming, llama.cpp auto-tuning, model stats, and Azure support."
date: 2025-10-02
ogImage: "/assets/images/changelog/jan-release-v0.7.0.jpeg"
diff --git a/docs/src/pages/changelog/2025-10-03-jan-stability-improvements.mdx b/docs/src/pages/changelog/2025-10-03-jan-stability-improvements.mdx
new file mode 100644
index 000000000..df756ccfc
--- /dev/null
+++ b/docs/src/pages/changelog/2025-10-03-jan-stability-improvements.mdx
@@ -0,0 +1,26 @@
+---
+title: "Jan v0.7.1: Fixes Windows Version Revert & OpenRouter Models"
+version: 0.7.1
+description: "Jan v0.7.1 focuses on bug fixes, including a windows version revert and improvements to OpenRouter models."
+date: 2025-10-03
+---
+
+import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
+import { Callout } from 'nextra/components'
+
+
+
+### Bug Fixes: Windows Version Revert & OpenRouter Models
+
+#### Two quick fixes:
+- Jan no longer reverts to an older version on load
+- OpenRouter can now add models again
+- Add headers for anthropic request to fetch models
+
+---
+
+Update your Jan or [download the latest version](https://jan.ai/).
+
+For the complete list of changes, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.7.1).
+
+
diff --git a/src-tauri/tauri.linux.conf.json b/src-tauri/tauri.linux.conf.json
index 85f39ba50..02fa8cdf6 100644
--- a/src-tauri/tauri.linux.conf.json
+++ b/src-tauri/tauri.linux.conf.json
@@ -6,7 +6,8 @@
},
"bundle": {
"targets": ["deb", "appimage"],
- "resources": ["resources/LICENSE"],
+ "resources": ["resources/pre-install/**/*", "resources/LICENSE"],
+ "externalBin": ["resources/bin/uv"],
"linux": {
"appimage": {
"bundleMediaFramework": false,
diff --git a/src-tauri/tauri.macos.conf.json b/src-tauri/tauri.macos.conf.json
index 2113bd0fa..92f937f0f 100644
--- a/src-tauri/tauri.macos.conf.json
+++ b/src-tauri/tauri.macos.conf.json
@@ -6,6 +6,7 @@
},
"bundle": {
"targets": ["app", "dmg"],
- "resources": ["resources/LICENSE"]
+ "resources": ["resources/pre-install/**/*", "resources/LICENSE"],
+ "externalBin": ["resources/bin/bun", "resources/bin/uv"]
}
}
diff --git a/web-app/src/containers/dialogs/AddModel.tsx b/web-app/src/containers/dialogs/AddModel.tsx
index e8fd4e0fd..c44d3a0a5 100644
--- a/web-app/src/containers/dialogs/AddModel.tsx
+++ b/web-app/src/containers/dialogs/AddModel.tsx
@@ -15,8 +15,7 @@ import { IconPlus } from '@tabler/icons-react'
import { useState } from 'react'
import { getProviderTitle } from '@/lib/utils'
import { useTranslation } from '@/i18n/react-i18next-compat'
-import { ModelCapabilities } from '@/types/models'
-import { models as providerModels } from 'token.js'
+import { getModelCapabilities } from '@/lib/models'
import { toast } from 'sonner'
type DialogAddModelProps = {
@@ -52,23 +51,7 @@ export const DialogAddModel = ({ provider, trigger }: DialogAddModelProps) => {
id: modelId,
model: modelId,
name: modelId,
- capabilities: [
- ModelCapabilities.COMPLETION,
- (
- providerModels[
- provider.provider as unknown as keyof typeof providerModels
- ]?.supportsToolCalls as unknown as string[]
- )?.includes(modelId)
- ? ModelCapabilities.TOOLS
- : undefined,
- (
- providerModels[
- provider.provider as unknown as keyof typeof providerModels
- ]?.supportsImages as unknown as string[]
- )?.includes(modelId)
- ? ModelCapabilities.VISION
- : undefined,
- ].filter(Boolean) as string[],
+ capabilities: getModelCapabilities(provider.provider, modelId),
version: '1.0',
}
diff --git a/web-app/src/lib/__tests__/models.test.ts b/web-app/src/lib/__tests__/models.test.ts
index 67f37f873..bba4a64a1 100644
--- a/web-app/src/lib/__tests__/models.test.ts
+++ b/web-app/src/lib/__tests__/models.test.ts
@@ -5,19 +5,30 @@ import {
removeYamlFrontMatter,
extractModelName,
extractModelRepo,
+ getModelCapabilities,
} from '../models'
+import { ModelCapabilities } from '@/types/models'
// Mock the token.js module
vi.mock('token.js', () => ({
models: {
openai: {
models: ['gpt-3.5-turbo', 'gpt-4'],
+ supportsToolCalls: ['gpt-3.5-turbo', 'gpt-4'],
+ supportsImages: ['gpt-4-vision-preview'],
},
anthropic: {
models: ['claude-3-sonnet', 'claude-3-haiku'],
+ supportsToolCalls: ['claude-3-sonnet'],
+ supportsImages: ['claude-3-sonnet', 'claude-3-haiku'],
},
mistral: {
models: ['mistral-7b', 'mistral-8x7b'],
+ supportsToolCalls: ['mistral-8x7b'],
+ },
+ // Provider with no capability arrays
+ cohere: {
+ models: ['command', 'command-light'],
},
},
}))
@@ -223,3 +234,74 @@ describe('extractModelRepo', () => {
)
})
})
+
+describe('getModelCapabilities', () => {
+ it('returns completion capability for all models', () => {
+ const capabilities = getModelCapabilities('openai', 'gpt-3.5-turbo')
+ expect(capabilities).toContain(ModelCapabilities.COMPLETION)
+ })
+
+ it('includes tools capability when model supports it', () => {
+ const capabilities = getModelCapabilities('openai', 'gpt-3.5-turbo')
+ expect(capabilities).toContain(ModelCapabilities.TOOLS)
+ expect(capabilities).toContain(ModelCapabilities.COMPLETION)
+ })
+
+ it('excludes tools capability when model does not support it', () => {
+ const capabilities = getModelCapabilities('mistral', 'mistral-7b')
+ expect(capabilities).not.toContain(ModelCapabilities.TOOLS)
+ expect(capabilities).toContain(ModelCapabilities.COMPLETION)
+ })
+
+ it('includes vision capability when model supports it', () => {
+ const capabilities = getModelCapabilities('openai', 'gpt-4-vision-preview')
+ expect(capabilities).toContain(ModelCapabilities.VISION)
+ expect(capabilities).toContain(ModelCapabilities.COMPLETION)
+ })
+
+ it('excludes vision capability when model does not support it', () => {
+ const capabilities = getModelCapabilities('openai', 'gpt-3.5-turbo')
+ expect(capabilities).not.toContain(ModelCapabilities.VISION)
+ })
+
+ it('includes both tools and vision when model supports both', () => {
+ const capabilities = getModelCapabilities('anthropic', 'claude-3-sonnet')
+ expect(capabilities).toContain(ModelCapabilities.COMPLETION)
+ expect(capabilities).toContain(ModelCapabilities.TOOLS)
+ expect(capabilities).toContain(ModelCapabilities.VISION)
+ })
+
+ it('handles provider with no capability arrays gracefully', () => {
+ const capabilities = getModelCapabilities('cohere', 'command')
+ expect(capabilities).toEqual([ModelCapabilities.COMPLETION])
+ expect(capabilities).not.toContain(ModelCapabilities.TOOLS)
+ expect(capabilities).not.toContain(ModelCapabilities.VISION)
+ })
+
+ it('handles unknown provider gracefully', () => {
+ const capabilities = getModelCapabilities('openrouter', 'some-model')
+ expect(capabilities).toEqual([ModelCapabilities.COMPLETION])
+ expect(capabilities).not.toContain(ModelCapabilities.TOOLS)
+ expect(capabilities).not.toContain(ModelCapabilities.VISION)
+ })
+
+ it('handles model not in capability list', () => {
+ const capabilities = getModelCapabilities('anthropic', 'claude-3-haiku')
+ expect(capabilities).toContain(ModelCapabilities.COMPLETION)
+ expect(capabilities).toContain(ModelCapabilities.VISION)
+ expect(capabilities).not.toContain(ModelCapabilities.TOOLS)
+ })
+
+ it('returns only completion for provider with partial capability data', () => {
+ // Mistral has supportsToolCalls but no supportsImages
+ const capabilities = getModelCapabilities('mistral', 'mistral-7b')
+ expect(capabilities).toEqual([ModelCapabilities.COMPLETION])
+ })
+
+ it('handles model that supports tools but not vision', () => {
+ const capabilities = getModelCapabilities('mistral', 'mistral-8x7b')
+ expect(capabilities).toContain(ModelCapabilities.COMPLETION)
+ expect(capabilities).toContain(ModelCapabilities.TOOLS)
+ expect(capabilities).not.toContain(ModelCapabilities.VISION)
+ })
+})
diff --git a/web-app/src/lib/models.ts b/web-app/src/lib/models.ts
index 0f9b79c40..18d0b6d8e 100644
--- a/web-app/src/lib/models.ts
+++ b/web-app/src/lib/models.ts
@@ -1,4 +1,5 @@
import { models } from 'token.js'
+import { ModelCapabilities } from '@/types/models'
export const defaultModel = (provider?: string) => {
if (!provider || !Object.keys(models).includes(provider)) {
@@ -10,6 +11,38 @@ export const defaultModel = (provider?: string) => {
)[0]
}
+/**
+ * Determines model capabilities based on provider configuration from token.js
+ * @param providerName - The provider name (e.g., 'openai', 'anthropic', 'openrouter')
+ * @param modelId - The model ID to check capabilities for
+ * @returns Array of model capabilities
+ */
+export const getModelCapabilities = (
+ providerName: string,
+ modelId: string
+): string[] => {
+ const providerConfig =
+ models[providerName as unknown as keyof typeof models]
+
+ const supportsToolCalls = Array.isArray(
+ providerConfig?.supportsToolCalls as unknown
+ )
+ ? (providerConfig.supportsToolCalls as unknown as string[])
+ : []
+
+ const supportsImages = Array.isArray(
+ providerConfig?.supportsImages as unknown
+ )
+ ? (providerConfig.supportsImages as unknown as string[])
+ : []
+
+ return [
+ ModelCapabilities.COMPLETION,
+ supportsToolCalls.includes(modelId) ? ModelCapabilities.TOOLS : undefined,
+ supportsImages.includes(modelId) ? ModelCapabilities.VISION : undefined,
+ ].filter(Boolean) as string[]
+}
+
/**
* This utility is to extract cortexso model description from README.md file
* @returns
diff --git a/web-app/src/routes/settings/local-api-server.tsx b/web-app/src/routes/settings/local-api-server.tsx
index 3628a6f8c..b3360e98e 100644
--- a/web-app/src/routes/settings/local-api-server.tsx
+++ b/web-app/src/routes/settings/local-api-server.tsx
@@ -258,7 +258,7 @@ function LocalAPIServerContent() {
}
}
- const isServerRunning = serverStatus === 'running'
+ const isServerRunning = serverStatus !== 'stopped'
return (
diff --git a/web-app/src/services/providers/tauri.ts b/web-app/src/services/providers/tauri.ts
index 50f1217da..a8ca36fbb 100644
--- a/web-app/src/services/providers/tauri.ts
+++ b/web-app/src/services/providers/tauri.ts
@@ -10,6 +10,7 @@ import { modelSettings } from '@/lib/predefined'
import { ExtensionManager } from '@/lib/extension'
import { fetch as fetchTauri } from '@tauri-apps/plugin-http'
import { DefaultProvidersService } from './default'
+import { getModelCapabilities } from '@/lib/models'
export class TauriProvidersService extends DefaultProvidersService {
fetch(): typeof fetch {
@@ -26,32 +27,16 @@ export class TauriProvidersService extends DefaultProvidersService {
provider.provider as unknown as keyof typeof providerModels
].models as unknown as string[]
- if (Array.isArray(builtInModels))
+ if (Array.isArray(builtInModels)) {
models = builtInModels.map((model) => {
const modelManifest = models.find((e) => e.id === model)
// TODO: Check chat_template for tool call support
- const capabilities = [
- ModelCapabilities.COMPLETION,
- (
- providerModels[
- provider.provider as unknown as keyof typeof providerModels
- ]?.supportsToolCalls as unknown as string[]
- )?.includes(model)
- ? ModelCapabilities.TOOLS
- : undefined,
- (
- providerModels[
- provider.provider as unknown as keyof typeof providerModels
- ]?.supportsImages as unknown as string[]
- )?.includes(model)
- ? ModelCapabilities.VISION
- : undefined,
- ].filter(Boolean) as string[]
return {
...(modelManifest ?? { id: model, name: model }),
- capabilities,
+ capabilities: getModelCapabilities(provider.provider, model),
} as Model
})
+ }
}
return {
diff --git a/web-app/src/services/providers/web.ts b/web-app/src/services/providers/web.ts
index 6a7865be8..29d4a9cb7 100644
--- a/web-app/src/services/providers/web.ts
+++ b/web-app/src/services/providers/web.ts
@@ -11,6 +11,7 @@ import { ExtensionManager } from '@/lib/extension'
import type { ProvidersService } from './types'
import { PlatformFeatures } from '@/lib/platform/const'
import { PlatformFeature } from '@/lib/platform/types'
+import { getModelCapabilities } from '@/lib/models'
export class WebProvidersService implements ProvidersService {
async getProviders(): Promise {
@@ -88,19 +89,9 @@ export class WebProvidersService implements ProvidersService {
models = builtInModels.map((model) => {
const modelManifest = models.find((e) => e.id === model)
// TODO: Check chat_template for tool call support
- const capabilities = [
- ModelCapabilities.COMPLETION,
- (
- providerModels[
- provider.provider as unknown as keyof typeof providerModels
- ]?.supportsToolCalls as unknown as string[]
- )?.includes(model)
- ? ModelCapabilities.TOOLS
- : undefined,
- ].filter(Boolean) as string[]
return {
...(modelManifest ?? { id: model, name: model }),
- capabilities,
+ capabilities: getModelCapabilities(provider.provider, model),
} as Model
})
}