|
import { serve } from "https://deno.land/[email protected]/http/server.ts"; |
|
|
|
|
|
const JULEP_API_BASE = "https://api.julep.ai/api"; |
|
|
|
|
|
const HARDCODED_MODELS = [ |
|
'mistral-large-2411', 'o1', 'text-embedding-3-large', 'vertex_ai/text-embedding-004', |
|
'claude-3.5-haiku', 'cerebras/llama-4-scout-17b-16e-instruct', 'llama-3.1-8b', |
|
'magnum-v4-72b', 'voyage-multilingual-2', 'claude-3-haiku', 'gpt-4o', |
|
'BAAI/bge-m3', 'openrouter/meta-llama/llama-4-maverick', 'openrouter/meta-llama/llama-4-scout', |
|
'claude-3.5-sonnet', 'hermes-3-llama-3.1-70b', 'claude-3.5-sonnet-20240620', |
|
'qwen-2.5-72b-instruct', 'l3.3-euryale-70b', 'gpt-4o-mini', 'cerebras/llama-3.3-70b', |
|
'o1-preview', 'gemini-1.5-pro-latest', 'l3.1-euryale-70b', 'claude-3-sonnet', |
|
'Alibaba-NLP/gte-large-en-v1.5', 'openrouter/meta-llama/llama-4-scout:free', |
|
'llama-3.1-70b', 'eva-qwen-2.5-72b', 'claude-3.5-sonnet-20241022', 'gemini-2.0-flash', |
|
'deepseek-chat', 'o1-mini', 'eva-llama-3.33-70b', 'gemini-2.5-pro-preview-03-25', |
|
'gemini-1.5-pro', 'gpt-4-turbo', 'openrouter/meta-llama/llama-4-maverick:free', |
|
'o3-mini', 'claude-3.7-sonnet', 'voyage-3', 'cerebras/llama-3.1-8b', 'claude-3-opus' |
|
]; |
|
|
|
|
|
function getJulepApiKey(req: Request): string | null { |
|
const authHeader = req.headers.get("Authorization"); |
|
if (authHeader && authHeader.startsWith("Bearer ")) { |
|
return authHeader.substring(7); |
|
} |
|
return null; |
|
} |
|
|
|
|
|
async function handleModels(req: Request): Promise<Response> { |
|
const julepApiKey = getJulepApiKey(req); |
|
if (!julepApiKey) { |
|
return new Response("Unauthorized: Missing or invalid Authorization header", { status: 401 }); |
|
} |
|
|
|
|
|
const openaiModels = HARDCODED_MODELS.map((modelId) => ({ |
|
id: modelId, |
|
object: "model", |
|
created: Math.floor(Date.now() / 1000), |
|
owned_by: "julep", |
|
permission: [ |
|
{ |
|
id: `modelperm-${modelId}`, |
|
object: "model_permission", |
|
created: Math.floor(Date.now() / 1000), |
|
allow_create_engine: false, |
|
allow_sampling: true, |
|
allow_logprobs: true, |
|
allow_search_indices: false, |
|
allow_view: true, |
|
allow_fine_tuning: false, |
|
organization: "*", |
|
group: null, |
|
is_blocking: false, |
|
}, |
|
], |
|
root: modelId, |
|
parent: null, |
|
})); |
|
|
|
return new Response(JSON.stringify({ data: openaiModels, object: "list" }), { |
|
headers: { "Content-Type": "application/json" }, |
|
status: 200, |
|
}); |
|
} |
|
|
|
|
|
async function handleChatCompletions(req: Request): Promise<Response> { |
|
const julepApiKey = getJulepApiKey(req); |
|
if (!julepApiKey) { |
|
return new Response("Unauthorized: Missing or invalid Authorization header", { status: 401 }); |
|
} |
|
|
|
const headers = { |
|
"Authorization": `Bearer ${julepApiKey}`, |
|
"Content-Type": "application/json", |
|
}; |
|
|
|
let agentId: string | null = null; |
|
let sessionId: string | null = null; |
|
|
|
try { |
|
const requestBody = await req.json(); |
|
const { model, messages, stream, ...rest } = requestBody; |
|
|
|
if (!model || !messages || !Array.isArray(messages) || messages.length === 0) { |
|
return new Response("Invalid request body. 'model' and 'messages' are required.", { status: 400 }); |
|
} |
|
|
|
|
|
if (!HARDCODED_MODELS.includes(model)) { |
|
return new Response(`Invalid model: ${model}. Please use one of the available models.`, { status: 400 }); |
|
} |
|
|
|
|
|
const createAgentUrl = `${JULEP_API_BASE}/agents`; |
|
const createAgentBody = { |
|
name: model, |
|
model: model, |
|
about: model, |
|
instructions: ["Follow user instructions carefully."], |
|
}; |
|
|
|
const createAgentResponse = await fetch(createAgentUrl, { |
|
method: "POST", |
|
headers, |
|
body: JSON.stringify(createAgentBody), |
|
}); |
|
|
|
if (!createAgentResponse.ok) { |
|
const errorText = await createAgentResponse.text(); |
|
console.error(`Error creating Julep Agent: ${createAgentResponse.status} - ${errorText}`); |
|
return new Response(`Error creating Julep Agent: ${createAgentResponse.statusText}`, { status: createAgentResponse.status }); |
|
} |
|
|
|
const agentData = await createAgentResponse.json(); |
|
agentId = agentData.id; |
|
|
|
|
|
const createSessionUrl = `${JULEP_API_BASE}/sessions`; |
|
const createSessionBody = { |
|
agent: agentId, |
|
|
|
}; |
|
|
|
const createSessionResponse = await fetch(createSessionUrl, { |
|
method: "POST", |
|
headers, |
|
body: JSON.stringify(createSessionBody), |
|
}); |
|
|
|
if (!createSessionResponse.ok) { |
|
const errorText = await createSessionResponse.text(); |
|
console.error(`Error creating Julep Session: ${createSessionResponse.status} - ${errorText}`); |
|
|
|
if (agentId) { |
|
fetch(`${JULEP_API_BASE}/agents/${agentId}`, { method: "DELETE", headers }).catch(console.error); |
|
} |
|
return new Response(`Error creating Julep Session: ${createSessionResponse.statusText}`, { status: createSessionResponse.status }); |
|
} |
|
|
|
const sessionData = await createSessionResponse.json(); |
|
sessionId = sessionData.id; |
|
|
|
|
|
const chatUrl = `${JULEP_API_BASE}/sessions/${sessionId}/chat`; |
|
const chatBody = { |
|
messages: messages.map((msg: any) => ({ |
|
role: msg.role, |
|
content: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content), |
|
|
|
})), |
|
stream: stream === true, |
|
...rest, |
|
}; |
|
|
|
const chatResponse = await fetch(chatUrl, { |
|
method: "POST", |
|
headers, |
|
body: JSON.stringify(chatBody), |
|
}); |
|
|
|
|
|
if (!chatResponse.ok) { |
|
|
|
const errorText = await chatResponse.text(); |
|
console.error(`Error during Julep Chat Completion: ${chatResponse.status} - ${errorText}`); |
|
|
|
if (sessionId) { |
|
fetch(`${JULEP_API_BASE}/sessions/${sessionId}`, { method: "DELETE", headers }).catch(console.error); |
|
} |
|
if (agentId) { |
|
fetch(`${JULEP_API_BASE}/agents/${agentId}`, { method: "DELETE", headers }).catch(console.error); |
|
} |
|
return new Response(`Error during Julep Chat Completion: ${chatResponse.statusText} - ${errorText}`, { status: chatResponse.status }); |
|
} |
|
|
|
if (stream) { |
|
|
|
|
|
|
|
const readableStream = chatResponse.body!.pipeThrough(new TextDecoderStream()).pipeThrough(new TransformStream({ |
|
transform(chunk, controller) { |
|
|
|
const lines = chunk.split('\n').filter(line => line.trim() !== ''); |
|
for (const line of lines) { |
|
if (line.startsWith('data:')) { |
|
const data = JSON.parse(line.substring(5).trim()); |
|
|
|
const openaiChunk = { |
|
id: data.id, |
|
object: "chat.completion.chunk", |
|
created: Math.floor(new Date(data.created_at).getTime() / 1000), |
|
model: model, |
|
choices: data.choices.map((choice: any) => ({ |
|
index: choice.index, |
|
delta: { |
|
role: choice.delta.role, |
|
content: choice.delta.content, |
|
tool_calls: choice.delta.tool_calls ? toolCallDeltaToOpenAI(choice.delta.tool_calls) : undefined, |
|
}, |
|
finish_reason: choice.finish_reason, |
|
})), |
|
}; |
|
controller.enqueue(`data: ${JSON.stringify(openaiChunk)}\n\n`); |
|
} else { |
|
|
|
controller.enqueue(`${line}\n`); |
|
} |
|
} |
|
}, |
|
})); |
|
|
|
|
|
|
|
|
|
const [stream1, stream2] = readableStream.tee(); |
|
|
|
const cleanupPromise = new Promise<void>((resolve, reject) => { |
|
stream2.pipeTo(new WritableStream({ |
|
close: () => { |
|
if (sessionId) { |
|
fetch(`${JULEP_API_BASE}/sessions/${sessionId}`, { method: "DELETE", headers }).catch(console.error); |
|
} |
|
if (agentId) { |
|
fetch(`${JULEP_API_BASE}/agents/${agentId}`, { method: "DELETE", headers }).catch(console.error); |
|
} |
|
resolve(); |
|
}, |
|
abort: (reason) => { |
|
console.error("Stream aborted:", reason); |
|
if (sessionId) { |
|
fetch(`${JULEP_API_BASE}/sessions/${sessionId}`, { method: "DELETE", headers }).catch(console.error); |
|
} |
|
if (agentId) { |
|
fetch(`${JULEP_API_BASE}/agents/${agentId}`, { method: "DELETE", headers }).catch(console.error); |
|
} |
|
reject(reason); |
|
} |
|
})).catch(reject); |
|
}); |
|
|
|
|
|
return new Response(stream1, { |
|
headers: { |
|
"Content-Type": "text/event-stream", |
|
"Cache-Control": "no-cache", |
|
"Connection": "keep-alive", |
|
}, |
|
status: 200, |
|
}); |
|
|
|
} else { |
|
|
|
const julepChatData = await chatResponse.json(); |
|
|
|
const openaiCompletion = { |
|
id: julepChatData.id, |
|
object: "chat.completion", |
|
created: Math.floor(new Date(julepChatData.created_at).getTime() / 1000), |
|
model: model, |
|
choices: julepChatData.choices.map((choice: any) => ({ |
|
index: choice.index, |
|
message: { |
|
role: choice.message.role, |
|
content: choice.message.content, |
|
tool_calls: choice.message.tool_calls ? toolCallMessageToOpenAI(choice.message.tool_calls) : undefined, |
|
}, |
|
finish_reason: choice.finish_reason, |
|
})), |
|
usage: julepChatData.usage ? { |
|
prompt_tokens: julepChatData.usage.prompt_tokens, |
|
completion_tokens: julepChatData.usage.completion_tokens, |
|
total_tokens: julepChatData.usage.total_tokens, |
|
} : undefined, |
|
}; |
|
|
|
|
|
if (sessionId) { |
|
fetch(`${JULEP_API_BASE}/sessions/${sessionId}`, { method: "DELETE", headers }).catch(console.error); |
|
} |
|
if (agentId) { |
|
fetch(`${JULEP_API_BASE}/agents/${agentId}`, { method: "DELETE", headers }).catch(console.error); |
|
} |
|
|
|
return new Response(JSON.stringify(openaiCompletion), { |
|
headers: { "Content-Type": "application/json" }, |
|
status: 200, |
|
}); |
|
} |
|
|
|
} catch (error) { |
|
console.error("Error handling chat completions request:", error); |
|
|
|
if (sessionId) { |
|
fetch(`${JULEP_API_BASE}/sessions/${sessionId}`, { method: "DELETE", headers }).catch(console.error); |
|
} |
|
if (agentId) { |
|
fetch(`${Julep_API_BASE}/agents/${agentId}`, { method: "DELETE", headers }).catch(console.error); |
|
} |
|
return new Response("Internal Server Error", { status: 500 }); |
|
} |
|
} |
|
|
|
|
|
function toolCallDeltaToOpenAI(julepToolCalls: any[]): any[] { |
|
return julepToolCalls.map(toolCall => { |
|
|
|
|
|
return { |
|
id: toolCall.id, |
|
type: "function", |
|
function: { |
|
name: toolCall.function?.name, |
|
arguments: toolCall.function?.arguments, |
|
}, |
|
}; |
|
}); |
|
} |
|
|
|
|
|
function toolCallMessageToOpenAI(julepToolCalls: any[]): any[] { |
|
return julepToolCalls.map(toolCall => { |
|
return { |
|
id: toolCall.id, |
|
type: "function", |
|
function: { |
|
name: toolCall.function?.name, |
|
arguments: toolCall.function?.arguments, |
|
}, |
|
}; |
|
}); |
|
} |
|
|
|
|
|
async function handler(req: Request): Promise<Response> { |
|
const url = new URL(req.url); |
|
|
|
if (url.pathname === "/v1/models" && req.method === "GET") { |
|
return handleModels(req); |
|
} else if (url.pathname === "/v1/chat/completions" && req.method === "POST") { |
|
return handleChatCompletions(req); |
|
} else { |
|
return new Response("Not Found", { status: 404 }); |
|
} |
|
} |
|
|
|
console.log(`HTTP server running on http://localhost:8000`); |
|
serve(handler, { port: 7860 }); |