@m6d/cortex-server 1.1.1 → 1.2.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 (46) hide show
  1. package/README.md +38 -38
  2. package/dist/src/ai/interceptors/{resolve-captured-files.d.ts → request-interceptor.d.ts} +3 -2
  3. package/dist/src/config.d.ts +6 -3
  4. package/dist/src/factory.d.ts +13 -1
  5. package/dist/src/index.d.ts +2 -0
  6. package/dist/src/ws/index.d.ts +1 -1
  7. package/package.json +54 -54
  8. package/src/adapters/database.ts +21 -28
  9. package/src/adapters/minio.ts +69 -69
  10. package/src/adapters/mssql.ts +167 -195
  11. package/src/adapters/storage.ts +4 -4
  12. package/src/ai/fetch.ts +31 -31
  13. package/src/ai/helpers.ts +18 -22
  14. package/src/ai/index.ts +106 -114
  15. package/src/ai/interceptors/request-interceptor.ts +61 -0
  16. package/src/ai/prompt.ts +80 -83
  17. package/src/ai/tools/call-endpoint.tool.ts +75 -82
  18. package/src/ai/tools/capture-files.tool.ts +15 -17
  19. package/src/ai/tools/execute-code.tool.ts +73 -80
  20. package/src/ai/tools/query-graph.tool.ts +17 -17
  21. package/src/auth/middleware.ts +51 -51
  22. package/src/cli/extract-endpoints.ts +436 -474
  23. package/src/config.ts +128 -135
  24. package/src/db/migrate.ts +13 -13
  25. package/src/db/migrations/20260309012148_cloudy_maria_hill/snapshot.json +303 -303
  26. package/src/db/schema.ts +46 -58
  27. package/src/factory.ts +136 -139
  28. package/src/graph/generate-cypher.ts +97 -97
  29. package/src/graph/helpers.ts +37 -37
  30. package/src/graph/index.ts +20 -20
  31. package/src/graph/neo4j.ts +82 -89
  32. package/src/graph/resolver.ts +201 -211
  33. package/src/graph/seed.ts +101 -114
  34. package/src/graph/types.ts +88 -88
  35. package/src/graph/validate.ts +55 -57
  36. package/src/index.ts +12 -5
  37. package/src/routes/chat.ts +23 -23
  38. package/src/routes/files.ts +75 -80
  39. package/src/routes/threads.ts +52 -54
  40. package/src/routes/ws.ts +22 -22
  41. package/src/types.ts +30 -30
  42. package/src/ws/connections.ts +11 -11
  43. package/src/ws/events.ts +2 -2
  44. package/src/ws/index.ts +1 -5
  45. package/src/ws/notify.ts +4 -4
  46. package/src/ai/interceptors/resolve-captured-files.ts +0 -64
@@ -7,32 +7,32 @@ import { requireAuth } from "../auth/middleware.ts";
7
7
  import { stream } from "../ai/index.ts";
8
8
 
9
9
  export function createChatRoutes() {
10
- const app = new Hono<CortexAppEnv>();
10
+ const app = new Hono<CortexAppEnv>();
11
11
 
12
- app.post(
13
- "/chat",
14
- requireAuth,
15
- zValidator(
16
- "json",
17
- z.object({
18
- id: z.string(),
19
- messages: z.array(z.unknown()),
20
- }),
21
- ),
22
- async function (c) {
23
- const config = c.get("agentConfig");
24
- const { id: userId, token } = c.get("user");
25
- const { messages, id: threadId } = c.req.valid("json");
12
+ app.post(
13
+ "/chat",
14
+ requireAuth,
15
+ zValidator(
16
+ "json",
17
+ z.object({
18
+ id: z.string(),
19
+ messages: z.array(z.unknown()),
20
+ }),
21
+ ),
22
+ async function (c) {
23
+ const config = c.get("agentConfig");
24
+ const { id: userId, token } = c.get("user");
25
+ const { messages, id: threadId } = c.req.valid("json");
26
26
 
27
- const thread = await config.db.threads.getById(userId, threadId);
27
+ const thread = await config.db.threads.getById(userId, threadId);
28
28
 
29
- if (!thread) {
30
- throw new HTTPException(404, { message: "Not found" });
31
- }
29
+ if (!thread) {
30
+ throw new HTTPException(404, { message: "Not found" });
31
+ }
32
32
 
33
- return stream(messages, thread, userId, token, config);
34
- },
35
- );
33
+ return stream(messages, thread, userId, token, config);
34
+ },
35
+ );
36
36
 
37
- return app;
37
+ return app;
38
38
  }
@@ -7,99 +7,94 @@ import type { CortexAppEnv } from "../types.ts";
7
7
  import { requireAuth } from "../auth/middleware.ts";
8
8
 
9
9
  export function createFileRoutes() {
10
- const app = new Hono<CortexAppEnv>();
10
+ const app = new Hono<CortexAppEnv>();
11
11
 
12
- app.get("/files/:id", requireAuth, async function (c) {
13
- const config = c.get("agentConfig");
14
- const id = c.req.param("id");
15
- const { id: userId } = c.get("user");
16
- const file = await config.db.capturedFiles.getById(id, userId);
12
+ app.get("/files/:id", requireAuth, async function (c) {
13
+ const config = c.get("agentConfig");
14
+ const id = c.req.param("id");
15
+ const { id: userId } = c.get("user");
16
+ const file = await config.db.capturedFiles.getById(id, userId);
17
17
 
18
- if (!file) {
19
- throw new HTTPException(404, { message: "File not found" });
20
- }
18
+ if (!file) {
19
+ throw new HTTPException(404, { message: "File not found" });
20
+ }
21
21
 
22
- const fileStream = await config.storage.stream(`captured_files/${id}`);
23
- return new Response(fileStream, {
24
- headers: {
25
- "Content-Type": "application/octet-stream",
26
- "Content-Disposition": `attachment; filename="${file.name}"`,
27
- },
22
+ const fileStream = await config.storage.stream(`captured_files/${id}`);
23
+ return new Response(fileStream, {
24
+ headers: {
25
+ "Content-Type": "application/octet-stream",
26
+ "Content-Disposition": `attachment; filename="${file.name}"`,
27
+ },
28
+ });
28
29
  });
29
- });
30
30
 
31
- app.post(
32
- "/files",
33
- requireAuth,
34
- zValidator(
35
- "json",
36
- z.object({
37
- toolCallId: z.string(),
38
- message: z.unknown().transform(async (message, ctx) => {
39
- const result = await safeValidateUIMessages({
40
- messages: [message],
41
- });
42
- if (!result.success) {
43
- ctx.addIssue({
44
- code: "custom",
45
- path: ["message"],
46
- message: "Invalid message format",
47
- });
48
- return z.NEVER;
49
- }
31
+ app.post(
32
+ "/files",
33
+ requireAuth,
34
+ zValidator(
35
+ "json",
36
+ z.object({
37
+ toolCallId: z.string(),
38
+ message: z.unknown().transform(async (message, ctx) => {
39
+ const result = await safeValidateUIMessages({
40
+ messages: [message],
41
+ });
42
+ if (!result.success) {
43
+ ctx.addIssue({
44
+ code: "custom",
45
+ path: ["message"],
46
+ message: "Invalid message format",
47
+ });
48
+ return z.NEVER;
49
+ }
50
50
 
51
- return result.data[0]!;
52
- }),
53
- files: z.array(
54
- z.object({
55
- id: z.string(),
56
- file: z.object({
57
- name: z.string(),
58
- bytes: z.string(),
51
+ return result.data[0]!;
52
+ }),
53
+ files: z.array(
54
+ z.object({
55
+ id: z.string(),
56
+ file: z.object({
57
+ name: z.string(),
58
+ bytes: z.string(),
59
+ }),
60
+ }),
61
+ ),
59
62
  }),
60
- }),
61
63
  ),
62
- }),
63
- ),
64
- async function (c) {
65
- const config = c.get("agentConfig");
66
- const { message, toolCallId, files } = c.req.valid("json");
67
- const { id: userId } = c.get("user");
64
+ async function (c) {
65
+ const config = c.get("agentConfig");
66
+ const { message, toolCallId, files } = c.req.valid("json");
67
+ const { id: userId } = c.get("user");
68
68
 
69
- function isTool(value: UIMessage["parts"][number]): value is ToolUIPart {
70
- return value.type.startsWith("tool-");
71
- }
69
+ function isTool(value: UIMessage["parts"][number]): value is ToolUIPart {
70
+ return value.type.startsWith("tool-");
71
+ }
72
72
 
73
- const toolPart = message.parts
74
- .filter(isTool)
75
- .find((x) => x.toolCallId === toolCallId);
73
+ const toolPart = message.parts.filter(isTool).find((x) => x.toolCallId === toolCallId);
76
74
 
77
- if (!toolPart) {
78
- throw new HTTPException(423, {
79
- message: "Invalid tool call id",
80
- });
81
- }
75
+ if (!toolPart) {
76
+ throw new HTTPException(423, {
77
+ message: "Invalid tool call id",
78
+ });
79
+ }
82
80
 
83
- const result = await config.db.capturedFiles.create(
84
- userId,
85
- message.id,
86
- toolPart,
87
- files,
88
- );
81
+ const result = await config.db.capturedFiles.create(
82
+ userId,
83
+ message.id,
84
+ toolPart,
85
+ files,
86
+ );
89
87
 
90
- // Store file bytes
91
- await Promise.all(
92
- result.map(async (r, i) => {
93
- await config.storage.put(
94
- `captured_files/${r.uploadId}`,
95
- files[i]!.file.bytes,
96
- );
97
- }),
98
- );
88
+ // Store file bytes
89
+ await Promise.all(
90
+ result.map(async (r, i) => {
91
+ await config.storage.put(`captured_files/${r.uploadId}`, files[i]!.file.bytes);
92
+ }),
93
+ );
99
94
 
100
- return c.json(result);
101
- },
102
- );
95
+ return c.json(result);
96
+ },
97
+ );
103
98
 
104
- return app;
99
+ return app;
105
100
  }
@@ -6,64 +6,62 @@ import { requireAuth } from "../auth/middleware.ts";
6
6
  import { generateTitle } from "../ai/index.ts";
7
7
 
8
8
  export function createThreadRoutes() {
9
- const app = new Hono<CortexAppEnv>();
9
+ const app = new Hono<CortexAppEnv>();
10
10
 
11
- app.get("/threads", requireAuth, async function (c) {
12
- const config = c.get("agentConfig");
13
- const agentId = c.get("agentId");
14
- const threads = await config.db.threads
15
- .list(c.get("user").id, agentId)
16
- .then((x) =>
17
- x.map((y) => ({ id: y.id, title: y.title, createdAt: y.createdAt })),
18
- );
19
- return c.json(threads);
20
- });
11
+ app.get("/threads", requireAuth, async function (c) {
12
+ const config = c.get("agentConfig");
13
+ const agentId = c.get("agentId");
14
+ const threads = await config.db.threads
15
+ .list(c.get("user").id, agentId)
16
+ .then((x) => x.map((y) => ({ id: y.id, title: y.title, createdAt: y.createdAt })));
17
+ return c.json(threads);
18
+ });
21
19
 
22
- app.post(
23
- "/threads",
24
- requireAuth,
25
- zValidator("json", z.object({ prompt: z.string() })),
26
- async function (c) {
27
- const config = c.get("agentConfig");
28
- const agentId = c.get("agentId");
29
- const { prompt } = c.req.valid("json");
30
- const { id, createdAt, title } = await config.db.threads.create(
31
- c.get("user").id,
32
- agentId,
33
- );
34
- generateTitle(id, prompt, c.get("user").id, config);
35
- return c.json({ id, title, createdAt });
36
- },
37
- );
20
+ app.post(
21
+ "/threads",
22
+ requireAuth,
23
+ zValidator("json", z.object({ prompt: z.string() })),
24
+ async function (c) {
25
+ const config = c.get("agentConfig");
26
+ const agentId = c.get("agentId");
27
+ const { prompt } = c.req.valid("json");
28
+ const { id, createdAt, title } = await config.db.threads.create(
29
+ c.get("user").id,
30
+ agentId,
31
+ );
32
+ generateTitle(id, prompt, c.get("user").id, config);
33
+ return c.json({ id, title, createdAt });
34
+ },
35
+ );
38
36
 
39
- app.delete("/threads/:threadId", requireAuth, async function (c) {
40
- const config = c.get("agentConfig");
41
- const threadId = c.req.param("threadId");
42
- await config.db.threads.delete(c.get("user").id, threadId);
43
- return c.body(null, 204);
44
- });
37
+ app.delete("/threads/:threadId", requireAuth, async function (c) {
38
+ const config = c.get("agentConfig");
39
+ const threadId = c.req.param("threadId");
40
+ await config.db.threads.delete(c.get("user").id, threadId);
41
+ return c.body(null, 204);
42
+ });
45
43
 
46
- app.get("/threads/:threadId/messages", requireAuth, async function (c) {
47
- const config = c.get("agentConfig");
48
- const threadId = c.req.param("threadId");
49
- const messages = await config.db.messages
50
- .list(c.get("user").id, threadId)
51
- .then((x) => x.map((y) => y.content));
52
- return c.json(messages);
53
- });
44
+ app.get("/threads/:threadId/messages", requireAuth, async function (c) {
45
+ const config = c.get("agentConfig");
46
+ const threadId = c.req.param("threadId");
47
+ const messages = await config.db.messages
48
+ .list(c.get("user").id, threadId)
49
+ .then((x) => x.map((y) => y.content));
50
+ return c.json(messages);
51
+ });
54
52
 
55
- app.post(
56
- "/threads/:threadId/session",
57
- requireAuth,
58
- zValidator("json", z.object({ session: z.record(z.unknown()) })),
59
- async function (c) {
60
- const config = c.get("agentConfig");
61
- const threadId = c.req.param("threadId");
62
- const { session } = c.req.valid("json");
63
- await config.db.threads.updateSession(threadId, session);
64
- return c.body(null, 204);
65
- },
66
- );
53
+ app.post(
54
+ "/threads/:threadId/session",
55
+ requireAuth,
56
+ zValidator("json", z.object({ session: z.record(z.unknown()) })),
57
+ async function (c) {
58
+ const config = c.get("agentConfig");
59
+ const threadId = c.req.param("threadId");
60
+ const { session } = c.req.valid("json");
61
+ await config.db.threads.updateSession(threadId, session);
62
+ return c.body(null, 204);
63
+ },
64
+ );
67
65
 
68
- return app;
66
+ return app;
69
67
  }
package/src/routes/ws.ts CHANGED
@@ -4,30 +4,30 @@ import type { AppEnv } from "../types.ts";
4
4
  import { addConnection, removeConnection } from "../ws/index.ts";
5
5
 
6
6
  export function createWsRoute() {
7
- const app = new Hono<AppEnv>();
7
+ const app = new Hono<AppEnv>();
8
8
 
9
- app.get(
10
- "/ws",
11
- upgradeWebSocket(function (c) {
12
- const user = c.get("user");
13
- const userId = user?.id;
9
+ app.get(
10
+ "/ws",
11
+ upgradeWebSocket(function (c) {
12
+ const user = c.get("user");
13
+ const userId = user?.id;
14
14
 
15
- return {
16
- onOpen(_, ws) {
17
- if (userId) {
18
- addConnection(userId, ws);
19
- }
20
- ws.send("Connected!");
21
- },
15
+ return {
16
+ onOpen(_, ws) {
17
+ if (userId) {
18
+ addConnection(userId, ws);
19
+ }
20
+ ws.send("Connected!");
21
+ },
22
22
 
23
- onClose(_, ws) {
24
- if (userId) {
25
- removeConnection(userId, ws);
26
- }
27
- },
28
- };
29
- }),
30
- );
23
+ onClose(_, ws) {
24
+ if (userId) {
25
+ removeConnection(userId, ws);
26
+ }
27
+ },
28
+ };
29
+ }),
30
+ );
31
31
 
32
- return app;
32
+ return app;
33
33
  }
package/src/types.ts CHANGED
@@ -2,49 +2,49 @@ import type { ToolUIPart, UIMessage } from "ai";
2
2
  import type { ResolvedCortexAgentConfig } from "./config";
3
3
 
4
4
  export type Thread = {
5
- id: string;
6
- userId: string;
7
- agentId: string;
8
- title: string | null;
9
- session: Record<string, unknown> | null;
10
- createdAt: Date;
11
- updatedAt: Date;
5
+ id: string;
6
+ userId: string;
7
+ agentId: string;
8
+ title: string | null;
9
+ session: Record<string, unknown> | null;
10
+ createdAt: Date;
11
+ updatedAt: Date;
12
12
  };
13
13
 
14
14
  export type StoredMessage = {
15
- id: string;
16
- threadId: string;
17
- text: string | null;
18
- content: UIMessage;
19
- role: "system" | "user" | "assistant" | "tool";
20
- createdAt: Date;
21
- updatedAt: Date;
15
+ id: string;
16
+ threadId: string;
17
+ text: string | null;
18
+ content: UIMessage;
19
+ role: "system" | "user" | "assistant" | "tool";
20
+ createdAt: Date;
21
+ updatedAt: Date;
22
22
  };
23
23
 
24
24
  export type CapturedFileInput = {
25
- id: string;
26
- file: { name: string; bytes: string };
25
+ id: string;
26
+ file: { name: string; bytes: string };
27
27
  };
28
28
 
29
29
  export type AppEnv = {
30
- Bindings: {};
31
- Variables: {
32
- user: { id: string; token: string } | undefined;
33
- };
30
+ Bindings: {};
31
+ Variables: {
32
+ user: { id: string; token: string } | undefined;
33
+ };
34
34
  };
35
35
 
36
36
  export type AuthedAppEnv = {
37
- Bindings: {};
38
- Variables: {
39
- user: { id: string; token: string };
40
- };
37
+ Bindings: {};
38
+ Variables: {
39
+ user: { id: string; token: string };
40
+ };
41
41
  };
42
42
 
43
43
  export type CortexAppEnv = {
44
- Bindings: {};
45
- Variables: {
46
- user: { id: string; token: string };
47
- agentConfig: ResolvedCortexAgentConfig;
48
- agentId: string;
49
- };
44
+ Bindings: {};
45
+ Variables: {
46
+ user: { id: string; token: string };
47
+ agentConfig: ResolvedCortexAgentConfig;
48
+ agentId: string;
49
+ };
50
50
  };
@@ -3,21 +3,21 @@ import type { WSContext } from "hono/ws";
3
3
  const connections = new Map<string, WSContext[]>();
4
4
 
5
5
  export function addConnection(userId: string, ws: WSContext) {
6
- const sockets = connections.get(userId) ?? [];
7
- sockets.push(ws);
8
- connections.set(userId, sockets);
6
+ const sockets = connections.get(userId) ?? [];
7
+ sockets.push(ws);
8
+ connections.set(userId, sockets);
9
9
  }
10
10
 
11
11
  export function removeConnection(userId: string, ws: WSContext) {
12
- let sockets = connections.get(userId) ?? [];
13
- sockets = sockets.filter((x) => x !== ws);
14
- if (sockets.length) {
15
- connections.set(userId, sockets);
16
- } else {
17
- connections.delete(userId);
18
- }
12
+ let sockets = connections.get(userId) ?? [];
13
+ sockets = sockets.filter((x) => x !== ws);
14
+ if (sockets.length) {
15
+ connections.set(userId, sockets);
16
+ } else {
17
+ connections.delete(userId);
18
+ }
19
19
  }
20
20
 
21
21
  export function getConnections(userId: string) {
22
- return connections.get(userId) ?? [];
22
+ return connections.get(userId) ?? [];
23
23
  }
package/src/ws/events.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export type ThreadTitleUpdatedEvent = {
2
- type: "thread:title-updated";
3
- payload: { threadId: string; title: string };
2
+ type: "thread:title-updated";
3
+ payload: { threadId: string; title: string };
4
4
  };
5
5
 
6
6
  export type WsEvent = ThreadTitleUpdatedEvent;
package/src/ws/index.ts CHANGED
@@ -1,7 +1,3 @@
1
- export {
2
- addConnection,
3
- removeConnection,
4
- getConnections,
5
- } from "./connections.ts";
1
+ export { addConnection, removeConnection, getConnections } from "./connections.ts";
6
2
  export { notify } from "./notify.ts";
7
3
  export type { WsEvent, ThreadTitleUpdatedEvent } from "./events.ts";
package/src/ws/notify.ts CHANGED
@@ -2,8 +2,8 @@ import { getConnections } from "./connections.ts";
2
2
  import type { WsEvent } from "./events.ts";
3
3
 
4
4
  export function notify(userId: string, event: WsEvent) {
5
- const message = JSON.stringify(event);
6
- for (const ws of getConnections(userId)) {
7
- ws.send(message);
8
- }
5
+ const message = JSON.stringify(event);
6
+ for (const ws of getConnections(userId)) {
7
+ ws.send(message);
8
+ }
9
9
  }
@@ -1,64 +0,0 @@
1
- import type { DatabaseAdapter } from "../../adapters/database.ts";
2
- import type { StorageAdapter } from "../../adapters/storage.ts";
3
- import { streamToBase64, tokenToUserId } from "../helpers.ts";
4
-
5
- export type ResolvedFile = {
6
- name: string;
7
- bytes: string;
8
- };
9
-
10
- export function createCapturedFileInterceptor(
11
- db: DatabaseAdapter,
12
- storage: StorageAdapter,
13
- options?: { transformFile?: (file: ResolvedFile) => unknown },
14
- ) {
15
- const transformFile =
16
- options?.transformFile ?? ((file: ResolvedFile) => file);
17
-
18
- return async function resolveCapturedFiles(
19
- body: Record<string, unknown>,
20
- context: { token: string },
21
- ) {
22
- const userId = tokenToUserId(context.token);
23
-
24
- async function traverse(obj: Record<string, unknown>) {
25
- await Promise.all(
26
- Object.keys(obj).map(async (key) => {
27
- const value = obj[key];
28
- if (typeof value === "string" && value.startsWith("capturedFile")) {
29
- const [_, uploadId] = value.split("#");
30
- const file = await db.capturedFiles.getById(uploadId!, userId);
31
- if (file) {
32
- const fileStream = await storage.stream(
33
- `captured_files/${uploadId}`,
34
- );
35
- const bytes = await streamToBase64(fileStream);
36
- obj[key] = transformFile({ name: file.name, bytes });
37
- }
38
- }
39
-
40
- if (Array.isArray(value)) {
41
- await Promise.all(
42
- value.map(async (child) => {
43
- if (typeof child === "object" && child !== null) {
44
- await traverse(child as Record<string, unknown>);
45
- }
46
- }),
47
- );
48
- }
49
-
50
- if (
51
- typeof value === "object" &&
52
- value !== null &&
53
- !Array.isArray(value)
54
- ) {
55
- await traverse(value as Record<string, unknown>);
56
- }
57
- }),
58
- );
59
- }
60
-
61
- await traverse(body);
62
- return body;
63
- };
64
- }