Skip to content

Commit aca1410

Browse files
authored
Merge pull request #471 from miurla/fix-models-json-loading
Fix models.json loading in various environments
2 parents 4e698b7 + b48f041 commit aca1410

File tree

3 files changed

+257
-15
lines changed

3 files changed

+257
-15
lines changed

lib/config/default-models.json

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
{
2+
"models": [
3+
{
4+
"id": "o3-mini",
5+
"name": "o3 mini",
6+
"provider": "OpenAI",
7+
"providerId": "openai",
8+
"enabled": true,
9+
"toolCallType": "native"
10+
},
11+
{
12+
"id": "gpt-4o",
13+
"name": "GPT-4o",
14+
"provider": "OpenAI",
15+
"providerId": "openai",
16+
"enabled": true,
17+
"toolCallType": "native"
18+
},
19+
{
20+
"id": "gpt-4o-mini",
21+
"name": "GPT-4o mini",
22+
"provider": "OpenAI",
23+
"providerId": "openai",
24+
"enabled": true,
25+
"toolCallType": "native"
26+
},
27+
{
28+
"id": "claude-3-7-sonnet-20250219",
29+
"name": "Claude 3.7 Sonnet",
30+
"provider": "Anthropic",
31+
"providerId": "anthropic",
32+
"enabled": true,
33+
"toolCallType": "native"
34+
},
35+
{
36+
"id": "claude-3-5-sonnet-latest",
37+
"name": "Claude 3.5 Sonnet",
38+
"provider": "Anthropic",
39+
"providerId": "anthropic",
40+
"enabled": true,
41+
"toolCallType": "native"
42+
},
43+
{
44+
"id": "claude-3-5-haiku-20241022",
45+
"name": "Claude 3.5 Haiku",
46+
"provider": "Anthropic",
47+
"providerId": "anthropic",
48+
"enabled": true,
49+
"toolCallType": "native"
50+
},
51+
{
52+
"id": "<AZURE_DEPLOYMENT_NAME>",
53+
"name": "<AZURE_DEPLOYMENT_NAME>",
54+
"provider": "Azure",
55+
"providerId": "azure",
56+
"enabled": true,
57+
"toolCallType": "native"
58+
},
59+
{
60+
"id": "gemini-2.0-flash",
61+
"name": "Gemini 2.0 Flash",
62+
"provider": "Google Generative AI",
63+
"providerId": "google",
64+
"enabled": true,
65+
"toolCallType": "manual"
66+
},
67+
{
68+
"id": "gemini-2.0-pro-exp-02-05",
69+
"name": "Gemini 2.0 Pro (Exp)",
70+
"provider": "Google Generative AI",
71+
"providerId": "google",
72+
"enabled": true,
73+
"toolCallType": "manual",
74+
"toolCallModel": "gemini-2.0-flash"
75+
},
76+
{
77+
"id": "gemini-2.0-flash-thinking-exp-01-21",
78+
"name": "Gemini 2.0 Flash Thinking (Exp)",
79+
"provider": "Google Generative AI",
80+
"providerId": "google",
81+
"enabled": true,
82+
"toolCallType": "manual",
83+
"toolCallModel": "gemini-2.0-flash"
84+
},
85+
{
86+
"id": "accounts/fireworks/models/deepseek-r1",
87+
"name": "DeepSeek R1",
88+
"provider": "Fireworks",
89+
"providerId": "fireworks",
90+
"enabled": true,
91+
"toolCallType": "manual",
92+
"toolCallModel": "accounts/fireworks/models/llama-v3p1-8b-instruct"
93+
},
94+
{
95+
"id": "deepseek-reasoner",
96+
"name": "DeepSeek R1",
97+
"provider": "DeepSeek",
98+
"providerId": "deepseek",
99+
"enabled": true,
100+
"toolCallType": "manual",
101+
"toolCallModel": "deepseek-chat"
102+
},
103+
{
104+
"id": "deepseek-chat",
105+
"name": "DeepSeek V3",
106+
"provider": "DeepSeek",
107+
"providerId": "deepseek",
108+
"enabled": true,
109+
"toolCallType": "manual"
110+
},
111+
{
112+
"id": "deepseek-r1-distill-llama-70b",
113+
"name": "DeepSeek R1 Distill Llama 70B",
114+
"provider": "Groq",
115+
"providerId": "groq",
116+
"enabled": true,
117+
"toolCallType": "manual",
118+
"toolCallModel": "llama-3.1-8b-instant"
119+
},
120+
{
121+
"id": "deepseek-r1",
122+
"name": "DeepSeek R1",
123+
"provider": "Ollama",
124+
"providerId": "ollama",
125+
"enabled": true,
126+
"toolCallType": "manual",
127+
"toolCallModel": "phi4"
128+
},
129+
{
130+
"id": "<OLLAMA_MODEL_ID>",
131+
"name": "<OLLAMA_MODEL_NAME>",
132+
"provider": "Ollama",
133+
"providerId": "ollama",
134+
"enabled": true,
135+
"toolCallType": "manual",
136+
"toolCallModel": "<OLLAMA_MODEL_ID>"
137+
},
138+
{
139+
"id": "grok-2-1212",
140+
"name": "Grok 2",
141+
"provider": "xAI",
142+
"providerId": "xai",
143+
"enabled": true,
144+
"toolCallType": "native"
145+
},
146+
{
147+
"id": "grok-2-vision-1212",
148+
"name": "Grok 2 Vision",
149+
"provider": "xAI",
150+
"providerId": "xai",
151+
"enabled": true,
152+
"toolCallType": "native"
153+
},
154+
{
155+
"id": "<OPENAI_COMPATIBLE_MODEL>",
156+
"name": "<OPENAI_COMPATIBLE_MODEL>",
157+
"provider": "OpenAI Compatible",
158+
"providerId": "openai-compatible",
159+
"enabled": true,
160+
"toolCallType": "native"
161+
}
162+
]
163+
}

lib/config/models.ts

+79-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Model } from '@/lib/types/models'
22
import { headers } from 'next/headers'
3+
import defaultModels from './default-models.json'
34

45
export function validateModel(model: any): model is Model {
56
return (
@@ -9,27 +10,93 @@ export function validateModel(model: any): model is Model {
910
typeof model.providerId === 'string' &&
1011
typeof model.enabled === 'boolean' &&
1112
(model.toolCallType === 'native' || model.toolCallType === 'manual') &&
12-
(model.toolCallModel === undefined || typeof model.toolCallModel === 'string')
13+
(model.toolCallModel === undefined ||
14+
typeof model.toolCallModel === 'string')
1315
)
1416
}
1517

1618
export async function getModels(): Promise<Model[]> {
1719
try {
1820
const headersList = await headers()
19-
const baseUrl = new URL(headersList.get('x-url') || 'http://localhost:3000')
20-
const modelUrl = new URL('/config/models.json', baseUrl.origin)
21-
22-
const response = await fetch(modelUrl, {
23-
cache: 'no-store'
24-
})
25-
const config = await response.json()
26-
if (Array.isArray(config.models) && config.models.every(validateModel)) {
27-
return config.models
21+
const baseUrl = headersList.get('x-base-url')
22+
const url = headersList.get('x-url')
23+
const host = headersList.get('x-host')
24+
const protocol = headersList.get('x-protocol') || 'http:'
25+
26+
// Construct base URL using the headers
27+
let baseUrlObj: URL
28+
29+
try {
30+
// Try to use the pre-constructed base URL if available
31+
if (baseUrl) {
32+
baseUrlObj = new URL(baseUrl)
33+
} else if (url) {
34+
baseUrlObj = new URL(url)
35+
} else if (host) {
36+
const constructedUrl = `${protocol}${
37+
protocol.endsWith(':') ? '//' : '://'
38+
}${host}`
39+
baseUrlObj = new URL(constructedUrl)
40+
} else {
41+
baseUrlObj = new URL('http://localhost:3000')
42+
}
43+
} catch (urlError) {
44+
// Fallback to default URL if any error occurs during URL construction
45+
baseUrlObj = new URL('http://localhost:3000')
46+
}
47+
48+
// Construct the models.json URL
49+
const modelUrl = new URL('/config/models.json', baseUrlObj)
50+
console.log('Attempting to fetch models from:', modelUrl.toString())
51+
52+
try {
53+
const response = await fetch(modelUrl, {
54+
cache: 'no-store',
55+
headers: {
56+
Accept: 'application/json'
57+
}
58+
})
59+
60+
if (!response.ok) {
61+
console.warn(
62+
`HTTP error when fetching models: ${response.status} ${response.statusText}`
63+
)
64+
throw new Error(`HTTP error! status: ${response.status}`)
65+
}
66+
67+
const text = await response.text()
68+
69+
// Check if the response starts with HTML doctype
70+
if (text.trim().toLowerCase().startsWith('<!doctype')) {
71+
console.warn('Received HTML instead of JSON when fetching models')
72+
throw new Error('Received HTML instead of JSON')
73+
}
74+
75+
const config = JSON.parse(text)
76+
if (Array.isArray(config.models) && config.models.every(validateModel)) {
77+
console.log('Successfully loaded models from URL')
78+
return config.models
79+
}
80+
} catch (error: any) {
81+
// Fallback to default models if fetch fails
82+
console.warn(
83+
'Fetch failed, falling back to default models:',
84+
error.message || 'Unknown error'
85+
)
86+
87+
if (
88+
Array.isArray(defaultModels.models) &&
89+
defaultModels.models.every(validateModel)
90+
) {
91+
console.log('Successfully loaded default models')
92+
return defaultModels.models
93+
}
2894
}
29-
console.warn('Invalid model configuration')
3095
} catch (error) {
3196
console.warn('Failed to load models:', error)
3297
}
33-
98+
99+
// Last resort: return empty array
100+
console.warn('All attempts to load models failed, returning empty array')
34101
return []
35102
}

middleware.ts

+15-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,26 @@
1-
import { NextResponse } from 'next/server'
21
import type { NextRequest } from 'next/server'
2+
import { NextResponse } from 'next/server'
33

44
export function middleware(request: NextRequest) {
55
// Create a response
66
const response = NextResponse.next()
77

8+
// Get the protocol from X-Forwarded-Proto header or request protocol
9+
const protocol =
10+
request.headers.get('x-forwarded-proto') || request.nextUrl.protocol
11+
12+
// Get the host from X-Forwarded-Host header or request host
13+
const host =
14+
request.headers.get('x-forwarded-host') || request.headers.get('host') || ''
15+
16+
// Construct the base URL - ensure protocol has :// format
17+
const baseUrl = `${protocol}${protocol.endsWith(':') ? '//' : '://'}${host}`
18+
819
// Add request information to response headers
920
response.headers.set('x-url', request.url)
10-
response.headers.set('x-host', request.headers.get('host') || '')
11-
response.headers.set('x-protocol', request.nextUrl.protocol)
21+
response.headers.set('x-host', host)
22+
response.headers.set('x-protocol', protocol)
23+
response.headers.set('x-base-url', baseUrl)
1224

1325
return response
1426
}

0 commit comments

Comments
 (0)