@textcortex/zenocode 0.1.12 → 0.1.13
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 +1 -1
- package/scripts/branding-patch.mjs +35 -1
- package/scripts/branding-patch.test.mjs +34 -1
- package/scripts/build-branded-opencode.mjs +50 -8
- package/scripts/build-branded-opencode.test.mjs +24 -0
- package/scripts/opencode-version.mjs +4 -0
- package/scripts/run-zenocode.mjs +201 -71
- package/scripts/run-zenocode.test.mjs +83 -14
package/package.json
CHANGED
|
@@ -139,21 +139,55 @@ export function patchOpenCodeVersionFooterText(text) {
|
|
|
139
139
|
return { patched, text: next };
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
export function patchOpenCodeDisplayNameText(text) {
|
|
143
|
+
const replacements = [
|
|
144
|
+
['TD("Uninstall OpenCode")', 'TD("Uninstall Zenocode")'],
|
|
145
|
+
['"name":"OpenCode"', '"name":"Zenocode"'],
|
|
146
|
+
['"short_name":"OpenCode"', '"short_name":"Zenocode"'],
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
let patched = false;
|
|
150
|
+
let nextText = text;
|
|
151
|
+
for (const [target, replacement] of replacements) {
|
|
152
|
+
if (!nextText.includes(target)) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
nextText = nextText.replaceAll(target, replacement);
|
|
156
|
+
patched = true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return { patched, text: nextText };
|
|
160
|
+
}
|
|
161
|
+
|
|
142
162
|
export function patchZenocodeBinaryText(text) {
|
|
143
163
|
let patched = false;
|
|
144
164
|
let nextText = text;
|
|
165
|
+
const patches = {
|
|
166
|
+
logo: false,
|
|
167
|
+
footer: false,
|
|
168
|
+
displayName: false,
|
|
169
|
+
};
|
|
145
170
|
|
|
146
171
|
const logoPatch = patchLogoSnippetText(nextText);
|
|
147
172
|
if (logoPatch.patched) {
|
|
148
173
|
nextText = logoPatch.text;
|
|
149
174
|
patched = true;
|
|
175
|
+
patches.logo = true;
|
|
150
176
|
}
|
|
151
177
|
|
|
152
178
|
const footerPatch = patchOpenCodeVersionFooterText(nextText);
|
|
153
179
|
if (footerPatch.patched) {
|
|
154
180
|
nextText = footerPatch.text;
|
|
155
181
|
patched = true;
|
|
182
|
+
patches.footer = true;
|
|
156
183
|
}
|
|
157
184
|
|
|
158
|
-
|
|
185
|
+
const displayNamePatch = patchOpenCodeDisplayNameText(nextText);
|
|
186
|
+
if (displayNamePatch.patched) {
|
|
187
|
+
nextText = displayNamePatch.text;
|
|
188
|
+
patched = true;
|
|
189
|
+
patches.displayName = true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return { patched, patches, text: nextText };
|
|
159
193
|
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
padBinaryReplacement,
|
|
5
|
+
patchOpenCodeDisplayNameText,
|
|
6
|
+
patchOpenCodeVersionFooterText,
|
|
7
|
+
patchZenocodeBinaryText,
|
|
8
|
+
} from "./branding-patch.mjs";
|
|
4
9
|
|
|
5
10
|
test("padBinaryReplacement keeps binary length stable", () => {
|
|
6
11
|
const padded = padBinaryReplacement("abcdef", "abc");
|
|
@@ -54,3 +59,31 @@ test("patchOpenCodeVersionFooterText is a no-op when already branded", () => {
|
|
|
54
59
|
assert.equal(result.patched, false);
|
|
55
60
|
assert.equal(result.text, branded);
|
|
56
61
|
});
|
|
62
|
+
|
|
63
|
+
test("patchOpenCodeDisplayNameText rewrites current compiled display names", () => {
|
|
64
|
+
const text = [
|
|
65
|
+
'TD("Uninstall OpenCode");"name":"OpenCode","short_name":"OpenCode"',
|
|
66
|
+
'"Powered by OpenCode"',
|
|
67
|
+
].join(";");
|
|
68
|
+
|
|
69
|
+
const result = patchOpenCodeDisplayNameText(text);
|
|
70
|
+
|
|
71
|
+
assert.equal(result.patched, true);
|
|
72
|
+
assert.equal(result.text.length, text.length);
|
|
73
|
+
assert.equal(
|
|
74
|
+
result.text,
|
|
75
|
+
'TD("Uninstall Zenocode");"name":"Zenocode","short_name":"Zenocode";"Powered by OpenCode"',
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("patchZenocodeBinaryText reports display-name-only branding separately", () => {
|
|
80
|
+
const result = patchZenocodeBinaryText('"name":"OpenCode"');
|
|
81
|
+
|
|
82
|
+
assert.equal(result.patched, true);
|
|
83
|
+
assert.deepEqual(result.patches, {
|
|
84
|
+
logo: false,
|
|
85
|
+
footer: false,
|
|
86
|
+
displayName: true,
|
|
87
|
+
});
|
|
88
|
+
assert.equal(result.text, '"name":"Zenocode"');
|
|
89
|
+
});
|
|
@@ -7,17 +7,27 @@ import path from "node:path";
|
|
|
7
7
|
import process from "node:process";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
import { patchZenocodeBinaryText } from "./branding-patch.mjs";
|
|
10
|
+
import { openCodeRef, openCodeVersion } from "./opencode-version.mjs";
|
|
10
11
|
|
|
11
12
|
const currentFilePath = realpathSync(fileURLToPath(import.meta.url));
|
|
12
13
|
const __dirname = path.dirname(currentFilePath);
|
|
13
14
|
const appRoot = path.resolve(__dirname, "..");
|
|
14
15
|
const defaultOutputDir = path.join(appRoot, ".zenocode", "brand-build");
|
|
16
|
+
export const defaultOpenCodeForkUrl = "https://github.com/sst/opencode";
|
|
17
|
+
export const defaultOpenCodeRef = openCodeRef;
|
|
18
|
+
export const defaultOpenCodeVersion = openCodeVersion;
|
|
19
|
+
export const defaultOpenCodeBuildArgs = ["--skip-install"];
|
|
20
|
+
const displayNameOnlyBrandingRefs = new Set([defaultOpenCodeRef]);
|
|
15
21
|
|
|
16
22
|
const forkUrl =
|
|
17
23
|
process.env.ZENOCODE_OPENCODE_FORK_URL ||
|
|
18
24
|
process.env.CODECORTEX_OPENCODE_FORK_URL ||
|
|
19
|
-
|
|
20
|
-
const forkRef = (
|
|
25
|
+
defaultOpenCodeForkUrl;
|
|
26
|
+
const forkRef = (
|
|
27
|
+
process.env.ZENOCODE_OPENCODE_REF ||
|
|
28
|
+
process.env.CODECORTEX_OPENCODE_REF ||
|
|
29
|
+
defaultOpenCodeRef
|
|
30
|
+
).trim();
|
|
21
31
|
const outputDir =
|
|
22
32
|
process.env.ZENOCODE_BRANDED_OUTPUT_DIR ||
|
|
23
33
|
process.env.CODECORTEX_BRANDED_OUTPUT_DIR ||
|
|
@@ -36,16 +46,25 @@ const publishTag =
|
|
|
36
46
|
process.env.ZENOCODE_PUBLISH_TAG ||
|
|
37
47
|
process.env.CODECORTEX_PUBLISH_TAG ||
|
|
38
48
|
"latest";
|
|
39
|
-
const
|
|
49
|
+
const configuredBuildArgs = (
|
|
40
50
|
process.env.ZENOCODE_OPENCODE_BUILD_ARGS ||
|
|
41
51
|
process.env.CODECORTEX_OPENCODE_BUILD_ARGS ||
|
|
42
52
|
""
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
.split(/\s+/)
|
|
46
|
-
|
|
53
|
+
).trim();
|
|
54
|
+
const buildArgs = configuredBuildArgs
|
|
55
|
+
? configuredBuildArgs.split(/\s+/).filter(Boolean)
|
|
56
|
+
: defaultOpenCodeBuildArgs;
|
|
47
57
|
const cliArgs = process.argv.slice(2);
|
|
48
58
|
|
|
59
|
+
export function deriveOpenCodeVersionFromRef(ref, fallbackVersion = defaultOpenCodeVersion) {
|
|
60
|
+
const match = ref.match(/^v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)(?:\+.*)?$/);
|
|
61
|
+
return match?.[1] || fallbackVersion;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function allowsDisplayNameOnlyBranding(ref) {
|
|
65
|
+
return displayNameOnlyBrandingRefs.has(ref);
|
|
66
|
+
}
|
|
67
|
+
|
|
49
68
|
function _command(name) {
|
|
50
69
|
if (process.platform === "win32" && ["npm", "pnpm", "npx"].includes(name)) {
|
|
51
70
|
return `${name}.cmd`;
|
|
@@ -270,6 +289,13 @@ function _binaryFilename() {
|
|
|
270
289
|
return process.platform === "win32" ? "opencode.exe" : "opencode";
|
|
271
290
|
}
|
|
272
291
|
|
|
292
|
+
async function adHocSignBinary(binaryPath) {
|
|
293
|
+
if (process.platform !== "darwin") return;
|
|
294
|
+
await run("codesign", ["--force", "--sign", "-", binaryPath], {
|
|
295
|
+
stdio: "ignore",
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
273
299
|
async function patchBinaryAtPath(binaryPath) {
|
|
274
300
|
const buffer = await fs.readFile(binaryPath);
|
|
275
301
|
const originalLength = buffer.length;
|
|
@@ -277,6 +303,12 @@ async function patchBinaryAtPath(binaryPath) {
|
|
|
277
303
|
if (!patch.patched) {
|
|
278
304
|
throw new Error(`Branding patch did not match binary ${binaryPath}`);
|
|
279
305
|
}
|
|
306
|
+
const patchedMandatoryBranding = patch.patches.logo || patch.patches.footer;
|
|
307
|
+
if (!patchedMandatoryBranding && !allowsDisplayNameOnlyBranding(forkRef)) {
|
|
308
|
+
throw new Error(
|
|
309
|
+
`Branding patch only matched display names for ${binaryPath}; update the logo/footer patch signatures before building ${forkRef}.`,
|
|
310
|
+
);
|
|
311
|
+
}
|
|
280
312
|
const nextBuffer = Buffer.from(patch.text, "latin1");
|
|
281
313
|
if (nextBuffer.length !== originalLength) {
|
|
282
314
|
throw new Error(`Branding patch changed binary length for ${binaryPath}`);
|
|
@@ -285,6 +317,7 @@ async function patchBinaryAtPath(binaryPath) {
|
|
|
285
317
|
if (process.platform !== "win32") {
|
|
286
318
|
await fs.chmod(binaryPath, 0o755);
|
|
287
319
|
}
|
|
320
|
+
await adHocSignBinary(binaryPath);
|
|
288
321
|
}
|
|
289
322
|
|
|
290
323
|
function _buildWrapperExecutable({ runtimePackageName, binName }) {
|
|
@@ -636,7 +669,16 @@ async function main() {
|
|
|
636
669
|
await run(
|
|
637
670
|
bunCommand,
|
|
638
671
|
["./packages/opencode/script/build.ts", "--single", ...buildArgs],
|
|
639
|
-
{
|
|
672
|
+
{
|
|
673
|
+
cwd: checkoutDir,
|
|
674
|
+
env: {
|
|
675
|
+
...process.env,
|
|
676
|
+
OPENCODE_CHANNEL: process.env.OPENCODE_CHANNEL || "latest",
|
|
677
|
+
OPENCODE_VERSION:
|
|
678
|
+
process.env.OPENCODE_VERSION ||
|
|
679
|
+
deriveOpenCodeVersionFromRef(forkRef, defaultOpenCodeVersion),
|
|
680
|
+
},
|
|
681
|
+
},
|
|
640
682
|
);
|
|
641
683
|
|
|
642
684
|
await fs.mkdir(artifactDir, { recursive: true });
|
|
@@ -3,9 +3,33 @@ import test from "node:test";
|
|
|
3
3
|
import {
|
|
4
4
|
buildPublishCommandArgs,
|
|
5
5
|
buildWrapperBinMap,
|
|
6
|
+
allowsDisplayNameOnlyBranding,
|
|
7
|
+
defaultOpenCodeBuildArgs,
|
|
8
|
+
defaultOpenCodeForkUrl,
|
|
9
|
+
defaultOpenCodeRef,
|
|
10
|
+
defaultOpenCodeVersion,
|
|
11
|
+
deriveOpenCodeVersionFromRef,
|
|
6
12
|
mapBrandedBinaryPackageName,
|
|
7
13
|
} from "./build-branded-opencode.mjs";
|
|
8
14
|
|
|
15
|
+
test("branded OpenCode build defaults to the latest stable upstream release", () => {
|
|
16
|
+
assert.equal(defaultOpenCodeForkUrl, "https://github.com/sst/opencode");
|
|
17
|
+
assert.equal(defaultOpenCodeRef, "v1.17.6");
|
|
18
|
+
assert.equal(defaultOpenCodeVersion, "1.17.6");
|
|
19
|
+
assert.deepEqual(defaultOpenCodeBuildArgs, ["--skip-install"]);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("deriveOpenCodeVersionFromRef follows explicit tag overrides", () => {
|
|
23
|
+
assert.equal(deriveOpenCodeVersionFromRef("v1.17.5"), "1.17.5");
|
|
24
|
+
assert.equal(deriveOpenCodeVersionFromRef("1.18.0-beta.1"), "1.18.0-beta.1");
|
|
25
|
+
assert.equal(deriveOpenCodeVersionFromRef("main", "1.17.4"), "1.17.4");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("display-name-only branding is limited to reviewed upstream refs", () => {
|
|
29
|
+
assert.equal(allowsDisplayNameOnlyBranding(defaultOpenCodeRef), true);
|
|
30
|
+
assert.equal(allowsDisplayNameOnlyBranding("v1.17.7"), false);
|
|
31
|
+
});
|
|
32
|
+
|
|
9
33
|
test("mapBrandedBinaryPackageName scopes runtime binaries under zenocode", () => {
|
|
10
34
|
assert.equal(
|
|
11
35
|
mapBrandedBinaryPackageName("opencode-darwin-arm64", "@textcortex/zenocode-ai"),
|
package/scripts/run-zenocode.mjs
CHANGED
|
@@ -11,6 +11,10 @@ import {
|
|
|
11
11
|
patchZenocodeBinaryText,
|
|
12
12
|
zenocodeLogo,
|
|
13
13
|
} from "./branding-patch.mjs";
|
|
14
|
+
import {
|
|
15
|
+
openCodePackageName,
|
|
16
|
+
openCodePackageSpec,
|
|
17
|
+
} from "./opencode-version.mjs";
|
|
14
18
|
|
|
15
19
|
const currentFilePath = realpathSync(fileURLToPath(import.meta.url));
|
|
16
20
|
const __dirname = path.dirname(currentFilePath);
|
|
@@ -40,19 +44,21 @@ const configuredOpencodePackage =
|
|
|
40
44
|
process.env.CODECORTEX_OPENCODE_PACKAGE ||
|
|
41
45
|
process.env.OPENCODE_PACKAGE ||
|
|
42
46
|
null;
|
|
43
|
-
const defaultBrandedOpencodePackage =
|
|
47
|
+
const defaultBrandedOpencodePackage = openCodePackageSpec;
|
|
44
48
|
const legacyBrandedOpencodePackage = "@textcortex/opencode-ai";
|
|
45
|
-
const fallbackOpencodePackage =
|
|
49
|
+
const fallbackOpencodePackage = openCodePackageName;
|
|
46
50
|
const opencodePackage = configuredOpencodePackage || defaultBrandedOpencodePackage;
|
|
47
51
|
const opencodeBinaryPath =
|
|
48
52
|
process.env.ZENOCODE_OPENCODE_BIN_PATH ||
|
|
49
53
|
process.env.CODECORTEX_OPENCODE_BIN_PATH ||
|
|
50
54
|
"";
|
|
51
|
-
const oauthInitiatePath = "/internal/
|
|
52
|
-
const oauthTokenPath = "/internal/
|
|
55
|
+
const oauthInitiatePath = "/internal/v2/fastapi/zenocode/oauth2/initiate";
|
|
56
|
+
const oauthTokenPath = "/internal/v2/fastapi/zenocode/oauth2/token";
|
|
53
57
|
const defaultOrder = [
|
|
58
|
+
"minimax-m3-thinking",
|
|
59
|
+
"kimi-k2-6",
|
|
54
60
|
"kimi-k2-5-thinking",
|
|
55
|
-
"glm-5",
|
|
61
|
+
"glm-5-1",
|
|
56
62
|
"gpt-5-2",
|
|
57
63
|
"gpt-5-1",
|
|
58
64
|
"gpt-5",
|
|
@@ -403,7 +409,7 @@ export function buildOpenCodeConfig({ baseUrl, providerID, model, smallModel })
|
|
|
403
409
|
[providerID]: {
|
|
404
410
|
name: "Zenocode",
|
|
405
411
|
options: {
|
|
406
|
-
baseURL: new URL("/internal/
|
|
412
|
+
baseURL: new URL("/internal/v2/fastapi/zenocode/v1", baseUrl).toString(),
|
|
407
413
|
},
|
|
408
414
|
},
|
|
409
415
|
// Older fallback opencode-ai builds can load the Codex auth plugin when
|
|
@@ -464,7 +470,7 @@ async function requestJson(url, init) {
|
|
|
464
470
|
}
|
|
465
471
|
|
|
466
472
|
async function prepareRuntime(baseUrl, token) {
|
|
467
|
-
const modelsUrl = new URL("/internal/
|
|
473
|
+
const modelsUrl = new URL("/internal/v2/fastapi/zenocode/models/api.json", baseUrl).toString();
|
|
468
474
|
const { response, payload, text } = await requestJson(modelsUrl, {
|
|
469
475
|
headers: { Authorization: `Bearer ${token}`, Accept: "application/json" },
|
|
470
476
|
});
|
|
@@ -954,11 +960,26 @@ function _runtimeBrandedBinaryPath() {
|
|
|
954
960
|
return path.join(runtimeDir, "bin", binaryName);
|
|
955
961
|
}
|
|
956
962
|
|
|
963
|
+
export function packageNameFromSpecifier(packageSpecifier) {
|
|
964
|
+
if (!packageSpecifier.includes("@")) {
|
|
965
|
+
return packageSpecifier;
|
|
966
|
+
}
|
|
967
|
+
if (!packageSpecifier.startsWith("@")) {
|
|
968
|
+
return packageSpecifier.split("@")[0];
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
const versionSeparator = packageSpecifier.lastIndexOf("@");
|
|
972
|
+
return versionSeparator > packageSpecifier.indexOf("/")
|
|
973
|
+
? packageSpecifier.slice(0, versionSeparator)
|
|
974
|
+
: packageSpecifier;
|
|
975
|
+
}
|
|
976
|
+
|
|
957
977
|
function shouldPatchOpencodeRuntimePackage(packageName) {
|
|
978
|
+
const runtimePackageName = packageNameFromSpecifier(packageName);
|
|
958
979
|
return (
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
980
|
+
runtimePackageName.endsWith("/opencode-ai") ||
|
|
981
|
+
runtimePackageName.endsWith("/zenocode-ai") ||
|
|
982
|
+
runtimePackageName === openCodePackageName
|
|
962
983
|
);
|
|
963
984
|
}
|
|
964
985
|
|
|
@@ -999,6 +1020,88 @@ async function _collectPnpmDlxPnpmDirs(rootDir) {
|
|
|
999
1020
|
return pnpmDirs;
|
|
1000
1021
|
}
|
|
1001
1022
|
|
|
1023
|
+
async function _collectNodeModulesDirs(rootDir) {
|
|
1024
|
+
const queue = [{ dir: rootDir, depth: 0 }];
|
|
1025
|
+
const nodeModulesDirs = [];
|
|
1026
|
+
|
|
1027
|
+
while (queue.length) {
|
|
1028
|
+
const current = queue.shift();
|
|
1029
|
+
const nodeModulesPath = path.join(current.dir, "node_modules");
|
|
1030
|
+
if (await _pathExists(nodeModulesPath)) {
|
|
1031
|
+
nodeModulesDirs.push(nodeModulesPath);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
if (current.depth >= 3) continue;
|
|
1035
|
+
let entries = [];
|
|
1036
|
+
try {
|
|
1037
|
+
entries = await fs.readdir(current.dir, { withFileTypes: true });
|
|
1038
|
+
} catch {
|
|
1039
|
+
continue;
|
|
1040
|
+
}
|
|
1041
|
+
for (const entry of entries) {
|
|
1042
|
+
if (!entry.isDirectory()) continue;
|
|
1043
|
+
if (entry.name === "node_modules") continue;
|
|
1044
|
+
queue.push({ dir: path.join(current.dir, entry.name), depth: current.depth + 1 });
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
return nodeModulesDirs;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
async function _collectOpencodeBinariesFromNodeModules(nodeModulesPath) {
|
|
1052
|
+
const binaryName = process.platform === "win32" ? "opencode.exe" : "opencode";
|
|
1053
|
+
const hiddenCachedBinaryName = ".opencode";
|
|
1054
|
+
const candidates = [];
|
|
1055
|
+
|
|
1056
|
+
let topLevel = [];
|
|
1057
|
+
try {
|
|
1058
|
+
topLevel = await fs.readdir(nodeModulesPath, { withFileTypes: true });
|
|
1059
|
+
} catch {
|
|
1060
|
+
return candidates;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
for (const moduleEntry of topLevel) {
|
|
1064
|
+
if (!moduleEntry.isDirectory()) continue;
|
|
1065
|
+
|
|
1066
|
+
if (moduleEntry.name === openCodePackageName) {
|
|
1067
|
+
const cachedPath = path.join(nodeModulesPath, openCodePackageName, "bin", hiddenCachedBinaryName);
|
|
1068
|
+
if (await _pathExists(cachedPath)) candidates.push(cachedPath);
|
|
1069
|
+
continue;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
if (moduleEntry.name.startsWith("opencode-")) {
|
|
1073
|
+
const binaryPath = path.join(nodeModulesPath, moduleEntry.name, "bin", binaryName);
|
|
1074
|
+
if (await _pathExists(binaryPath)) candidates.push(binaryPath);
|
|
1075
|
+
continue;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
if (!moduleEntry.name.startsWith("@")) continue;
|
|
1079
|
+
const scopePath = path.join(nodeModulesPath, moduleEntry.name);
|
|
1080
|
+
let scopedPackages = [];
|
|
1081
|
+
try {
|
|
1082
|
+
scopedPackages = await fs.readdir(scopePath, { withFileTypes: true });
|
|
1083
|
+
} catch {
|
|
1084
|
+
continue;
|
|
1085
|
+
}
|
|
1086
|
+
for (const scopedPackage of scopedPackages) {
|
|
1087
|
+
if (!scopedPackage.isDirectory()) continue;
|
|
1088
|
+
const scopedNodeModulesPath = path.join(scopePath, scopedPackage.name);
|
|
1089
|
+
|
|
1090
|
+
if (scopedPackage.name === "opencode-ai") {
|
|
1091
|
+
const cachedPath = path.join(scopedNodeModulesPath, "bin", hiddenCachedBinaryName);
|
|
1092
|
+
if (await _pathExists(cachedPath)) candidates.push(cachedPath);
|
|
1093
|
+
continue;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
if (!scopedPackage.name.startsWith("opencode-")) continue;
|
|
1097
|
+
const binaryPath = path.join(scopedNodeModulesPath, "bin", binaryName);
|
|
1098
|
+
if (await _pathExists(binaryPath)) candidates.push(binaryPath);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
return candidates;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1002
1105
|
async function _collectPnpmDlxOpencodeBinaries() {
|
|
1003
1106
|
const dlxRoots = [
|
|
1004
1107
|
path.join(os.homedir(), "Library", "Caches", "pnpm", "dlx"),
|
|
@@ -1006,8 +1109,6 @@ async function _collectPnpmDlxOpencodeBinaries() {
|
|
|
1006
1109
|
path.join(process.env.LOCALAPPDATA || "", "pnpm", "dlx"),
|
|
1007
1110
|
].filter(Boolean);
|
|
1008
1111
|
|
|
1009
|
-
const binaryName = process.platform === "win32" ? "opencode.exe" : "opencode";
|
|
1010
|
-
const hiddenCachedBinaryName = ".opencode";
|
|
1011
1112
|
const candidates = [];
|
|
1012
1113
|
|
|
1013
1114
|
for (const root of dlxRoots) {
|
|
@@ -1026,51 +1127,7 @@ async function _collectPnpmDlxOpencodeBinaries() {
|
|
|
1026
1127
|
const nodeModulesPath = path.join(pnpmDir, entry.name, "node_modules");
|
|
1027
1128
|
if (!(await _pathExists(nodeModulesPath))) continue;
|
|
1028
1129
|
|
|
1029
|
-
|
|
1030
|
-
try {
|
|
1031
|
-
topLevel = await fs.readdir(nodeModulesPath, { withFileTypes: true });
|
|
1032
|
-
} catch {
|
|
1033
|
-
continue;
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
for (const moduleEntry of topLevel) {
|
|
1037
|
-
if (!moduleEntry.isDirectory()) continue;
|
|
1038
|
-
|
|
1039
|
-
if (moduleEntry.name === "opencode-ai") {
|
|
1040
|
-
const cachedPath = path.join(nodeModulesPath, "opencode-ai", "bin", hiddenCachedBinaryName);
|
|
1041
|
-
if (await _pathExists(cachedPath)) candidates.push(cachedPath);
|
|
1042
|
-
continue;
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
if (moduleEntry.name.startsWith("opencode-")) {
|
|
1046
|
-
const binaryPath = path.join(nodeModulesPath, moduleEntry.name, "bin", binaryName);
|
|
1047
|
-
if (await _pathExists(binaryPath)) candidates.push(binaryPath);
|
|
1048
|
-
continue;
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
if (!moduleEntry.name.startsWith("@")) continue;
|
|
1052
|
-
const scopePath = path.join(nodeModulesPath, moduleEntry.name);
|
|
1053
|
-
let scopedPackages = [];
|
|
1054
|
-
try {
|
|
1055
|
-
scopedPackages = await fs.readdir(scopePath, { withFileTypes: true });
|
|
1056
|
-
} catch {
|
|
1057
|
-
continue;
|
|
1058
|
-
}
|
|
1059
|
-
for (const scopedPackage of scopedPackages) {
|
|
1060
|
-
if (!scopedPackage.isDirectory()) continue;
|
|
1061
|
-
const scopedNodeModulesPath = path.join(scopePath, scopedPackage.name);
|
|
1062
|
-
|
|
1063
|
-
if (scopedPackage.name === "opencode-ai") {
|
|
1064
|
-
const cachedPath = path.join(scopedNodeModulesPath, "bin", hiddenCachedBinaryName);
|
|
1065
|
-
if (await _pathExists(cachedPath)) candidates.push(cachedPath);
|
|
1066
|
-
continue;
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
if (!scopedPackage.name.startsWith("opencode-")) continue;
|
|
1070
|
-
const binaryPath = path.join(scopedNodeModulesPath, "bin", binaryName);
|
|
1071
|
-
if (await _pathExists(binaryPath)) candidates.push(binaryPath);
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1130
|
+
candidates.push(...await _collectOpencodeBinariesFromNodeModules(nodeModulesPath));
|
|
1074
1131
|
}
|
|
1075
1132
|
}
|
|
1076
1133
|
}
|
|
@@ -1078,6 +1135,44 @@ async function _collectPnpmDlxOpencodeBinaries() {
|
|
|
1078
1135
|
return [...new Set(candidates)];
|
|
1079
1136
|
}
|
|
1080
1137
|
|
|
1138
|
+
async function _collectNpxOpencodeBinaries() {
|
|
1139
|
+
const npxRoots = [
|
|
1140
|
+
path.join(os.homedir(), ".npm", "_npx"),
|
|
1141
|
+
path.join(process.env.LOCALAPPDATA || "", "npm-cache", "_npx"),
|
|
1142
|
+
].filter(Boolean);
|
|
1143
|
+
const candidates = [];
|
|
1144
|
+
|
|
1145
|
+
for (const root of npxRoots) {
|
|
1146
|
+
if (!(await _pathExists(root))) continue;
|
|
1147
|
+
const nodeModulesDirs = await _collectNodeModulesDirs(root);
|
|
1148
|
+
for (const nodeModulesDir of nodeModulesDirs) {
|
|
1149
|
+
candidates.push(...await _collectOpencodeBinariesFromNodeModules(nodeModulesDir));
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
return [...new Set(candidates)];
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
function _buildPackageWarmupArgs(packageName, runner) {
|
|
1157
|
+
if (runner.command === _runnerCommand("pnpm")) {
|
|
1158
|
+
return ["dlx", packageName, "--version"];
|
|
1159
|
+
}
|
|
1160
|
+
if (runner.command === _runnerCommand("npx")) {
|
|
1161
|
+
return ["--yes", packageName, "--version"];
|
|
1162
|
+
}
|
|
1163
|
+
return null;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
async function _collectRunnerOpencodeBinaries(runner) {
|
|
1167
|
+
if (runner.command === _runnerCommand("pnpm")) {
|
|
1168
|
+
return _collectPnpmDlxOpencodeBinaries();
|
|
1169
|
+
}
|
|
1170
|
+
if (runner.command === _runnerCommand("npx")) {
|
|
1171
|
+
return _collectNpxOpencodeBinaries();
|
|
1172
|
+
}
|
|
1173
|
+
return [];
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1081
1176
|
async function _adHocSignBinary(binaryPath) {
|
|
1082
1177
|
if (process.platform !== "darwin") return;
|
|
1083
1178
|
try {
|
|
@@ -1143,9 +1238,6 @@ async function _ensurePatchedOpencodeDlxBinaries(packageName, runner, options) {
|
|
|
1143
1238
|
if (!shouldPatchOpencodeRuntimePackage(packageName)) {
|
|
1144
1239
|
return null;
|
|
1145
1240
|
}
|
|
1146
|
-
if (runner.command !== _runnerCommand("pnpm")) {
|
|
1147
|
-
return null;
|
|
1148
|
-
}
|
|
1149
1241
|
if (
|
|
1150
1242
|
process.env.ZENOCODE_DISABLE_OPENCODE_LOGO_PATCH === "1" ||
|
|
1151
1243
|
process.env.CODECORTEX_DISABLE_OPENCODE_LOGO_PATCH === "1"
|
|
@@ -1153,19 +1245,18 @@ async function _ensurePatchedOpencodeDlxBinaries(packageName, runner, options) {
|
|
|
1153
1245
|
return null;
|
|
1154
1246
|
}
|
|
1155
1247
|
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
await runChild(runner.command, ["dlx", packageName, "--version"], {
|
|
1248
|
+
const warmupArgs = _buildPackageWarmupArgs(packageName, runner);
|
|
1249
|
+
try {
|
|
1250
|
+
if (warmupArgs) {
|
|
1251
|
+
await runChild(runner.command, warmupArgs, {
|
|
1161
1252
|
...options,
|
|
1162
1253
|
stdio: "ignore",
|
|
1163
1254
|
});
|
|
1164
|
-
} catch {
|
|
1165
|
-
// ignore warm-up failures and continue with best effort patching
|
|
1166
1255
|
}
|
|
1167
|
-
|
|
1256
|
+
} catch {
|
|
1257
|
+
// ignore warm-up failures and continue with best effort patching
|
|
1168
1258
|
}
|
|
1259
|
+
const binaryCandidates = await _collectRunnerOpencodeBinaries(runner);
|
|
1169
1260
|
|
|
1170
1261
|
if (!binaryCandidates.length) {
|
|
1171
1262
|
return null;
|
|
@@ -1198,6 +1289,22 @@ export function buildPackageLauncherChildOptions(options, pinnedRuntimePath) {
|
|
|
1198
1289
|
};
|
|
1199
1290
|
}
|
|
1200
1291
|
|
|
1292
|
+
export function buildPackageLauncherInvocation({ runner, args, pinnedRuntimePath }) {
|
|
1293
|
+
if (pinnedRuntimePath) {
|
|
1294
|
+
return {
|
|
1295
|
+
command: pinnedRuntimePath,
|
|
1296
|
+
args,
|
|
1297
|
+
usePinnedRuntime: true,
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
return {
|
|
1302
|
+
command: runner.command,
|
|
1303
|
+
args: runner.args,
|
|
1304
|
+
usePinnedRuntime: false,
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1201
1308
|
async function runPackageLauncher(packageName, args, options) {
|
|
1202
1309
|
const runners = [
|
|
1203
1310
|
{ command: _runnerCommand("pnpm"), args: ["dlx", packageName, ...args] },
|
|
@@ -1215,9 +1322,17 @@ async function runPackageLauncher(packageName, args, options) {
|
|
|
1215
1322
|
}
|
|
1216
1323
|
}
|
|
1217
1324
|
|
|
1218
|
-
const
|
|
1325
|
+
const invocation = buildPackageLauncherInvocation({
|
|
1326
|
+
runner,
|
|
1327
|
+
args,
|
|
1328
|
+
pinnedRuntimePath,
|
|
1329
|
+
});
|
|
1330
|
+
const childOptions = buildPackageLauncherChildOptions(
|
|
1331
|
+
options,
|
|
1332
|
+
invocation.usePinnedRuntime ? null : pinnedRuntimePath,
|
|
1333
|
+
);
|
|
1219
1334
|
|
|
1220
|
-
const result = await runChild(
|
|
1335
|
+
const result = await runChild(invocation.command, invocation.args, childOptions);
|
|
1221
1336
|
if (result.signal) {
|
|
1222
1337
|
process.kill(process.pid, result.signal);
|
|
1223
1338
|
return;
|
|
@@ -1363,10 +1478,11 @@ export async function runRuntimeWithSessionRecovery({
|
|
|
1363
1478
|
}
|
|
1364
1479
|
|
|
1365
1480
|
async function packageExistsOnNpm(packageName) {
|
|
1481
|
+
const packageSpecName = packageNameFromSpecifier(packageName);
|
|
1366
1482
|
const controller = new AbortController();
|
|
1367
1483
|
const timeout = setTimeout(() => controller.abort(), 4_000);
|
|
1368
1484
|
try {
|
|
1369
|
-
const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(
|
|
1485
|
+
const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(packageSpecName)}`, {
|
|
1370
1486
|
method: "GET",
|
|
1371
1487
|
headers: { Accept: "application/json" },
|
|
1372
1488
|
signal: controller.signal,
|
|
@@ -1424,6 +1540,11 @@ function shouldRenderBanner(args) {
|
|
|
1424
1540
|
return !args.some((arg) => suppressFlags.has(arg));
|
|
1425
1541
|
}
|
|
1426
1542
|
|
|
1543
|
+
export function shouldBypassZenocodePreparation(args) {
|
|
1544
|
+
const metadataFlags = new Set(["--help", "-h", "--version", "-v", "completion"]);
|
|
1545
|
+
return metadataFlags.has(args[0]);
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1427
1548
|
function maybeRenderBanner(args) {
|
|
1428
1549
|
if (!shouldRenderBanner(args)) {
|
|
1429
1550
|
return;
|
|
@@ -1617,6 +1738,15 @@ async function main() {
|
|
|
1617
1738
|
}
|
|
1618
1739
|
|
|
1619
1740
|
maybeRenderBanner(runtimeArgs);
|
|
1741
|
+
if (shouldBypassZenocodePreparation(runtimeArgs)) {
|
|
1742
|
+
const launchPackage = await resolveLaunchPackage();
|
|
1743
|
+
await runPackageLauncher(launchPackage, runtimeArgs, {
|
|
1744
|
+
cwd: process.cwd(),
|
|
1745
|
+
env: { ...process.env },
|
|
1746
|
+
});
|
|
1747
|
+
return;
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1620
1750
|
const tokenResolution = await resolveTokenWithAutoLogin(baseUrl, runtimeArgs, {
|
|
1621
1751
|
preferLocalhost,
|
|
1622
1752
|
});
|
|
@@ -11,32 +11,56 @@ import {
|
|
|
11
11
|
} from "./branding-patch.mjs";
|
|
12
12
|
import {
|
|
13
13
|
buildOpenCodeConfig,
|
|
14
|
+
buildPackageLauncherInvocation,
|
|
14
15
|
buildPackageLauncherChildOptions,
|
|
15
16
|
canRecoverRuntimeSessionFromTranscript,
|
|
16
17
|
buildZenocodeBanner,
|
|
17
18
|
chooseDefaults,
|
|
18
19
|
hasLocalBaseUrlFlag,
|
|
20
|
+
packageNameFromSpecifier,
|
|
19
21
|
resolveLoginBaseUrl,
|
|
20
22
|
resolveLoginSuccessIdentifier,
|
|
21
23
|
resolveTextCortexBaseUrl,
|
|
22
24
|
runRuntimeWithSessionRecovery,
|
|
25
|
+
shouldBypassZenocodePreparation,
|
|
23
26
|
shouldFallbackLoginToCloud,
|
|
24
27
|
writePrivateJsonFile,
|
|
25
28
|
} from "./run-zenocode.mjs";
|
|
26
29
|
|
|
27
|
-
test("chooseDefaults prefers
|
|
30
|
+
test("chooseDefaults prefers MiniMax M3 thinking for Zenocode", () => {
|
|
28
31
|
const defaults = chooseDefaults({
|
|
29
|
-
"glm-5": {},
|
|
30
|
-
"kimi-k2-
|
|
32
|
+
"glm-5-1": {},
|
|
33
|
+
"kimi-k2-6": {},
|
|
34
|
+
"minimax-m3-thinking": {},
|
|
31
35
|
"gpt-5-2": {},
|
|
32
36
|
});
|
|
33
37
|
|
|
34
38
|
assert.deepEqual(defaults, {
|
|
35
|
-
model: "
|
|
36
|
-
smallModel: "
|
|
39
|
+
model: "minimax-m3-thinking",
|
|
40
|
+
smallModel: "minimax-m3-thinking",
|
|
37
41
|
});
|
|
38
42
|
});
|
|
39
43
|
|
|
44
|
+
test("packageNameFromSpecifier strips npm versions without breaking scopes", () => {
|
|
45
|
+
assert.equal(packageNameFromSpecifier("opencode-ai@1.17.6"), "opencode-ai");
|
|
46
|
+
assert.equal(
|
|
47
|
+
packageNameFromSpecifier("@textcortex/zenocode-ai@1.17.6"),
|
|
48
|
+
"@textcortex/zenocode-ai",
|
|
49
|
+
);
|
|
50
|
+
assert.equal(
|
|
51
|
+
packageNameFromSpecifier("@textcortex/zenocode-ai"),
|
|
52
|
+
"@textcortex/zenocode-ai",
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("shouldBypassZenocodePreparation skips auth for runtime metadata commands", () => {
|
|
57
|
+
assert.equal(shouldBypassZenocodePreparation(["--version"]), true);
|
|
58
|
+
assert.equal(shouldBypassZenocodePreparation(["completion", "zsh"]), true);
|
|
59
|
+
assert.equal(shouldBypassZenocodePreparation(["run"]), false);
|
|
60
|
+
assert.equal(shouldBypassZenocodePreparation(["run", "implement", "completion"]), false);
|
|
61
|
+
assert.equal(shouldBypassZenocodePreparation(["run", "--help"]), false);
|
|
62
|
+
});
|
|
63
|
+
|
|
40
64
|
test("resolveTextCortexBaseUrl defaults to the cloud API for packaged usage", () => {
|
|
41
65
|
assert.equal(
|
|
42
66
|
resolveTextCortexBaseUrl({
|
|
@@ -128,12 +152,13 @@ test("buildOpenCodeConfig includes an openai stub for fallback runtime auth plug
|
|
|
128
152
|
const config = buildOpenCodeConfig({
|
|
129
153
|
baseUrl: "http://127.0.0.1:8080",
|
|
130
154
|
providerID: "textcortex",
|
|
131
|
-
model: "
|
|
132
|
-
smallModel: "
|
|
155
|
+
model: "minimax-m3-thinking",
|
|
156
|
+
smallModel: "gpt-5-2",
|
|
133
157
|
});
|
|
134
158
|
|
|
135
159
|
assert.deepEqual(config.enabled_providers, ["textcortex"]);
|
|
136
|
-
assert.equal(config.model, "textcortex/
|
|
160
|
+
assert.equal(config.model, "textcortex/minimax-m3-thinking");
|
|
161
|
+
assert.equal(config.small_model, "textcortex/gpt-5-2");
|
|
137
162
|
assert.equal(config.theme, "system");
|
|
138
163
|
assert.ok(config.provider.openai);
|
|
139
164
|
assert.deepEqual(config.provider.openai.models, {});
|
|
@@ -156,6 +181,40 @@ test("buildPackageLauncherChildOptions keeps fallback package launchers attached
|
|
|
156
181
|
});
|
|
157
182
|
});
|
|
158
183
|
|
|
184
|
+
test("buildPackageLauncherInvocation runs pinned runtimes directly", () => {
|
|
185
|
+
const invocation = buildPackageLauncherInvocation({
|
|
186
|
+
runner: {
|
|
187
|
+
command: "npx",
|
|
188
|
+
args: ["--yes", "opencode-ai@1.17.6", "--version"],
|
|
189
|
+
},
|
|
190
|
+
args: ["--version"],
|
|
191
|
+
pinnedRuntimePath: "/tmp/zenocode-runtime",
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
assert.deepEqual(invocation, {
|
|
195
|
+
command: "/tmp/zenocode-runtime",
|
|
196
|
+
args: ["--version"],
|
|
197
|
+
usePinnedRuntime: true,
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("buildPackageLauncherInvocation uses package runners without a pinned runtime", () => {
|
|
202
|
+
const invocation = buildPackageLauncherInvocation({
|
|
203
|
+
runner: {
|
|
204
|
+
command: "pnpm",
|
|
205
|
+
args: ["dlx", "opencode-ai@1.17.6", "--version"],
|
|
206
|
+
},
|
|
207
|
+
args: ["--version"],
|
|
208
|
+
pinnedRuntimePath: null,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
assert.deepEqual(invocation, {
|
|
212
|
+
command: "pnpm",
|
|
213
|
+
args: ["dlx", "opencode-ai@1.17.6", "--version"],
|
|
214
|
+
usePinnedRuntime: false,
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
159
218
|
test("buildZenocodeBanner renders block logo art instead of plain text", () => {
|
|
160
219
|
const banner = buildZenocodeBanner();
|
|
161
220
|
|
|
@@ -510,7 +569,7 @@ test("prepare-only refreshes stored Zenocode credentials when the access token h
|
|
|
510
569
|
|
|
511
570
|
if (
|
|
512
571
|
req.method === "GET" &&
|
|
513
|
-
req.url === "/internal/
|
|
572
|
+
req.url === "/internal/v2/fastapi/zenocode/models/api.json"
|
|
514
573
|
) {
|
|
515
574
|
modelAuthHeader = req.headers.authorization || null;
|
|
516
575
|
if (modelAuthHeader === "Bearer expired-access") {
|
|
@@ -524,8 +583,10 @@ test("prepare-only refreshes stored Zenocode credentials when the access token h
|
|
|
524
583
|
JSON.stringify({
|
|
525
584
|
textcortex: {
|
|
526
585
|
models: {
|
|
586
|
+
"minimax-m3-thinking": {},
|
|
587
|
+
"kimi-k2-6": {},
|
|
527
588
|
"kimi-k2-5-thinking": {},
|
|
528
|
-
"glm-5": {},
|
|
589
|
+
"glm-5-1": {},
|
|
529
590
|
},
|
|
530
591
|
},
|
|
531
592
|
}),
|
|
@@ -580,6 +641,11 @@ test("prepare-only refreshes stored Zenocode credentials when the access token h
|
|
|
580
641
|
const savedCredentials = JSON.parse(await fs.readFile(credentialsPath, "utf-8"));
|
|
581
642
|
assert.equal(savedCredentials.access_token, "fresh-access");
|
|
582
643
|
assert.equal(savedCredentials.refresh_token, "fresh-refresh");
|
|
644
|
+
|
|
645
|
+
const savedConfig = JSON.parse(
|
|
646
|
+
await fs.readFile(path.join(zenocodeHome, "opencode.jsonc"), "utf-8"),
|
|
647
|
+
);
|
|
648
|
+
assert.equal(savedConfig.model, "textcortex/minimax-m3-thinking");
|
|
583
649
|
});
|
|
584
650
|
|
|
585
651
|
test("login launches the runtime immediately with system TUI theming", async (t) => {
|
|
@@ -623,7 +689,7 @@ main().catch((error) => {
|
|
|
623
689
|
const server = http.createServer((req, res) => {
|
|
624
690
|
if (
|
|
625
691
|
req.method === "POST" &&
|
|
626
|
-
req.url === "/internal/
|
|
692
|
+
req.url === "/internal/v2/fastapi/zenocode/oauth2/initiate"
|
|
627
693
|
) {
|
|
628
694
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
629
695
|
res.end(
|
|
@@ -642,7 +708,7 @@ main().catch((error) => {
|
|
|
642
708
|
|
|
643
709
|
if (
|
|
644
710
|
req.method === "POST" &&
|
|
645
|
-
req.url === "/internal/
|
|
711
|
+
req.url === "/internal/v2/fastapi/zenocode/oauth2/token"
|
|
646
712
|
) {
|
|
647
713
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
648
714
|
res.end(
|
|
@@ -659,15 +725,17 @@ main().catch((error) => {
|
|
|
659
725
|
|
|
660
726
|
if (
|
|
661
727
|
req.method === "GET" &&
|
|
662
|
-
req.url === "/internal/
|
|
728
|
+
req.url === "/internal/v2/fastapi/zenocode/models/api.json"
|
|
663
729
|
) {
|
|
664
730
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
665
731
|
res.end(
|
|
666
732
|
JSON.stringify({
|
|
667
733
|
textcortex: {
|
|
668
734
|
models: {
|
|
735
|
+
"minimax-m3-thinking": {},
|
|
736
|
+
"kimi-k2-6": {},
|
|
669
737
|
"kimi-k2-5-thinking": {},
|
|
670
|
-
"glm-5": {},
|
|
738
|
+
"glm-5-1": {},
|
|
671
739
|
},
|
|
672
740
|
},
|
|
673
741
|
}),
|
|
@@ -736,6 +804,7 @@ main().catch((error) => {
|
|
|
736
804
|
await fs.readFile(runtimeInvocation.env.OPENCODE_CONFIG, "utf-8"),
|
|
737
805
|
);
|
|
738
806
|
assert.equal(opencodeConfig.theme, "system");
|
|
807
|
+
assert.equal(opencodeConfig.model, "textcortex/minimax-m3-thinking");
|
|
739
808
|
|
|
740
809
|
const tuiConfig = JSON.parse(
|
|
741
810
|
await fs.readFile(runtimeInvocation.env.OPENCODE_TUI_CONFIG, "utf-8"),
|