routstrd 0.2.6 → 0.2.9

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 || "";
@@ -34900,6 +34900,102 @@ init_cashu_ts_es();
34900
34900
 
34901
34901
  // src/daemon/wallet/cocod-client.ts
34902
34902
  import { createHash } from "crypto";
34903
+
34904
+ // src/utils/process-lock.ts
34905
+ import { randomUUID } from "crypto";
34906
+ import { mkdir as mkdir3, readFile, rm, stat, writeFile } from "fs/promises";
34907
+ import { dirname } from "path";
34908
+ function delay(ms) {
34909
+ return new Promise((resolve) => setTimeout(resolve, ms));
34910
+ }
34911
+ function isProcessRunning(pid) {
34912
+ if (!Number.isFinite(pid) || pid <= 0) {
34913
+ return false;
34914
+ }
34915
+ try {
34916
+ process.kill(pid, 0);
34917
+ return true;
34918
+ } catch (error) {
34919
+ const code = error.code;
34920
+ return code === "EPERM";
34921
+ }
34922
+ }
34923
+ async function readLockOwner(lockDir) {
34924
+ try {
34925
+ const raw = await readFile(`${lockDir}/owner.json`, "utf8");
34926
+ const parsed = JSON.parse(raw);
34927
+ if (typeof parsed.pid === "number" && typeof parsed.createdAt === "number") {
34928
+ return {
34929
+ pid: parsed.pid,
34930
+ createdAt: parsed.createdAt,
34931
+ token: typeof parsed.token === "string" ? parsed.token : undefined
34932
+ };
34933
+ }
34934
+ } catch {
34935
+ }
34936
+ return null;
34937
+ }
34938
+ async function isLockStale(lockDir, staleAfterMs) {
34939
+ const owner = await readLockOwner(lockDir);
34940
+ if (owner) {
34941
+ return !isProcessRunning(owner.pid) || Date.now() - owner.createdAt > staleAfterMs;
34942
+ }
34943
+ try {
34944
+ const info = await stat(lockDir);
34945
+ return Date.now() - info.mtimeMs > staleAfterMs;
34946
+ } catch {
34947
+ return false;
34948
+ }
34949
+ }
34950
+ async function acquireCrossProcessLock(lockDir, options = {}) {
34951
+ const acquireTimeoutMs = options.acquireTimeoutMs ?? 120000;
34952
+ const retryIntervalMs = options.retryIntervalMs ?? 100;
34953
+ const staleAfterMs = options.staleAfterMs ?? 120000;
34954
+ const deadline = Date.now() + acquireTimeoutMs;
34955
+ await mkdir3(dirname(lockDir), { recursive: true });
34956
+ while (true) {
34957
+ try {
34958
+ await mkdir3(lockDir);
34959
+ const token = randomUUID();
34960
+ const owner = { pid: process.pid, createdAt: Date.now(), token };
34961
+ await writeFile(`${lockDir}/owner.json`, JSON.stringify(owner), "utf8");
34962
+ let released = false;
34963
+ return async () => {
34964
+ if (released)
34965
+ return;
34966
+ released = true;
34967
+ const currentOwner = await readLockOwner(lockDir);
34968
+ if (currentOwner?.token === token) {
34969
+ await rm(lockDir, { recursive: true, force: true });
34970
+ }
34971
+ };
34972
+ } catch (error) {
34973
+ const code = error.code;
34974
+ if (code !== "EEXIST") {
34975
+ throw error;
34976
+ }
34977
+ if (await isLockStale(lockDir, staleAfterMs)) {
34978
+ options.log?.(`Removing stale lock at ${lockDir}`);
34979
+ await rm(lockDir, { recursive: true, force: true });
34980
+ continue;
34981
+ }
34982
+ if (Date.now() >= deadline) {
34983
+ throw new Error(`Timed out waiting to acquire lock ${lockDir}`);
34984
+ }
34985
+ await delay(retryIntervalMs);
34986
+ }
34987
+ }
34988
+ }
34989
+ async function withCrossProcessLock(lockDir, fn, options = {}) {
34990
+ const release = await acquireCrossProcessLock(lockDir, options);
34991
+ try {
34992
+ return await fn();
34993
+ } finally {
34994
+ await release();
34995
+ }
34996
+ }
34997
+
34998
+ // src/daemon/wallet/cocod-client.ts
34903
34999
  var DEFAULT_CONFIG_DIR = process.env.COCOD_DIR || `${process.env.HOME || process.env.USERPROFILE || ""}/.cocod`;
34904
35000
  var DEFAULT_SOCKET_PATH = process.env.COCOD_SOCKET || `${DEFAULT_CONFIG_DIR}/cocod.sock`;
34905
35001
 
@@ -34929,7 +35025,7 @@ function parseMintList(output4) {
34929
35025
  return (output4 || "").split(`
34930
35026
  `).map((line) => line.trim()).filter(Boolean);
34931
35027
  }
34932
- function delay(ms) {
35028
+ function delay2(ms) {
34933
35029
  return new Promise((resolve) => setTimeout(resolve, ms));
34934
35030
  }
34935
35031
  function toErrorText(value) {
@@ -34951,6 +35047,7 @@ function tokenFingerprint(token) {
34951
35047
  function createCocodClient(options = {}) {
34952
35048
  const executable = resolveCocodExecutable(options.cocodPath);
34953
35049
  const socketPath = options.socketPath || DEFAULT_SOCKET_PATH;
35050
+ const startupLockPath = `${socketPath}.startup.lock`;
34954
35051
  const fetchImpl = options.fetchImpl || fetch;
34955
35052
  const pollIntervalMs = options.pollIntervalMs ?? 100;
34956
35053
  const startupTimeoutMs = options.startupTimeoutMs ?? 5000;
@@ -35005,31 +35102,40 @@ function createCocodClient(options = {}) {
35005
35102
  }
35006
35103
  async function startDaemon() {
35007
35104
  const env = { ...process.env, COCOD_SOCKET: socketPath };
35008
- const proc = spawnDaemon([executable, "daemon"], env);
35105
+ const proc = spawnDaemon([executable, "init"], env);
35009
35106
  const maxPolls = Math.ceil(startupTimeoutMs / pollIntervalMs);
35010
35107
  let exitCode = null;
35011
35108
  proc.exited.then((code) => {
35012
35109
  exitCode = code;
35013
35110
  });
35014
35111
  for (let i4 = 0;i4 < maxPolls; i4++) {
35015
- await delay(pollIntervalMs);
35016
- if (exitCode !== null) {
35017
- throw new Error(`cocod daemon exited early with code ${exitCode}`);
35112
+ await delay2(pollIntervalMs);
35113
+ if (exitCode !== null && exitCode !== 0) {
35114
+ throw new Error(`cocod init exited early with code ${exitCode}`);
35018
35115
  }
35019
35116
  if (await pingInternal()) {
35020
35117
  logger3.debug(`Connected to cocod daemon on ${socketPath}`);
35021
35118
  return;
35022
35119
  }
35023
35120
  }
35024
- throw new Error(`cocod daemon failed to start within ${Math.round(startupTimeoutMs / 1000)} seconds`);
35121
+ throw new Error(`cocod failed to start within ${Math.round(startupTimeoutMs / 1000)} seconds`);
35025
35122
  }
35026
35123
  async function ensureDaemonRunning() {
35027
35124
  if (await pingInternal()) {
35028
35125
  return;
35029
35126
  }
35030
35127
  if (!startPromise) {
35031
- logger3.debug(`Starting cocod daemon via ${executable}...`);
35032
- startPromise = startDaemon().finally(() => {
35128
+ startPromise = withCrossProcessLock(startupLockPath, async () => {
35129
+ if (await pingInternal()) {
35130
+ return;
35131
+ }
35132
+ logger3.debug(`Starting cocod daemon via ${executable} init...`);
35133
+ await startDaemon();
35134
+ }, {
35135
+ acquireTimeoutMs: startupTimeoutMs + 30000,
35136
+ staleAfterMs: startupTimeoutMs + 30000,
35137
+ log: (message) => logger3.debug(message)
35138
+ }).finally(() => {
35033
35139
  startPromise = null;
35034
35140
  });
35035
35141
  }
@@ -35253,6 +35359,439 @@ function createModelService(modelManager) {
35253
35359
  init_dist3();
35254
35360
  import { randomBytes as randomBytes5 } from "crypto";
35255
35361
  import { Readable } from "stream";
35362
+
35363
+ // src/utils/daemon-client.ts
35364
+ import { existsSync as existsSync3 } from "fs";
35365
+
35366
+ // src/start-daemon.ts
35367
+ var DAEMON_STARTUP_LOCK_PATH = `${CONFIG_DIR}/routstrd-startup.lock`;
35368
+
35369
+ // src/utils/nip98.ts
35370
+ init_esm2();
35371
+ var NIP98_KIND = 27235;
35372
+ function hexToBytes6(hex3) {
35373
+ const normalized = hex3.trim().toLowerCase();
35374
+ if (!/^[a-f0-9]{64}$/.test(normalized)) {
35375
+ throw new Error("Expected a 64-char hex private key or an nsec private key.");
35376
+ }
35377
+ const bytes4 = new Uint8Array(32);
35378
+ for (let i4 = 0;i4 < bytes4.length; i4++) {
35379
+ bytes4[i4] = Number.parseInt(normalized.slice(i4 * 2, i4 * 2 + 2), 16);
35380
+ }
35381
+ return bytes4;
35382
+ }
35383
+ function parseSecretKey(value) {
35384
+ const trimmed = value.trim();
35385
+ if (!trimmed) {
35386
+ throw new Error("Missing Nostr private key.");
35387
+ }
35388
+ if (trimmed.toLowerCase().startsWith("nsec1")) {
35389
+ const decoded = nip19_exports.decode(trimmed);
35390
+ if (decoded.type !== "nsec" || !(decoded.data instanceof Uint8Array)) {
35391
+ throw new Error("Invalid nsec private key.");
35392
+ }
35393
+ return decoded.data;
35394
+ }
35395
+ return hexToBytes6(trimmed);
35396
+ }
35397
+ async function sha256Hex(data) {
35398
+ const digest = await crypto.subtle.digest("SHA-256", new Uint8Array(data));
35399
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
35400
+ }
35401
+ function base64EncodeUtf8(value) {
35402
+ return btoa(String.fromCharCode(...new TextEncoder().encode(value)));
35403
+ }
35404
+ function npubFromPubkey(pubkey) {
35405
+ return nip19_exports.npubEncode(pubkey.toLowerCase());
35406
+ }
35407
+ function npubFromSecretKey(secretKey) {
35408
+ return npubFromPubkey(getPublicKey2(secretKey));
35409
+ }
35410
+ async function createNIP98Authorization(secretKey, url2, method, body) {
35411
+ const tags3 = [
35412
+ ["u", url2],
35413
+ ["method", method.toUpperCase()]
35414
+ ];
35415
+ if (body && body.byteLength > 0) {
35416
+ tags3.push(["payload", await sha256Hex(body)]);
35417
+ }
35418
+ const template = {
35419
+ kind: NIP98_KIND,
35420
+ created_at: Math.round(Date.now() / 1000),
35421
+ content: "",
35422
+ tags: tags3
35423
+ };
35424
+ const signed = finalizeEvent2(template, secretKey);
35425
+ return `Nostr ${base64EncodeUtf8(JSON.stringify(signed))}`;
35426
+ }
35427
+
35428
+ // src/utils/daemon-client.ts
35429
+ async function loadConfig() {
35430
+ try {
35431
+ if (existsSync3(CONFIG_FILE)) {
35432
+ const content2 = await Bun.file(CONFIG_FILE).text();
35433
+ return { ...DEFAULT_CONFIG, ...JSON.parse(content2) };
35434
+ }
35435
+ } catch (error) {
35436
+ console.error("Failed to load config:", error);
35437
+ }
35438
+ return DEFAULT_CONFIG;
35439
+ }
35440
+ function getDaemonBaseUrl(config) {
35441
+ return config.daemonUrl?.replace(/\/$/, "") || `http://localhost:${config.port}`;
35442
+ }
35443
+ async function callDaemon(path, options = {}) {
35444
+ const { method = "GET", body } = options;
35445
+ const config = await loadConfig();
35446
+ const baseUrl = getDaemonBaseUrl(config);
35447
+ const url2 = `${baseUrl}${path}`;
35448
+ const bodyString = body ? JSON.stringify(body) : undefined;
35449
+ const bodyBytes = bodyString ? new TextEncoder().encode(bodyString) : undefined;
35450
+ let authorization;
35451
+ if (config.daemonUrl && config.nsec) {
35452
+ const secretKey = parseSecretKey(config.nsec);
35453
+ authorization = await createNIP98Authorization(secretKey, url2, method, bodyBytes);
35454
+ }
35455
+ const response = await fetch(url2, {
35456
+ method,
35457
+ headers: {
35458
+ ...authorization ? { Authorization: authorization } : {},
35459
+ ...bodyString ? { "Content-Type": "application/json" } : {}
35460
+ },
35461
+ body: bodyString
35462
+ });
35463
+ if (!response.ok) {
35464
+ const errorData = await response.json();
35465
+ throw new Error(errorData.error || `HTTP ${response.status}`);
35466
+ }
35467
+ return response.json();
35468
+ }
35469
+
35470
+ // src/integrations/registry.ts
35471
+ import { join as join4 } from "path";
35472
+
35473
+ // src/integrations/opencode.ts
35474
+ import { existsSync as existsSync4, mkdirSync } from "fs";
35475
+ import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
35476
+ import { dirname as dirname2 } from "path";
35477
+ var OPENCODE_SMALL_MODEL = "routstr/minimax-m2.5";
35478
+ async function installOpencodeIntegration(config, apiKey, integrationConfig) {
35479
+ const { name, configPath } = integrationConfig;
35480
+ logger3.log(`
35481
+ Installing routstr models in opencode.json...`);
35482
+ logger3.log(`Using API key for ${name}`);
35483
+ const baseUrl = getDaemonBaseUrl(config);
35484
+ let opencodeConfig;
35485
+ try {
35486
+ if (existsSync4(configPath)) {
35487
+ const content2 = await readFile2(configPath, "utf-8");
35488
+ opencodeConfig = JSON.parse(content2);
35489
+ } else {
35490
+ opencodeConfig = { provider: {} };
35491
+ }
35492
+ } catch {
35493
+ opencodeConfig = { provider: {} };
35494
+ }
35495
+ if (!opencodeConfig.provider) {
35496
+ opencodeConfig.provider = {};
35497
+ }
35498
+ try {
35499
+ mkdirSync(dirname2(configPath), { recursive: true });
35500
+ const data = await callDaemon("/models");
35501
+ const models = data.output?.models || [];
35502
+ if (models.length === 0) {
35503
+ logger3.log("No models found from routstr daemon.");
35504
+ return;
35505
+ }
35506
+ const modelsObj = {};
35507
+ for (const model2 of models) {
35508
+ modelsObj[model2.id] = { name: model2.name || model2.id };
35509
+ }
35510
+ opencodeConfig.provider["routstr"] = {
35511
+ npm: "@ai-sdk/openai-compatible",
35512
+ name: "routstr",
35513
+ options: {
35514
+ baseURL: `${baseUrl}/`,
35515
+ apiKey,
35516
+ includeUsage: true
35517
+ },
35518
+ models: modelsObj
35519
+ };
35520
+ opencodeConfig.small_model = OPENCODE_SMALL_MODEL;
35521
+ await writeFile2(configPath, JSON.stringify(opencodeConfig, null, 2));
35522
+ logger3.log(`Added "routstr" provider with ${models.length} models to opencode.json`);
35523
+ } catch (error) {
35524
+ logger3.error("Failed to install models in opencode.json:", error);
35525
+ }
35526
+ }
35527
+
35528
+ // src/integrations/pi.ts
35529
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
35530
+ import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
35531
+ import { dirname as dirname3 } from "path";
35532
+ async function installPiIntegration(config, apiKey, integrationConfig) {
35533
+ const { name, configPath } = integrationConfig;
35534
+ logger3.log(`
35535
+ Installing routstr models in pi models.json...`);
35536
+ logger3.log(`Using API key for ${name}`);
35537
+ const baseUrl = `${getDaemonBaseUrl(config)}/v1`;
35538
+ let piConfig = {};
35539
+ try {
35540
+ if (existsSync5(configPath)) {
35541
+ const content2 = await readFile3(configPath, "utf-8");
35542
+ piConfig = JSON.parse(content2);
35543
+ }
35544
+ } catch {
35545
+ piConfig = {};
35546
+ }
35547
+ if (!piConfig.providers) {
35548
+ piConfig.providers = {};
35549
+ }
35550
+ try {
35551
+ mkdirSync2(dirname3(configPath), { recursive: true });
35552
+ const data = await callDaemon("/models");
35553
+ const models = data.output?.models || [];
35554
+ if (models.length === 0) {
35555
+ logger3.log("No models found from routstr daemon.");
35556
+ return;
35557
+ }
35558
+ const providerModels = models.map((model2) => ({
35559
+ id: model2.id
35560
+ }));
35561
+ piConfig.providers["routstr"] = {
35562
+ baseUrl,
35563
+ api: "openai-completions",
35564
+ apiKey,
35565
+ models: providerModels
35566
+ };
35567
+ await writeFile3(configPath, JSON.stringify(piConfig, null, 2));
35568
+ logger3.log(`Added "routstr" provider with ${models.length} models to pi models.json`);
35569
+ } catch (error) {
35570
+ logger3.error("Failed to install models in pi models.json:", error);
35571
+ }
35572
+ }
35573
+
35574
+ // src/integrations/openclaw.ts
35575
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
35576
+ import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
35577
+ import { dirname as dirname4 } from "path";
35578
+ var OPENCLAW_PROVIDER_ID = "routstr";
35579
+ var OPENCLAW_DEFAULT_PRIMARY_MODEL = "routstr/minimax-m2.5";
35580
+ var OPENCLAW_DEFAULT_FALLBACK_MODEL = "routstr/kimi-k2.5";
35581
+ async function installOpenClawIntegration(config, apiKey, integrationConfig) {
35582
+ const { name, configPath } = integrationConfig;
35583
+ logger3.log(`
35584
+ Installing routstr models in openclaw.json...`);
35585
+ logger3.log(`Using API key for ${name}`);
35586
+ const baseUrl = getDaemonBaseUrl(config);
35587
+ let openclawConfig = {};
35588
+ try {
35589
+ if (existsSync6(configPath)) {
35590
+ const content2 = await readFile4(configPath, "utf-8");
35591
+ openclawConfig = JSON.parse(content2);
35592
+ }
35593
+ } catch {
35594
+ openclawConfig = {};
35595
+ }
35596
+ if (!openclawConfig.models) {
35597
+ openclawConfig.models = {};
35598
+ }
35599
+ if (!openclawConfig.models.providers) {
35600
+ openclawConfig.models.providers = {};
35601
+ }
35602
+ if (!openclawConfig.agents) {
35603
+ openclawConfig.agents = {};
35604
+ }
35605
+ if (!openclawConfig.agents.defaults) {
35606
+ openclawConfig.agents.defaults = {};
35607
+ }
35608
+ try {
35609
+ mkdirSync3(dirname4(configPath), { recursive: true });
35610
+ const data = await callDaemon("/models");
35611
+ const models = data.output?.models || [];
35612
+ if (models.length === 0) {
35613
+ logger3.log("No models found from routstr daemon.");
35614
+ return;
35615
+ }
35616
+ const providerModels = models.map((model2) => ({
35617
+ id: model2.id,
35618
+ name: model2.name || model2.id,
35619
+ reasoning: true
35620
+ }));
35621
+ openclawConfig.models.providers[OPENCLAW_PROVIDER_ID] = {
35622
+ baseUrl: `${baseUrl}/v1`,
35623
+ apiKey,
35624
+ api: "openai-completions",
35625
+ models: providerModels
35626
+ };
35627
+ const availableModelIds = new Set(providerModels.map((model2) => model2.id));
35628
+ const primaryId = availableModelIds.has("gpt-5.3-codex") ? "gpt-5.3-codex" : providerModels[0]?.id;
35629
+ const fallbackId = availableModelIds.has("minimax-m2.5") ? "minimax-m2.5" : providerModels.find((model2) => model2.id !== primaryId)?.id;
35630
+ if (primaryId) {
35631
+ openclawConfig.agents.defaults.model = {
35632
+ primary: `${OPENCLAW_PROVIDER_ID}/${primaryId}`,
35633
+ fallbacks: fallbackId ? [`${OPENCLAW_PROVIDER_ID}/${fallbackId}`] : []
35634
+ };
35635
+ } else {
35636
+ openclawConfig.agents.defaults.model = {
35637
+ primary: OPENCLAW_DEFAULT_PRIMARY_MODEL,
35638
+ fallbacks: [OPENCLAW_DEFAULT_FALLBACK_MODEL]
35639
+ };
35640
+ }
35641
+ await writeFile4(configPath, JSON.stringify(openclawConfig, null, 2));
35642
+ logger3.log(`Added "${OPENCLAW_PROVIDER_ID}" provider with ${models.length} models to openclaw.json`);
35643
+ } catch (error) {
35644
+ logger3.error("Failed to install models in openclaw.json:", error);
35645
+ }
35646
+ }
35647
+
35648
+ // src/integrations/claudecode.ts
35649
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
35650
+ import { readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
35651
+ import { dirname as dirname5 } from "path";
35652
+ async function installClaudeCodeIntegration(config, apiKey, integrationConfig) {
35653
+ const { name, configPath } = integrationConfig;
35654
+ logger3.log(`
35655
+ Installing routstr configuration in ${configPath}...`);
35656
+ logger3.log(`Using API key for ${name}`);
35657
+ const baseUrl = getDaemonBaseUrl(config);
35658
+ let settings = {};
35659
+ try {
35660
+ if (existsSync7(configPath)) {
35661
+ const content2 = await readFile5(configPath, "utf-8");
35662
+ settings = JSON.parse(content2);
35663
+ }
35664
+ } catch (error) {
35665
+ logger3.error(`Error reading ${configPath}, creating new one.`);
35666
+ }
35667
+ if (!settings.env) {
35668
+ settings.env = {};
35669
+ }
35670
+ settings.env["ANTHROPIC_AUTH_TOKEN"] = apiKey;
35671
+ settings.env["ANTHROPIC_BASE_URL"] = baseUrl;
35672
+ try {
35673
+ const data = await callDaemon("/models");
35674
+ const models = data.output?.models || [];
35675
+ if (models.length >= 3) {
35676
+ const opus = models[0];
35677
+ const sonnet = models[1];
35678
+ const haiku = models[2];
35679
+ settings.env["ANTHROPIC_DEFAULT_OPUS_MODEL"] = opus.id;
35680
+ settings.env["ANTHROPIC_DEFAULT_SONNET_MODEL"] = sonnet.id;
35681
+ settings.env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = haiku.id;
35682
+ logger3.log(`Set Claude models: Opus=${opus.id}, Sonnet=${sonnet.id}, Haiku=${haiku.id}`);
35683
+ } else if (models.length > 0) {
35684
+ const model2 = models[0];
35685
+ logger3.log(`Only ${models.length} models available, falling back to defaults.`);
35686
+ settings.env["ANTHROPIC_DEFAULT_OPUS_MODEL"] = model2.id;
35687
+ settings.env["ANTHROPIC_DEFAULT_SONNET_MODEL"] = model2.id;
35688
+ settings.env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = model2.id;
35689
+ } else {
35690
+ logger3.log("No models available from routstr daemon.");
35691
+ }
35692
+ } catch (error) {
35693
+ logger3.error("Failed to fetch models for Claude Code integration:", error);
35694
+ }
35695
+ try {
35696
+ mkdirSync4(dirname5(configPath), { recursive: true });
35697
+ await writeFile5(configPath, JSON.stringify(settings, null, 2));
35698
+ logger3.log(`Successfully updated ${configPath} with routstr settings.`);
35699
+ } catch (error) {
35700
+ logger3.error(`Failed to write to ${configPath}:`, error);
35701
+ }
35702
+ }
35703
+
35704
+ // src/integrations/registry.ts
35705
+ var CLIENT_CONFIGS = {
35706
+ opencode: {
35707
+ clientId: "opencode",
35708
+ name: "OpenCode",
35709
+ configPath: join4(process.env.HOME || "", ".config/opencode/opencode.json")
35710
+ },
35711
+ "pi-agent": {
35712
+ clientId: "pi-agent",
35713
+ name: "Pi Agent",
35714
+ configPath: join4(process.env.HOME || "", ".pi/agent/models.json")
35715
+ },
35716
+ openclaw: {
35717
+ clientId: "openclaw",
35718
+ name: "OpenClaw",
35719
+ configPath: join4(process.env.HOME || "", ".openclaw/openclaw.json")
35720
+ },
35721
+ "claude-code": {
35722
+ clientId: "claude-code",
35723
+ name: "Claude Code",
35724
+ configPath: join4(process.env.HOME || "", ".claude/settings.json")
35725
+ }
35726
+ };
35727
+ var CLIENT_INTEGRATIONS = {
35728
+ opencode: installOpencodeIntegration,
35729
+ "pi-agent": installPiIntegration,
35730
+ openclaw: installOpenClawIntegration,
35731
+ "claude-code": installClaudeCodeIntegration
35732
+ };
35733
+ async function runIntegrationsForClients(clientIds, config) {
35734
+ for (const client2 of clientIds) {
35735
+ const integrationFn = CLIENT_INTEGRATIONS[client2.clientId];
35736
+ const integrationConfig = CLIENT_CONFIGS[client2.clientId];
35737
+ if (integrationFn && integrationConfig && client2.apiKey) {
35738
+ try {
35739
+ await integrationFn(config, client2.apiKey, integrationConfig);
35740
+ } catch (error) {
35741
+ console.error(`Integration failed for ${client2.clientId}:`, error);
35742
+ }
35743
+ }
35744
+ }
35745
+ }
35746
+
35747
+ // src/utils/clients.ts
35748
+ function getNpubSuffix(config) {
35749
+ if (!config.daemonUrl || !config.nsec)
35750
+ return null;
35751
+ try {
35752
+ const secretKey = parseSecretKey(config.nsec);
35753
+ const npub = npubFromSecretKey(secretKey);
35754
+ return npub.slice(-7);
35755
+ } catch {
35756
+ return null;
35757
+ }
35758
+ }
35759
+ function removeSuffixFromId(id, suffix) {
35760
+ const suffixStr = `-${suffix}`;
35761
+ if (id.endsWith(suffixStr)) {
35762
+ return id.slice(0, -suffixStr.length);
35763
+ }
35764
+ return id;
35765
+ }
35766
+ function getClientsFromStore(store) {
35767
+ const state = store.getState();
35768
+ const clientIds = state.clientIds || [];
35769
+ return clientIds.map((c) => ({
35770
+ clientId: c.clientId,
35771
+ name: c.name,
35772
+ apiKey: c.apiKey,
35773
+ createdAt: c.createdAt,
35774
+ lastUsed: c.lastUsed
35775
+ }));
35776
+ }
35777
+ async function getClientsList() {
35778
+ const config = await loadConfig();
35779
+ const result = await callDaemon("/clients");
35780
+ const clients = result.output?.clients;
35781
+ if (!clients) {
35782
+ return [];
35783
+ }
35784
+ const suffix = config.daemonUrl ? getNpubSuffix(config) : null;
35785
+ return clients.filter((c) => !suffix || c.id.endsWith(`-${suffix}`)).map((c) => ({
35786
+ clientId: suffix ? removeSuffixFromId(c.id, suffix) : c.id,
35787
+ name: c.name,
35788
+ apiKey: c.apiKey,
35789
+ createdAt: c.createdAt,
35790
+ lastUsed: c.lastUsed
35791
+ }));
35792
+ }
35793
+
35794
+ // src/daemon/http/index.ts
35256
35795
  function generateApiKey() {
35257
35796
  const bytes4 = randomBytes5(24);
35258
35797
  return `sk-${bytes4.toString("hex")}`;
@@ -35733,9 +36272,7 @@ function createDaemonRequestHandler(deps) {
35733
36272
  }
35734
36273
  if (req.method === "GET" && url2.pathname === "/clients") {
35735
36274
  try {
35736
- const state = deps.store.getState();
35737
- const clientIds = state.clientIds || [];
35738
- const clients = clientIds.map((c) => ({
36275
+ const clients = getClientsFromStore(deps.store).map((c) => ({
35739
36276
  id: c.clientId,
35740
36277
  name: c.name,
35741
36278
  apiKey: c.apiKey,
@@ -35760,6 +36297,7 @@ function createDaemonRequestHandler(deps) {
35760
36297
  const bodyText = await readBody(req);
35761
36298
  const body = bodyText ? JSON.parse(bodyText) : {};
35762
36299
  const name = body.name;
36300
+ const explicitId = body.id;
35763
36301
  if (!name || typeof name !== "string" || name.trim() === "") {
35764
36302
  res.writeHead(400, { "Content-Type": "application/json" });
35765
36303
  res.end(JSON.stringify({
@@ -35767,16 +36305,23 @@ function createDaemonRequestHandler(deps) {
35767
36305
  }));
35768
36306
  return;
35769
36307
  }
35770
- const clientId = name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
36308
+ if (!explicitId || typeof explicitId !== "string" || explicitId.trim() === "") {
36309
+ res.writeHead(400, { "Content-Type": "application/json" });
36310
+ res.end(JSON.stringify({
36311
+ error: "Missing required 'id' field (must be a non-empty string)."
36312
+ }));
36313
+ return;
36314
+ }
36315
+ const sanitizeId = (value) => value.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
36316
+ const clientId = sanitizeId(explicitId);
35771
36317
  if (!clientId) {
35772
36318
  res.writeHead(400, { "Content-Type": "application/json" });
35773
36319
  res.end(JSON.stringify({
35774
- error: "Invalid client name. Must contain alphanumeric characters."
36320
+ error: "Invalid client id. Must contain alphanumeric characters."
35775
36321
  }));
35776
36322
  return;
35777
36323
  }
35778
- const state = deps.store.getState();
35779
- const existingClients = state.clientIds || [];
36324
+ const existingClients = getClientsFromStore(deps.store);
35780
36325
  const existingClient = existingClients.find((c) => c.clientId === clientId);
35781
36326
  if (existingClient) {
35782
36327
  res.writeHead(409, { "Content-Type": "application/json" });
@@ -35826,8 +36371,7 @@ function createDaemonRequestHandler(deps) {
35826
36371
  }));
35827
36372
  return;
35828
36373
  }
35829
- const state = deps.store.getState();
35830
- const existingClients = state.clientIds || [];
36374
+ const existingClients = getClientsFromStore(deps.store);
35831
36375
  const index = existingClients.findIndex((c) => c.clientId === id);
35832
36376
  if (index === -1) {
35833
36377
  res.writeHead(404, { "Content-Type": "application/json" });
@@ -36040,362 +36584,18 @@ function createDaemonRequestHandler(deps) {
36040
36584
  };
36041
36585
  }
36042
36586
 
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
- }
36587
+ // src/integrations/index.ts
36588
+ async function refreshModelsAndIntegrations(getRoutstr21Models, config, label = "Scheduled") {
36589
+ await getRoutstr21Models(true);
36590
+ logger3.log(`${label} model refresh completed successfully.`);
36591
+ const clientIds = await getClientsList();
36592
+ if (clientIds.length > 0) {
36593
+ logger3.log(`Refreshing ${clientIds.length} client integration(s)...`);
36594
+ await runIntegrationsForClients(clientIds, config);
36595
+ logger3.log("Client integrations refreshed.");
36326
36596
  }
36327
36597
  }
36328
36598
 
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
36599
  // src/daemon/index.ts
36400
36600
  init_dist3();
36401
36601
  async function main() {
@@ -36407,7 +36607,8 @@ async function main() {
36407
36607
  const updatedConfig = { ...config, port, provider };
36408
36608
  saveDaemonConfig(updatedConfig);
36409
36609
  const sqliteDriver = await createBunSqliteDriver2(DB_PATH);
36410
- const { store } = await createSdkStore({ driver: sqliteDriver });
36610
+ const { store, hydrate } = createSdkStore({ driver: sqliteDriver });
36611
+ await hydrate;
36411
36612
  const { Database } = await import("bun:sqlite");
36412
36613
  const usageTrackingDriver = createBunSqliteUsageTrackingDriver2({
36413
36614
  dbPath: DB_PATH,
@@ -36445,7 +36646,7 @@ async function main() {
36445
36646
  }));
36446
36647
  Bun.write(PID_FILE, String(process.pid));
36447
36648
  try {
36448
- if (existsSync7(SOCKET_PATH)) {
36649
+ if (existsSync8(SOCKET_PATH)) {
36449
36650
  Bun.spawn(["rm", SOCKET_PATH]);
36450
36651
  }
36451
36652
  } catch {
@@ -36457,15 +36658,7 @@ async function main() {
36457
36658
  refreshInterval = setInterval(async () => {
36458
36659
  logger3.log("Running scheduled model refresh...");
36459
36660
  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
- }
36661
+ await refreshModelsAndIntegrations(getRoutstr21Models, updatedConfig, "Scheduled");
36469
36662
  } catch (error) {
36470
36663
  logger3.error("Scheduled model refresh failed:", error);
36471
36664
  }
@@ -36526,20 +36719,11 @@ async function main() {
36526
36719
  });
36527
36720
  server.listen(port, async () => {
36528
36721
  logger3.log(`Routstr daemon listening on http://localhost:${port}/v1`);
36529
- ensureProvidersBootstrapped().then(() => {
36722
+ ensureProvidersBootstrapped().then(async () => {
36530
36723
  startModelRefreshJob();
36531
36724
  startRefundJob();
36532
36725
  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
- }
36726
+ await refreshModelsAndIntegrations(getRoutstr21Models, updatedConfig, "Initial");
36543
36727
  }).catch((error) => {
36544
36728
  logger3.error("Initial model refresh failed:", error);
36545
36729
  startModelRefreshJob();