@mytegroupinc/myte-core 0.0.37 → 0.0.39

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.
Files changed (2) hide show
  1. package/mytecody-cli.js +184 -19
  2. package/package.json +1 -1
package/mytecody-cli.js CHANGED
@@ -151,13 +151,21 @@ function installRoot() {
151
151
  return path.join(os.homedir(), ".myte", "cody");
152
152
  }
153
153
 
154
+ function currentInstallRoot() {
155
+ return path.join(installRoot(), "current");
156
+ }
157
+
154
158
  function currentClientManifestPath() {
155
- return path.join(installRoot(), "current", "manifest.json");
159
+ return path.join(currentInstallRoot(), "manifest.json");
156
160
  }
157
161
 
158
162
  function currentEnginePath() {
159
163
  const executable = process.platform === "win32" ? "mytecody-engine.exe" : "mytecody-engine";
160
- return path.join(installRoot(), "current", "bin", executable);
164
+ return path.join(currentInstallRoot(), "bin", executable);
165
+ }
166
+
167
+ function currentBridgePath() {
168
+ return path.join(currentInstallRoot(), "lib", "mytecody-async-responses-bridge.js");
161
169
  }
162
170
 
163
171
  function codexHome() {
@@ -184,6 +192,96 @@ function installedClientCommand() {
184
192
  return { cmd: enginePath, args: [], source: "myte-installed-engine" };
185
193
  }
186
194
 
195
+ function loadSignedBridge() {
196
+ const bridgePath = currentBridgePath();
197
+ if (!fs.existsSync(bridgePath)) {
198
+ throw new Error("signed MyteCody inference bridge asset is missing; run `mytecody update`.");
199
+ }
200
+ const bridge = require(bridgePath);
201
+ if (!bridge || typeof bridge.startMyteCodyAsyncResponsesBridge !== "function") {
202
+ throw new Error("signed MyteCody inference bridge asset is invalid.");
203
+ }
204
+ return bridge;
205
+ }
206
+
207
+ function releaseAssetsForPlatform(manifest, artifact) {
208
+ const assets = [];
209
+ const collect = (value, source) => {
210
+ if (!value) return;
211
+ if (Array.isArray(value)) {
212
+ value.forEach((item, index) => {
213
+ if (item && typeof item === "object") {
214
+ assets.push({ ...item, source, index });
215
+ }
216
+ });
217
+ return;
218
+ }
219
+ if (typeof value === "object") {
220
+ Object.entries(value).forEach(([name, item], index) => {
221
+ if (item && typeof item === "object") {
222
+ assets.push({ name: item.name || name, ...item, source, index });
223
+ }
224
+ });
225
+ }
226
+ };
227
+
228
+ collect(manifest && manifest.client_assets, "manifest.client_assets");
229
+ collect(artifact && artifact.assets, "artifact.assets");
230
+
231
+ return assets
232
+ .map((asset) => ({
233
+ ...asset,
234
+ install_path: asset.install_path || asset.path || "",
235
+ }))
236
+ .filter((asset) => asset && asset.url && asset.sha256 && asset.install_path);
237
+ }
238
+
239
+ function assertSafeReleaseAssetInstallPath(installPath) {
240
+ const raw = String(installPath || "").replace(/\\/g, "/").trim();
241
+ if (!raw) throw new Error("release asset install_path is required");
242
+ if (path.isAbsolute(raw) || /^[A-Za-z]:/.test(raw)) {
243
+ throw new Error(`release asset install_path must be relative: ${installPath}`);
244
+ }
245
+ const parts = raw.split("/").filter(Boolean);
246
+ if (!parts.length || parts.includes("..")) {
247
+ throw new Error(`release asset install_path is unsafe: ${installPath}`);
248
+ }
249
+ return path.join(currentInstallRoot(), ...parts);
250
+ }
251
+
252
+ function currentReleaseAssetRecordByPath(current) {
253
+ const map = new Map();
254
+ for (const asset of Array.isArray(current && current.assets) ? current.assets : []) {
255
+ if (asset && asset.install_path) {
256
+ map.set(String(asset.install_path).replace(/\\/g, "/"), asset);
257
+ }
258
+ }
259
+ return map;
260
+ }
261
+
262
+ function installedReleaseAssetsMatchManifest(manifest, artifact) {
263
+ const assets = releaseAssetsForPlatform(manifest, artifact);
264
+ if (!assets.length) return true;
265
+ const current = readCurrentClientManifest();
266
+ const currentAssets = currentReleaseAssetRecordByPath(current);
267
+
268
+ for (const asset of assets) {
269
+ const installPath = String(asset.install_path || "").replace(/\\/g, "/");
270
+ const currentAsset = currentAssets.get(installPath);
271
+ if (!currentAsset) return false;
272
+ if (String(currentAsset.sha256 || "").toLowerCase() !== String(asset.sha256 || "").toLowerCase()) {
273
+ return false;
274
+ }
275
+ const targetPath = assertSafeReleaseAssetInstallPath(asset.install_path);
276
+ if (!fs.existsSync(targetPath)) return false;
277
+ const installedSha = sha256Hex(fs.readFileSync(targetPath));
278
+ if (installedSha.toLowerCase() !== String(asset.installed_sha256 || asset.sha256).toLowerCase()) {
279
+ return false;
280
+ }
281
+ }
282
+ return true;
283
+ }
284
+
187
285
  function installedClientMatchesManifest(manifest, artifact) {
188
286
  const current = readCurrentClientManifest();
189
287
  if (!current || !installedClientCommand()) return false;
@@ -204,6 +302,8 @@ function installedClientMatchesManifest(manifest, artifact) {
204
302
  }
205
303
  }
206
304
 
305
+ if (!installedReleaseAssetsMatchManifest(manifest, artifact)) return false;
306
+
207
307
  return true;
208
308
  }
209
309
 
@@ -249,6 +349,7 @@ function commonStatus(args, envPath) {
249
349
  gateway: {
250
350
  base_url: gatewayBase(args),
251
351
  inference_base_url: codyInferenceBase(args),
352
+ responses_transport: "async-job-bridge",
252
353
  network_required_for_coding: true,
253
354
  },
254
355
  instruction_pack: {
@@ -267,8 +368,10 @@ function commonStatus(args, envPath) {
267
368
  platform: platformKey(),
268
369
  install_root: installRoot(),
269
370
  engine_path: currentEnginePath(),
371
+ bridge_path: currentBridgePath(),
270
372
  client_manifest: currentClientManifestPath(),
271
373
  client_installed: Boolean(current && installedClientCommand()),
374
+ bridge_installed: fs.existsSync(currentBridgePath()),
272
375
  client_version: current && current.version ? current.version : null,
273
376
  },
274
377
  };
@@ -513,11 +616,11 @@ function localPathFromArtifactUrl(urlValue) {
513
616
  return null;
514
617
  }
515
618
 
516
- async function readArtifactBytes(artifact, { progress } = {}) {
619
+ async function readArtifactBytes(artifact, { progress, label = "MyteCody engine" } = {}) {
517
620
  const urlValue = artifact && artifact.url ? String(artifact.url) : "";
518
621
  const localPath = localPathFromArtifactUrl(urlValue);
519
622
  if (localPath) {
520
- if (progress) progress("reading local MyteCody engine artifact");
623
+ if (progress) progress(`reading local ${label} artifact`);
521
624
  return fs.readFileSync(localPath);
522
625
  }
523
626
  const headers = {};
@@ -525,7 +628,7 @@ async function readArtifactBytes(artifact, { progress } = {}) {
525
628
  if (token) headers.Authorization = `Bearer ${token}`;
526
629
  if (progress) {
527
630
  const expectedSize = Number(artifact && artifact.size_bytes ? artifact.size_bytes : 0);
528
- progress(`downloading MyteCody engine (${formatBytes(expectedSize)})`);
631
+ progress(`downloading ${label} (${formatBytes(expectedSize)})`);
529
632
  }
530
633
  const response = await fetch(urlValue, { method: "GET", headers });
531
634
  if (!response.ok) {
@@ -534,7 +637,7 @@ async function readArtifactBytes(artifact, { progress } = {}) {
534
637
  }
535
638
  if (!response.body || typeof response.body.getReader !== "function") {
536
639
  const bytes = Buffer.from(await response.arrayBuffer());
537
- if (progress) progress(`downloaded MyteCody engine (${formatBytes(bytes.length)})`);
640
+ if (progress) progress(`downloaded ${label} (${formatBytes(bytes.length)})`);
538
641
  return bytes;
539
642
  }
540
643
 
@@ -552,13 +655,13 @@ async function readArtifactBytes(artifact, { progress } = {}) {
552
655
  if (progress && total > 0) {
553
656
  const pct = Math.min(100, Math.floor((received / total) * 100));
554
657
  if (pct >= lastPct + 10 || pct === 100) {
555
- progress(`downloading MyteCody engine ${pct}% (${formatBytes(received)} / ${formatBytes(total)})`);
658
+ progress(`downloading ${label} ${pct}% (${formatBytes(received)} / ${formatBytes(total)})`);
556
659
  lastPct = pct;
557
660
  }
558
661
  }
559
662
  }
560
663
  const bytes = Buffer.concat(chunks);
561
- if (progress) progress(`downloaded MyteCody engine (${formatBytes(bytes.length)})`);
664
+ if (progress) progress(`downloaded ${label} (${formatBytes(bytes.length)})`);
562
665
  return bytes;
563
666
  }
564
667
 
@@ -617,6 +720,34 @@ function installArtifactBytes(bytes, manifest, artifact) {
617
720
  return installedManifest;
618
721
  }
619
722
 
723
+ async function installReleaseAssets(manifest, artifact, { progress } = {}) {
724
+ const assets = releaseAssetsForPlatform(manifest, artifact);
725
+ const installed = [];
726
+ for (const asset of assets) {
727
+ const name = String(asset.name || path.basename(String(asset.install_path || "")) || "client asset");
728
+ const bytes = await readArtifactBytes(asset, { progress, label: `MyteCody ${name}` });
729
+ const digest = sha256Hex(bytes);
730
+ if (digest.toLowerCase() !== String(asset.sha256 || "").toLowerCase()) {
731
+ throw new Error(`Release asset SHA-256 mismatch for ${name}: expected ${asset.sha256}, got ${digest}`);
732
+ }
733
+ const installBytes = artifactBytesForInstall(bytes, asset);
734
+ const targetPath = assertSafeReleaseAssetInstallPath(asset.install_path);
735
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
736
+ fs.writeFileSync(targetPath, installBytes);
737
+ installed.push({
738
+ name,
739
+ install_path: String(asset.install_path || "").replace(/\\/g, "/"),
740
+ url: asset.url,
741
+ sha256: asset.sha256,
742
+ format: artifactFormat(asset),
743
+ size_bytes: bytes.length,
744
+ installed_sha256: sha256Hex(installBytes),
745
+ installed_size_bytes: installBytes.length,
746
+ });
747
+ }
748
+ return installed;
749
+ }
750
+
620
751
  async function fetchJson(url, { headers = {}, timeoutMs = 8000 } = {}) {
621
752
  const controller = typeof AbortController !== "undefined" ? new AbortController() : undefined;
622
753
  const timeoutId =
@@ -765,7 +896,7 @@ function writeCodexModelCatalog() {
765
896
  return catalogPath;
766
897
  }
767
898
 
768
- function writeCodexConfig(args = {}) {
899
+ function writeCodexConfig(args = {}, providerBaseUrl = codyInferenceBase(args)) {
769
900
  fs.mkdirSync(codexHome(), { recursive: true });
770
901
  const catalogPath = writeCodexModelCatalog();
771
902
  const config = `model = ${tomlString(DEFAULT_MODEL_ALIAS)}
@@ -786,7 +917,7 @@ terminal_title = ["project"]
786
917
 
787
918
  [model_providers.myte_ai]
788
919
  name = "Myte AI"
789
- base_url = ${tomlString(codyInferenceBase(args))}
920
+ base_url = ${tomlString(providerBaseUrl)}
790
921
  env_key = "MYTE_CODY_AUTH_TOKEN"
791
922
  wire_api = "responses"
792
923
  requires_openai_auth = false
@@ -826,14 +957,14 @@ function resolveCodexCommand() {
826
957
  return null;
827
958
  }
828
959
 
829
- function codexProviderArgs(args = {}) {
960
+ function codexProviderArgs(args = {}, providerBaseUrl = codyInferenceBase(args)) {
830
961
  return [
831
962
  "-c",
832
963
  'model_provider="myte_ai"',
833
964
  "-c",
834
965
  'model_providers.myte_ai.name="Myte AI"',
835
966
  "-c",
836
- `model_providers.myte_ai.base_url="${codyInferenceBase(args)}"`,
967
+ `model_providers.myte_ai.base_url="${providerBaseUrl}"`,
837
968
  "-c",
838
969
  'model_providers.myte_ai.env_key="MYTE_CODY_AUTH_TOKEN"',
839
970
  "-c",
@@ -863,8 +994,8 @@ function codexProviderArgs(args = {}) {
863
994
  ];
864
995
  }
865
996
 
866
- function codexLaunchArgs(rawArgs, args = {}) {
867
- const providerArgs = codexProviderArgs(args);
997
+ function codexLaunchArgs(rawArgs, args = {}, providerBaseUrl = codyInferenceBase(args)) {
998
+ const providerArgs = codexProviderArgs(args, providerBaseUrl);
868
999
  if (!rawArgs.length) return providerArgs;
869
1000
  if (rawArgs[0] === "exec") return [...providerArgs, "exec", "--skip-git-repo-check", ...rawArgs.slice(1)];
870
1001
  return [...providerArgs, ...rawArgs];
@@ -880,7 +1011,6 @@ async function runCodex(rawArgs, args = {}, envPath = null) {
880
1011
  const progress = setupProgress(splash);
881
1012
  splash.start("preparing trusted workspace");
882
1013
  progress("preparing trusted workspace");
883
- writeCodexConfig(args);
884
1014
  try {
885
1015
  const install = await ensureBrandedEngineInstalled(args, envPath, { progress });
886
1016
  if (install.ok && install.installed) {
@@ -914,12 +1044,28 @@ async function runCodex(rawArgs, args = {}, envPath = null) {
914
1044
  console.error("Run `mytecody update` with access to the Myte release manifest.");
915
1045
  return 1;
916
1046
  }
917
- const launchArgs = [...command.args, ...codexLaunchArgs(rawArgs, args)];
1047
+ let bridge = null;
1048
+ try {
1049
+ progress("opening Myte inference bridge");
1050
+ const signedBridge = loadSignedBridge();
1051
+ bridge = await signedBridge.startMyteCodyAsyncResponsesBridge({
1052
+ gatewayRoot: gatewayRoot(args),
1053
+ token,
1054
+ });
1055
+ writeCodexConfig(args, bridge.baseUrl);
1056
+ } catch (error) {
1057
+ await splash.stop();
1058
+ console.error(`MyteCody inference bridge failed to start: ${error && error.message ? error.message : error}`);
1059
+ return 1;
1060
+ }
1061
+
1062
+ const launchArgs = [...command.args, ...codexLaunchArgs(rawArgs, args, bridge.baseUrl)];
918
1063
  const env = {
919
1064
  ...process.env,
920
1065
  CODEX_HOME: codexHome(),
921
1066
  MYTE_CODY_AUTH_TOKEN: token,
922
1067
  MYTE_CODY_BRAND: "1",
1068
+ MYTE_CODY_BRIDGE_BASE_URL: bridge.baseUrl,
923
1069
  };
924
1070
  progress("opening MyteCody workspace");
925
1071
  await splash.stop();
@@ -933,9 +1079,11 @@ async function runCodex(rawArgs, args = {}, envPath = null) {
933
1079
  });
934
1080
  child.on("error", (error) => {
935
1081
  console.error(`Unable to launch MyteCody engine: ${error.message || error}`);
936
- resolve(1);
1082
+ bridge.close().finally(() => resolve(1));
1083
+ });
1084
+ child.on("close", (code) => {
1085
+ bridge.close().finally(() => resolve(Number.isInteger(code) ? code : 1));
937
1086
  });
938
- child.on("close", (code) => resolve(Number.isInteger(code) ? code : 1));
939
1087
  });
940
1088
  }
941
1089
 
@@ -949,7 +1097,8 @@ async function runDoctor(args, envPath) {
949
1097
  if (args["probe-gateway"]) {
950
1098
  payload.gateway.probe = await probeGateway(args);
951
1099
  }
952
- payload.ready_for_coding = payload.auth.present && payload.release.client_installed;
1100
+ payload.ready_for_coding =
1101
+ payload.auth.present && payload.release.client_installed && payload.release.bridge_installed;
953
1102
  if (payload.gateway.probe) {
954
1103
  payload.ready_for_coding = Boolean(payload.ready_for_coding && payload.gateway.probe.ok);
955
1104
  }
@@ -978,6 +1127,7 @@ async function runDoctor(args, envPath) {
978
1127
  console.log(`package update: ${payload.package.check_status}`);
979
1128
  }
980
1129
  console.log(`client: ${payload.release.client_installed ? payload.release.client_version : "not installed"}`);
1130
+ console.log(`bridge: ${payload.release.bridge_installed ? "installed" : "not installed"}`);
981
1131
  console.log(`install: ${payload.release.install_root}`);
982
1132
  console.log("");
983
1133
  console.log("Coding requires the Myte AI gateway and a Myte AI key.");
@@ -995,6 +1145,7 @@ async function buildUpdatePayload(args, envPath, { dryRun = false, progress = nu
995
1145
  const artifact = manifest ? artifactForPlatform(manifest) : null;
996
1146
  const signature = manifest ? signatureAccepted(manifest, args) : { ok: false, signature: { status: "not-checked", verified: false } };
997
1147
  const artifactMetadata = manifest ? validateArtifactMetadata(artifact) : { status: "not-checked", ok: false };
1148
+ const releaseAssets = manifest ? releaseAssetsForPlatform(manifest, artifact) : [];
998
1149
  const payload = {
999
1150
  ok: true,
1000
1151
  dry_run: isDryRun,
@@ -1008,6 +1159,13 @@ async function buildUpdatePayload(args, envPath, { dryRun = false, progress = nu
1008
1159
  trusted_unsigned: Boolean(signature.trusted_unsigned),
1009
1160
  },
1010
1161
  artifact: artifactMetadata,
1162
+ release_assets: releaseAssets.map((asset) => ({
1163
+ name: asset.name || null,
1164
+ install_path: asset.install_path,
1165
+ url: asset.url || null,
1166
+ format: asset.format || null,
1167
+ sha256_present: Boolean(asset.sha256),
1168
+ })),
1011
1169
  };
1012
1170
 
1013
1171
  if (!isDryRun) {
@@ -1026,6 +1184,11 @@ async function buildUpdatePayload(args, envPath, { dryRun = false, progress = nu
1026
1184
  throw new Error(`Artifact SHA-256 mismatch: expected ${artifact.sha256}, got ${digest}`);
1027
1185
  }
1028
1186
  const installed = installArtifactBytes(bytes, manifest, artifact);
1187
+ const installedAssets = await installReleaseAssets(manifest, artifact, { progress });
1188
+ if (installedAssets.length) {
1189
+ installed.assets = installedAssets;
1190
+ fs.writeFileSync(currentClientManifestPath(), JSON.stringify(installed, null, 2), "utf8");
1191
+ }
1029
1192
  payload.installed = {
1030
1193
  ok: true,
1031
1194
  version: installed.version,
@@ -1035,6 +1198,7 @@ async function buildUpdatePayload(args, envPath, { dryRun = false, progress = nu
1035
1198
  installed_sha256: installed.artifact.installed_sha256,
1036
1199
  installed_size_bytes: installed.artifact.installed_size_bytes,
1037
1200
  format: installed.artifact.format,
1201
+ assets: installedAssets,
1038
1202
  };
1039
1203
  payload.release = {
1040
1204
  ...payload.release,
@@ -1165,6 +1329,7 @@ module.exports = {
1165
1329
  codexProviderArgs,
1166
1330
  codyInferenceBase,
1167
1331
  codyGatewayUrl,
1332
+ currentBridgePath,
1168
1333
  currentClientManifestPath,
1169
1334
  currentEnginePath,
1170
1335
  ensureBrandedEngineInstalled,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mytegroupinc/myte-core",
3
- "version": "0.0.37",
3
+ "version": "0.0.39",
4
4
  "description": "Myte CLI core implementation.",
5
5
  "type": "commonjs",
6
6
  "main": "cli.js",