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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/server.js +19 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "freegate",
3
- "version": "0.6.1",
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
- return { role: 'user', content: `${m.content.find((c) => c.type === 'text')?.text || ''}\n\n[Содержимое скриншота]\n${cleaned}` };
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).filter(([_, p]) => p.enabled).map(([key, p]) => ({ id: p.model, object: 'model', owned_by: key }));
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;