@mytegroupinc/myte-core 0.0.16 → 0.0.17
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 +2 -0
- package/cli.js +152 -19
- package/lib/ai-gateway.js +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -139,3 +139,5 @@ Examples:
|
|
|
139
139
|
- `npx myte update-client --subject "Weekly client update" --body-file ./updates/week-12.md`
|
|
140
140
|
|
|
141
141
|
This package is published under the org scope for governance; the public `myte` wrapper delegates here.
|
|
142
|
+
|
|
143
|
+
`--json-response` sends both the Myte convenience flag (`myte_json_response: true`) and the OpenAI-compatible chat-completions shape (`response_format: { type: "json_object" }`). On the public developer API, that strict-JSON path also defaults to `medium` reasoning unless the caller explicitly overrides reasoning.
|
package/cli.js
CHANGED
|
@@ -275,7 +275,7 @@ function printHelp() {
|
|
|
275
275
|
" --timeout-ms <ms> Request timeout (default: 300000)",
|
|
276
276
|
" --base-url <url> API base (default: https://api.myte.dev)",
|
|
277
277
|
" --payload-file <path> Raw OpenAI-style chat-completions payload for `myte ai`",
|
|
278
|
-
" --json-response Ask the Myte AI gateway to return clean JSON only",
|
|
278
|
+
" --json-response Ask the Myte AI gateway to return clean JSON only and send OpenAI-compatible response_format",
|
|
279
279
|
" --max-output-tokens Output token cap for `myte ai` simple queries",
|
|
280
280
|
" --temperature <num> Temperature for `myte ai` simple queries",
|
|
281
281
|
" --output-dir <path> Command Center output directory (default: <wrapper-root>/MyteCommandCenter)",
|
|
@@ -1067,9 +1067,15 @@ async function fetchQaqcSyncSnapshot({ apiBase, key, timeoutMs }) {
|
|
|
1067
1067
|
);
|
|
1068
1068
|
|
|
1069
1069
|
if (!resp.ok || body.status !== "success") {
|
|
1070
|
+
const retryAfter = resp.headers?.get?.("retry-after");
|
|
1070
1071
|
const msg = body?.message || `QAQC sync request failed (${resp.status})`;
|
|
1071
|
-
const err = new Error(
|
|
1072
|
+
const err = new Error(
|
|
1073
|
+
retryAfter
|
|
1074
|
+
? `${msg} Retry after ${retryAfter}s.`
|
|
1075
|
+
: msg
|
|
1076
|
+
);
|
|
1072
1077
|
err.status = resp.status;
|
|
1078
|
+
if (retryAfter) err.retryAfter = retryAfter;
|
|
1073
1079
|
throw err;
|
|
1074
1080
|
}
|
|
1075
1081
|
return body.data || {};
|
|
@@ -1227,15 +1233,30 @@ async function fetchRunQaqcBatchStatus({ apiBase, key, timeoutMs, batchId }) {
|
|
|
1227
1233
|
);
|
|
1228
1234
|
|
|
1229
1235
|
if (!resp.ok || body.status !== "success") {
|
|
1236
|
+
const retryAfter = resp.headers?.get?.("retry-after");
|
|
1230
1237
|
const msg = body?.message || `Run QAQC status request failed (${resp.status})`;
|
|
1231
|
-
const err = new Error(
|
|
1238
|
+
const err = new Error(
|
|
1239
|
+
retryAfter
|
|
1240
|
+
? `${msg} Retry after ${retryAfter}s.`
|
|
1241
|
+
: msg
|
|
1242
|
+
);
|
|
1232
1243
|
err.status = resp.status;
|
|
1244
|
+
if (retryAfter) err.retryAfter = retryAfter;
|
|
1233
1245
|
throw err;
|
|
1234
1246
|
}
|
|
1235
1247
|
return body.data || {};
|
|
1236
1248
|
}
|
|
1237
1249
|
|
|
1238
|
-
|
|
1250
|
+
function resolveRetryAfterMs(err, fallbackMs = 5_000) {
|
|
1251
|
+
const retryAfterRaw = firstNonEmptyString(err?.retryAfter);
|
|
1252
|
+
const retryAfterSeconds = Number.parseInt(String(retryAfterRaw || ""), 10);
|
|
1253
|
+
if (Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0) {
|
|
1254
|
+
return Math.min(retryAfterSeconds * 1_000, 60_000);
|
|
1255
|
+
}
|
|
1256
|
+
return fallbackMs;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
async function createAssistantQueryJob({ apiBase, key, payload, timeoutMs, endpoint = "/project-assistant/query" }) {
|
|
1239
1260
|
const fetchFn = await getFetch();
|
|
1240
1261
|
const url = `${apiBase}${endpoint}`;
|
|
1241
1262
|
const { resp, body } = await fetchJsonWithTimeout(
|
|
@@ -1253,9 +1274,35 @@ async function callAssistantQuery({ apiBase, key, payload, timeoutMs, endpoint =
|
|
|
1253
1274
|
);
|
|
1254
1275
|
|
|
1255
1276
|
if (!resp.ok || body.status !== "success") {
|
|
1277
|
+
const retryAfter = resp.headers?.get?.("retry-after");
|
|
1256
1278
|
const msg = body?.message || `Query failed (${resp.status})`;
|
|
1257
|
-
const err = new Error(msg);
|
|
1279
|
+
const err = new Error(retryAfter ? `${msg} Retry after ${retryAfter}s.` : msg);
|
|
1280
|
+
err.status = resp.status;
|
|
1281
|
+
if (retryAfter) err.retryAfter = retryAfter;
|
|
1282
|
+
throw err;
|
|
1283
|
+
}
|
|
1284
|
+
return body.data || {};
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
async function fetchAssistantQueryJobStatus({ apiBase, key, timeoutMs, jobId }) {
|
|
1288
|
+
const fetchFn = await getFetch();
|
|
1289
|
+
const url = `${apiBase}/project-assistant/query/${encodeURIComponent(String(jobId || ""))}`;
|
|
1290
|
+
const { resp, body } = await fetchJsonWithTimeout(
|
|
1291
|
+
fetchFn,
|
|
1292
|
+
url,
|
|
1293
|
+
{
|
|
1294
|
+
method: "GET",
|
|
1295
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
1296
|
+
},
|
|
1297
|
+
timeoutMs
|
|
1298
|
+
);
|
|
1299
|
+
|
|
1300
|
+
if (!resp.ok || body.status !== "success") {
|
|
1301
|
+
const retryAfter = resp.headers?.get?.("retry-after");
|
|
1302
|
+
const msg = body?.message || `Query status request failed (${resp.status})`;
|
|
1303
|
+
const err = new Error(retryAfter ? `${msg} Retry after ${retryAfter}s.` : msg);
|
|
1258
1304
|
err.status = resp.status;
|
|
1305
|
+
if (retryAfter) err.retryAfter = retryAfter;
|
|
1259
1306
|
throw err;
|
|
1260
1307
|
}
|
|
1261
1308
|
return body.data || {};
|
|
@@ -1326,6 +1373,10 @@ async function runRunQaqc(args) {
|
|
|
1326
1373
|
try {
|
|
1327
1374
|
finalStatus = await fetchRunQaqcBatchStatus({ apiBase, key, timeoutMs, batchId: data.batch_id });
|
|
1328
1375
|
} catch (err) {
|
|
1376
|
+
if (Number(err?.status) === 429) {
|
|
1377
|
+
await sleep(resolveRetryAfterMs(err));
|
|
1378
|
+
continue;
|
|
1379
|
+
}
|
|
1329
1380
|
console.error("Failed to fetch QAQC batch status:", err?.message || err);
|
|
1330
1381
|
process.exit(1);
|
|
1331
1382
|
}
|
|
@@ -1341,11 +1392,18 @@ async function runRunQaqc(args) {
|
|
|
1341
1392
|
|
|
1342
1393
|
if (shouldSync && ["completed", "completed_with_errors"].includes(String(finalStatus.status || "").trim())) {
|
|
1343
1394
|
let snapshot;
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1395
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
1396
|
+
try {
|
|
1397
|
+
snapshot = await fetchQaqcSyncSnapshot({ apiBase, key, timeoutMs });
|
|
1398
|
+
break;
|
|
1399
|
+
} catch (err) {
|
|
1400
|
+
if (Number(err?.status) === 429 && attempt < 4) {
|
|
1401
|
+
await sleep(resolveRetryAfterMs(err));
|
|
1402
|
+
continue;
|
|
1403
|
+
}
|
|
1404
|
+
console.error("Failed to fetch QAQC sync snapshot after batch completion:", err?.message || err);
|
|
1405
|
+
process.exit(1);
|
|
1406
|
+
}
|
|
1349
1407
|
}
|
|
1350
1408
|
|
|
1351
1409
|
let resolved;
|
|
@@ -1668,6 +1726,20 @@ function stableItemId(item, keys, fallback) {
|
|
|
1668
1726
|
return fallback;
|
|
1669
1727
|
}
|
|
1670
1728
|
|
|
1729
|
+
function bootstrapPathId(item, preferredKeys, fallbackKeys, fallback) {
|
|
1730
|
+
return stableItemId(item, preferredKeys, stableItemId(item, fallbackKeys, fallback));
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
function bootstrapScopedPathId(item, preferredKeys, scopeKeys, fallbackKeys, fallback) {
|
|
1734
|
+
const preferred = stableItemId(item, preferredKeys, "");
|
|
1735
|
+
if (preferred) return preferred;
|
|
1736
|
+
const fallbackId = stableItemId(item, fallbackKeys, fallback);
|
|
1737
|
+
const scopeParts = (Array.isArray(scopeKeys) ? scopeKeys : [])
|
|
1738
|
+
.map((key) => String(item?.[key] || "").trim())
|
|
1739
|
+
.filter(Boolean);
|
|
1740
|
+
return [...scopeParts, fallbackId].filter(Boolean).join("__") || fallbackId;
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1671
1743
|
function stringifyYaml(value) {
|
|
1672
1744
|
// eslint-disable-next-line global-require
|
|
1673
1745
|
const YAML = require("yaml");
|
|
@@ -1889,20 +1961,42 @@ function writeBootstrapSnapshot({ snapshot, wrapperRoot, outputDir }) {
|
|
|
1889
1961
|
const missions = Array.isArray(snapshot.missions) ? snapshot.missions.map((item) => scrubBootstrapValue(item)) : [];
|
|
1890
1962
|
|
|
1891
1963
|
phases.forEach((phase, index) => {
|
|
1892
|
-
const
|
|
1893
|
-
|
|
1964
|
+
const phasePathId = bootstrapPathId(
|
|
1965
|
+
phase,
|
|
1966
|
+
["phase_key", "id"],
|
|
1967
|
+
["phase_id"],
|
|
1968
|
+
`phase-${String(index + 1).padStart(3, "0")}`
|
|
1969
|
+
);
|
|
1970
|
+
writeYamlFile(path.join(phasesDir, `${phasePathId}.yml`), phase);
|
|
1894
1971
|
});
|
|
1895
1972
|
epics.forEach((epic, index) => {
|
|
1896
|
-
const
|
|
1897
|
-
|
|
1973
|
+
const epicPathId = bootstrapScopedPathId(
|
|
1974
|
+
epic,
|
|
1975
|
+
["epic_key", "id"],
|
|
1976
|
+
["phase_key", "phase_id"],
|
|
1977
|
+
["epic_id"],
|
|
1978
|
+
`epic-${String(index + 1).padStart(3, "0")}`
|
|
1979
|
+
);
|
|
1980
|
+
writeYamlFile(path.join(epicsDir, `${epicPathId}.yml`), epic);
|
|
1898
1981
|
});
|
|
1899
1982
|
stories.forEach((story, index) => {
|
|
1900
|
-
const
|
|
1901
|
-
|
|
1983
|
+
const storyPathId = bootstrapScopedPathId(
|
|
1984
|
+
story,
|
|
1985
|
+
["story_key", "id"],
|
|
1986
|
+
["phase_key", "phase_id", "epic_key", "epic_id"],
|
|
1987
|
+
["story_id"],
|
|
1988
|
+
`story-${String(index + 1).padStart(3, "0")}`
|
|
1989
|
+
);
|
|
1990
|
+
writeYamlFile(path.join(storiesDir, `${storyPathId}.yml`), story);
|
|
1902
1991
|
});
|
|
1903
1992
|
missions.forEach((mission, index) => {
|
|
1904
|
-
const
|
|
1905
|
-
|
|
1993
|
+
const missionPathId = bootstrapPathId(
|
|
1994
|
+
mission,
|
|
1995
|
+
["mission_key", "id"],
|
|
1996
|
+
["mission_id"],
|
|
1997
|
+
`mission-${String(index + 1).padStart(3, "0")}`
|
|
1998
|
+
);
|
|
1999
|
+
writeYamlFile(path.join(missionsDir, `${missionPathId}.yml`), mission);
|
|
1906
2000
|
});
|
|
1907
2001
|
|
|
1908
2002
|
if (snapshot.project && typeof snapshot.project === "object") {
|
|
@@ -3815,7 +3909,46 @@ async function runQuery(args) {
|
|
|
3815
3909
|
|
|
3816
3910
|
let data;
|
|
3817
3911
|
try {
|
|
3818
|
-
|
|
3912
|
+
const queued = await createAssistantQueryJob({ apiBase, key, payload, timeoutMs });
|
|
3913
|
+
if (queued.answer) {
|
|
3914
|
+
data = queued;
|
|
3915
|
+
} else {
|
|
3916
|
+
const jobId = firstNonEmptyString(queued.job_id, queued.id);
|
|
3917
|
+
if (!jobId) {
|
|
3918
|
+
throw new Error("Query job was accepted without a job_id.");
|
|
3919
|
+
}
|
|
3920
|
+
const pollTimeoutMs = Math.max(timeoutMs, 900_000);
|
|
3921
|
+
const startedAt = Date.now();
|
|
3922
|
+
let finalStatus = null;
|
|
3923
|
+
do {
|
|
3924
|
+
await sleep(2_000);
|
|
3925
|
+
try {
|
|
3926
|
+
finalStatus = await fetchAssistantQueryJobStatus({ apiBase, key, timeoutMs, jobId });
|
|
3927
|
+
} catch (err) {
|
|
3928
|
+
if (Number(err?.status) === 429) {
|
|
3929
|
+
await sleep(resolveRetryAfterMs(err));
|
|
3930
|
+
continue;
|
|
3931
|
+
}
|
|
3932
|
+
throw err;
|
|
3933
|
+
}
|
|
3934
|
+
if (["completed", "failed"].includes(String(finalStatus.status || "").trim())) {
|
|
3935
|
+
break;
|
|
3936
|
+
}
|
|
3937
|
+
} while (Date.now() - startedAt < pollTimeoutMs);
|
|
3938
|
+
|
|
3939
|
+
if (!finalStatus || !["completed", "failed"].includes(String(finalStatus.status || "").trim())) {
|
|
3940
|
+
throw new Error(`Timed out waiting for query job ${jobId}`);
|
|
3941
|
+
}
|
|
3942
|
+
if (String(finalStatus.status || "").trim() === "failed") {
|
|
3943
|
+
const detail = firstNonEmptyString(finalStatus?.error?.message, finalStatus?.error?.code);
|
|
3944
|
+
throw new Error(detail || `Query job ${jobId} failed`);
|
|
3945
|
+
}
|
|
3946
|
+
data = {
|
|
3947
|
+
answer: finalStatus.answer,
|
|
3948
|
+
context_blocks: finalStatus.context_blocks,
|
|
3949
|
+
telemetry: finalStatus.telemetry,
|
|
3950
|
+
};
|
|
3951
|
+
}
|
|
3819
3952
|
} catch (err) {
|
|
3820
3953
|
if (err?.name === "AbortError") {
|
|
3821
3954
|
console.error(`Request timed out after ${timeoutMs}ms`);
|
package/lib/ai-gateway.js
CHANGED
|
@@ -16,7 +16,10 @@ function buildSimpleAiPayload({ query, jsonResponse = false, maxOutputTokens, te
|
|
|
16
16
|
const payload = {
|
|
17
17
|
messages: [{ role: "user", content: String(query || "").trim() }],
|
|
18
18
|
};
|
|
19
|
-
if (jsonResponse)
|
|
19
|
+
if (jsonResponse) {
|
|
20
|
+
payload.myte_json_response = true;
|
|
21
|
+
payload.response_format = { type: "json_object" };
|
|
22
|
+
}
|
|
20
23
|
if (Number.isFinite(Number(maxOutputTokens)) && Number(maxOutputTokens) > 0) {
|
|
21
24
|
payload.max_tokens = Number(maxOutputTokens);
|
|
22
25
|
}
|