@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.
- package/README.md +38 -38
- package/dist/src/ai/interceptors/{resolve-captured-files.d.ts → request-interceptor.d.ts} +3 -2
- package/dist/src/config.d.ts +6 -3
- package/dist/src/factory.d.ts +13 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/ws/index.d.ts +1 -1
- package/package.json +54 -54
- package/src/adapters/database.ts +21 -28
- package/src/adapters/minio.ts +69 -69
- package/src/adapters/mssql.ts +167 -195
- package/src/adapters/storage.ts +4 -4
- package/src/ai/fetch.ts +31 -31
- package/src/ai/helpers.ts +18 -22
- package/src/ai/index.ts +106 -114
- package/src/ai/interceptors/request-interceptor.ts +61 -0
- package/src/ai/prompt.ts +80 -83
- package/src/ai/tools/call-endpoint.tool.ts +75 -82
- package/src/ai/tools/capture-files.tool.ts +15 -17
- package/src/ai/tools/execute-code.tool.ts +73 -80
- package/src/ai/tools/query-graph.tool.ts +17 -17
- package/src/auth/middleware.ts +51 -51
- package/src/cli/extract-endpoints.ts +436 -474
- package/src/config.ts +128 -135
- package/src/db/migrate.ts +13 -13
- package/src/db/migrations/20260309012148_cloudy_maria_hill/snapshot.json +303 -303
- package/src/db/schema.ts +46 -58
- package/src/factory.ts +136 -139
- package/src/graph/generate-cypher.ts +97 -97
- package/src/graph/helpers.ts +37 -37
- package/src/graph/index.ts +20 -20
- package/src/graph/neo4j.ts +82 -89
- package/src/graph/resolver.ts +201 -211
- package/src/graph/seed.ts +101 -114
- package/src/graph/types.ts +88 -88
- package/src/graph/validate.ts +55 -57
- package/src/index.ts +12 -5
- package/src/routes/chat.ts +23 -23
- package/src/routes/files.ts +75 -80
- package/src/routes/threads.ts +52 -54
- package/src/routes/ws.ts +22 -22
- package/src/types.ts +30 -30
- package/src/ws/connections.ts +11 -11
- package/src/ws/events.ts +2 -2
- package/src/ws/index.ts +1 -5
- package/src/ws/notify.ts +4 -4
- package/src/ai/interceptors/resolve-captured-files.ts +0 -64
package/src/adapters/mssql.ts
CHANGED
|
@@ -1,203 +1,175 @@
|
|
|
1
1
|
import type { ToolUIPart, UIMessage } from "ai";
|
|
2
|
-
import {
|
|
3
|
-
and,
|
|
4
|
-
desc,
|
|
5
|
-
eq,
|
|
6
|
-
getColumns,
|
|
7
|
-
inArray,
|
|
8
|
-
type InferInsertModel,
|
|
9
|
-
} from "drizzle-orm";
|
|
2
|
+
import { and, desc, eq, getColumns, inArray, type InferInsertModel } from "drizzle-orm";
|
|
10
3
|
import { drizzle } from "drizzle-orm/node-mssql";
|
|
11
4
|
import { threads, messages, capturedFiles } from "../db/schema";
|
|
12
5
|
import type { DatabaseAdapter } from "./database";
|
|
13
6
|
import type { StorageAdapter } from "./storage";
|
|
14
7
|
import type { Thread, CapturedFileInput } from "../types";
|
|
15
8
|
|
|
16
|
-
export function createMssqlAdapter(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
(x) =>
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
({
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
return result.map((r, i) => ({ id: files[i]!.id, uploadId: r }));
|
|
184
|
-
},
|
|
185
|
-
|
|
186
|
-
async getById(id: string, userId: string) {
|
|
187
|
-
const file = await db
|
|
188
|
-
.select()
|
|
189
|
-
.top(1)
|
|
190
|
-
.from(capturedFiles)
|
|
191
|
-
.where(
|
|
192
|
-
and(eq(capturedFiles.id, id), eq(capturedFiles.userId, userId)),
|
|
193
|
-
)
|
|
194
|
-
.execute()
|
|
195
|
-
.then((x) => (x.length ? x[0] : null));
|
|
196
|
-
|
|
197
|
-
return file ? { name: file.name } : null;
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
return adapter;
|
|
9
|
+
export function createMssqlAdapter(connectionString: string, storage: StorageAdapter) {
|
|
10
|
+
const db = drizzle(connectionString, { casing: "snake_case" });
|
|
11
|
+
|
|
12
|
+
const adapter: DatabaseAdapter = {
|
|
13
|
+
threads: {
|
|
14
|
+
async list(userId: string, agentId: string) {
|
|
15
|
+
return (await db
|
|
16
|
+
.select()
|
|
17
|
+
.from(threads)
|
|
18
|
+
.where(and(eq(threads.userId, userId), eq(threads.agentId, agentId)))
|
|
19
|
+
.orderBy(desc(threads.createdAt))) as Thread[];
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
async getById(userId: string, threadId: string) {
|
|
23
|
+
const result = await db
|
|
24
|
+
.select()
|
|
25
|
+
.top(1)
|
|
26
|
+
.from(threads)
|
|
27
|
+
.where(and(eq(threads.id, threadId), eq(threads.userId, userId)))
|
|
28
|
+
.execute();
|
|
29
|
+
return result.length ? (result[0] as Thread) : null;
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
async create(userId: string, agentId: string) {
|
|
33
|
+
const result = await db
|
|
34
|
+
.insert(threads)
|
|
35
|
+
.output()
|
|
36
|
+
.values({ userId, agentId })
|
|
37
|
+
.execute();
|
|
38
|
+
return result[0] as Thread;
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
async delete(userId: string, threadId: string) {
|
|
42
|
+
const capturedFilePaths = await db
|
|
43
|
+
.select({ id: capturedFiles.id })
|
|
44
|
+
.from(capturedFiles)
|
|
45
|
+
.innerJoin(messages, eq(messages.id, capturedFiles.messageId))
|
|
46
|
+
.innerJoin(threads, eq(threads.id, messages.threadId))
|
|
47
|
+
.where(and(eq(threads.id, threadId), eq(threads.userId, userId)))
|
|
48
|
+
.execute()
|
|
49
|
+
.then((x) => x.map((y) => `captured_files/${y.id}`));
|
|
50
|
+
|
|
51
|
+
await storage.delete(capturedFilePaths);
|
|
52
|
+
|
|
53
|
+
await db
|
|
54
|
+
.delete(threads)
|
|
55
|
+
.where(and(eq(threads.id, threadId), eq(threads.userId, userId)))
|
|
56
|
+
.execute();
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
async updateTitle(threadId: string, title: string) {
|
|
60
|
+
await db.update(threads).set({ title }).where(eq(threads.id, threadId)).execute();
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
async updateSession(threadId: string, session: Record<string, unknown>) {
|
|
64
|
+
await db.update(threads).set({ session }).where(eq(threads.id, threadId)).execute();
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
messages: {
|
|
69
|
+
async list(userId: string, threadId: string, opts?: { limit?: number }) {
|
|
70
|
+
return await db
|
|
71
|
+
.select(getColumns(messages))
|
|
72
|
+
.from(messages)
|
|
73
|
+
.innerJoin(threads, eq(threads.id, messages.threadId))
|
|
74
|
+
.where(and(eq(threads.userId, userId), eq(threads.id, threadId)))
|
|
75
|
+
.orderBy(desc(messages.createdAt))
|
|
76
|
+
.offset(0)
|
|
77
|
+
.fetch(opts?.limit ?? 100)
|
|
78
|
+
.then((x) => x.reverse());
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
async upsert(threadId: string, messagesToInsert: UIMessage[]) {
|
|
82
|
+
const ids = messagesToInsert.map((x) => x.id);
|
|
83
|
+
|
|
84
|
+
const existingIds = await db
|
|
85
|
+
.select({ id: messages.id })
|
|
86
|
+
.from(messages)
|
|
87
|
+
.where(inArray(messages.id, ids))
|
|
88
|
+
.execute()
|
|
89
|
+
.then((x) => x.map((y) => y.id));
|
|
90
|
+
|
|
91
|
+
const newMessages = messagesToInsert.filter((x) => !existingIds.includes(x.id));
|
|
92
|
+
|
|
93
|
+
if (newMessages.length) {
|
|
94
|
+
await db
|
|
95
|
+
.insert(messages)
|
|
96
|
+
.values(
|
|
97
|
+
newMessages.map(
|
|
98
|
+
(x) =>
|
|
99
|
+
({
|
|
100
|
+
id: x.id,
|
|
101
|
+
role: x.role,
|
|
102
|
+
threadId,
|
|
103
|
+
content: x,
|
|
104
|
+
text: x.parts.find((y) => y.type === "text")?.text,
|
|
105
|
+
}) satisfies InferInsertModel<typeof messages>,
|
|
106
|
+
),
|
|
107
|
+
)
|
|
108
|
+
.execute();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const existingMessages = messagesToInsert.filter((x) => existingIds.includes(x.id));
|
|
112
|
+
for (const message of existingMessages) {
|
|
113
|
+
await db
|
|
114
|
+
.update(messages)
|
|
115
|
+
.set({
|
|
116
|
+
content: message,
|
|
117
|
+
text: message.parts.find((x) => x.type === "text")?.text,
|
|
118
|
+
})
|
|
119
|
+
.where(eq(messages.id, message.id))
|
|
120
|
+
.execute();
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
capturedFiles: {
|
|
126
|
+
async create(
|
|
127
|
+
userId: string,
|
|
128
|
+
messageId: string,
|
|
129
|
+
toolPart: ToolUIPart,
|
|
130
|
+
files: CapturedFileInput[],
|
|
131
|
+
) {
|
|
132
|
+
const result = await db
|
|
133
|
+
.insert(capturedFiles)
|
|
134
|
+
.output({ id: capturedFiles.id })
|
|
135
|
+
.values(
|
|
136
|
+
files.map(
|
|
137
|
+
(file) =>
|
|
138
|
+
({
|
|
139
|
+
userId,
|
|
140
|
+
messageId,
|
|
141
|
+
toolPart,
|
|
142
|
+
name: file.file.name,
|
|
143
|
+
agentGeneratedId: file.id,
|
|
144
|
+
}) satisfies InferInsertModel<typeof capturedFiles>,
|
|
145
|
+
),
|
|
146
|
+
)
|
|
147
|
+
.execute()
|
|
148
|
+
.then((x) => x.map((y) => y.id));
|
|
149
|
+
|
|
150
|
+
await Promise.all(
|
|
151
|
+
result.map(
|
|
152
|
+
async (id, idx) =>
|
|
153
|
+
await storage.put(`captured_files/${id}`, files[idx]!.file.bytes),
|
|
154
|
+
),
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
return result.map((r, i) => ({ id: files[i]!.id, uploadId: r }));
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
async getById(id: string, userId: string) {
|
|
161
|
+
const file = await db
|
|
162
|
+
.select()
|
|
163
|
+
.top(1)
|
|
164
|
+
.from(capturedFiles)
|
|
165
|
+
.where(and(eq(capturedFiles.id, id), eq(capturedFiles.userId, userId)))
|
|
166
|
+
.execute()
|
|
167
|
+
.then((x) => (x.length ? x[0] : null));
|
|
168
|
+
|
|
169
|
+
return file ? { name: file.name } : null;
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
return adapter;
|
|
203
175
|
}
|
package/src/adapters/storage.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type StorageAdapter = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
put(path: string, data: string): Promise<boolean>;
|
|
3
|
+
get(path: string): Promise<string | false>;
|
|
4
|
+
delete(paths: string[]): Promise<boolean>;
|
|
5
|
+
stream(path: string): Promise<ReadableStream>;
|
|
6
6
|
};
|
package/src/ai/fetch.ts
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
1
|
import type { ResolvedCortexAgentConfig } from "../config.ts";
|
|
2
2
|
|
|
3
3
|
export async function fetchBackend(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
path: string,
|
|
5
|
+
backendFetch: NonNullable<ResolvedCortexAgentConfig["backendFetch"]>,
|
|
6
|
+
token: string,
|
|
7
|
+
options?: RequestInit,
|
|
8
8
|
) {
|
|
9
|
-
|
|
9
|
+
let finalOptions = options;
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
11
|
+
// Apply body interceptor if configured and request has a body
|
|
12
|
+
if (
|
|
13
|
+
backendFetch.transformRequestBody &&
|
|
14
|
+
finalOptions?.body &&
|
|
15
|
+
typeof finalOptions.body === "string"
|
|
16
|
+
) {
|
|
17
|
+
try {
|
|
18
|
+
const parsed = JSON.parse(finalOptions.body) as Record<string, unknown>;
|
|
19
|
+
const transformed = await backendFetch.transformRequestBody(parsed, {
|
|
20
|
+
token,
|
|
21
|
+
});
|
|
22
|
+
finalOptions = { ...finalOptions, body: JSON.stringify(transformed) };
|
|
23
|
+
} catch {
|
|
24
|
+
// If body isn't valid JSON, pass through as-is
|
|
25
|
+
}
|
|
25
26
|
}
|
|
26
|
-
}
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
28
|
+
return fetch(`${backendFetch.baseUrl}${path}`, {
|
|
29
|
+
...finalOptions,
|
|
30
|
+
headers: {
|
|
31
|
+
"Content-Type": "application/json",
|
|
32
|
+
"X-Requested-With": "cortex-server",
|
|
33
|
+
"X-Service-Api-Key": backendFetch.apiKey,
|
|
34
|
+
"X-Service-Token": token,
|
|
35
|
+
...backendFetch.headers,
|
|
36
|
+
...finalOptions?.headers,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
39
|
}
|
package/src/ai/helpers.ts
CHANGED
|
@@ -2,35 +2,31 @@ import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
|
2
2
|
import type { ResolvedCortexAgentConfig } from "../config.ts";
|
|
3
3
|
|
|
4
4
|
export function createModel(config: ResolvedCortexAgentConfig["model"]) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
const provider = createOpenAICompatible({
|
|
6
|
+
name: config.providerName ?? "default",
|
|
7
|
+
baseURL: config.baseURL,
|
|
8
|
+
apiKey: config.apiKey,
|
|
9
|
+
});
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
return provider(config.modelName);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
export function createEmbeddingModel(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
apiKey: config.apiKey,
|
|
21
|
-
});
|
|
14
|
+
export function createEmbeddingModel(config: ResolvedCortexAgentConfig["embedding"]) {
|
|
15
|
+
const provider = createOpenAICompatible({
|
|
16
|
+
name: "embedding",
|
|
17
|
+
baseURL: config.baseURL,
|
|
18
|
+
apiKey: config.apiKey,
|
|
19
|
+
});
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
return provider.textEmbeddingModel(config.modelName);
|
|
24
22
|
}
|
|
25
23
|
|
|
26
|
-
export function streamToBase64(stream: ReadableStream<Uint8Array>) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
export async function streamToBase64(stream: ReadableStream<Uint8Array>) {
|
|
25
|
+
return new Response(stream)
|
|
26
|
+
.arrayBuffer()
|
|
27
|
+
.then((buffer) => Buffer.from(buffer).toString("base64"));
|
|
30
28
|
}
|
|
31
29
|
|
|
32
30
|
export function tokenToUserId(token: string) {
|
|
33
|
-
|
|
34
|
-
"sub"
|
|
35
|
-
] as string;
|
|
31
|
+
return JSON.parse(Buffer.from(token.split(".")[1]!, "base64").toString())["sub"] as string;
|
|
36
32
|
}
|