Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/src/pages/docs/desktop/remote-models/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
"openrouter": {
"title": "OpenRouter"
},
"nearai": {
"title": "NEAR AI Cloud"
},
"huggingface": {
"title": "Hugging Face"
}
Expand Down
69 changes: 69 additions & 0 deletions docs/src/pages/docs/desktop/remote-models/nearai.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
title: NEAR AI Cloud
description: A step-by-step guide on integrating Jan with NEAR AI Cloud.
keywords:
[
Jan,
local AI,
privacy focus,
conversational AI,
large language models,
NEAR AI,
NEAR AI Cloud,
TEE inference,
]
---

import { Callout, Steps } from 'nextra/components'
import { Settings } from 'lucide-react'

# NEAR AI Cloud

Jan supports [NEAR AI Cloud](https://cloud.near.ai) through its OpenAI-compatible API, allowing you to use NEAR-hosted models with TEE-backed private inference from Jan.

## Integrate NEAR AI Cloud with Jan

<Steps>

### Step 1: Get Your API Key
1. Visit [NEAR AI Cloud](https://cloud.near.ai) and sign in
2. Create or copy an API key for your account

<Callout type='info'>
NEAR AI Cloud model availability and TEE attestation support can vary by model. Check the public model catalog before selecting a model ID.
</Callout>

### Step 2: Configure Jan

1. Navigate to the **Settings** page (<Settings width={16} height={16} style={{display:"inline"}}/>)
2. Under **Model Providers**, select **NEAR AI**
3. Insert your **API Key**
4. Keep the default base URL: `https://cloud-api.near.ai/v1`

### Step 3: Start Using NEAR AI Cloud Models

1. Open any existing **Chat** or create a new one
2. Select a NEAR AI Cloud model from the **model selector**
3. Start chatting
</Steps>

## Available Models Through NEAR AI Cloud

Jan refreshes NEAR AI Cloud models from the public catalog at `https://cloud-api.near.ai/v1/model/list`. Use the `modelId` value from that catalog when adding a model manually.

## Troubleshooting

**1. API Key Issues**
- Verify your API key is correct and not expired
- Confirm your account has access to the model you selected

**2. Connection Problems**
- Check your internet connection
- Verify the base URL is `https://cloud-api.near.ai/v1`
- Look for error messages in [Jan's logs](/docs/desktop/troubleshooting#how-to-get-error-logs)

**3. Model Unavailable**
- Confirm the model appears in the [NEAR AI Cloud model catalog](https://cloud-api.near.ai/v1/model/list)
- Check whether the model supports chat-style text output

Need more help? Join our [Discord community](https://discord.gg/FTk2MvZwJH).
25 changes: 25 additions & 0 deletions web-app/src/constants/__tests__/nearai-provider.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { describe, expect, it } from 'vitest'
import { providerModels } from '../models'
import { predefinedProviders } from '../providers'

describe('NEAR AI predefined provider', () => {
it('registers NEAR AI Cloud as an OpenAI-compatible remote provider', () => {
const provider = predefinedProviders.find((p) => p.provider === 'nearai')

expect(provider).toBeDefined()
expect(provider?.base_url).toBe('https://cloud-api.near.ai/v1')
expect(provider?.explore_models_url).toBe(
'https://cloud-api.near.ai/v1/model/list'
)
expect(provider?.settings.map((s) => s.key)).toEqual([
'api-key',
'base-url',
])
})

it('uses the live model catalog instead of a static model list', () => {
expect(providerModels.nearai.models).toBe(true)
expect(providerModels.nearai.supportsStreaming).toBe(true)
expect(providerModels.nearai.supportsToolCalls).toBe(true)
})
})
11 changes: 10 additions & 1 deletion web-app/src/constants/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,15 @@ export const providerModels = {
supportsToolCalls: true,
supportsN: true,
},
nearai: {
models: true,
supportsCompletion: true,
supportsStreaming: true,
supportsJSON: true,
supportsImages: true,
supportsToolCalls: true,
supportsN: true,
},
nvidia: {
models: ['moonshotai/kimi-k2.5', 'minimaxai/minimax-m2.5', 'z-ai/glm5'],
supportsCompletion: true,
Expand All @@ -144,4 +153,4 @@ export const providerModels = {
supportsToolCalls: true,
supportsN: true,
},
} as const
} as const
34 changes: 34 additions & 0 deletions web-app/src/constants/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,40 @@ export const predefinedProviders = [
},
],
},
{
active: true,
api_key: '',
base_url: 'https://cloud-api.near.ai/v1',
explore_models_url: 'https://cloud-api.near.ai/v1/model/list',
provider: 'nearai',
Comment thread
PierreLeGuen marked this conversation as resolved.
settings: [
{
key: 'api-key',
title: 'API Key',
description:
"The NEAR AI Cloud API uses API keys for authentication. Visit [NEAR AI Cloud](https://cloud.near.ai) to create the API key you'll use for TEE-backed private inference.",
controller_type: 'input',
controller_props: {
placeholder: 'Insert API Key',
value: '',
type: 'password',
input_actions: ['unobscure', 'copy'],
},
},
{
key: 'base-url',
title: 'Base URL',
description:
'The NEAR AI Cloud OpenAI-compatible endpoint to use. See the [NEAR AI Cloud model catalog](https://cloud-api.near.ai/v1/model/list) for available model IDs.',
controller_type: 'input',
controller_props: {
placeholder: 'https://cloud-api.near.ai/v1',
value: 'https://cloud-api.near.ai/v1',
},
},
],
models: [],
},
{
active: true,
api_key: '',
Expand Down
24 changes: 24 additions & 0 deletions web-app/src/lib/__tests__/model-factory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,30 @@ describe('ModelFactory', () => {
expect(model.type).toBe('openai-compatible')
})

it('should create an OpenAI-compatible model for NEAR AI provider', async () => {
const provider: ProviderObject = {
provider: 'nearai',
api_key: 'test-api-key',
base_url: 'https://cloud-api.near.ai/v1',
models: [],
settings: [],
active: true,
}

const model = await ModelFactory.createModel(
'Qwen/Qwen3.6-35B-A3B-FP8',
provider
)
expect(model).toBeDefined()
expect(model.type).toBe('openai-compatible')
expect(mockedCreateOpenAICompatible).toHaveBeenLastCalledWith(
expect.objectContaining({
name: 'nearai',
baseURL: 'https://cloud-api.near.ai/v1',
})
)
})

it('should handle custom headers for OpenAI-compatible providers', async () => {
const provider: ProviderObject = {
provider: 'custom',
Expand Down
152 changes: 151 additions & 1 deletion web-app/src/lib/__tests__/remoteModelCatalog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,21 @@ function mkGeminiProvider(extra: Record<string, unknown> = {}) {
} as any
}

function mkNearAIProvider(extra: Record<string, unknown> = {}) {
return {
provider: 'nearai',
base_url: 'https://cloud-api.near.ai/v1',
api_key: 'near-test',
...extra,
} as any
}

describe('supportsRemoteCatalog', () => {
it('supports openai, anthropic and gemini', () => {
it('supports openai, anthropic, gemini and nearai', () => {
expect(supportsRemoteCatalog('openai')).toBe(true)
expect(supportsRemoteCatalog('anthropic')).toBe(true)
expect(supportsRemoteCatalog('gemini')).toBe(true)
expect(supportsRemoteCatalog('nearai')).toBe(true)
expect(supportsRemoteCatalog('groq')).toBe(false)
expect(supportsRemoteCatalog('mistral')).toBe(false)
})
Expand Down Expand Up @@ -173,6 +183,146 @@ describe('fetchTopRemoteModels openai', () => {
})
})

describe('fetchTopRemoteModels nearai', () => {
it('uses the NEAR model catalog and filters non-chat utility models', async () => {
const fetchImpl = vi.fn().mockResolvedValue(
mkResponse({
models: [
{
modelId: 'anthropic/claude-haiku-4-5',
metadata: {
verifiable: false,
attestationSupported: false,
architecture: {
inputModalities: ['text', 'image'],
outputModalities: ['text'],
},
},
},
{
modelId: 'Qwen/Qwen3.6-35B-A3B-FP8',
metadata: {
verifiable: true,
attestationSupported: true,
architecture: {
inputModalities: ['text'],
outputModalities: ['text'],
},
},
},
{
modelId: 'Qwen/Qwen3-VL-30B-A3B-Instruct',
metadata: {
verifiable: true,
attestationSupported: true,
architecture: {
inputModalities: ['text', 'image'],
outputModalities: ['text'],
},
},
},
{
modelId: 'black-forest-labs/FLUX.2-klein-4B',
metadata: {
verifiable: true,
attestationSupported: true,
architecture: {
inputModalities: ['text'],
outputModalities: ['image'],
},
},
},
{
modelId: 'Qwen/Qwen3-Embedding-0.6B',
metadata: {
verifiable: true,
attestationSupported: true,
architecture: {
inputModalities: ['text'],
outputModalities: ['embedding'],
},
},
},
{
modelId: 'Qwen/Qwen3-Reranker-0.6B',
metadata: {
verifiable: true,
attestationSupported: true,
architecture: {
inputModalities: ['text'],
outputModalities: ['text'],
},
},
},
{
modelId: 'openai/privacy-filter',
metadata: {
verifiable: true,
attestationSupported: true,
architecture: {
inputModalities: ['text'],
outputModalities: ['text'],
},
},
},
{
modelId: 'openai/whisper-large-v3',
metadata: {
verifiable: true,
attestationSupported: true,
architecture: {
inputModalities: ['audio'],
outputModalities: ['text'],
},
},
},
{
modelId: 'missing-modalities',
metadata: {
verifiable: true,
attestationSupported: true,
architecture: {
inputModalities: [],
outputModalities: [],
},
},
},
],
})
)

const result = await fetchTopRemoteModels(mkNearAIProvider(), fetchImpl)
const ids = result.map((m) => m.id)

expect(fetchImpl).toHaveBeenCalledWith(
'https://cloud-api.near.ai/v1/model/list',
expect.objectContaining({
method: 'GET',
headers: expect.objectContaining({
Authorization: 'Bearer near-test',
}),
})
)
expect(ids).toContain('Qwen/Qwen3.6-35B-A3B-FP8')
expect(ids).toContain('Qwen/Qwen3-VL-30B-A3B-Instruct')
expect(ids).toContain('anthropic/claude-haiku-4-5')
expect(ids).not.toContain('black-forest-labs/FLUX.2-klein-4B')
expect(ids).not.toContain('Qwen/Qwen3-Embedding-0.6B')
expect(ids).not.toContain('Qwen/Qwen3-Reranker-0.6B')
expect(ids).not.toContain('openai/privacy-filter')
expect(ids).not.toContain('openai/whisper-large-v3')
expect(ids).not.toContain('missing-modalities')

expect(
result.find((m) => m.id === 'Qwen/Qwen3-VL-30B-A3B-Instruct')
?.capabilities
).toEqual(['completion', 'tools', 'vision'])
expect(
result.find((m) => m.id === 'Qwen/Qwen3.6-35B-A3B-FP8')?.capabilities
).toEqual(['completion', 'tools'])
})
})

describe('fetchTopRemoteModels anthropic', () => {
it('parses created_at ISO and infers claude capabilities', async () => {
const fetchImpl = vi.fn().mockResolvedValue(
Expand Down
1 change: 1 addition & 0 deletions web-app/src/lib/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ describe('getProviderTitle', () => {
expect(getProviderTitle('openai')).toBe('OpenAI')
expect(getProviderTitle('openrouter')).toBe('OpenRouter')
expect(getProviderTitle('gemini')).toBe('Gemini')
expect(getProviderTitle('nearai')).toBe('NEAR AI')
expect(getProviderTitle('nvidia')).toBe('NVIDIA NIM')
})

Expand Down
2 changes: 2 additions & 0 deletions web-app/src/lib/model-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,8 @@ export class ModelFactory {
case 'moonshot':
case 'minimax':
return this.createOpenAICompatibleModel(modelId, provider)
case 'nearai':
return this.createOpenAICompatibleModel(modelId, provider, parameters)

case 'mistral':
return this.createMistralModel(modelId, provider, parameters)
Expand Down
Loading