| import Anthropic from "@anthropic-ai/sdk"; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| const NIM_CHAT_URL = "https://integrate.api.nvidia.com/v1/chat/completions"; |
| const DEFAULT_NIM_MODEL = "nvidia/nemotron-3-ultra-550b-a55b"; |
| const DEFAULT_ANTHROPIC_MODEL = "claude-sonnet-5"; |
|
|
| export const MISSING_KEY_MESSAGE = |
| "No model API key configured on the server. Add NGC_API_KEY (NVIDIA NIM, from build.nvidia.com) or ANTHROPIC_API_KEY as a repository secret to enable this."; |
|
|
| export type ChatMessage = { role: "user" | "assistant"; content: string }; |
|
|
| export type ToolSchema = { |
| name: string; |
| description: string; |
| input_schema: Record<string, unknown>; |
| }; |
|
|
| export type ChatResult = { text?: string; toolInput?: unknown }; |
|
|
| async function callNim( |
| apiKey: string, |
| model: string, |
| system: string, |
| messages: ChatMessage[], |
| maxTokens: number, |
| tool?: ToolSchema, |
| ): Promise<ChatResult> { |
| const body: Record<string, unknown> = { |
| model, |
| max_tokens: maxTokens, |
| messages: [{ role: "system", content: system }, ...messages], |
| }; |
|
|
| if (tool) { |
| body.tools = [ |
| { type: "function", function: { name: tool.name, description: tool.description, parameters: tool.input_schema } }, |
| ]; |
| body.tool_choice = { type: "function", function: { name: tool.name } }; |
| } |
|
|
| const res = await fetch(NIM_CHAT_URL, { |
| method: "POST", |
| headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" }, |
| body: JSON.stringify(body), |
| }); |
|
|
| if (!res.ok) { |
| const errText = await res.text().catch(() => res.statusText); |
| throw new Error(`NVIDIA NIM request failed (${res.status}): ${errText.slice(0, 500)}`); |
| } |
|
|
| const data = await res.json(); |
| const choice = data.choices?.[0]?.message; |
| if (!choice) throw new Error("NVIDIA NIM returned no choices."); |
|
|
| const toolCall = choice.tool_calls?.[0]; |
| if (toolCall) { |
| try { |
| return { toolInput: JSON.parse(toolCall.function.arguments) }; |
| } catch { |
| throw new Error("NVIDIA NIM returned a tool call with unparseable arguments."); |
| } |
| } |
|
|
| return { text: choice.content ?? "" }; |
| } |
|
|
| async function callAnthropic( |
| apiKey: string, |
| model: string, |
| system: string, |
| messages: ChatMessage[], |
| maxTokens: number, |
| tool?: ToolSchema, |
| ): Promise<ChatResult> { |
| const client = new Anthropic({ apiKey }); |
| const response = await client.messages.create({ |
| model, |
| max_tokens: maxTokens, |
| system, |
| messages, |
| ...(tool |
| ? { |
| tools: [tool as unknown as Anthropic.Tool], |
| tool_choice: { type: "tool" as const, name: tool.name }, |
| } |
| : {}), |
| }); |
|
|
| if (tool) { |
| const toolUse = response.content.find( |
| (b): b is Anthropic.ToolUseBlock => b.type === "tool_use", |
| ); |
| if (!toolUse) throw new Error("Model did not return a structured result."); |
| return { toolInput: toolUse.input }; |
| } |
|
|
| const text = response.content |
| .filter((b): b is Anthropic.TextBlock => b.type === "text") |
| .map((b) => b.text) |
| .join("\n"); |
| return { text }; |
| } |
|
|
| export async function callChatModel(opts: { |
| system: string; |
| messages: ChatMessage[]; |
| maxTokens: number; |
| tool?: ToolSchema; |
| }): Promise<ChatResult> { |
| const ngcKey = process.env.NGC_API_KEY; |
| if (ngcKey) { |
| const model = process.env.NGC_MODEL || DEFAULT_NIM_MODEL; |
| return callNim(ngcKey, model, opts.system, opts.messages, opts.maxTokens, opts.tool); |
| } |
|
|
| const anthropicKey = process.env.ANTHROPIC_API_KEY; |
| if (anthropicKey) { |
| const model = process.env.ANTHROPIC_MODEL || DEFAULT_ANTHROPIC_MODEL; |
| return callAnthropic(anthropicKey, model, opts.system, opts.messages, opts.maxTokens, opts.tool); |
| } |
|
|
| throw new Error(MISSING_KEY_MESSAGE); |
| } |
|
|
| export function hasModelKey(): boolean { |
| return Boolean(process.env.NGC_API_KEY || process.env.ANTHROPIC_API_KEY); |
| } |
|
|