File size: 3,471 Bytes
60faa09
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/**
 * Provider registry — single entry point for the messages route to call
 * any LLM backend. Picks the right adapter based on the model id prefix
 * and resolves the correct API key (user's saved key OR server env var).
 */
import { getModelMeta } from "./models";
import { callOpenAI } from "./openai";
import { callAnthropic } from "./anthropic";
import { callHuggingFace } from "./huggingface";
import { callGroq } from "./groq";
import { callMuAPI } from "./muapi";
import { callGradio } from "./gradio";

export function resolveApiKey(provider, opts = {}) {
  const { customApiKey, hfAccessToken } = opts;
  const envMap = {
    openai: "OPENAI_API_KEY",
    anthropic: "ANTHROPIC_API_KEY",
    huggingface: "HF_TOKEN",
    groq: "GROQ_API_KEY",
    muapi: "MU_API_KEY",
  };

  if (customApiKey) {
    const k = customApiKey.trim();
    if (provider === "openai" && k.startsWith("sk-")) return k;
    if (provider === "anthropic" && k.startsWith("sk-ant")) return k;
    if (provider === "groq" && k.startsWith("gsk_")) return k;
    if (provider === "huggingface" && k.startsWith("hf_")) return k;
    if (provider === "muapi" && k.startsWith("mu_")) return k;
    if (!envMap[provider]) return k;
  }

  if (provider === "huggingface" || provider === "gradio") {
    if (hfAccessToken?.startsWith("hf_")) return hfAccessToken;
  }

  const envVar = envMap[provider];
  if (envVar) return process.env[envVar] || null;

  return null;
}

export async function callLLM(opts) {
  const {
    model, messages, systemPrompt = "", temperature = 1.0, maxTokens = 2048,
    reasoning = false, stream = false, onToken, customApiKey, hfAccessToken,
  } = opts;

  const meta = getModelMeta(model);
  if (!meta) throw new Error(`Unknown model: ${model}`);
  const provider = meta.provider;
  const apiKey = resolveApiKey(provider, { customApiKey, hfAccessToken });

  if (provider === "gradio") {
    const lastUser = [...messages].reverse().find((m) => m.role === "user");
    return callGradio({
      model,
      prompt: lastUser?.content || "",
      systemPrompt,
      history: messages,
      apiKey,
    });
  }

  if (provider === "muapi") {
    if (!apiKey) throw new Error("No API key available for MuAPI. Set MU_API_KEY env var or save a personal MuAPI key.");
    const lastUser = [...messages].reverse().find((m) => m.role === "user");
    return callMuAPI({
      model, prompt: lastUser?.content || "", systemPrompt,
      temperature, maxTokens, reasoning,
      imageUrl: lastUser?.imageUrl, apiKey,
    });
  }

  if (!apiKey) {
    throw new Error(`No API key available for ${provider}. Set the matching env var or save a personal key.`);
  }

  const fullMessages = systemPrompt
    ? [{ role: "system", content: systemPrompt }, ...messages]
    : messages;

  switch (provider) {
    case "openai":
      return callOpenAI({ model, messages: fullMessages, temperature, maxTokens, reasoning, stream, apiKey, onToken });
    case "anthropic":
      return callAnthropic({ model, messages, system: systemPrompt, temperature, maxTokens, stream, apiKey, onToken });
    case "huggingface":
      return callHuggingFace({ model, messages: fullMessages, temperature, maxTokens, stream, apiKey, onToken });
    case "groq":
      return callGroq({ model, messages: fullMessages, temperature, maxTokens, reasoning, stream, apiKey, onToken });
    default:
      throw new Error(`Provider ${provider} is not implemented.`);
  }
}

export { getModelMeta };