routstrd 0.2.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bun.lock +16 -217
- package/dist/daemon/index.js +471 -396
- package/dist/index.js +10238 -31672
- package/package.json +2 -1
- package/src/cli.ts +291 -208
- package/src/integrations/claudecode.ts +19 -40
- package/src/integrations/openclaw.ts +8 -34
- package/src/integrations/opencode.ts +8 -34
- package/src/integrations/pi.ts +7 -34
- package/src/integrations/registry.ts +4 -12
- package/src/tui/usage/data.ts +19 -7
- package/src/utils/clients.ts +304 -0
- package/src/utils/config.ts +2 -0
- package/src/utils/daemon-client.ts +84 -13
- package/src/utils/nip98.ts +102 -0
- package/src/daemon/http/index.ts +0 -1130
- package/src/daemon/index.ts +0 -242
- package/src/daemon/wallet/index.ts +0 -122
- package/src/index.ts +0 -4
- package/src/integrations/index.ts +0 -76
- package/src/tui/usage/index.ts +0 -1
package/dist/daemon/index.js
CHANGED
|
@@ -62,9 +62,9 @@ var init__assert = () => {
|
|
|
62
62
|
|
|
63
63
|
// node_modules/nostr-tools/node_modules/@noble/curves/node_modules/@noble/hashes/esm/cryptoNode.js
|
|
64
64
|
import * as nc from "crypto";
|
|
65
|
-
var
|
|
65
|
+
var crypto2;
|
|
66
66
|
var init_cryptoNode = __esm(() => {
|
|
67
|
-
|
|
67
|
+
crypto2 = nc && typeof nc === "object" && "webcrypto" in nc ? nc.webcrypto : undefined;
|
|
68
68
|
});
|
|
69
69
|
|
|
70
70
|
// node_modules/nostr-tools/node_modules/@noble/curves/node_modules/@noble/hashes/esm/utils.js
|
|
@@ -106,8 +106,8 @@ function wrapConstructor(hashCons) {
|
|
|
106
106
|
return hashC;
|
|
107
107
|
}
|
|
108
108
|
function randomBytes(bytesLength = 32) {
|
|
109
|
-
if (
|
|
110
|
-
return
|
|
109
|
+
if (crypto2 && typeof crypto2.getRandomValues === "function") {
|
|
110
|
+
return crypto2.getRandomValues(new Uint8Array(bytesLength));
|
|
111
111
|
}
|
|
112
112
|
throw new Error("crypto.getRandomValues must be defined");
|
|
113
113
|
}
|
|
@@ -1951,9 +1951,9 @@ var init_secp256k1 = __esm(() => {
|
|
|
1951
1951
|
|
|
1952
1952
|
// node_modules/nostr-tools/node_modules/@noble/hashes/esm/cryptoNode.js
|
|
1953
1953
|
import * as nc2 from "crypto";
|
|
1954
|
-
var
|
|
1954
|
+
var crypto3;
|
|
1955
1955
|
var init_cryptoNode2 = __esm(() => {
|
|
1956
|
-
|
|
1956
|
+
crypto3 = nc2 && typeof nc2 === "object" && "webcrypto" in nc2 ? nc2.webcrypto : undefined;
|
|
1957
1957
|
});
|
|
1958
1958
|
|
|
1959
1959
|
// node_modules/nostr-tools/node_modules/@noble/hashes/esm/utils.js
|
|
@@ -2021,8 +2021,8 @@ function wrapConstructor2(hashCons) {
|
|
|
2021
2021
|
return hashC;
|
|
2022
2022
|
}
|
|
2023
2023
|
function randomBytes2(bytesLength = 32) {
|
|
2024
|
-
if (
|
|
2025
|
-
return
|
|
2024
|
+
if (crypto3 && typeof crypto3.getRandomValues === "function") {
|
|
2025
|
+
return crypto3.getRandomValues(new Uint8Array(bytesLength));
|
|
2026
2026
|
}
|
|
2027
2027
|
throw new Error("crypto.getRandomValues must be defined");
|
|
2028
2028
|
}
|
|
@@ -19631,14 +19631,14 @@ var init_observable = __esm(() => {
|
|
|
19631
19631
|
var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
|
|
19632
19632
|
|
|
19633
19633
|
// node_modules/nanoid/index.js
|
|
19634
|
-
import { webcrypto as
|
|
19634
|
+
import { webcrypto as crypto4 } from "crypto";
|
|
19635
19635
|
function fillPool(bytes4) {
|
|
19636
19636
|
if (!pool || pool.length < bytes4) {
|
|
19637
19637
|
pool = Buffer.allocUnsafe(bytes4 * POOL_SIZE_MULTIPLIER);
|
|
19638
|
-
|
|
19638
|
+
crypto4.getRandomValues(pool);
|
|
19639
19639
|
poolOffset = 0;
|
|
19640
19640
|
} else if (poolOffset + bytes4 > pool.length) {
|
|
19641
|
-
|
|
19641
|
+
crypto4.getRandomValues(pool);
|
|
19642
19642
|
poolOffset = 0;
|
|
19643
19643
|
}
|
|
19644
19644
|
poolOffset += bytes4;
|
|
@@ -34560,7 +34560,7 @@ var init_dist3 = __esm(() => {
|
|
|
34560
34560
|
// src/daemon/index.ts
|
|
34561
34561
|
init_dist3();
|
|
34562
34562
|
import { createServer } from "http";
|
|
34563
|
-
import { existsSync as
|
|
34563
|
+
import { existsSync as existsSync8 } from "fs";
|
|
34564
34564
|
|
|
34565
34565
|
// src/utils/config.ts
|
|
34566
34566
|
var HOME = process.env.HOME || process.env.USERPROFILE || "";
|
|
@@ -35253,6 +35253,436 @@ function createModelService(modelManager) {
|
|
|
35253
35253
|
init_dist3();
|
|
35254
35254
|
import { randomBytes as randomBytes5 } from "crypto";
|
|
35255
35255
|
import { Readable } from "stream";
|
|
35256
|
+
|
|
35257
|
+
// src/utils/daemon-client.ts
|
|
35258
|
+
import { existsSync as existsSync3 } from "fs";
|
|
35259
|
+
|
|
35260
|
+
// src/utils/nip98.ts
|
|
35261
|
+
init_esm2();
|
|
35262
|
+
var NIP98_KIND = 27235;
|
|
35263
|
+
function hexToBytes6(hex3) {
|
|
35264
|
+
const normalized = hex3.trim().toLowerCase();
|
|
35265
|
+
if (!/^[a-f0-9]{64}$/.test(normalized)) {
|
|
35266
|
+
throw new Error("Expected a 64-char hex private key or an nsec private key.");
|
|
35267
|
+
}
|
|
35268
|
+
const bytes4 = new Uint8Array(32);
|
|
35269
|
+
for (let i4 = 0;i4 < bytes4.length; i4++) {
|
|
35270
|
+
bytes4[i4] = Number.parseInt(normalized.slice(i4 * 2, i4 * 2 + 2), 16);
|
|
35271
|
+
}
|
|
35272
|
+
return bytes4;
|
|
35273
|
+
}
|
|
35274
|
+
function parseSecretKey(value) {
|
|
35275
|
+
const trimmed = value.trim();
|
|
35276
|
+
if (!trimmed) {
|
|
35277
|
+
throw new Error("Missing Nostr private key.");
|
|
35278
|
+
}
|
|
35279
|
+
if (trimmed.toLowerCase().startsWith("nsec1")) {
|
|
35280
|
+
const decoded = nip19_exports.decode(trimmed);
|
|
35281
|
+
if (decoded.type !== "nsec" || !(decoded.data instanceof Uint8Array)) {
|
|
35282
|
+
throw new Error("Invalid nsec private key.");
|
|
35283
|
+
}
|
|
35284
|
+
return decoded.data;
|
|
35285
|
+
}
|
|
35286
|
+
return hexToBytes6(trimmed);
|
|
35287
|
+
}
|
|
35288
|
+
async function sha256Hex(data) {
|
|
35289
|
+
const digest = await crypto.subtle.digest("SHA-256", new Uint8Array(data));
|
|
35290
|
+
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
35291
|
+
}
|
|
35292
|
+
function base64EncodeUtf8(value) {
|
|
35293
|
+
return btoa(String.fromCharCode(...new TextEncoder().encode(value)));
|
|
35294
|
+
}
|
|
35295
|
+
function npubFromPubkey(pubkey) {
|
|
35296
|
+
return nip19_exports.npubEncode(pubkey.toLowerCase());
|
|
35297
|
+
}
|
|
35298
|
+
function npubFromSecretKey(secretKey) {
|
|
35299
|
+
return npubFromPubkey(getPublicKey2(secretKey));
|
|
35300
|
+
}
|
|
35301
|
+
async function createNIP98Authorization(secretKey, url2, method, body) {
|
|
35302
|
+
const tags3 = [
|
|
35303
|
+
["u", url2],
|
|
35304
|
+
["method", method.toUpperCase()]
|
|
35305
|
+
];
|
|
35306
|
+
if (body && body.byteLength > 0) {
|
|
35307
|
+
tags3.push(["payload", await sha256Hex(body)]);
|
|
35308
|
+
}
|
|
35309
|
+
const template = {
|
|
35310
|
+
kind: NIP98_KIND,
|
|
35311
|
+
created_at: Math.round(Date.now() / 1000),
|
|
35312
|
+
content: "",
|
|
35313
|
+
tags: tags3
|
|
35314
|
+
};
|
|
35315
|
+
const signed = finalizeEvent2(template, secretKey);
|
|
35316
|
+
return `Nostr ${base64EncodeUtf8(JSON.stringify(signed))}`;
|
|
35317
|
+
}
|
|
35318
|
+
|
|
35319
|
+
// src/utils/daemon-client.ts
|
|
35320
|
+
async function loadConfig() {
|
|
35321
|
+
try {
|
|
35322
|
+
if (existsSync3(CONFIG_FILE)) {
|
|
35323
|
+
const content2 = await Bun.file(CONFIG_FILE).text();
|
|
35324
|
+
return { ...DEFAULT_CONFIG, ...JSON.parse(content2) };
|
|
35325
|
+
}
|
|
35326
|
+
} catch (error) {
|
|
35327
|
+
console.error("Failed to load config:", error);
|
|
35328
|
+
}
|
|
35329
|
+
return DEFAULT_CONFIG;
|
|
35330
|
+
}
|
|
35331
|
+
function getDaemonBaseUrl(config) {
|
|
35332
|
+
return config.daemonUrl?.replace(/\/$/, "") || `http://localhost:${config.port}`;
|
|
35333
|
+
}
|
|
35334
|
+
async function callDaemon(path, options = {}) {
|
|
35335
|
+
const { method = "GET", body } = options;
|
|
35336
|
+
const config = await loadConfig();
|
|
35337
|
+
const baseUrl = getDaemonBaseUrl(config);
|
|
35338
|
+
const url2 = `${baseUrl}${path}`;
|
|
35339
|
+
const bodyString = body ? JSON.stringify(body) : undefined;
|
|
35340
|
+
const bodyBytes = bodyString ? new TextEncoder().encode(bodyString) : undefined;
|
|
35341
|
+
let authorization;
|
|
35342
|
+
if (config.daemonUrl && config.nsec) {
|
|
35343
|
+
const secretKey = parseSecretKey(config.nsec);
|
|
35344
|
+
authorization = await createNIP98Authorization(secretKey, url2, method, bodyBytes);
|
|
35345
|
+
}
|
|
35346
|
+
const response = await fetch(url2, {
|
|
35347
|
+
method,
|
|
35348
|
+
headers: {
|
|
35349
|
+
...authorization ? { Authorization: authorization } : {},
|
|
35350
|
+
...bodyString ? { "Content-Type": "application/json" } : {}
|
|
35351
|
+
},
|
|
35352
|
+
body: bodyString
|
|
35353
|
+
});
|
|
35354
|
+
if (!response.ok) {
|
|
35355
|
+
const errorData = await response.json();
|
|
35356
|
+
throw new Error(errorData.error || `HTTP ${response.status}`);
|
|
35357
|
+
}
|
|
35358
|
+
return response.json();
|
|
35359
|
+
}
|
|
35360
|
+
|
|
35361
|
+
// src/integrations/registry.ts
|
|
35362
|
+
import { join as join4 } from "path";
|
|
35363
|
+
|
|
35364
|
+
// src/integrations/opencode.ts
|
|
35365
|
+
import { existsSync as existsSync4, mkdirSync } from "fs";
|
|
35366
|
+
import { readFile, writeFile } from "fs/promises";
|
|
35367
|
+
import { dirname } from "path";
|
|
35368
|
+
var OPENCODE_SMALL_MODEL = "routstr/minimax-m2.5";
|
|
35369
|
+
async function installOpencodeIntegration(config, apiKey, integrationConfig) {
|
|
35370
|
+
const { name, configPath } = integrationConfig;
|
|
35371
|
+
logger3.log(`
|
|
35372
|
+
Installing routstr models in opencode.json...`);
|
|
35373
|
+
logger3.log(`Using API key for ${name}`);
|
|
35374
|
+
const baseUrl = getDaemonBaseUrl(config);
|
|
35375
|
+
let opencodeConfig;
|
|
35376
|
+
try {
|
|
35377
|
+
if (existsSync4(configPath)) {
|
|
35378
|
+
const content2 = await readFile(configPath, "utf-8");
|
|
35379
|
+
opencodeConfig = JSON.parse(content2);
|
|
35380
|
+
} else {
|
|
35381
|
+
opencodeConfig = { provider: {} };
|
|
35382
|
+
}
|
|
35383
|
+
} catch {
|
|
35384
|
+
opencodeConfig = { provider: {} };
|
|
35385
|
+
}
|
|
35386
|
+
if (!opencodeConfig.provider) {
|
|
35387
|
+
opencodeConfig.provider = {};
|
|
35388
|
+
}
|
|
35389
|
+
try {
|
|
35390
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
35391
|
+
const data = await callDaemon("/models");
|
|
35392
|
+
const models = data.output?.models || [];
|
|
35393
|
+
if (models.length === 0) {
|
|
35394
|
+
logger3.log("No models found from routstr daemon.");
|
|
35395
|
+
return;
|
|
35396
|
+
}
|
|
35397
|
+
const modelsObj = {};
|
|
35398
|
+
for (const model2 of models) {
|
|
35399
|
+
modelsObj[model2.id] = { name: model2.name || model2.id };
|
|
35400
|
+
}
|
|
35401
|
+
opencodeConfig.provider["routstr"] = {
|
|
35402
|
+
npm: "@ai-sdk/openai-compatible",
|
|
35403
|
+
name: "routstr",
|
|
35404
|
+
options: {
|
|
35405
|
+
baseURL: `${baseUrl}/`,
|
|
35406
|
+
apiKey,
|
|
35407
|
+
includeUsage: true
|
|
35408
|
+
},
|
|
35409
|
+
models: modelsObj
|
|
35410
|
+
};
|
|
35411
|
+
opencodeConfig.small_model = OPENCODE_SMALL_MODEL;
|
|
35412
|
+
await writeFile(configPath, JSON.stringify(opencodeConfig, null, 2));
|
|
35413
|
+
logger3.log(`Added "routstr" provider with ${models.length} models to opencode.json`);
|
|
35414
|
+
} catch (error) {
|
|
35415
|
+
logger3.error("Failed to install models in opencode.json:", error);
|
|
35416
|
+
}
|
|
35417
|
+
}
|
|
35418
|
+
|
|
35419
|
+
// src/integrations/pi.ts
|
|
35420
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
|
|
35421
|
+
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
35422
|
+
import { dirname as dirname2 } from "path";
|
|
35423
|
+
async function installPiIntegration(config, apiKey, integrationConfig) {
|
|
35424
|
+
const { name, configPath } = integrationConfig;
|
|
35425
|
+
logger3.log(`
|
|
35426
|
+
Installing routstr models in pi models.json...`);
|
|
35427
|
+
logger3.log(`Using API key for ${name}`);
|
|
35428
|
+
const baseUrl = `${getDaemonBaseUrl(config)}/v1`;
|
|
35429
|
+
let piConfig = {};
|
|
35430
|
+
try {
|
|
35431
|
+
if (existsSync5(configPath)) {
|
|
35432
|
+
const content2 = await readFile2(configPath, "utf-8");
|
|
35433
|
+
piConfig = JSON.parse(content2);
|
|
35434
|
+
}
|
|
35435
|
+
} catch {
|
|
35436
|
+
piConfig = {};
|
|
35437
|
+
}
|
|
35438
|
+
if (!piConfig.providers) {
|
|
35439
|
+
piConfig.providers = {};
|
|
35440
|
+
}
|
|
35441
|
+
try {
|
|
35442
|
+
mkdirSync2(dirname2(configPath), { recursive: true });
|
|
35443
|
+
const data = await callDaemon("/models");
|
|
35444
|
+
const models = data.output?.models || [];
|
|
35445
|
+
if (models.length === 0) {
|
|
35446
|
+
logger3.log("No models found from routstr daemon.");
|
|
35447
|
+
return;
|
|
35448
|
+
}
|
|
35449
|
+
const providerModels = models.map((model2) => ({
|
|
35450
|
+
id: model2.id
|
|
35451
|
+
}));
|
|
35452
|
+
piConfig.providers["routstr"] = {
|
|
35453
|
+
baseUrl,
|
|
35454
|
+
api: "openai-completions",
|
|
35455
|
+
apiKey,
|
|
35456
|
+
models: providerModels
|
|
35457
|
+
};
|
|
35458
|
+
await writeFile2(configPath, JSON.stringify(piConfig, null, 2));
|
|
35459
|
+
logger3.log(`Added "routstr" provider with ${models.length} models to pi models.json`);
|
|
35460
|
+
} catch (error) {
|
|
35461
|
+
logger3.error("Failed to install models in pi models.json:", error);
|
|
35462
|
+
}
|
|
35463
|
+
}
|
|
35464
|
+
|
|
35465
|
+
// src/integrations/openclaw.ts
|
|
35466
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
35467
|
+
import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
35468
|
+
import { dirname as dirname3 } from "path";
|
|
35469
|
+
var OPENCLAW_PROVIDER_ID = "routstr";
|
|
35470
|
+
var OPENCLAW_DEFAULT_PRIMARY_MODEL = "routstr/minimax-m2.5";
|
|
35471
|
+
var OPENCLAW_DEFAULT_FALLBACK_MODEL = "routstr/kimi-k2.5";
|
|
35472
|
+
async function installOpenClawIntegration(config, apiKey, integrationConfig) {
|
|
35473
|
+
const { name, configPath } = integrationConfig;
|
|
35474
|
+
logger3.log(`
|
|
35475
|
+
Installing routstr models in openclaw.json...`);
|
|
35476
|
+
logger3.log(`Using API key for ${name}`);
|
|
35477
|
+
const baseUrl = getDaemonBaseUrl(config);
|
|
35478
|
+
let openclawConfig = {};
|
|
35479
|
+
try {
|
|
35480
|
+
if (existsSync6(configPath)) {
|
|
35481
|
+
const content2 = await readFile3(configPath, "utf-8");
|
|
35482
|
+
openclawConfig = JSON.parse(content2);
|
|
35483
|
+
}
|
|
35484
|
+
} catch {
|
|
35485
|
+
openclawConfig = {};
|
|
35486
|
+
}
|
|
35487
|
+
if (!openclawConfig.models) {
|
|
35488
|
+
openclawConfig.models = {};
|
|
35489
|
+
}
|
|
35490
|
+
if (!openclawConfig.models.providers) {
|
|
35491
|
+
openclawConfig.models.providers = {};
|
|
35492
|
+
}
|
|
35493
|
+
if (!openclawConfig.agents) {
|
|
35494
|
+
openclawConfig.agents = {};
|
|
35495
|
+
}
|
|
35496
|
+
if (!openclawConfig.agents.defaults) {
|
|
35497
|
+
openclawConfig.agents.defaults = {};
|
|
35498
|
+
}
|
|
35499
|
+
try {
|
|
35500
|
+
mkdirSync3(dirname3(configPath), { recursive: true });
|
|
35501
|
+
const data = await callDaemon("/models");
|
|
35502
|
+
const models = data.output?.models || [];
|
|
35503
|
+
if (models.length === 0) {
|
|
35504
|
+
logger3.log("No models found from routstr daemon.");
|
|
35505
|
+
return;
|
|
35506
|
+
}
|
|
35507
|
+
const providerModels = models.map((model2) => ({
|
|
35508
|
+
id: model2.id,
|
|
35509
|
+
name: model2.name || model2.id,
|
|
35510
|
+
reasoning: true
|
|
35511
|
+
}));
|
|
35512
|
+
openclawConfig.models.providers[OPENCLAW_PROVIDER_ID] = {
|
|
35513
|
+
baseUrl: `${baseUrl}/v1`,
|
|
35514
|
+
apiKey,
|
|
35515
|
+
api: "openai-completions",
|
|
35516
|
+
models: providerModels
|
|
35517
|
+
};
|
|
35518
|
+
const availableModelIds = new Set(providerModels.map((model2) => model2.id));
|
|
35519
|
+
const primaryId = availableModelIds.has("gpt-5.3-codex") ? "gpt-5.3-codex" : providerModels[0]?.id;
|
|
35520
|
+
const fallbackId = availableModelIds.has("minimax-m2.5") ? "minimax-m2.5" : providerModels.find((model2) => model2.id !== primaryId)?.id;
|
|
35521
|
+
if (primaryId) {
|
|
35522
|
+
openclawConfig.agents.defaults.model = {
|
|
35523
|
+
primary: `${OPENCLAW_PROVIDER_ID}/${primaryId}`,
|
|
35524
|
+
fallbacks: fallbackId ? [`${OPENCLAW_PROVIDER_ID}/${fallbackId}`] : []
|
|
35525
|
+
};
|
|
35526
|
+
} else {
|
|
35527
|
+
openclawConfig.agents.defaults.model = {
|
|
35528
|
+
primary: OPENCLAW_DEFAULT_PRIMARY_MODEL,
|
|
35529
|
+
fallbacks: [OPENCLAW_DEFAULT_FALLBACK_MODEL]
|
|
35530
|
+
};
|
|
35531
|
+
}
|
|
35532
|
+
await writeFile3(configPath, JSON.stringify(openclawConfig, null, 2));
|
|
35533
|
+
logger3.log(`Added "${OPENCLAW_PROVIDER_ID}" provider with ${models.length} models to openclaw.json`);
|
|
35534
|
+
} catch (error) {
|
|
35535
|
+
logger3.error("Failed to install models in openclaw.json:", error);
|
|
35536
|
+
}
|
|
35537
|
+
}
|
|
35538
|
+
|
|
35539
|
+
// src/integrations/claudecode.ts
|
|
35540
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
|
|
35541
|
+
import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
35542
|
+
import { dirname as dirname4 } from "path";
|
|
35543
|
+
async function installClaudeCodeIntegration(config, apiKey, integrationConfig) {
|
|
35544
|
+
const { name, configPath } = integrationConfig;
|
|
35545
|
+
logger3.log(`
|
|
35546
|
+
Installing routstr configuration in ${configPath}...`);
|
|
35547
|
+
logger3.log(`Using API key for ${name}`);
|
|
35548
|
+
const baseUrl = getDaemonBaseUrl(config);
|
|
35549
|
+
let settings = {};
|
|
35550
|
+
try {
|
|
35551
|
+
if (existsSync7(configPath)) {
|
|
35552
|
+
const content2 = await readFile4(configPath, "utf-8");
|
|
35553
|
+
settings = JSON.parse(content2);
|
|
35554
|
+
}
|
|
35555
|
+
} catch (error) {
|
|
35556
|
+
logger3.error(`Error reading ${configPath}, creating new one.`);
|
|
35557
|
+
}
|
|
35558
|
+
if (!settings.env) {
|
|
35559
|
+
settings.env = {};
|
|
35560
|
+
}
|
|
35561
|
+
settings.env["ANTHROPIC_AUTH_TOKEN"] = apiKey;
|
|
35562
|
+
settings.env["ANTHROPIC_BASE_URL"] = baseUrl;
|
|
35563
|
+
try {
|
|
35564
|
+
const data = await callDaemon("/models");
|
|
35565
|
+
const models = data.output?.models || [];
|
|
35566
|
+
if (models.length >= 3) {
|
|
35567
|
+
const opus = models[0];
|
|
35568
|
+
const sonnet = models[1];
|
|
35569
|
+
const haiku = models[2];
|
|
35570
|
+
settings.env["ANTHROPIC_DEFAULT_OPUS_MODEL"] = opus.id;
|
|
35571
|
+
settings.env["ANTHROPIC_DEFAULT_SONNET_MODEL"] = sonnet.id;
|
|
35572
|
+
settings.env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = haiku.id;
|
|
35573
|
+
logger3.log(`Set Claude models: Opus=${opus.id}, Sonnet=${sonnet.id}, Haiku=${haiku.id}`);
|
|
35574
|
+
} else if (models.length > 0) {
|
|
35575
|
+
const model2 = models[0];
|
|
35576
|
+
logger3.log(`Only ${models.length} models available, falling back to defaults.`);
|
|
35577
|
+
settings.env["ANTHROPIC_DEFAULT_OPUS_MODEL"] = model2.id;
|
|
35578
|
+
settings.env["ANTHROPIC_DEFAULT_SONNET_MODEL"] = model2.id;
|
|
35579
|
+
settings.env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = model2.id;
|
|
35580
|
+
} else {
|
|
35581
|
+
logger3.log("No models available from routstr daemon.");
|
|
35582
|
+
}
|
|
35583
|
+
} catch (error) {
|
|
35584
|
+
logger3.error("Failed to fetch models for Claude Code integration:", error);
|
|
35585
|
+
}
|
|
35586
|
+
try {
|
|
35587
|
+
mkdirSync4(dirname4(configPath), { recursive: true });
|
|
35588
|
+
await writeFile4(configPath, JSON.stringify(settings, null, 2));
|
|
35589
|
+
logger3.log(`Successfully updated ${configPath} with routstr settings.`);
|
|
35590
|
+
} catch (error) {
|
|
35591
|
+
logger3.error(`Failed to write to ${configPath}:`, error);
|
|
35592
|
+
}
|
|
35593
|
+
}
|
|
35594
|
+
|
|
35595
|
+
// src/integrations/registry.ts
|
|
35596
|
+
var CLIENT_CONFIGS = {
|
|
35597
|
+
opencode: {
|
|
35598
|
+
clientId: "opencode",
|
|
35599
|
+
name: "OpenCode",
|
|
35600
|
+
configPath: join4(process.env.HOME || "", ".config/opencode/opencode.json")
|
|
35601
|
+
},
|
|
35602
|
+
"pi-agent": {
|
|
35603
|
+
clientId: "pi-agent",
|
|
35604
|
+
name: "Pi Agent",
|
|
35605
|
+
configPath: join4(process.env.HOME || "", ".pi/agent/models.json")
|
|
35606
|
+
},
|
|
35607
|
+
openclaw: {
|
|
35608
|
+
clientId: "openclaw",
|
|
35609
|
+
name: "OpenClaw",
|
|
35610
|
+
configPath: join4(process.env.HOME || "", ".openclaw/openclaw.json")
|
|
35611
|
+
},
|
|
35612
|
+
"claude-code": {
|
|
35613
|
+
clientId: "claude-code",
|
|
35614
|
+
name: "Claude Code",
|
|
35615
|
+
configPath: join4(process.env.HOME || "", ".claude/settings.json")
|
|
35616
|
+
}
|
|
35617
|
+
};
|
|
35618
|
+
var CLIENT_INTEGRATIONS = {
|
|
35619
|
+
opencode: installOpencodeIntegration,
|
|
35620
|
+
"pi-agent": installPiIntegration,
|
|
35621
|
+
openclaw: installOpenClawIntegration,
|
|
35622
|
+
"claude-code": installClaudeCodeIntegration
|
|
35623
|
+
};
|
|
35624
|
+
async function runIntegrationsForClients(clientIds, config) {
|
|
35625
|
+
for (const client2 of clientIds) {
|
|
35626
|
+
const integrationFn = CLIENT_INTEGRATIONS[client2.clientId];
|
|
35627
|
+
const integrationConfig = CLIENT_CONFIGS[client2.clientId];
|
|
35628
|
+
if (integrationFn && integrationConfig && client2.apiKey) {
|
|
35629
|
+
try {
|
|
35630
|
+
await integrationFn(config, client2.apiKey, integrationConfig);
|
|
35631
|
+
} catch (error) {
|
|
35632
|
+
console.error(`Integration failed for ${client2.clientId}:`, error);
|
|
35633
|
+
}
|
|
35634
|
+
}
|
|
35635
|
+
}
|
|
35636
|
+
}
|
|
35637
|
+
|
|
35638
|
+
// src/utils/clients.ts
|
|
35639
|
+
function getNpubSuffix(config) {
|
|
35640
|
+
if (!config.daemonUrl || !config.nsec)
|
|
35641
|
+
return null;
|
|
35642
|
+
try {
|
|
35643
|
+
const secretKey = parseSecretKey(config.nsec);
|
|
35644
|
+
const npub = npubFromSecretKey(secretKey);
|
|
35645
|
+
return npub.slice(-7);
|
|
35646
|
+
} catch {
|
|
35647
|
+
return null;
|
|
35648
|
+
}
|
|
35649
|
+
}
|
|
35650
|
+
function removeSuffixFromId(id, suffix) {
|
|
35651
|
+
const suffixStr = `-${suffix}`;
|
|
35652
|
+
if (id.endsWith(suffixStr)) {
|
|
35653
|
+
return id.slice(0, -suffixStr.length);
|
|
35654
|
+
}
|
|
35655
|
+
return id;
|
|
35656
|
+
}
|
|
35657
|
+
function getClientsFromStore(store) {
|
|
35658
|
+
const state = store.getState();
|
|
35659
|
+
const clientIds = state.clientIds || [];
|
|
35660
|
+
return clientIds.map((c) => ({
|
|
35661
|
+
clientId: c.clientId,
|
|
35662
|
+
name: c.name,
|
|
35663
|
+
apiKey: c.apiKey,
|
|
35664
|
+
createdAt: c.createdAt,
|
|
35665
|
+
lastUsed: c.lastUsed
|
|
35666
|
+
}));
|
|
35667
|
+
}
|
|
35668
|
+
async function getClientsList() {
|
|
35669
|
+
const config = await loadConfig();
|
|
35670
|
+
const result = await callDaemon("/clients");
|
|
35671
|
+
const clients = result.output?.clients;
|
|
35672
|
+
if (!clients) {
|
|
35673
|
+
return [];
|
|
35674
|
+
}
|
|
35675
|
+
const suffix = config.daemonUrl ? getNpubSuffix(config) : null;
|
|
35676
|
+
return clients.filter((c) => !suffix || c.id.endsWith(`-${suffix}`)).map((c) => ({
|
|
35677
|
+
clientId: suffix ? removeSuffixFromId(c.id, suffix) : c.id,
|
|
35678
|
+
name: c.name,
|
|
35679
|
+
apiKey: c.apiKey,
|
|
35680
|
+
createdAt: c.createdAt,
|
|
35681
|
+
lastUsed: c.lastUsed
|
|
35682
|
+
}));
|
|
35683
|
+
}
|
|
35684
|
+
|
|
35685
|
+
// src/daemon/http/index.ts
|
|
35256
35686
|
function generateApiKey() {
|
|
35257
35687
|
const bytes4 = randomBytes5(24);
|
|
35258
35688
|
return `sk-${bytes4.toString("hex")}`;
|
|
@@ -35733,9 +36163,7 @@ function createDaemonRequestHandler(deps) {
|
|
|
35733
36163
|
}
|
|
35734
36164
|
if (req.method === "GET" && url2.pathname === "/clients") {
|
|
35735
36165
|
try {
|
|
35736
|
-
const
|
|
35737
|
-
const clientIds = state.clientIds || [];
|
|
35738
|
-
const clients = clientIds.map((c) => ({
|
|
36166
|
+
const clients = getClientsFromStore(deps.store).map((c) => ({
|
|
35739
36167
|
id: c.clientId,
|
|
35740
36168
|
name: c.name,
|
|
35741
36169
|
apiKey: c.apiKey,
|
|
@@ -35760,6 +36188,7 @@ function createDaemonRequestHandler(deps) {
|
|
|
35760
36188
|
const bodyText = await readBody(req);
|
|
35761
36189
|
const body = bodyText ? JSON.parse(bodyText) : {};
|
|
35762
36190
|
const name = body.name;
|
|
36191
|
+
const explicitId = body.id;
|
|
35763
36192
|
if (!name || typeof name !== "string" || name.trim() === "") {
|
|
35764
36193
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
35765
36194
|
res.end(JSON.stringify({
|
|
@@ -35767,16 +36196,23 @@ function createDaemonRequestHandler(deps) {
|
|
|
35767
36196
|
}));
|
|
35768
36197
|
return;
|
|
35769
36198
|
}
|
|
35770
|
-
|
|
36199
|
+
if (!explicitId || typeof explicitId !== "string" || explicitId.trim() === "") {
|
|
36200
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
36201
|
+
res.end(JSON.stringify({
|
|
36202
|
+
error: "Missing required 'id' field (must be a non-empty string)."
|
|
36203
|
+
}));
|
|
36204
|
+
return;
|
|
36205
|
+
}
|
|
36206
|
+
const sanitizeId = (value) => value.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
36207
|
+
const clientId = sanitizeId(explicitId);
|
|
35771
36208
|
if (!clientId) {
|
|
35772
36209
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
35773
36210
|
res.end(JSON.stringify({
|
|
35774
|
-
error: "Invalid client
|
|
36211
|
+
error: "Invalid client id. Must contain alphanumeric characters."
|
|
35775
36212
|
}));
|
|
35776
36213
|
return;
|
|
35777
36214
|
}
|
|
35778
|
-
const
|
|
35779
|
-
const existingClients = state.clientIds || [];
|
|
36215
|
+
const existingClients = getClientsFromStore(deps.store);
|
|
35780
36216
|
const existingClient = existingClients.find((c) => c.clientId === clientId);
|
|
35781
36217
|
if (existingClient) {
|
|
35782
36218
|
res.writeHead(409, { "Content-Type": "application/json" });
|
|
@@ -35826,8 +36262,7 @@ function createDaemonRequestHandler(deps) {
|
|
|
35826
36262
|
}));
|
|
35827
36263
|
return;
|
|
35828
36264
|
}
|
|
35829
|
-
const
|
|
35830
|
-
const existingClients = state.clientIds || [];
|
|
36265
|
+
const existingClients = getClientsFromStore(deps.store);
|
|
35831
36266
|
const index = existingClients.findIndex((c) => c.clientId === id);
|
|
35832
36267
|
if (index === -1) {
|
|
35833
36268
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
@@ -36040,362 +36475,18 @@ function createDaemonRequestHandler(deps) {
|
|
|
36040
36475
|
};
|
|
36041
36476
|
}
|
|
36042
36477
|
|
|
36043
|
-
// src/integrations/
|
|
36044
|
-
|
|
36045
|
-
|
|
36046
|
-
|
|
36047
|
-
|
|
36048
|
-
|
|
36049
|
-
|
|
36050
|
-
|
|
36051
|
-
|
|
36052
|
-
// src/integrations/pi.ts
|
|
36053
|
-
import { existsSync as existsSync3, mkdirSync } from "fs";
|
|
36054
|
-
import { readFile, writeFile } from "fs/promises";
|
|
36055
|
-
import { dirname } from "path";
|
|
36056
|
-
async function installPiIntegration(config, store, integrationConfig) {
|
|
36057
|
-
const { clientId, name, configPath } = integrationConfig;
|
|
36058
|
-
logger3.log(`
|
|
36059
|
-
Installing routstr models in pi models.json...`);
|
|
36060
|
-
const port = config.port || 8008;
|
|
36061
|
-
const baseUrl = `http://localhost:${port}/v1`;
|
|
36062
|
-
const state = store.getState();
|
|
36063
|
-
const existingClient = (state.clientIds || []).find((c) => c.clientId === clientId);
|
|
36064
|
-
let apiKey;
|
|
36065
|
-
if (existingClient) {
|
|
36066
|
-
apiKey = existingClient.apiKey;
|
|
36067
|
-
logger3.log(`Using existing API key for ${name}`);
|
|
36068
|
-
} else {
|
|
36069
|
-
apiKey = generateApiKey2();
|
|
36070
|
-
store.getState().setClientIds((prev) => [
|
|
36071
|
-
...prev || [],
|
|
36072
|
-
{
|
|
36073
|
-
clientId,
|
|
36074
|
-
name,
|
|
36075
|
-
apiKey,
|
|
36076
|
-
createdAt: Date.now()
|
|
36077
|
-
}
|
|
36078
|
-
]);
|
|
36079
|
-
logger3.log(`Created new API key for ${name}`);
|
|
36080
|
-
}
|
|
36081
|
-
let piConfig = {};
|
|
36082
|
-
try {
|
|
36083
|
-
if (existsSync3(configPath)) {
|
|
36084
|
-
const content2 = await readFile(configPath, "utf-8");
|
|
36085
|
-
piConfig = JSON.parse(content2);
|
|
36086
|
-
}
|
|
36087
|
-
} catch {
|
|
36088
|
-
piConfig = {};
|
|
36089
|
-
}
|
|
36090
|
-
if (!piConfig.providers) {
|
|
36091
|
-
piConfig.providers = {};
|
|
36092
|
-
}
|
|
36093
|
-
try {
|
|
36094
|
-
mkdirSync(dirname(configPath), { recursive: true });
|
|
36095
|
-
const response = await fetch(`http://localhost:${port}/models`);
|
|
36096
|
-
const data = await response.json();
|
|
36097
|
-
const models = data.output?.models || [];
|
|
36098
|
-
if (models.length === 0) {
|
|
36099
|
-
logger3.log("No models found from routstr daemon.");
|
|
36100
|
-
return;
|
|
36101
|
-
}
|
|
36102
|
-
const providerModels = models.map((model2) => ({
|
|
36103
|
-
id: model2.id
|
|
36104
|
-
}));
|
|
36105
|
-
piConfig.providers["routstr"] = {
|
|
36106
|
-
baseUrl,
|
|
36107
|
-
api: "openai-completions",
|
|
36108
|
-
apiKey,
|
|
36109
|
-
models: providerModels
|
|
36110
|
-
};
|
|
36111
|
-
await writeFile(configPath, JSON.stringify(piConfig, null, 2));
|
|
36112
|
-
logger3.log(`Added "routstr" provider with ${models.length} models to pi models.json`);
|
|
36113
|
-
} catch (error) {
|
|
36114
|
-
logger3.error("Failed to install models in pi models.json:", error);
|
|
36115
|
-
}
|
|
36116
|
-
}
|
|
36117
|
-
|
|
36118
|
-
// src/integrations/openclaw.ts
|
|
36119
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
|
|
36120
|
-
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
36121
|
-
import { dirname as dirname2 } from "path";
|
|
36122
|
-
var OPENCLAW_PROVIDER_ID = "routstr";
|
|
36123
|
-
var OPENCLAW_DEFAULT_PRIMARY_MODEL = "routstr/minimax-m2.5";
|
|
36124
|
-
var OPENCLAW_DEFAULT_FALLBACK_MODEL = "routstr/kimi-k2.5";
|
|
36125
|
-
async function installOpenClawIntegration(config, store, integrationConfig) {
|
|
36126
|
-
const { clientId, name, configPath } = integrationConfig;
|
|
36127
|
-
logger3.log(`
|
|
36128
|
-
Installing routstr models in openclaw.json...`);
|
|
36129
|
-
const port = config.port || 8008;
|
|
36130
|
-
const state = store.getState();
|
|
36131
|
-
const existingClient = (state.clientIds || []).find((c) => c.clientId === clientId);
|
|
36132
|
-
let apiKey;
|
|
36133
|
-
if (existingClient) {
|
|
36134
|
-
apiKey = existingClient.apiKey;
|
|
36135
|
-
logger3.log(`Using existing API key for ${name}`);
|
|
36136
|
-
} else {
|
|
36137
|
-
apiKey = generateApiKey2();
|
|
36138
|
-
store.getState().setClientIds((prev) => [
|
|
36139
|
-
...prev || [],
|
|
36140
|
-
{
|
|
36141
|
-
clientId,
|
|
36142
|
-
name,
|
|
36143
|
-
apiKey,
|
|
36144
|
-
createdAt: Date.now()
|
|
36145
|
-
}
|
|
36146
|
-
]);
|
|
36147
|
-
logger3.log(`Created new API key for ${name}`);
|
|
36148
|
-
}
|
|
36149
|
-
let openclawConfig = {};
|
|
36150
|
-
try {
|
|
36151
|
-
if (existsSync4(configPath)) {
|
|
36152
|
-
const content2 = await readFile2(configPath, "utf-8");
|
|
36153
|
-
openclawConfig = JSON.parse(content2);
|
|
36154
|
-
}
|
|
36155
|
-
} catch {
|
|
36156
|
-
openclawConfig = {};
|
|
36157
|
-
}
|
|
36158
|
-
if (!openclawConfig.models) {
|
|
36159
|
-
openclawConfig.models = {};
|
|
36160
|
-
}
|
|
36161
|
-
if (!openclawConfig.models.providers) {
|
|
36162
|
-
openclawConfig.models.providers = {};
|
|
36163
|
-
}
|
|
36164
|
-
if (!openclawConfig.agents) {
|
|
36165
|
-
openclawConfig.agents = {};
|
|
36166
|
-
}
|
|
36167
|
-
if (!openclawConfig.agents.defaults) {
|
|
36168
|
-
openclawConfig.agents.defaults = {};
|
|
36169
|
-
}
|
|
36170
|
-
try {
|
|
36171
|
-
mkdirSync2(dirname2(configPath), { recursive: true });
|
|
36172
|
-
const response = await fetch(`http://localhost:${port}/models`);
|
|
36173
|
-
const data = await response.json();
|
|
36174
|
-
const models = data.output?.models || [];
|
|
36175
|
-
if (models.length === 0) {
|
|
36176
|
-
logger3.log("No models found from routstr daemon.");
|
|
36177
|
-
return;
|
|
36178
|
-
}
|
|
36179
|
-
const providerModels = models.map((model2) => ({
|
|
36180
|
-
id: model2.id,
|
|
36181
|
-
name: model2.name || model2.id,
|
|
36182
|
-
reasoning: true
|
|
36183
|
-
}));
|
|
36184
|
-
openclawConfig.models.providers[OPENCLAW_PROVIDER_ID] = {
|
|
36185
|
-
baseUrl: `http://localhost:${port}/v1`,
|
|
36186
|
-
apiKey,
|
|
36187
|
-
api: "openai-completions",
|
|
36188
|
-
models: providerModels
|
|
36189
|
-
};
|
|
36190
|
-
const availableModelIds = new Set(providerModels.map((model2) => model2.id));
|
|
36191
|
-
const primaryId = availableModelIds.has("gpt-5.3-codex") ? "gpt-5.3-codex" : providerModels[0]?.id;
|
|
36192
|
-
const fallbackId = availableModelIds.has("minimax-m2.5") ? "minimax-m2.5" : providerModels.find((model2) => model2.id !== primaryId)?.id;
|
|
36193
|
-
if (primaryId) {
|
|
36194
|
-
openclawConfig.agents.defaults.model = {
|
|
36195
|
-
primary: `${OPENCLAW_PROVIDER_ID}/${primaryId}`,
|
|
36196
|
-
fallbacks: fallbackId ? [`${OPENCLAW_PROVIDER_ID}/${fallbackId}`] : []
|
|
36197
|
-
};
|
|
36198
|
-
} else {
|
|
36199
|
-
openclawConfig.agents.defaults.model = {
|
|
36200
|
-
primary: OPENCLAW_DEFAULT_PRIMARY_MODEL,
|
|
36201
|
-
fallbacks: [OPENCLAW_DEFAULT_FALLBACK_MODEL]
|
|
36202
|
-
};
|
|
36203
|
-
}
|
|
36204
|
-
await writeFile2(configPath, JSON.stringify(openclawConfig, null, 2));
|
|
36205
|
-
logger3.log(`Added "${OPENCLAW_PROVIDER_ID}" provider with ${models.length} models to openclaw.json`);
|
|
36206
|
-
} catch (error) {
|
|
36207
|
-
logger3.error("Failed to install models in openclaw.json:", error);
|
|
36208
|
-
}
|
|
36209
|
-
}
|
|
36210
|
-
|
|
36211
|
-
// src/integrations/claudecode.ts
|
|
36212
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
36213
|
-
import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
36214
|
-
import { dirname as dirname3 } from "path";
|
|
36215
|
-
async function installClaudeCodeIntegration(config, store, integrationConfig) {
|
|
36216
|
-
const { clientId, name, configPath } = integrationConfig;
|
|
36217
|
-
logger3.log(`
|
|
36218
|
-
Installing routstr configuration in ${configPath}...`);
|
|
36219
|
-
const port = config.port || 8008;
|
|
36220
|
-
const state = store.getState();
|
|
36221
|
-
const existingClient = (state.clientIds || []).find((c) => c.clientId === clientId);
|
|
36222
|
-
let apiKey;
|
|
36223
|
-
if (existingClient) {
|
|
36224
|
-
apiKey = existingClient.apiKey;
|
|
36225
|
-
logger3.log(`Using existing API key for ${name}`);
|
|
36226
|
-
} else {
|
|
36227
|
-
apiKey = generateApiKey2();
|
|
36228
|
-
store.getState().setClientIds((prev) => [
|
|
36229
|
-
...prev || [],
|
|
36230
|
-
{
|
|
36231
|
-
clientId,
|
|
36232
|
-
name,
|
|
36233
|
-
apiKey,
|
|
36234
|
-
createdAt: Date.now()
|
|
36235
|
-
}
|
|
36236
|
-
]);
|
|
36237
|
-
logger3.log(`Created new API key for ${name}`);
|
|
36238
|
-
}
|
|
36239
|
-
let settings = {};
|
|
36240
|
-
try {
|
|
36241
|
-
if (existsSync5(configPath)) {
|
|
36242
|
-
const content2 = await readFile3(configPath, "utf-8");
|
|
36243
|
-
settings = JSON.parse(content2);
|
|
36244
|
-
}
|
|
36245
|
-
} catch (error) {
|
|
36246
|
-
logger3.error(`Error reading ${configPath}, creating new one.`);
|
|
36247
|
-
}
|
|
36248
|
-
if (!settings.env) {
|
|
36249
|
-
settings.env = {};
|
|
36250
|
-
}
|
|
36251
|
-
settings.env["ANTHROPIC_AUTH_TOKEN"] = apiKey;
|
|
36252
|
-
settings.env["ANTHROPIC_BASE_URL"] = `http://localhost:${port}`;
|
|
36253
|
-
try {
|
|
36254
|
-
const response = await fetch(`http://localhost:${port}/models`);
|
|
36255
|
-
const data = await response.json();
|
|
36256
|
-
const models = data.output?.models || [];
|
|
36257
|
-
if (models.length >= 3) {
|
|
36258
|
-
settings.env["ANTHROPIC_DEFAULT_OPUS_MODEL"] = models[0].id;
|
|
36259
|
-
settings.env["ANTHROPIC_DEFAULT_SONNET_MODEL"] = models[1].id;
|
|
36260
|
-
settings.env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = models[2].id;
|
|
36261
|
-
logger3.log(`Set Claude models: Opus=${models[0].id}, Sonnet=${models[1].id}, Haiku=${models[2].id}`);
|
|
36262
|
-
} else if (models.length > 0) {
|
|
36263
|
-
logger3.log(`Only ${models.length} models available, falling back to defaults.`);
|
|
36264
|
-
settings.env["ANTHROPIC_DEFAULT_OPUS_MODEL"] = models[0].id;
|
|
36265
|
-
settings.env["ANTHROPIC_DEFAULT_SONNET_MODEL"] = models[0].id;
|
|
36266
|
-
settings.env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = models[0].id;
|
|
36267
|
-
} else {
|
|
36268
|
-
logger3.log("No models available from routstr daemon.");
|
|
36269
|
-
}
|
|
36270
|
-
} catch (error) {
|
|
36271
|
-
logger3.error("Failed to fetch models for Claude Code integration:", error);
|
|
36272
|
-
}
|
|
36273
|
-
try {
|
|
36274
|
-
mkdirSync3(dirname3(configPath), { recursive: true });
|
|
36275
|
-
await writeFile3(configPath, JSON.stringify(settings, null, 2));
|
|
36276
|
-
logger3.log(`Successfully updated ${configPath} with routstr settings.`);
|
|
36277
|
-
} catch (error) {
|
|
36278
|
-
logger3.error(`Failed to write to ${configPath}:`, error);
|
|
36279
|
-
}
|
|
36280
|
-
}
|
|
36281
|
-
|
|
36282
|
-
// src/integrations/registry.ts
|
|
36283
|
-
function generateApiKey2() {
|
|
36284
|
-
const bytes4 = randomBytes6(24);
|
|
36285
|
-
return `sk-${bytes4.toString("hex")}`;
|
|
36286
|
-
}
|
|
36287
|
-
var CLIENT_CONFIGS = {
|
|
36288
|
-
opencode: {
|
|
36289
|
-
clientId: "opencode",
|
|
36290
|
-
name: "OpenCode",
|
|
36291
|
-
configPath: join4(process.env.HOME || "", ".config/opencode/opencode.json")
|
|
36292
|
-
},
|
|
36293
|
-
"pi-agent": {
|
|
36294
|
-
clientId: "pi-agent",
|
|
36295
|
-
name: "Pi Agent",
|
|
36296
|
-
configPath: join4(process.env.HOME || "", ".pi/agent/models.json")
|
|
36297
|
-
},
|
|
36298
|
-
openclaw: {
|
|
36299
|
-
clientId: "openclaw",
|
|
36300
|
-
name: "OpenClaw",
|
|
36301
|
-
configPath: join4(process.env.HOME || "", ".openclaw/openclaw.json")
|
|
36302
|
-
},
|
|
36303
|
-
"claude-code": {
|
|
36304
|
-
clientId: "claude-code",
|
|
36305
|
-
name: "Claude Code",
|
|
36306
|
-
configPath: join4(process.env.HOME || "", ".claude/settings.json")
|
|
36307
|
-
}
|
|
36308
|
-
};
|
|
36309
|
-
var CLIENT_INTEGRATIONS = {
|
|
36310
|
-
opencode: installOpencodeIntegration,
|
|
36311
|
-
"pi-agent": installPiIntegration,
|
|
36312
|
-
openclaw: installOpenClawIntegration,
|
|
36313
|
-
"claude-code": installClaudeCodeIntegration
|
|
36314
|
-
};
|
|
36315
|
-
async function runIntegrationsForClients(clientIds, config, store) {
|
|
36316
|
-
for (const client2 of clientIds) {
|
|
36317
|
-
const integrationFn = CLIENT_INTEGRATIONS[client2.clientId];
|
|
36318
|
-
const integrationConfig = CLIENT_CONFIGS[client2.clientId];
|
|
36319
|
-
if (integrationFn && integrationConfig) {
|
|
36320
|
-
try {
|
|
36321
|
-
await integrationFn(config, store, integrationConfig);
|
|
36322
|
-
} catch (error) {
|
|
36323
|
-
console.error(`Integration failed for ${client2.clientId}:`, error);
|
|
36324
|
-
}
|
|
36325
|
-
}
|
|
36478
|
+
// src/integrations/index.ts
|
|
36479
|
+
async function refreshModelsAndIntegrations(getRoutstr21Models, config, label = "Scheduled") {
|
|
36480
|
+
await getRoutstr21Models(true);
|
|
36481
|
+
logger3.log(`${label} model refresh completed successfully.`);
|
|
36482
|
+
const clientIds = await getClientsList();
|
|
36483
|
+
if (clientIds.length > 0) {
|
|
36484
|
+
logger3.log(`Refreshing ${clientIds.length} client integration(s)...`);
|
|
36485
|
+
await runIntegrationsForClients(clientIds, config);
|
|
36486
|
+
logger3.log("Client integrations refreshed.");
|
|
36326
36487
|
}
|
|
36327
36488
|
}
|
|
36328
36489
|
|
|
36329
|
-
// src/integrations/opencode.ts
|
|
36330
|
-
var OPENCODE_SMALL_MODEL = "routstr/minimax-m2.5";
|
|
36331
|
-
async function installOpencodeIntegration(config, store, integrationConfig) {
|
|
36332
|
-
const { clientId, name, configPath } = integrationConfig;
|
|
36333
|
-
logger3.log(`
|
|
36334
|
-
Installing routstr models in opencode.json...`);
|
|
36335
|
-
const port = config.port || 8008;
|
|
36336
|
-
const state = store.getState();
|
|
36337
|
-
const existingClient = (state.clientIds || []).find((c) => c.clientId === clientId);
|
|
36338
|
-
let apiKey;
|
|
36339
|
-
if (existingClient) {
|
|
36340
|
-
apiKey = existingClient.apiKey;
|
|
36341
|
-
logger3.log(`Using existing API key for ${name}`);
|
|
36342
|
-
} else {
|
|
36343
|
-
apiKey = generateApiKey2();
|
|
36344
|
-
store.getState().setClientIds((prev) => [
|
|
36345
|
-
...prev || [],
|
|
36346
|
-
{
|
|
36347
|
-
clientId,
|
|
36348
|
-
name,
|
|
36349
|
-
apiKey,
|
|
36350
|
-
createdAt: Date.now()
|
|
36351
|
-
}
|
|
36352
|
-
]);
|
|
36353
|
-
logger3.log(`Created new API key for ${name}`);
|
|
36354
|
-
}
|
|
36355
|
-
let opencodeConfig;
|
|
36356
|
-
try {
|
|
36357
|
-
if (existsSync6(configPath)) {
|
|
36358
|
-
const content2 = await readFile4(configPath, "utf-8");
|
|
36359
|
-
opencodeConfig = JSON.parse(content2);
|
|
36360
|
-
} else {
|
|
36361
|
-
opencodeConfig = { provider: {} };
|
|
36362
|
-
}
|
|
36363
|
-
} catch {
|
|
36364
|
-
opencodeConfig = { provider: {} };
|
|
36365
|
-
}
|
|
36366
|
-
if (!opencodeConfig.provider) {
|
|
36367
|
-
opencodeConfig.provider = {};
|
|
36368
|
-
}
|
|
36369
|
-
try {
|
|
36370
|
-
mkdirSync4(dirname4(configPath), { recursive: true });
|
|
36371
|
-
const response = await fetch(`http://localhost:${port}/models`);
|
|
36372
|
-
const data = await response.json();
|
|
36373
|
-
const models = data.output?.models || [];
|
|
36374
|
-
if (models.length === 0) {
|
|
36375
|
-
logger3.log("No models found from routstr daemon.");
|
|
36376
|
-
return;
|
|
36377
|
-
}
|
|
36378
|
-
const modelsObj = {};
|
|
36379
|
-
for (const model2 of models) {
|
|
36380
|
-
modelsObj[model2.id] = { name: model2.name || model2.id };
|
|
36381
|
-
}
|
|
36382
|
-
opencodeConfig.provider["routstr"] = {
|
|
36383
|
-
npm: "@ai-sdk/openai-compatible",
|
|
36384
|
-
name: "routstr",
|
|
36385
|
-
options: {
|
|
36386
|
-
baseURL: `http://localhost:${port}/`,
|
|
36387
|
-
apiKey,
|
|
36388
|
-
includeUsage: true
|
|
36389
|
-
},
|
|
36390
|
-
models: modelsObj
|
|
36391
|
-
};
|
|
36392
|
-
opencodeConfig.small_model = OPENCODE_SMALL_MODEL;
|
|
36393
|
-
await writeFile4(configPath, JSON.stringify(opencodeConfig, null, 2));
|
|
36394
|
-
logger3.log(`Added "routstr" provider with ${models.length} models to opencode.json`);
|
|
36395
|
-
} catch (error) {
|
|
36396
|
-
logger3.error("Failed to install models in opencode.json:", error);
|
|
36397
|
-
}
|
|
36398
|
-
}
|
|
36399
36490
|
// src/daemon/index.ts
|
|
36400
36491
|
init_dist3();
|
|
36401
36492
|
async function main() {
|
|
@@ -36407,7 +36498,8 @@ async function main() {
|
|
|
36407
36498
|
const updatedConfig = { ...config, port, provider };
|
|
36408
36499
|
saveDaemonConfig(updatedConfig);
|
|
36409
36500
|
const sqliteDriver = await createBunSqliteDriver2(DB_PATH);
|
|
36410
|
-
const { store } =
|
|
36501
|
+
const { store, hydrate } = createSdkStore({ driver: sqliteDriver });
|
|
36502
|
+
await hydrate;
|
|
36411
36503
|
const { Database } = await import("bun:sqlite");
|
|
36412
36504
|
const usageTrackingDriver = createBunSqliteUsageTrackingDriver2({
|
|
36413
36505
|
dbPath: DB_PATH,
|
|
@@ -36445,7 +36537,7 @@ async function main() {
|
|
|
36445
36537
|
}));
|
|
36446
36538
|
Bun.write(PID_FILE, String(process.pid));
|
|
36447
36539
|
try {
|
|
36448
|
-
if (
|
|
36540
|
+
if (existsSync8(SOCKET_PATH)) {
|
|
36449
36541
|
Bun.spawn(["rm", SOCKET_PATH]);
|
|
36450
36542
|
}
|
|
36451
36543
|
} catch {
|
|
@@ -36457,15 +36549,7 @@ async function main() {
|
|
|
36457
36549
|
refreshInterval = setInterval(async () => {
|
|
36458
36550
|
logger3.log("Running scheduled model refresh...");
|
|
36459
36551
|
try {
|
|
36460
|
-
await getRoutstr21Models
|
|
36461
|
-
logger3.log("Scheduled model refresh completed successfully.");
|
|
36462
|
-
const state = store.getState();
|
|
36463
|
-
const clientIds = state.clientIds || [];
|
|
36464
|
-
if (clientIds.length > 0) {
|
|
36465
|
-
logger3.log(`Refreshing ${clientIds.length} client integration(s)...`);
|
|
36466
|
-
await runIntegrationsForClients(clientIds, updatedConfig, store);
|
|
36467
|
-
logger3.log("Client integrations refreshed.");
|
|
36468
|
-
}
|
|
36552
|
+
await refreshModelsAndIntegrations(getRoutstr21Models, updatedConfig, "Scheduled");
|
|
36469
36553
|
} catch (error) {
|
|
36470
36554
|
logger3.error("Scheduled model refresh failed:", error);
|
|
36471
36555
|
}
|
|
@@ -36526,20 +36610,11 @@ async function main() {
|
|
|
36526
36610
|
});
|
|
36527
36611
|
server.listen(port, async () => {
|
|
36528
36612
|
logger3.log(`Routstr daemon listening on http://localhost:${port}/v1`);
|
|
36529
|
-
ensureProvidersBootstrapped().then(() => {
|
|
36613
|
+
ensureProvidersBootstrapped().then(async () => {
|
|
36530
36614
|
startModelRefreshJob();
|
|
36531
36615
|
startRefundJob();
|
|
36532
36616
|
logger3.log("Running initial model refresh...");
|
|
36533
|
-
|
|
36534
|
-
}).then(async () => {
|
|
36535
|
-
logger3.log("Initial model refresh completed.");
|
|
36536
|
-
const state = store.getState();
|
|
36537
|
-
const clientIds = state.clientIds || [];
|
|
36538
|
-
if (clientIds.length > 0) {
|
|
36539
|
-
logger3.log(`Refreshing ${clientIds.length} client integration(s)...`);
|
|
36540
|
-
await runIntegrationsForClients(clientIds, updatedConfig, store);
|
|
36541
|
-
logger3.log("Client integrations refreshed.");
|
|
36542
|
-
}
|
|
36617
|
+
await refreshModelsAndIntegrations(getRoutstr21Models, updatedConfig, "Initial");
|
|
36543
36618
|
}).catch((error) => {
|
|
36544
36619
|
logger3.error("Initial model refresh failed:", error);
|
|
36545
36620
|
startModelRefreshJob();
|