askexperts
Version:
AskExperts SDK: build and use AI experts - ask them questions and pay with bitcoin on an open protocol
193 lines • 6.75 kB
JavaScript
import { debugExpert, debugError } from "../../common/debug.js";
import OpenAI from "openai";
/**
* OpenRouter API client
* Provides access to OpenRouter models and pricing
* Implements ModelPricing interface
*/
export class OpenRouter {
/**
* Gets all available models
* Updates the cache if it's older than the expiry time
*
* @returns Array of models
*/
async list() {
// Update cache if needed
await this.updateCacheIfNeeded();
// Return all models
return [...this.models];
}
/**
* Creates a new OpenRouter instance
*/
constructor() {
/**
* Cached models from OpenRouter API
*/
this.models = [];
/**
* Last time models were fetched
*/
this.lastFetchTime = 0;
/**
* Cache expiry time in milliseconds (1 hour)
*/
this.cacheExpiryTime = 60 * 60 * 1000;
// No parameters needed
}
/**
* Gets a model by name (id)
* Updates the cache if it's older than 1 hour
*
* @param name - Model ID
* @returns Model information or undefined if not found
*/
async model(name) {
// Update cache if needed
await this.updateCacheIfNeeded();
// Find the model
return this.models.find((m) => m.id === name);
}
/**
* Gets pricing information for a model in sats per million tokens
* Implements ModelPricing interface
*
* @param name - Model ID
* @returns Promise resolving to pricing information
* @throws Error if pricing information cannot be retrieved
*/
async pricing(name) {
try {
// Get the model
const modelInfo = await this.model(name);
if (!modelInfo) {
debugError(`Model ${name} not found in OpenRouter API`);
throw new Error(`Model ${name} not found in OpenRouter API`);
}
// Get the pricing
const inputPriceUsd = modelInfo.pricing.prompt;
const outputPriceUsd = modelInfo.pricing.completion;
if (!inputPriceUsd || !outputPriceUsd) {
debugError("Failed to get valid pricing");
throw new Error("Failed to get valid pricing");
}
debugExpert(`Fetched USD prices from OpenRouter: input=${inputPriceUsd} usd/token, output=${outputPriceUsd} usd/token`);
// If BTC/USD rate is not set, fetch it
if (!this.btcUsd) {
await this.fetchBtcUsdRate();
}
if (!this.btcUsd) {
debugError("Failed to fetch BTC/USD rate");
throw new Error("Failed to fetch BTC/USD rate");
}
// Convert USD prices per token to sats per million tokens
// 1 BTC = 100,000,000 sats, so sats = usd * 100,000,000 / btcUsd * 1,000,000,
// add base price of 1 sat.
const inputTokenPPM = Math.ceil(1 + ((inputPriceUsd * 100000000) / this.btcUsd) * 1000000);
const outputTokenPPM = Math.ceil(1 + ((outputPriceUsd * 100000000) / this.btcUsd) * 1000000);
debugExpert(`Calculated prices: input=${inputTokenPPM} sats/M, output=${outputTokenPPM} sats/M`);
return {
inputPricePPM: inputTokenPPM,
outputPricePPM: outputTokenPPM,
};
}
catch (error) {
debugError("Error calculating pricing:", error);
throw error;
}
}
/**
* Force a model cache update
*/
async update() {
await this.fetchModels();
}
/**
* Updates the model cache if it's older than the expiry time
*/
async updateCacheIfNeeded() {
const now = Date.now();
if (now - this.lastFetchTime > this.cacheExpiryTime ||
this.models.length === 0) {
await this.fetchModels();
}
}
/**
* Fetches models from OpenRouter API
*/
async fetchModels() {
try {
debugExpert("Fetching models from OpenRouter API");
const response = await fetch("https://openrouter.ai/api/v1/models");
if (!response.ok) {
throw new Error(`Failed to fetch models: ${response.statusText}`);
}
const data = await response.json();
// ignore BYOK models
this.models = data.data.filter((m) => !m.description.includes("BYOK"));
this.lastFetchTime = Date.now();
debugExpert(`Fetched ${this.models.length} models from OpenRouter API`);
}
catch (error) {
debugError("Error fetching models:", error);
throw error;
}
}
/**
* Fetches the current BTC/USD exchange rate from Binance
*/
async fetchBtcUsdRate() {
try {
const response = await fetch("https://api.binance.com/api/v3/avgPrice?symbol=BTCUSDT");
if (!response.ok) {
throw new Error(`Failed to fetch BTC/USD rate: ${response.statusText}`);
}
const data = await response.json();
this.btcUsd = parseFloat(data.price);
debugExpert(`Fetched BTC/USD rate: ${this.btcUsd}`);
}
catch (error) {
debugError("Error fetching BTC/USD rate:", error);
throw error;
}
}
/**
* Checks if a model is accessible with the given API key
* Sends a simple test prompt and verifies the response
*
* @param model - Model ID to check
* @param apiKey - OpenRouter API key
* @returns Promise resolving to true if model is accessible, false otherwise
*/
async checkModel(model, apiKey) {
try {
debugExpert(`Testing model accessibility: ${model}`);
// Create an OpenAI client with the provided API key
const openai = new OpenAI({
apiKey,
baseURL: "https://openrouter.ai/api/v1",
});
// Send a simple test prompt
await openai.chat.completions.create({
model: model,
messages: [{ role: "user", content: "hello" }],
max_tokens: 16,
});
// If we get 200 return code we assume the model is working
debugExpert(`Model ${model} is accessible`);
return true;
}
catch (error) {
debugError(`Error testing model ${model}: ${error}`);
return false;
}
}
}
let instance;
export function getOpenRouter() {
if (!instance)
instance = new OpenRouter();
return instance;
}
//# sourceMappingURL=OpenRouter.js.map