@massa-ai/tools-api 1.41.0 → 1.42.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 +189 -4
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -108904,6 +108904,48 @@ var init_postgres_vector_store = __esm(() => {
|
|
|
108904
108904
|
lastIndexed: row.last_updated?.toISOString() ?? null
|
|
108905
108905
|
}));
|
|
108906
108906
|
}
|
|
108907
|
+
async getPool() {
|
|
108908
|
+
if (this.pool)
|
|
108909
|
+
return this.pool;
|
|
108910
|
+
const pg = await import("pg");
|
|
108911
|
+
const PgPool = pg.default?.Pool ?? pg.Pool;
|
|
108912
|
+
const poolConfig = {
|
|
108913
|
+
connectionString: this.config.connectionString,
|
|
108914
|
+
max: this.config.poolSize,
|
|
108915
|
+
idleTimeoutMillis: 30000,
|
|
108916
|
+
connectionTimeoutMillis: 5000
|
|
108917
|
+
};
|
|
108918
|
+
this.pool = new PgPool(poolConfig);
|
|
108919
|
+
return this.pool;
|
|
108920
|
+
}
|
|
108921
|
+
async listAllProjectsAcrossDimensions() {
|
|
108922
|
+
const pool = await this.getPool();
|
|
108923
|
+
const { rows: tables } = await pool.query(`
|
|
108924
|
+
SELECT tablename FROM pg_tables
|
|
108925
|
+
WHERE tablename = 'vector_documents'
|
|
108926
|
+
OR tablename ~ '^vector_documents_[0-9]+d$'
|
|
108927
|
+
ORDER BY tablename
|
|
108928
|
+
`);
|
|
108929
|
+
if (tables.length === 0)
|
|
108930
|
+
return [];
|
|
108931
|
+
const unionParts = tables.map((t2) => `SELECT project_id, COUNT(*)::int AS doc_count, MAX(updated_at) AS last_updated, SUM(LENGTH(content))::bigint AS total_size FROM ${t2.tablename} WHERE id NOT LIKE '_metadata:%' GROUP BY project_id`).join(" UNION ALL ");
|
|
108932
|
+
const { rows } = await pool.query(`
|
|
108933
|
+
SELECT project_id,
|
|
108934
|
+
SUM(doc_count)::int AS doc_count,
|
|
108935
|
+
MAX(last_updated) AS last_updated,
|
|
108936
|
+
SUM(total_size)::bigint AS total_size
|
|
108937
|
+
FROM (${unionParts}) AS merged
|
|
108938
|
+
GROUP BY project_id
|
|
108939
|
+
ORDER BY last_updated DESC
|
|
108940
|
+
`);
|
|
108941
|
+
return rows.map((row) => ({
|
|
108942
|
+
projectId: row.project_id,
|
|
108943
|
+
projectPath: null,
|
|
108944
|
+
documentCount: parseInt(row.doc_count),
|
|
108945
|
+
totalSize: parseInt(row.total_size ?? "0"),
|
|
108946
|
+
lastIndexed: row.last_updated?.toISOString() ?? null
|
|
108947
|
+
}));
|
|
108948
|
+
}
|
|
108907
108949
|
async getCollection(name26) {
|
|
108908
108950
|
const pool = await this.ensureInitialized();
|
|
108909
108951
|
return new PostgresVectorCollection(pool, name26, this.tableName, this);
|
|
@@ -179209,7 +179251,12 @@ var identityBodySchema = t.Object({
|
|
|
179209
179251
|
var projectRoutes = new Elysia({ prefix: "/api/v1/project" }).get("/list", async () => {
|
|
179210
179252
|
try {
|
|
179211
179253
|
const vectorStore = await getVectorStore();
|
|
179212
|
-
|
|
179254
|
+
let projects;
|
|
179255
|
+
try {
|
|
179256
|
+
projects = await vectorStore.listProjects();
|
|
179257
|
+
} catch {
|
|
179258
|
+
projects = await vectorStore.listAllProjectsAcrossDimensions?.() ?? [];
|
|
179259
|
+
}
|
|
179213
179260
|
return {
|
|
179214
179261
|
success: true,
|
|
179215
179262
|
data: {
|
|
@@ -181605,6 +181652,26 @@ init_dist();
|
|
|
181605
181652
|
var CONFIG_DETAIL = {
|
|
181606
181653
|
tags: ["config"]
|
|
181607
181654
|
};
|
|
181655
|
+
var SENSITIVE_FIELDS = {
|
|
181656
|
+
database: ["url"],
|
|
181657
|
+
embedding: ["apiKey"],
|
|
181658
|
+
llm: ["apiKey"],
|
|
181659
|
+
security: ["apiKey"]
|
|
181660
|
+
};
|
|
181661
|
+
function getFieldByPath(config3, section, field3) {
|
|
181662
|
+
const sec = config3[section];
|
|
181663
|
+
if (!sec || typeof sec !== "object")
|
|
181664
|
+
return;
|
|
181665
|
+
const parts = field3.split(".");
|
|
181666
|
+
let val = sec;
|
|
181667
|
+
for (const p of parts) {
|
|
181668
|
+
if (val && typeof val === "object")
|
|
181669
|
+
val = val[p];
|
|
181670
|
+
else
|
|
181671
|
+
return;
|
|
181672
|
+
}
|
|
181673
|
+
return val;
|
|
181674
|
+
}
|
|
181608
181675
|
var configRoutes = new Elysia({ prefix: "/api/v1/config" }).get("/", ({ set: set3 }) => {
|
|
181609
181676
|
const config3 = loadConfig();
|
|
181610
181677
|
const masked = maskSensitive(config3);
|
|
@@ -181620,6 +181687,32 @@ var configRoutes = new Elysia({ prefix: "/api/v1/config" }).get("/", ({ set: set
|
|
|
181620
181687
|
summary: "Get current config with sensitive fields masked",
|
|
181621
181688
|
description: "Returns the current config.json with security.apiKey, llm.apiKey, embedding.apiKey, and database.url masked to '***'. Includes restartNeededSections \u2014 the subset of [database, embedding, llm, security] present in the config."
|
|
181622
181689
|
}
|
|
181690
|
+
}).get("/reveal", ({ query, set: set3 }) => {
|
|
181691
|
+
const section = query.section;
|
|
181692
|
+
const field3 = query.field;
|
|
181693
|
+
if (!section || !field3) {
|
|
181694
|
+
set3.status = 400;
|
|
181695
|
+
return { success: false, error: "section and field query params are required" };
|
|
181696
|
+
}
|
|
181697
|
+
const allowed = SENSITIVE_FIELDS[section];
|
|
181698
|
+
if (!allowed || !allowed.includes(field3)) {
|
|
181699
|
+
set3.status = 400;
|
|
181700
|
+
return { success: false, error: `field "${section}.${field3}" is not a sensitive field` };
|
|
181701
|
+
}
|
|
181702
|
+
const config3 = loadConfig();
|
|
181703
|
+
const value = getFieldByPath(config3, section, field3);
|
|
181704
|
+
set3.status = 200;
|
|
181705
|
+
return { success: true, data: { section, field: field3, value: value ?? "" } };
|
|
181706
|
+
}, {
|
|
181707
|
+
query: t.Object({
|
|
181708
|
+
section: t.String(),
|
|
181709
|
+
field: t.String()
|
|
181710
|
+
}),
|
|
181711
|
+
detail: {
|
|
181712
|
+
...CONFIG_DETAIL,
|
|
181713
|
+
summary: "Reveal a single sensitive config field (unmasked)",
|
|
181714
|
+
description: "Returns the unmasked value for one sensitive field (database.url, embedding.apiKey, llm.apiKey, security.apiKey). Requires API key. Only sensitive fields can be revealed."
|
|
181715
|
+
}
|
|
181623
181716
|
}).put("/", ({ body, set: set3 }) => {
|
|
181624
181717
|
const result = savePartialConfig(body);
|
|
181625
181718
|
if (!result.success) {
|
|
@@ -181828,13 +181921,105 @@ function writeOverlayAtomically(overlayPath, data) {
|
|
|
181828
181921
|
}
|
|
181829
181922
|
}
|
|
181830
181923
|
|
|
181924
|
+
// src/routes/model-registry-stream.ts
|
|
181925
|
+
init_config();
|
|
181926
|
+
import path36 from "path";
|
|
181927
|
+
import { spawn as spawn3 } from "child_process";
|
|
181928
|
+
var GENERATE_SCRIPT2 = path36.resolve(import.meta.dirname, "../../../../scripts/generate-subagent-artifacts.ts");
|
|
181929
|
+
var encoder3 = new TextEncoder;
|
|
181930
|
+
function sseFrame(data) {
|
|
181931
|
+
return encoder3.encode(`data: ${JSON.stringify(data)}
|
|
181932
|
+
|
|
181933
|
+
`);
|
|
181934
|
+
}
|
|
181935
|
+
var modelRegistryStreamRoutes = new Elysia({ prefix: "/api/v1/model-registry" }).post("/regenerate-stream", () => {
|
|
181936
|
+
let child = null;
|
|
181937
|
+
let closed = false;
|
|
181938
|
+
const stream2 = new ReadableStream({
|
|
181939
|
+
start(controller2) {
|
|
181940
|
+
try {
|
|
181941
|
+
child = spawn3("bun", [GENERATE_SCRIPT2], {
|
|
181942
|
+
env: { ...process.env },
|
|
181943
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
181944
|
+
});
|
|
181945
|
+
} catch (e) {
|
|
181946
|
+
controller2.enqueue(sseFrame({
|
|
181947
|
+
type: "done",
|
|
181948
|
+
exitCode: null,
|
|
181949
|
+
error: `spawn failed: ${e.message}`
|
|
181950
|
+
}));
|
|
181951
|
+
controller2.close();
|
|
181952
|
+
closed = true;
|
|
181953
|
+
return;
|
|
181954
|
+
}
|
|
181955
|
+
const emitLine = (streamName, chunk) => {
|
|
181956
|
+
if (closed)
|
|
181957
|
+
return;
|
|
181958
|
+
const text3 = chunk.toString();
|
|
181959
|
+
const lines = text3.split(`
|
|
181960
|
+
`);
|
|
181961
|
+
for (const line of lines) {
|
|
181962
|
+
if (line.length === 0)
|
|
181963
|
+
continue;
|
|
181964
|
+
try {
|
|
181965
|
+
controller2.enqueue(sseFrame({ type: "line", stream: streamName, text: line }));
|
|
181966
|
+
} catch {
|
|
181967
|
+
closed = true;
|
|
181968
|
+
return;
|
|
181969
|
+
}
|
|
181970
|
+
}
|
|
181971
|
+
};
|
|
181972
|
+
child.stdout?.on("data", (chunk) => emitLine("stdout", chunk));
|
|
181973
|
+
child.stderr?.on("data", (chunk) => emitLine("stderr", chunk));
|
|
181974
|
+
child.on("error", (e) => {
|
|
181975
|
+
if (closed)
|
|
181976
|
+
return;
|
|
181977
|
+
closed = true;
|
|
181978
|
+
try {
|
|
181979
|
+
controller2.enqueue(sseFrame({ type: "done", exitCode: null, error: `spawn error: ${e.message}` }));
|
|
181980
|
+
controller2.close();
|
|
181981
|
+
} catch {}
|
|
181982
|
+
});
|
|
181983
|
+
child.on("close", (code) => {
|
|
181984
|
+
if (closed)
|
|
181985
|
+
return;
|
|
181986
|
+
closed = true;
|
|
181987
|
+
try {
|
|
181988
|
+
controller2.enqueue(sseFrame({ type: "done", exitCode: code }));
|
|
181989
|
+
controller2.close();
|
|
181990
|
+
} catch {}
|
|
181991
|
+
});
|
|
181992
|
+
},
|
|
181993
|
+
cancel() {
|
|
181994
|
+
closed = true;
|
|
181995
|
+
try {
|
|
181996
|
+
child?.kill();
|
|
181997
|
+
} catch {}
|
|
181998
|
+
}
|
|
181999
|
+
});
|
|
182000
|
+
return new Response(stream2, {
|
|
182001
|
+
headers: {
|
|
182002
|
+
"Content-Type": "text/event-stream",
|
|
182003
|
+
"Cache-Control": "no-cache",
|
|
182004
|
+
Connection: "keep-alive",
|
|
182005
|
+
"X-Accel-Buffering": "no"
|
|
182006
|
+
}
|
|
182007
|
+
});
|
|
182008
|
+
}, {
|
|
182009
|
+
detail: {
|
|
182010
|
+
tags: ["model-registry"],
|
|
182011
|
+
summary: "Regenerate subagent artifacts (streaming SSE)",
|
|
182012
|
+
description: 'Spawns `bun scripts/generate-subagent-artifacts.ts` with child_process.spawn (non-blocking). Pipes stdout/stderr line-by-line as SSE `data: {"type":"line","stream":"stdout|stderr","text":"..."}` events, then a terminal `data: {"type":"done","exitCode":<n>}` event. On spawn failure emits `done` with `exitCode:null` + `error`. The existing blocking POST /regenerate route stays for API compatibility.'
|
|
182013
|
+
}
|
|
182014
|
+
});
|
|
182015
|
+
|
|
181831
182016
|
// src/middleware/error.ts
|
|
181832
182017
|
init_dist();
|
|
181833
|
-
var errorHandler = new Elysia({ name: "error-handler" }).onError(({ code, error: error51, set: set3, path:
|
|
182018
|
+
var errorHandler = new Elysia({ name: "error-handler" }).onError(({ code, error: error51, set: set3, path: path37, request }) => {
|
|
181834
182019
|
logger.error("[massa-ai-api] Request failed", undefined, {
|
|
181835
182020
|
...safeErrorSummary(error51),
|
|
181836
182021
|
code,
|
|
181837
|
-
path:
|
|
182022
|
+
path: path37,
|
|
181838
182023
|
method: request.method
|
|
181839
182024
|
});
|
|
181840
182025
|
if (error51 instanceof SearchServiceError) {
|
|
@@ -181950,7 +182135,7 @@ var app = new Elysia({ adapter: node() }).use(cors(buildCorsOptions(config.get("
|
|
|
181950
182135
|
},
|
|
181951
182136
|
security: [{ ApiKeyAuth: [] }]
|
|
181952
182137
|
}
|
|
181953
|
-
})).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).get("/health", () => buildHealthResponse(getParserReadiness()));
|
|
182138
|
+
})).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()));
|
|
181954
182139
|
initAuthOrExit();
|
|
181955
182140
|
warnIfTrustOverrideEnabled();
|
|
181956
182141
|
await listenAfterParserValidation({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@massa-ai/tools-api",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.42.0",
|
|
4
4
|
"author": "luizgmassa",
|
|
5
5
|
"description": "massa-ai REST API server - Semantic code search, memory, and context compression",
|
|
6
6
|
"type": "module",
|
|
@@ -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.42.0",
|
|
25
|
+
"@massa-ai/shared": "^1.42.0",
|
|
26
26
|
"elysia": "^1.2.25",
|
|
27
27
|
"@elysiajs/swagger": "^1.2.0",
|
|
28
28
|
"@elysiajs/cors": "^1.2.0",
|