@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 +4 -0
- package/lib/mytecody-async-responses-bridge.js +431 -0
- package/mytecody-cli.js +173 -24
- package/package.json +1 -1
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:
|
|
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(
|
|
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="${
|
|
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
|
-
|
|
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,
|