freegate 0.6.1 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/server.js +19 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "freegate",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "Free multi-provider LLM gateway with automatic failover. One OpenAI-compatible endpoint routes to 25 free models (Groq, Mistral, Gemini, NIM, OpenRouter, ZAI, Cerebras, DeepSeek). Never pay for LLMs.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
package/server.js
CHANGED
|
@@ -130,7 +130,7 @@ async function handleChatCompletion(req, res, body) {
|
|
|
130
130
|
// text as context — so a coding model handles the fix, not vision.
|
|
131
131
|
const hasImage = Array.isArray(body.messages) && body.messages.some((m) => {
|
|
132
132
|
if (Array.isArray(m.content)) {
|
|
133
|
-
return m.content.some((c) => c && (c.type === 'image_url' || c.type === 'image'));
|
|
133
|
+
return m.content.some((c) => c && (c.type === 'image_url' || c.type === 'image' || c.type === 'file' || c.type === 'input_image'));
|
|
134
134
|
}
|
|
135
135
|
return false;
|
|
136
136
|
});
|
|
@@ -150,7 +150,11 @@ async function handleChatCompletion(req, res, body) {
|
|
|
150
150
|
role: 'user',
|
|
151
151
|
content: [
|
|
152
152
|
{ type: 'text', text: 'Распознай и извлеки ВЕСЬ текст с изображения (ошибка, код, сообщение). Верни только содержимое, без комментариев. Если это код — верни код как есть.' },
|
|
153
|
-
...(Array.isArray(body.messages) ? body.messages.flatMap((m) => (Array.isArray(m.content) ? m.content.filter((c) => c.type === 'image_url' || c.type === 'image'
|
|
153
|
+
...(Array.isArray(body.messages) ? body.messages.flatMap((m) => (Array.isArray(m.content) ? m.content.filter((c) => c && (c.type === 'image_url' || c.type === 'image' || c.type === 'input_image')).map((c) => {
|
|
154
|
+
// Normalize any image part to the universal image_url format
|
|
155
|
+
const url = c.image_url?.url || c.image?.url || (c.image && typeof c.image === 'string' ? c.image : null) || c.url;
|
|
156
|
+
return url ? { type: 'image_url', image_url: { url } } : null;
|
|
157
|
+
}).filter(Boolean) : [])) : []),
|
|
154
158
|
],
|
|
155
159
|
}],
|
|
156
160
|
max_tokens: 2000,
|
|
@@ -171,8 +175,9 @@ async function handleChatCompletion(req, res, body) {
|
|
|
171
175
|
body = {
|
|
172
176
|
...body,
|
|
173
177
|
messages: userMsgs.map((m) => {
|
|
174
|
-
if (Array.isArray(m.content) && m.content.some((c) => c.type === 'image_url' || c.type === 'image')) {
|
|
175
|
-
|
|
178
|
+
if (Array.isArray(m.content) && m.content.some((c) => c && (c.type === 'image_url' || c.type === 'image' || c.type === 'input_image'))) {
|
|
179
|
+
const textPart = m.content.find((c) => c && c.type === 'text')?.text || '';
|
|
180
|
+
return { role: 'user', content: `${textPart}\n\n[Содержимое скриншота]\n${cleaned}` };
|
|
176
181
|
}
|
|
177
182
|
return m;
|
|
178
183
|
}),
|
|
@@ -530,7 +535,16 @@ const server = http.createServer(async (req, res) => {
|
|
|
530
535
|
}
|
|
531
536
|
|
|
532
537
|
if (parsedUrl.pathname === '/v1/models') {
|
|
533
|
-
const models = Object.entries(PROVIDERS)
|
|
538
|
+
const models = Object.entries(PROVIDERS)
|
|
539
|
+
.filter(([_, p]) => p.enabled)
|
|
540
|
+
.map(([key, p]) => ({
|
|
541
|
+
id: p.model,
|
|
542
|
+
object: 'model',
|
|
543
|
+
owned_by: key,
|
|
544
|
+
category: p.category || 'general',
|
|
545
|
+
vision: p.vision === true,
|
|
546
|
+
latency_ms: getHealth()[key]?.latency || null,
|
|
547
|
+
}));
|
|
534
548
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
535
549
|
res.end(JSON.stringify({ object: 'list', data: models }));
|
|
536
550
|
return;
|