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.
@@ -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 crypto;
65
+ var crypto2;
66
66
  var init_cryptoNode = __esm(() => {
67
- crypto = nc && typeof nc === "object" && "webcrypto" in nc ? nc.webcrypto : undefined;
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 (crypto && typeof crypto.getRandomValues === "function") {
110
- return crypto.getRandomValues(new Uint8Array(bytesLength));
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 crypto2;
1954
+ var crypto3;
1955
1955
  var init_cryptoNode2 = __esm(() => {
1956
- crypto2 = nc2 && typeof nc2 === "object" && "webcrypto" in nc2 ? nc2.webcrypto : undefined;
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 (crypto2 && typeof crypto2.getRandomValues === "function") {
2025
- return crypto2.getRandomValues(new Uint8Array(bytesLength));
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 crypto3 } from "crypto";
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
- crypto3.getRandomValues(pool);
19638
+ crypto4.getRandomValues(pool);
19639
19639
  poolOffset = 0;
19640
19640
  } else if (poolOffset + bytes4 > pool.length) {
19641
- crypto3.getRandomValues(pool);
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 existsSync7 } from "fs";
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 state = deps.store.getState();
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
- const clientId = name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
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 name. Must contain alphanumeric characters."
36211
+ error: "Invalid client id. Must contain alphanumeric characters."
35775
36212
  }));
35776
36213
  return;
35777
36214
  }
35778
- const state = deps.store.getState();
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 state = deps.store.getState();
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/opencode.ts
36044
- import { existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
36045
- import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
36046
- import { dirname as dirname4 } from "path";
36047
-
36048
- // src/integrations/registry.ts
36049
- import { randomBytes as randomBytes6 } from "crypto";
36050
- import { join as join4 } from "path";
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 } = await createSdkStore({ driver: sqliteDriver });
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 (existsSync7(SOCKET_PATH)) {
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(true);
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
- return getRoutstr21Models(true);
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();