@m6d/cortex-server 1.7.0 → 2.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 +8 -3
- package/contracts/README.md +23 -0
- package/contracts/graph/embed.ts +50 -0
- package/{src → contracts}/graph/helpers.ts +4 -10
- package/{src → contracts}/graph/neo4j.ts +14 -27
- package/contracts/graph/schema.ts +65 -0
- package/{src → contracts}/graph/types.ts +2 -29
- package/contracts/graph.ts +36 -0
- package/contracts/runtime.ts +208 -0
- package/contracts/wire.ts +143 -0
- package/dist/contracts/graph/embed.d.ts +22 -0
- package/dist/{src → contracts}/graph/helpers.d.ts +14 -12
- package/dist/{src → contracts}/graph/neo4j.d.ts +5 -5
- package/dist/contracts/graph/schema.d.ts +62 -0
- package/dist/{src → contracts}/graph/types.d.ts +1 -1
- package/dist/contracts/graph.d.ts +14 -0
- package/dist/contracts/runtime.d.ts +278 -0
- package/dist/contracts/wire.d.ts +120 -0
- package/dist/index.d.ts +1 -1
- package/dist/src/lib/adapters/database/index.d.ts +61 -0
- package/dist/src/lib/adapters/database/message-content.d.ts +30 -0
- package/dist/src/lib/adapters/database/mssql/attachments.d.ts +100 -0
- package/dist/src/lib/adapters/database/mssql/client.d.ts +8 -0
- package/dist/src/lib/adapters/database/mssql/index.d.ts +194 -0
- package/dist/src/lib/adapters/database/mssql/llm-requests.d.ts +17 -0
- package/dist/src/lib/adapters/database/mssql/messages.d.ts +37 -0
- package/dist/src/lib/adapters/database/mssql/threads.d.ts +50 -0
- package/dist/src/lib/adapters/database/postgres/attachments.d.ts +113 -0
- package/dist/src/lib/adapters/database/postgres/client.d.ts +8 -0
- package/dist/src/lib/adapters/database/postgres/index.d.ts +207 -0
- package/dist/src/lib/adapters/database/postgres/llm-requests.d.ts +17 -0
- package/dist/src/lib/adapters/database/postgres/messages.d.ts +37 -0
- package/dist/src/lib/adapters/database/postgres/threads.d.ts +50 -0
- package/{src/adapters/storage.ts → dist/src/lib/adapters/storage/index.d.ts} +1 -1
- package/dist/src/{adapters → lib/adapters/storage}/minio.d.ts +1 -1
- package/dist/src/lib/ai/active-runs.d.ts +63 -0
- package/dist/src/lib/ai/attachments.d.ts +30 -0
- package/dist/src/lib/ai/backend-url.d.ts +16 -0
- package/dist/src/lib/ai/cc-runtime.d.ts +61 -0
- package/dist/src/lib/ai/commit-gate.d.ts +21 -0
- package/dist/src/{ai → lib/ai}/context/builder.d.ts +6 -7
- package/dist/src/lib/ai/context/compression-middleware.d.ts +15 -0
- package/dist/src/lib/ai/context/compressor.d.ts +7 -0
- package/dist/src/lib/ai/context/intra-turn-compressor.d.ts +24 -0
- package/dist/src/lib/ai/context/optimize-thread-context.d.ts +9 -0
- package/dist/src/lib/ai/context/summarizer.d.ts +5 -0
- package/dist/src/{ai → lib/ai}/context/token-estimator.d.ts +5 -8
- package/dist/src/{ai → lib/ai}/context/types.d.ts +2 -0
- package/dist/src/{ai → lib/ai}/fetch.d.ts +1 -1
- package/dist/src/lib/ai/finish-turn.d.ts +23 -0
- package/dist/src/lib/ai/helpers.d.ts +38 -0
- package/dist/src/lib/ai/index.d.ts +7 -0
- package/dist/src/lib/ai/inspector.d.ts +41 -0
- package/dist/src/lib/ai/interceptors/request-interceptor.d.ts +13 -0
- package/dist/src/{ai → lib/ai}/prompt.d.ts +5 -4
- package/dist/src/lib/ai/redis-runs.d.ts +41 -0
- package/dist/src/lib/ai/redis-stream-log.d.ts +39 -0
- package/dist/src/lib/ai/tools/execute-code.tool.d.ts +18 -0
- package/dist/src/lib/ai/tools/query-graph.tool.d.ts +13 -0
- package/dist/src/lib/ai/tools/read-attachment.tool.d.ts +21 -0
- package/dist/src/lib/ai/tools/search-common.d.ts +17 -0
- package/dist/src/lib/ai/tools/search-knowledge.tool.d.ts +15 -0
- package/dist/src/lib/ai/tools/search-services.tool.d.ts +12 -0
- package/dist/src/lib/ai/tools/search-tools.tool.d.ts +12 -0
- package/dist/src/lib/ai/turn-tools.d.ts +39 -0
- package/dist/src/lib/ai/vision/rasterize-pdf.d.ts +26 -0
- package/dist/src/lib/ai/vision/rasterize-pdf.worker.d.ts +1 -0
- package/dist/src/lib/ai/vision/vision-reader.d.ts +45 -0
- package/dist/src/lib/cc/client.d.ts +183 -0
- package/dist/src/lib/cc/config-cache.d.ts +20 -0
- package/dist/src/lib/cc/format.d.ts +23 -0
- package/dist/src/lib/cc/registry.d.ts +34 -0
- package/dist/src/lib/cc/types.d.ts +27 -0
- package/dist/src/lib/config.d.ts +170 -0
- package/dist/src/lib/db/drizzle.config.mssql.d.ts +2 -0
- package/dist/src/lib/db/drizzle.config.pg.d.ts +2 -0
- package/dist/src/lib/db/migrate.d.ts +7 -0
- package/dist/src/{db/schema.d.ts → lib/db/schema.mssql.d.ts} +236 -24
- package/dist/src/lib/db/schema.pg.d.ts +546 -0
- package/dist/src/{factory.d.ts → lib/factory.d.ts} +2 -12
- package/dist/src/lib/graph/index.d.ts +10 -0
- package/dist/src/{graph → lib/graph}/resolver.d.ts +2 -3
- package/dist/src/{index.d.ts → lib/index.d.ts} +3 -4
- package/dist/src/lib/redis.d.ts +19 -0
- package/dist/src/{routes → lib/routes}/chat.d.ts +1 -1
- package/dist/src/{routes → lib/routes}/files.d.ts +1 -1
- package/dist/src/lib/routes/owned-thread.d.ts +17 -0
- package/dist/src/{routes → lib/routes}/threads.d.ts +1 -1
- package/dist/src/{routes → lib/routes}/ws.d.ts +1 -1
- package/dist/src/lib/types.d.ts +58 -0
- package/dist/src/{ws → lib/ws}/connections.d.ts +2 -1
- package/index.ts +1 -1
- package/package.json +35 -15
- package/src/lib/adapters/database/index.ts +78 -0
- package/src/lib/adapters/database/message-content.ts +39 -0
- package/src/lib/adapters/database/mssql/attachments.ts +90 -0
- package/src/lib/adapters/database/mssql/client.ts +11 -0
- package/src/lib/adapters/database/mssql/index.ts +23 -0
- package/src/lib/adapters/database/mssql/llm-requests.ts +43 -0
- package/src/lib/adapters/database/mssql/messages.ts +92 -0
- package/src/lib/adapters/database/mssql/threads.ts +83 -0
- package/src/lib/adapters/database/postgres/attachments.ts +88 -0
- package/src/lib/adapters/database/postgres/client.ts +11 -0
- package/src/lib/adapters/database/postgres/index.ts +23 -0
- package/src/lib/adapters/database/postgres/llm-requests.ts +45 -0
- package/src/lib/adapters/database/postgres/messages.ts +92 -0
- package/src/lib/adapters/database/postgres/threads.ts +84 -0
- package/{dist/src/adapters/storage.d.ts → src/lib/adapters/storage/index.ts} +1 -1
- package/src/{adapters → lib/adapters/storage}/minio.ts +7 -6
- package/src/lib/ai/active-runs.ts +157 -0
- package/src/lib/ai/attachments.ts +144 -0
- package/src/lib/ai/backend-url.ts +27 -0
- package/src/lib/ai/cc-runtime.ts +63 -0
- package/src/lib/ai/commit-gate.ts +116 -0
- package/src/{ai → lib/ai}/context/builder.ts +12 -14
- package/src/lib/ai/context/compression-middleware.ts +44 -0
- package/src/lib/ai/context/compressor.ts +267 -0
- package/src/lib/ai/context/intra-turn-compressor.ts +181 -0
- package/src/lib/ai/context/optimize-thread-context.ts +50 -0
- package/src/lib/ai/context/summarizer.ts +48 -0
- package/src/lib/ai/context/token-estimator.ts +65 -0
- package/src/{ai → lib/ai}/context/types.ts +4 -1
- package/src/{ai → lib/ai}/fetch.ts +10 -5
- package/src/lib/ai/finish-turn.ts +75 -0
- package/src/lib/ai/helpers.ts +100 -0
- package/src/lib/ai/index.ts +408 -0
- package/src/lib/ai/inspector.ts +112 -0
- package/src/lib/ai/interceptors/request-interceptor.ts +89 -0
- package/src/lib/ai/prompt.ts +355 -0
- package/src/lib/ai/redis-runs.ts +167 -0
- package/src/lib/ai/redis-stream-log.ts +219 -0
- package/src/lib/ai/tools/execute-code.tool.ts +267 -0
- package/src/{ai → lib/ai}/tools/query-graph.tool.ts +7 -8
- package/src/lib/ai/tools/read-attachment.tool.ts +59 -0
- package/src/lib/ai/tools/search-common.ts +15 -0
- package/src/lib/ai/tools/search-knowledge.tool.ts +33 -0
- package/src/lib/ai/tools/search-services.tool.ts +31 -0
- package/src/lib/ai/tools/search-tools.tool.ts +29 -0
- package/src/lib/ai/turn-tools.ts +154 -0
- package/src/lib/ai/vision/rasterize-pdf.ts +69 -0
- package/src/lib/ai/vision/rasterize-pdf.worker.ts +81 -0
- package/src/lib/ai/vision/vision-reader.ts +130 -0
- package/src/{auth → lib/auth}/middleware.ts +12 -3
- package/src/lib/cc/client.ts +273 -0
- package/src/lib/cc/config-cache.ts +32 -0
- package/src/lib/cc/format.ts +96 -0
- package/src/lib/cc/registry.ts +62 -0
- package/src/lib/cc/types.ts +44 -0
- package/src/lib/config.ts +212 -0
- package/src/lib/db/drizzle.config.mssql.ts +9 -0
- package/src/lib/db/drizzle.config.pg.ts +9 -0
- package/src/lib/db/migrate.ts +56 -0
- package/src/{db/migrations/20260309012148_cloudy_maria_hill → lib/db/migrations/mssql/20260326231647_nice_speedball}/migration.sql +12 -0
- package/src/{db/migrations/20260309012148_cloudy_maria_hill → lib/db/migrations/mssql/20260326231647_nice_speedball}/snapshot.json +106 -1
- package/src/lib/db/migrations/mssql/20260729092726_talented_wilson_fisk/migration.sql +20 -0
- package/src/lib/db/migrations/mssql/20260729092726_talented_wilson_fisk/snapshot.json +497 -0
- package/src/lib/db/migrations/mssql/20260801003652_perpetual_blue_shield/migration.sql +1 -0
- package/src/lib/db/migrations/mssql/20260801003652_perpetual_blue_shield/snapshot.json +516 -0
- package/src/lib/db/migrations/mssql/20260812174642_user_id_text/migration.sql +2 -0
- package/src/lib/db/migrations/mssql/20260812174642_user_id_text/snapshot.json +516 -0
- package/src/lib/db/migrations/pg/20260801084117_thick_ben_grimm/migration.sql +53 -0
- package/src/lib/db/migrations/pg/20260801084117_thick_ben_grimm/snapshot.json +584 -0
- package/src/lib/db/migrations/pg/20260812174622_user_id_text/migration.sql +2 -0
- package/src/lib/db/migrations/pg/20260812174622_user_id_text/snapshot.json +584 -0
- package/src/lib/db/schema.mssql.ts +94 -0
- package/src/lib/db/schema.pg.ts +95 -0
- package/src/lib/factory.ts +127 -0
- package/src/lib/graph/index.ts +19 -0
- package/src/lib/graph/resolver.ts +387 -0
- package/src/{index.ts → lib/index.ts} +13 -5
- package/src/lib/redis.ts +44 -0
- package/src/lib/routes/chat.ts +115 -0
- package/src/lib/routes/files.ts +129 -0
- package/src/lib/routes/owned-thread.ts +17 -0
- package/src/{routes → lib/routes}/threads.ts +22 -31
- package/src/{routes → lib/routes}/ws.ts +9 -3
- package/src/lib/types.ts +76 -0
- package/src/{ws → lib/ws}/connections.ts +9 -1
- package/tsconfig.json +48 -0
- package/dist/src/adapters/database.d.ts +0 -30
- package/dist/src/adapters/mssql.d.ts +0 -3
- package/dist/src/ai/active-streams.d.ts +0 -14
- package/dist/src/ai/context/compressor.d.ts +0 -7
- package/dist/src/ai/context/index.d.ts +0 -15
- package/dist/src/ai/context/summarizer.d.ts +0 -5
- package/dist/src/ai/helpers.d.ts +0 -5
- package/dist/src/ai/index.d.ts +0 -4
- package/dist/src/ai/interceptors/request-interceptor.d.ts +0 -12
- package/dist/src/ai/tools/call-endpoint.tool.d.ts +0 -7
- package/dist/src/ai/tools/capture-files.tool.d.ts +0 -6
- package/dist/src/ai/tools/execute-code.tool.d.ts +0 -4
- package/dist/src/ai/tools/query-graph.tool.d.ts +0 -5
- package/dist/src/cli/extract-endpoints.d.ts +0 -6
- package/dist/src/config.d.ts +0 -165
- package/dist/src/db/migrate.d.ts +0 -1
- package/dist/src/graph/expand-domains.d.ts +0 -2
- package/dist/src/graph/generate-cypher.d.ts +0 -22
- package/dist/src/graph/index.d.ts +0 -11
- package/dist/src/graph/seed.d.ts +0 -19
- package/dist/src/graph/validate.d.ts +0 -2
- package/dist/src/routes/index.d.ts +0 -4
- package/dist/src/types.d.ts +0 -74
- package/dist/src/ws/events.d.ts +0 -39
- package/dist/src/ws/index.d.ts +0 -3
- package/dist/src/ws/notify.d.ts +0 -2
- package/src/adapters/database.ts +0 -29
- package/src/adapters/mssql.ts +0 -195
- package/src/ai/active-streams.ts +0 -123
- package/src/ai/context/compressor.ts +0 -47
- package/src/ai/context/index.ts +0 -75
- package/src/ai/context/summarizer.ts +0 -50
- package/src/ai/context/token-estimator.ts +0 -60
- package/src/ai/helpers.ts +0 -32
- package/src/ai/index.ts +0 -277
- package/src/ai/interceptors/request-interceptor.ts +0 -61
- package/src/ai/prompt.ts +0 -126
- package/src/ai/tools/call-endpoint.tool.ts +0 -89
- package/src/ai/tools/capture-files.tool.ts +0 -20
- package/src/ai/tools/execute-code.tool.ts +0 -153
- package/src/cli/extract-endpoints.ts +0 -550
- package/src/config.ts +0 -182
- package/src/db/migrate.ts +0 -21
- package/src/db/migrations/20260315000000_add_context_meta/migration.sql +0 -1
- package/src/db/schema.ts +0 -70
- package/src/factory.ts +0 -171
- package/src/graph/expand-domains.ts +0 -276
- package/src/graph/generate-cypher.ts +0 -192
- package/src/graph/index.ts +0 -47
- package/src/graph/resolver.ts +0 -357
- package/src/graph/seed.ts +0 -162
- package/src/graph/validate.ts +0 -78
- package/src/routes/chat.ts +0 -86
- package/src/routes/files.ts +0 -100
- package/src/routes/index.ts +0 -4
- package/src/types.ts +0 -74
- package/src/ws/events.ts +0 -39
- package/src/ws/index.ts +0 -11
- package/src/ws/notify.ts +0 -9
- /package/dist/src/{auth → lib/auth}/middleware.d.ts +0 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { ATTACHMENT_STATUSES } from "@cortex/contracts/wire";
|
|
2
|
+
import {
|
|
3
|
+
index,
|
|
4
|
+
integer,
|
|
5
|
+
jsonb,
|
|
6
|
+
pgSchema,
|
|
7
|
+
text,
|
|
8
|
+
timestamp,
|
|
9
|
+
uuid,
|
|
10
|
+
varchar,
|
|
11
|
+
} from "drizzle-orm/pg-core";
|
|
12
|
+
import type { ChatMessage, TokenUsage } from "@/types";
|
|
13
|
+
import type { ThreadContextMeta } from "@/ai/context/types";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The Postgres twin of `schema.mssql.ts`. Drizzle types its schemas per dialect,
|
|
17
|
+
* so the two cannot be shared — `schema.parity.test.ts` asserts at compile time
|
|
18
|
+
* that they still infer the same rows, which is what the rest of the code sees.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export const aiSchema = pgSchema("ai");
|
|
22
|
+
|
|
23
|
+
const auditColumns = {
|
|
24
|
+
createdAt: timestamp({ mode: "date", withTimezone: true })
|
|
25
|
+
.$default(() => new Date())
|
|
26
|
+
.notNull(),
|
|
27
|
+
updatedAt: timestamp({ mode: "date", withTimezone: true })
|
|
28
|
+
.$default(() => new Date())
|
|
29
|
+
.$onUpdate(() => new Date())
|
|
30
|
+
.notNull(),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const threads = aiSchema.table("threads", {
|
|
34
|
+
id: uuid().defaultRandom().primaryKey(),
|
|
35
|
+
userId: varchar({ length: 255 }).notNull(),
|
|
36
|
+
agentId: varchar({ length: 128 }).notNull().default("default"),
|
|
37
|
+
title: varchar({ length: 256 }),
|
|
38
|
+
session: jsonb().$type<Record<string, unknown>>(),
|
|
39
|
+
contextMeta: jsonb().$type<ThreadContextMeta>(),
|
|
40
|
+
...auditColumns,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export const messages = aiSchema.table("messages", {
|
|
44
|
+
id: varchar({ length: 128 }).primaryKey(),
|
|
45
|
+
threadId: uuid()
|
|
46
|
+
.references(() => threads.id, { onDelete: "cascade" })
|
|
47
|
+
.notNull(),
|
|
48
|
+
text: text(),
|
|
49
|
+
content: jsonb().notNull().$type<ChatMessage>(),
|
|
50
|
+
/**
|
|
51
|
+
* Position within the insert batch that wrote the row. A turn commits
|
|
52
|
+
* several rows at once, so they share a `createdAt`; the transcript orders
|
|
53
|
+
* on (createdAt, ordinal) to keep them in the order the turn produced them.
|
|
54
|
+
*/
|
|
55
|
+
ordinal: integer().notNull().default(0),
|
|
56
|
+
role: varchar({
|
|
57
|
+
enum: ["system", "user", "assistant", "tool"],
|
|
58
|
+
length: 16,
|
|
59
|
+
}).notNull(),
|
|
60
|
+
...auditColumns,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
export const llmRequests = aiSchema.table("llm_requests", {
|
|
64
|
+
id: uuid().defaultRandom().primaryKey(),
|
|
65
|
+
messageId: varchar({ length: 128 })
|
|
66
|
+
.references(() => messages.id, { onDelete: "cascade" })
|
|
67
|
+
.notNull(),
|
|
68
|
+
stepNumber: integer().notNull(),
|
|
69
|
+
prompt: text().notNull(),
|
|
70
|
+
output: text(),
|
|
71
|
+
tokenUsage: jsonb().$type<TokenUsage>(),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
export const attachments = aiSchema.table(
|
|
75
|
+
"attachments",
|
|
76
|
+
{
|
|
77
|
+
id: uuid().defaultRandom().primaryKey(),
|
|
78
|
+
threadId: uuid()
|
|
79
|
+
.references(() => threads.id, { onDelete: "cascade" })
|
|
80
|
+
.notNull(),
|
|
81
|
+
userId: varchar({ length: 255 }).notNull(),
|
|
82
|
+
messageId: varchar({ length: 128 }).references(() => messages.id),
|
|
83
|
+
filename: varchar({ length: 2048 }).notNull(),
|
|
84
|
+
contentType: varchar({ length: 256 }).notNull(),
|
|
85
|
+
sizeBytes: integer().notNull(),
|
|
86
|
+
storageKey: varchar({ length: 512 }).notNull(),
|
|
87
|
+
status: varchar({
|
|
88
|
+
enum: ATTACHMENT_STATUSES,
|
|
89
|
+
length: 16,
|
|
90
|
+
}).notNull(),
|
|
91
|
+
description: text(),
|
|
92
|
+
...auditColumns,
|
|
93
|
+
},
|
|
94
|
+
(table) => [index("attachments_thread_id_index").on(table.threadId)],
|
|
95
|
+
);
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { websocket } from "hono/bun";
|
|
3
|
+
import type { CortexConfig, ResolvedCortexAgentConfig } from "./config";
|
|
4
|
+
import { DEFAULT_CONTEXT_CONFIG } from "./ai/context/types";
|
|
5
|
+
import type { ContextConfig } from "./ai/context/types";
|
|
6
|
+
import type { AppEnv, CortexAppEnv } from "./types";
|
|
7
|
+
import { createUserLoaderMiddleware } from "./auth/middleware";
|
|
8
|
+
import { createThreadRoutes } from "./routes/threads";
|
|
9
|
+
import { createChatRoutes } from "./routes/chat";
|
|
10
|
+
import { createFileRoutes } from "./routes/files";
|
|
11
|
+
import { createWsRoute } from "./routes/ws";
|
|
12
|
+
import { runMigrations } from "./db/migrate";
|
|
13
|
+
import { configureRunCoordination } from "./ai/active-runs";
|
|
14
|
+
import { createRedisRuns } from "./ai/redis-runs";
|
|
15
|
+
import { getRedisConnections } from "./redis";
|
|
16
|
+
import { createMssqlAdapter } from "./adapters/database/mssql/index";
|
|
17
|
+
import { createPostgresAdapter } from "./adapters/database/postgres/index";
|
|
18
|
+
import { createMinioAdapter } from "./adapters/storage/minio";
|
|
19
|
+
import { ControlCenterClient } from "./cc/client";
|
|
20
|
+
import { getControlCenterConfig } from "./cc/config-cache";
|
|
21
|
+
import { AGENT_SLUG_PATTERN } from "./cc/types";
|
|
22
|
+
|
|
23
|
+
export type CortexInstance = {
|
|
24
|
+
serve(): Promise<{
|
|
25
|
+
app: Hono<AppEnv>;
|
|
26
|
+
port: number;
|
|
27
|
+
fetch: Hono<AppEnv>["fetch"];
|
|
28
|
+
websocket: typeof websocket;
|
|
29
|
+
}>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export function createCortex(config: CortexConfig) {
|
|
33
|
+
return {
|
|
34
|
+
async serve() {
|
|
35
|
+
const storage = config.storage ? createMinioAdapter(config.storage) : undefined;
|
|
36
|
+
const createAdapter =
|
|
37
|
+
config.database.type === "postgres" ? createPostgresAdapter : createMssqlAdapter;
|
|
38
|
+
const db = createAdapter(config.database.connectionString, storage);
|
|
39
|
+
const ccClient = config.controlCenter
|
|
40
|
+
? new ControlCenterClient(config.controlCenter)
|
|
41
|
+
: null;
|
|
42
|
+
|
|
43
|
+
if (config.redis) {
|
|
44
|
+
configureRunCoordination(
|
|
45
|
+
createRedisRuns(getRedisConnections(config.redis.url), config.redis),
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
await runMigrations(config.database);
|
|
50
|
+
|
|
51
|
+
const app = new Hono<AppEnv>();
|
|
52
|
+
|
|
53
|
+
const userLoader = createUserLoaderMiddleware(config.auth);
|
|
54
|
+
app.use("*", userLoader);
|
|
55
|
+
|
|
56
|
+
// Compatibility WebSocket route
|
|
57
|
+
app.route("/", createWsRoute());
|
|
58
|
+
|
|
59
|
+
// Agent-scoped routes
|
|
60
|
+
const agentApp = new Hono<CortexAppEnv>();
|
|
61
|
+
|
|
62
|
+
agentApp.use("*", async function (c, next) {
|
|
63
|
+
const agentId = c.req.param("agentId") as string;
|
|
64
|
+
// An id with no local entry may still be an agent published on the
|
|
65
|
+
// Control Center: existence is checked against it (ETag-cached, so
|
|
66
|
+
// usually a 304), which makes publishing there the only allowlist.
|
|
67
|
+
let agentDef = config.agents?.[agentId];
|
|
68
|
+
if (!agentDef && ccClient && AGENT_SLUG_PATTERN.test(agentId)) {
|
|
69
|
+
const ccConfig = await getControlCenterConfig(ccClient, agentId);
|
|
70
|
+
if (ccConfig) agentDef = {};
|
|
71
|
+
}
|
|
72
|
+
if (!agentDef) return c.json({ error: "Agent not found" }, 404);
|
|
73
|
+
|
|
74
|
+
const resolvedContext: ContextConfig = {
|
|
75
|
+
...DEFAULT_CONTEXT_CONFIG,
|
|
76
|
+
...config.context,
|
|
77
|
+
...agentDef.context,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const resolvedConfig: ResolvedCortexAgentConfig = {
|
|
81
|
+
agentId,
|
|
82
|
+
db,
|
|
83
|
+
storage,
|
|
84
|
+
model: agentDef.model ?? config.model,
|
|
85
|
+
fastModel: agentDef.fastModel ?? config.fastModel,
|
|
86
|
+
vision: agentDef.vision ?? config.vision,
|
|
87
|
+
embedding: agentDef.embedding ?? config.embedding,
|
|
88
|
+
neo4j: agentDef.neo4j ?? config.neo4j,
|
|
89
|
+
reranker: agentDef.reranker ?? config.reranker,
|
|
90
|
+
controlCenter: config.controlCenter,
|
|
91
|
+
knowledge:
|
|
92
|
+
agentDef.knowledge === null
|
|
93
|
+
? undefined
|
|
94
|
+
: (agentDef.knowledge ?? config.knowledge),
|
|
95
|
+
systemPrompt: agentDef.systemPrompt,
|
|
96
|
+
tools: agentDef.tools,
|
|
97
|
+
backendFetch: agentDef.backendFetch,
|
|
98
|
+
loadSessionData: agentDef.loadSessionData,
|
|
99
|
+
resolveRequestContext: agentDef.resolveRequestContext,
|
|
100
|
+
onToolCall: agentDef.onToolCall,
|
|
101
|
+
onStreamFinish: agentDef.onStreamFinish,
|
|
102
|
+
context: resolvedContext,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
c.set("agentConfig", resolvedConfig);
|
|
106
|
+
c.set("agentId", agentId);
|
|
107
|
+
await next();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
agentApp.route("/", createThreadRoutes());
|
|
111
|
+
agentApp.route("/", createChatRoutes());
|
|
112
|
+
agentApp.route("/", createFileRoutes());
|
|
113
|
+
agentApp.route("/", createWsRoute({ useAgentParam: true }));
|
|
114
|
+
|
|
115
|
+
app.route("/agents/:agentId", agentApp);
|
|
116
|
+
|
|
117
|
+
const port = config.port ?? 3331;
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
app,
|
|
121
|
+
port,
|
|
122
|
+
fetch: app.fetch,
|
|
123
|
+
websocket,
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
} satisfies CortexInstance;
|
|
127
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The graph contract lives in `@cortex/contracts/graph` (internal/contracts),
|
|
3
|
+
* because `@m6d/cortex-cli` writes the graph this package reads and the two are
|
|
4
|
+
* installed separately (each vendors its own copy at pack time). It is
|
|
5
|
+
* re-exported here so authoring a project's domains still needs exactly one
|
|
6
|
+
* import path — nothing about the public surface moved.
|
|
7
|
+
*/
|
|
8
|
+
export * from "@cortex/contracts/graph";
|
|
9
|
+
|
|
10
|
+
// Graph resolver — the reader, and the one part of the graph that is this
|
|
11
|
+
// package's own.
|
|
12
|
+
export type {
|
|
13
|
+
RerankerConfig,
|
|
14
|
+
ResolverConfig,
|
|
15
|
+
ResolvedEndpoint,
|
|
16
|
+
ResolvedService,
|
|
17
|
+
ResolvedContext,
|
|
18
|
+
} from "./resolver";
|
|
19
|
+
export { resolveFromGraph } from "./resolver";
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-resolves relevant API endpoints from the Neo4j knowledge graph
|
|
3
|
+
* using vector similarity search on concept descriptions.
|
|
4
|
+
*
|
|
5
|
+
* All configuration is passed explicitly — no hardcoded model references
|
|
6
|
+
* or environment variables.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
GRAPH_SCHEMA,
|
|
11
|
+
GRAPH_SCHEMA_VERSION,
|
|
12
|
+
type ConceptDef,
|
|
13
|
+
type EmbedFn,
|
|
14
|
+
type Neo4jClient,
|
|
15
|
+
} from "@cortex/contracts/graph";
|
|
16
|
+
|
|
17
|
+
// The graph's vocabulary, aliased short so the Cypher below still reads as Cypher.
|
|
18
|
+
const { label: L, rel: R, prop: P } = GRAPH_SCHEMA;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Seed data, not schema: the concept a project names to expose its generic
|
|
22
|
+
* service-request endpoints, which is why it stays here rather than in
|
|
23
|
+
* `schema.ts` — the seeder writes whatever concepts a domain declares.
|
|
24
|
+
*/
|
|
25
|
+
const SERVICE_REQUEST = "ServiceRequest";
|
|
26
|
+
|
|
27
|
+
export type RerankerConfig = {
|
|
28
|
+
url: string;
|
|
29
|
+
apiKey: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type ResolverConfig = {
|
|
33
|
+
neo4j: Neo4jClient;
|
|
34
|
+
embed: EmbedFn;
|
|
35
|
+
reranker?: RerankerConfig;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
type EndpointDependency = {
|
|
39
|
+
depPath: string;
|
|
40
|
+
depMethod: string;
|
|
41
|
+
paramName: string;
|
|
42
|
+
fromField: string;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
type EndpointRelation = "read" | "write";
|
|
46
|
+
|
|
47
|
+
type RelationType = typeof R.queriedVia | typeof R.mutatedVia;
|
|
48
|
+
|
|
49
|
+
export type ResolvedEndpoint = {
|
|
50
|
+
concept: string;
|
|
51
|
+
relation: EndpointRelation;
|
|
52
|
+
name: string;
|
|
53
|
+
path: string;
|
|
54
|
+
method: string;
|
|
55
|
+
params: string;
|
|
56
|
+
body: string;
|
|
57
|
+
response: string;
|
|
58
|
+
dependencies: EndpointDependency[];
|
|
59
|
+
rules: string[];
|
|
60
|
+
metadata: string;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type ResolvedService = {
|
|
64
|
+
concept: string;
|
|
65
|
+
serviceName: string;
|
|
66
|
+
builtInId: string;
|
|
67
|
+
description: string;
|
|
68
|
+
rules: string[];
|
|
69
|
+
metadata: string;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export type ResolvedContext = {
|
|
73
|
+
readEndpoints: ResolvedEndpoint[];
|
|
74
|
+
writeEndpoints: ResolvedEndpoint[];
|
|
75
|
+
services: ResolvedService[];
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
type EndpointRow = {
|
|
79
|
+
concept: string;
|
|
80
|
+
relationType: RelationType;
|
|
81
|
+
name: string;
|
|
82
|
+
path: string;
|
|
83
|
+
method: string;
|
|
84
|
+
params: string | null;
|
|
85
|
+
body: string | null;
|
|
86
|
+
response: string | null;
|
|
87
|
+
metadata: string | null;
|
|
88
|
+
dependencies: (EndpointDependency | { depPath: null })[];
|
|
89
|
+
rules: (string | null)[];
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
type ServiceRow = {
|
|
93
|
+
concept: string;
|
|
94
|
+
serviceName: string;
|
|
95
|
+
builtInId: string;
|
|
96
|
+
description: string | null;
|
|
97
|
+
metadata: string | null;
|
|
98
|
+
rules: (string | null)[];
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Cleared only by a restart, which is also the only thing that can change which
|
|
103
|
+
* schema this process reads with. A re-seed under a running server is the case
|
|
104
|
+
* this misses, and re-seeding at the same version is the overwhelmingly common one.
|
|
105
|
+
*/
|
|
106
|
+
let versionChecked = false;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* `@m6d/cortex-cli` stamps the schema version it seeded onto the graph; this reads
|
|
110
|
+
* it back before any Cypher below runs. Lazily, on the first resolve rather than at
|
|
111
|
+
* boot, so a Neo4j that happens to be down cannot stop `serve()`.
|
|
112
|
+
*/
|
|
113
|
+
async function assertGraphVersion(neo4j: Neo4jClient) {
|
|
114
|
+
if (versionChecked) return;
|
|
115
|
+
|
|
116
|
+
const rows: unknown = await neo4j
|
|
117
|
+
.query(
|
|
118
|
+
`OPTIONAL MATCH (g:${L.marker}) WITH g LIMIT 1
|
|
119
|
+
OPTIONAL MATCH (c:${L.concept})
|
|
120
|
+
RETURN g.${P.version} AS version, count(c) AS concepts`,
|
|
121
|
+
)
|
|
122
|
+
.then(JSON.parse)
|
|
123
|
+
.catch(() => undefined);
|
|
124
|
+
|
|
125
|
+
// An unreachable graph answers with an error object rather than a row array.
|
|
126
|
+
// That is the resolve's own failure to report, not schema skew.
|
|
127
|
+
if (!Array.isArray(rows)) return;
|
|
128
|
+
|
|
129
|
+
// OPTIONAL MATCH answers a missing marker with `null`, not with a missing row.
|
|
130
|
+
const { version: stamped, concepts } =
|
|
131
|
+
(rows as { version?: number | null; concepts?: number }[])[0] ?? {};
|
|
132
|
+
if (stamped === GRAPH_SCHEMA_VERSION) {
|
|
133
|
+
versionChecked = true;
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// No stamp over an empty graph is a project mid-setup (Neo4j configured, seed
|
|
138
|
+
// not yet run), not skew: resolve as the empty context it is. Not cached, so
|
|
139
|
+
// the stamp is validated as soon as a seed lands.
|
|
140
|
+
if (stamped == null && (concepts ?? 0) === 0) return;
|
|
141
|
+
|
|
142
|
+
throw new Error(
|
|
143
|
+
[
|
|
144
|
+
stamped == null
|
|
145
|
+
? `The Neo4j graph holds data but no schema stamp, and this server reads graph schema v${GRAPH_SCHEMA_VERSION}.`
|
|
146
|
+
: `The Neo4j graph was written with graph schema v${stamped}, and this server reads v${GRAPH_SCHEMA_VERSION}.`,
|
|
147
|
+
"Writer and reader disagree on the labels, relationships and property names, so retrieval would return empty or wrong results instead of failing.",
|
|
148
|
+
"Re-seed the graph with `bunx @m6d/cortex-cli graph seed`, or install the @m6d/cortex-server that matches the graph you have.",
|
|
149
|
+
].join(" "),
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function resolveFromGraph(prompt: string, config: ResolverConfig) {
|
|
154
|
+
// Before the first query and outside the catch below: skew is invisible to
|
|
155
|
+
// every check further down, which is the whole reason it is checked here.
|
|
156
|
+
await assertGraphVersion(config.neo4j);
|
|
157
|
+
|
|
158
|
+
const empty: ResolvedContext = {
|
|
159
|
+
readEndpoints: [],
|
|
160
|
+
writeEndpoints: [],
|
|
161
|
+
services: [],
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
const embedding = (await config.embed([prompt]))[0]!;
|
|
166
|
+
|
|
167
|
+
const conceptsRaw = await config.neo4j.query(
|
|
168
|
+
`CALL db.index.vector.queryNodes('${GRAPH_SCHEMA.conceptEmbeddingsIndex}', 10, $embedding)
|
|
169
|
+
YIELD node, score
|
|
170
|
+
WHERE score > 0.3
|
|
171
|
+
RETURN node.${P.name} AS name, node.${P.description} AS description, score
|
|
172
|
+
ORDER BY score DESC`,
|
|
173
|
+
{ embedding },
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
let concepts = JSON.parse(conceptsRaw) as (Pick<ConceptDef, "name" | "description"> & {
|
|
177
|
+
score: number;
|
|
178
|
+
})[];
|
|
179
|
+
|
|
180
|
+
if (!Array.isArray(concepts) || concepts.length === 0) {
|
|
181
|
+
return empty;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (config.reranker) {
|
|
185
|
+
concepts = await computeRelevanceScores(prompt, concepts, config.reranker);
|
|
186
|
+
} else {
|
|
187
|
+
concepts = concepts.slice(0, 3);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const conceptNames = concepts.map((c) => c.name);
|
|
191
|
+
|
|
192
|
+
const endpointsRaw = await config.neo4j.query(
|
|
193
|
+
`UNWIND $names AS conceptName
|
|
194
|
+
MATCH (c:${L.concept} {${P.name}: conceptName})-[:${R.specializes}*0..3]->(ancestor:${L.concept})
|
|
195
|
+
WITH conceptName, collect(DISTINCT ancestor) AS family
|
|
196
|
+
UNWIND family AS related
|
|
197
|
+
MATCH (related)-[rel:${R.queriedVia}|${R.mutatedVia}]->(e:${L.endpoint})
|
|
198
|
+
WITH conceptName, related, rel, e
|
|
199
|
+
OPTIONAL MATCH (e)-[d:${R.dependsOn}]->(dep:${L.endpoint})
|
|
200
|
+
WITH conceptName, related, rel, e,
|
|
201
|
+
collect(DISTINCT {
|
|
202
|
+
depPath: dep.${P.path},
|
|
203
|
+
depMethod: dep.${P.method},
|
|
204
|
+
paramName: d.${P.paramName},
|
|
205
|
+
fromField: d.${P.fromField}
|
|
206
|
+
}) AS dependencies
|
|
207
|
+
OPTIONAL MATCH (rEndpoint:${L.rule})-[:${R.governs}]->(e)
|
|
208
|
+
OPTIONAL MATCH (rConcept:${L.rule})-[:${R.governs}]->(related)
|
|
209
|
+
RETURN conceptName AS concept,
|
|
210
|
+
type(rel) AS relationType,
|
|
211
|
+
e.${P.name} AS name,
|
|
212
|
+
e.${P.path} AS path,
|
|
213
|
+
e.${P.method} AS method,
|
|
214
|
+
e.${P.params} AS params,
|
|
215
|
+
e.${P.body} AS body,
|
|
216
|
+
e.${P.response} AS response,
|
|
217
|
+
e.${P.metadata} AS metadata,
|
|
218
|
+
dependencies,
|
|
219
|
+
collect(DISTINCT rEndpoint.${P.description}) +
|
|
220
|
+
collect(DISTINCT rConcept.${P.description}) AS rules`,
|
|
221
|
+
{ names: conceptNames },
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
const endpointRows = asArray<EndpointRow>(JSON.parse(endpointsRaw));
|
|
225
|
+
const allEndpoints = dedupeEndpoints(endpointRows.map(toResolvedEndpoint));
|
|
226
|
+
|
|
227
|
+
const servicesRaw = await config.neo4j.query(
|
|
228
|
+
`UNWIND $names AS conceptName
|
|
229
|
+
MATCH (c:${L.concept} {${P.name}: conceptName})-[:${R.specializes}*0..3]->(ancestor:${L.concept})
|
|
230
|
+
WITH conceptName, collect(DISTINCT ancestor) AS family
|
|
231
|
+
UNWIND family AS related
|
|
232
|
+
MATCH (related)-[:${R.requestedVia}]->(svc:${L.service})
|
|
233
|
+
WITH conceptName, related, svc
|
|
234
|
+
OPTIONAL MATCH (rService:${L.rule})-[:${R.governs}]->(svc)
|
|
235
|
+
OPTIONAL MATCH (rConcept:${L.rule})-[:${R.governs}]->(related)
|
|
236
|
+
RETURN conceptName AS concept,
|
|
237
|
+
svc.${P.name} AS serviceName,
|
|
238
|
+
svc.${P.builtInId} AS builtInId,
|
|
239
|
+
svc.${P.description} AS description,
|
|
240
|
+
svc.${P.metadata} AS metadata,
|
|
241
|
+
collect(DISTINCT rService.${P.description}) +
|
|
242
|
+
collect(DISTINCT rConcept.${P.description}) AS rules`,
|
|
243
|
+
{ names: conceptNames },
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
const services = dedupeServices(
|
|
247
|
+
asArray<ServiceRow>(JSON.parse(servicesRaw)).map(function (row) {
|
|
248
|
+
return {
|
|
249
|
+
concept: row.concept,
|
|
250
|
+
serviceName: row.serviceName,
|
|
251
|
+
builtInId: row.builtInId,
|
|
252
|
+
description: row.description ?? "",
|
|
253
|
+
metadata: row.metadata ?? "{}",
|
|
254
|
+
rules: row.rules.filter((rule): rule is string => Boolean(rule)),
|
|
255
|
+
};
|
|
256
|
+
}),
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
// Service-to-Servicing bridge: if any services were resolved, pull in
|
|
260
|
+
// the generic Servicing endpoints
|
|
261
|
+
let servicingEndpoints: ResolvedEndpoint[] = [];
|
|
262
|
+
if (services.length > 0) {
|
|
263
|
+
const servicingRaw = await config.neo4j.query(
|
|
264
|
+
`MATCH (c:${L.concept} {${P.name}: '${SERVICE_REQUEST}'})-[rel:${R.queriedVia}|${R.mutatedVia}]->(e:${L.endpoint})
|
|
265
|
+
OPTIONAL MATCH (e)-[d:${R.dependsOn}]->(dep:${L.endpoint})
|
|
266
|
+
WITH c, rel, e,
|
|
267
|
+
collect(DISTINCT {
|
|
268
|
+
depPath: dep.${P.path},
|
|
269
|
+
depMethod: dep.${P.method},
|
|
270
|
+
paramName: d.${P.paramName},
|
|
271
|
+
fromField: d.${P.fromField}
|
|
272
|
+
}) AS dependencies
|
|
273
|
+
OPTIONAL MATCH (rEndpoint:${L.rule})-[:${R.governs}]->(e)
|
|
274
|
+
OPTIONAL MATCH (rConcept:${L.rule})-[:${R.governs}]->(c)
|
|
275
|
+
RETURN '${SERVICE_REQUEST}' AS concept,
|
|
276
|
+
type(rel) AS relationType,
|
|
277
|
+
e.${P.name} AS name,
|
|
278
|
+
e.${P.path} AS path,
|
|
279
|
+
e.${P.method} AS method,
|
|
280
|
+
e.${P.params} AS params,
|
|
281
|
+
e.${P.body} AS body,
|
|
282
|
+
e.${P.response} AS response,
|
|
283
|
+
e.${P.metadata} AS metadata,
|
|
284
|
+
dependencies,
|
|
285
|
+
collect(DISTINCT rEndpoint.${P.description}) +
|
|
286
|
+
collect(DISTINCT rConcept.${P.description}) AS rules`,
|
|
287
|
+
{},
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
servicingEndpoints = dedupeEndpoints(
|
|
291
|
+
asArray<EndpointRow>(JSON.parse(servicingRaw)).map(toResolvedEndpoint),
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const combinedEndpoints = dedupeEndpoints([...allEndpoints, ...servicingEndpoints]);
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
readEndpoints: combinedEndpoints.filter((ep) => ep.relation === "read"),
|
|
299
|
+
writeEndpoints: combinedEndpoints.filter((ep) => ep.relation === "write"),
|
|
300
|
+
services,
|
|
301
|
+
} satisfies ResolvedContext;
|
|
302
|
+
} catch (error) {
|
|
303
|
+
console.error("Graph resolution failed:", error);
|
|
304
|
+
return empty;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function asArray<T>(value: unknown) {
|
|
309
|
+
return Array.isArray(value) ? (value as T[]) : ([] as T[]);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** Maps a Cypher row to an endpoint. The string defaults stand in for columns
|
|
313
|
+
* Neo4j returns as null, which downstream JSON.parse calls would choke on. */
|
|
314
|
+
function toResolvedEndpoint(row: EndpointRow) {
|
|
315
|
+
return {
|
|
316
|
+
concept: row.concept,
|
|
317
|
+
relation: row.relationType === R.mutatedVia ? ("write" as const) : ("read" as const),
|
|
318
|
+
name: row.name,
|
|
319
|
+
path: row.path,
|
|
320
|
+
method: row.method,
|
|
321
|
+
params: row.params ?? "[]",
|
|
322
|
+
body: row.body ?? "[]",
|
|
323
|
+
response: row.response ?? "[]",
|
|
324
|
+
metadata: row.metadata ?? "{}",
|
|
325
|
+
dependencies: row.dependencies.filter(
|
|
326
|
+
(dep): dep is EndpointDependency => dep.depPath != null,
|
|
327
|
+
),
|
|
328
|
+
rules: row.rules.filter((rule): rule is string => Boolean(rule)),
|
|
329
|
+
} satisfies ResolvedEndpoint;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function dedupeEndpoints(endpoints: ResolvedEndpoint[]) {
|
|
333
|
+
const seen = new Set<string>();
|
|
334
|
+
const deduped: ResolvedEndpoint[] = [];
|
|
335
|
+
|
|
336
|
+
for (const ep of endpoints) {
|
|
337
|
+
const key = `${ep.relation}|${ep.concept}|${ep.method}|${ep.path}`;
|
|
338
|
+
if (seen.has(key)) continue;
|
|
339
|
+
seen.add(key);
|
|
340
|
+
deduped.push(ep);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return deduped;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function dedupeServices(services: ResolvedService[]) {
|
|
347
|
+
const seen = new Set<string>();
|
|
348
|
+
const deduped: ResolvedService[] = [];
|
|
349
|
+
|
|
350
|
+
for (const svc of services) {
|
|
351
|
+
const key = `${svc.concept}|${svc.builtInId}`;
|
|
352
|
+
if (seen.has(key)) continue;
|
|
353
|
+
seen.add(key);
|
|
354
|
+
deduped.push(svc);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return deduped;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async function computeRelevanceScores(
|
|
361
|
+
prompt: string,
|
|
362
|
+
concepts: (Pick<ConceptDef, "name" | "description"> & { score: number })[],
|
|
363
|
+
reranker: RerankerConfig,
|
|
364
|
+
) {
|
|
365
|
+
const response = await fetch(reranker.url, {
|
|
366
|
+
method: "POST",
|
|
367
|
+
headers: {
|
|
368
|
+
Authorization: `Bearer ${reranker.apiKey}`,
|
|
369
|
+
"Content-Type": "application/json",
|
|
370
|
+
},
|
|
371
|
+
body: JSON.stringify({
|
|
372
|
+
queries: [prompt],
|
|
373
|
+
documents: concepts.map((x) => x.description),
|
|
374
|
+
}),
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
const data = (await response.json()) as { scores: number[] };
|
|
378
|
+
const scores = data.scores;
|
|
379
|
+
|
|
380
|
+
const scoredConcepts = concepts.map((c, idx) => ({
|
|
381
|
+
...c,
|
|
382
|
+
score: scores[idx]!,
|
|
383
|
+
}));
|
|
384
|
+
|
|
385
|
+
scoredConcepts.sort((a, b) => b.score - a.score);
|
|
386
|
+
return scoredConcepts.slice(0, 3);
|
|
387
|
+
}
|
|
@@ -2,13 +2,21 @@
|
|
|
2
2
|
export type {
|
|
3
3
|
CortexConfig,
|
|
4
4
|
CortexAgentDefinition,
|
|
5
|
+
ControlCenterConfig,
|
|
5
6
|
KnowledgeConfig,
|
|
6
7
|
DatabaseConfig,
|
|
8
|
+
RedisConfig,
|
|
7
9
|
StorageConfig,
|
|
8
10
|
PromptContext,
|
|
11
|
+
ToolContext,
|
|
12
|
+
ToolSet,
|
|
13
|
+
ToolSetFactory,
|
|
9
14
|
} from "./config";
|
|
10
15
|
export { defineAgent } from "./config";
|
|
11
16
|
|
|
17
|
+
// Provider shape, so a consumer can name what it passes to the model helpers.
|
|
18
|
+
export type { ProviderConfig } from "./ai/helpers";
|
|
19
|
+
|
|
12
20
|
export type { ContextConfig, ThreadContextMeta } from "./ai/context/types";
|
|
13
21
|
|
|
14
22
|
// Types consumers may need
|
|
@@ -26,13 +34,13 @@ export type {
|
|
|
26
34
|
export { createRequestInterceptor } from "./ai/interceptors/request-interceptor";
|
|
27
35
|
|
|
28
36
|
// Tools (consumers may register custom tools or use built-in ones)
|
|
29
|
-
export { captureFilesTool } from "./ai/tools/capture-files.tool";
|
|
30
37
|
export { createQueryGraphTool } from "./ai/tools/query-graph.tool";
|
|
31
|
-
export { createCallEndpointTool } from "./ai/tools/call-endpoint.tool";
|
|
32
38
|
export { createExecuteCodeTool } from "./ai/tools/execute-code.tool";
|
|
33
39
|
|
|
34
|
-
// Graph (consumers may use independently)
|
|
40
|
+
// Graph (consumers may use independently). The contract itself lives in
|
|
41
|
+
// @cortex/contracts/graph — see ./graph/index — and is re-exported through here.
|
|
35
42
|
export * from "./graph/index";
|
|
36
43
|
|
|
37
|
-
//
|
|
38
|
-
|
|
44
|
+
// The wire contract, re-exported so consumers get it without a second import path.
|
|
45
|
+
// Its home is @cortex/contracts/wire, which the client SDKs compile in too.
|
|
46
|
+
export * from "@cortex/contracts/wire";
|