@thyn-ai/sqai 0.1.4 → 0.1.6
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/LICENSE +202 -0
- package/NOTICE +8 -0
- package/README.md +13 -0
- package/dist/index.cjs +426 -82
- package/dist/index.d.cts +122 -21
- package/dist/index.d.ts +122 -21
- package/dist/index.js +384 -57
- package/dist/unsafe.cjs +0 -1
- package/dist/unsafe.js +0 -1
- package/package.json +9 -10
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/unsafe.cjs.map +0 -1
- package/dist/unsafe.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -112,13 +112,13 @@ var SQAI_ERROR_CODES = [
|
|
|
112
112
|
"contract_mismatch",
|
|
113
113
|
"invalid_intent"
|
|
114
114
|
];
|
|
115
|
-
var
|
|
115
|
+
var RETRYABLE_ENGINE_CODES = /* @__PURE__ */ new Set([
|
|
116
116
|
"network_error",
|
|
117
117
|
"timeout",
|
|
118
118
|
"rate_limited",
|
|
119
119
|
"service_unavailable"
|
|
120
120
|
]);
|
|
121
|
-
function
|
|
121
|
+
function fromEngineError(error) {
|
|
122
122
|
if (error instanceof SqaiError) {
|
|
123
123
|
return error;
|
|
124
124
|
}
|
|
@@ -128,7 +128,7 @@ function fromAlgentaError(error) {
|
|
|
128
128
|
const details = errorLike.details && typeof errorLike.details === "object" ? errorLike.details : {};
|
|
129
129
|
return new SqaiError(code, message, {
|
|
130
130
|
source: "engine",
|
|
131
|
-
retryable:
|
|
131
|
+
retryable: RETRYABLE_ENGINE_CODES.has(code),
|
|
132
132
|
details
|
|
133
133
|
});
|
|
134
134
|
}
|
|
@@ -137,12 +137,14 @@ function fromAlgentaError(error) {
|
|
|
137
137
|
var DEFAULT_BUILD_SERVICE_URL = "https://sqai-buildservice.fly.dev";
|
|
138
138
|
function readEnv(env = process.env) {
|
|
139
139
|
const apiKey = (env.SQAI_API_KEY ?? "").trim() || void 0;
|
|
140
|
-
const
|
|
140
|
+
const deploymentUrl = (env.SQAI_DEPLOYMENT_URL ?? "").trim() || void 0;
|
|
141
|
+
const legacyEngineUrl = (env.SQAI_ENGINE_URL ?? "").trim() || void 0;
|
|
142
|
+
const engineUrl = deploymentUrl ?? legacyEngineUrl;
|
|
141
143
|
const autoInstall = (env.SQAI_RUNTIME_AUTO_INSTALL ?? "").trim() !== "0";
|
|
142
144
|
const modulesRaw = (env.SQAI_RUNTIME_MODULES ?? "").split(",").map((m) => m.trim()).filter((m) => m.length > 0);
|
|
143
145
|
const runtimeModules = modulesRaw.length > 0 ? modulesRaw : void 0;
|
|
144
146
|
const buildServiceUrl = (env.SQAI_BUILD_SERVICE_URL ?? "").trim() || DEFAULT_BUILD_SERVICE_URL;
|
|
145
|
-
return { apiKey, engineUrl, autoInstall, runtimeModules, buildServiceUrl };
|
|
147
|
+
return { apiKey, engineUrl, deploymentUrl, autoInstall, runtimeModules, buildServiceUrl };
|
|
146
148
|
}
|
|
147
149
|
function inferMode(explicit, resolved) {
|
|
148
150
|
if (explicit) {
|
|
@@ -151,9 +153,6 @@ function inferMode(explicit, resolved) {
|
|
|
151
153
|
if (resolved.engineUrl) {
|
|
152
154
|
return "self_hosted";
|
|
153
155
|
}
|
|
154
|
-
if (resolved.apiKey) {
|
|
155
|
-
return "api";
|
|
156
|
-
}
|
|
157
156
|
return "local";
|
|
158
157
|
}
|
|
159
158
|
|
|
@@ -315,22 +314,180 @@ var ResultStore = class {
|
|
|
315
314
|
};
|
|
316
315
|
|
|
317
316
|
// src/runtime/provision.ts
|
|
318
|
-
import { homedir, platform as osPlatform, arch as osArch } from "os";
|
|
319
|
-
import { join as
|
|
320
|
-
import { createWriteStream, existsSync, promises as fs2 } from "fs";
|
|
321
|
-
import { createHash as
|
|
317
|
+
import { homedir as homedir2, platform as osPlatform, arch as osArch } from "os";
|
|
318
|
+
import { join as join3 } from "path";
|
|
319
|
+
import { createWriteStream, existsSync as existsSync2, promises as fs2 } from "fs";
|
|
320
|
+
import { createHash as createHash4, randomBytes as randomBytes2 } from "crypto";
|
|
322
321
|
import { Readable } from "stream";
|
|
323
322
|
import { pipeline } from "stream/promises";
|
|
324
323
|
import { MojoRuntime } from "algenta-sdk";
|
|
325
324
|
|
|
325
|
+
// src/runtime/auth.ts
|
|
326
|
+
import { createHash as createHash2, createPrivateKey, createPublicKey, generateKeyPairSync } from "crypto";
|
|
327
|
+
import { readFileSync, writeFileSync, mkdirSync, chmodSync, existsSync, rmSync } from "fs";
|
|
328
|
+
import { homedir, hostname } from "os";
|
|
329
|
+
import { join } from "path";
|
|
330
|
+
var CLIENT_ID = "sqai-cli";
|
|
331
|
+
var SCOPE = "sqai";
|
|
332
|
+
var PRODUCT = "sqai";
|
|
333
|
+
var DEFAULT_PLAN = "sqai_developer";
|
|
334
|
+
var DEFAULT_ACCOUNTS_URL = "https://accounts.thyn.ai";
|
|
335
|
+
var DEFAULT_API_URL = "https://api.sqai.com";
|
|
336
|
+
var LoginError = class extends Error {
|
|
337
|
+
};
|
|
338
|
+
function sqaiHome() {
|
|
339
|
+
return process.env.SQAI_HOME ?? join(homedir(), ".sqai");
|
|
340
|
+
}
|
|
341
|
+
var p = (name) => join(sqaiHome(), name);
|
|
342
|
+
var apiKeyPath = () => p("api_key");
|
|
343
|
+
var tokenPath = () => p("token");
|
|
344
|
+
var deviceIdPath = () => p("device_id");
|
|
345
|
+
var accountsUrl = () => (process.env.SQAI_ACCOUNTS_URL ?? DEFAULT_ACCOUNTS_URL).replace(/\/$/, "");
|
|
346
|
+
var apiUrl = () => (process.env.SQAI_API_URL ?? DEFAULT_API_URL).replace(/\/$/, "");
|
|
347
|
+
function readSecret(path) {
|
|
348
|
+
try {
|
|
349
|
+
const v = readFileSync(path, "utf8").trim();
|
|
350
|
+
return v || void 0;
|
|
351
|
+
} catch {
|
|
352
|
+
return void 0;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
async function readCachedApiKey() {
|
|
356
|
+
return process.env.SQAI_API_KEY ?? process.env.ALGENTA_API_KEY ?? readSecret(apiKeyPath());
|
|
357
|
+
}
|
|
358
|
+
function cache(path, value) {
|
|
359
|
+
mkdirSync(sqaiHome(), { recursive: true });
|
|
360
|
+
writeFileSync(path, value.trim() + "\n", "utf8");
|
|
361
|
+
try {
|
|
362
|
+
chmodSync(path, 384);
|
|
363
|
+
} catch {
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function deviceId() {
|
|
367
|
+
const existing = readSecret(deviceIdPath());
|
|
368
|
+
if (existing) return existing;
|
|
369
|
+
const value = createHash2("sha256").update(`${hostname()}:${Date.now()}:${Math.random()}`).digest("hex");
|
|
370
|
+
cache(deviceIdPath(), value);
|
|
371
|
+
return value;
|
|
372
|
+
}
|
|
373
|
+
async function request(method, url, opts = {}) {
|
|
374
|
+
const headers = { Accept: "application/json", "User-Agent": "sqai-cli" };
|
|
375
|
+
if (opts.body !== void 0) headers["Content-Type"] = "application/json";
|
|
376
|
+
if (opts.bearer) headers["Authorization"] = `Bearer ${opts.bearer}`;
|
|
377
|
+
let res;
|
|
378
|
+
try {
|
|
379
|
+
res = await fetch(url, {
|
|
380
|
+
method,
|
|
381
|
+
headers,
|
|
382
|
+
body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0
|
|
383
|
+
});
|
|
384
|
+
} catch (e) {
|
|
385
|
+
throw new LoginError(`${method} ${url}: ${e instanceof Error ? e.message : String(e)}`);
|
|
386
|
+
}
|
|
387
|
+
let json = {};
|
|
388
|
+
try {
|
|
389
|
+
json = await res.json();
|
|
390
|
+
} catch {
|
|
391
|
+
json = {};
|
|
392
|
+
}
|
|
393
|
+
return { status: res.status, json };
|
|
394
|
+
}
|
|
395
|
+
async function deviceAuthorize(accounts, openBrowser, maxWaitMs = 3e5) {
|
|
396
|
+
const { json: start } = await request("POST", `${accounts}/oauth/device/code`, {
|
|
397
|
+
body: { client_id: CLIENT_ID, scope: SCOPE }
|
|
398
|
+
});
|
|
399
|
+
const deviceCode = start.device_code;
|
|
400
|
+
const userCode = start.user_code;
|
|
401
|
+
const verify = start.verification_uri_complete || start.verification_uri;
|
|
402
|
+
const interval = Number(start.interval ?? 5);
|
|
403
|
+
if (!deviceCode || !userCode || !verify) throw new LoginError("accounts device-code response missing fields");
|
|
404
|
+
process.stdout.write(`
|
|
405
|
+
Open ${verify}
|
|
406
|
+
and enter code: ${userCode}
|
|
407
|
+
|
|
408
|
+
`);
|
|
409
|
+
if (openBrowser) await tryOpenBrowser(verify);
|
|
410
|
+
const deadline = Date.now() + maxWaitMs;
|
|
411
|
+
while (Date.now() < deadline) {
|
|
412
|
+
await new Promise((r) => setTimeout(r, interval * 1e3));
|
|
413
|
+
const { status, json: tok } = await request("POST", `${accounts}/oauth/device/token`, {
|
|
414
|
+
body: {
|
|
415
|
+
client_id: CLIENT_ID,
|
|
416
|
+
device_code: deviceCode,
|
|
417
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
const access = tok.access_token;
|
|
421
|
+
if (access) return access;
|
|
422
|
+
const error = tok.error;
|
|
423
|
+
if (error === "authorization_pending" || error === "slow_down" || status === 400 || status === 428) continue;
|
|
424
|
+
throw new LoginError(`device authorization failed: ${error ?? `HTTP ${status}`}`);
|
|
425
|
+
}
|
|
426
|
+
throw new LoginError("timed out waiting for device authorization");
|
|
427
|
+
}
|
|
428
|
+
async function tryOpenBrowser(url) {
|
|
429
|
+
try {
|
|
430
|
+
const { spawn: spawn2 } = await import("child_process");
|
|
431
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
432
|
+
spawn2(cmd, [url], { stdio: "ignore", detached: true, shell: process.platform === "win32" }).unref();
|
|
433
|
+
} catch {
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
async function createApiKey(api, accessToken, plan) {
|
|
437
|
+
const { status, json } = await request("POST", `${api}/v1/products/${PRODUCT}/onboard`, {
|
|
438
|
+
bearer: accessToken,
|
|
439
|
+
body: { name: CLIENT_ID, product: PRODUCT, plan }
|
|
440
|
+
});
|
|
441
|
+
const key = json.raw_key || json.key || json.api_key;
|
|
442
|
+
if (status >= 400 || !key) {
|
|
443
|
+
const detail = json.error && typeof json.error === "object" ? json.error.message : void 0;
|
|
444
|
+
throw new LoginError(`could not create API key (HTTP ${status})${detail ? `: ${detail}` : ""}`);
|
|
445
|
+
}
|
|
446
|
+
return key;
|
|
447
|
+
}
|
|
448
|
+
async function login(opts = {}) {
|
|
449
|
+
const plan = opts.plan ?? DEFAULT_PLAN;
|
|
450
|
+
const accounts = accountsUrl();
|
|
451
|
+
const api = apiUrl();
|
|
452
|
+
let token = opts.accessToken ?? process.env.SQAI_TOKEN;
|
|
453
|
+
if (token) {
|
|
454
|
+
process.stdout.write("using supplied token (headless)\n");
|
|
455
|
+
} else {
|
|
456
|
+
process.stdout.write(`authenticating via ${accounts} \u2026
|
|
457
|
+
`);
|
|
458
|
+
token = await deviceAuthorize(accounts, opts.openBrowser ?? true);
|
|
459
|
+
}
|
|
460
|
+
cache(tokenPath(), token);
|
|
461
|
+
process.stdout.write("creating your free SQAI Developer API key \u2026\n");
|
|
462
|
+
const apiKey = await createApiKey(api, token, plan);
|
|
463
|
+
cache(apiKeyPath(), apiKey);
|
|
464
|
+
deviceId();
|
|
465
|
+
process.stdout.write(
|
|
466
|
+
"\n \u2713 sqai login complete \u2014 the local runtime will provision a device-bound offline license on first use.\n SQAI runs fully on your machine; no data leaves it.\n\n"
|
|
467
|
+
);
|
|
468
|
+
return {
|
|
469
|
+
device_id: deviceId(),
|
|
470
|
+
plan,
|
|
471
|
+
api_key_prefix: apiKey.includes("_") ? `${apiKey.split("_", 1)[0]}_\u2026` : `${apiKey.slice(0, 6)}\u2026`
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
function logout() {
|
|
475
|
+
for (const path of [tokenPath(), apiKeyPath()]) {
|
|
476
|
+
try {
|
|
477
|
+
rmSync(path);
|
|
478
|
+
} catch {
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
326
483
|
// src/runtime/bundle.ts
|
|
327
484
|
import { spawn } from "child_process";
|
|
328
|
-
import { createHash as
|
|
485
|
+
import { createHash as createHash3, timingSafeEqual } from "crypto";
|
|
329
486
|
import { createReadStream, promises as fs } from "fs";
|
|
330
|
-
import { join } from "path";
|
|
487
|
+
import { join as join2 } from "path";
|
|
331
488
|
|
|
332
489
|
// src/runtime/trust.ts
|
|
333
|
-
import { createPublicKey, verify as cryptoVerify } from "crypto";
|
|
490
|
+
import { createPublicKey as createPublicKey2, verify as cryptoVerify } from "crypto";
|
|
334
491
|
var RELEASE_1_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
335
492
|
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7m4G1dEMi5FppLa5hu9N
|
|
336
493
|
4pUyoGELPaN5X7bO+9Qktgbv21+c8I4sQCufKouZEVNMNluKNrWcQ5dSwOuVxxS9
|
|
@@ -429,7 +586,7 @@ function keyValidNow(key, nowMs) {
|
|
|
429
586
|
}
|
|
430
587
|
function signatureVerifies(manifestBytes, signature, publicKeyPem) {
|
|
431
588
|
try {
|
|
432
|
-
const key =
|
|
589
|
+
const key = createPublicKey2(publicKeyPem);
|
|
433
590
|
if (key.asymmetricKeyType !== "rsa") {
|
|
434
591
|
return false;
|
|
435
592
|
}
|
|
@@ -613,7 +770,7 @@ async function hashTarMember(tarball, member, cap) {
|
|
|
613
770
|
const child = spawn("tar", ["-xOf", tarball, "--", member], {
|
|
614
771
|
stdio: ["ignore", "pipe", "pipe"]
|
|
615
772
|
});
|
|
616
|
-
const digest =
|
|
773
|
+
const digest = createHash3("sha256");
|
|
617
774
|
let total = 0;
|
|
618
775
|
let overflow = false;
|
|
619
776
|
child.stdout.on("data", (chunk) => {
|
|
@@ -673,7 +830,7 @@ async function extractTarball(tarball, destinationDir) {
|
|
|
673
830
|
}
|
|
674
831
|
function sha256File(path) {
|
|
675
832
|
return new Promise((resolve, reject) => {
|
|
676
|
-
const digest =
|
|
833
|
+
const digest = createHash3("sha256");
|
|
677
834
|
const stream = createReadStream(path);
|
|
678
835
|
stream.on("data", (chunk) => digest.update(chunk));
|
|
679
836
|
stream.on("error", reject);
|
|
@@ -685,6 +842,9 @@ function digestsEqual(actualHex, expectedHex) {
|
|
|
685
842
|
const expected = Buffer.from(expectedHex.toLowerCase(), "utf-8");
|
|
686
843
|
return actual.length === expected.length && timingSafeEqual(actual, expected);
|
|
687
844
|
}
|
|
845
|
+
function isGeneratedBytecodeCache(path) {
|
|
846
|
+
return path.split("/").includes("__pycache__") && path.endsWith(".pyc") && isSafeRelativePath(path);
|
|
847
|
+
}
|
|
688
848
|
function parseVerifiedManifest(manifestBytes) {
|
|
689
849
|
let manifest;
|
|
690
850
|
try {
|
|
@@ -833,7 +993,7 @@ async function verifyBundleTarball(tarball, expectations) {
|
|
|
833
993
|
return manifest;
|
|
834
994
|
}
|
|
835
995
|
async function listFilesRecursively(root, relative = "") {
|
|
836
|
-
const entries = await fs.readdir(
|
|
996
|
+
const entries = await fs.readdir(join2(root, relative), { withFileTypes: true });
|
|
837
997
|
const files = [];
|
|
838
998
|
for (const entry of entries) {
|
|
839
999
|
const relPath = relative === "" ? entry.name : `${relative}/${entry.name}`;
|
|
@@ -857,8 +1017,8 @@ async function listFilesRecursively(root, relative = "") {
|
|
|
857
1017
|
return files;
|
|
858
1018
|
}
|
|
859
1019
|
async function verifyInstalledBundleDir(installDir, expectations) {
|
|
860
|
-
const manifestPath =
|
|
861
|
-
const signaturePath =
|
|
1020
|
+
const manifestPath = join2(installDir, MANIFEST_NAME);
|
|
1021
|
+
const signaturePath = join2(installDir, MANIFEST_SIG_NAME);
|
|
862
1022
|
let manifestBytes;
|
|
863
1023
|
let signature;
|
|
864
1024
|
try {
|
|
@@ -893,12 +1053,15 @@ async function verifyInstalledBundleDir(installDir, expectations) {
|
|
|
893
1053
|
}
|
|
894
1054
|
const entry = byPath.get(relPath);
|
|
895
1055
|
if (entry === void 0) {
|
|
1056
|
+
if (isGeneratedBytecodeCache(relPath)) {
|
|
1057
|
+
continue;
|
|
1058
|
+
}
|
|
896
1059
|
throw new BundleVerificationError(
|
|
897
1060
|
"unlisted_member",
|
|
898
1061
|
`installed file not listed in signed manifest: '${relPath}'`
|
|
899
1062
|
);
|
|
900
1063
|
}
|
|
901
|
-
const absolute =
|
|
1064
|
+
const absolute = join2(installDir, relPath);
|
|
902
1065
|
const stat = await fs.lstat(absolute);
|
|
903
1066
|
if (stat.nlink > 1) {
|
|
904
1067
|
throw new BundleVerificationError(
|
|
@@ -963,20 +1126,20 @@ function currentPlatformKey() {
|
|
|
963
1126
|
function cacheDirFor(brand) {
|
|
964
1127
|
const os = osPlatform();
|
|
965
1128
|
if (os === "darwin") {
|
|
966
|
-
return
|
|
1129
|
+
return join3(homedir2(), "Library", "Caches", brand, "runtime");
|
|
967
1130
|
}
|
|
968
1131
|
if (os === "win32") {
|
|
969
|
-
const localAppData = process.env.LOCALAPPDATA ??
|
|
970
|
-
return
|
|
1132
|
+
const localAppData = process.env.LOCALAPPDATA ?? join3(homedir2(), "AppData", "Local");
|
|
1133
|
+
return join3(localAppData, brand, "runtime");
|
|
971
1134
|
}
|
|
972
|
-
const xdgCache = process.env.XDG_CACHE_HOME ??
|
|
973
|
-
return
|
|
1135
|
+
const xdgCache = process.env.XDG_CACHE_HOME ?? join3(homedir2(), ".cache");
|
|
1136
|
+
return join3(xdgCache, brand.toLowerCase(), "runtime");
|
|
974
1137
|
}
|
|
975
1138
|
function runtimeCacheDir() {
|
|
976
1139
|
const current = cacheDirFor("SQAI");
|
|
977
|
-
if (!
|
|
1140
|
+
if (!existsSync2(current)) {
|
|
978
1141
|
const legacy = cacheDirFor("Algenta");
|
|
979
|
-
if (legacy !== current &&
|
|
1142
|
+
if (legacy !== current && existsSync2(legacy)) {
|
|
980
1143
|
return legacy;
|
|
981
1144
|
}
|
|
982
1145
|
}
|
|
@@ -1061,6 +1224,21 @@ var RuntimeProvisioner = class _RuntimeProvisioner {
|
|
|
1061
1224
|
}
|
|
1062
1225
|
return new MojoRuntime(config ?? {});
|
|
1063
1226
|
}
|
|
1227
|
+
async ensureLocalLoginKey() {
|
|
1228
|
+
if (this.options.runtimeFactory || process.env.ALGENTA_API_KEY || process.env.DE_API_KEY) {
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
const key = await readCachedApiKey();
|
|
1232
|
+
if (key) {
|
|
1233
|
+
process.env.ALGENTA_API_KEY = key;
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
throw new SqaiError(
|
|
1237
|
+
"login_required",
|
|
1238
|
+
"Local compute requires a free SQAI device login. Run `sqai login` once, or set SQAI_API_KEY in this process.",
|
|
1239
|
+
{ details: { reason: "login_key_missing" } }
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
1064
1242
|
cacheDir() {
|
|
1065
1243
|
return this.options.cacheDir ?? runtimeCacheDir();
|
|
1066
1244
|
}
|
|
@@ -1125,7 +1303,7 @@ var RuntimeProvisioner = class _RuntimeProvisioner {
|
|
|
1125
1303
|
localCacheKey(modules, platformKey) {
|
|
1126
1304
|
const canonical = modules.join(",");
|
|
1127
1305
|
const version = this.options.contract.runtime_bundle.version;
|
|
1128
|
-
return
|
|
1306
|
+
return createHash4("sha256").update(`${canonical}|${platformKey}|${version}`).digest("hex").slice(0, 20);
|
|
1129
1307
|
}
|
|
1130
1308
|
/**
|
|
1131
1309
|
* Deterministic per-filter daemon endpoint so a filtered runtime never collides
|
|
@@ -1134,7 +1312,7 @@ var RuntimeProvisioner = class _RuntimeProvisioner {
|
|
|
1134
1312
|
filterEndpoint(cacheKey) {
|
|
1135
1313
|
const seed = parseInt(cacheKey.slice(0, 6), 16) || 0;
|
|
1136
1314
|
const port = 50100 + seed % 800;
|
|
1137
|
-
return { sock:
|
|
1315
|
+
return { sock: join3(this.cacheDir(), `${cacheKey}.sock`), tcp: `127.0.0.1:${port}` };
|
|
1138
1316
|
}
|
|
1139
1317
|
static DAEMON_ENDPOINT_ENV = [
|
|
1140
1318
|
"ALGENTA_DAEMON_SOCK",
|
|
@@ -1151,7 +1329,7 @@ var RuntimeProvisioner = class _RuntimeProvisioner {
|
|
|
1151
1329
|
process.env.ALGENTA_DAEMON_SOCK = endpoint.sock;
|
|
1152
1330
|
process.env.ALGENTA_DAEMON_TCP = endpoint.tcp;
|
|
1153
1331
|
process.env.ALGENTA_MOJO_SOCK = `${endpoint.sock}.worker`;
|
|
1154
|
-
process.env.ALGENTA_RUNTIME_DIR =
|
|
1332
|
+
process.env.ALGENTA_RUNTIME_DIR = join3(installDir, ".runtime");
|
|
1155
1333
|
return saved;
|
|
1156
1334
|
}
|
|
1157
1335
|
restoreEnv(saved) {
|
|
@@ -1166,6 +1344,7 @@ var RuntimeProvisioner = class _RuntimeProvisioner {
|
|
|
1166
1344
|
async doEnsure() {
|
|
1167
1345
|
const platformKey = currentPlatformKey();
|
|
1168
1346
|
const filter = this.filterModules();
|
|
1347
|
+
await this.ensureLocalLoginKey();
|
|
1169
1348
|
const probeEndpoint = filter ? this.filterEndpoint(this.localCacheKey(filter, platformKey)) : void 0;
|
|
1170
1349
|
const runtime = this.newRuntime(probeEndpoint ? { tcpAddress: probeEndpoint.tcp } : void 0);
|
|
1171
1350
|
try {
|
|
@@ -1193,7 +1372,7 @@ var RuntimeProvisioner = class _RuntimeProvisioner {
|
|
|
1193
1372
|
const supported = Object.keys(bundle.platforms);
|
|
1194
1373
|
throw new SqaiError(
|
|
1195
1374
|
"unsupported_platform",
|
|
1196
|
-
supported.length === 0 ? "No managed runtime bundle is published yet for any platform.
|
|
1375
|
+
supported.length === 0 ? "No managed runtime bundle is published yet for any platform. Run `sqai login`, or set SQAI_DEPLOYMENT_URL / SQAI_API_KEY for a managed SQAI deployment." : `No managed runtime bundle is published for '${platformKey}'.`,
|
|
1197
1376
|
{ details: { platform: platformKey, supported } }
|
|
1198
1377
|
);
|
|
1199
1378
|
}
|
|
@@ -1266,9 +1445,9 @@ var RuntimeProvisioner = class _RuntimeProvisioner {
|
|
|
1266
1445
|
}
|
|
1267
1446
|
/** Verify an existing install if present; returns null when absent/invalid. */
|
|
1268
1447
|
async verifiedInstallOrNull(platformKey, version, removeOnFailure, installKey = version) {
|
|
1269
|
-
const installDir =
|
|
1448
|
+
const installDir = join3(this.cacheDir(), installKey);
|
|
1270
1449
|
try {
|
|
1271
|
-
await fs2.access(
|
|
1450
|
+
await fs2.access(join3(installDir, MANIFEST_NAME));
|
|
1272
1451
|
} catch {
|
|
1273
1452
|
return null;
|
|
1274
1453
|
}
|
|
@@ -1301,12 +1480,22 @@ var RuntimeProvisioner = class _RuntimeProvisioner {
|
|
|
1301
1480
|
}
|
|
1302
1481
|
async lockIsStale(lockPath) {
|
|
1303
1482
|
const freshMs = this.options.lockFreshMs ?? LOCK_FRESH_MS;
|
|
1483
|
+
const ageFromMtime = async () => {
|
|
1484
|
+
try {
|
|
1485
|
+
const stat = await fs2.stat(lockPath);
|
|
1486
|
+
return Date.now() - stat.mtimeMs;
|
|
1487
|
+
} catch (error) {
|
|
1488
|
+
const code = error.code;
|
|
1489
|
+
return code === "ENOENT" ? null : Number.POSITIVE_INFINITY;
|
|
1490
|
+
}
|
|
1491
|
+
};
|
|
1304
1492
|
try {
|
|
1305
1493
|
const raw = await fs2.readFile(lockPath, "utf-8");
|
|
1306
1494
|
const parsed = JSON.parse(raw);
|
|
1307
1495
|
const createdAt = typeof parsed.created_at === "string" ? Date.parse(parsed.created_at) : NaN;
|
|
1308
1496
|
if (!Number.isFinite(createdAt)) {
|
|
1309
|
-
|
|
1497
|
+
const age = await ageFromMtime();
|
|
1498
|
+
return age !== null && age >= freshMs;
|
|
1310
1499
|
}
|
|
1311
1500
|
return Date.now() - createdAt >= freshMs;
|
|
1312
1501
|
} catch (error) {
|
|
@@ -1314,7 +1503,8 @@ var RuntimeProvisioner = class _RuntimeProvisioner {
|
|
|
1314
1503
|
if (code === "ENOENT") {
|
|
1315
1504
|
return false;
|
|
1316
1505
|
}
|
|
1317
|
-
|
|
1506
|
+
const age = await ageFromMtime();
|
|
1507
|
+
return age !== null && age >= freshMs;
|
|
1318
1508
|
}
|
|
1319
1509
|
}
|
|
1320
1510
|
/** Serialize installers on `<cacheDir>/<installKey>.lock`; returns the verified install. */
|
|
@@ -1325,7 +1515,7 @@ var RuntimeProvisioner = class _RuntimeProvisioner {
|
|
|
1325
1515
|
if (existing) {
|
|
1326
1516
|
return existing;
|
|
1327
1517
|
}
|
|
1328
|
-
const lockPath =
|
|
1518
|
+
const lockPath = join3(cacheDir, `${installKey}.lock`);
|
|
1329
1519
|
const waitTimeoutMs = this.options.lockWaitTimeoutMs ?? LOCK_WAIT_TIMEOUT_MS;
|
|
1330
1520
|
const pollIntervalMs = this.options.lockPollIntervalMs ?? LOCK_POLL_INTERVAL_MS;
|
|
1331
1521
|
const deadline = Date.now() + waitTimeoutMs;
|
|
@@ -1371,9 +1561,9 @@ var RuntimeProvisioner = class _RuntimeProvisioner {
|
|
|
1371
1561
|
async downloadVerifyExtract(platformKey, version, platformBundle, installKey = version) {
|
|
1372
1562
|
const cacheDir = this.cacheDir();
|
|
1373
1563
|
const token = randomBytes2(6).toString("hex");
|
|
1374
|
-
const tarballPath =
|
|
1375
|
-
const extractDir =
|
|
1376
|
-
const installDir =
|
|
1564
|
+
const tarballPath = join3(cacheDir, `tmp-${token}.tgz`);
|
|
1565
|
+
const extractDir = join3(cacheDir, `tmp-${token}`);
|
|
1566
|
+
const installDir = join3(cacheDir, installKey);
|
|
1377
1567
|
const url = resolveBundleUrl(platformBundle.url, version, platformKey);
|
|
1378
1568
|
try {
|
|
1379
1569
|
const download = this.options.downloadBundle ?? defaultDownloadBundle;
|
|
@@ -1448,10 +1638,11 @@ var RuntimeProvisioner = class _RuntimeProvisioner {
|
|
|
1448
1638
|
throw this.wrapVerificationError(error, platformKey, version);
|
|
1449
1639
|
}
|
|
1450
1640
|
const daemonCommand = [
|
|
1451
|
-
|
|
1641
|
+
join3(installed.installDir, ...entryRelative.split("/")),
|
|
1452
1642
|
"start",
|
|
1453
1643
|
"--foreground"
|
|
1454
1644
|
];
|
|
1645
|
+
await this.ensureLocalLoginKey();
|
|
1455
1646
|
const managed = this.newRuntime(
|
|
1456
1647
|
endpoint ? { autoStart: true, daemonCommand, tcpAddress: endpoint.tcp } : { autoStart: true, daemonCommand }
|
|
1457
1648
|
);
|
|
@@ -1486,7 +1677,7 @@ var RuntimeProvisioner = class _RuntimeProvisioner {
|
|
|
1486
1677
|
}
|
|
1487
1678
|
try {
|
|
1488
1679
|
await fs2.writeFile(
|
|
1489
|
-
|
|
1680
|
+
join3(this.cacheDir(), "state.json"),
|
|
1490
1681
|
JSON.stringify(
|
|
1491
1682
|
{ pid: null, version, started_at: (/* @__PURE__ */ new Date()).toISOString() },
|
|
1492
1683
|
null,
|
|
@@ -1520,7 +1711,7 @@ async function dispatchComputation(target, entry, argsVector, seed, requestId, t
|
|
|
1520
1711
|
request_id: response.request_id ?? requestId
|
|
1521
1712
|
};
|
|
1522
1713
|
} catch (error) {
|
|
1523
|
-
throw
|
|
1714
|
+
throw fromEngineError(error);
|
|
1524
1715
|
}
|
|
1525
1716
|
}
|
|
1526
1717
|
return dispatchHttp(target, module, functionName, args, requestId);
|
|
@@ -1557,7 +1748,7 @@ async function dispatchHttp(target, module, functionName, args, requestId) {
|
|
|
1557
1748
|
} catch (error) {
|
|
1558
1749
|
throw new SqaiError(
|
|
1559
1750
|
"compute_runtime_unavailable",
|
|
1560
|
-
`The
|
|
1751
|
+
`The SQAI deployment endpoint is unreachable: ${error.message}. Check SQAI_DEPLOYMENT_URL / SQAI_API_KEY.`,
|
|
1561
1752
|
{ retryable: true }
|
|
1562
1753
|
);
|
|
1563
1754
|
}
|
|
@@ -1740,7 +1931,7 @@ var SQAI = class {
|
|
|
1740
1931
|
const env = readEnv();
|
|
1741
1932
|
this.mode = inferMode(config.mode, env);
|
|
1742
1933
|
this.apiKey = config.apiKey ?? env.apiKey;
|
|
1743
|
-
this.engineUrl = config.engineUrl ?? env.engineUrl;
|
|
1934
|
+
this.engineUrl = config.deploymentUrl ?? config.engineUrl ?? env.engineUrl;
|
|
1744
1935
|
this.tenantId = config.tenantId ?? null;
|
|
1745
1936
|
this.policy = config.policy;
|
|
1746
1937
|
this.configuredTarget = config.computeTarget;
|
|
@@ -1768,7 +1959,7 @@ var SQAI = class {
|
|
|
1768
1959
|
this.algentaRuntime = this.rawRuntime;
|
|
1769
1960
|
}
|
|
1770
1961
|
// ── Query plane ────────────────────────────────────────────────────────────
|
|
1771
|
-
async connect(source, options = {}) {
|
|
1962
|
+
async connect(source = null, options = {}) {
|
|
1772
1963
|
try {
|
|
1773
1964
|
const registration = await this.algentaRuntime.connect(source, options);
|
|
1774
1965
|
const mapped = mapSource(registration);
|
|
@@ -1787,7 +1978,84 @@ var SQAI = class {
|
|
|
1787
1978
|
this.sources.set(mapped.name, mapped);
|
|
1788
1979
|
return mapped;
|
|
1789
1980
|
} catch (error) {
|
|
1790
|
-
throw error instanceof SqaiError ? error :
|
|
1981
|
+
throw error instanceof SqaiError ? error : fromEngineError(error);
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
async createConnector(request2) {
|
|
1985
|
+
const createConnector = this.algentaRuntime.createConnector;
|
|
1986
|
+
if (typeof createConnector !== "function") {
|
|
1987
|
+
throw unsupportedSubstrateOperation("createConnector");
|
|
1988
|
+
}
|
|
1989
|
+
try {
|
|
1990
|
+
return await createConnector.call(this.algentaRuntime, request2);
|
|
1991
|
+
} catch (error) {
|
|
1992
|
+
throw fromEngineError(error);
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
async listConnectors(options = {}) {
|
|
1996
|
+
const listConnectors = this.algentaRuntime.listConnectors;
|
|
1997
|
+
if (typeof listConnectors !== "function") {
|
|
1998
|
+
throw unsupportedSubstrateOperation("listConnectors");
|
|
1999
|
+
}
|
|
2000
|
+
try {
|
|
2001
|
+
return await listConnectors.call(this.algentaRuntime, options);
|
|
2002
|
+
} catch (error) {
|
|
2003
|
+
throw fromEngineError(error);
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
async getConnector(connectorId) {
|
|
2007
|
+
const getConnector = this.algentaRuntime.getConnector;
|
|
2008
|
+
if (typeof getConnector !== "function") {
|
|
2009
|
+
throw unsupportedSubstrateOperation("getConnector");
|
|
2010
|
+
}
|
|
2011
|
+
try {
|
|
2012
|
+
return await getConnector.call(this.algentaRuntime, connectorId);
|
|
2013
|
+
} catch (error) {
|
|
2014
|
+
throw fromEngineError(error);
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
async updateConnector(connectorId, request2) {
|
|
2018
|
+
const updateConnector = this.algentaRuntime.updateConnector;
|
|
2019
|
+
if (typeof updateConnector !== "function") {
|
|
2020
|
+
throw unsupportedSubstrateOperation("updateConnector");
|
|
2021
|
+
}
|
|
2022
|
+
try {
|
|
2023
|
+
return await updateConnector.call(this.algentaRuntime, connectorId, request2);
|
|
2024
|
+
} catch (error) {
|
|
2025
|
+
throw fromEngineError(error);
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
async testConnector(connectorIdOrConnector) {
|
|
2029
|
+
const testConnector = this.algentaRuntime.testConnector;
|
|
2030
|
+
if (typeof testConnector !== "function") {
|
|
2031
|
+
throw unsupportedSubstrateOperation("testConnector");
|
|
2032
|
+
}
|
|
2033
|
+
try {
|
|
2034
|
+
return await testConnector.call(this.algentaRuntime, connectorIdOrConnector);
|
|
2035
|
+
} catch (error) {
|
|
2036
|
+
throw fromEngineError(error);
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
async browseConnector(connectorIdOrConnector) {
|
|
2040
|
+
const browseConnector = this.algentaRuntime.browseConnector;
|
|
2041
|
+
if (typeof browseConnector !== "function") {
|
|
2042
|
+
throw unsupportedSubstrateOperation("browseConnector");
|
|
2043
|
+
}
|
|
2044
|
+
try {
|
|
2045
|
+
return await browseConnector.call(this.algentaRuntime, connectorIdOrConnector);
|
|
2046
|
+
} catch (error) {
|
|
2047
|
+
throw fromEngineError(error);
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
async deleteConnector(connectorId) {
|
|
2051
|
+
const deleteConnector = this.algentaRuntime.deleteConnector;
|
|
2052
|
+
if (typeof deleteConnector !== "function") {
|
|
2053
|
+
throw unsupportedSubstrateOperation("deleteConnector");
|
|
2054
|
+
}
|
|
2055
|
+
try {
|
|
2056
|
+
await deleteConnector.call(this.algentaRuntime, connectorId);
|
|
2057
|
+
} catch (error) {
|
|
2058
|
+
throw fromEngineError(error);
|
|
1791
2059
|
}
|
|
1792
2060
|
}
|
|
1793
2061
|
listSources() {
|
|
@@ -1798,28 +2066,28 @@ var SQAI = class {
|
|
|
1798
2066
|
try {
|
|
1799
2067
|
return await this.algentaRuntime.resolve(spec);
|
|
1800
2068
|
} catch (error) {
|
|
1801
|
-
throw
|
|
2069
|
+
throw fromEngineError(error);
|
|
1802
2070
|
}
|
|
1803
2071
|
}
|
|
1804
2072
|
async query(plan) {
|
|
1805
2073
|
try {
|
|
1806
2074
|
return await this.algentaRuntime.query(plan);
|
|
1807
2075
|
} catch (error) {
|
|
1808
|
-
throw
|
|
2076
|
+
throw fromEngineError(error);
|
|
1809
2077
|
}
|
|
1810
2078
|
}
|
|
1811
2079
|
async queryWithMetadata(plan) {
|
|
1812
2080
|
try {
|
|
1813
2081
|
return await this.algentaRuntime.queryWithMetadata(plan);
|
|
1814
2082
|
} catch (error) {
|
|
1815
|
-
throw
|
|
2083
|
+
throw fromEngineError(error);
|
|
1816
2084
|
}
|
|
1817
2085
|
}
|
|
1818
2086
|
async verify(plan) {
|
|
1819
2087
|
try {
|
|
1820
2088
|
return await this.algentaRuntime.verify(plan);
|
|
1821
2089
|
} catch (error) {
|
|
1822
|
-
throw
|
|
2090
|
+
throw fromEngineError(error);
|
|
1823
2091
|
}
|
|
1824
2092
|
}
|
|
1825
2093
|
async ask(spec) {
|
|
@@ -1841,14 +2109,14 @@ var SQAI = class {
|
|
|
1841
2109
|
if (typeof this.algentaRuntime.extractColumns !== "function") {
|
|
1842
2110
|
throw new SqaiError(
|
|
1843
2111
|
"unsupported_operation",
|
|
1844
|
-
"The installed
|
|
1845
|
-
{ details: { required: "
|
|
2112
|
+
"The installed SQAI package does not provide extractColumns; upgrade SQAI.",
|
|
2113
|
+
{ details: { required: "sqai.extractColumns" } }
|
|
1846
2114
|
);
|
|
1847
2115
|
}
|
|
1848
2116
|
try {
|
|
1849
2117
|
return await this.algentaRuntime.extractColumns(sourceName, columns, options);
|
|
1850
2118
|
} catch (error) {
|
|
1851
|
-
throw
|
|
2119
|
+
throw fromEngineError(error);
|
|
1852
2120
|
}
|
|
1853
2121
|
}
|
|
1854
2122
|
// ── Computation plane ──────────────────────────────────────────────────────
|
|
@@ -1978,7 +2246,7 @@ var SQAI = class {
|
|
|
1978
2246
|
"API mode requires SQAI_API_KEY for the computation plane."
|
|
1979
2247
|
);
|
|
1980
2248
|
}
|
|
1981
|
-
return { kind: "http", baseUrl: "https://api.
|
|
2249
|
+
return { kind: "http", baseUrl: "https://api.sqai.com", apiKey: this.apiKey };
|
|
1982
2250
|
}
|
|
1983
2251
|
const runtime = await this.provisioner.ensure();
|
|
1984
2252
|
return { kind: "local", runtime };
|
|
@@ -2036,6 +2304,13 @@ var SQAI = class {
|
|
|
2036
2304
|
function createSQAI(config = {}) {
|
|
2037
2305
|
return new SQAI(config);
|
|
2038
2306
|
}
|
|
2307
|
+
function unsupportedSubstrateOperation(operation) {
|
|
2308
|
+
return new SqaiError(
|
|
2309
|
+
"unsupported_operation",
|
|
2310
|
+
`The installed SQAI package does not provide ${operation}; upgrade SQAI.`,
|
|
2311
|
+
{ details: { required: `sqai.${operation}` } }
|
|
2312
|
+
);
|
|
2313
|
+
}
|
|
2039
2314
|
function mapSource(registration) {
|
|
2040
2315
|
const record = registration;
|
|
2041
2316
|
return {
|
|
@@ -2049,11 +2324,60 @@ function mapSource(registration) {
|
|
|
2049
2324
|
status: String(registration.status ?? "ready")
|
|
2050
2325
|
};
|
|
2051
2326
|
}
|
|
2327
|
+
|
|
2328
|
+
// src/types.ts
|
|
2329
|
+
var SQAI_CONNECTOR_TYPES = [
|
|
2330
|
+
"s3",
|
|
2331
|
+
"gcs",
|
|
2332
|
+
"azure",
|
|
2333
|
+
"clickhouse",
|
|
2334
|
+
"bigquery",
|
|
2335
|
+
"snowflake",
|
|
2336
|
+
"redshift",
|
|
2337
|
+
"postgres",
|
|
2338
|
+
"postgresql",
|
|
2339
|
+
"sqlite",
|
|
2340
|
+
"mysql",
|
|
2341
|
+
"mssql",
|
|
2342
|
+
"oracle",
|
|
2343
|
+
"neo4j",
|
|
2344
|
+
"redis",
|
|
2345
|
+
"elastic",
|
|
2346
|
+
"elasticsearch",
|
|
2347
|
+
"rest",
|
|
2348
|
+
"rest_api",
|
|
2349
|
+
"file",
|
|
2350
|
+
"file_upload",
|
|
2351
|
+
"github_repo",
|
|
2352
|
+
"gitlab_repo",
|
|
2353
|
+
"bitbucket_repo",
|
|
2354
|
+
"local_repo",
|
|
2355
|
+
"repo_archive"
|
|
2356
|
+
];
|
|
2357
|
+
var SQAI_CONNECTOR_TYPE_ALIASES = {
|
|
2358
|
+
postgresql: "postgres",
|
|
2359
|
+
click_house: "clickhouse",
|
|
2360
|
+
redis_cache: "redis",
|
|
2361
|
+
elastic: "elasticsearch",
|
|
2362
|
+
rest_api: "rest",
|
|
2363
|
+
file_upload: "file",
|
|
2364
|
+
github: "github_repo",
|
|
2365
|
+
github_repository: "github_repo",
|
|
2366
|
+
gitlab: "gitlab_repo",
|
|
2367
|
+
gitlab_repository: "gitlab_repo",
|
|
2368
|
+
bitbucket: "bitbucket_repo",
|
|
2369
|
+
bitbucket_repository: "bitbucket_repo",
|
|
2370
|
+
local_repository: "local_repo",
|
|
2371
|
+
archive_repository: "repo_archive"
|
|
2372
|
+
};
|
|
2052
2373
|
export {
|
|
2053
2374
|
ContractIndex,
|
|
2375
|
+
LoginError,
|
|
2054
2376
|
ResultStore,
|
|
2055
2377
|
RuntimeProvisioner,
|
|
2056
2378
|
SQAI,
|
|
2379
|
+
SQAI_CONNECTOR_TYPES,
|
|
2380
|
+
SQAI_CONNECTOR_TYPE_ALIASES,
|
|
2057
2381
|
SQAI_ERROR_CODES,
|
|
2058
2382
|
SqaiError,
|
|
2059
2383
|
computationHash,
|
|
@@ -2061,8 +2385,11 @@ export {
|
|
|
2061
2385
|
currentPlatformKey,
|
|
2062
2386
|
invocationHash,
|
|
2063
2387
|
isReadOnlyEligible,
|
|
2388
|
+
login,
|
|
2389
|
+
logout,
|
|
2064
2390
|
lookupCapability,
|
|
2391
|
+
readCachedApiKey,
|
|
2065
2392
|
runtimeCacheDir,
|
|
2393
|
+
sqaiHome,
|
|
2066
2394
|
validateComputation
|
|
2067
2395
|
};
|
|
2068
|
-
//# sourceMappingURL=index.js.map
|