Files
routstrd/src/integrations/openclaw.ts
T

142 lines
4.3 KiB
TypeScript

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<string, {
baseUrl?: string;
apiKey?: string;
api?: string;
models?: OpenClawModelEntry[];
}>;
};
agents?: {
defaults?: {
model?: {
primary?: string;
fallbacks?: string[];
};
models?: Record<string, {
alias?: string;
}>;
};
};
};
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<void> {
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<string, { alias?: string }> = {};
// 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);
}
}