UNPKG

askexperts

Version:

AskExperts SDK: build and use AI experts - ask them questions and pay with bitcoin on an open protocol

276 lines 14.2 kB
import { getOpenRouter } from "../../../experts/utils/OpenRouter.js"; import { debugError, debugExpert, enableAllDebug, enableErrorDebug, } from "../../../common/debug.js"; import { generateRandomKeyPair } from "../../../common/crypto.js"; import { getPublicKey } from "nostr-tools"; import { bytesToHex } from "nostr-tools/utils"; import { getWalletByNameOrDefault } from "../wallet/utils.js"; import { addRemoteOptions } from "./index.js"; import { createDBClientForCommands } from "../utils.js"; /** * Manage OpenRouter experts in the database * Creates or updates experts for available models * Disables experts for unavailable models * * @param options Command line options */ export async function manageOpenRouterExperts(options) { // Enable debug if requested if (options.debug) enableAllDebug(); else enableErrorDebug(); try { // Get the expert client const db = await createDBClientForCommands(options); const user_id = await db.getUserId(); const openRouter = getOpenRouter(); // Get the list of models const models = await openRouter.list(); debugExpert(`Found ${models.length} models from OpenRouter`); // Filter models if specific ones were requested let filteredModels = models; if (options.models && options.models.length > 0) { const modelSet = new Set(options.models); filteredModels = models.filter((model) => modelSet.has(model.id)); debugExpert(`Filtered to ${filteredModels.length} requested models`); } // Get the wallet to use for experts const wallet = await getWalletByNameOrDefault(db, options.wallet); debugExpert(`Using wallet: ${wallet.name} (ID: ${wallet.id})`); // Get all existing OpenRouter experts from the database const allExperts = await db.listExperts(); const openRouterExperts = allExperts.filter((expert) => expert.type === "openrouter"); debugExpert(`Found ${openRouterExperts.length} existing OpenRouter experts in database`); // Create a map of existing experts by model ID const existingExpertsByModel = new Map(); for (const expert of openRouterExperts) { // Extract model from env (format: EXPERT_MODEL=model\nEXPERT_MARGIN=margin) const envLines = expert.env.split("\n"); const modelLine = envLines.find((line) => line.startsWith("EXPERT_MODEL=")); if (modelLine) { const model = modelLine.substring("EXPERT_MODEL=".length); existingExpertsByModel.set(model, expert); } } // Create or update experts for each available model for (const model of filteredModels) { const modelId = model.id; try { debugExpert(`Processing model: ${modelId}`); // Check if the model is accessible const apiKey = process.env.OPENROUTER_API_KEY || ""; const isModelAccessible = await openRouter.checkModel(modelId, apiKey); if (!isModelAccessible) { debugExpert(`Skipping model ${modelId} as it requires a provider API key or is not accessible`); // If we have an existing expert for this model, disable it const existingExpert = existingExpertsByModel.get(modelId); if (existingExpert && !existingExpert.disabled) { debugExpert(`Disabling expert for inaccessible model ${modelId}`); await db.setExpertDisabled(existingExpert.pubkey, true); } continue; } // Check if we already have an expert for this model const existingExpert = existingExpertsByModel.get(modelId); if (existingExpert) { // Update existing expert debugExpert(`Updating existing expert for model ${modelId}`); // Update environment variables with current margin const envLines = existingExpert.env.split("\n"); const updatedEnvLines = envLines.map((line) => { if (line.startsWith("EXPERT_MARGIN=")) { return `EXPERT_MARGIN=${options.margin}`; } return line; }); existingExpert.env = updatedEnvLines.join("\n"); existingExpert.disabled = false; // Ensure expert is enabled // Update in database await db.updateExpert(existingExpert); debugExpert(`Updated expert for model ${modelId}`); } else { // Create new expert debugExpert(`Creating new expert for model ${modelId}`); // Generate keypair const { privateKey } = generateRandomKeyPair(); const privkey = privateKey; const pubkey = getPublicKey(privkey); // Create environment variables const env = `EXPERT_MODEL=${modelId}\nEXPERT_MARGIN=${options.margin}`; // Create expert object const expert = { user_id, pubkey, wallet_id: wallet.id, type: "openrouter", nickname: `openrouter_${modelId}`, env, docstores: "", privkey: bytesToHex(privkey), disabled: false, }; // Insert into database await db.insertExpert(expert); debugExpert(`Created expert for model ${modelId} with pubkey ${pubkey}`); } } catch (error) { debugError(`Error processing model ${modelId}: ${error}`); } } // Disable experts for models that are no longer available const availableModelIds = new Set(filteredModels.map((model) => model.id)); for (const [modelId, expert] of existingExpertsByModel.entries()) { if (!availableModelIds.has(modelId)) { debugExpert(`Disabling expert for unavailable model ${modelId}`); await db.setExpertDisabled(expert.pubkey, true); } } // Set up hourly refresh const refreshInterval = 60 * 60 * 1000; // 1 hour const refreshExperts = async () => { try { debugExpert("Refreshing OpenRouter models..."); // Fetch latest models await openRouter.update(); const latestModels = await openRouter.list(); // Filter models if specific ones were requested let latestFilteredModels = latestModels; if (options.models && options.models.length > 0) { const modelSet = new Set(options.models); latestFilteredModels = latestModels.filter((model) => modelSet.has(model.id)); } // Get all existing OpenRouter experts from the database const currentExperts = (await db.listExperts()).filter((expert) => expert.type === "openrouter"); // Create a map of existing experts by model ID const currentExpertsByModel = new Map(); for (const expert of currentExperts) { const envLines = expert.env.split("\n"); const modelLine = envLines.find((line) => line.startsWith("EXPERT_MODEL=")); if (modelLine) { const model = modelLine.substring("EXPERT_MODEL=".length); currentExpertsByModel.set(model, expert); } } // Create or update experts for each available model for (const model of latestFilteredModels) { const modelId = model.id; try { // Check if the model is accessible const apiKey = process.env.OPENROUTER_API_KEY || ""; const isModelAccessible = await openRouter.checkModel(modelId, apiKey); if (!isModelAccessible) { debugExpert(`Skipping model ${modelId} as it requires a provider API key or is not accessible`); // If we have an existing expert for this model, disable it const existingExpert = currentExpertsByModel.get(modelId); if (existingExpert && !existingExpert.disabled) { debugExpert(`Disabling expert for inaccessible model ${modelId}`); await db.setExpertDisabled(existingExpert.pubkey, true); } continue; } // Check if we already have an expert for this model const existingExpert = currentExpertsByModel.get(modelId); if (existingExpert) { // Enable expert if it was disabled if (existingExpert.disabled) { debugExpert(`Re-enabling expert for model ${modelId}`); await db.setExpertDisabled(existingExpert.pubkey, false); } } else { // Create new expert debugExpert(`Creating new expert for model ${modelId}`); // Generate keypair const { privateKey } = generateRandomKeyPair(); const privkey = privateKey; const pubkey = getPublicKey(privkey); // Create environment variables const env = `EXPERT_MODEL=${modelId}\nEXPERT_MARGIN=${options.margin}`; // Create expert object const expert = { user_id, pubkey, wallet_id: wallet.id, type: "openrouter", nickname: `openrouter_${modelId}`, env, docstores: "", privkey: bytesToHex(privkey), disabled: false, }; // Insert into database await db.insertExpert(expert); debugExpert(`Created expert for model ${modelId} with pubkey ${pubkey}`); } } catch (error) { debugError(`Error processing model ${modelId}: ${error}`); } } // Disable experts for models that are no longer available const availableModelIds = new Set(latestFilteredModels.map((model) => model.id)); for (const [modelId, expert] of currentExpertsByModel.entries()) { if (!availableModelIds.has(modelId)) { debugExpert(`Disabling expert for unavailable model ${modelId}`); await db.setExpertDisabled(expert.pubkey, true); } } debugExpert(`Refreshed experts: ${latestFilteredModels.length} active models`); } catch (error) { debugError(`Error refreshing experts: ${error}`); } }; // Set up the interval const intervalId = setInterval(refreshExperts, refreshInterval); // Handle SIGINT/SIGTERM (Ctrl+C) const sigHandler = async () => { debugExpert("\nReceived SIGINT. Shutting down..."); // Clear the interval clearInterval(intervalId); debugExpert("Shutdown complete."); process.exit(0); }; process.on("SIGINT", sigHandler); process.on("SIGTERM", sigHandler); debugExpert(`Managed ${filteredModels.length} OpenRouter experts with margin ${options.margin}`); debugExpert("Press Ctrl+C to exit."); } catch (error) { debugError("Error managing OpenRouter experts:", error); throw error; } } /** * Register the OpenRouter experts command with the CLI * Can be used as a standalone command or as a subcommand * * @param program The commander program or parent command */ export function registerOpenRouterCommand(program) { const command = program .command("openrouter") .description("Manage experts for OpenRouter models in the database. Creates or updates experts for available models and disables experts for unavailable models.") .requiredOption("-m, --margin <number>", "Profit margin (e.g., 0.1 for 10%)", parseFloat) .option("--models <items>", "Comma-separated list of specific models to manage", (value) => value.split(",").map((item) => item.trim())) .option("-w, --wallet <name>", "Wallet name to use (uses default if not provided)") .option("-d, --debug", "Enable debug logging") .action(async (options) => { if (options.debug) enableAllDebug(); else enableErrorDebug(); try { await manageOpenRouterExperts(options); } catch (error) { debugError("Error managing OpenRouter experts:", error); process.exit(1); } }); // Add remote options addRemoteOptions(command); } //# sourceMappingURL=openrouter.js.map