@thyn-ai/sqai 0.1.2 → 0.1.3
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/dist/index.cjs +165 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -1
- package/dist/index.d.ts +48 -1
- package/dist/index.js +166 -37
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
- package/LICENSE +0 -202
package/dist/index.cjs
CHANGED
|
@@ -173,11 +173,15 @@ function fromAlgentaError(error) {
|
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
// src/env.ts
|
|
176
|
+
var DEFAULT_BUILD_SERVICE_URL = "https://sqai-buildservice.fly.dev";
|
|
176
177
|
function readEnv(env = process.env) {
|
|
177
178
|
const apiKey = (env.SQAI_API_KEY ?? "").trim() || void 0;
|
|
178
179
|
const engineUrl = (env.SQAI_ENGINE_URL ?? "").trim() || void 0;
|
|
179
180
|
const autoInstall = (env.SQAI_RUNTIME_AUTO_INSTALL ?? "").trim() !== "0";
|
|
180
|
-
|
|
181
|
+
const modulesRaw = (env.SQAI_RUNTIME_MODULES ?? "").split(",").map((m) => m.trim()).filter((m) => m.length > 0);
|
|
182
|
+
const runtimeModules = modulesRaw.length > 0 ? modulesRaw : void 0;
|
|
183
|
+
const buildServiceUrl = (env.SQAI_BUILD_SERVICE_URL ?? "").trim() || DEFAULT_BUILD_SERVICE_URL;
|
|
184
|
+
return { apiKey, engineUrl, autoInstall, runtimeModules, buildServiceUrl };
|
|
181
185
|
}
|
|
182
186
|
function inferMode(explicit, resolved) {
|
|
183
187
|
if (explicit) {
|
|
@@ -1029,7 +1033,7 @@ async function removeQuietly(path) {
|
|
|
1029
1033
|
} catch {
|
|
1030
1034
|
}
|
|
1031
1035
|
}
|
|
1032
|
-
var RuntimeProvisioner = class {
|
|
1036
|
+
var RuntimeProvisioner = class _RuntimeProvisioner {
|
|
1033
1037
|
options;
|
|
1034
1038
|
trustRoot;
|
|
1035
1039
|
runtime = null;
|
|
@@ -1089,8 +1093,110 @@ var RuntimeProvisioner = class {
|
|
|
1089
1093
|
cacheDir() {
|
|
1090
1094
|
return this.options.cacheDir ?? runtimeCacheDir();
|
|
1091
1095
|
}
|
|
1096
|
+
/** Normalized module filter (sorted, unique, non-empty) or null for the default bundle. */
|
|
1097
|
+
filterModules() {
|
|
1098
|
+
const mods = (this.options.runtimeModules ?? []).map((m) => m.trim()).filter((m) => m.length > 0);
|
|
1099
|
+
if (mods.length === 0) {
|
|
1100
|
+
return null;
|
|
1101
|
+
}
|
|
1102
|
+
return Array.from(new Set(mods)).sort();
|
|
1103
|
+
}
|
|
1104
|
+
/** Ask the build service to compile + sign a bundle for exactly these modules. */
|
|
1105
|
+
async resolveFilteredBundle(modules, platformKey) {
|
|
1106
|
+
if (this.options.resolveFilteredBundle) {
|
|
1107
|
+
return this.options.resolveFilteredBundle(modules, platformKey);
|
|
1108
|
+
}
|
|
1109
|
+
const base = this.options.buildServiceUrl;
|
|
1110
|
+
if (!base) {
|
|
1111
|
+
throw new SqaiError(
|
|
1112
|
+
"runtime_provision_failed",
|
|
1113
|
+
"runtimeModules was set but no build service is configured. Set SQAI_BUILD_SERVICE_URL to a build-on-provision endpoint, or omit runtimeModules to use the default bundle.",
|
|
1114
|
+
{ details: { reason: "no_build_service", modules } }
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
let response;
|
|
1118
|
+
try {
|
|
1119
|
+
response = await fetch(`${base.replace(/\/$/, "")}/v1/runtime/build`, {
|
|
1120
|
+
method: "POST",
|
|
1121
|
+
headers: { "content-type": "application/json" },
|
|
1122
|
+
body: JSON.stringify({ modules, platform: platformKey })
|
|
1123
|
+
});
|
|
1124
|
+
} catch (error) {
|
|
1125
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1126
|
+
throw new SqaiError(
|
|
1127
|
+
"runtime_provision_failed",
|
|
1128
|
+
`Build service request failed: ${message}`,
|
|
1129
|
+
{ retryable: true, details: { reason: "build_service_unreachable", modules } }
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
if (!response.ok) {
|
|
1133
|
+
throw new SqaiError(
|
|
1134
|
+
"runtime_provision_failed",
|
|
1135
|
+
`Build service returned HTTP ${response.status} for the requested filter.`,
|
|
1136
|
+
{ retryable: response.status >= 500, details: { reason: "build_failed", modules } }
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
const body = await response.json();
|
|
1140
|
+
return {
|
|
1141
|
+
url: body.url,
|
|
1142
|
+
sha256: body.sha256,
|
|
1143
|
+
version: body.version ?? this.options.contract.runtime_bundle.version,
|
|
1144
|
+
cacheKey: body.cache_key
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
/**
|
|
1148
|
+
* The filter's cache key, computed IDENTICALLY to the build service
|
|
1149
|
+
* (sha256("mods|platform|version")[:20], mods = sorted+unique, comma-joined).
|
|
1150
|
+
* Lets the reuse fast-path find an already-running filtered daemon without a
|
|
1151
|
+
* build-service round-trip. The build service's returned key is authoritative
|
|
1152
|
+
* for install; this only has to match for the reuse optimization to hit.
|
|
1153
|
+
*/
|
|
1154
|
+
localCacheKey(modules, platformKey) {
|
|
1155
|
+
const canonical = modules.join(",");
|
|
1156
|
+
const version = this.options.contract.runtime_bundle.version;
|
|
1157
|
+
return (0, import_node_crypto5.createHash)("sha256").update(`${canonical}|${platformKey}|${version}`).digest("hex").slice(0, 20);
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* Deterministic per-filter daemon endpoint so a filtered runtime never collides
|
|
1161
|
+
* with the default (or another filter's) daemon on the shared default socket.
|
|
1162
|
+
*/
|
|
1163
|
+
filterEndpoint(cacheKey) {
|
|
1164
|
+
const seed = parseInt(cacheKey.slice(0, 6), 16) || 0;
|
|
1165
|
+
const port = 50100 + seed % 800;
|
|
1166
|
+
return { sock: (0, import_node_path2.join)(this.cacheDir(), `${cacheKey}.sock`), tcp: `127.0.0.1:${port}` };
|
|
1167
|
+
}
|
|
1168
|
+
static DAEMON_ENDPOINT_ENV = [
|
|
1169
|
+
"ALGENTA_DAEMON_SOCK",
|
|
1170
|
+
"ALGENTA_DAEMON_TCP",
|
|
1171
|
+
"ALGENTA_MOJO_SOCK",
|
|
1172
|
+
"ALGENTA_RUNTIME_DIR"
|
|
1173
|
+
];
|
|
1174
|
+
/** Point the about-to-be-spawned daemon at a filter-specific socket/port/dir. */
|
|
1175
|
+
applyDaemonEndpointEnv(endpoint, installDir) {
|
|
1176
|
+
const saved = {};
|
|
1177
|
+
for (const key of _RuntimeProvisioner.DAEMON_ENDPOINT_ENV) {
|
|
1178
|
+
saved[key] = process.env[key];
|
|
1179
|
+
}
|
|
1180
|
+
process.env.ALGENTA_DAEMON_SOCK = endpoint.sock;
|
|
1181
|
+
process.env.ALGENTA_DAEMON_TCP = endpoint.tcp;
|
|
1182
|
+
process.env.ALGENTA_MOJO_SOCK = `${endpoint.sock}.worker`;
|
|
1183
|
+
process.env.ALGENTA_RUNTIME_DIR = (0, import_node_path2.join)(installDir, ".runtime");
|
|
1184
|
+
return saved;
|
|
1185
|
+
}
|
|
1186
|
+
restoreEnv(saved) {
|
|
1187
|
+
for (const [key, value] of Object.entries(saved)) {
|
|
1188
|
+
if (value === void 0) {
|
|
1189
|
+
delete process.env[key];
|
|
1190
|
+
} else {
|
|
1191
|
+
process.env[key] = value;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1092
1195
|
async doEnsure() {
|
|
1093
|
-
const
|
|
1196
|
+
const platformKey = currentPlatformKey();
|
|
1197
|
+
const filter = this.filterModules();
|
|
1198
|
+
const probeEndpoint = filter ? this.filterEndpoint(this.localCacheKey(filter, platformKey)) : void 0;
|
|
1199
|
+
const runtime = this.newRuntime(probeEndpoint ? { tcpAddress: probeEndpoint.tcp } : void 0);
|
|
1094
1200
|
try {
|
|
1095
1201
|
const health = await runtime.health();
|
|
1096
1202
|
if (health.runtime_available) {
|
|
@@ -1099,40 +1205,54 @@ var RuntimeProvisioner = class {
|
|
|
1099
1205
|
}
|
|
1100
1206
|
} catch {
|
|
1101
1207
|
}
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
);
|
|
1208
|
+
let version;
|
|
1209
|
+
let installKey;
|
|
1210
|
+
let platformBundle;
|
|
1211
|
+
let endpoint;
|
|
1212
|
+
if (filter) {
|
|
1213
|
+
const resolved = await this.resolveFilteredBundle(filter, platformKey);
|
|
1214
|
+
version = resolved.version;
|
|
1215
|
+
installKey = `filtered-${resolved.cacheKey}`;
|
|
1216
|
+
platformBundle = { url: resolved.url, sha256: resolved.sha256 };
|
|
1217
|
+
endpoint = this.filterEndpoint(resolved.cacheKey);
|
|
1218
|
+
} else {
|
|
1219
|
+
const bundle = this.options.contract.runtime_bundle;
|
|
1220
|
+
const pinned = bundle.platforms[platformKey];
|
|
1221
|
+
if (!pinned) {
|
|
1222
|
+
const supported = Object.keys(bundle.platforms);
|
|
1223
|
+
throw new SqaiError(
|
|
1224
|
+
"unsupported_platform",
|
|
1225
|
+
supported.length === 0 ? "No managed runtime bundle is published yet for any platform. Start a local Algenta runtime daemon, or set SQAI_ENGINE_URL / SQAI_API_KEY to use a hosted engine." : `No managed runtime bundle is published for '${platformKey}'.`,
|
|
1226
|
+
{ details: { platform: platformKey, supported } }
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
version = bundle.version;
|
|
1230
|
+
installKey = version;
|
|
1231
|
+
platformBundle = pinned;
|
|
1112
1232
|
}
|
|
1113
1233
|
if (!this.options.autoInstall) {
|
|
1114
1234
|
throw new SqaiError(
|
|
1115
1235
|
"runtime_provision_failed",
|
|
1116
1236
|
"Automatic runtime installation is disabled (SQAI_RUNTIME_AUTO_INSTALL=0). Install manually with `sqai runtime install` or `sqai runtime install --from-file`.",
|
|
1117
|
-
{ details: { platform: platformKey, version
|
|
1237
|
+
{ details: { platform: platformKey, version } }
|
|
1118
1238
|
);
|
|
1119
1239
|
}
|
|
1120
|
-
if (compareRuntimeVersions(
|
|
1240
|
+
if (compareRuntimeVersions(version, this.trustRoot.minAcceptedRuntimeVersion) < 0) {
|
|
1121
1241
|
throw new SqaiError(
|
|
1122
1242
|
"runtime_provision_failed",
|
|
1123
|
-
`Managed runtime ${
|
|
1243
|
+
`Managed runtime ${version} is below the minimum accepted runtime version ${this.trustRoot.minAcceptedRuntimeVersion} (rollback protection).`,
|
|
1124
1244
|
{
|
|
1125
1245
|
details: {
|
|
1126
1246
|
reason: "rollback_protected",
|
|
1127
1247
|
platform: platformKey,
|
|
1128
|
-
version
|
|
1248
|
+
version,
|
|
1129
1249
|
min_accepted_version: this.trustRoot.minAcceptedRuntimeVersion
|
|
1130
1250
|
}
|
|
1131
1251
|
}
|
|
1132
1252
|
);
|
|
1133
1253
|
}
|
|
1134
|
-
const installed = await this.ensureInstalled(platformKey,
|
|
1135
|
-
return this.spawnInstalled(platformKey, installed);
|
|
1254
|
+
const installed = await this.ensureInstalled(platformKey, version, platformBundle, installKey);
|
|
1255
|
+
return this.spawnInstalled(platformKey, installed, endpoint);
|
|
1136
1256
|
}
|
|
1137
1257
|
provisionError(reason, message, platformKey, version, extra = {}, retryable = false) {
|
|
1138
1258
|
return new SqaiError("runtime_provision_failed", message, {
|
|
@@ -1174,8 +1294,8 @@ var RuntimeProvisioner = class {
|
|
|
1174
1294
|
};
|
|
1175
1295
|
}
|
|
1176
1296
|
/** Verify an existing install if present; returns null when absent/invalid. */
|
|
1177
|
-
async verifiedInstallOrNull(platformKey, version, removeOnFailure) {
|
|
1178
|
-
const installDir = (0, import_node_path2.join)(this.cacheDir(),
|
|
1297
|
+
async verifiedInstallOrNull(platformKey, version, removeOnFailure, installKey = version) {
|
|
1298
|
+
const installDir = (0, import_node_path2.join)(this.cacheDir(), installKey);
|
|
1179
1299
|
try {
|
|
1180
1300
|
await import_node_fs2.promises.access((0, import_node_path2.join)(installDir, MANIFEST_NAME));
|
|
1181
1301
|
} catch {
|
|
@@ -1226,15 +1346,15 @@ var RuntimeProvisioner = class {
|
|
|
1226
1346
|
return true;
|
|
1227
1347
|
}
|
|
1228
1348
|
}
|
|
1229
|
-
/** Serialize installers on `<cacheDir>/<
|
|
1230
|
-
async ensureInstalled(platformKey, version, platformBundle) {
|
|
1349
|
+
/** Serialize installers on `<cacheDir>/<installKey>.lock`; returns the verified install. */
|
|
1350
|
+
async ensureInstalled(platformKey, version, platformBundle, installKey = version) {
|
|
1231
1351
|
const cacheDir = this.cacheDir();
|
|
1232
1352
|
await import_node_fs2.promises.mkdir(cacheDir, { recursive: true });
|
|
1233
|
-
const existing = await this.verifiedInstallOrNull(platformKey, version, true);
|
|
1353
|
+
const existing = await this.verifiedInstallOrNull(platformKey, version, true, installKey);
|
|
1234
1354
|
if (existing) {
|
|
1235
1355
|
return existing;
|
|
1236
1356
|
}
|
|
1237
|
-
const lockPath = (0, import_node_path2.join)(cacheDir, `${
|
|
1357
|
+
const lockPath = (0, import_node_path2.join)(cacheDir, `${installKey}.lock`);
|
|
1238
1358
|
const waitTimeoutMs = this.options.lockWaitTimeoutMs ?? LOCK_WAIT_TIMEOUT_MS;
|
|
1239
1359
|
const pollIntervalMs = this.options.lockPollIntervalMs ?? LOCK_POLL_INTERVAL_MS;
|
|
1240
1360
|
const deadline = Date.now() + waitTimeoutMs;
|
|
@@ -1246,12 +1366,12 @@ var RuntimeProvisioner = class {
|
|
|
1246
1366
|
await removeQuietly(lockPath);
|
|
1247
1367
|
continue;
|
|
1248
1368
|
}
|
|
1249
|
-
const finished = await this.verifiedInstallOrNull(platformKey, version, false);
|
|
1369
|
+
const finished = await this.verifiedInstallOrNull(platformKey, version, false, installKey);
|
|
1250
1370
|
if (finished) {
|
|
1251
1371
|
return finished;
|
|
1252
1372
|
}
|
|
1253
1373
|
if (Date.now() >= deadline) {
|
|
1254
|
-
const lastChance = await this.verifiedInstallOrNull(platformKey, version, false);
|
|
1374
|
+
const lastChance = await this.verifiedInstallOrNull(platformKey, version, false, installKey);
|
|
1255
1375
|
if (lastChance) {
|
|
1256
1376
|
return lastChance;
|
|
1257
1377
|
}
|
|
@@ -1260,29 +1380,29 @@ var RuntimeProvisioner = class {
|
|
|
1260
1380
|
`Another installer holds the runtime install lock for ${version} and did not finish within ${Math.round(waitTimeoutMs / 1e3)}s. Retry, or remove the stale lock from the runtime cache directory.`,
|
|
1261
1381
|
platformKey,
|
|
1262
1382
|
version,
|
|
1263
|
-
{ lock_file: `${
|
|
1383
|
+
{ lock_file: `${installKey}.lock` },
|
|
1264
1384
|
true
|
|
1265
1385
|
);
|
|
1266
1386
|
}
|
|
1267
1387
|
await sleep(pollIntervalMs);
|
|
1268
1388
|
}
|
|
1269
1389
|
try {
|
|
1270
|
-
const raced = await this.verifiedInstallOrNull(platformKey, version, false);
|
|
1390
|
+
const raced = await this.verifiedInstallOrNull(platformKey, version, false, installKey);
|
|
1271
1391
|
if (raced) {
|
|
1272
1392
|
return raced;
|
|
1273
1393
|
}
|
|
1274
|
-
return await this.downloadVerifyExtract(platformKey, version, platformBundle);
|
|
1394
|
+
return await this.downloadVerifyExtract(platformKey, version, platformBundle, installKey);
|
|
1275
1395
|
} finally {
|
|
1276
1396
|
await removeQuietly(lockPath);
|
|
1277
1397
|
}
|
|
1278
1398
|
}
|
|
1279
1399
|
/** Holder-of-the-lock path: download → verify → extract → atomic rename. */
|
|
1280
|
-
async downloadVerifyExtract(platformKey, version, platformBundle) {
|
|
1400
|
+
async downloadVerifyExtract(platformKey, version, platformBundle, installKey = version) {
|
|
1281
1401
|
const cacheDir = this.cacheDir();
|
|
1282
1402
|
const token = (0, import_node_crypto5.randomBytes)(6).toString("hex");
|
|
1283
1403
|
const tarballPath = (0, import_node_path2.join)(cacheDir, `tmp-${token}.tgz`);
|
|
1284
1404
|
const extractDir = (0, import_node_path2.join)(cacheDir, `tmp-${token}`);
|
|
1285
|
-
const installDir = (0, import_node_path2.join)(cacheDir,
|
|
1405
|
+
const installDir = (0, import_node_path2.join)(cacheDir, installKey);
|
|
1286
1406
|
const url = resolveBundleUrl(platformBundle.url, version, platformKey);
|
|
1287
1407
|
try {
|
|
1288
1408
|
const download = this.options.downloadBundle ?? defaultDownloadBundle;
|
|
@@ -1329,7 +1449,7 @@ var RuntimeProvisioner = class {
|
|
|
1329
1449
|
try {
|
|
1330
1450
|
await import_node_fs2.promises.rename(extractDir, installDir);
|
|
1331
1451
|
} catch (error) {
|
|
1332
|
-
const raced = await this.verifiedInstallOrNull(platformKey, version, false);
|
|
1452
|
+
const raced = await this.verifiedInstallOrNull(platformKey, version, false, installKey);
|
|
1333
1453
|
if (raced) {
|
|
1334
1454
|
return raced;
|
|
1335
1455
|
}
|
|
@@ -1348,7 +1468,7 @@ var RuntimeProvisioner = class {
|
|
|
1348
1468
|
}
|
|
1349
1469
|
}
|
|
1350
1470
|
/** Spawn the installed bundle's daemon through MojoRuntime autoStart. */
|
|
1351
|
-
async spawnInstalled(platformKey, installed) {
|
|
1471
|
+
async spawnInstalled(platformKey, installed, endpoint) {
|
|
1352
1472
|
const version = installed.manifest.runtime_bundle_version;
|
|
1353
1473
|
let entryRelative;
|
|
1354
1474
|
try {
|
|
@@ -1361,7 +1481,10 @@ var RuntimeProvisioner = class {
|
|
|
1361
1481
|
"start",
|
|
1362
1482
|
"--foreground"
|
|
1363
1483
|
];
|
|
1364
|
-
const managed = this.newRuntime(
|
|
1484
|
+
const managed = this.newRuntime(
|
|
1485
|
+
endpoint ? { autoStart: true, daemonCommand, tcpAddress: endpoint.tcp } : { autoStart: true, daemonCommand }
|
|
1486
|
+
);
|
|
1487
|
+
const savedEnv = endpoint ? this.applyDaemonEndpointEnv(endpoint, installed.installDir) : null;
|
|
1365
1488
|
try {
|
|
1366
1489
|
const health = await managed.health();
|
|
1367
1490
|
if (!health.runtime_available) {
|
|
@@ -1385,6 +1508,10 @@ var RuntimeProvisioner = class {
|
|
|
1385
1508
|
version,
|
|
1386
1509
|
{ daemon_entry: entryRelative }
|
|
1387
1510
|
);
|
|
1511
|
+
} finally {
|
|
1512
|
+
if (savedEnv) {
|
|
1513
|
+
this.restoreEnv(savedEnv);
|
|
1514
|
+
}
|
|
1388
1515
|
}
|
|
1389
1516
|
try {
|
|
1390
1517
|
await import_node_fs2.promises.writeFile(
|
|
@@ -1651,7 +1778,9 @@ var SQAI = class {
|
|
|
1651
1778
|
this.resultStore = new ResultStore(config.resultStore);
|
|
1652
1779
|
this.provisioner = new RuntimeProvisioner({
|
|
1653
1780
|
contract: this.contractIndex.contract,
|
|
1654
|
-
autoInstall: env.autoInstall
|
|
1781
|
+
autoInstall: env.autoInstall,
|
|
1782
|
+
runtimeModules: config.runtimeModules ?? env.runtimeModules,
|
|
1783
|
+
buildServiceUrl: config.buildServiceUrl ?? env.buildServiceUrl
|
|
1655
1784
|
});
|
|
1656
1785
|
if (config.runtime) {
|
|
1657
1786
|
this.rawRuntime = config.runtime;
|