133 lines
3.4 KiB
JavaScript
133 lines
3.4 KiB
JavaScript
export default {
|
|
async fetch(request, env) {
|
|
const url = new URL(request.url);
|
|
|
|
if (request.method === 'OPTIONS') {
|
|
return new Response(null, { headers: corsHeaders(request, env) });
|
|
}
|
|
|
|
if (url.pathname === '/api/models' && request.method === 'GET') {
|
|
return handleModels(request, env);
|
|
}
|
|
|
|
if (url.pathname === '/api/chat' && request.method === 'POST') {
|
|
return handleChat(request, env);
|
|
}
|
|
|
|
return json({ error: 'Not found' }, 404, request, env);
|
|
},
|
|
};
|
|
|
|
async function handleModels(request, env) {
|
|
const authError = verifyAccessKey(request, env);
|
|
if (authError) return authError;
|
|
|
|
const upstream = await fetch(joinUrl(env.OPENAI_BASE_URL, '/v1/models'), {
|
|
method: 'GET',
|
|
headers: {
|
|
Authorization: `Bearer ${env.OPENAI_API_KEY}`,
|
|
},
|
|
});
|
|
|
|
const text = await upstream.text();
|
|
let data;
|
|
|
|
try {
|
|
data = JSON.parse(text);
|
|
} catch {
|
|
return json({ error: 'Invalid upstream /v1/models response', raw: text.slice(0, 500) }, 502, request, env);
|
|
}
|
|
|
|
if (!upstream.ok) {
|
|
return json(
|
|
{
|
|
error: data?.error?.message || data?.error || `Upstream /v1/models failed with HTTP ${upstream.status}`,
|
|
upstreamStatus: upstream.status,
|
|
},
|
|
upstream.status,
|
|
request,
|
|
env,
|
|
);
|
|
}
|
|
|
|
const models = Array.isArray(data?.data)
|
|
? data.data
|
|
.filter((item) => item && typeof item.id === 'string' && item.id.trim())
|
|
.map((item) => ({ id: item.id, label: item.id }))
|
|
: [];
|
|
|
|
return json({ models }, 200, request, env);
|
|
}
|
|
|
|
async function handleChat(request, env) {
|
|
const authError = verifyAccessKey(request, env);
|
|
if (authError) return authError;
|
|
|
|
let payload;
|
|
try {
|
|
payload = await request.json();
|
|
} catch {
|
|
return json({ error: 'Invalid JSON body' }, 400, request, env);
|
|
}
|
|
|
|
const upstream = await fetch(joinUrl(env.OPENAI_BASE_URL, '/v1/chat/completions'), {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
Authorization: `Bearer ${env.OPENAI_API_KEY}`,
|
|
},
|
|
body: JSON.stringify(payload),
|
|
});
|
|
|
|
const headers = new Headers(corsHeaders(request, env));
|
|
const contentType = upstream.headers.get('content-type');
|
|
if (contentType) headers.set('content-type', contentType);
|
|
|
|
return new Response(upstream.body, {
|
|
status: upstream.status,
|
|
headers,
|
|
});
|
|
}
|
|
|
|
function verifyAccessKey(request, env) {
|
|
const auth = request.headers.get('authorization') || '';
|
|
const expected = `Bearer ${env.ACCESS_KEY}`;
|
|
|
|
if (!env.ACCESS_KEY) {
|
|
return json({ error: 'Server ACCESS_KEY is not configured' }, 500, request, env);
|
|
}
|
|
|
|
if (auth !== expected) {
|
|
return json({ error: 'Unauthorized' }, 401, request, env);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function joinUrl(base, path) {
|
|
return `${String(base || '').replace(/\/$/, '')}${path}`;
|
|
}
|
|
|
|
function corsHeaders(request, env) {
|
|
const origin = request?.headers?.get('origin');
|
|
const allowedOrigin = env.ALLOWED_ORIGIN || '*';
|
|
const finalOrigin = allowedOrigin === '*' ? '*' : origin || allowedOrigin;
|
|
|
|
return {
|
|
'Access-Control-Allow-Origin': finalOrigin,
|
|
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
|
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
'Vary': 'Origin',
|
|
};
|
|
}
|
|
|
|
function json(data, status = 200, request, env) {
|
|
return new Response(JSON.stringify(data), {
|
|
status,
|
|
headers: {
|
|
'Content-Type': 'application/json; charset=utf-8',
|
|
...corsHeaders(request, env),
|
|
},
|
|
});
|
|
}
|