import { existsSync, mkdirSync } from "fs"; import { readFile, writeFile } from "fs/promises"; import { dirname } from "path"; import type { RoutstrdConfig } from "../utils/config"; import type { IntegrationConfig, RoutstrModel } from "./registry"; import { callDaemon, getDaemonBaseUrl } from "../utils/daemon-client"; const OPENCLAW_PROVIDER_ID = "routstr"; const OPENCLAW_DEFAULT_PRIMARY_MODEL = "routstr/minimax-m2.5"; const OPENCLAW_DEFAULT_FALLBACK_MODEL = "routstr/kimi-k2.5"; type OpenClawModelEntry = { id: string; name: string; reasoning: boolean; }; type OpenClawConfig = { models?: { providers?: Record; }; agents?: { defaults?: { model?: { primary?: string; fallbacks?: string[]; }; models?: Record; }; }; }; function toAlias(modelId: string): string { if (modelId === "claude-sonnet-4.5") return "sonnet-4.5"; if (modelId === "claude-opus-4.5") return "opus-4.5"; if (modelId === "gemini-3-pro-preview") return "gemini-3-pro"; if (modelId === "gemini-3-flash-preview") return "gemini-3-flash"; if (modelId === "kimi-k2-thinking") return "kimi-k2"; if (modelId === "deepseek-v3.2-speciale") return "deepseek-special"; if (modelId === "grok-code-fast-1") return "grok-code"; return modelId; } export async function installOpenClawIntegration( config: RoutstrdConfig, apiKey: string, integrationConfig: IntegrationConfig, ): Promise { const { name, configPath } = integrationConfig; console.log("\nInstalling routstr models in openclaw.json..."); console.log(`Using API key for ${name}`); const baseUrl = getDaemonBaseUrl(config); let openclawConfig: OpenClawConfig = {}; try { if (existsSync(configPath)) { const content = await readFile(configPath, "utf-8"); openclawConfig = JSON.parse(content) as OpenClawConfig; } } catch (error) { console.error(`Failed to read or parse ${configPath}; leaving it unchanged:`, error); return; } if (!openclawConfig.models) { openclawConfig.models = {}; } if (!openclawConfig.models.providers) { openclawConfig.models.providers = {}; } if (!openclawConfig.agents) { openclawConfig.agents = {}; } if (!openclawConfig.agents.defaults) { openclawConfig.agents.defaults = {}; } try { mkdirSync(dirname(configPath), { recursive: true }); const data = await callDaemon("/models"); const models = (data.output as { models: RoutstrModel[] } | undefined)?.models || []; if (models.length === 0) { console.log("No models found from routstr daemon."); return; } const providerModels: OpenClawModelEntry[] = models.map((model) => ({ id: model.id, name: model.name || model.id, reasoning: true, })); openclawConfig.models.providers[OPENCLAW_PROVIDER_ID] = { baseUrl: `${baseUrl}/v1`, apiKey, api: "openai-completions", models: providerModels, }; const availableModelIds = new Set(providerModels.map((model) => model.id)); const primaryId = availableModelIds.has("gpt-5.3-codex") ? "gpt-5.3-codex" : providerModels[0]?.id; const fallbackId = availableModelIds.has("minimax-m2.5") ? "minimax-m2.5" : providerModels.find((model) => model.id !== primaryId)?.id; if (primaryId) { openclawConfig.agents.defaults.model = { primary: `${OPENCLAW_PROVIDER_ID}/${primaryId}`, fallbacks: fallbackId ? [`${OPENCLAW_PROVIDER_ID}/${fallbackId}`] : [], }; } else { openclawConfig.agents.defaults.model = { primary: OPENCLAW_DEFAULT_PRIMARY_MODEL, fallbacks: [OPENCLAW_DEFAULT_FALLBACK_MODEL], }; } // const aliasMap: Record = {}; // for (const model of providerModels) { // aliasMap[`${OPENCLAW_PROVIDER_ID}/${model.id}`] = { alias: toAlias(model.id) }; // } // openclawConfig.agents.defaults.models = aliasMap; await writeFile(configPath, JSON.stringify(openclawConfig, null, 2)); console.log(`Added "${OPENCLAW_PROVIDER_ID}" provider with ${models.length} models to openclaw.json`); } catch (error) { console.error("Failed to install models in openclaw.json:", error); } }