@mytegroupinc/myte-core 0.0.36 → 0.0.38

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/README.md CHANGED
@@ -31,6 +31,10 @@ This package exists so the public wrapper can stay small and versioned cleanly.
31
31
  run, it installs the branded MyteCody engine from the Myte release manifest
32
32
  into a user-local Myte cache after signature/hash checks. Coding execution
33
33
  requires that engine, a reachable Myte AI Cody gateway, and `MYTEAI_API_KEY`.
34
+ - `mytecody doctor` reports both the signed MyteCody engine state and this npm
35
+ package version. `mytecody update` updates the engine only. Use
36
+ `npm install -g myte@latest` to update the launcher and the Myte API CLI
37
+ commands shipped by npm.
34
38
  - The package does not bundle the large MyteCody engine binary. See
35
39
  `THIRD_PARTY_NOTICES.md` and `TRADEMARKS.md` for Codex lineage and Myte brand
36
40
  notices.
@@ -0,0 +1,431 @@
1
+ "use strict";
2
+
3
+ const http = require("node:http");
4
+ const crypto = require("node:crypto");
5
+
6
+ const HOST = "127.0.0.1";
7
+ const PATCH_TOOL_NAME = "apply_patch";
8
+
9
+ function sendJson(res, status, payload, headers = {}) {
10
+ res.writeHead(status, {
11
+ "content-type": "application/json",
12
+ "cache-control": "no-store",
13
+ ...headers,
14
+ });
15
+ res.end(JSON.stringify(payload));
16
+ }
17
+
18
+ function readJsonBody(req) {
19
+ return new Promise((resolve, reject) => {
20
+ const chunks = [];
21
+ req.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
22
+ req.on("error", reject);
23
+ req.on("end", () => {
24
+ const text = Buffer.concat(chunks).toString("utf8");
25
+ if (!text.trim()) return resolve(null);
26
+ try {
27
+ resolve(JSON.parse(text));
28
+ } catch (error) {
29
+ reject(new Error(`invalid JSON body: ${error.message || error}`));
30
+ }
31
+ });
32
+ });
33
+ }
34
+
35
+ function stableJson(value) {
36
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
37
+ if (value && typeof value === "object") {
38
+ return `{${Object.keys(value)
39
+ .sort()
40
+ .map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`)
41
+ .join(",")}}`;
42
+ }
43
+ return JSON.stringify(value);
44
+ }
45
+
46
+ function requestHash(value) {
47
+ return crypto.createHash("sha256").update(stableJson(value)).digest("hex");
48
+ }
49
+
50
+ function parseJsonMaybe(value) {
51
+ if (value && typeof value === "object") return value;
52
+ if (typeof value !== "string") return null;
53
+ try {
54
+ return JSON.parse(value);
55
+ } catch {
56
+ return null;
57
+ }
58
+ }
59
+
60
+ function extractPatchArgument(argumentsValue) {
61
+ const parsed = parseJsonMaybe(argumentsValue);
62
+ if (parsed && typeof parsed.patch === "string") return parsed.patch;
63
+ if (parsed && typeof parsed.input === "string") return parsed.input;
64
+ if (
65
+ typeof argumentsValue === "string" &&
66
+ argumentsValue.includes("*** Begin Patch") &&
67
+ argumentsValue.includes("*** End Patch")
68
+ ) {
69
+ return argumentsValue;
70
+ }
71
+ return "";
72
+ }
73
+
74
+ function normalizeOutputItem(item) {
75
+ if (!item || typeof item !== "object") return null;
76
+ const normalized = { ...item };
77
+ if (
78
+ normalized.type === "function_call" &&
79
+ normalized.arguments &&
80
+ typeof normalized.arguments !== "string"
81
+ ) {
82
+ normalized.arguments = JSON.stringify(normalized.arguments);
83
+ }
84
+ return normalized;
85
+ }
86
+
87
+ function functionCallToCustomPatchCall(item) {
88
+ if (!item || item.type !== "function_call" || item.name !== PATCH_TOOL_NAME) return null;
89
+ const patch = extractPatchArgument(item.arguments);
90
+ if (!patch) return null;
91
+ const callId = item.call_id || item.id || `call_myte_patch_${Date.now()}`;
92
+ return {
93
+ id: item.id || callId,
94
+ type: "custom_tool_call",
95
+ status: item.status || "completed",
96
+ call_id: callId,
97
+ name: PATCH_TOOL_NAME,
98
+ input: patch,
99
+ };
100
+ }
101
+
102
+ function adaptOutputItemForCodex(rawItem) {
103
+ const item = normalizeOutputItem(rawItem);
104
+ if (!item) return null;
105
+ return functionCallToCustomPatchCall(item) || item;
106
+ }
107
+
108
+ function adaptResponsePayloadForCodex(payload) {
109
+ if (!payload || typeof payload !== "object") return payload;
110
+ const output = Array.isArray(payload.output)
111
+ ? payload.output.map(adaptOutputItemForCodex).filter(Boolean)
112
+ : payload.output;
113
+ return {
114
+ ...payload,
115
+ model: "myte",
116
+ output,
117
+ };
118
+ }
119
+
120
+ function sse(res, event) {
121
+ res.write(`event: ${event.type}\n`);
122
+ res.write(`data: ${JSON.stringify(event)}\n\n`);
123
+ }
124
+
125
+ function chunkText(text, size = 4096) {
126
+ const value = String(text || "");
127
+ const chunks = [];
128
+ for (let index = 0; index < value.length; index += size) {
129
+ chunks.push(value.slice(index, index + size));
130
+ }
131
+ return chunks.length ? chunks : [""];
132
+ }
133
+
134
+ function emitOutputItem(res, item, outputIndex) {
135
+ if (item && item.type === "custom_tool_call" && item.name === PATCH_TOOL_NAME) {
136
+ const itemId = item.id || item.call_id || `call_myte_patch_${Date.now()}`;
137
+ const callId = item.call_id || itemId;
138
+ const input = item.input || "";
139
+ sse(res, {
140
+ type: "response.output_item.added",
141
+ output_index: outputIndex,
142
+ item: {
143
+ ...item,
144
+ id: itemId,
145
+ call_id: callId,
146
+ input: "",
147
+ status: "in_progress",
148
+ },
149
+ });
150
+ for (const delta of chunkText(input)) {
151
+ sse(res, {
152
+ type: "response.custom_tool_call_input.delta",
153
+ output_index: outputIndex,
154
+ item_id: itemId,
155
+ call_id: callId,
156
+ delta,
157
+ });
158
+ }
159
+ sse(res, {
160
+ type: "response.custom_tool_call_input.done",
161
+ output_index: outputIndex,
162
+ item_id: itemId,
163
+ call_id: callId,
164
+ input,
165
+ });
166
+ sse(res, {
167
+ type: "response.output_item.done",
168
+ output_index: outputIndex,
169
+ item: {
170
+ ...item,
171
+ id: itemId,
172
+ call_id: callId,
173
+ input,
174
+ status: "completed",
175
+ },
176
+ });
177
+ return;
178
+ }
179
+
180
+ sse(res, {
181
+ type: "response.output_item.done",
182
+ output_index: outputIndex,
183
+ item,
184
+ });
185
+ }
186
+
187
+ function retryAfterMs(headers, fallbackMs) {
188
+ const headerValue =
189
+ headers && typeof headers.get === "function" ? Number(headers.get("retry-after")) : 0;
190
+ if (Number.isFinite(headerValue) && headerValue > 0) {
191
+ return Math.max(250, Math.min(15000, headerValue * 1000));
192
+ }
193
+ return fallbackMs;
194
+ }
195
+
196
+ function sleep(ms) {
197
+ return new Promise((resolve) => setTimeout(resolve, ms));
198
+ }
199
+
200
+ async function fetchJson(url, options = {}) {
201
+ const response = await fetch(url, options);
202
+ const text = await response.text();
203
+ let body = {};
204
+ if (text.trim()) {
205
+ try {
206
+ body = JSON.parse(text);
207
+ } catch {
208
+ body = { raw_text: text };
209
+ }
210
+ }
211
+ return { response, body, text };
212
+ }
213
+
214
+ function absoluteGatewayUrl(root, pathOrUrl) {
215
+ const value = String(pathOrUrl || "");
216
+ if (/^https?:\/\//i.test(value)) return value;
217
+ const base = String(root || "").replace(/\/+$/, "");
218
+ const tail = value.startsWith("/") ? value : `/${value}`;
219
+ return `${base}${tail}`;
220
+ }
221
+
222
+ function resultPayloadFromJobResult(body) {
223
+ if (body && typeof body === "object" && body.object === "response") return body;
224
+ if (body && typeof body === "object" && body.payload && typeof body.payload === "object") {
225
+ return body.payload;
226
+ }
227
+ return body;
228
+ }
229
+
230
+ async function submitAndDrainResponseJob(body, options) {
231
+ const gatewayRoot = String(options.gatewayRoot || "").replace(/\/+$/, "");
232
+ const token = String(options.token || "").trim();
233
+ if (!gatewayRoot) throw new Error("missing Myte Cody gateway root");
234
+ if (!token) throw new Error("missing MYTEAI_API_KEY");
235
+
236
+ const bridgeRequestId = `codybridge_${Date.now()}_${crypto.randomBytes(4).toString("hex")}`;
237
+ const requestBody = {
238
+ ...body,
239
+ request_id: body.request_id || bridgeRequestId,
240
+ stream: false,
241
+ };
242
+ const timeoutMs = Number(options.timeoutMs || process.env.MYTE_CODY_ASYNC_TIMEOUT_MS || 900000);
243
+ const startedAt = Date.now();
244
+ const submitUrl = `${gatewayRoot}/cody/v1/jobs/responses`;
245
+ const authHeaders = {
246
+ authorization: `Bearer ${token}`,
247
+ accept: "application/json",
248
+ };
249
+ const idempotencyKey = `mytecody-${bridgeRequestId}-${requestHash(requestBody).slice(0, 16)}`;
250
+ const submitted = await fetchJson(submitUrl, {
251
+ method: "POST",
252
+ headers: {
253
+ ...authHeaders,
254
+ "content-type": "application/json",
255
+ "idempotency-key": idempotencyKey,
256
+ },
257
+ body: JSON.stringify(requestBody),
258
+ });
259
+ if (!submitted.response.ok && submitted.response.status !== 202) {
260
+ throw new Error(
261
+ `MyteCody async submit failed (${submitted.response.status}): ${submitted.text.slice(0, 500)}`,
262
+ );
263
+ }
264
+
265
+ const submitBody = submitted.body || {};
266
+ const jobId = submitBody.job_id || submitBody.id;
267
+ const poll = submitBody.poll || {};
268
+ let resultUrl = absoluteGatewayUrl(
269
+ gatewayRoot,
270
+ poll.result_url || (jobId ? `/cody/v1/jobs/${jobId}/result` : ""),
271
+ );
272
+ const statusUrl = absoluteGatewayUrl(
273
+ gatewayRoot,
274
+ poll.status_url || (jobId ? `/cody/v1/jobs/${jobId}` : ""),
275
+ );
276
+ if (!jobId || !resultUrl) {
277
+ return resultPayloadFromJobResult(submitBody);
278
+ }
279
+
280
+ let waitMs = retryAfterMs(submitted.response.headers, 500);
281
+ while (Date.now() - startedAt < timeoutMs) {
282
+ const separator = resultUrl.includes("?") ? "&" : "?";
283
+ const result = await fetchJson(`${resultUrl}${separator}consume=1`, {
284
+ method: "GET",
285
+ headers: authHeaders,
286
+ });
287
+ if (result.response.status === 200) return resultPayloadFromJobResult(result.body);
288
+ if (result.response.status !== 202) {
289
+ throw new Error(
290
+ `MyteCody async result failed (${result.response.status}): ${result.text.slice(0, 500)}`,
291
+ );
292
+ }
293
+ const status = result.body || {};
294
+ if (status.status === "failed" || status.status === "cancelled" || status.status === "expired") {
295
+ throw new Error(`MyteCody async job ${jobId} ended with status ${status.status}`);
296
+ }
297
+ resultUrl = absoluteGatewayUrl(gatewayRoot, status.poll?.result_url || resultUrl);
298
+ waitMs = retryAfterMs(result.response.headers, waitMs);
299
+ if (typeof options.onPoll === "function") {
300
+ options.onPoll({
301
+ job_id: jobId,
302
+ status: status.status || "queued",
303
+ status_url: statusUrl,
304
+ result_url: resultUrl,
305
+ });
306
+ }
307
+ await sleep(waitMs);
308
+ }
309
+ throw new Error(`MyteCody async job ${jobId} timed out after ${Math.round(timeoutMs / 1000)}s`);
310
+ }
311
+
312
+ async function handleResponses(req, res, body, options) {
313
+ const responseId = `resp_myte_${Date.now()}_${crypto.randomBytes(4).toString("hex")}`;
314
+ const wantsStream = body && body.stream !== false;
315
+ if (!wantsStream) {
316
+ const payload = adaptResponsePayloadForCodex(await submitAndDrainResponseJob(body, options));
317
+ return sendJson(res, 200, payload);
318
+ }
319
+
320
+ res.writeHead(200, {
321
+ "content-type": "text/event-stream; charset=utf-8",
322
+ "cache-control": "no-cache, no-transform",
323
+ connection: "keep-alive",
324
+ "x-myte-cody-async-bridge": "1",
325
+ });
326
+ sse(res, {
327
+ type: "response.created",
328
+ response: { id: responseId, object: "response", status: "in_progress" },
329
+ });
330
+
331
+ const keepalive = setInterval(() => {
332
+ res.write(": keepalive\n\n");
333
+ }, 10000);
334
+ let closed = false;
335
+ res.on("close", () => {
336
+ closed = true;
337
+ clearInterval(keepalive);
338
+ });
339
+
340
+ try {
341
+ const payload = adaptResponsePayloadForCodex(await submitAndDrainResponseJob(body, options));
342
+ if (closed) return;
343
+ const output = Array.isArray(payload && payload.output) ? payload.output : [];
344
+ output.forEach((item, index) => emitOutputItem(res, item, index));
345
+ sse(res, {
346
+ type: "response.completed",
347
+ response: {
348
+ ...payload,
349
+ id: responseId,
350
+ object: payload && payload.object ? payload.object : "response",
351
+ status: "completed",
352
+ },
353
+ });
354
+ clearInterval(keepalive);
355
+ res.end();
356
+ } catch (error) {
357
+ clearInterval(keepalive);
358
+ if (!closed) {
359
+ sse(res, {
360
+ type: "response.failed",
361
+ response: {
362
+ id: responseId,
363
+ error: {
364
+ code: "myte_cody_async_bridge_failed",
365
+ message: error && error.message ? error.message : String(error),
366
+ },
367
+ },
368
+ });
369
+ res.end();
370
+ }
371
+ }
372
+ }
373
+
374
+ async function handle(req, res, options) {
375
+ try {
376
+ const url = new URL(req.url, `http://${HOST}`);
377
+ if (req.method === "GET" && (url.pathname === "/health" || url.pathname === "/v1/health")) {
378
+ return sendJson(res, 200, { ok: true, service: "myte-cody-async-responses-bridge" });
379
+ }
380
+ if (req.method === "GET" && url.pathname === "/v1/models") {
381
+ return sendJson(res, 200, {
382
+ object: "list",
383
+ data: [{ id: "myte", object: "model", owned_by: "myte" }],
384
+ });
385
+ }
386
+ if (req.method === "POST" && (url.pathname === "/v1/responses" || url.pathname === "/responses")) {
387
+ const body = await readJsonBody(req);
388
+ if (!body || typeof body !== "object") {
389
+ return sendJson(res, 400, { error: { message: "JSON object body required" } });
390
+ }
391
+ return handleResponses(req, res, body, options);
392
+ }
393
+ return sendJson(res, 404, { error: { message: "not found" } });
394
+ } catch (error) {
395
+ return sendJson(res, 500, {
396
+ error: { message: error && error.message ? error.message : String(error) },
397
+ });
398
+ }
399
+ }
400
+
401
+ async function startMyteCodyAsyncResponsesBridge(options = {}) {
402
+ const server = http.createServer((req, res) => {
403
+ handle(req, res, options);
404
+ });
405
+ await new Promise((resolve, reject) => {
406
+ server.once("error", reject);
407
+ server.listen(0, HOST, resolve);
408
+ });
409
+ const address = server.address();
410
+ const port = address && typeof address === "object" ? address.port : 0;
411
+ let closed = false;
412
+ return {
413
+ host: HOST,
414
+ port,
415
+ baseUrl: `http://${HOST}:${port}/v1`,
416
+ close: () => {
417
+ if (closed) return Promise.resolve();
418
+ closed = true;
419
+ return new Promise((resolve) => {
420
+ server.close(() => resolve());
421
+ });
422
+ },
423
+ };
424
+ }
425
+
426
+ module.exports = {
427
+ adaptResponsePayloadForCodex,
428
+ extractPatchArgument,
429
+ startMyteCodyAsyncResponsesBridge,
430
+ submitAndDrainResponseJob,
431
+ };
package/mytecody-cli.js CHANGED
@@ -12,14 +12,21 @@ const {
12
12
  normalizeMyteAiBase,
13
13
  } = require("./lib/ai-gateway");
14
14
  const { createMyteSplash } = require("./lib/mytecody-splash");
15
+ const {
16
+ startMyteCodyAsyncResponsesBridge,
17
+ } = require("./lib/mytecody-async-responses-bridge");
15
18
 
19
+ const PACKAGE_NAME = "myte";
16
20
  const PACKAGE_VERSION = require("./package.json").version;
21
+ const DEFAULT_PACKAGE_LATEST_URL = "https://registry.npmjs.org/myte/latest";
17
22
  const DEFAULT_CHANNEL = "alpha";
18
23
  const DEFAULT_MODEL_ALIAS = "myte";
19
24
  const DEFAULT_CONTEXT_WINDOW = Number(process.env.MYTE_CODY_CONTEXT_WINDOW || 49152);
20
25
  const DEFAULT_AUTO_COMPACT_TOKENS = Number(process.env.MYTE_CODY_AUTO_COMPACT_TOKENS || 40960);
21
26
  const DEFAULT_TOOL_OUTPUT_TOKENS = Number(process.env.MYTE_CODY_TOOL_OUTPUT_TOKENS || 10000);
22
27
  const DEFAULT_AGENT_THREADS = Number(process.env.MYTE_CODY_AGENT_THREADS || 4);
28
+ const CLIENT_BASE_INSTRUCTIONS =
29
+ "You are MyteCody, a coding agent running through the Myte coding gateway.";
23
30
 
24
31
  function findEnvPath(startDir) {
25
32
  let cur = startDir;
@@ -214,6 +221,10 @@ Usage:
214
221
  mytecody update [--json] [--manifest <url-or-file>]
215
222
  mytecody help
216
223
 
224
+ Updates:
225
+ mytecody update updates the signed MyteCody engine only
226
+ npm install -g myte@latest updates this npm launcher and Myte API tools
227
+
217
228
  Network:
218
229
  The distributed MyteCody client uses the Myte AI gateway for coding
219
230
  inference. It can inspect local config without network, but coding requires
@@ -233,6 +244,7 @@ function commonStatus(args, envPath) {
233
244
  product: "MyteCody",
234
245
  command: "mytecody",
235
246
  package_version: PACKAGE_VERSION,
247
+ package: packageStatusBase(args),
236
248
  mode: "team-gateway",
237
249
  workspace: process.cwd(),
238
250
  env_file: envPath,
@@ -240,8 +252,19 @@ function commonStatus(args, envPath) {
240
252
  gateway: {
241
253
  base_url: gatewayBase(args),
242
254
  inference_base_url: codyInferenceBase(args),
255
+ responses_transport: "async-job-bridge",
243
256
  network_required_for_coding: true,
244
257
  },
258
+ instruction_pack: {
259
+ owner: "myte-cody-gateway",
260
+ client_embedded: false,
261
+ },
262
+ runtime: {
263
+ context_window: DEFAULT_CONTEXT_WINDOW,
264
+ auto_compact_tokens: DEFAULT_AUTO_COMPACT_TOKENS,
265
+ tool_output_tokens: DEFAULT_TOOL_OUTPUT_TOKENS,
266
+ max_concurrent_agent_threads: DEFAULT_AGENT_THREADS,
267
+ },
245
268
  release: {
246
269
  channel: String(args.channel || process.env.MYTE_CODY_RELEASE_CHANNEL || DEFAULT_CHANNEL),
247
270
  manifest_url: manifestUrl(args),
@@ -255,6 +278,103 @@ function commonStatus(args, envPath) {
255
278
  };
256
279
  }
257
280
 
281
+ function packageLatestUrl(args = {}) {
282
+ return String(
283
+ args["package-latest-url"] ||
284
+ process.env.MYTE_CODY_PACKAGE_LATEST_URL ||
285
+ process.env.MYTE_PACKAGE_LATEST_URL ||
286
+ DEFAULT_PACKAGE_LATEST_URL,
287
+ );
288
+ }
289
+
290
+ function packageUpdateCheckEnabled(args = {}) {
291
+ if (args["package-update-check"] === false) return false;
292
+ return process.env.MYTE_CODY_PACKAGE_UPDATE_CHECK !== "0";
293
+ }
294
+
295
+ function packageStatusBase(args = {}) {
296
+ return {
297
+ name: PACKAGE_NAME,
298
+ installed_version: PACKAGE_VERSION,
299
+ latest_version: null,
300
+ update_available: null,
301
+ check_status: "not-checked",
302
+ latest_url: packageLatestUrl(args),
303
+ update_command: `npm install -g ${PACKAGE_NAME}@latest`,
304
+ engine_update_command: "mytecody update",
305
+ };
306
+ }
307
+
308
+ function parseSemverish(version) {
309
+ const main = String(version || "")
310
+ .trim()
311
+ .replace(/^v/i, "")
312
+ .split(/[+-]/)[0];
313
+ const match = main.match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
314
+ if (!match) return null;
315
+ return [match[1], match[2] || "0", match[3] || "0"].map((part) => Number(part));
316
+ }
317
+
318
+ function compareSemverish(left, right) {
319
+ const leftParts = parseSemverish(left);
320
+ const rightParts = parseSemverish(right);
321
+ if (!leftParts || !rightParts) return String(left || "").localeCompare(String(right || ""));
322
+ for (let i = 0; i < 3; i += 1) {
323
+ if (leftParts[i] > rightParts[i]) return 1;
324
+ if (leftParts[i] < rightParts[i]) return -1;
325
+ }
326
+ return 0;
327
+ }
328
+
329
+ function isVersionNewer(latest, installed = PACKAGE_VERSION) {
330
+ return compareSemverish(latest, installed) > 0;
331
+ }
332
+
333
+ function packageUpdateTimeoutMs() {
334
+ const value = Number(process.env.MYTE_CODY_PACKAGE_UPDATE_TIMEOUT_MS || 1500);
335
+ return Number.isFinite(value) && value > 0 ? value : 1500;
336
+ }
337
+
338
+ async function checkPackageUpdate(args = {}) {
339
+ const status = packageStatusBase(args);
340
+ if (!packageUpdateCheckEnabled(args)) {
341
+ return { ...status, check_status: "skipped" };
342
+ }
343
+ try {
344
+ const response = await fetchJson(status.latest_url, {
345
+ timeoutMs: packageUpdateTimeoutMs(),
346
+ });
347
+ if (!response.ok) {
348
+ return {
349
+ ...status,
350
+ check_status: "unavailable",
351
+ registry_status: response.status,
352
+ };
353
+ }
354
+ const latestVersion = response.body && response.body.version ? String(response.body.version) : "";
355
+ if (!latestVersion) {
356
+ return { ...status, check_status: "invalid-response" };
357
+ }
358
+ return {
359
+ ...status,
360
+ latest_version: latestVersion,
361
+ update_available: isVersionNewer(latestVersion, PACKAGE_VERSION),
362
+ check_status: "ok",
363
+ };
364
+ } catch (error) {
365
+ return {
366
+ ...status,
367
+ check_status: "unavailable",
368
+ error: error && error.message ? error.message : String(error),
369
+ };
370
+ }
371
+ }
372
+
373
+ function packageUpdateNotice(packageStatus) {
374
+ if (!packageStatus || packageStatus.update_available !== true) return "";
375
+ return `myte package update available: ${packageStatus.latest_version} (installed ${packageStatus.installed_version}). Run ${packageStatus.update_command}`;
376
+ }
377
+
258
378
  function printJson(payload) {
259
379
  console.log(JSON.stringify(payload, null, 2));
260
380
  }
@@ -601,26 +721,13 @@ function pathForToml(value) {
601
721
 
602
722
  function writeCodexModelCatalog() {
603
723
  fs.mkdirSync(codexHome(), { recursive: true });
604
- const baseInstructions = [
605
- "You are MyteCody, a coding agent running in the Myte terminal harness.",
606
- "",
607
- "You and the user share one local workspace. Start in the current repository and operate there by default. Inspect outside the current repository only when explicitly asked.",
608
- "",
609
- "Your job is to understand the user's intent, inspect the relevant files, run local commands when useful, edit files with the available patch tool, run focused verification, and stop when the task is actually handled.",
610
- "",
611
- "For documentation, architecture, and repo-summary work, verify each material claim independently before presenting it as implemented fact. Separate current implementation from planned direction and unknowns. Treat planning docs as intent unless source code, config, command output, or logs prove the behavior exists.",
612
- "",
613
- "For edits, prefer apply_patch. If a patch fails, read the error, inspect the target file again, and retry with a corrected patch. Do not claim completion until after inspecting the resulting file or relevant diff.",
614
- "",
615
- "Keep responses concise and factual. Do not invent commands, flags, telemetry names, tool behavior, context-window defaults, retrieval behavior, or implemented features.",
616
- ].join("\n");
617
724
  const catalog = {
618
725
  models: [
619
726
  {
620
727
  slug: DEFAULT_MODEL_ALIAS,
621
728
  display_name: "Myte",
622
729
  description: "Myte AI coding model.",
623
- base_instructions: baseInstructions,
730
+ base_instructions: CLIENT_BASE_INSTRUCTIONS,
624
731
  default_reasoning_level: "medium",
625
732
  supported_reasoning_levels: [
626
733
  { effort: "low", description: "Fast local coding pass." },
@@ -662,7 +769,7 @@ function writeCodexModelCatalog() {
662
769
  return catalogPath;
663
770
  }
664
771
 
665
- function writeCodexConfig(args = {}) {
772
+ function writeCodexConfig(args = {}, providerBaseUrl = codyInferenceBase(args)) {
666
773
  fs.mkdirSync(codexHome(), { recursive: true });
667
774
  const catalogPath = writeCodexModelCatalog();
668
775
  const config = `model = ${tomlString(DEFAULT_MODEL_ALIAS)}
@@ -683,7 +790,7 @@ terminal_title = ["project"]
683
790
 
684
791
  [model_providers.myte_ai]
685
792
  name = "Myte AI"
686
- base_url = ${tomlString(codyInferenceBase(args))}
793
+ base_url = ${tomlString(providerBaseUrl)}
687
794
  env_key = "MYTE_CODY_AUTH_TOKEN"
688
795
  wire_api = "responses"
689
796
  requires_openai_auth = false
@@ -723,14 +830,14 @@ function resolveCodexCommand() {
723
830
  return null;
724
831
  }
725
832
 
726
- function codexProviderArgs(args = {}) {
833
+ function codexProviderArgs(args = {}, providerBaseUrl = codyInferenceBase(args)) {
727
834
  return [
728
835
  "-c",
729
836
  'model_provider="myte_ai"',
730
837
  "-c",
731
838
  'model_providers.myte_ai.name="Myte AI"',
732
839
  "-c",
733
- `model_providers.myte_ai.base_url="${codyInferenceBase(args)}"`,
840
+ `model_providers.myte_ai.base_url="${providerBaseUrl}"`,
734
841
  "-c",
735
842
  'model_providers.myte_ai.env_key="MYTE_CODY_AUTH_TOKEN"',
736
843
  "-c",
@@ -760,8 +867,8 @@ function codexProviderArgs(args = {}) {
760
867
  ];
761
868
  }
762
869
 
763
- function codexLaunchArgs(rawArgs, args = {}) {
764
- const providerArgs = codexProviderArgs(args);
870
+ function codexLaunchArgs(rawArgs, args = {}, providerBaseUrl = codyInferenceBase(args)) {
871
+ const providerArgs = codexProviderArgs(args, providerBaseUrl);
765
872
  if (!rawArgs.length) return providerArgs;
766
873
  if (rawArgs[0] === "exec") return [...providerArgs, "exec", "--skip-git-repo-check", ...rawArgs.slice(1)];
767
874
  return [...providerArgs, ...rawArgs];
@@ -777,7 +884,6 @@ async function runCodex(rawArgs, args = {}, envPath = null) {
777
884
  const progress = setupProgress(splash);
778
885
  splash.start("preparing trusted workspace");
779
886
  progress("preparing trusted workspace");
780
- writeCodexConfig(args);
781
887
  try {
782
888
  const install = await ensureBrandedEngineInstalled(args, envPath, { progress });
783
889
  if (install.ok && install.installed) {
@@ -796,6 +902,14 @@ async function runCodex(rawArgs, args = {}, envPath = null) {
796
902
  return 1;
797
903
  }
798
904
 
905
+ let packageNotice = "";
906
+ try {
907
+ progress("checking myte package version");
908
+ packageNotice = packageUpdateNotice(await checkPackageUpdate(args));
909
+ } catch {
910
+ packageNotice = "";
911
+ }
912
+
799
913
  const command = resolveCodexCommand();
800
914
  if (!command) {
801
915
  await splash.stop();
@@ -803,15 +917,31 @@ async function runCodex(rawArgs, args = {}, envPath = null) {
803
917
  console.error("Run `mytecody update` with access to the Myte release manifest.");
804
918
  return 1;
805
919
  }
806
- const launchArgs = [...command.args, ...codexLaunchArgs(rawArgs, args)];
920
+ let bridge = null;
921
+ try {
922
+ progress("opening Myte inference bridge");
923
+ bridge = await startMyteCodyAsyncResponsesBridge({
924
+ gatewayRoot: gatewayRoot(args),
925
+ token,
926
+ });
927
+ writeCodexConfig(args, bridge.baseUrl);
928
+ } catch (error) {
929
+ await splash.stop();
930
+ console.error(`MyteCody inference bridge failed to start: ${error && error.message ? error.message : error}`);
931
+ return 1;
932
+ }
933
+
934
+ const launchArgs = [...command.args, ...codexLaunchArgs(rawArgs, args, bridge.baseUrl)];
807
935
  const env = {
808
936
  ...process.env,
809
937
  CODEX_HOME: codexHome(),
810
938
  MYTE_CODY_AUTH_TOKEN: token,
811
939
  MYTE_CODY_BRAND: "1",
940
+ MYTE_CODY_BRIDGE_BASE_URL: bridge.baseUrl,
812
941
  };
813
942
  progress("opening MyteCody workspace");
814
943
  await splash.stop();
944
+ if (packageNotice) statusLine(packageNotice);
815
945
  return new Promise((resolve) => {
816
946
  const child = spawn(command.cmd, launchArgs, {
817
947
  cwd: process.cwd(),
@@ -821,9 +951,11 @@ async function runCodex(rawArgs, args = {}, envPath = null) {
821
951
  });
822
952
  child.on("error", (error) => {
823
953
  console.error(`Unable to launch MyteCody engine: ${error.message || error}`);
824
- resolve(1);
954
+ bridge.close().finally(() => resolve(1));
955
+ });
956
+ child.on("close", (code) => {
957
+ bridge.close().finally(() => resolve(Number.isInteger(code) ? code : 1));
825
958
  });
826
- child.on("close", (code) => resolve(Number.isInteger(code) ? code : 1));
827
959
  });
828
960
  }
829
961
 
@@ -833,6 +965,7 @@ async function runDoctor(args, envPath) {
833
965
  ready_for_coding: false,
834
966
  ...commonStatus(args, envPath),
835
967
  };
968
+ payload.package = await checkPackageUpdate(args);
836
969
  if (args["probe-gateway"]) {
837
970
  payload.gateway.probe = await probeGateway(args);
838
971
  }
@@ -853,6 +986,17 @@ async function runDoctor(args, envPath) {
853
986
  if (payload.gateway.probe) {
854
987
  console.log(`gateway probe: ${payload.gateway.probe.ok ? "ok" : "failed"}`);
855
988
  }
989
+ console.log(`package: ${payload.package.installed_version}`);
990
+ if (payload.package.check_status === "ok" && payload.package.update_available) {
991
+ console.log(`package update: ${payload.package.latest_version} available`);
992
+ console.log(`package command: ${payload.package.update_command}`);
993
+ } else if (payload.package.check_status === "ok") {
994
+ console.log("package update: current");
995
+ } else if (payload.package.check_status === "skipped") {
996
+ console.log("package update: skipped");
997
+ } else {
998
+ console.log(`package update: ${payload.package.check_status}`);
999
+ }
856
1000
  console.log(`client: ${payload.release.client_installed ? payload.release.client_version : "not installed"}`);
857
1001
  console.log(`install: ${payload.release.install_root}`);
858
1002
  console.log("");
@@ -981,6 +1125,7 @@ async function runUpdate(args, envPath) {
981
1125
  return 0;
982
1126
  }
983
1127
  console.log(dryRun ? "MYTE CODY update dry-run" : "MYTE CODY update");
1128
+ console.log("scope: MyteCody engine only");
984
1129
  console.log(`manifest: ${payload.manifest.source}`);
985
1130
  console.log(`manifest read: ${payload.manifest.read_status}`);
986
1131
  console.log(`platform: ${payload.release.platform}`);
@@ -996,6 +1141,7 @@ async function runUpdate(args, envPath) {
996
1141
  if (payload.installed) {
997
1142
  console.log(`installed: ${payload.installed.executable}`);
998
1143
  }
1144
+ console.log(`npm launcher/API tools: ${payload.package.update_command}`);
999
1145
  return 0;
1000
1146
  }
1001
1147
 
@@ -1034,6 +1180,7 @@ if (require.main === module) {
1034
1180
  }
1035
1181
 
1036
1182
  module.exports = {
1183
+ checkPackageUpdate,
1037
1184
  codexLaunchArgs,
1038
1185
  codexProviderArgs,
1039
1186
  codyInferenceBase,
@@ -1043,6 +1190,8 @@ module.exports = {
1043
1190
  ensureBrandedEngineInstalled,
1044
1191
  gatewayRoot,
1045
1192
  installedClientCommand,
1193
+ isVersionNewer,
1194
+ packageUpdateNotice,
1046
1195
  resolveCodexCommand,
1047
1196
  run,
1048
1197
  sha256Hex,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mytegroupinc/myte-core",
3
- "version": "0.0.36",
3
+ "version": "0.0.38",
4
4
  "description": "Myte CLI core implementation.",
5
5
  "type": "commonjs",
6
6
  "main": "cli.js",