mu-agent 0.2.1 → 0.2.3
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/CHANGELOG.md +44 -0
- package/README.md +3 -7
- package/dist/bundle/chunks/anthropic.js +1 -1
- package/dist/bundle/chunks/chunk-FGOMFVJ2.js +2 -0
- package/dist/bundle/chunks/{chunk-BK7M6BDT.js → chunk-HPQUXAZK.js} +6 -6
- package/dist/bundle/chunks/openai-chatgpt.js +1 -1
- package/dist/bundle/chunks/openai-codex.js +2 -2
- package/dist/bundle/chunks/openrouter.js +1 -1
- package/dist/bundle/chunks/radius.js +1 -1
- package/dist/bundle/chunks/{virtual-modules-U6MRV5BC.js → virtual-modules-6BAN7L2H.js} +1 -1
- package/dist/bundle/cli-runtime.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/judge/dist/auth.js +60 -15
- package/judge/dist/mude.js +731 -126
- package/judge/dist/setup.js +383 -156
- package/judge/manifest.json +29 -30
- package/judge/mu/{chunk-6XRUK3PM.js → chunk-JZ4X4HQO.js} +1 -1
- package/judge/mu/{cloud-code-XR7SZMJC.js → cloud-code-PDKLWJEG.js} +1 -1
- package/judge/mu/index.js +42 -24
- package/kyrn/bin/kyrn-doctor +0 -1
- package/kyrn/bin/kyrn-ledger +0 -2
- package/kyrn/bin/mu.mjs +52 -18
- package/package.json +2 -2
- package/dist/bundle/chunks/chunk-AN7MSYCK.js +0 -2
package/judge/dist/setup.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createRequire as __muCreateRequire } from "node:module"; const require
|
|
|
2
2
|
|
|
3
3
|
// packages/kyrn-judge/src/setup/main.ts
|
|
4
4
|
import { constants as constants2 } from "node:os";
|
|
5
|
-
import { join as
|
|
5
|
+
import { join as join10 } from "node:path";
|
|
6
6
|
import {
|
|
7
7
|
applyHttpProxySettings,
|
|
8
8
|
configureHttpDispatcher,
|
|
@@ -54,9 +54,9 @@ function drop(lock) {
|
|
|
54
54
|
// packages/kyrn-judge/src/mude/command.ts
|
|
55
55
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
56
56
|
import { randomBytes } from "node:crypto";
|
|
57
|
-
import { existsSync as existsSync6, mkdirSync as
|
|
57
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync7, readdirSync as readdirSync2, readFileSync as readFileSync7, rmSync as rmSync4, statfsSync, writeFileSync as writeFileSync7 } from "node:fs";
|
|
58
58
|
import { homedir as homedir4, release, totalmem as totalmem2 } from "node:os";
|
|
59
|
-
import { join as
|
|
59
|
+
import { join as join8 } from "node:path";
|
|
60
60
|
|
|
61
61
|
// packages/kyrn-judge/src/mude/config.ts
|
|
62
62
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
@@ -154,21 +154,21 @@ function readEnvEntries(text) {
|
|
|
154
154
|
}
|
|
155
155
|
let body = rest.slice(1);
|
|
156
156
|
let end = closingQuote(body, quote);
|
|
157
|
-
const
|
|
157
|
+
const startedAt2 = index;
|
|
158
158
|
while (end < 0 && index + 1 < lines.length) {
|
|
159
159
|
body += `
|
|
160
160
|
${lines[++index]}`;
|
|
161
161
|
end = closingQuote(body, quote);
|
|
162
162
|
}
|
|
163
163
|
if (end < 0) {
|
|
164
|
-
index =
|
|
164
|
+
index = startedAt2;
|
|
165
165
|
continue;
|
|
166
166
|
}
|
|
167
167
|
const value = body.slice(0, end);
|
|
168
168
|
entries.push({
|
|
169
169
|
name,
|
|
170
170
|
value: quote === '"' ? value.replace(/\\(["\\$`])/g, "$1") : value,
|
|
171
|
-
first:
|
|
171
|
+
first: startedAt2,
|
|
172
172
|
last: index
|
|
173
173
|
});
|
|
174
174
|
}
|
|
@@ -432,8 +432,8 @@ var FILES = [
|
|
|
432
432
|
];
|
|
433
433
|
var MUDE_FILES = FILES.map(([path, size, sha256]) => ({ path, size, sha256 }));
|
|
434
434
|
var MUDE_BYTES = MUDE_FILES.reduce((sum, file) => sum + file.size, 0);
|
|
435
|
-
function
|
|
436
|
-
return `${endpoint.replace(/\/+$/, "")}/${
|
|
435
|
+
function fileUrl(repo, revision, path, endpoint = "https://huggingface.co") {
|
|
436
|
+
return `${endpoint.replace(/\/+$/, "")}/${repo}/resolve/${revision}/${path.split("/").map(encodeURIComponent).join("/")}`;
|
|
437
437
|
}
|
|
438
438
|
|
|
439
439
|
// packages/kyrn-judge/src/mude/server.ts
|
|
@@ -503,12 +503,21 @@ function tail(file, lines = 12) {
|
|
|
503
503
|
return "";
|
|
504
504
|
}
|
|
505
505
|
}
|
|
506
|
+
var StartStopped = class extends Error {
|
|
507
|
+
/** Whether the server went too: one this call started does, one that was running before does not. */
|
|
508
|
+
serverStopped;
|
|
509
|
+
constructor(serverStopped = false) {
|
|
510
|
+
super("Stopped while the mude 1.0 server was starting");
|
|
511
|
+
this.name = "StartStopped";
|
|
512
|
+
this.serverStopped = serverStopped;
|
|
513
|
+
}
|
|
514
|
+
};
|
|
506
515
|
async function startServer(paths, options) {
|
|
507
516
|
const sleep2 = options.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms)));
|
|
508
517
|
const timeoutMs = options.timeoutMs ?? 18e4;
|
|
509
518
|
const running = readPid(paths);
|
|
510
519
|
if (running && serverAlive(paths, running)) {
|
|
511
|
-
if (options.wait !== false) await untilReady(paths, running, options.port, timeoutMs, sleep2);
|
|
520
|
+
if (options.wait !== false) await untilReady(paths, running, options.port, timeoutMs, sleep2, options.signal);
|
|
512
521
|
return { pid: running, port: options.port, already: true };
|
|
513
522
|
}
|
|
514
523
|
if (!await portFree(options.port)) {
|
|
@@ -530,7 +539,7 @@ async function startServer(paths, options) {
|
|
|
530
539
|
"--port",
|
|
531
540
|
String(options.port),
|
|
532
541
|
"--name",
|
|
533
|
-
MUDE_MODEL,
|
|
542
|
+
options.name ?? MUDE_MODEL,
|
|
534
543
|
"--verify",
|
|
535
544
|
"fast"
|
|
536
545
|
],
|
|
@@ -557,12 +566,21 @@ async function startServer(paths, options) {
|
|
|
557
566
|
child.unref();
|
|
558
567
|
writeFileSync2(paths.pid, `${pid}
|
|
559
568
|
`, { mode: 384 });
|
|
560
|
-
if (options.wait !== false)
|
|
569
|
+
if (options.wait !== false) {
|
|
570
|
+
try {
|
|
571
|
+
await untilReady(paths, pid, options.port, timeoutMs, sleep2, options.signal);
|
|
572
|
+
} catch (error) {
|
|
573
|
+
if (!(error instanceof StartStopped)) throw error;
|
|
574
|
+
await stopServer(paths);
|
|
575
|
+
throw new StartStopped(true);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
561
578
|
return { pid, port: options.port, already: false };
|
|
562
579
|
}
|
|
563
|
-
async function untilReady(paths, pid, port2, timeoutMs, sleep2) {
|
|
580
|
+
async function untilReady(paths, pid, port2, timeoutMs, sleep2, signal) {
|
|
564
581
|
const deadline = Date.now() + timeoutMs;
|
|
565
582
|
while (Date.now() < deadline) {
|
|
583
|
+
if (signal?.aborted) throw new StartStopped();
|
|
566
584
|
if ((await health(port2))?.status === "ready") return;
|
|
567
585
|
if (!serverAlive(paths, pid)) {
|
|
568
586
|
rmSync(paths.pid, { force: true });
|
|
@@ -602,6 +620,11 @@ function serverMemory(pid) {
|
|
|
602
620
|
var ROUTED_AWAY = ["tool.description", "tool.injection"];
|
|
603
621
|
var MUDE = "mude";
|
|
604
622
|
var KEY_VARIABLE = "MU_JUDGE_MUDE_KEY";
|
|
623
|
+
var MUDE_JUDGE = {
|
|
624
|
+
name: MUDE,
|
|
625
|
+
keyVariable: KEY_VARIABLE,
|
|
626
|
+
entry: (port2) => port2 === DEFAULT_PORT ? void 0 : entryFor(port2)
|
|
627
|
+
};
|
|
605
628
|
function readState(paths) {
|
|
606
629
|
try {
|
|
607
630
|
const value = JSON.parse(readFileSync3(paths.state, "utf8"));
|
|
@@ -614,18 +637,22 @@ function writeState(paths, state) {
|
|
|
614
637
|
writeFileSync3(paths.state, `${JSON.stringify(state, null, 2)}
|
|
615
638
|
`, { mode: 384 });
|
|
616
639
|
}
|
|
617
|
-
function judgeSetting(files) {
|
|
640
|
+
function judgeSetting(files, name = MUDE) {
|
|
618
641
|
const file = readJsonFile(judgeConfigPath(files.agentDir));
|
|
619
642
|
if (file.state === "unreadable") return { enabled: false, localOnly: false, fallback: [], unreadable: true };
|
|
620
643
|
const config = file.state === "ok" ? file.value : {};
|
|
621
644
|
const tiers = tiersOf(config);
|
|
622
|
-
const enabled = tiers[0] ===
|
|
623
|
-
const fallback = tiers.filter((tier) => tier !==
|
|
645
|
+
const enabled = tiers[0] === name && !judgeOff(config);
|
|
646
|
+
const fallback = tiers.filter((tier) => tier !== name);
|
|
624
647
|
return { enabled, localOnly: enabled && fallback.length === 0, fallback };
|
|
625
648
|
}
|
|
626
|
-
function mudeInTiers(files) {
|
|
649
|
+
function mudeInTiers(files, name = MUDE) {
|
|
650
|
+
const file = readJsonFile(judgeConfigPath(files.agentDir));
|
|
651
|
+
return file.state === "ok" && tiersOf(file.value).includes(name);
|
|
652
|
+
}
|
|
653
|
+
function currentTiers(files) {
|
|
627
654
|
const file = readJsonFile(judgeConfigPath(files.agentDir));
|
|
628
|
-
return file.state === "ok"
|
|
655
|
+
return file.state === "ok" ? tiersOf(file.value) : [];
|
|
629
656
|
}
|
|
630
657
|
function readConfig(files) {
|
|
631
658
|
const path = judgeConfigPath(files.agentDir);
|
|
@@ -659,18 +686,20 @@ function withoutOwnRoutes(config, state) {
|
|
|
659
686
|
function entryFor(port2) {
|
|
660
687
|
return { ...BUILT_IN_JUDGES[MUDE], baseUrl: mudeBaseUrl(port2) };
|
|
661
688
|
}
|
|
662
|
-
function
|
|
689
|
+
function enableLocal(judge, paths, files, options) {
|
|
690
|
+
const name = judge.name;
|
|
663
691
|
const { path, config } = readConfig(files);
|
|
664
692
|
const state = readState(paths);
|
|
665
|
-
const current = tiersOf(config).filter((tier) => tier !==
|
|
693
|
+
const current = tiersOf(config).filter((tier) => tier !== name);
|
|
666
694
|
const before = current.length > 0 ? current : [...state?.previousTiers ?? ["jev"]];
|
|
667
|
-
const tiers = options.localOnly ? [
|
|
695
|
+
const tiers = options.localOnly ? [name] : [name, ...before.filter((tier) => tier !== name)];
|
|
668
696
|
const judgeWasOff = state?.judgeWasOff ?? judgeOff(config);
|
|
669
697
|
let next = withJudge(withTiers(withoutOwnRoutes(config, state), tiers), true);
|
|
670
698
|
const judges = judgesOf(next);
|
|
671
|
-
const
|
|
672
|
-
if (
|
|
673
|
-
else if (state?.wroteEntry) delete judges[
|
|
699
|
+
const written = judge.entry(options.port);
|
|
700
|
+
if (written) judges[name] = written;
|
|
701
|
+
else if (state?.wroteEntry) delete judges[name];
|
|
702
|
+
const entry = written !== void 0;
|
|
674
703
|
next = withSection(next, "judges", judges);
|
|
675
704
|
const fallback = tiers.slice(1);
|
|
676
705
|
const routes = recordOf(next.routes);
|
|
@@ -686,9 +715,9 @@ function enableMude(paths, files, options) {
|
|
|
686
715
|
const backups = new Backups(files.home, files.now ?? /* @__PURE__ */ new Date());
|
|
687
716
|
backups.save(path);
|
|
688
717
|
writeJsonFile(path, next);
|
|
689
|
-
if (envFileValue(readText(files.envFile),
|
|
718
|
+
if (envFileValue(readText(files.envFile), judge.keyVariable) !== options.key) {
|
|
690
719
|
backups.save(files.envFile);
|
|
691
|
-
writeEnvValue(files.envFile,
|
|
720
|
+
writeEnvValue(files.envFile, judge.keyVariable, options.key);
|
|
692
721
|
}
|
|
693
722
|
writeState(paths, {
|
|
694
723
|
port: options.port,
|
|
@@ -700,15 +729,18 @@ function enableMude(paths, files, options) {
|
|
|
700
729
|
return tiers;
|
|
701
730
|
}
|
|
702
731
|
function disableMude(paths, files, options = {}) {
|
|
732
|
+
return disableLocal(MUDE_JUDGE, paths, files, options);
|
|
733
|
+
}
|
|
734
|
+
function disableLocal(judge, paths, files, options = {}) {
|
|
703
735
|
const { path, config } = readConfig(files);
|
|
704
736
|
const state = readState(paths);
|
|
705
|
-
const rest = tiersOf(config).filter((tier) => tier !==
|
|
737
|
+
const rest = tiersOf(config).filter((tier) => tier !== judge.name);
|
|
706
738
|
const tiers = rest.length > 0 ? rest : [...state?.previousTiers ?? ["jev"]];
|
|
707
739
|
let next = withTiers(withoutOwnRoutes(config, state), tiers);
|
|
708
740
|
if (state?.judgeWasOff) next = withJudge(next, false);
|
|
709
741
|
if (options.removeEntry && state?.wroteEntry) {
|
|
710
742
|
const judges = judgesOf(next);
|
|
711
|
-
delete judges[
|
|
743
|
+
delete judges[judge.name];
|
|
712
744
|
next = withSection(next, "judges", judges);
|
|
713
745
|
}
|
|
714
746
|
new Backups(files.home, files.now ?? /* @__PURE__ */ new Date()).save(path);
|
|
@@ -748,6 +780,7 @@ import { open } from "node:fs/promises";
|
|
|
748
780
|
import { homedir as homedir2 } from "node:os";
|
|
749
781
|
import { dirname as dirname2, join as join4 } from "node:path";
|
|
750
782
|
import { pipeline } from "node:stream/promises";
|
|
783
|
+
import { setTimeout as wait } from "node:timers/promises";
|
|
751
784
|
var CHECKPOINT_BYTES = 32 * 1024 * 1024;
|
|
752
785
|
var RETRY_WAITS = [2e3, 5e3, 1e4, 2e4, 3e4];
|
|
753
786
|
function huggingFaceToken(env = process.env, home2 = homedir2()) {
|
|
@@ -773,6 +806,14 @@ async function sha256File(path) {
|
|
|
773
806
|
await pipeline(createReadStream(path), hash);
|
|
774
807
|
return hash.digest("hex");
|
|
775
808
|
}
|
|
809
|
+
var pinnedHash = (file) => file.sha256 ?? file.gitSha1 ?? "";
|
|
810
|
+
async function hashOf(path, file) {
|
|
811
|
+
if (file.sha256 || !file.gitSha1) return sha256File(path);
|
|
812
|
+
const hash = createHash("sha1");
|
|
813
|
+
hash.update(`blob ${statSync3(path).size}\0`);
|
|
814
|
+
await pipeline(createReadStream(path), hash);
|
|
815
|
+
return hash.digest("hex");
|
|
816
|
+
}
|
|
776
817
|
function readVerified(dir) {
|
|
777
818
|
try {
|
|
778
819
|
const value = JSON.parse(readFileSync4(join4(dir, ".mu-verified.json"), "utf8"));
|
|
@@ -783,7 +824,7 @@ function readVerified(dir) {
|
|
|
783
824
|
}
|
|
784
825
|
function knownGood(dir, file, verified) {
|
|
785
826
|
const seen = verified[file.path];
|
|
786
|
-
if (!seen || seen.sha256 !== file
|
|
827
|
+
if (!seen || seen.sha256 !== pinnedHash(file)) return false;
|
|
787
828
|
try {
|
|
788
829
|
const stats = statSync3(join4(dir, file.path));
|
|
789
830
|
return stats.size === file.size && stats.mtimeMs === seen.mtimeMs;
|
|
@@ -793,7 +834,7 @@ function knownGood(dir, file, verified) {
|
|
|
793
834
|
}
|
|
794
835
|
function remember(dir, verified, file) {
|
|
795
836
|
const stats = statSync3(join4(dir, file.path));
|
|
796
|
-
verified[file.path] = { size: stats.size, mtimeMs: stats.mtimeMs, sha256: file
|
|
837
|
+
verified[file.path] = { size: stats.size, mtimeMs: stats.mtimeMs, sha256: pinnedHash(file) };
|
|
797
838
|
writeFileSync4(join4(dir, ".mu-verified.json"), `${JSON.stringify(verified, null, 1)}
|
|
798
839
|
`);
|
|
799
840
|
}
|
|
@@ -844,7 +885,7 @@ async function downloadFiles(options) {
|
|
|
844
885
|
mkdirSync4(dirname2(target), { recursive: true });
|
|
845
886
|
if (existsSync4(target) && statSync3(target).size === file.size) {
|
|
846
887
|
options.onProgress?.({ file: file.path, checking: true, done: settled, total });
|
|
847
|
-
if (await
|
|
888
|
+
if (await hashOf(target, file) === pinnedHash(file)) {
|
|
848
889
|
remember(dir, verified, file);
|
|
849
890
|
settled += file.size;
|
|
850
891
|
continue;
|
|
@@ -855,17 +896,17 @@ async function downloadFiles(options) {
|
|
|
855
896
|
const onBytes = (bytes) => options.onProgress?.({ file: file.path, checking: false, done: settled + bytes, total });
|
|
856
897
|
let resumed = await fetchFile(options, file, part, onBytes);
|
|
857
898
|
options.onProgress?.({ file: file.path, checking: true, done: settled + file.size, total });
|
|
858
|
-
let actual = await
|
|
859
|
-
if (actual !== file
|
|
899
|
+
let actual = await hashOf(part, file);
|
|
900
|
+
if (actual !== pinnedHash(file) && resumed) {
|
|
860
901
|
dropPart(part);
|
|
861
902
|
resumed = await fetchFile(options, file, part, onBytes);
|
|
862
903
|
options.onProgress?.({ file: file.path, checking: true, done: settled + file.size, total });
|
|
863
|
-
actual = await
|
|
904
|
+
actual = await hashOf(part, file);
|
|
864
905
|
}
|
|
865
|
-
if (actual !== file
|
|
906
|
+
if (actual !== pinnedHash(file)) {
|
|
866
907
|
dropPart(part);
|
|
867
908
|
throw new DownloadError(
|
|
868
|
-
`${file.path} does not match its pinned sha256 (got ${actual.slice(0, 12)}\u2026, want ${file.
|
|
909
|
+
`${file.path} does not match its pinned ${file.sha256 ? "sha256" : "git hash"} (got ${actual.slice(0, 12)}\u2026, want ${pinnedHash(file).slice(0, 12)}\u2026); it was removed`,
|
|
869
910
|
"integrity"
|
|
870
911
|
);
|
|
871
912
|
}
|
|
@@ -899,7 +940,8 @@ function writeSaved(part, bytes) {
|
|
|
899
940
|
}
|
|
900
941
|
async function fetchFile(options, file, part, onBytes) {
|
|
901
942
|
const attempts = options.attempts ?? 8;
|
|
902
|
-
const sleep2 = options.sleep ?? ((ms) =>
|
|
943
|
+
const sleep2 = options.sleep ?? ((ms) => wait(ms, void 0, { signal: options.signal }).catch(() => {
|
|
944
|
+
}));
|
|
903
945
|
let resumed = false;
|
|
904
946
|
let failures = 0;
|
|
905
947
|
let lastError;
|
|
@@ -911,6 +953,12 @@ async function fetchFile(options, file, part, onBytes) {
|
|
|
911
953
|
return resumed;
|
|
912
954
|
} catch (error) {
|
|
913
955
|
if (error instanceof DownloadError && error.kind !== "network") throw error;
|
|
956
|
+
const code = error.code;
|
|
957
|
+
if (code === "ENOSPC" || code === "EDQUOT") {
|
|
958
|
+
throw new Error(
|
|
959
|
+
`The disk is full: couldn't write ${file.path}. Free up some space, then run mu judge mude install to resume`
|
|
960
|
+
);
|
|
961
|
+
}
|
|
914
962
|
lastError = error;
|
|
915
963
|
failures = resumeFrom(part, file.size, options.checkpointBytes) > before ? 1 : failures + 1;
|
|
916
964
|
if (failures >= attempts) break;
|
|
@@ -919,7 +967,7 @@ async function fetchFile(options, file, part, onBytes) {
|
|
|
919
967
|
}
|
|
920
968
|
const reason = lastError instanceof Error ? lastError.message : String(lastError);
|
|
921
969
|
throw new DownloadError(
|
|
922
|
-
`${file.path} could not be downloaded (${reason})
|
|
970
|
+
`${file.path} could not be downloaded (${reason}). Run mu judge mude install again to resume`,
|
|
923
971
|
"network"
|
|
924
972
|
);
|
|
925
973
|
}
|
|
@@ -949,7 +997,7 @@ async function fetchOnce(options, file, part, onBytes) {
|
|
|
949
997
|
});
|
|
950
998
|
if (response.status === 401 || response.status === 403 || response.status === 404) {
|
|
951
999
|
throw new DownloadError(
|
|
952
|
-
`Hugging Face
|
|
1000
|
+
`Hugging Face returned ${response.status} for ${file.path}: the model isn't public yet, or this machine's token can't access it`,
|
|
953
1001
|
"access"
|
|
954
1002
|
);
|
|
955
1003
|
}
|
|
@@ -1002,6 +1050,10 @@ async function fetchOnce(options, file, part, onBytes) {
|
|
|
1002
1050
|
}
|
|
1003
1051
|
}
|
|
1004
1052
|
|
|
1053
|
+
// packages/kyrn-judge/src/mude/models.ts
|
|
1054
|
+
import { mkdirSync as mkdirSync5, readdirSync, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
1055
|
+
import { join as join6 } from "node:path";
|
|
1056
|
+
|
|
1005
1057
|
// packages/kyrn-judge/src/mude/paths.ts
|
|
1006
1058
|
import { join as join5 } from "node:path";
|
|
1007
1059
|
function mudePaths(muHome2) {
|
|
@@ -1020,16 +1072,77 @@ function mudePaths(muHome2) {
|
|
|
1020
1072
|
pid: join5(root, "server.pid"),
|
|
1021
1073
|
log: join5(root, "server.log"),
|
|
1022
1074
|
state: join5(root, "state.json"),
|
|
1023
|
-
install: join5(root, "install.json")
|
|
1075
|
+
install: join5(root, "install.json"),
|
|
1076
|
+
installLock: join5(root, "install.lock")
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// packages/kyrn-judge/src/mude/models.ts
|
|
1081
|
+
function modelsDir(muHome2) {
|
|
1082
|
+
return join6(muHome2, "local-judge", "models");
|
|
1083
|
+
}
|
|
1084
|
+
function modelPaths(muHome2, record) {
|
|
1085
|
+
const shared = mudePaths(muHome2);
|
|
1086
|
+
const root = join6(modelsDir(muHome2), record.id);
|
|
1087
|
+
const model = join6(root, "model", record.revision);
|
|
1088
|
+
return {
|
|
1089
|
+
...shared,
|
|
1090
|
+
root,
|
|
1091
|
+
model,
|
|
1092
|
+
verified: join6(model, ".mu-verified.json"),
|
|
1093
|
+
key: join6(root, "key"),
|
|
1094
|
+
pid: join6(root, "server.pid"),
|
|
1095
|
+
log: join6(root, "server.log"),
|
|
1096
|
+
state: join6(root, "state.json"),
|
|
1097
|
+
install: join6(root, "install.json"),
|
|
1098
|
+
installLock: join6(root, "install.lock")
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
function isRecord2(value) {
|
|
1102
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1103
|
+
const record = value;
|
|
1104
|
+
return typeof record.id === "string" && typeof record.repo === "string" && typeof record.revision === "string" && typeof record.bytes === "number" && Array.isArray(record.files);
|
|
1105
|
+
}
|
|
1106
|
+
function listRecords(muHome2) {
|
|
1107
|
+
let names;
|
|
1108
|
+
try {
|
|
1109
|
+
names = readdirSync(modelsDir(muHome2));
|
|
1110
|
+
} catch {
|
|
1111
|
+
return [];
|
|
1112
|
+
}
|
|
1113
|
+
const records = [];
|
|
1114
|
+
for (const name of names.sort()) {
|
|
1115
|
+
try {
|
|
1116
|
+
const value = JSON.parse(readFileSync5(join6(modelsDir(muHome2), name, "model.json"), "utf8"));
|
|
1117
|
+
if (isRecord2(value) && value.id === name) records.push(value);
|
|
1118
|
+
} catch {
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
return records;
|
|
1122
|
+
}
|
|
1123
|
+
function keyVariable(id) {
|
|
1124
|
+
return `MU_JUDGE_${id.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}_KEY`;
|
|
1125
|
+
}
|
|
1126
|
+
function localJudgeOf(id) {
|
|
1127
|
+
return {
|
|
1128
|
+
name: id,
|
|
1129
|
+
keyVariable: keyVariable(id),
|
|
1130
|
+
entry: (port2) => ({
|
|
1131
|
+
type: "typesafe",
|
|
1132
|
+
baseUrl: mudeBaseUrl(port2),
|
|
1133
|
+
model: id,
|
|
1134
|
+
apiKeyEnv: keyVariable(id),
|
|
1135
|
+
timeoutMs: 4e3
|
|
1136
|
+
})
|
|
1024
1137
|
};
|
|
1025
1138
|
}
|
|
1026
1139
|
|
|
1027
1140
|
// packages/kyrn-judge/src/mude/python.ts
|
|
1028
1141
|
import { spawn as spawn2 } from "node:child_process";
|
|
1029
1142
|
import { createHash as createHash2 } from "node:crypto";
|
|
1030
|
-
import { accessSync, constants, existsSync as existsSync5, mkdirSync as
|
|
1143
|
+
import { accessSync, constants, existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync6, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "node:fs";
|
|
1031
1144
|
import { homedir as homedir3 } from "node:os";
|
|
1032
|
-
import { delimiter, join as
|
|
1145
|
+
import { delimiter, join as join7 } from "node:path";
|
|
1033
1146
|
var PYTHON_VERSION = "3.13";
|
|
1034
1147
|
var UV_RELEASE = {
|
|
1035
1148
|
version: "0.13.1",
|
|
@@ -1047,14 +1160,14 @@ function executable(path) {
|
|
|
1047
1160
|
function findUv(paths, env = process.env, home2 = homedir3()) {
|
|
1048
1161
|
const dirs = [
|
|
1049
1162
|
...(env.PATH ?? "").split(delimiter),
|
|
1050
|
-
|
|
1051
|
-
|
|
1163
|
+
join7(home2, ".local", "bin"),
|
|
1164
|
+
join7(home2, ".cargo", "bin"),
|
|
1052
1165
|
"/opt/homebrew/bin",
|
|
1053
1166
|
"/usr/local/bin",
|
|
1054
1167
|
paths.tools
|
|
1055
1168
|
];
|
|
1056
1169
|
for (const dir of dirs) {
|
|
1057
|
-
if (dir && executable(
|
|
1170
|
+
if (dir && executable(join7(dir, "uv"))) return join7(dir, "uv");
|
|
1058
1171
|
}
|
|
1059
1172
|
return void 0;
|
|
1060
1173
|
}
|
|
@@ -1084,36 +1197,47 @@ function run(command, args, options = {}) {
|
|
|
1084
1197
|
});
|
|
1085
1198
|
}
|
|
1086
1199
|
async function downloadUv(paths, options = {}) {
|
|
1087
|
-
|
|
1200
|
+
mkdirSync6(paths.tools, { recursive: true });
|
|
1088
1201
|
const archive = "uv.tar.gz";
|
|
1089
1202
|
options.onLine?.(`Downloading uv ${UV_RELEASE.version}`);
|
|
1090
|
-
const
|
|
1091
|
-
|
|
1092
|
-
|
|
1203
|
+
const timeoutMs = options.timeoutMs ?? 3e5;
|
|
1204
|
+
const timeout = AbortSignal.timeout(timeoutMs);
|
|
1205
|
+
let bytes;
|
|
1206
|
+
try {
|
|
1207
|
+
const response = await (options.fetch ?? fetch)(UV_RELEASE.url, {
|
|
1208
|
+
redirect: "follow",
|
|
1209
|
+
signal: options.signal ? AbortSignal.any([options.signal, timeout]) : timeout
|
|
1210
|
+
});
|
|
1211
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
1212
|
+
bytes = Buffer.from(await response.arrayBuffer());
|
|
1213
|
+
} catch (error) {
|
|
1214
|
+
const reason = timeout.aborted ? `no answer in ${timeoutMs / 1e3} s` : error instanceof Error ? error.message : String(error);
|
|
1215
|
+
throw new Error(`uv could not be downloaded from GitHub (${reason}): run mu judge mude install again`);
|
|
1216
|
+
}
|
|
1093
1217
|
const actual = createHash2("sha256").update(bytes).digest("hex");
|
|
1094
1218
|
if (actual !== UV_RELEASE.sha256)
|
|
1095
1219
|
throw new Error("The downloaded uv does not match its pinned sha256; nothing was installed");
|
|
1096
|
-
|
|
1220
|
+
writeFileSync6(join7(paths.tools, archive), bytes);
|
|
1097
1221
|
const unpacked = await run("/usr/bin/tar", [
|
|
1098
1222
|
"-xzf",
|
|
1099
|
-
|
|
1223
|
+
join7(paths.tools, archive),
|
|
1100
1224
|
"-C",
|
|
1101
1225
|
paths.tools,
|
|
1102
1226
|
"--strip-components",
|
|
1103
1227
|
"1"
|
|
1104
1228
|
]);
|
|
1105
|
-
rmSync3(
|
|
1106
|
-
if (unpacked.code !== 0 || !executable(
|
|
1107
|
-
throw new Error(`
|
|
1108
|
-
return
|
|
1229
|
+
rmSync3(join7(paths.tools, archive), { force: true });
|
|
1230
|
+
if (unpacked.code !== 0 || !executable(join7(paths.tools, "uv")))
|
|
1231
|
+
throw new Error(`Couldn't unpack uv: ${unpacked.output}`);
|
|
1232
|
+
return join7(paths.tools, "uv");
|
|
1109
1233
|
}
|
|
1110
1234
|
function lockDigest(lockFile) {
|
|
1111
|
-
return createHash2("sha256").update(
|
|
1235
|
+
return createHash2("sha256").update(readFileSync6(lockFile)).digest("hex");
|
|
1112
1236
|
}
|
|
1113
1237
|
function venvReady(paths, lockFile) {
|
|
1114
1238
|
if (!existsSync5(paths.python)) return false;
|
|
1115
1239
|
try {
|
|
1116
|
-
return
|
|
1240
|
+
return readFileSync6(paths.venvLock, "utf8").trim() === lockDigest(lockFile);
|
|
1117
1241
|
} catch {
|
|
1118
1242
|
return false;
|
|
1119
1243
|
}
|
|
@@ -1126,7 +1250,7 @@ async function ensureVenv(paths, lockFile, uv, options = {}) {
|
|
|
1126
1250
|
env,
|
|
1127
1251
|
onLine: options.onLine
|
|
1128
1252
|
});
|
|
1129
|
-
if (made.code !== 0) throw new Error(`
|
|
1253
|
+
if (made.code !== 0) throw new Error(`Couldn't create the Python environment (uv venv):
|
|
1130
1254
|
${made.output}`);
|
|
1131
1255
|
const installed2 = await run(
|
|
1132
1256
|
uv,
|
|
@@ -1145,9 +1269,9 @@ ${made.output}`);
|
|
|
1145
1269
|
{ env, onLine: options.onLine }
|
|
1146
1270
|
);
|
|
1147
1271
|
if (installed2.code !== 0)
|
|
1148
|
-
throw new Error(`mude's Python packages
|
|
1272
|
+
throw new Error(`Couldn't install mude's Python packages (uv pip install):
|
|
1149
1273
|
${installed2.output}`);
|
|
1150
|
-
|
|
1274
|
+
writeFileSync6(paths.venvLock, `${lockDigest(lockFile)}
|
|
1151
1275
|
`);
|
|
1152
1276
|
}
|
|
1153
1277
|
|
|
@@ -1156,7 +1280,7 @@ var USAGE = `mu judge mude: mude 1.0, mu's own decision model optimized for codi
|
|
|
1156
1280
|
(Apple silicon, macOS 14 or later)
|
|
1157
1281
|
|
|
1158
1282
|
mu judge mude install [--local-only] download it (${(MUDE_BYTES / 1e9).toFixed(1)} GB), set it up, start it and make it the judge;
|
|
1159
|
-
after a stop (Ctrl-C, a lost connection, a power cut), it
|
|
1283
|
+
after a stop (Ctrl-C, a lost connection, a power cut), it picks up where it left off
|
|
1160
1284
|
mu judge mude enable [--local-only | --with-fallback]
|
|
1161
1285
|
make it the judge again, or change what happens when it can't answer
|
|
1162
1286
|
mu judge mude disable back to the judges from before, and stop its server
|
|
@@ -1185,7 +1309,7 @@ function jsonOut(write) {
|
|
|
1185
1309
|
error: (message, kind) => emit({ type: "error", message, kind })
|
|
1186
1310
|
};
|
|
1187
1311
|
}
|
|
1188
|
-
function humanOut(write, tty) {
|
|
1312
|
+
function humanOut(write, tty, pkg = MUDE_PACKAGE) {
|
|
1189
1313
|
let shown = -1;
|
|
1190
1314
|
return {
|
|
1191
1315
|
step: (_step, message) => {
|
|
@@ -1209,7 +1333,7 @@ function humanOut(write, tty) {
|
|
|
1209
1333
|
},
|
|
1210
1334
|
line: (text) => write(` ${text}
|
|
1211
1335
|
`),
|
|
1212
|
-
status: (status2) => write(describe(status2)),
|
|
1336
|
+
status: (status2) => write(describe(status2, pkg)),
|
|
1213
1337
|
error: (message) => {
|
|
1214
1338
|
if (shown >= 0 && tty) write("\n");
|
|
1215
1339
|
shown = -1;
|
|
@@ -1218,31 +1342,43 @@ function humanOut(write, tty) {
|
|
|
1218
1342
|
}
|
|
1219
1343
|
};
|
|
1220
1344
|
}
|
|
1221
|
-
function describe(status2) {
|
|
1345
|
+
function describe(status2, pkg) {
|
|
1222
1346
|
const lines = [];
|
|
1223
1347
|
if (!status2.supported) {
|
|
1224
|
-
lines.push(
|
|
1348
|
+
lines.push(`${pkg.label} can't run here: ${status2.problems.join("; ")}`);
|
|
1225
1349
|
return `${lines.join("\n")}
|
|
1226
1350
|
`;
|
|
1227
1351
|
}
|
|
1228
1352
|
lines.push(
|
|
1229
|
-
`model ${status2.installed ? `installed (${
|
|
1353
|
+
`model ${status2.installed ? `installed (${pkg.repo} @ ${pkg.revision.slice(0, 7)})` : status2.downloadedBytes > 0 ? `partly downloaded (${(status2.downloadedBytes / 1e9).toFixed(1)} of ${(pkg.bytes / 1e9).toFixed(1)} GB): ${pkg.install} resumes it` : `not installed: ${pkg.install}`}`
|
|
1230
1354
|
);
|
|
1231
|
-
if (status2.installed && !status2.pythonReady) lines.push(
|
|
1355
|
+
if (status2.installed && !status2.pythonReady) lines.push(`python not set up: ${pkg.install}`);
|
|
1232
1356
|
lines.push(
|
|
1233
1357
|
`server ${status2.ready ? `running on 127.0.0.1:${status2.port}${status2.memoryBytes ? ` \xB7 ${(status2.memoryBytes / 1e9).toFixed(1)} GB of memory` : ""}` : status2.running ? `starting on 127.0.0.1:${status2.port}` : "stopped"}`
|
|
1234
1358
|
);
|
|
1235
1359
|
if (status2.enabled) {
|
|
1236
1360
|
lines.push(
|
|
1237
|
-
status2.localOnly ?
|
|
1361
|
+
status2.localOnly ? `judge ${pkg.label} only: a decision it cannot answer takes its fallback, and nothing goes to a hosted judge` : `judge ${pkg.label}, then ${status2.fallback.join(" > ")} for what it cannot answer (what those read leaves this machine)`
|
|
1238
1362
|
);
|
|
1239
1363
|
} else {
|
|
1240
|
-
lines.push(`judge ${status2.installed ?
|
|
1364
|
+
lines.push(`judge ${status2.installed ? `not enabled: ${pkg.again("enable")}` : "not enabled"}`);
|
|
1241
1365
|
}
|
|
1242
1366
|
lines.push(`folder ${status2.folder}`);
|
|
1243
1367
|
return `${lines.join("\n")}
|
|
1244
1368
|
`;
|
|
1245
1369
|
}
|
|
1370
|
+
var MUDE_PACKAGE = {
|
|
1371
|
+
label: "mude 1.0",
|
|
1372
|
+
repo: MUDE_REPO,
|
|
1373
|
+
revision: MUDE_REVISION,
|
|
1374
|
+
files: MUDE_FILES,
|
|
1375
|
+
bytes: MUDE_BYTES,
|
|
1376
|
+
judge: MUDE_JUDGE,
|
|
1377
|
+
served: MUDE_MODEL,
|
|
1378
|
+
install: "mu judge mude install",
|
|
1379
|
+
again: (what) => `mu judge mude ${what}`
|
|
1380
|
+
};
|
|
1381
|
+
var pkgOf = (ctx) => ctx.pkg ?? MUDE_PACKAGE;
|
|
1246
1382
|
function problems(options = {}) {
|
|
1247
1383
|
const platform = options.platform ?? process.platform;
|
|
1248
1384
|
if (platform !== "darwin") return ["it needs a Mac with Apple silicon (an M1 or newer)"];
|
|
@@ -1269,7 +1405,7 @@ function appleSilicon() {
|
|
|
1269
1405
|
function freeBytes(path) {
|
|
1270
1406
|
let dir = path;
|
|
1271
1407
|
while (!existsSync6(dir)) {
|
|
1272
|
-
const parent =
|
|
1408
|
+
const parent = join8(dir, "..");
|
|
1273
1409
|
if (parent === dir) return void 0;
|
|
1274
1410
|
dir = parent;
|
|
1275
1411
|
}
|
|
@@ -1285,24 +1421,59 @@ function installLocalOnly(ctx, args) {
|
|
|
1285
1421
|
if (args.includes("--local-only")) return true;
|
|
1286
1422
|
if (args.includes("--with-fallback")) return false;
|
|
1287
1423
|
try {
|
|
1288
|
-
const value = JSON.parse(
|
|
1424
|
+
const value = JSON.parse(readFileSync7(ctx.paths.install, "utf8"));
|
|
1289
1425
|
return typeof value === "object" && value !== null && "localOnly" in value && value.localOnly === true;
|
|
1290
1426
|
} catch {
|
|
1291
1427
|
return false;
|
|
1292
1428
|
}
|
|
1293
1429
|
}
|
|
1430
|
+
function startedAt(pid) {
|
|
1431
|
+
try {
|
|
1432
|
+
return execFileSync2("/bin/ps", ["-p", String(pid), "-o", "lstart="], { encoding: "utf8" }).trim() || void 0;
|
|
1433
|
+
} catch {
|
|
1434
|
+
return void 0;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
function holdInstall(paths, pkg = MUDE_PACKAGE) {
|
|
1438
|
+
mkdirSync7(paths.root, { recursive: true });
|
|
1439
|
+
const mine = `${JSON.stringify({ pid: process.pid, started: startedAt(process.pid) ?? "" })}
|
|
1440
|
+
`;
|
|
1441
|
+
try {
|
|
1442
|
+
writeFileSync7(paths.installLock, mine, { flag: "wx" });
|
|
1443
|
+
} catch (error) {
|
|
1444
|
+
if (error.code !== "EEXIST") throw error;
|
|
1445
|
+
let holder = {};
|
|
1446
|
+
try {
|
|
1447
|
+
holder = JSON.parse(readFileSync7(paths.installLock, "utf8"));
|
|
1448
|
+
} catch {
|
|
1449
|
+
}
|
|
1450
|
+
const pid = typeof holder.pid === "number" ? holder.pid : 0;
|
|
1451
|
+
if (pid > 0 && pid !== process.pid && holder.started && startedAt(pid) === holder.started) {
|
|
1452
|
+
throw new Error(
|
|
1453
|
+
`Another ${pkg.label} install is running (process ${pid}). Wait for it to finish, or stop it, then run ${pkg.install} again`
|
|
1454
|
+
);
|
|
1455
|
+
}
|
|
1456
|
+
writeFileSync7(paths.installLock, mine);
|
|
1457
|
+
}
|
|
1458
|
+
return () => {
|
|
1459
|
+
try {
|
|
1460
|
+
if (readFileSync7(paths.installLock, "utf8") === mine) rmSync4(paths.installLock, { force: true });
|
|
1461
|
+
} catch {
|
|
1462
|
+
}
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1294
1465
|
function installed(ctx) {
|
|
1295
|
-
return missingFiles(ctx.paths.model,
|
|
1466
|
+
return missingFiles(ctx.paths.model, pkgOf(ctx).files).length === 0;
|
|
1296
1467
|
}
|
|
1297
1468
|
function key(ctx) {
|
|
1298
1469
|
try {
|
|
1299
|
-
const existing =
|
|
1470
|
+
const existing = readFileSync7(ctx.paths.key, "utf8").trim();
|
|
1300
1471
|
if (existing) return existing;
|
|
1301
1472
|
} catch {
|
|
1302
1473
|
}
|
|
1303
|
-
|
|
1474
|
+
mkdirSync7(ctx.paths.root, { recursive: true });
|
|
1304
1475
|
const made = randomBytes(32).toString("hex");
|
|
1305
|
-
|
|
1476
|
+
writeFileSync7(ctx.paths.key, `${made}
|
|
1306
1477
|
`, { mode: 384 });
|
|
1307
1478
|
return made;
|
|
1308
1479
|
}
|
|
@@ -1312,18 +1483,20 @@ async function port(ctx) {
|
|
|
1312
1483
|
return choosePort(DEFAULT_PORT);
|
|
1313
1484
|
}
|
|
1314
1485
|
async function status(ctx) {
|
|
1486
|
+
const pkg = pkgOf(ctx);
|
|
1315
1487
|
const found = problems();
|
|
1316
1488
|
const state = readState(ctx.paths);
|
|
1317
1489
|
const at = state?.port ?? DEFAULT_PORT;
|
|
1318
1490
|
const pid = readPid(ctx.paths);
|
|
1319
1491
|
const running = serverAlive(ctx.paths, pid);
|
|
1320
1492
|
const ready = running && (await health(at))?.status === "ready";
|
|
1321
|
-
const setting = judgeSetting(ctx.files);
|
|
1493
|
+
const setting = judgeSetting(ctx.files, pkg.judge.name);
|
|
1322
1494
|
return {
|
|
1495
|
+
name: pkg.judge.name,
|
|
1323
1496
|
supported: found.length === 0,
|
|
1324
1497
|
problems: found,
|
|
1325
1498
|
installed: installed(ctx),
|
|
1326
|
-
downloadedBytes: downloadedBytes(ctx.paths.model,
|
|
1499
|
+
downloadedBytes: downloadedBytes(ctx.paths.model, pkg.files),
|
|
1327
1500
|
pythonReady: venvReady(ctx.paths, ctx.lockFile),
|
|
1328
1501
|
running,
|
|
1329
1502
|
ready,
|
|
@@ -1333,31 +1506,33 @@ async function status(ctx) {
|
|
|
1333
1506
|
enabled: setting.enabled,
|
|
1334
1507
|
localOnly: setting.localOnly,
|
|
1335
1508
|
fallback: setting.fallback,
|
|
1336
|
-
model: `${
|
|
1509
|
+
model: `${pkg.repo}@${pkg.revision}`,
|
|
1337
1510
|
folder: ctx.paths.root,
|
|
1338
1511
|
log: ctx.paths.log
|
|
1339
1512
|
};
|
|
1340
1513
|
}
|
|
1341
1514
|
async function ensureInstalled(ctx, out) {
|
|
1342
|
-
const
|
|
1515
|
+
const pkg = pkgOf(ctx);
|
|
1516
|
+
const missing = missingFiles(ctx.paths.model, pkg.files);
|
|
1343
1517
|
if (missing.length > 0) {
|
|
1344
1518
|
const need = missing.reduce((sum, file) => sum + file.size, 0);
|
|
1519
|
+
const already = downloadedBytes(ctx.paths.model, pkg.files);
|
|
1345
1520
|
const free = freeBytes(ctx.paths.root);
|
|
1346
|
-
|
|
1521
|
+
const room = pkg.bytes - already + 2e9;
|
|
1522
|
+
if (free !== void 0 && free < room) {
|
|
1347
1523
|
throw new Error(
|
|
1348
|
-
`Not enough free disk space:
|
|
1524
|
+
`Not enough free disk space: ${pkg.label} needs about ${gb(room)} GB, and only ${gb(free)} GB are free. Free up some space, then run ${pkg.install} again`
|
|
1349
1525
|
);
|
|
1350
1526
|
}
|
|
1351
|
-
const already = downloadedBytes(ctx.paths.model, MUDE_FILES);
|
|
1352
1527
|
out.step(
|
|
1353
1528
|
"download",
|
|
1354
|
-
already > 0 ? `
|
|
1529
|
+
already > 0 ? `Resuming the ${pkg.label} download (${pkg.repo}): ${gb(already)} of ${gb(pkg.bytes)} GB already downloaded` : `Downloading ${pkg.label} (${pkg.repo}, ${gb(need)} GB) and checking every file`
|
|
1355
1530
|
);
|
|
1356
1531
|
try {
|
|
1357
1532
|
await downloadFiles({
|
|
1358
1533
|
dir: ctx.paths.model,
|
|
1359
|
-
files:
|
|
1360
|
-
url: (path) =>
|
|
1534
|
+
files: pkg.files,
|
|
1535
|
+
url: (path) => fileUrl(pkg.repo, pkg.revision, path, ctx.env.HF_ENDPOINT || void 0),
|
|
1361
1536
|
token: huggingFaceToken(ctx.env),
|
|
1362
1537
|
onProgress: (event) => out.progress(event),
|
|
1363
1538
|
signal: ctx.signal
|
|
@@ -1365,7 +1540,7 @@ async function ensureInstalled(ctx, out) {
|
|
|
1365
1540
|
} catch (error) {
|
|
1366
1541
|
if (error instanceof DownloadError && error.kind === "aborted") {
|
|
1367
1542
|
throw new DownloadError(
|
|
1368
|
-
`Stopped with ${gb(downloadedBytes(ctx.paths.model,
|
|
1543
|
+
`Stopped with ${gb(downloadedBytes(ctx.paths.model, pkg.files))} of ${gb(pkg.bytes)} GB downloaded. Run ${pkg.install} to resume`,
|
|
1369
1544
|
"aborted"
|
|
1370
1545
|
);
|
|
1371
1546
|
}
|
|
@@ -1373,33 +1548,99 @@ async function ensureInstalled(ctx, out) {
|
|
|
1373
1548
|
}
|
|
1374
1549
|
}
|
|
1375
1550
|
if (!venvReady(ctx.paths, ctx.lockFile)) {
|
|
1376
|
-
out.step("python",
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1551
|
+
out.step("python", `Setting up the Python environment for ${pkg.label} (MLX, transformers; about 1 GB, once)`);
|
|
1552
|
+
try {
|
|
1553
|
+
let uv = findUv(ctx.paths, ctx.env);
|
|
1554
|
+
if (!uv) uv = await downloadUv(ctx.paths, { onLine: (line) => out.line(line), signal: ctx.signal });
|
|
1555
|
+
await ensureVenv(ctx.paths, ctx.lockFile, uv, { onLine: (line) => out.line(line), env: ctx.env });
|
|
1556
|
+
} catch (error) {
|
|
1557
|
+
if (ctx.signal?.aborted) {
|
|
1558
|
+
throw new DownloadError(
|
|
1559
|
+
`Stopped while setting up the Python environment. Run ${pkg.install} to resume`,
|
|
1560
|
+
"aborted"
|
|
1561
|
+
);
|
|
1562
|
+
}
|
|
1563
|
+
throw error;
|
|
1564
|
+
}
|
|
1380
1565
|
}
|
|
1381
1566
|
}
|
|
1382
1567
|
function requireInstalled(ctx) {
|
|
1383
1568
|
if (!installed(ctx) || !venvReady(ctx.paths, ctx.lockFile)) {
|
|
1384
|
-
|
|
1569
|
+
const pkg = pkgOf(ctx);
|
|
1570
|
+
throw new Error(`${pkg.label} is not installed here yet: ${pkg.install}`);
|
|
1385
1571
|
}
|
|
1386
1572
|
}
|
|
1387
|
-
async function start(ctx, out,
|
|
1573
|
+
async function start(ctx, out, command, wait2 = true) {
|
|
1574
|
+
const pkg = pkgOf(ctx);
|
|
1388
1575
|
const at = await port(ctx);
|
|
1389
1576
|
out.step(
|
|
1390
1577
|
"start",
|
|
1391
|
-
serverAlive(ctx.paths) ? `The
|
|
1578
|
+
serverAlive(ctx.paths) ? `The ${pkg.label} server is running on 127.0.0.1:${at}` : `Starting the ${pkg.label} server on 127.0.0.1:${at} (loading the model takes a few seconds)`
|
|
1392
1579
|
);
|
|
1393
|
-
|
|
1580
|
+
try {
|
|
1581
|
+
await startServer(ctx.paths, { port: at, key: key(ctx), wait: wait2, signal: ctx.signal, name: pkg.served });
|
|
1582
|
+
} catch (error) {
|
|
1583
|
+
if (error instanceof StartStopped) {
|
|
1584
|
+
throw new DownloadError(
|
|
1585
|
+
error.serverStopped ? `Stopped while the ${pkg.label} server was starting, and stopped the server too. Run ${pkg.again(command)} again to finish` : `Stopped waiting for the ${pkg.label} server, which is still starting. Run ${pkg.again(command)} again to finish`,
|
|
1586
|
+
"aborted"
|
|
1587
|
+
);
|
|
1588
|
+
}
|
|
1589
|
+
throw error;
|
|
1590
|
+
}
|
|
1394
1591
|
return at;
|
|
1395
1592
|
}
|
|
1396
|
-
function
|
|
1397
|
-
const
|
|
1593
|
+
function others(ctx) {
|
|
1594
|
+
const name = pkgOf(ctx).judge.name;
|
|
1595
|
+
const home2 = ctx.files.home;
|
|
1596
|
+
const found = [];
|
|
1597
|
+
if (name !== MUDE) found.push({ judge: MUDE_JUDGE, paths: mudePaths(home2) });
|
|
1598
|
+
for (const record of listRecords(home2)) {
|
|
1599
|
+
if (record.id !== name) found.push({ judge: localJudgeOf(record.id), paths: modelPaths(home2, record) });
|
|
1600
|
+
}
|
|
1601
|
+
return found;
|
|
1602
|
+
}
|
|
1603
|
+
async function stopOthers(ctx) {
|
|
1604
|
+
for (const other of others(ctx)) await stopServer(other.paths);
|
|
1605
|
+
}
|
|
1606
|
+
async function enable(ctx, out, at, localOnly) {
|
|
1607
|
+
const pkg = pkgOf(ctx);
|
|
1608
|
+
const tiers = currentTiers(ctx.files);
|
|
1609
|
+
for (const other of others(ctx)) {
|
|
1610
|
+
if (tiers.includes(other.judge.name)) disableLocal(other.judge, other.paths, ctx.files);
|
|
1611
|
+
}
|
|
1612
|
+
const order = enableLocal(pkg.judge, ctx.paths, ctx.files, { port: at, key: key(ctx), localOnly });
|
|
1398
1613
|
out.step(
|
|
1399
1614
|
"enable",
|
|
1400
|
-
localOnly ?
|
|
1615
|
+
localOnly ? `${pkg.label} is the judge, on its own. New mu sessions use it; one that is open now picks it up when it starts again.` : `${pkg.label} is the judge, with ${order.slice(1).join(" > ")} behind it for what it cannot answer. New mu sessions use it.`
|
|
1401
1616
|
);
|
|
1402
1617
|
}
|
|
1618
|
+
async function install(ctx, out, args, holdAlso) {
|
|
1619
|
+
const localOnly = installLocalOnly(ctx, args);
|
|
1620
|
+
const release2 = holdInstall(ctx.paths, pkgOf(ctx));
|
|
1621
|
+
let releaseAlso;
|
|
1622
|
+
try {
|
|
1623
|
+
if (holdAlso && !venvReady(ctx.paths, ctx.lockFile)) releaseAlso = holdInstall(holdAlso);
|
|
1624
|
+
writeFileSync7(ctx.paths.install, `${JSON.stringify({ localOnly })}
|
|
1625
|
+
`);
|
|
1626
|
+
await ensureInstalled(ctx, out);
|
|
1627
|
+
await stopOthers(ctx);
|
|
1628
|
+
const at = await start(ctx, out, "install");
|
|
1629
|
+
await enable(ctx, out, at, localOnly);
|
|
1630
|
+
rmSync4(ctx.paths.install, { force: true });
|
|
1631
|
+
} finally {
|
|
1632
|
+
releaseAlso?.();
|
|
1633
|
+
release2();
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
async function enableCommand(ctx, out, args) {
|
|
1637
|
+
requireInstalled(ctx);
|
|
1638
|
+
const current = judgeSetting(ctx.files, pkgOf(ctx).judge.name);
|
|
1639
|
+
const localOnly = args.includes("--local-only") ? true : args.includes("--with-fallback") ? false : current.enabled && current.localOnly;
|
|
1640
|
+
await stopOthers(ctx);
|
|
1641
|
+
const at = await start(ctx, out, "enable");
|
|
1642
|
+
await enable(ctx, out, at, localOnly);
|
|
1643
|
+
}
|
|
1403
1644
|
async function main(argv, ctx, write, tty = false) {
|
|
1404
1645
|
const json = argv.includes("--json");
|
|
1405
1646
|
const args = argv.filter((arg) => arg !== "--json");
|
|
@@ -1426,25 +1667,12 @@ async function main(argv, ctx, write, tty = false) {
|
|
|
1426
1667
|
return 1;
|
|
1427
1668
|
}
|
|
1428
1669
|
switch (command) {
|
|
1429
|
-
case "install":
|
|
1430
|
-
|
|
1431
|
-
mkdirSync6(ctx.paths.root, { recursive: true });
|
|
1432
|
-
writeFileSync6(ctx.paths.install, `${JSON.stringify({ localOnly })}
|
|
1433
|
-
`);
|
|
1434
|
-
await ensureInstalled(ctx, out);
|
|
1435
|
-
const at = await start(ctx, out);
|
|
1436
|
-
enable(ctx, out, at, localOnly);
|
|
1437
|
-
rmSync4(ctx.paths.install, { force: true });
|
|
1670
|
+
case "install":
|
|
1671
|
+
await install(ctx, out, args);
|
|
1438
1672
|
break;
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
requireInstalled(ctx);
|
|
1442
|
-
const current = judgeSetting(ctx.files);
|
|
1443
|
-
const localOnly = flag("--local-only") ? true : flag("--with-fallback") ? false : current.enabled && current.localOnly;
|
|
1444
|
-
const at = await start(ctx, out);
|
|
1445
|
-
enable(ctx, out, at, localOnly);
|
|
1673
|
+
case "enable":
|
|
1674
|
+
await enableCommand(ctx, out, args);
|
|
1446
1675
|
break;
|
|
1447
|
-
}
|
|
1448
1676
|
case "disable": {
|
|
1449
1677
|
if (!mudeInTiers(ctx.files)) {
|
|
1450
1678
|
const stopped2 = await stopServer(ctx.paths);
|
|
@@ -1461,7 +1689,7 @@ async function main(argv, ctx, write, tty = false) {
|
|
|
1461
1689
|
}
|
|
1462
1690
|
case "start": {
|
|
1463
1691
|
requireInstalled(ctx);
|
|
1464
|
-
await start(ctx, out, !flag("--no-wait"));
|
|
1692
|
+
await start(ctx, out, "start", !flag("--no-wait"));
|
|
1465
1693
|
break;
|
|
1466
1694
|
}
|
|
1467
1695
|
case "stop": {
|
|
@@ -1475,8 +1703,20 @@ async function main(argv, ctx, write, tty = false) {
|
|
|
1475
1703
|
out.step("disable", `mude 1.0 is no longer the judge: ${tiers.join(" > ")} again.`);
|
|
1476
1704
|
}
|
|
1477
1705
|
await stopServer(ctx.paths);
|
|
1478
|
-
|
|
1479
|
-
|
|
1706
|
+
if (listRecords(ctx.files.home).length > 0) {
|
|
1707
|
+
for (const name of existsSync6(ctx.paths.root) ? readdirSync2(ctx.paths.root) : []) {
|
|
1708
|
+
const path = join8(ctx.paths.root, name);
|
|
1709
|
+
if (path !== ctx.paths.venv && path !== ctx.paths.tools)
|
|
1710
|
+
rmSync4(path, { recursive: true, force: true });
|
|
1711
|
+
}
|
|
1712
|
+
out.step(
|
|
1713
|
+
"uninstall",
|
|
1714
|
+
`Deleted mude 1.0 and its key from ${ctx.paths.root}. Its Python environment stays there for the other decision models.`
|
|
1715
|
+
);
|
|
1716
|
+
} else {
|
|
1717
|
+
rmSync4(ctx.paths.root, { recursive: true, force: true });
|
|
1718
|
+
out.step("uninstall", `Deleted ${ctx.paths.root}: the model, its Python environment and its key.`);
|
|
1719
|
+
}
|
|
1480
1720
|
break;
|
|
1481
1721
|
}
|
|
1482
1722
|
default:
|
|
@@ -1494,12 +1734,12 @@ ${USAGE}`, "usage");
|
|
|
1494
1734
|
}
|
|
1495
1735
|
}
|
|
1496
1736
|
function contextFromEnv(env = process.env, home2 = homedir4()) {
|
|
1497
|
-
const muHome2 = env.MU_MUDE_HOME ||
|
|
1498
|
-
const agentDir2 = env.MU_AGENT_DIR ||
|
|
1737
|
+
const muHome2 = env.MU_MUDE_HOME || join8(home2, ".mu");
|
|
1738
|
+
const agentDir2 = env.MU_AGENT_DIR || join8(muHome2, "agent");
|
|
1499
1739
|
return {
|
|
1500
1740
|
paths: mudePaths(muHome2),
|
|
1501
|
-
files: { agentDir: agentDir2, home: muHome2, envFile: env.MU_MUDE_ENV_FILE ||
|
|
1502
|
-
lockFile: env.MU_MUDE_LOCK ||
|
|
1741
|
+
files: { agentDir: agentDir2, home: muHome2, envFile: env.MU_MUDE_ENV_FILE || join8(muHome2, ".env") },
|
|
1742
|
+
lockFile: env.MU_MUDE_LOCK || join8(import.meta.dirname, "..", "..", "..", "..", "kyrn", "local-judge", "mude-requirements.lock.txt"),
|
|
1503
1743
|
env
|
|
1504
1744
|
};
|
|
1505
1745
|
}
|
|
@@ -1701,8 +1941,8 @@ async function readAll(input = process.stdin) {
|
|
|
1701
1941
|
}
|
|
1702
1942
|
|
|
1703
1943
|
// packages/kyrn-judge/src/setup/wizard.ts
|
|
1704
|
-
import { mkdirSync as
|
|
1705
|
-
import { join as
|
|
1944
|
+
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "node:fs";
|
|
1945
|
+
import { join as join9 } from "node:path";
|
|
1706
1946
|
|
|
1707
1947
|
// packages/kyrn-judge/src/setup/check.ts
|
|
1708
1948
|
var CHECK_TIMEOUT_MS = 1e4;
|
|
@@ -2502,14 +2742,6 @@ var Setup = class {
|
|
|
2502
2742
|
SETUP_EXIT.usage
|
|
2503
2743
|
);
|
|
2504
2744
|
}
|
|
2505
|
-
if (!args.firstRun) {
|
|
2506
|
-
this.io.say(
|
|
2507
|
-
this.t({
|
|
2508
|
-
zh: "mu setup\uFF1A\u7ED9 mu \u63A5\u4E0A\u4E00\u4E2A\u5E72\u6D3B\u7684\u6A21\u578B\uFF0C\u518D\u9009\u4E00\u4E2A\u5224\u5B9A\u5668\u3002",
|
|
2509
|
-
en: "mu setup: connect a model for mu to work with, and choose a judge."
|
|
2510
|
-
})
|
|
2511
|
-
);
|
|
2512
|
-
}
|
|
2513
2745
|
const start2 = await this.start(key2 === void 0 ? void 0 : cleanKey(key2));
|
|
2514
2746
|
if (start2 === "subscription") {
|
|
2515
2747
|
const judge2 = await this.judge(void 0);
|
|
@@ -2541,7 +2773,7 @@ var Setup = class {
|
|
|
2541
2773
|
this.io.warn(
|
|
2542
2774
|
this.t({
|
|
2543
2775
|
zh: "mude 1.0 \u8FD8\u6CA1\u6709\u88C5\u597D\uFF0C\u5728\u90A3\u4E4B\u524D\u7531 Jev \u5224\u5B9A\u3002\u8FD0\u884C mu judge mude install \u4F1A\u4ECE\u4E2D\u65AD\u7684\u5730\u65B9\u63A5\u7740\u88C5\u3002",
|
|
2544
|
-
en: "mude 1.0 isn't installed yet, so Jev is the judge until it is. Run mu judge mude install to
|
|
2776
|
+
en: "mude 1.0 isn't installed yet, so Jev is the judge until it is. Run mu judge mude install to resume it."
|
|
2545
2777
|
})
|
|
2546
2778
|
);
|
|
2547
2779
|
return this.scripted ? SETUP_EXIT.failed : SETUP_EXIT.done;
|
|
@@ -2557,9 +2789,9 @@ var Setup = class {
|
|
|
2557
2789
|
);
|
|
2558
2790
|
if (yes) return true;
|
|
2559
2791
|
try {
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2792
|
+
mkdirSync8(this.deps.paths.agentDir, { recursive: true });
|
|
2793
|
+
writeFileSync8(
|
|
2794
|
+
join9(this.deps.paths.agentDir, SETUP_DECLINED_FILE),
|
|
2563
2795
|
"mu asked on its first start whether to set up a model, and the answer was no.\nDelete this file to be asked again; run mu setup any time.\n"
|
|
2564
2796
|
);
|
|
2565
2797
|
} catch {
|
|
@@ -2600,11 +2832,10 @@ var Setup = class {
|
|
|
2600
2832
|
]
|
|
2601
2833
|
);
|
|
2602
2834
|
if (route === "subscription") {
|
|
2603
|
-
const auth = this.path(join8(this.deps.paths.agentDir, "auth.json"));
|
|
2604
2835
|
this.io.say(
|
|
2605
2836
|
this.t({
|
|
2606
|
-
zh:
|
|
2607
|
-
en:
|
|
2837
|
+
zh: "\u8BA2\u9605\u8D26\u53F7\u5728 mu \u91CC\u7528 /login \u767B\u5F55\uFF1A\u9009 Sign in with an account\uFF0C\u518D\u9009 OpenAI\uFF08ChatGPT\uFF09\u3001xAI\uFF08Grok\uFF09\u6216 Google \u7684\u4E00\u9879\uFF0C\u6309\u63D0\u793A\u5728\u6D4F\u89C8\u5668\u91CC\u5B8C\u6210\u3002setup \u63A5\u7740\u542F\u52A8 mu \u65F6\uFF0C/login \u5DF2\u7ECF\u6253\u5F00\u3002",
|
|
2838
|
+
en: "Sign in to your subscription from inside mu with /login: choose Sign in with an account, then OpenAI for ChatGPT, xAI for Grok or one of the Google sign-ins, and finish in the browser. When setup starts mu, /login is already open."
|
|
2608
2839
|
})
|
|
2609
2840
|
);
|
|
2610
2841
|
return "subscription";
|
|
@@ -2773,7 +3004,7 @@ var Setup = class {
|
|
|
2773
3004
|
return [{ provider, builtIn: false, baseUrl: base, api }];
|
|
2774
3005
|
}
|
|
2775
3006
|
inModelsFile(provider) {
|
|
2776
|
-
const models = readJsonFile(
|
|
3007
|
+
const models = readJsonFile(join9(this.deps.paths.agentDir, "models.json"));
|
|
2777
3008
|
return models.state === "ok" && existingProvider(models.value, provider) !== void 0;
|
|
2778
3009
|
}
|
|
2779
3010
|
/** A failure as the person can act on it: a local service that is silent is not ready yet, whatever the key. */
|
|
@@ -2893,7 +3124,7 @@ var Setup = class {
|
|
|
2893
3124
|
}
|
|
2894
3125
|
}
|
|
2895
3126
|
modelsFileWritable() {
|
|
2896
|
-
const path =
|
|
3127
|
+
const path = join9(this.deps.paths.agentDir, "models.json");
|
|
2897
3128
|
if (readJsonFile(path).state !== "unreadable") return;
|
|
2898
3129
|
throw this.fail({
|
|
2899
3130
|
zh: `${this.path(path)} \u4E0D\u662F\u7EAF JSON\uFF08\u4E5F\u8BB8\u6709\u6CE8\u91CA\u6216\u5199\u9519\u4E86\uFF09\uFF0Cmu setup \u4E0D\u6539\u5199\u5B83\u3002\u5148\u4FEE\u597D\u5B83\uFF0C\u6216\u624B\u52A8\u628A\u670D\u52A1\u52A0\u8FDB\u53BB\u3002`,
|
|
@@ -2975,7 +3206,7 @@ var Setup = class {
|
|
|
2975
3206
|
/** A provider models.json has at another address is moved only with a yes. */
|
|
2976
3207
|
async conflicts(target) {
|
|
2977
3208
|
if (target.reach.builtIn && !this.args.baseUrl) return;
|
|
2978
|
-
const models = readJsonFile(
|
|
3209
|
+
const models = readJsonFile(join9(this.deps.paths.agentDir, "models.json"));
|
|
2979
3210
|
if (models.state !== "ok") return;
|
|
2980
3211
|
const current = existingProvider(models.value, target.reach.provider)?.baseUrl;
|
|
2981
3212
|
const old = typeof current === "string" ? current.replace(/\/+$/, "") : void 0;
|
|
@@ -3093,8 +3324,8 @@ var Setup = class {
|
|
|
3093
3324
|
});
|
|
3094
3325
|
const picked = await this.choose(
|
|
3095
3326
|
this.t({
|
|
3096
|
-
zh: "mu \u5DE5\u4F5C\
|
|
3097
|
-
en: "
|
|
3327
|
+
zh: "mu \u5DE5\u4F5C\u4E2D\u7684\u5C0F\u5224\u65AD\u4EA4\u7ED9\u54EA\u4E2A\u5224\u5B9A\u5668\uFF1F",
|
|
3328
|
+
en: "Which judge should answer mu's small decisions?"
|
|
3098
3329
|
}),
|
|
3099
3330
|
choices
|
|
3100
3331
|
);
|
|
@@ -3200,9 +3431,9 @@ var Setup = class {
|
|
|
3200
3431
|
const backups = new Backups(deps.paths.home, deps.now());
|
|
3201
3432
|
const saved = [];
|
|
3202
3433
|
const notes = [];
|
|
3203
|
-
const modelsPath =
|
|
3204
|
-
const authPath2 =
|
|
3205
|
-
const settingsPath =
|
|
3434
|
+
const modelsPath = join9(agentDir2, "models.json");
|
|
3435
|
+
const authPath2 = join9(agentDir2, "auth.json");
|
|
3436
|
+
const settingsPath = join9(agentDir2, "settings.json");
|
|
3206
3437
|
try {
|
|
3207
3438
|
if (target && model) {
|
|
3208
3439
|
const { service, reach, key: key2 } = target;
|
|
@@ -3406,12 +3637,8 @@ var Setup = class {
|
|
|
3406
3637
|
}) : this.t({ zh: "\u5224\u5B9A\u5668\uFF1AJev\u3002", en: " Judge: Jev." });
|
|
3407
3638
|
this.io.say("");
|
|
3408
3639
|
this.io.say(`${done}${judged}`);
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
zh: "\u5728 mu \u91CC\u7528 /model \u6362\u6A21\u578B\uFF1B\u968F\u65F6\u53EF\u4EE5\u518D\u8FD0\u884C mu setup\u3002",
|
|
3412
|
-
en: "Switch models inside mu with /model, and run mu setup again any time."
|
|
3413
|
-
}) : this.t({ zh: "\u63A5\u4E0B\u6765\u5728 mu \u91CC\u7528 /login \u767B\u5F55\u3002", en: "Next, sign in with /login in mu." })
|
|
3414
|
-
);
|
|
3640
|
+
if (!target)
|
|
3641
|
+
this.io.say(this.t({ zh: "\u63A5\u4E0B\u6765\u5728 mu \u91CC\u7528 /login \u767B\u5F55\u3002", en: "Next, sign in with /login in mu." }));
|
|
3415
3642
|
}
|
|
3416
3643
|
/**
|
|
3417
3644
|
* On its own, a finished setup offers a session; on a first start the launcher goes on into one anyway. After a
|
|
@@ -3451,10 +3678,10 @@ async function runSetup(argv, deps) {
|
|
|
3451
3678
|
var givenKey = process.env.MU_SETUP_KEY || void 0;
|
|
3452
3679
|
delete process.env.MU_SETUP_KEY;
|
|
3453
3680
|
var home = process.env.MU_SETUP_HOME || muHome();
|
|
3454
|
-
var agentDir = muEnv("AGENT_DIR") ||
|
|
3455
|
-
var envFile = process.env.MU_SETUP_ENV_FILE ||
|
|
3456
|
-
var authPath =
|
|
3457
|
-
var modelsStorePath =
|
|
3681
|
+
var agentDir = muEnv("AGENT_DIR") || join10(home, "agent");
|
|
3682
|
+
var envFile = process.env.MU_SETUP_ENV_FILE || join10(home, ".env");
|
|
3683
|
+
var authPath = join10(agentDir, "auth.json");
|
|
3684
|
+
var modelsStorePath = join10(agentDir, "models-store.json");
|
|
3458
3685
|
var givenEnv = { ...process.env };
|
|
3459
3686
|
var PROXY_VARIABLES = ["HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy"];
|
|
3460
3687
|
function useSessionProxy() {
|
|
@@ -3478,7 +3705,7 @@ async function main2() {
|
|
|
3478
3705
|
const proxy = useSessionProxy();
|
|
3479
3706
|
const runtime = await ModelRuntime.create({
|
|
3480
3707
|
authPath,
|
|
3481
|
-
modelsPath:
|
|
3708
|
+
modelsPath: join10(agentDir, "models.json"),
|
|
3482
3709
|
modelsStorePath,
|
|
3483
3710
|
refreshOnCreate: false
|
|
3484
3711
|
});
|