@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.
Files changed (2) hide show
  1. package/dist/index.js +133 -14
  2. 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: { config: masked, restartNeededSections: result.restartNeededSections }
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, async () => {
182621
+ process.on(signal, () => {
182495
182622
  console.log(`${signal} received, shutting down gracefully...`);
182496
- clearInterval(jobReaperTimer);
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.45.0",
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.45.0",
25
- "@massa-ai/shared": "^1.45.0",
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",