UNPKG

jorel

Version:

A unified wrapper for working with LLMs from multiple providers, including streams, images, documents & automatic tool use.

260 lines (259 loc) 10.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.OpenRouterProviderNative = void 0; const sdk_1 = require("@openrouter/sdk"); const providers_1 = require("../../providers"); const shared_1 = require("../../shared"); const tools_1 = require("../../tools"); const convert_inputs_1 = require("./convert-inputs"); const convert_llm_message_1 = require("./convert-llm-message"); /** * Provides access to OpenRouter using their native SDK */ class OpenRouterProviderNative { constructor(config) { this.name = config?.name || OpenRouterProviderNative.defaultName; this.client = new sdk_1.OpenRouter({ apiKey: config?.apiKey || process.env.OPEN_ROUTER_API_KEY, }); } async generateResponse(model, messages, config = {}) { const start = Date.now(); const temperature = config.temperature ?? undefined; // Convert LlmMessage[] to OpenRouter format const openRouterMessages = await (0, convert_llm_message_1.convertLlmMessagesToOpenRouterMessages)(messages); let response; try { response = await this.client.chat.send({ model, messages: openRouterMessages, temperature, maxTokens: config.maxTokens, responseFormat: (0, convert_inputs_1.jsonResponseToOpenRouter)(config.json, config.jsonDescription), tools: config.tools?.asLlmFunctions, toolChoice: (0, convert_inputs_1.toolChoiceToOpenRouter)(config.toolChoice), reasoning: (0, convert_inputs_1.reasoningToOpenRouter)(config.reasoningEffort ?? undefined, config.reasoningSummaryVerbosity ?? undefined), stream: false, maxCompletionTokens: config.maxCompletionTokens, }, { signal: config.abortSignal, }); } catch (error) { if (error instanceof Error && error.message.toLowerCase().includes("aborted")) { throw new shared_1.JorElAbortError("Request was aborted"); } throw error; } const durationMs = Date.now() - start; const inputTokens = response.usage?.promptTokens; const outputTokens = response.usage?.completionTokens; const reasoningTokens = response.usage?.completionTokensDetails?.reasoningTokens ?? undefined; const message = response.choices[0].message; const toolCalls = message.toolCalls?.map((call) => { return { id: (0, shared_1.generateUniqueId)(), request: { id: call.id, function: { name: call.function.name, arguments: tools_1.LlmToolKit.deserialize(call.function.arguments), }, }, approvalState: config.tools?.getTool(call.function.name)?.requiresConfirmation ? "requiresApproval" : "noApprovalRequired", executionState: "pending", result: null, error: null, }; }); const provider = this.name; // Extract text content (handles both string and array formats) const textContent = (0, convert_llm_message_1.extractTextContent)(message.content); const reasoningContent = message.reasoning ?? null; return { ...(0, providers_1.generateAssistantMessage)(textContent, reasoningContent, toolCalls), meta: { model, provider, temperature, durationMs, inputTokens: inputTokens ?? undefined, outputTokens: outputTokens ?? undefined, reasoningTokens: reasoningTokens ?? undefined, }, }; } async *generateResponseStream(model, messages, config = {}) { const start = Date.now(); const temperature = config.temperature ?? undefined; // Convert LlmMessage[] to OpenRouter format const openRouterMessages = await (0, convert_llm_message_1.convertLlmMessagesToOpenRouterMessages)(messages); let stream; try { stream = await this.client.chat.send({ model, messages: openRouterMessages, temperature, maxTokens: config.maxTokens, responseFormat: (0, convert_inputs_1.jsonResponseToOpenRouter)(config.json, config.jsonDescription), tools: config.tools?.asLlmFunctions, toolChoice: (0, convert_inputs_1.toolChoiceToOpenRouter)(config.toolChoice), reasoning: (0, convert_inputs_1.reasoningToOpenRouter)(config.reasoningEffort ?? undefined, config.reasoningSummaryVerbosity ?? undefined), stream: true, maxCompletionTokens: config.maxCompletionTokens, streamOptions: { includeUsage: true, }, }, { signal: config.abortSignal, }); } catch (error) { if (error instanceof Error && error.message.toLowerCase().includes("aborted")) { throw new shared_1.JorElAbortError("Request was aborted"); } throw error; } let inputTokens; let outputTokens; let reasoningTokens; const _toolCalls = []; let content = ""; let reasoningContent = ""; for await (const chunk of stream) { const delta = (0, shared_1.firstEntry)(chunk.choices)?.delta; if (delta?.content) { content += delta.content; const chunkId = (0, shared_1.generateUniqueId)(); yield { type: "chunk", content: delta.content, chunkId }; } if (delta?.reasoning) { reasoningContent += delta.reasoning; const chunkId = (0, shared_1.generateUniqueId)(); yield { type: "reasoningChunk", content: delta.reasoning, chunkId }; } if (delta?.toolCalls) { for (const toolCall of delta.toolCalls) { const _toolCall = _toolCalls[toolCall.index] || { id: "", function: { name: "", arguments: "" } }; if (toolCall.id) _toolCall.id += toolCall.id; if (toolCall.function) { if (toolCall.function.name) _toolCall.function.name += toolCall.function.name; if (toolCall.function.arguments) _toolCall.function.arguments += toolCall.function.arguments; } _toolCalls[toolCall.index] = _toolCall; } } if (chunk.usage) { inputTokens = (inputTokens || 0) + (chunk.usage?.promptTokens ?? 0); outputTokens = (outputTokens || 0) + (chunk.usage?.completionTokens ?? 0); reasoningTokens = (reasoningTokens || 0) + (chunk.usage?.completionTokensDetails?.reasoningTokens ?? 0); } } const durationMs = Date.now() - start; const provider = this.name; const toolCalls = _toolCalls.map((call) => { let parsedArgs = null; let parseError = null; try { parsedArgs = tools_1.LlmToolKit.deserialize(call.function.arguments); } catch (e) { parseError = e instanceof Error ? e : new Error("Unable to parse tool call arguments"); } const approvalState = config.tools?.getTool(call.function.name) ?.requiresConfirmation ? "requiresApproval" : "noApprovalRequired"; const base = { id: (0, shared_1.generateUniqueId)(), request: { id: call.id, function: { name: call.function.name, arguments: parsedArgs ?? {}, }, }, approvalState, }; if (parseError) { return { ...base, executionState: "error", result: null, error: { type: parseError.name || "ToolArgumentParseError", message: parseError.message || "Invalid tool call arguments", numberOfAttempts: 1, lastAttempt: new Date(), }, }; } return { ...base, executionState: "pending", result: null, error: null, }; }); const meta = { model, provider, temperature, durationMs, inputTokens: inputTokens ?? undefined, outputTokens: outputTokens ?? undefined, reasoningTokens: reasoningTokens ?? undefined, }; if (_toolCalls.length > 0) { yield { type: "response", role: "assistant_with_tools", content, reasoningContent, toolCalls, meta, }; } else { yield { type: "response", role: "assistant", content, reasoningContent, meta, }; } } async getAvailableModels() { const response = await this.client.models.list(); return response.data.map((model) => model.id); } // eslint-disable-next-line @typescript-eslint/no-unused-vars async createEmbedding(model, text, abortSignal) { // OpenRouter SDK doesn't currently support abort signals for embeddings const response = await this.client.embeddings.generate({ model, input: text, }); if (typeof response === "string") { throw new Error("Received unexpected string response from embeddings endpoint"); } if (!response || !response.data || response.data.length === 0) { throw new Error("Failed to create embedding"); } const embedding = response.data[0].embedding; if (typeof embedding === "string") { const buffer = Buffer.from(embedding, "base64"); const floatArray = new Float32Array(buffer.buffer); return Array.from(floatArray); } return embedding; } } exports.OpenRouterProviderNative = OpenRouterProviderNative; OpenRouterProviderNative.defaultName = "open-router";