@m6d/cortex-server 1.1.0 → 1.1.2

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 (42) hide show
  1. package/README.md +38 -38
  2. package/dist/src/factory.d.ts +13 -1
  3. package/dist/src/ws/index.d.ts +1 -1
  4. package/package.json +54 -54
  5. package/src/adapters/database.ts +21 -28
  6. package/src/adapters/minio.ts +69 -69
  7. package/src/adapters/mssql.ts +171 -195
  8. package/src/adapters/storage.ts +4 -4
  9. package/src/ai/fetch.ts +31 -31
  10. package/src/ai/helpers.ts +18 -22
  11. package/src/ai/index.ts +101 -113
  12. package/src/ai/interceptors/resolve-captured-files.ts +42 -49
  13. package/src/ai/prompt.ts +80 -83
  14. package/src/ai/tools/call-endpoint.tool.ts +75 -82
  15. package/src/ai/tools/capture-files.tool.ts +15 -17
  16. package/src/ai/tools/execute-code.tool.ts +73 -80
  17. package/src/ai/tools/query-graph.tool.ts +17 -17
  18. package/src/auth/middleware.ts +51 -51
  19. package/src/cli/extract-endpoints.ts +436 -474
  20. package/src/config.ts +124 -134
  21. package/src/db/migrate.ts +13 -13
  22. package/src/db/migrations/20260309012148_cloudy_maria_hill/snapshot.json +303 -303
  23. package/src/db/schema.ts +46 -58
  24. package/src/factory.ts +136 -139
  25. package/src/graph/generate-cypher.ts +97 -97
  26. package/src/graph/helpers.ts +37 -37
  27. package/src/graph/index.ts +20 -20
  28. package/src/graph/neo4j.ts +82 -89
  29. package/src/graph/resolver.ts +201 -211
  30. package/src/graph/seed.ts +101 -114
  31. package/src/graph/types.ts +88 -88
  32. package/src/graph/validate.ts +55 -57
  33. package/src/index.ts +5 -5
  34. package/src/routes/chat.ts +23 -23
  35. package/src/routes/files.ts +75 -80
  36. package/src/routes/threads.ts +52 -54
  37. package/src/routes/ws.ts +22 -22
  38. package/src/types.ts +30 -30
  39. package/src/ws/connections.ts +11 -11
  40. package/src/ws/events.ts +2 -2
  41. package/src/ws/index.ts +1 -5
  42. package/src/ws/notify.ts +4 -4
@@ -16,14 +16,14 @@ import type { ConceptDef } from "./types.ts";
16
16
  // ---------------------------------------------------------------------------
17
17
 
18
18
  export type RerankerConfig = {
19
- url: string;
20
- apiKey: string;
19
+ url: string;
20
+ apiKey: string;
21
21
  };
22
22
 
23
23
  export type ResolverConfig = {
24
- neo4j: Neo4jClient;
25
- embeddingModel: EmbeddingModel;
26
- reranker?: RerankerConfig;
24
+ neo4j: Neo4jClient;
25
+ embeddingModel: EmbeddingModel;
26
+ reranker?: RerankerConfig;
27
27
  };
28
28
 
29
29
  // ---------------------------------------------------------------------------
@@ -31,39 +31,39 @@ export type ResolverConfig = {
31
31
  // ---------------------------------------------------------------------------
32
32
 
33
33
  type EndpointDependency = {
34
- depPath: string;
35
- depMethod: string;
36
- paramName: string;
37
- fromField: string;
34
+ depPath: string;
35
+ depMethod: string;
36
+ paramName: string;
37
+ fromField: string;
38
38
  };
39
39
 
40
40
  type EndpointRelation = "read" | "write";
41
41
 
42
42
  export type ResolvedEndpoint = {
43
- concept: string;
44
- relation: EndpointRelation;
45
- name: string;
46
- path: string;
47
- method: string;
48
- params: string;
49
- body: string;
50
- response: string;
51
- dependencies: EndpointDependency[];
52
- rules: string[];
43
+ concept: string;
44
+ relation: EndpointRelation;
45
+ name: string;
46
+ path: string;
47
+ method: string;
48
+ params: string;
49
+ body: string;
50
+ response: string;
51
+ dependencies: EndpointDependency[];
52
+ rules: string[];
53
53
  };
54
54
 
55
55
  export type ResolvedService = {
56
- concept: string;
57
- serviceName: string;
58
- builtInId: string;
59
- description: string;
60
- rules: string[];
56
+ concept: string;
57
+ serviceName: string;
58
+ builtInId: string;
59
+ description: string;
60
+ rules: string[];
61
61
  };
62
62
 
63
63
  export type ResolvedContext = {
64
- readEndpoints: ResolvedEndpoint[];
65
- writeEndpoints: ResolvedEndpoint[];
66
- services: ResolvedService[];
64
+ readEndpoints: ResolvedEndpoint[];
65
+ writeEndpoints: ResolvedEndpoint[];
66
+ services: ResolvedService[];
67
67
  };
68
68
 
69
69
  // ---------------------------------------------------------------------------
@@ -71,24 +71,24 @@ export type ResolvedContext = {
71
71
  // ---------------------------------------------------------------------------
72
72
 
73
73
  type EndpointRow = {
74
- concept: string;
75
- relationType: "QUERIED_VIA" | "MUTATED_VIA";
76
- name: string;
77
- path: string;
78
- method: string;
79
- params: string | null;
80
- body: string | null;
81
- response: string | null;
82
- dependencies: (EndpointDependency | { depPath: null })[];
83
- rules: (string | null)[];
74
+ concept: string;
75
+ relationType: "QUERIED_VIA" | "MUTATED_VIA";
76
+ name: string;
77
+ path: string;
78
+ method: string;
79
+ params: string | null;
80
+ body: string | null;
81
+ response: string | null;
82
+ dependencies: (EndpointDependency | { depPath: null })[];
83
+ rules: (string | null)[];
84
84
  };
85
85
 
86
86
  type ServiceRow = {
87
- concept: string;
88
- serviceName: string;
89
- builtInId: string;
90
- description: string | null;
91
- rules: (string | null)[];
87
+ concept: string;
88
+ serviceName: string;
89
+ builtInId: string;
90
+ description: string | null;
91
+ rules: (string | null)[];
92
92
  };
93
93
 
94
94
  // ---------------------------------------------------------------------------
@@ -96,50 +96,45 @@ type ServiceRow = {
96
96
  // ---------------------------------------------------------------------------
97
97
 
98
98
  export async function resolveFromGraph(prompt: string, config: ResolverConfig) {
99
- const empty: ResolvedContext = {
100
- readEndpoints: [],
101
- writeEndpoints: [],
102
- services: [],
103
- };
104
-
105
- try {
106
- const { embedding } = await embed({
107
- model: config.embeddingModel,
108
- value: prompt,
109
- });
110
-
111
- const conceptsRaw = await config.neo4j.query(
112
- `CALL db.index.vector.queryNodes('concept_embeddings', 10, $embedding)
99
+ const empty: ResolvedContext = {
100
+ readEndpoints: [],
101
+ writeEndpoints: [],
102
+ services: [],
103
+ };
104
+
105
+ try {
106
+ const { embedding } = await embed({
107
+ model: config.embeddingModel,
108
+ value: prompt,
109
+ });
110
+
111
+ const conceptsRaw = await config.neo4j.query(
112
+ `CALL db.index.vector.queryNodes('concept_embeddings', 10, $embedding)
113
113
  YIELD node, score
114
114
  WHERE score > 0.3
115
115
  RETURN node.name AS name, node.description AS description, score
116
116
  ORDER BY score DESC`,
117
- { embedding },
118
- );
117
+ { embedding },
118
+ );
119
119
 
120
- let concepts = JSON.parse(conceptsRaw) as (Pick<
121
- ConceptDef,
122
- "name" | "description"
123
- > & { score: number })[];
120
+ let concepts = JSON.parse(conceptsRaw) as (Pick<ConceptDef, "name" | "description"> & {
121
+ score: number;
122
+ })[];
124
123
 
125
- if (!Array.isArray(concepts) || concepts.length === 0) {
126
- return empty;
127
- }
124
+ if (!Array.isArray(concepts) || concepts.length === 0) {
125
+ return empty;
126
+ }
128
127
 
129
- if (config.reranker) {
130
- concepts = await computeRelevanceScores(
131
- prompt,
132
- concepts,
133
- config.reranker,
134
- );
135
- } else {
136
- concepts = concepts.slice(0, 3);
137
- }
128
+ if (config.reranker) {
129
+ concepts = await computeRelevanceScores(prompt, concepts, config.reranker);
130
+ } else {
131
+ concepts = concepts.slice(0, 3);
132
+ }
138
133
 
139
- const conceptNames = concepts.map((c) => c.name);
134
+ const conceptNames = concepts.map((c) => c.name);
140
135
 
141
- const endpointsRaw = await config.neo4j.query(
142
- `UNWIND $names AS conceptName
136
+ const endpointsRaw = await config.neo4j.query(
137
+ `UNWIND $names AS conceptName
143
138
  MATCH (c:Concept {name: conceptName})-[:SPECIALIZES*0..3]->(ancestor:Concept)
144
139
  WITH conceptName, collect(DISTINCT ancestor) AS family
145
140
  UNWIND family AS related
@@ -166,34 +161,32 @@ export async function resolveFromGraph(prompt: string, config: ResolverConfig) {
166
161
  dependencies,
167
162
  collect(DISTINCT rEndpoint.description) +
168
163
  collect(DISTINCT rConcept.description) AS rules`,
169
- { names: conceptNames },
170
- );
171
-
172
- const endpointRows = asArray<EndpointRow>(JSON.parse(endpointsRaw));
173
- const allEndpoints = dedupeEndpoints(
174
- endpointRows.map(function (row) {
175
- return {
176
- concept: row.concept,
177
- relation:
178
- row.relationType === "MUTATED_VIA"
179
- ? ("write" as const)
180
- : ("read" as const),
181
- name: row.name,
182
- path: row.path,
183
- method: row.method,
184
- params: row.params ?? "[]",
185
- body: row.body ?? "[]",
186
- response: row.response ?? "[]",
187
- dependencies: row.dependencies.filter(
188
- (dep): dep is EndpointDependency => dep.depPath != null,
189
- ),
190
- rules: row.rules.filter((rule): rule is string => Boolean(rule)),
191
- };
192
- }),
193
- );
194
-
195
- const servicesRaw = await config.neo4j.query(
196
- `UNWIND $names AS conceptName
164
+ { names: conceptNames },
165
+ );
166
+
167
+ const endpointRows = asArray<EndpointRow>(JSON.parse(endpointsRaw));
168
+ const allEndpoints = dedupeEndpoints(
169
+ endpointRows.map(function (row) {
170
+ return {
171
+ concept: row.concept,
172
+ relation:
173
+ row.relationType === "MUTATED_VIA" ? ("write" as const) : ("read" as const),
174
+ name: row.name,
175
+ path: row.path,
176
+ method: row.method,
177
+ params: row.params ?? "[]",
178
+ body: row.body ?? "[]",
179
+ response: row.response ?? "[]",
180
+ dependencies: row.dependencies.filter(
181
+ (dep): dep is EndpointDependency => dep.depPath != null,
182
+ ),
183
+ rules: row.rules.filter((rule): rule is string => Boolean(rule)),
184
+ };
185
+ }),
186
+ );
187
+
188
+ const servicesRaw = await config.neo4j.query(
189
+ `UNWIND $names AS conceptName
197
190
  MATCH (c:Concept {name: conceptName})-[:SPECIALIZES*0..3]->(ancestor:Concept)
198
191
  WITH conceptName, collect(DISTINCT ancestor) AS family
199
192
  UNWIND family AS related
@@ -207,27 +200,27 @@ export async function resolveFromGraph(prompt: string, config: ResolverConfig) {
207
200
  svc.description AS description,
208
201
  collect(DISTINCT rService.description) +
209
202
  collect(DISTINCT rConcept.description) AS rules`,
210
- { names: conceptNames },
211
- );
212
-
213
- const services = dedupeServices(
214
- asArray<ServiceRow>(JSON.parse(servicesRaw)).map(function (row) {
215
- return {
216
- concept: row.concept,
217
- serviceName: row.serviceName,
218
- builtInId: row.builtInId,
219
- description: row.description ?? "",
220
- rules: row.rules.filter((rule): rule is string => Boolean(rule)),
221
- };
222
- }),
223
- );
224
-
225
- // Service-to-Servicing bridge: if any services were resolved, pull in
226
- // the generic Servicing endpoints
227
- let servicingEndpoints: ResolvedEndpoint[] = [];
228
- if (services.length > 0) {
229
- const servicingRaw = await config.neo4j.query(
230
- `MATCH (c:Concept {name: 'ServiceRequest'})-[rel:QUERIED_VIA|MUTATED_VIA]->(e:Endpoint)
203
+ { names: conceptNames },
204
+ );
205
+
206
+ const services = dedupeServices(
207
+ asArray<ServiceRow>(JSON.parse(servicesRaw)).map(function (row) {
208
+ return {
209
+ concept: row.concept,
210
+ serviceName: row.serviceName,
211
+ builtInId: row.builtInId,
212
+ description: row.description ?? "",
213
+ rules: row.rules.filter((rule): rule is string => Boolean(rule)),
214
+ };
215
+ }),
216
+ );
217
+
218
+ // Service-to-Servicing bridge: if any services were resolved, pull in
219
+ // the generic Servicing endpoints
220
+ let servicingEndpoints: ResolvedEndpoint[] = [];
221
+ if (services.length > 0) {
222
+ const servicingRaw = await config.neo4j.query(
223
+ `MATCH (c:Concept {name: 'ServiceRequest'})-[rel:QUERIED_VIA|MUTATED_VIA]->(e:Endpoint)
231
224
  OPTIONAL MATCH (e)-[d:DEPENDS_ON]->(dep:Endpoint)
232
225
  WITH c, rel, e,
233
226
  collect(DISTINCT {
@@ -249,46 +242,43 @@ export async function resolveFromGraph(prompt: string, config: ResolverConfig) {
249
242
  dependencies,
250
243
  collect(DISTINCT rEndpoint.description) +
251
244
  collect(DISTINCT rConcept.description) AS rules`,
252
- {},
253
- );
254
-
255
- servicingEndpoints = dedupeEndpoints(
256
- asArray<EndpointRow>(JSON.parse(servicingRaw)).map(function (row) {
257
- return {
258
- concept: row.concept,
259
- relation:
260
- row.relationType === "MUTATED_VIA"
261
- ? ("write" as const)
262
- : ("read" as const),
263
- name: row.name,
264
- path: row.path,
265
- method: row.method,
266
- params: row.params ?? "[]",
267
- body: row.body ?? "[]",
268
- response: row.response ?? "[]",
269
- dependencies: row.dependencies.filter(
270
- (dep): dep is EndpointDependency => dep.depPath != null,
271
- ),
272
- rules: row.rules.filter((rule): rule is string => Boolean(rule)),
273
- };
274
- }),
275
- );
276
- }
245
+ {},
246
+ );
247
+
248
+ servicingEndpoints = dedupeEndpoints(
249
+ asArray<EndpointRow>(JSON.parse(servicingRaw)).map(function (row) {
250
+ return {
251
+ concept: row.concept,
252
+ relation:
253
+ row.relationType === "MUTATED_VIA"
254
+ ? ("write" as const)
255
+ : ("read" as const),
256
+ name: row.name,
257
+ path: row.path,
258
+ method: row.method,
259
+ params: row.params ?? "[]",
260
+ body: row.body ?? "[]",
261
+ response: row.response ?? "[]",
262
+ dependencies: row.dependencies.filter(
263
+ (dep): dep is EndpointDependency => dep.depPath != null,
264
+ ),
265
+ rules: row.rules.filter((rule): rule is string => Boolean(rule)),
266
+ };
267
+ }),
268
+ );
269
+ }
270
+
271
+ const combinedEndpoints = dedupeEndpoints([...allEndpoints, ...servicingEndpoints]);
277
272
 
278
- const combinedEndpoints = dedupeEndpoints([
279
- ...allEndpoints,
280
- ...servicingEndpoints,
281
- ]);
282
-
283
- return {
284
- readEndpoints: combinedEndpoints.filter((ep) => ep.relation === "read"),
285
- writeEndpoints: combinedEndpoints.filter((ep) => ep.relation === "write"),
286
- services,
287
- } satisfies ResolvedContext;
288
- } catch (error) {
289
- console.error("Graph resolution failed:", error);
290
- return empty;
291
- }
273
+ return {
274
+ readEndpoints: combinedEndpoints.filter((ep) => ep.relation === "read"),
275
+ writeEndpoints: combinedEndpoints.filter((ep) => ep.relation === "write"),
276
+ services,
277
+ } satisfies ResolvedContext;
278
+ } catch (error) {
279
+ console.error("Graph resolution failed:", error);
280
+ return empty;
281
+ }
292
282
  }
293
283
 
294
284
  // ---------------------------------------------------------------------------
@@ -296,62 +286,62 @@ export async function resolveFromGraph(prompt: string, config: ResolverConfig) {
296
286
  // ---------------------------------------------------------------------------
297
287
 
298
288
  function asArray<T>(value: unknown) {
299
- return Array.isArray(value) ? (value as T[]) : ([] as T[]);
289
+ return Array.isArray(value) ? (value as T[]) : ([] as T[]);
300
290
  }
301
291
 
302
292
  function dedupeEndpoints(endpoints: ResolvedEndpoint[]) {
303
- const seen = new Set<string>();
304
- const deduped: ResolvedEndpoint[] = [];
305
-
306
- for (const ep of endpoints) {
307
- const key = `${ep.relation}|${ep.concept}|${ep.method}|${ep.path}`;
308
- if (seen.has(key)) continue;
309
- seen.add(key);
310
- deduped.push(ep);
311
- }
293
+ const seen = new Set<string>();
294
+ const deduped: ResolvedEndpoint[] = [];
295
+
296
+ for (const ep of endpoints) {
297
+ const key = `${ep.relation}|${ep.concept}|${ep.method}|${ep.path}`;
298
+ if (seen.has(key)) continue;
299
+ seen.add(key);
300
+ deduped.push(ep);
301
+ }
312
302
 
313
- return deduped;
303
+ return deduped;
314
304
  }
315
305
 
316
306
  function dedupeServices(services: ResolvedService[]) {
317
- const seen = new Set<string>();
318
- const deduped: ResolvedService[] = [];
319
-
320
- for (const svc of services) {
321
- const key = `${svc.concept}|${svc.builtInId}`;
322
- if (seen.has(key)) continue;
323
- seen.add(key);
324
- deduped.push(svc);
325
- }
307
+ const seen = new Set<string>();
308
+ const deduped: ResolvedService[] = [];
309
+
310
+ for (const svc of services) {
311
+ const key = `${svc.concept}|${svc.builtInId}`;
312
+ if (seen.has(key)) continue;
313
+ seen.add(key);
314
+ deduped.push(svc);
315
+ }
326
316
 
327
- return deduped;
317
+ return deduped;
328
318
  }
329
319
 
330
320
  async function computeRelevanceScores(
331
- prompt: string,
332
- concepts: (Pick<ConceptDef, "name" | "description"> & { score: number })[],
333
- reranker: RerankerConfig,
321
+ prompt: string,
322
+ concepts: (Pick<ConceptDef, "name" | "description"> & { score: number })[],
323
+ reranker: RerankerConfig,
334
324
  ) {
335
- const response = await fetch(reranker.url, {
336
- method: "POST",
337
- headers: {
338
- Authorization: `Bearer ${reranker.apiKey}`,
339
- "Content-Type": "application/json",
340
- },
341
- body: JSON.stringify({
342
- queries: [prompt],
343
- documents: concepts.map((x) => x.description),
344
- }),
345
- });
346
-
347
- const data = (await response.json()) as { scores: number[] };
348
- const scores = data.scores;
349
-
350
- const scoredConcepts = concepts.map((c, idx) => ({
351
- ...c,
352
- score: scores[idx]!,
353
- }));
354
-
355
- scoredConcepts.sort((a, b) => b.score - a.score);
356
- return scoredConcepts.slice(0, 3);
325
+ const response = await fetch(reranker.url, {
326
+ method: "POST",
327
+ headers: {
328
+ Authorization: `Bearer ${reranker.apiKey}`,
329
+ "Content-Type": "application/json",
330
+ },
331
+ body: JSON.stringify({
332
+ queries: [prompt],
333
+ documents: concepts.map((x) => x.description),
334
+ }),
335
+ });
336
+
337
+ const data = (await response.json()) as { scores: number[] };
338
+ const scores = data.scores;
339
+
340
+ const scoredConcepts = concepts.map((c, idx) => ({
341
+ ...c,
342
+ score: scores[idx]!,
343
+ }));
344
+
345
+ scoredConcepts.sort((a, b) => b.score - a.score);
346
+ return scoredConcepts.slice(0, 3);
357
347
  }