@m6d/cortex-server 1.0.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 +64 -0
- package/dist/index.d.ts +1 -0
- package/dist/src/adapters/database.d.ts +27 -0
- package/dist/src/adapters/minio.d.ts +10 -0
- package/dist/src/adapters/mssql.d.ts +3 -0
- package/dist/src/adapters/storage.d.ts +6 -0
- package/dist/src/ai/fetch.d.ts +2 -0
- package/dist/src/ai/helpers.d.ts +5 -0
- package/dist/src/ai/index.d.ts +4 -0
- package/dist/src/ai/interceptors/resolve-captured-files.d.ts +11 -0
- package/dist/src/ai/prompt.d.ts +4 -0
- package/dist/src/ai/tools/call-endpoint.tool.d.ts +7 -0
- package/dist/src/ai/tools/capture-files.tool.d.ts +6 -0
- package/dist/src/ai/tools/execute-code.tool.d.ts +4 -0
- package/dist/src/ai/tools/query-graph.tool.d.ts +5 -0
- package/dist/src/auth/middleware.d.ts +4 -0
- package/dist/src/cli/extract-endpoints.d.ts +6 -0
- package/dist/src/config.d.ts +145 -0
- package/dist/src/db/migrate.d.ts +1 -0
- package/dist/src/db/schema.d.ts +345 -0
- package/dist/src/factory.d.ts +17 -0
- package/dist/src/graph/generate-cypher.d.ts +22 -0
- package/dist/src/graph/helpers.d.ts +60 -0
- package/dist/src/graph/index.d.ts +11 -0
- package/dist/src/graph/neo4j.d.ts +18 -0
- package/dist/src/graph/resolver.d.ts +51 -0
- package/dist/src/graph/seed.d.ts +19 -0
- package/dist/src/graph/types.d.ts +104 -0
- package/dist/src/graph/validate.d.ts +2 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/routes/chat.d.ts +3 -0
- package/dist/src/routes/files.d.ts +3 -0
- package/dist/src/routes/index.d.ts +4 -0
- package/dist/src/routes/threads.d.ts +3 -0
- package/dist/src/routes/ws.d.ts +3 -0
- package/dist/src/types.d.ts +56 -0
- package/dist/src/ws/connections.d.ts +4 -0
- package/dist/src/ws/events.d.ts +8 -0
- package/dist/src/ws/index.d.ts +3 -0
- package/dist/src/ws/notify.d.ts +2 -0
- package/index.ts +1 -0
- package/package.json +57 -0
- package/src/adapters/database.ts +33 -0
- package/src/adapters/minio.ts +89 -0
- package/src/adapters/mssql.ts +203 -0
- package/src/adapters/storage.ts +6 -0
- package/src/ai/fetch.ts +39 -0
- package/src/ai/helpers.ts +36 -0
- package/src/ai/index.ts +145 -0
- package/src/ai/interceptors/resolve-captured-files.ts +64 -0
- package/src/ai/prompt.ts +120 -0
- package/src/ai/tools/call-endpoint.tool.ts +96 -0
- package/src/ai/tools/capture-files.tool.ts +22 -0
- package/src/ai/tools/execute-code.tool.ts +108 -0
- package/src/ai/tools/query-graph.tool.ts +35 -0
- package/src/auth/middleware.ts +63 -0
- package/src/cli/extract-endpoints.ts +588 -0
- package/src/config.ts +155 -0
- package/src/db/migrate.ts +21 -0
- package/src/db/migrations/20260309012148_cloudy_maria_hill/migration.sql +36 -0
- package/src/db/migrations/20260309012148_cloudy_maria_hill/snapshot.json +305 -0
- package/src/db/schema.ts +77 -0
- package/src/factory.ts +159 -0
- package/src/graph/generate-cypher.ts +179 -0
- package/src/graph/helpers.ts +68 -0
- package/src/graph/index.ts +47 -0
- package/src/graph/neo4j.ts +117 -0
- package/src/graph/resolver.ts +357 -0
- package/src/graph/seed.ts +172 -0
- package/src/graph/types.ts +152 -0
- package/src/graph/validate.ts +80 -0
- package/src/index.ts +27 -0
- package/src/routes/chat.ts +38 -0
- package/src/routes/files.ts +105 -0
- package/src/routes/index.ts +4 -0
- package/src/routes/threads.ts +69 -0
- package/src/routes/ws.ts +33 -0
- package/src/types.ts +50 -0
- package/src/ws/connections.ts +23 -0
- package/src/ws/events.ts +6 -0
- package/src/ws/index.ts +7 -0
- package/src/ws/notify.ts +9 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { UIMessage } from "ai";
|
|
2
|
+
import type { ResolvedCortexAgentConfig } from "./config";
|
|
3
|
+
export type Thread = {
|
|
4
|
+
id: string;
|
|
5
|
+
userId: string;
|
|
6
|
+
agentId: string;
|
|
7
|
+
title: string | null;
|
|
8
|
+
session: Record<string, unknown> | null;
|
|
9
|
+
createdAt: Date;
|
|
10
|
+
updatedAt: Date;
|
|
11
|
+
};
|
|
12
|
+
export type StoredMessage = {
|
|
13
|
+
id: string;
|
|
14
|
+
threadId: string;
|
|
15
|
+
text: string | null;
|
|
16
|
+
content: UIMessage;
|
|
17
|
+
role: "system" | "user" | "assistant" | "tool";
|
|
18
|
+
createdAt: Date;
|
|
19
|
+
updatedAt: Date;
|
|
20
|
+
};
|
|
21
|
+
export type CapturedFileInput = {
|
|
22
|
+
id: string;
|
|
23
|
+
file: {
|
|
24
|
+
name: string;
|
|
25
|
+
bytes: string;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
export type AppEnv = {
|
|
29
|
+
Bindings: {};
|
|
30
|
+
Variables: {
|
|
31
|
+
user: {
|
|
32
|
+
id: string;
|
|
33
|
+
token: string;
|
|
34
|
+
} | undefined;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
export type AuthedAppEnv = {
|
|
38
|
+
Bindings: {};
|
|
39
|
+
Variables: {
|
|
40
|
+
user: {
|
|
41
|
+
id: string;
|
|
42
|
+
token: string;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
export type CortexAppEnv = {
|
|
47
|
+
Bindings: {};
|
|
48
|
+
Variables: {
|
|
49
|
+
user: {
|
|
50
|
+
id: string;
|
|
51
|
+
token: string;
|
|
52
|
+
};
|
|
53
|
+
agentConfig: ResolvedCortexAgentConfig;
|
|
54
|
+
agentId: string;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { WSContext } from "hono/ws";
|
|
2
|
+
export declare function addConnection(userId: string, ws: WSContext): void;
|
|
3
|
+
export declare function removeConnection(userId: string, ws: WSContext): void;
|
|
4
|
+
export declare function getConnections(userId: string): WSContext<unknown>[];
|
package/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./src/index";
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@m6d/cortex-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Reusable AI agent chat server library for Hono + Bun",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"bun": "./index.ts",
|
|
11
|
+
"default": "./index.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"main": "./index.ts",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"src",
|
|
19
|
+
"index.ts",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc -p tsconfig.build.json",
|
|
24
|
+
"db:generate": "drizzle-kit generate",
|
|
25
|
+
"db:migrate": "drizzle-kit migrate"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@ai-sdk/provider": "^3.0.0",
|
|
29
|
+
"@hono/zod-validator": "^0.5.0",
|
|
30
|
+
"drizzle-orm": "^1.0.0-beta.15-859cf75",
|
|
31
|
+
"minio": "^8.0.7",
|
|
32
|
+
"mssql": "^12.2.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@ai-sdk/openai-compatible": "^2.0.0",
|
|
36
|
+
"@types/bun": "latest",
|
|
37
|
+
"@types/minio": "^7.1.1",
|
|
38
|
+
"@types/mssql": "^9.1.5",
|
|
39
|
+
"ai": "^6.0.104",
|
|
40
|
+
"drizzle-kit": "^1.0.0-beta.15-859cf75",
|
|
41
|
+
"hono": "^4.12.3",
|
|
42
|
+
"typescript": "^5"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"@ai-sdk/openai-compatible": "^2.0.0",
|
|
46
|
+
"ai": "^6.0.0",
|
|
47
|
+
"hono": "^4.12.3",
|
|
48
|
+
"jose": "^6.1.3",
|
|
49
|
+
"zod": "^3.24.0"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"bun": ">=1.0.0"
|
|
53
|
+
},
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ToolUIPart, UIMessage } from "ai";
|
|
2
|
+
import type { Thread, StoredMessage, CapturedFileInput } from "../types";
|
|
3
|
+
|
|
4
|
+
export type DatabaseAdapter = {
|
|
5
|
+
threads: {
|
|
6
|
+
list(userId: string, agentId: string): Promise<Thread[]>;
|
|
7
|
+
getById(userId: string, threadId: string): Promise<Thread | null>;
|
|
8
|
+
create(userId: string, agentId: string): Promise<Thread>;
|
|
9
|
+
delete(userId: string, threadId: string): Promise<void>;
|
|
10
|
+
updateTitle(threadId: string, title: string): Promise<void>;
|
|
11
|
+
updateSession(
|
|
12
|
+
threadId: string,
|
|
13
|
+
session: Record<string, unknown>,
|
|
14
|
+
): Promise<void>;
|
|
15
|
+
};
|
|
16
|
+
messages: {
|
|
17
|
+
list(
|
|
18
|
+
userId: string,
|
|
19
|
+
threadId: string,
|
|
20
|
+
opts?: { limit?: number },
|
|
21
|
+
): Promise<StoredMessage[]>;
|
|
22
|
+
upsert(threadId: string, messages: UIMessage[]): Promise<void>;
|
|
23
|
+
};
|
|
24
|
+
capturedFiles: {
|
|
25
|
+
create(
|
|
26
|
+
userId: string,
|
|
27
|
+
messageId: string,
|
|
28
|
+
toolPart: ToolUIPart,
|
|
29
|
+
files: CapturedFileInput[],
|
|
30
|
+
): Promise<{ id: string; uploadId: string }[]>;
|
|
31
|
+
getById(id: string, userId: string): Promise<{ name: string } | null>;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import * as Minio from "minio";
|
|
2
|
+
import { Readable } from "node:stream";
|
|
3
|
+
import type { StorageAdapter } from "./storage";
|
|
4
|
+
|
|
5
|
+
export type MinioConfig = {
|
|
6
|
+
endPoint: string;
|
|
7
|
+
port: number;
|
|
8
|
+
useSSL: boolean;
|
|
9
|
+
accessKey: string;
|
|
10
|
+
secretKey: string;
|
|
11
|
+
bucketName?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function createMinioAdapter(config: MinioConfig) {
|
|
15
|
+
if (!config.endPoint) {
|
|
16
|
+
throw new Error("MinIO adapter: endPoint is required");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const client = new Minio.Client({
|
|
20
|
+
endPoint: config.endPoint,
|
|
21
|
+
port: config.port,
|
|
22
|
+
useSSL: config.useSSL,
|
|
23
|
+
accessKey: config.accessKey,
|
|
24
|
+
secretKey: config.secretKey,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const bucketName = config.bucketName ?? "ai-storage";
|
|
28
|
+
let bucketReady = false;
|
|
29
|
+
|
|
30
|
+
async function ensureBucket() {
|
|
31
|
+
if (bucketReady) return;
|
|
32
|
+
if (!(await client.bucketExists(bucketName))) {
|
|
33
|
+
await client.makeBucket(bucketName);
|
|
34
|
+
}
|
|
35
|
+
bucketReady = true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const adapter: StorageAdapter = {
|
|
39
|
+
async put(path: string, data: string) {
|
|
40
|
+
try {
|
|
41
|
+
await ensureBucket();
|
|
42
|
+
await client.putObject(bucketName, path, Buffer.from(data, "base64"));
|
|
43
|
+
return true;
|
|
44
|
+
} catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
async get(path: string) {
|
|
50
|
+
try {
|
|
51
|
+
const stream = await client.getObject(bucketName, path);
|
|
52
|
+
return await new Promise<string>((resolve, reject) => {
|
|
53
|
+
const chunks: Uint8Array[] = [];
|
|
54
|
+
|
|
55
|
+
stream.on("error", () => {
|
|
56
|
+
reject();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
stream.on("data", (chunk) => chunks.push(chunk));
|
|
60
|
+
stream.on("end", () => {
|
|
61
|
+
const buffer = Buffer.concat(chunks);
|
|
62
|
+
resolve(buffer.toString("base64"));
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
async delete(paths: string[]) {
|
|
71
|
+
try {
|
|
72
|
+
await client.removeObjects(
|
|
73
|
+
bucketName,
|
|
74
|
+
paths.map((x) => ({ name: x })),
|
|
75
|
+
);
|
|
76
|
+
return true;
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
async stream(path: string) {
|
|
83
|
+
const nodeStream = await client.getObject(bucketName, path);
|
|
84
|
+
return Readable.toWeb(nodeStream) as unknown as ReadableStream;
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
return adapter;
|
|
89
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
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";
|
|
10
|
+
import { drizzle } from "drizzle-orm/node-mssql";
|
|
11
|
+
import { threads, messages, capturedFiles } from "../db/schema";
|
|
12
|
+
import type { DatabaseAdapter } from "./database";
|
|
13
|
+
import type { StorageAdapter } from "./storage";
|
|
14
|
+
import type { Thread, CapturedFileInput } from "../types";
|
|
15
|
+
|
|
16
|
+
export function createMssqlAdapter(
|
|
17
|
+
connectionString: string,
|
|
18
|
+
storage: StorageAdapter,
|
|
19
|
+
) {
|
|
20
|
+
const db = drizzle(connectionString, { casing: "snake_case" });
|
|
21
|
+
|
|
22
|
+
const adapter: DatabaseAdapter = {
|
|
23
|
+
threads: {
|
|
24
|
+
async list(userId: string, agentId: string) {
|
|
25
|
+
return (await db
|
|
26
|
+
.select()
|
|
27
|
+
.from(threads)
|
|
28
|
+
.where(and(eq(threads.userId, userId), eq(threads.agentId, agentId)))
|
|
29
|
+
.orderBy(desc(threads.createdAt))) as Thread[];
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
async getById(userId: string, threadId: string) {
|
|
33
|
+
const result = await db
|
|
34
|
+
.select()
|
|
35
|
+
.top(1)
|
|
36
|
+
.from(threads)
|
|
37
|
+
.where(and(eq(threads.id, threadId), eq(threads.userId, userId)))
|
|
38
|
+
.execute();
|
|
39
|
+
return result.length ? (result[0] as Thread) : null;
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
async create(userId: string, agentId: string) {
|
|
43
|
+
const result = await db
|
|
44
|
+
.insert(threads)
|
|
45
|
+
.output()
|
|
46
|
+
.values({ userId, agentId })
|
|
47
|
+
.execute();
|
|
48
|
+
return result[0] as Thread;
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
async delete(userId: string, threadId: string) {
|
|
52
|
+
const capturedFilePaths = await db
|
|
53
|
+
.select({ id: capturedFiles.id })
|
|
54
|
+
.from(capturedFiles)
|
|
55
|
+
.innerJoin(messages, eq(messages.id, capturedFiles.messageId))
|
|
56
|
+
.innerJoin(threads, eq(threads.id, messages.threadId))
|
|
57
|
+
.where(and(eq(threads.id, threadId), eq(threads.userId, userId)))
|
|
58
|
+
.execute()
|
|
59
|
+
.then((x) => x.map((y) => `captured_files/${y.id}`));
|
|
60
|
+
|
|
61
|
+
await storage.delete(capturedFilePaths);
|
|
62
|
+
|
|
63
|
+
await db
|
|
64
|
+
.delete(threads)
|
|
65
|
+
.where(and(eq(threads.id, threadId), eq(threads.userId, userId)))
|
|
66
|
+
.execute();
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
async updateTitle(threadId: string, title: string) {
|
|
70
|
+
await db
|
|
71
|
+
.update(threads)
|
|
72
|
+
.set({ title })
|
|
73
|
+
.where(eq(threads.id, threadId))
|
|
74
|
+
.execute();
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
async updateSession(threadId: string, session: Record<string, unknown>) {
|
|
78
|
+
await db
|
|
79
|
+
.update(threads)
|
|
80
|
+
.set({ session })
|
|
81
|
+
.where(eq(threads.id, threadId))
|
|
82
|
+
.execute();
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
messages: {
|
|
87
|
+
async list(userId: string, threadId: string, opts?: { limit?: number }) {
|
|
88
|
+
return await db
|
|
89
|
+
.select(getColumns(messages))
|
|
90
|
+
.from(messages)
|
|
91
|
+
.innerJoin(threads, eq(threads.id, messages.threadId))
|
|
92
|
+
.where(and(eq(threads.userId, userId), eq(threads.id, threadId)))
|
|
93
|
+
.orderBy(desc(messages.createdAt))
|
|
94
|
+
.offset(0)
|
|
95
|
+
.fetch(opts?.limit ?? 100)
|
|
96
|
+
.then((x) => x.reverse());
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
async upsert(threadId: string, messagesToInsert: UIMessage[]) {
|
|
100
|
+
const ids = messagesToInsert.map((x) => x.id);
|
|
101
|
+
|
|
102
|
+
const existingIds = await db
|
|
103
|
+
.select({ id: messages.id })
|
|
104
|
+
.from(messages)
|
|
105
|
+
.where(inArray(messages.id, ids))
|
|
106
|
+
.execute()
|
|
107
|
+
.then((x) => x.map((y) => y.id));
|
|
108
|
+
|
|
109
|
+
const newMessages = messagesToInsert.filter(
|
|
110
|
+
(x) => !existingIds.includes(x.id),
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
if (newMessages.length) {
|
|
114
|
+
await db
|
|
115
|
+
.insert(messages)
|
|
116
|
+
.values(
|
|
117
|
+
newMessages.map(
|
|
118
|
+
(x) =>
|
|
119
|
+
({
|
|
120
|
+
id: x.id,
|
|
121
|
+
role: x.role,
|
|
122
|
+
threadId,
|
|
123
|
+
content: x,
|
|
124
|
+
text: x.parts.find((y) => y.type === "text")?.text,
|
|
125
|
+
}) satisfies InferInsertModel<typeof messages>,
|
|
126
|
+
),
|
|
127
|
+
)
|
|
128
|
+
.execute();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const existingMessages = messagesToInsert.filter((x) =>
|
|
132
|
+
existingIds.includes(x.id),
|
|
133
|
+
);
|
|
134
|
+
if (existingMessages.length) {
|
|
135
|
+
await db.transaction(async (tx) => {
|
|
136
|
+
for (const message of existingMessages) {
|
|
137
|
+
await tx
|
|
138
|
+
.update(messages)
|
|
139
|
+
.set({
|
|
140
|
+
content: message,
|
|
141
|
+
text: message.parts.find((x) => x.type === "text")?.text,
|
|
142
|
+
})
|
|
143
|
+
.where(eq(messages.id, message.id))
|
|
144
|
+
.execute();
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
capturedFiles: {
|
|
152
|
+
async create(
|
|
153
|
+
userId: string,
|
|
154
|
+
messageId: string,
|
|
155
|
+
toolPart: ToolUIPart,
|
|
156
|
+
files: CapturedFileInput[],
|
|
157
|
+
) {
|
|
158
|
+
const result = await db
|
|
159
|
+
.insert(capturedFiles)
|
|
160
|
+
.output({ id: capturedFiles.id })
|
|
161
|
+
.values(
|
|
162
|
+
files.map(
|
|
163
|
+
(file) =>
|
|
164
|
+
({
|
|
165
|
+
userId,
|
|
166
|
+
messageId,
|
|
167
|
+
toolPart,
|
|
168
|
+
name: file.file.name,
|
|
169
|
+
agentGeneratedId: file.id,
|
|
170
|
+
}) satisfies InferInsertModel<typeof capturedFiles>,
|
|
171
|
+
),
|
|
172
|
+
)
|
|
173
|
+
.execute()
|
|
174
|
+
.then((x) => x.map((y) => y.id));
|
|
175
|
+
|
|
176
|
+
await Promise.all(
|
|
177
|
+
result.map(
|
|
178
|
+
async (id, idx) =>
|
|
179
|
+
await storage.put(`captured_files/${id}`, files[idx]!.file.bytes),
|
|
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;
|
|
203
|
+
}
|
package/src/ai/fetch.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ResolvedCortexAgentConfig } from "../config.ts";
|
|
2
|
+
|
|
3
|
+
export async function fetchBackend(
|
|
4
|
+
path: string,
|
|
5
|
+
backendFetch: NonNullable<ResolvedCortexAgentConfig["backendFetch"]>,
|
|
6
|
+
token: string,
|
|
7
|
+
options?: RequestInit,
|
|
8
|
+
) {
|
|
9
|
+
let finalOptions = options;
|
|
10
|
+
|
|
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
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
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
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
2
|
+
import type { ResolvedCortexAgentConfig } from "../config.ts";
|
|
3
|
+
|
|
4
|
+
export function createModel(config: ResolvedCortexAgentConfig["model"]) {
|
|
5
|
+
const provider = createOpenAICompatible({
|
|
6
|
+
name: config.providerName ?? "default",
|
|
7
|
+
baseURL: config.baseURL,
|
|
8
|
+
apiKey: config.apiKey,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
return provider(config.modelName);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function createEmbeddingModel(
|
|
15
|
+
config: ResolvedCortexAgentConfig["embedding"],
|
|
16
|
+
) {
|
|
17
|
+
const provider = createOpenAICompatible({
|
|
18
|
+
name: "embedding",
|
|
19
|
+
baseURL: config.baseURL,
|
|
20
|
+
apiKey: config.apiKey,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return provider.textEmbeddingModel(config.modelName);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function streamToBase64(stream: ReadableStream<Uint8Array>) {
|
|
27
|
+
return new Response(stream)
|
|
28
|
+
.arrayBuffer()
|
|
29
|
+
.then((buffer) => Buffer.from(buffer).toString("base64"));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function tokenToUserId(token: string) {
|
|
33
|
+
return JSON.parse(Buffer.from(token.split(".")[1]!, "base64").toString())[
|
|
34
|
+
"sub"
|
|
35
|
+
] as string;
|
|
36
|
+
}
|