| |
| |
| |
| import { stripReasoning } from "./model-prompt.mjs"; |
|
|
| const JSON_GRAMMAR = `root ::= "{" ws members? "}" ws |
| members ::= pair ("," ws pair)* |
| pair ::= string ws ":" ws value |
| value ::= object | array | string | number | "true" | "false" | "null" |
| object ::= "{" ws members? "}" ws |
| array ::= "[" ws (value ("," ws value)*)? "]" ws |
| string ::= "\\"" ( [^"\\\\] | "\\\\" ["\\\\/bfnrt] )* "\\"" ws |
| number ::= "-"? ([0-9] | [1-9] [0-9]*) ("." [0-9]+)? ws |
| ws ::= [ \\t\\n]*`; |
|
|
| export function resolveActiveProvider(env = process.env) { |
| const raw = (env.AGENT_MODEL_PROVIDER || env.AGENT_PROVIDER || "auto").toLowerCase(); |
| const has = { |
| nvidia: Boolean(env.NVIDIA_API_KEY || env.NGC_API_KEY), |
| llamacpp: env.LLAMACPP_ENABLED === "1", |
| hf: Boolean(env.HF_TOKEN || env.HUGGINGFACEHUB_API_TOKEN || env.HUGGING_FACE_HUB_TOKEN), |
| }; |
| if (raw !== "auto" && raw !== "") return has[raw] ? raw : firstAvailable(has); |
| return firstAvailable(has); |
| } |
|
|
| function firstAvailable(has) { |
| if (has.nvidia) return "nvidia"; |
| if (has.llamacpp) return "llamacpp"; |
| if (has.hf) return "hf"; |
| return null; |
| } |
|
|
| function availability(env) { |
| return { |
| nvidia: Boolean(env.NVIDIA_API_KEY || env.NGC_API_KEY), |
| llamacpp: env.LLAMACPP_ENABLED === "1", |
| hf: Boolean(env.HF_TOKEN || env.HUGGINGFACEHUB_API_TOKEN || env.HUGGING_FACE_HUB_TOKEN), |
| }; |
| } |
|
|
| |
| |
| |
| export function resolveMainProvider(env = process.env) { |
| const has = availability(env); |
| const raw = (env.AGENT_MODEL_PROVIDER || env.AGENT_PROVIDER || "auto").toLowerCase(); |
| if (raw !== "auto" && raw !== "" && has[raw]) return raw; |
| if (has.nvidia) return "nvidia"; |
| if (has.hf) return "hf"; |
| if (has.llamacpp) return "llamacpp"; |
| return null; |
| } |
|
|
| |
| |
| |
| export function resolveSummarizerProvider(env = process.env) { |
| const has = availability(env); |
| const raw = (env.AGENT_SUMMARIZER_PROVIDER || "llamacpp").toLowerCase(); |
| if (raw && raw !== "auto" && has[raw]) return raw; |
| if (has.llamacpp) return "llamacpp"; |
| return resolveMainProvider(env); |
| } |
|
|
| function endpointFor(provider, env) { |
| if (provider === "nvidia") { |
| const base = env.NVIDIA_BASE_URL || "https://integrate.api.nvidia.com/v1"; |
| return { |
| url: env.NVIDIA_CHAT_COMPLETIONS_URL || `${base.replace(/\/$/, "")}/chat/completions`, |
| model: env.NVIDIA_MODEL || "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning", |
| auth: env.NVIDIA_API_KEY || env.NGC_API_KEY, |
| }; |
| } |
| if (provider === "llamacpp") { |
| const base = env.LLAMACPP_BASE_URL || "http://127.0.0.1:8080/v1"; |
| return { |
| url: env.LLAMACPP_CHAT_COMPLETIONS_URL || `${base.replace(/\/$/, "")}/chat/completions`, |
| model: env.LLAMACPP_MODEL || "local-gguf", |
| auth: env.LLAMACPP_API_KEY || "", |
| grammar: true, |
| }; |
| } |
| return { |
| url: env.HF_CHAT_COMPLETIONS_URL || "https://router.huggingface.co/v1/chat/completions", |
| model: env.HF_MODEL || "Qwen/Qwen3-Coder-30B-A3B-Instruct", |
| auth: env.HF_TOKEN || env.HUGGINGFACEHUB_API_TOKEN || env.HUGGING_FACE_HUB_TOKEN, |
| }; |
| } |
|
|
| |
| |
| export async function agenticChat(messages, env = process.env, opts = {}) { |
| const provider = opts.provider || resolveActiveProvider(env); |
| if (!provider) return null; |
| const ep = endpointFor(provider, env); |
| if (!ep.url) return null; |
|
|
| const payload = { |
| model: ep.model, |
| messages, |
| temperature: Number(opts.temperature ?? (provider === "nvidia" ? 0.6 : 0.2)), |
| max_tokens: Number(opts.maxTokens ?? 220), |
| stream: false, |
| }; |
| if (opts.json) { |
| if (ep.grammar) payload.grammar = JSON_GRAMMAR; |
| else payload.response_format = { type: "json_object" }; |
| } |
| |
| |
| if (provider === "nvidia") { |
| payload.top_p = Number(env.NVIDIA_TOP_P ?? 0.95); |
| const thinking = Boolean(opts.thinking); |
| payload.chat_template_kwargs = { enable_thinking: thinking }; |
| if (thinking) { |
| payload.reasoning_budget = Number(env.NVIDIA_REASONING_BUDGET ?? 4096); |
| payload.max_tokens = Number(opts.maxTokens ?? 4096) + payload.reasoning_budget; |
| } |
| } |
|
|
| try { |
| const response = await fetch(ep.url, { |
| method: "POST", |
| headers: { |
| ...(ep.auth ? { Authorization: `Bearer ${ep.auth}` } : {}), |
| "Content-Type": "application/json", |
| }, |
| body: JSON.stringify(payload), |
| signal: AbortSignal.timeout(Number(opts.timeoutMs ?? env.AGENT_CHAT_TIMEOUT_MS ?? 60000)), |
| }); |
| if (!response.ok) return null; |
| const data = await response.json(); |
| const content = stripReasoning(data?.choices?.[0]?.message?.content); |
| return content |
| ? { provider, model: ep.model, content } |
| : null; |
| } catch { |
| return null; |
| } |
| } |
|
|