@mytegroupinc/myte-core 0.0.37 → 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/lib/mytecody-async-responses-bridge.js +431 -0
- package/mytecody-cli.js +30 -10
- package/package.json +1 -1
|
@@ -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,6 +12,9 @@ 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
|
|
|
16
19
|
const PACKAGE_NAME = "myte";
|
|
17
20
|
const PACKAGE_VERSION = require("./package.json").version;
|
|
@@ -249,6 +252,7 @@ function commonStatus(args, envPath) {
|
|
|
249
252
|
gateway: {
|
|
250
253
|
base_url: gatewayBase(args),
|
|
251
254
|
inference_base_url: codyInferenceBase(args),
|
|
255
|
+
responses_transport: "async-job-bridge",
|
|
252
256
|
network_required_for_coding: true,
|
|
253
257
|
},
|
|
254
258
|
instruction_pack: {
|
|
@@ -765,7 +769,7 @@ function writeCodexModelCatalog() {
|
|
|
765
769
|
return catalogPath;
|
|
766
770
|
}
|
|
767
771
|
|
|
768
|
-
function writeCodexConfig(args = {}) {
|
|
772
|
+
function writeCodexConfig(args = {}, providerBaseUrl = codyInferenceBase(args)) {
|
|
769
773
|
fs.mkdirSync(codexHome(), { recursive: true });
|
|
770
774
|
const catalogPath = writeCodexModelCatalog();
|
|
771
775
|
const config = `model = ${tomlString(DEFAULT_MODEL_ALIAS)}
|
|
@@ -786,7 +790,7 @@ terminal_title = ["project"]
|
|
|
786
790
|
|
|
787
791
|
[model_providers.myte_ai]
|
|
788
792
|
name = "Myte AI"
|
|
789
|
-
base_url = ${tomlString(
|
|
793
|
+
base_url = ${tomlString(providerBaseUrl)}
|
|
790
794
|
env_key = "MYTE_CODY_AUTH_TOKEN"
|
|
791
795
|
wire_api = "responses"
|
|
792
796
|
requires_openai_auth = false
|
|
@@ -826,14 +830,14 @@ function resolveCodexCommand() {
|
|
|
826
830
|
return null;
|
|
827
831
|
}
|
|
828
832
|
|
|
829
|
-
function codexProviderArgs(args = {}) {
|
|
833
|
+
function codexProviderArgs(args = {}, providerBaseUrl = codyInferenceBase(args)) {
|
|
830
834
|
return [
|
|
831
835
|
"-c",
|
|
832
836
|
'model_provider="myte_ai"',
|
|
833
837
|
"-c",
|
|
834
838
|
'model_providers.myte_ai.name="Myte AI"',
|
|
835
839
|
"-c",
|
|
836
|
-
`model_providers.myte_ai.base_url="${
|
|
840
|
+
`model_providers.myte_ai.base_url="${providerBaseUrl}"`,
|
|
837
841
|
"-c",
|
|
838
842
|
'model_providers.myte_ai.env_key="MYTE_CODY_AUTH_TOKEN"',
|
|
839
843
|
"-c",
|
|
@@ -863,8 +867,8 @@ function codexProviderArgs(args = {}) {
|
|
|
863
867
|
];
|
|
864
868
|
}
|
|
865
869
|
|
|
866
|
-
function codexLaunchArgs(rawArgs, args = {}) {
|
|
867
|
-
const providerArgs = codexProviderArgs(args);
|
|
870
|
+
function codexLaunchArgs(rawArgs, args = {}, providerBaseUrl = codyInferenceBase(args)) {
|
|
871
|
+
const providerArgs = codexProviderArgs(args, providerBaseUrl);
|
|
868
872
|
if (!rawArgs.length) return providerArgs;
|
|
869
873
|
if (rawArgs[0] === "exec") return [...providerArgs, "exec", "--skip-git-repo-check", ...rawArgs.slice(1)];
|
|
870
874
|
return [...providerArgs, ...rawArgs];
|
|
@@ -880,7 +884,6 @@ async function runCodex(rawArgs, args = {}, envPath = null) {
|
|
|
880
884
|
const progress = setupProgress(splash);
|
|
881
885
|
splash.start("preparing trusted workspace");
|
|
882
886
|
progress("preparing trusted workspace");
|
|
883
|
-
writeCodexConfig(args);
|
|
884
887
|
try {
|
|
885
888
|
const install = await ensureBrandedEngineInstalled(args, envPath, { progress });
|
|
886
889
|
if (install.ok && install.installed) {
|
|
@@ -914,12 +917,27 @@ async function runCodex(rawArgs, args = {}, envPath = null) {
|
|
|
914
917
|
console.error("Run `mytecody update` with access to the Myte release manifest.");
|
|
915
918
|
return 1;
|
|
916
919
|
}
|
|
917
|
-
|
|
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)];
|
|
918
935
|
const env = {
|
|
919
936
|
...process.env,
|
|
920
937
|
CODEX_HOME: codexHome(),
|
|
921
938
|
MYTE_CODY_AUTH_TOKEN: token,
|
|
922
939
|
MYTE_CODY_BRAND: "1",
|
|
940
|
+
MYTE_CODY_BRIDGE_BASE_URL: bridge.baseUrl,
|
|
923
941
|
};
|
|
924
942
|
progress("opening MyteCody workspace");
|
|
925
943
|
await splash.stop();
|
|
@@ -933,9 +951,11 @@ async function runCodex(rawArgs, args = {}, envPath = null) {
|
|
|
933
951
|
});
|
|
934
952
|
child.on("error", (error) => {
|
|
935
953
|
console.error(`Unable to launch MyteCody engine: ${error.message || error}`);
|
|
936
|
-
resolve(1);
|
|
954
|
+
bridge.close().finally(() => resolve(1));
|
|
955
|
+
});
|
|
956
|
+
child.on("close", (code) => {
|
|
957
|
+
bridge.close().finally(() => resolve(Number.isInteger(code) ? code : 1));
|
|
937
958
|
});
|
|
938
|
-
child.on("close", (code) => resolve(Number.isInteger(code) ? code : 1));
|
|
939
959
|
});
|
|
940
960
|
}
|
|
941
961
|
|