@textcortex/zenocode 0.1.9 → 0.1.10
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/package.json
CHANGED
|
@@ -237,6 +237,14 @@ async function packPackage(packageDir) {
|
|
|
237
237
|
return path.join(packageDir, tarballs[tarballs.length - 1]);
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
+
export function buildPublishCommandArgs(tarballPath, { tag } = {}) {
|
|
241
|
+
const args = ["publish", tarballPath, "--access", "public"];
|
|
242
|
+
if (tag) {
|
|
243
|
+
args.push("--tag", tag);
|
|
244
|
+
}
|
|
245
|
+
return args;
|
|
246
|
+
}
|
|
247
|
+
|
|
240
248
|
async function publishTarballIfNeeded(packageName, version, tarballPath, cwd) {
|
|
241
249
|
if (!publishEnabled) return { published: false, skipped: false };
|
|
242
250
|
|
|
@@ -254,11 +262,7 @@ async function publishTarballIfNeeded(packageName, version, tarballPath, cwd) {
|
|
|
254
262
|
}
|
|
255
263
|
|
|
256
264
|
const npmCommand = _command("npm");
|
|
257
|
-
await run(
|
|
258
|
-
npmCommand,
|
|
259
|
-
["publish", tarballPath, "--access", "public", "--tag", publishTag, "--provenance"],
|
|
260
|
-
{ cwd },
|
|
261
|
-
);
|
|
265
|
+
await run(npmCommand, buildPublishCommandArgs(tarballPath, { tag: publishTag }), { cwd });
|
|
262
266
|
return { published: true, skipped: false };
|
|
263
267
|
}
|
|
264
268
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
import {
|
|
4
|
+
buildPublishCommandArgs,
|
|
4
5
|
buildWrapperBinMap,
|
|
5
6
|
mapBrandedBinaryPackageName,
|
|
6
7
|
} from "./build-branded-opencode.mjs";
|
|
@@ -23,3 +24,23 @@ test("buildWrapperBinMap includes package, opencode, and zenocode entrypoints",
|
|
|
23
24
|
zenocode: "./bin/opencode",
|
|
24
25
|
});
|
|
25
26
|
});
|
|
27
|
+
|
|
28
|
+
test("buildPublishCommandArgs omits npm provenance for runtime tarball publishing", () => {
|
|
29
|
+
assert.deepEqual(buildPublishCommandArgs("package.tgz", { tag: "latest" }), [
|
|
30
|
+
"publish",
|
|
31
|
+
"package.tgz",
|
|
32
|
+
"--access",
|
|
33
|
+
"public",
|
|
34
|
+
"--tag",
|
|
35
|
+
"latest",
|
|
36
|
+
]);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("buildPublishCommandArgs omits tag arguments when no tag is provided", () => {
|
|
40
|
+
assert.deepEqual(buildPublishCommandArgs("package.tgz"), [
|
|
41
|
+
"publish",
|
|
42
|
+
"package.tgz",
|
|
43
|
+
"--access",
|
|
44
|
+
"public",
|
|
45
|
+
]);
|
|
46
|
+
});
|
package/scripts/run-zenocode.mjs
CHANGED
|
@@ -31,6 +31,7 @@ const modelsPath = path.join(runtimeDir, "models.json");
|
|
|
31
31
|
const configPath = path.join(runtimeDir, "opencode.jsonc");
|
|
32
32
|
const localBaseUrlDefault = "http://127.0.0.1:8080";
|
|
33
33
|
const cloudBaseUrlDefault = "https://api.textcortex.com";
|
|
34
|
+
const localBaseUrlFlags = new Set(["--local", "--localhost"]);
|
|
34
35
|
|
|
35
36
|
const providerID = "textcortex";
|
|
36
37
|
const configuredOpencodePackage =
|
|
@@ -492,6 +493,15 @@ function sleep(ms) {
|
|
|
492
493
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
493
494
|
}
|
|
494
495
|
|
|
496
|
+
export function hasLocalBaseUrlFlag(args = []) {
|
|
497
|
+
const normalizedArgs = args[0] === "--" ? args.slice(1) : args;
|
|
498
|
+
return normalizedArgs.some((arg) => localBaseUrlFlags.has(arg));
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function stripLocalBaseUrlFlags(args = []) {
|
|
502
|
+
return args.filter((arg) => !localBaseUrlFlags.has(arg));
|
|
503
|
+
}
|
|
504
|
+
|
|
495
505
|
async function parseLoginArgs(args) {
|
|
496
506
|
const normalizedArgs = args[0] === "--" ? args.slice(1) : args;
|
|
497
507
|
let emailHint = process.env.TEXTCORTEX_LOGIN_EMAIL || null;
|
|
@@ -499,6 +509,9 @@ async function parseLoginArgs(args) {
|
|
|
499
509
|
|
|
500
510
|
for (let idx = 0; idx < normalizedArgs.length; idx += 1) {
|
|
501
511
|
const arg = normalizedArgs[idx];
|
|
512
|
+
if (localBaseUrlFlags.has(arg)) {
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
502
515
|
if (arg === "--no-launch-browser") {
|
|
503
516
|
launchBrowser = false;
|
|
504
517
|
continue;
|
|
@@ -515,6 +528,7 @@ async function parseLoginArgs(args) {
|
|
|
515
528
|
if (arg === "--help" || arg === "-h") {
|
|
516
529
|
console.log("Zenocode login options:");
|
|
517
530
|
console.log(" --email <address> Optional email hint for tenant/SSO routing");
|
|
531
|
+
console.log(` --local Use the local FastAPI backend at ${localBaseUrlDefault}`);
|
|
518
532
|
console.log(" --no-launch-browser Do not open browser automatically");
|
|
519
533
|
process.exit(0);
|
|
520
534
|
}
|
|
@@ -543,10 +557,29 @@ function isLoginRouteNotFoundError(error) {
|
|
|
543
557
|
return message.includes("Login initiate failed (404)");
|
|
544
558
|
}
|
|
545
559
|
|
|
560
|
+
export function shouldFallbackLoginToCloud({ baseUrl, hasExplicitBaseUrl, error }) {
|
|
561
|
+
if (hasExplicitBaseUrl || baseUrl !== localBaseUrlDefault) {
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return isFetchFailedError(error) || isLoginRouteNotFoundError(error);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
export function resolveTextCortexBaseUrl({
|
|
569
|
+
envBaseUrl,
|
|
570
|
+
storedBaseUrl,
|
|
571
|
+
preferLocalhost = false,
|
|
572
|
+
} = {}) {
|
|
573
|
+
if (preferLocalhost) {
|
|
574
|
+
return localBaseUrlDefault;
|
|
575
|
+
}
|
|
576
|
+
return envBaseUrl || storedBaseUrl || cloudBaseUrlDefault;
|
|
577
|
+
}
|
|
578
|
+
|
|
546
579
|
function _loginConnectivityHelp(baseUrl) {
|
|
547
580
|
return [
|
|
548
581
|
`Cannot reach Zenocode auth endpoint at ${baseUrl}.`,
|
|
549
|
-
|
|
582
|
+
`Set TEXTCORTEX_BASE_URL to ${cloudBaseUrlDefault} or, for local development, run local backend FastAPI (\`cd backend && uv run dev_fastapi\`).`,
|
|
550
583
|
].join(" ");
|
|
551
584
|
}
|
|
552
585
|
|
|
@@ -655,18 +688,20 @@ async function saveRuntimeCredentials(baseUrl, tokenData) {
|
|
|
655
688
|
await clearLogoutMarker();
|
|
656
689
|
}
|
|
657
690
|
|
|
658
|
-
async function runLoginCommand(baseUrl, args) {
|
|
691
|
+
async function runLoginCommand(baseUrl, args, options = {}) {
|
|
692
|
+
const preferLocalhost = options.preferLocalhost === true;
|
|
659
693
|
const { emailHint, launchBrowser } = await parseLoginArgs(args);
|
|
660
|
-
let resolvedBaseUrl = baseUrl;
|
|
694
|
+
let resolvedBaseUrl = preferLocalhost ? localBaseUrlDefault : baseUrl;
|
|
661
695
|
let login;
|
|
662
696
|
try {
|
|
663
697
|
login = await initiateDeviceLogin(resolvedBaseUrl, emailHint);
|
|
664
698
|
} catch (error) {
|
|
665
|
-
if (
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
699
|
+
if (shouldFallbackLoginToCloud({
|
|
700
|
+
baseUrl: resolvedBaseUrl,
|
|
701
|
+
hasExplicitBaseUrl:
|
|
702
|
+
Boolean(process.env.TEXTCORTEX_BASE_URL) || preferLocalhost,
|
|
703
|
+
error,
|
|
704
|
+
})) {
|
|
670
705
|
resolvedBaseUrl = cloudBaseUrlDefault;
|
|
671
706
|
console.log(
|
|
672
707
|
`Local backend not reachable at ${localBaseUrlDefault}. Falling back to ${cloudBaseUrlDefault}.`,
|
|
@@ -1225,6 +1260,7 @@ export async function runRuntimeWithSessionRecovery({
|
|
|
1225
1260
|
token,
|
|
1226
1261
|
childOptions,
|
|
1227
1262
|
canAutoLoginRuntime,
|
|
1263
|
+
preferLocalhost = false,
|
|
1228
1264
|
refreshTokenFn = refreshStoredCredentials,
|
|
1229
1265
|
runLogin = runLoginCommand,
|
|
1230
1266
|
resolveTokenFn = resolveToken,
|
|
@@ -1283,9 +1319,16 @@ export async function runRuntimeWithSessionRecovery({
|
|
|
1283
1319
|
}
|
|
1284
1320
|
|
|
1285
1321
|
console.log("Zenocode session expired. Starting login flow...\n");
|
|
1286
|
-
await runLogin(activeBaseUrl, buildAutoLoginArgs()
|
|
1322
|
+
await runLogin(activeBaseUrl, buildAutoLoginArgs({ preferLocalhost }), {
|
|
1323
|
+
preferLocalhost,
|
|
1324
|
+
});
|
|
1287
1325
|
activeToken = await resolveTokenFn();
|
|
1288
|
-
activeBaseUrl =
|
|
1326
|
+
activeBaseUrl =
|
|
1327
|
+
resolveTextCortexBaseUrl({
|
|
1328
|
+
envBaseUrl: process.env.TEXTCORTEX_BASE_URL,
|
|
1329
|
+
storedBaseUrl: await resolveStoredBaseUrlFn(),
|
|
1330
|
+
preferLocalhost,
|
|
1331
|
+
}) || activeBaseUrl;
|
|
1289
1332
|
await prepareRuntimeFn(activeBaseUrl, activeToken);
|
|
1290
1333
|
const retryDelayMs = Math.min(
|
|
1291
1334
|
Math.max(recoveryDelayMs, 0) * recoveryAttempts,
|
|
@@ -1366,11 +1409,18 @@ function maybeRenderBanner(args) {
|
|
|
1366
1409
|
console.log(`${buildZenocodeBanner()}\n`);
|
|
1367
1410
|
}
|
|
1368
1411
|
|
|
1369
|
-
function buildAutoLoginArgs() {
|
|
1370
|
-
|
|
1412
|
+
function buildAutoLoginArgs({ preferLocalhost = false } = {}) {
|
|
1413
|
+
const loginArgs = [];
|
|
1414
|
+
if (preferLocalhost) {
|
|
1415
|
+
loginArgs.push("--local");
|
|
1416
|
+
}
|
|
1417
|
+
if (
|
|
1418
|
+
process.env.ZENOCODE_AUTO_LOGIN_NO_BROWSER === "1" ||
|
|
1371
1419
|
process.env.CODECORTEX_AUTO_LOGIN_NO_BROWSER === "1"
|
|
1372
|
-
|
|
1373
|
-
|
|
1420
|
+
) {
|
|
1421
|
+
loginArgs.push("--no-launch-browser");
|
|
1422
|
+
}
|
|
1423
|
+
return loginArgs;
|
|
1374
1424
|
}
|
|
1375
1425
|
|
|
1376
1426
|
function isMissingTokenError(error) {
|
|
@@ -1443,7 +1493,8 @@ function shouldAttemptAutoLogin(error, args) {
|
|
|
1443
1493
|
return canAutoLogin(args);
|
|
1444
1494
|
}
|
|
1445
1495
|
|
|
1446
|
-
async function resolveTokenWithAutoLogin(baseUrl, args) {
|
|
1496
|
+
async function resolveTokenWithAutoLogin(baseUrl, args, options = {}) {
|
|
1497
|
+
const preferLocalhost = options.preferLocalhost === true;
|
|
1447
1498
|
try {
|
|
1448
1499
|
const token = await resolveToken();
|
|
1449
1500
|
return { token, baseUrl };
|
|
@@ -1453,14 +1504,21 @@ async function resolveTokenWithAutoLogin(baseUrl, args) {
|
|
|
1453
1504
|
}
|
|
1454
1505
|
|
|
1455
1506
|
console.log("No local Zenocode credentials found. Starting login flow...\n");
|
|
1456
|
-
await runLoginCommand(baseUrl, buildAutoLoginArgs()
|
|
1507
|
+
await runLoginCommand(baseUrl, buildAutoLoginArgs({ preferLocalhost }), {
|
|
1508
|
+
preferLocalhost,
|
|
1509
|
+
});
|
|
1457
1510
|
const token = await resolveToken();
|
|
1458
|
-
const persistedBaseUrl =
|
|
1511
|
+
const persistedBaseUrl = resolveTextCortexBaseUrl({
|
|
1512
|
+
envBaseUrl: process.env.TEXTCORTEX_BASE_URL,
|
|
1513
|
+
storedBaseUrl: await resolveStoredBaseUrl(),
|
|
1514
|
+
preferLocalhost,
|
|
1515
|
+
});
|
|
1459
1516
|
return { token, baseUrl: persistedBaseUrl };
|
|
1460
1517
|
}
|
|
1461
1518
|
}
|
|
1462
1519
|
|
|
1463
|
-
async function prepareRuntimeWithAutoLogin(baseUrl, token, args) {
|
|
1520
|
+
async function prepareRuntimeWithAutoLogin(baseUrl, token, args, options = {}) {
|
|
1521
|
+
const preferLocalhost = options.preferLocalhost === true;
|
|
1464
1522
|
try {
|
|
1465
1523
|
const model = await prepareRuntime(baseUrl, token);
|
|
1466
1524
|
return { model, token, baseUrl };
|
|
@@ -1484,9 +1542,15 @@ async function prepareRuntimeWithAutoLogin(baseUrl, token, args) {
|
|
|
1484
1542
|
}
|
|
1485
1543
|
|
|
1486
1544
|
console.log("Zenocode session expired. Starting login flow...\n");
|
|
1487
|
-
await runLoginCommand(baseUrl, buildAutoLoginArgs()
|
|
1545
|
+
await runLoginCommand(baseUrl, buildAutoLoginArgs({ preferLocalhost }), {
|
|
1546
|
+
preferLocalhost,
|
|
1547
|
+
});
|
|
1488
1548
|
const refreshedToken = await resolveToken();
|
|
1489
|
-
const refreshedBaseUrl =
|
|
1549
|
+
const refreshedBaseUrl = resolveTextCortexBaseUrl({
|
|
1550
|
+
envBaseUrl: process.env.TEXTCORTEX_BASE_URL,
|
|
1551
|
+
storedBaseUrl: await resolveStoredBaseUrl(),
|
|
1552
|
+
preferLocalhost,
|
|
1553
|
+
});
|
|
1490
1554
|
const model = await prepareRuntime(refreshedBaseUrl, refreshedToken);
|
|
1491
1555
|
return { model, token: refreshedToken, baseUrl: refreshedBaseUrl };
|
|
1492
1556
|
}
|
|
@@ -1503,12 +1567,18 @@ async function main() {
|
|
|
1503
1567
|
passthrough.shift();
|
|
1504
1568
|
}
|
|
1505
1569
|
|
|
1570
|
+
const preferLocalhost = hasLocalBaseUrlFlag(passthrough);
|
|
1571
|
+
const runtimeArgs = stripLocalBaseUrlFlags(passthrough);
|
|
1506
1572
|
const storedBaseUrl = await resolveStoredBaseUrl();
|
|
1507
|
-
const baseUrl =
|
|
1508
|
-
|
|
1573
|
+
const baseUrl = resolveTextCortexBaseUrl({
|
|
1574
|
+
envBaseUrl: process.env.TEXTCORTEX_BASE_URL,
|
|
1575
|
+
storedBaseUrl,
|
|
1576
|
+
preferLocalhost,
|
|
1577
|
+
});
|
|
1578
|
+
const subcommand = runtimeArgs[0];
|
|
1509
1579
|
|
|
1510
1580
|
if (subcommand === "login") {
|
|
1511
|
-
await runLoginCommand(baseUrl,
|
|
1581
|
+
await runLoginCommand(baseUrl, runtimeArgs.slice(1), { preferLocalhost });
|
|
1512
1582
|
return;
|
|
1513
1583
|
}
|
|
1514
1584
|
|
|
@@ -1517,12 +1587,15 @@ async function main() {
|
|
|
1517
1587
|
return;
|
|
1518
1588
|
}
|
|
1519
1589
|
|
|
1520
|
-
maybeRenderBanner(
|
|
1521
|
-
const tokenResolution = await resolveTokenWithAutoLogin(baseUrl,
|
|
1590
|
+
maybeRenderBanner(runtimeArgs);
|
|
1591
|
+
const tokenResolution = await resolveTokenWithAutoLogin(baseUrl, runtimeArgs, {
|
|
1592
|
+
preferLocalhost,
|
|
1593
|
+
});
|
|
1522
1594
|
const runtime = await prepareRuntimeWithAutoLogin(
|
|
1523
1595
|
tokenResolution.baseUrl,
|
|
1524
1596
|
tokenResolution.token,
|
|
1525
|
-
|
|
1597
|
+
runtimeArgs,
|
|
1598
|
+
{ preferLocalhost },
|
|
1526
1599
|
);
|
|
1527
1600
|
const token = runtime.token;
|
|
1528
1601
|
const model = runtime.model;
|
|
@@ -1539,14 +1612,15 @@ async function main() {
|
|
|
1539
1612
|
TEXTCORTEX_API_KEY: token,
|
|
1540
1613
|
},
|
|
1541
1614
|
};
|
|
1542
|
-
const monitorRuntimeSession = canAutoLogin(
|
|
1615
|
+
const monitorRuntimeSession = canAutoLogin(runtimeArgs);
|
|
1543
1616
|
|
|
1544
1617
|
if (opencodeBinaryPath) {
|
|
1545
1618
|
const result = await runRuntimeWithSessionRecovery({
|
|
1546
|
-
args:
|
|
1619
|
+
args: runtimeArgs,
|
|
1547
1620
|
baseUrl: runtime.baseUrl,
|
|
1548
1621
|
token,
|
|
1549
1622
|
childOptions,
|
|
1623
|
+
preferLocalhost,
|
|
1550
1624
|
canAutoLoginRuntime: monitorRuntimeSession,
|
|
1551
1625
|
launchRuntimeFn: ({ args, childOptions }) =>
|
|
1552
1626
|
runRuntimeBinary(opencodeBinaryPath, args, childOptions, monitorRuntimeSession),
|
|
@@ -1559,10 +1633,11 @@ async function main() {
|
|
|
1559
1633
|
const pinnedRuntimePath = await resolvePinnedRuntimeBinary(launchPackage, childOptions);
|
|
1560
1634
|
if (pinnedRuntimePath) {
|
|
1561
1635
|
const result = await runRuntimeWithSessionRecovery({
|
|
1562
|
-
args:
|
|
1636
|
+
args: runtimeArgs,
|
|
1563
1637
|
baseUrl: runtime.baseUrl,
|
|
1564
1638
|
token,
|
|
1565
1639
|
childOptions,
|
|
1640
|
+
preferLocalhost,
|
|
1566
1641
|
canAutoLoginRuntime: monitorRuntimeSession,
|
|
1567
1642
|
launchRuntimeFn: ({ args, childOptions }) =>
|
|
1568
1643
|
runRuntimeBinary(pinnedRuntimePath, args, childOptions, monitorRuntimeSession),
|
|
@@ -1570,7 +1645,7 @@ async function main() {
|
|
|
1570
1645
|
exitWithChildResult(result);
|
|
1571
1646
|
return;
|
|
1572
1647
|
}
|
|
1573
|
-
await runPackageLauncher(launchPackage,
|
|
1648
|
+
await runPackageLauncher(launchPackage, runtimeArgs, childOptions);
|
|
1574
1649
|
}
|
|
1575
1650
|
|
|
1576
1651
|
const resolveExecutablePath = (value) => {
|
|
@@ -15,8 +15,11 @@ import {
|
|
|
15
15
|
canRecoverRuntimeSessionFromTranscript,
|
|
16
16
|
buildZenocodeBanner,
|
|
17
17
|
chooseDefaults,
|
|
18
|
+
hasLocalBaseUrlFlag,
|
|
18
19
|
resolveLoginSuccessIdentifier,
|
|
20
|
+
resolveTextCortexBaseUrl,
|
|
19
21
|
runRuntimeWithSessionRecovery,
|
|
22
|
+
shouldFallbackLoginToCloud,
|
|
20
23
|
writePrivateJsonFile,
|
|
21
24
|
} from "./run-zenocode.mjs";
|
|
22
25
|
|
|
@@ -33,6 +36,65 @@ test("chooseDefaults prefers kimi k2.5 thinking for Zenocode", () => {
|
|
|
33
36
|
});
|
|
34
37
|
});
|
|
35
38
|
|
|
39
|
+
test("resolveTextCortexBaseUrl defaults to the cloud API for packaged usage", () => {
|
|
40
|
+
assert.equal(
|
|
41
|
+
resolveTextCortexBaseUrl({
|
|
42
|
+
envBaseUrl: "",
|
|
43
|
+
storedBaseUrl: null,
|
|
44
|
+
}),
|
|
45
|
+
"https://api.textcortex.com",
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("resolveTextCortexBaseUrl prefers the explicit env var over stored credentials", () => {
|
|
50
|
+
assert.equal(
|
|
51
|
+
resolveTextCortexBaseUrl({
|
|
52
|
+
envBaseUrl: "https://staging.textcortex.com",
|
|
53
|
+
storedBaseUrl: "http://127.0.0.1:8080",
|
|
54
|
+
}),
|
|
55
|
+
"https://staging.textcortex.com",
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("resolveTextCortexBaseUrl prefers localhost when the local flag is enabled", () => {
|
|
60
|
+
assert.equal(
|
|
61
|
+
resolveTextCortexBaseUrl({
|
|
62
|
+
envBaseUrl: "https://staging.textcortex.com",
|
|
63
|
+
storedBaseUrl: "https://api.textcortex.com",
|
|
64
|
+
preferLocalhost: true,
|
|
65
|
+
}),
|
|
66
|
+
"http://127.0.0.1:8080",
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("hasLocalBaseUrlFlag detects localhost flags", () => {
|
|
71
|
+
assert.equal(hasLocalBaseUrlFlag(["login", "--local"]), true);
|
|
72
|
+
assert.equal(hasLocalBaseUrlFlag(["--localhost", "run"]), true);
|
|
73
|
+
assert.equal(hasLocalBaseUrlFlag(["run", "--help"]), false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("shouldFallbackLoginToCloud retries the cloud API when the local auth route returns 404", () => {
|
|
77
|
+
assert.equal(
|
|
78
|
+
shouldFallbackLoginToCloud({
|
|
79
|
+
baseUrl: "http://127.0.0.1:8080",
|
|
80
|
+
hasExplicitBaseUrl: false,
|
|
81
|
+
error: new Error("Login initiate failed (404): not found"),
|
|
82
|
+
}),
|
|
83
|
+
true,
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("shouldFallbackLoginToCloud does not override an explicit base URL", () => {
|
|
88
|
+
assert.equal(
|
|
89
|
+
shouldFallbackLoginToCloud({
|
|
90
|
+
baseUrl: "http://127.0.0.1:8080",
|
|
91
|
+
hasExplicitBaseUrl: true,
|
|
92
|
+
error: new Error("fetch failed"),
|
|
93
|
+
}),
|
|
94
|
+
false,
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
|
|
36
98
|
test("buildOpenCodeConfig includes an openai stub for fallback runtime auth plugins", () => {
|
|
37
99
|
const config = buildOpenCodeConfig({
|
|
38
100
|
baseUrl: "http://127.0.0.1:8080",
|
|
@@ -253,6 +315,61 @@ test("runRuntimeWithSessionRecovery refreshes stored credentials before forcing
|
|
|
253
315
|
assert.deepEqual(result, { code: 0, signal: null, expiredSession: false });
|
|
254
316
|
});
|
|
255
317
|
|
|
318
|
+
test("runRuntimeWithSessionRecovery preserves explicit localhost preference during login recovery", async () => {
|
|
319
|
+
const events = [];
|
|
320
|
+
let launchCount = 0;
|
|
321
|
+
|
|
322
|
+
const result = await runRuntimeWithSessionRecovery({
|
|
323
|
+
args: ["run"],
|
|
324
|
+
baseUrl: "http://127.0.0.1:8080",
|
|
325
|
+
token: "token-1",
|
|
326
|
+
childOptions: {
|
|
327
|
+
cwd: process.cwd(),
|
|
328
|
+
env: {},
|
|
329
|
+
},
|
|
330
|
+
canAutoLoginRuntime: true,
|
|
331
|
+
preferLocalhost: true,
|
|
332
|
+
refreshTokenFn: async (baseUrl) => {
|
|
333
|
+
events.push(["refresh", baseUrl]);
|
|
334
|
+
throw new Error("refresh failed");
|
|
335
|
+
},
|
|
336
|
+
runLogin: async (baseUrl, loginArgs, options) => {
|
|
337
|
+
events.push(["login", baseUrl, loginArgs, options]);
|
|
338
|
+
},
|
|
339
|
+
resolveTokenFn: async () => {
|
|
340
|
+
events.push(["resolve-token"]);
|
|
341
|
+
return "token-2";
|
|
342
|
+
},
|
|
343
|
+
resolveStoredBaseUrlFn: async () => {
|
|
344
|
+
events.push(["resolve-base-url"]);
|
|
345
|
+
return "https://api.textcortex.com";
|
|
346
|
+
},
|
|
347
|
+
prepareRuntimeFn: async (baseUrl, token) => {
|
|
348
|
+
events.push(["prepare", baseUrl, token]);
|
|
349
|
+
return "kimi-k2-5-thinking";
|
|
350
|
+
},
|
|
351
|
+
launchRuntimeFn: async ({ childOptions }) => {
|
|
352
|
+
launchCount += 1;
|
|
353
|
+
events.push(["launch", launchCount, childOptions.env.TEXTCORTEX_API_KEY]);
|
|
354
|
+
if (launchCount === 1) {
|
|
355
|
+
return { expiredSession: true };
|
|
356
|
+
}
|
|
357
|
+
return { code: 0, signal: null, expiredSession: false };
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
assert.deepEqual(events, [
|
|
362
|
+
["launch", 1, "token-1"],
|
|
363
|
+
["refresh", "http://127.0.0.1:8080"],
|
|
364
|
+
["login", "http://127.0.0.1:8080", ["--local"], { preferLocalhost: true }],
|
|
365
|
+
["resolve-token"],
|
|
366
|
+
["resolve-base-url"],
|
|
367
|
+
["prepare", "http://127.0.0.1:8080", "token-2"],
|
|
368
|
+
["launch", 2, "token-2"],
|
|
369
|
+
]);
|
|
370
|
+
assert.deepEqual(result, { code: 0, signal: null, expiredSession: false });
|
|
371
|
+
});
|
|
372
|
+
|
|
256
373
|
test("runRuntimeWithSessionRecovery stops after repeated recovery failures", async () => {
|
|
257
374
|
const events = [];
|
|
258
375
|
let launchCount = 0;
|