@massa-ai/tools-api 1.45.0 → 1.46.0
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.js +133 -14
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -762,6 +762,22 @@ function restartNeededSections(config) {
|
|
|
762
762
|
return false;
|
|
763
763
|
});
|
|
764
764
|
}
|
|
765
|
+
function deepEqual(a, b) {
|
|
766
|
+
if (a === b)
|
|
767
|
+
return true;
|
|
768
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null)
|
|
769
|
+
return false;
|
|
770
|
+
if (Array.isArray(a) !== Array.isArray(b))
|
|
771
|
+
return false;
|
|
772
|
+
const ka = Object.keys(a);
|
|
773
|
+
const kb = Object.keys(b);
|
|
774
|
+
if (ka.length !== kb.length)
|
|
775
|
+
return false;
|
|
776
|
+
return ka.every((k) => deepEqual(a[k], b[k]));
|
|
777
|
+
}
|
|
778
|
+
function changedRestartSections(before, after) {
|
|
779
|
+
return RESTART_SECTIONS.filter((s) => !deepEqual(before[s], after[s]));
|
|
780
|
+
}
|
|
765
781
|
function applyMaskedSentinel(partial, current) {
|
|
766
782
|
const result = JSON.parse(JSON.stringify(partial));
|
|
767
783
|
if (result.security?.apiKey === MASK_SENTINEL) {
|
|
@@ -1044,7 +1060,8 @@ function savePartialConfig(partial) {
|
|
|
1044
1060
|
return {
|
|
1045
1061
|
success: true,
|
|
1046
1062
|
config: merged,
|
|
1047
|
-
restartNeededSections: restartNeededSections(merged)
|
|
1063
|
+
restartNeededSections: restartNeededSections(merged),
|
|
1064
|
+
changedRestartSections: changedRestartSections(current, merged)
|
|
1048
1065
|
};
|
|
1049
1066
|
}
|
|
1050
1067
|
var MASK_SENTINEL = "***", RESTART_SECTIONS, VALID_EMBEDDING_PROVIDERS, VALID_LOG_LEVELS, BACKUP_RETENTION_LIMIT = 10;
|
|
@@ -182131,7 +182148,11 @@ var configRoutes = new Elysia({ prefix: "/api/v1/config" }).get("/", ({ set: set
|
|
|
182131
182148
|
set3.status = 200;
|
|
182132
182149
|
return {
|
|
182133
182150
|
success: true,
|
|
182134
|
-
data: {
|
|
182151
|
+
data: {
|
|
182152
|
+
config: masked,
|
|
182153
|
+
restartNeededSections: result.restartNeededSections,
|
|
182154
|
+
changedRestartSections: result.changedRestartSections
|
|
182155
|
+
}
|
|
182135
182156
|
};
|
|
182136
182157
|
}, {
|
|
182137
182158
|
body: t.Object({}, { additionalProperties: true }),
|
|
@@ -182321,6 +182342,104 @@ var modelRegistryStreamRoutes = new Elysia({ prefix: "/api/v1/model-registry" })
|
|
|
182321
182342
|
}
|
|
182322
182343
|
});
|
|
182323
182344
|
|
|
182345
|
+
// src/lifecycle.ts
|
|
182346
|
+
import { existsSync as existsSync5 } from "fs";
|
|
182347
|
+
var serverStopper = () => {};
|
|
182348
|
+
var jobsStopper = () => {};
|
|
182349
|
+
function setServerStopper(fn) {
|
|
182350
|
+
serverStopper = fn;
|
|
182351
|
+
}
|
|
182352
|
+
function setJobsStopper(fn) {
|
|
182353
|
+
jobsStopper = fn;
|
|
182354
|
+
}
|
|
182355
|
+
function defaultSeams() {
|
|
182356
|
+
return {
|
|
182357
|
+
stopServer: () => serverStopper(),
|
|
182358
|
+
stopJobs: () => jobsStopper(),
|
|
182359
|
+
disconnect: async () => {
|
|
182360
|
+
const { disconnectPrisma: disconnectPrisma2 } = await Promise.resolve().then(() => (init_services(), exports_services));
|
|
182361
|
+
await disconnectPrisma2();
|
|
182362
|
+
},
|
|
182363
|
+
spawn: (argv, opts) => {
|
|
182364
|
+
Bun.spawn({
|
|
182365
|
+
cmd: argv,
|
|
182366
|
+
cwd: opts.cwd,
|
|
182367
|
+
env: opts.env,
|
|
182368
|
+
stdio: ["ignore", "inherit", "inherit"]
|
|
182369
|
+
}).unref();
|
|
182370
|
+
},
|
|
182371
|
+
exit: (code) => process.exit(code)
|
|
182372
|
+
};
|
|
182373
|
+
}
|
|
182374
|
+
var testSeams = null;
|
|
182375
|
+
var testMode = null;
|
|
182376
|
+
function detectRestartMode(env6 = process.env, dockerProbe = () => existsSync5("/.dockerenv")) {
|
|
182377
|
+
if (testMode)
|
|
182378
|
+
return testMode;
|
|
182379
|
+
if (env6.MASSA_AI_DEV_WATCH === "1")
|
|
182380
|
+
return "dev-watch";
|
|
182381
|
+
if (env6.MASSA_AI_SUPERVISED === "1" || dockerProbe())
|
|
182382
|
+
return "supervised";
|
|
182383
|
+
return "respawn";
|
|
182384
|
+
}
|
|
182385
|
+
var armedMode = null;
|
|
182386
|
+
function armRestart(mode) {
|
|
182387
|
+
armedMode = mode;
|
|
182388
|
+
}
|
|
182389
|
+
function consumeArmedRestart() {
|
|
182390
|
+
const mode = armedMode;
|
|
182391
|
+
armedMode = null;
|
|
182392
|
+
return mode;
|
|
182393
|
+
}
|
|
182394
|
+
async function shutdownAndRestart(mode, seams = testSeams ?? defaultSeams()) {
|
|
182395
|
+
await seams.stopServer();
|
|
182396
|
+
seams.stopJobs();
|
|
182397
|
+
try {
|
|
182398
|
+
await seams.disconnect();
|
|
182399
|
+
} catch {}
|
|
182400
|
+
if (mode === "respawn") {
|
|
182401
|
+
seams.spawn([process.execPath, ...process.argv.slice(1)], {
|
|
182402
|
+
cwd: process.cwd(),
|
|
182403
|
+
env: process.env
|
|
182404
|
+
});
|
|
182405
|
+
}
|
|
182406
|
+
seams.exit(0);
|
|
182407
|
+
}
|
|
182408
|
+
async function gracefulShutdown(seams = testSeams ?? defaultSeams()) {
|
|
182409
|
+
await seams.stopServer();
|
|
182410
|
+
seams.stopJobs();
|
|
182411
|
+
try {
|
|
182412
|
+
await seams.disconnect();
|
|
182413
|
+
} catch {}
|
|
182414
|
+
seams.exit(0);
|
|
182415
|
+
}
|
|
182416
|
+
|
|
182417
|
+
// src/routes/restart.ts
|
|
182418
|
+
var restartRoutes = new Elysia({ prefix: "/api/v1/system" }).onAfterResponse(() => {
|
|
182419
|
+
const mode = consumeArmedRestart();
|
|
182420
|
+
if (mode)
|
|
182421
|
+
shutdownAndRestart(mode);
|
|
182422
|
+
}).post("/restart", ({ set: set3 }) => {
|
|
182423
|
+
const mode = detectRestartMode();
|
|
182424
|
+
if (mode === "dev-watch") {
|
|
182425
|
+
set3.status = 409;
|
|
182426
|
+
return {
|
|
182427
|
+
success: false,
|
|
182428
|
+
error: "restart refused",
|
|
182429
|
+
reason: "dev watcher active (bun --watch owns this process's lifecycle) \u2014 save a file to restart, or run without the dev script"
|
|
182430
|
+
};
|
|
182431
|
+
}
|
|
182432
|
+
armRestart(mode);
|
|
182433
|
+
set3.status = 200;
|
|
182434
|
+
return { success: true, restarting: true, mode };
|
|
182435
|
+
}, {
|
|
182436
|
+
detail: {
|
|
182437
|
+
tags: ["system"],
|
|
182438
|
+
summary: "Gracefully restart the API server",
|
|
182439
|
+
description: "Responds first, then drains (listener, job reaper, scheduler, Prisma) and exits. " + "Mode 'supervised' (MASSA_AI_SUPERVISED=1 or /.dockerenv): exit 0 and let the supervisor respawn. " + "Mode 'respawn' (no supervisor): a detached replacement with the same argv is spawned after the listener stops. " + "409 when the dev watcher owns the process (MASSA_AI_DEV_WATCH=1). " + "Poll GET /health to observe recovery."
|
|
182440
|
+
}
|
|
182441
|
+
});
|
|
182442
|
+
|
|
182324
182443
|
// src/middleware/error.ts
|
|
182325
182444
|
init_dist();
|
|
182326
182445
|
var errorHandler = new Elysia({ name: "error-handler" }).onError(({ code, error: error51, set: set3, path: path41, request }) => {
|
|
@@ -182443,13 +182562,15 @@ var app = new Elysia({ adapter: node() }).use(cors(buildCorsOptions(config.get("
|
|
|
182443
182562
|
},
|
|
182444
182563
|
security: [{ ApiKeyAuth: [] }]
|
|
182445
182564
|
}
|
|
182446
|
-
})).use(errorHandler).use(authMiddleware).use(searchRoutes).use(memoryRoutes).use(checkpointRoutes).use(projectRoutes).use(contextRoutes).use(analyticsRoutes).use(systemRoutes).use(eventsRoutes).use(workspaceRoutes).use(fileRoutes).use(synapseRoutes).use(hookRoutes).use(bootstrapRoutes).use(handoffRoutes).use(proposalRoutes).use(executorRoutes).use(webRoutes).use(webUiRoutes).use(architectureRoutes).use(dashboardRoutes).use(profileRoutes).use(configRoutes).use(modelRegistryRoutes).use(modelRegistryStreamRoutes).get("/health", () => buildHealthResponse(getParserReadiness()));
|
|
182565
|
+
})).use(errorHandler).use(authMiddleware).use(searchRoutes).use(memoryRoutes).use(checkpointRoutes).use(projectRoutes).use(contextRoutes).use(analyticsRoutes).use(systemRoutes).use(eventsRoutes).use(workspaceRoutes).use(fileRoutes).use(synapseRoutes).use(hookRoutes).use(bootstrapRoutes).use(handoffRoutes).use(proposalRoutes).use(executorRoutes).use(webRoutes).use(webUiRoutes).use(architectureRoutes).use(dashboardRoutes).use(profileRoutes).use(configRoutes).use(modelRegistryRoutes).use(modelRegistryStreamRoutes).use(restartRoutes).get("/health", () => buildHealthResponse(getParserReadiness()));
|
|
182447
182566
|
initAuthOrExit();
|
|
182448
182567
|
warnIfTrustOverrideEnabled();
|
|
182449
182568
|
await listenAfterParserValidation({
|
|
182450
182569
|
validate: validateAllGrammars,
|
|
182451
182570
|
listen: () => {
|
|
182452
|
-
app.listen(PORT)
|
|
182571
|
+
app.listen(PORT, (server) => {
|
|
182572
|
+
setServerStopper(() => void server.stop());
|
|
182573
|
+
});
|
|
182453
182574
|
},
|
|
182454
182575
|
onValidationFailure: (error51) => {
|
|
182455
182576
|
console.error("Structural parser readiness failed; indexing is unavailable:", error51 instanceof Error ? error51.message : error51);
|
|
@@ -182490,18 +182611,16 @@ try {
|
|
|
182490
182611
|
} catch (err) {
|
|
182491
182612
|
console.error(`[scheduler] init error:`, err instanceof Error ? err.message : err);
|
|
182492
182613
|
}
|
|
182614
|
+
setJobsStopper(() => {
|
|
182615
|
+
clearInterval(jobReaperTimer);
|
|
182616
|
+
try {
|
|
182617
|
+
scheduler.stop();
|
|
182618
|
+
} catch {}
|
|
182619
|
+
});
|
|
182493
182620
|
for (const signal of ["SIGTERM", "SIGINT"]) {
|
|
182494
|
-
process.on(signal,
|
|
182621
|
+
process.on(signal, () => {
|
|
182495
182622
|
console.log(`${signal} received, shutting down gracefully...`);
|
|
182496
|
-
|
|
182497
|
-
try {
|
|
182498
|
-
scheduler.stop();
|
|
182499
|
-
} catch {}
|
|
182500
|
-
try {
|
|
182501
|
-
const { disconnectPrisma: disconnectPrisma2 } = await Promise.resolve().then(() => (init_services(), exports_services));
|
|
182502
|
-
await disconnectPrisma2();
|
|
182503
|
-
} catch {}
|
|
182504
|
-
process.exit(0);
|
|
182623
|
+
gracefulShutdown();
|
|
182505
182624
|
});
|
|
182506
182625
|
}
|
|
182507
182626
|
(async () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@massa-ai/tools-api",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.46.0",
|
|
4
4
|
"author": "luizgmassa",
|
|
5
5
|
"description": "massa-ai REST API server - Semantic code search, memory, and context compression",
|
|
6
6
|
"type": "module",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"dist"
|
|
13
13
|
],
|
|
14
14
|
"scripts": {
|
|
15
|
-
"dev": "bun --watch src/index.ts",
|
|
15
|
+
"dev": "MASSA_AI_DEV_WATCH=1 bun --watch src/index.ts",
|
|
16
16
|
"dev:insecure": "NODE_TLS_REJECT_UNAUTHORIZED=0 bun --watch src/index.ts",
|
|
17
17
|
"start": "bun src/index.ts",
|
|
18
18
|
"start:insecure": "NODE_TLS_REJECT_UNAUTHORIZED=0 bun src/index.ts",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"test": "bun scripts/run-tests-isolated.ts"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@massa-ai/core": "^1.
|
|
25
|
-
"@massa-ai/shared": "^1.
|
|
24
|
+
"@massa-ai/core": "^1.46.0",
|
|
25
|
+
"@massa-ai/shared": "^1.46.0",
|
|
26
26
|
"elysia": "^1.2.25",
|
|
27
27
|
"@elysiajs/swagger": "^1.2.0",
|
|
28
28
|
"@elysiajs/cors": "^1.2.0",
|