@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.
Files changed (239) hide show
  1. package/README.md +8 -3
  2. package/contracts/README.md +23 -0
  3. package/contracts/graph/embed.ts +50 -0
  4. package/{src → contracts}/graph/helpers.ts +4 -10
  5. package/{src → contracts}/graph/neo4j.ts +14 -27
  6. package/contracts/graph/schema.ts +65 -0
  7. package/{src → contracts}/graph/types.ts +2 -29
  8. package/contracts/graph.ts +36 -0
  9. package/contracts/runtime.ts +208 -0
  10. package/contracts/wire.ts +143 -0
  11. package/dist/contracts/graph/embed.d.ts +22 -0
  12. package/dist/{src → contracts}/graph/helpers.d.ts +14 -12
  13. package/dist/{src → contracts}/graph/neo4j.d.ts +5 -5
  14. package/dist/contracts/graph/schema.d.ts +62 -0
  15. package/dist/{src → contracts}/graph/types.d.ts +1 -1
  16. package/dist/contracts/graph.d.ts +14 -0
  17. package/dist/contracts/runtime.d.ts +278 -0
  18. package/dist/contracts/wire.d.ts +120 -0
  19. package/dist/index.d.ts +1 -1
  20. package/dist/src/lib/adapters/database/index.d.ts +61 -0
  21. package/dist/src/lib/adapters/database/message-content.d.ts +30 -0
  22. package/dist/src/lib/adapters/database/mssql/attachments.d.ts +100 -0
  23. package/dist/src/lib/adapters/database/mssql/client.d.ts +8 -0
  24. package/dist/src/lib/adapters/database/mssql/index.d.ts +194 -0
  25. package/dist/src/lib/adapters/database/mssql/llm-requests.d.ts +17 -0
  26. package/dist/src/lib/adapters/database/mssql/messages.d.ts +37 -0
  27. package/dist/src/lib/adapters/database/mssql/threads.d.ts +50 -0
  28. package/dist/src/lib/adapters/database/postgres/attachments.d.ts +113 -0
  29. package/dist/src/lib/adapters/database/postgres/client.d.ts +8 -0
  30. package/dist/src/lib/adapters/database/postgres/index.d.ts +207 -0
  31. package/dist/src/lib/adapters/database/postgres/llm-requests.d.ts +17 -0
  32. package/dist/src/lib/adapters/database/postgres/messages.d.ts +37 -0
  33. package/dist/src/lib/adapters/database/postgres/threads.d.ts +50 -0
  34. package/{src/adapters/storage.ts → dist/src/lib/adapters/storage/index.d.ts} +1 -1
  35. package/dist/src/{adapters → lib/adapters/storage}/minio.d.ts +1 -1
  36. package/dist/src/lib/ai/active-runs.d.ts +63 -0
  37. package/dist/src/lib/ai/attachments.d.ts +30 -0
  38. package/dist/src/lib/ai/backend-url.d.ts +16 -0
  39. package/dist/src/lib/ai/cc-runtime.d.ts +61 -0
  40. package/dist/src/lib/ai/commit-gate.d.ts +21 -0
  41. package/dist/src/{ai → lib/ai}/context/builder.d.ts +6 -7
  42. package/dist/src/lib/ai/context/compression-middleware.d.ts +15 -0
  43. package/dist/src/lib/ai/context/compressor.d.ts +7 -0
  44. package/dist/src/lib/ai/context/intra-turn-compressor.d.ts +24 -0
  45. package/dist/src/lib/ai/context/optimize-thread-context.d.ts +9 -0
  46. package/dist/src/lib/ai/context/summarizer.d.ts +5 -0
  47. package/dist/src/{ai → lib/ai}/context/token-estimator.d.ts +5 -8
  48. package/dist/src/{ai → lib/ai}/context/types.d.ts +2 -0
  49. package/dist/src/{ai → lib/ai}/fetch.d.ts +1 -1
  50. package/dist/src/lib/ai/finish-turn.d.ts +23 -0
  51. package/dist/src/lib/ai/helpers.d.ts +38 -0
  52. package/dist/src/lib/ai/index.d.ts +7 -0
  53. package/dist/src/lib/ai/inspector.d.ts +41 -0
  54. package/dist/src/lib/ai/interceptors/request-interceptor.d.ts +13 -0
  55. package/dist/src/{ai → lib/ai}/prompt.d.ts +5 -4
  56. package/dist/src/lib/ai/redis-runs.d.ts +41 -0
  57. package/dist/src/lib/ai/redis-stream-log.d.ts +39 -0
  58. package/dist/src/lib/ai/tools/execute-code.tool.d.ts +18 -0
  59. package/dist/src/lib/ai/tools/query-graph.tool.d.ts +13 -0
  60. package/dist/src/lib/ai/tools/read-attachment.tool.d.ts +21 -0
  61. package/dist/src/lib/ai/tools/search-common.d.ts +17 -0
  62. package/dist/src/lib/ai/tools/search-knowledge.tool.d.ts +15 -0
  63. package/dist/src/lib/ai/tools/search-services.tool.d.ts +12 -0
  64. package/dist/src/lib/ai/tools/search-tools.tool.d.ts +12 -0
  65. package/dist/src/lib/ai/turn-tools.d.ts +39 -0
  66. package/dist/src/lib/ai/vision/rasterize-pdf.d.ts +26 -0
  67. package/dist/src/lib/ai/vision/rasterize-pdf.worker.d.ts +1 -0
  68. package/dist/src/lib/ai/vision/vision-reader.d.ts +45 -0
  69. package/dist/src/lib/cc/client.d.ts +183 -0
  70. package/dist/src/lib/cc/config-cache.d.ts +20 -0
  71. package/dist/src/lib/cc/format.d.ts +23 -0
  72. package/dist/src/lib/cc/registry.d.ts +34 -0
  73. package/dist/src/lib/cc/types.d.ts +27 -0
  74. package/dist/src/lib/config.d.ts +170 -0
  75. package/dist/src/lib/db/drizzle.config.mssql.d.ts +2 -0
  76. package/dist/src/lib/db/drizzle.config.pg.d.ts +2 -0
  77. package/dist/src/lib/db/migrate.d.ts +7 -0
  78. package/dist/src/{db/schema.d.ts → lib/db/schema.mssql.d.ts} +236 -24
  79. package/dist/src/lib/db/schema.pg.d.ts +546 -0
  80. package/dist/src/{factory.d.ts → lib/factory.d.ts} +2 -12
  81. package/dist/src/lib/graph/index.d.ts +10 -0
  82. package/dist/src/{graph → lib/graph}/resolver.d.ts +2 -3
  83. package/dist/src/{index.d.ts → lib/index.d.ts} +3 -4
  84. package/dist/src/lib/redis.d.ts +19 -0
  85. package/dist/src/{routes → lib/routes}/chat.d.ts +1 -1
  86. package/dist/src/{routes → lib/routes}/files.d.ts +1 -1
  87. package/dist/src/lib/routes/owned-thread.d.ts +17 -0
  88. package/dist/src/{routes → lib/routes}/threads.d.ts +1 -1
  89. package/dist/src/{routes → lib/routes}/ws.d.ts +1 -1
  90. package/dist/src/lib/types.d.ts +58 -0
  91. package/dist/src/{ws → lib/ws}/connections.d.ts +2 -1
  92. package/index.ts +1 -1
  93. package/package.json +35 -15
  94. package/src/lib/adapters/database/index.ts +78 -0
  95. package/src/lib/adapters/database/message-content.ts +39 -0
  96. package/src/lib/adapters/database/mssql/attachments.ts +90 -0
  97. package/src/lib/adapters/database/mssql/client.ts +11 -0
  98. package/src/lib/adapters/database/mssql/index.ts +23 -0
  99. package/src/lib/adapters/database/mssql/llm-requests.ts +43 -0
  100. package/src/lib/adapters/database/mssql/messages.ts +92 -0
  101. package/src/lib/adapters/database/mssql/threads.ts +83 -0
  102. package/src/lib/adapters/database/postgres/attachments.ts +88 -0
  103. package/src/lib/adapters/database/postgres/client.ts +11 -0
  104. package/src/lib/adapters/database/postgres/index.ts +23 -0
  105. package/src/lib/adapters/database/postgres/llm-requests.ts +45 -0
  106. package/src/lib/adapters/database/postgres/messages.ts +92 -0
  107. package/src/lib/adapters/database/postgres/threads.ts +84 -0
  108. package/{dist/src/adapters/storage.d.ts → src/lib/adapters/storage/index.ts} +1 -1
  109. package/src/{adapters → lib/adapters/storage}/minio.ts +7 -6
  110. package/src/lib/ai/active-runs.ts +157 -0
  111. package/src/lib/ai/attachments.ts +144 -0
  112. package/src/lib/ai/backend-url.ts +27 -0
  113. package/src/lib/ai/cc-runtime.ts +63 -0
  114. package/src/lib/ai/commit-gate.ts +116 -0
  115. package/src/{ai → lib/ai}/context/builder.ts +12 -14
  116. package/src/lib/ai/context/compression-middleware.ts +44 -0
  117. package/src/lib/ai/context/compressor.ts +267 -0
  118. package/src/lib/ai/context/intra-turn-compressor.ts +181 -0
  119. package/src/lib/ai/context/optimize-thread-context.ts +50 -0
  120. package/src/lib/ai/context/summarizer.ts +48 -0
  121. package/src/lib/ai/context/token-estimator.ts +65 -0
  122. package/src/{ai → lib/ai}/context/types.ts +4 -1
  123. package/src/{ai → lib/ai}/fetch.ts +10 -5
  124. package/src/lib/ai/finish-turn.ts +75 -0
  125. package/src/lib/ai/helpers.ts +100 -0
  126. package/src/lib/ai/index.ts +408 -0
  127. package/src/lib/ai/inspector.ts +112 -0
  128. package/src/lib/ai/interceptors/request-interceptor.ts +89 -0
  129. package/src/lib/ai/prompt.ts +355 -0
  130. package/src/lib/ai/redis-runs.ts +167 -0
  131. package/src/lib/ai/redis-stream-log.ts +219 -0
  132. package/src/lib/ai/tools/execute-code.tool.ts +267 -0
  133. package/src/{ai → lib/ai}/tools/query-graph.tool.ts +7 -8
  134. package/src/lib/ai/tools/read-attachment.tool.ts +59 -0
  135. package/src/lib/ai/tools/search-common.ts +15 -0
  136. package/src/lib/ai/tools/search-knowledge.tool.ts +33 -0
  137. package/src/lib/ai/tools/search-services.tool.ts +31 -0
  138. package/src/lib/ai/tools/search-tools.tool.ts +29 -0
  139. package/src/lib/ai/turn-tools.ts +154 -0
  140. package/src/lib/ai/vision/rasterize-pdf.ts +69 -0
  141. package/src/lib/ai/vision/rasterize-pdf.worker.ts +81 -0
  142. package/src/lib/ai/vision/vision-reader.ts +130 -0
  143. package/src/{auth → lib/auth}/middleware.ts +12 -3
  144. package/src/lib/cc/client.ts +273 -0
  145. package/src/lib/cc/config-cache.ts +32 -0
  146. package/src/lib/cc/format.ts +96 -0
  147. package/src/lib/cc/registry.ts +62 -0
  148. package/src/lib/cc/types.ts +44 -0
  149. package/src/lib/config.ts +212 -0
  150. package/src/lib/db/drizzle.config.mssql.ts +9 -0
  151. package/src/lib/db/drizzle.config.pg.ts +9 -0
  152. package/src/lib/db/migrate.ts +56 -0
  153. package/src/{db/migrations/20260309012148_cloudy_maria_hill → lib/db/migrations/mssql/20260326231647_nice_speedball}/migration.sql +12 -0
  154. package/src/{db/migrations/20260309012148_cloudy_maria_hill → lib/db/migrations/mssql/20260326231647_nice_speedball}/snapshot.json +106 -1
  155. package/src/lib/db/migrations/mssql/20260729092726_talented_wilson_fisk/migration.sql +20 -0
  156. package/src/lib/db/migrations/mssql/20260729092726_talented_wilson_fisk/snapshot.json +497 -0
  157. package/src/lib/db/migrations/mssql/20260801003652_perpetual_blue_shield/migration.sql +1 -0
  158. package/src/lib/db/migrations/mssql/20260801003652_perpetual_blue_shield/snapshot.json +516 -0
  159. package/src/lib/db/migrations/mssql/20260812174642_user_id_text/migration.sql +2 -0
  160. package/src/lib/db/migrations/mssql/20260812174642_user_id_text/snapshot.json +516 -0
  161. package/src/lib/db/migrations/pg/20260801084117_thick_ben_grimm/migration.sql +53 -0
  162. package/src/lib/db/migrations/pg/20260801084117_thick_ben_grimm/snapshot.json +584 -0
  163. package/src/lib/db/migrations/pg/20260812174622_user_id_text/migration.sql +2 -0
  164. package/src/lib/db/migrations/pg/20260812174622_user_id_text/snapshot.json +584 -0
  165. package/src/lib/db/schema.mssql.ts +94 -0
  166. package/src/lib/db/schema.pg.ts +95 -0
  167. package/src/lib/factory.ts +127 -0
  168. package/src/lib/graph/index.ts +19 -0
  169. package/src/lib/graph/resolver.ts +387 -0
  170. package/src/{index.ts → lib/index.ts} +13 -5
  171. package/src/lib/redis.ts +44 -0
  172. package/src/lib/routes/chat.ts +115 -0
  173. package/src/lib/routes/files.ts +129 -0
  174. package/src/lib/routes/owned-thread.ts +17 -0
  175. package/src/{routes → lib/routes}/threads.ts +22 -31
  176. package/src/{routes → lib/routes}/ws.ts +9 -3
  177. package/src/lib/types.ts +76 -0
  178. package/src/{ws → lib/ws}/connections.ts +9 -1
  179. package/tsconfig.json +48 -0
  180. package/dist/src/adapters/database.d.ts +0 -30
  181. package/dist/src/adapters/mssql.d.ts +0 -3
  182. package/dist/src/ai/active-streams.d.ts +0 -14
  183. package/dist/src/ai/context/compressor.d.ts +0 -7
  184. package/dist/src/ai/context/index.d.ts +0 -15
  185. package/dist/src/ai/context/summarizer.d.ts +0 -5
  186. package/dist/src/ai/helpers.d.ts +0 -5
  187. package/dist/src/ai/index.d.ts +0 -4
  188. package/dist/src/ai/interceptors/request-interceptor.d.ts +0 -12
  189. package/dist/src/ai/tools/call-endpoint.tool.d.ts +0 -7
  190. package/dist/src/ai/tools/capture-files.tool.d.ts +0 -6
  191. package/dist/src/ai/tools/execute-code.tool.d.ts +0 -4
  192. package/dist/src/ai/tools/query-graph.tool.d.ts +0 -5
  193. package/dist/src/cli/extract-endpoints.d.ts +0 -6
  194. package/dist/src/config.d.ts +0 -165
  195. package/dist/src/db/migrate.d.ts +0 -1
  196. package/dist/src/graph/expand-domains.d.ts +0 -2
  197. package/dist/src/graph/generate-cypher.d.ts +0 -22
  198. package/dist/src/graph/index.d.ts +0 -11
  199. package/dist/src/graph/seed.d.ts +0 -19
  200. package/dist/src/graph/validate.d.ts +0 -2
  201. package/dist/src/routes/index.d.ts +0 -4
  202. package/dist/src/types.d.ts +0 -74
  203. package/dist/src/ws/events.d.ts +0 -39
  204. package/dist/src/ws/index.d.ts +0 -3
  205. package/dist/src/ws/notify.d.ts +0 -2
  206. package/src/adapters/database.ts +0 -29
  207. package/src/adapters/mssql.ts +0 -195
  208. package/src/ai/active-streams.ts +0 -123
  209. package/src/ai/context/compressor.ts +0 -47
  210. package/src/ai/context/index.ts +0 -75
  211. package/src/ai/context/summarizer.ts +0 -50
  212. package/src/ai/context/token-estimator.ts +0 -60
  213. package/src/ai/helpers.ts +0 -32
  214. package/src/ai/index.ts +0 -277
  215. package/src/ai/interceptors/request-interceptor.ts +0 -61
  216. package/src/ai/prompt.ts +0 -126
  217. package/src/ai/tools/call-endpoint.tool.ts +0 -89
  218. package/src/ai/tools/capture-files.tool.ts +0 -20
  219. package/src/ai/tools/execute-code.tool.ts +0 -153
  220. package/src/cli/extract-endpoints.ts +0 -550
  221. package/src/config.ts +0 -182
  222. package/src/db/migrate.ts +0 -21
  223. package/src/db/migrations/20260315000000_add_context_meta/migration.sql +0 -1
  224. package/src/db/schema.ts +0 -70
  225. package/src/factory.ts +0 -171
  226. package/src/graph/expand-domains.ts +0 -276
  227. package/src/graph/generate-cypher.ts +0 -192
  228. package/src/graph/index.ts +0 -47
  229. package/src/graph/resolver.ts +0 -357
  230. package/src/graph/seed.ts +0 -162
  231. package/src/graph/validate.ts +0 -78
  232. package/src/routes/chat.ts +0 -86
  233. package/src/routes/files.ts +0 -100
  234. package/src/routes/index.ts +0 -4
  235. package/src/types.ts +0 -74
  236. package/src/ws/events.ts +0 -39
  237. package/src/ws/index.ts +0 -11
  238. package/src/ws/notify.ts +0 -9
  239. /package/dist/src/{auth → lib/auth}/middleware.d.ts +0 -0
@@ -1,123 +0,0 @@
1
- type ActiveStream = {
2
- id: string;
3
- abortController: AbortController;
4
- buffer: string[];
5
- subscribers: Set<ReadableStreamDefaultController<string>>;
6
- isComplete: boolean;
7
- };
8
-
9
- const streams = new Map<string, ActiveStream>();
10
-
11
- export function registerStream(threadId: string, abortController: AbortController) {
12
- const existing = streams.get(threadId);
13
- if (existing) {
14
- existing.abortController.abort();
15
- for (const controller of existing.subscribers) {
16
- try {
17
- controller.close();
18
- } catch {
19
- /* already closed */
20
- }
21
- }
22
- streams.delete(threadId);
23
- }
24
-
25
- const entry: ActiveStream = {
26
- id: crypto.randomUUID(),
27
- abortController,
28
- buffer: [],
29
- subscribers: new Set(),
30
- isComplete: false,
31
- };
32
-
33
- streams.set(threadId, entry);
34
- return entry;
35
- }
36
-
37
- export function attachSseStream(threadId: string, sseStream: ReadableStream<string>) {
38
- const entry = streams.get(threadId);
39
- if (!entry) return;
40
-
41
- const reader = sseStream.getReader();
42
- (async function consume() {
43
- try {
44
- while (true) {
45
- const { done, value } = await reader.read();
46
- if (done) break;
47
- entry.buffer.push(value);
48
- for (const controller of entry.subscribers) {
49
- try {
50
- controller.enqueue(value);
51
- } catch {
52
- /* subscriber cancelled */
53
- }
54
- }
55
- }
56
- } catch {
57
- /* stream aborted or errored */
58
- } finally {
59
- entry.isComplete = true;
60
- for (const controller of entry.subscribers) {
61
- try {
62
- controller.close();
63
- } catch {
64
- /* already closed */
65
- }
66
- }
67
- }
68
- })();
69
- }
70
-
71
- export function subscribe(threadId: string) {
72
- const entry = streams.get(threadId);
73
- if (!entry) return null;
74
-
75
- let savedController: ReadableStreamDefaultController<string>;
76
-
77
- return new ReadableStream<string>({
78
- start(controller) {
79
- savedController = controller;
80
- controller.enqueue("[START]"); // XXX: we need to add this flag for an immediate response for the subscriber, otherwise we'd have to wait for the first token from the llm.
81
- for (const chunk of entry.buffer) {
82
- controller.enqueue(chunk);
83
- }
84
- if (entry.isComplete) {
85
- controller.close();
86
- return;
87
- }
88
- entry.subscribers.add(controller);
89
- },
90
- cancel() {
91
- entry.subscribers.delete(savedController);
92
- },
93
- });
94
- }
95
-
96
- export function abortStream(threadId: string) {
97
- const entry = streams.get(threadId);
98
- if (!entry) return false;
99
- queueMicrotask(() => entry.abortController.abort());
100
- return true;
101
- }
102
-
103
- export function isStreamRunning(threadId: string) {
104
- const entry = streams.get(threadId);
105
- return entry ? !entry.isComplete : false;
106
- }
107
-
108
- export function removeStream(threadId: string, streamId?: string) {
109
- const entry = streams.get(threadId);
110
- if (!entry) return;
111
- if (streamId && entry.id !== streamId) return;
112
-
113
- if (entry) {
114
- for (const controller of entry.subscribers) {
115
- try {
116
- controller.close();
117
- } catch {
118
- /* already closed */
119
- }
120
- }
121
- }
122
- streams.delete(threadId);
123
- }
@@ -1,47 +0,0 @@
1
- import type { UIMessage } from "ai";
2
- import { estimateTokens, CHARS_PER_TOKEN } from "./token-estimator.ts";
3
- import type { MessageMetadata } from "src/types.ts";
4
-
5
- /**
6
- * Returns a new array of messages with large tool outputs truncated
7
- * to `maxTokensPerResult`. Does not mutate the input messages.
8
- */
9
- export function compressToolResults(
10
- messages: UIMessage<MessageMetadata>[],
11
- maxTokensPerResult: number,
12
- ) {
13
- return messages.map((message) => {
14
- let hasLargeToolOutput = false;
15
-
16
- for (const part of message.parts) {
17
- if ("toolCallId" in part && "output" in part && part.output != null) {
18
- const outputTokens = estimateTokens(JSON.stringify(part.output));
19
- if (outputTokens > maxTokensPerResult) {
20
- hasLargeToolOutput = true;
21
- break;
22
- }
23
- }
24
- }
25
-
26
- if (!hasLargeToolOutput) return message;
27
-
28
- const compressedParts = message.parts.map((part) => {
29
- if (!("toolCallId" in part) || !("output" in part) || part.output == null) {
30
- return part;
31
- }
32
-
33
- const outputStr = JSON.stringify(part.output);
34
- const outputTokens = estimateTokens(outputStr);
35
-
36
- if (outputTokens <= maxTokensPerResult) return part;
37
-
38
- // Convert token budget back to character budget
39
- const charBudget = maxTokensPerResult * CHARS_PER_TOKEN;
40
- const truncatedOutput = outputStr.slice(0, charBudget) + "\n[...truncated]";
41
-
42
- return { ...part, output: truncatedOutput } as typeof part;
43
- });
44
-
45
- return { ...message, parts: compressedParts };
46
- });
47
- }
@@ -1,75 +0,0 @@
1
- export type { ContextConfig, ThreadContextMeta } from "./types.ts";
2
- export { DEFAULT_CONTEXT_CONFIG } from "./types.ts";
3
- export {
4
- CHARS_PER_TOKEN,
5
- estimateTokens,
6
- estimateMessageTokens,
7
- estimateMessagesTokens,
8
- } from "./token-estimator.ts";
9
- export { compressToolResults } from "./compressor.ts";
10
- export { summarizeMessages } from "./summarizer.ts";
11
- export { buildContextMessages, trimMessagesToFit } from "./builder.ts";
12
-
13
- import type { UIMessage } from "ai";
14
- import type { ResolvedCortexAgentConfig } from "../../config.ts";
15
- import type { Thread } from "../../types.ts";
16
- import type { ThreadContextMeta } from "./types.ts";
17
- import { estimateMessagesTokens } from "./token-estimator.ts";
18
- import { summarizeMessages } from "./summarizer.ts";
19
-
20
- /**
21
- * Post-response context optimization.
22
- * Called fire-and-forget from onFinish — summarizes older messages
23
- * when token usage exceeds the configured threshold.
24
- */
25
- export async function optimizeThreadContext(
26
- thread: Thread,
27
- messages: UIMessage[],
28
- config: ResolvedCortexAgentConfig,
29
- ) {
30
- const contextConfig = config.context;
31
-
32
- // 1. Estimate tokens for all messages
33
- const estimates = estimateMessagesTokens(messages);
34
- const totalEstimatedTokens = estimates.reduce((sum, e) => sum + e.tokens, 0);
35
-
36
- // 2. Check if over summarization threshold
37
- const threshold = contextConfig.maxContextTokens * contextConfig.summarizationThreshold;
38
-
39
- if (totalEstimatedTokens <= threshold) {
40
- // Update token estimate but skip summarization
41
- const meta: ThreadContextMeta = {
42
- summary: thread.contextMeta?.summary ?? null,
43
- summaryUpToMessageId: thread.contextMeta?.summaryUpToMessageId ?? null,
44
- totalEstimatedTokens,
45
- lastOptimizedAt: new Date().toISOString(),
46
- };
47
- await config.db.threads.updateContextMeta(thread.id, meta);
48
- return;
49
- }
50
-
51
- // 3. Keep the most recent messages unsummarized (they're the hot context)
52
- const recentCount = Math.min(contextConfig.recentMessagesToKeep, messages.length);
53
- const messagesToSummarize = messages.slice(0, messages.length - recentCount);
54
-
55
- if (messagesToSummarize.length === 0) return;
56
-
57
- // 4. Determine model config for summarization
58
- const modelConfig = contextConfig.summarizationModel ?? config.model;
59
-
60
- // 5. Generate summary incorporating any existing summary
61
- const existingSummary = thread.contextMeta?.summary ?? null;
62
-
63
- const summary = await summarizeMessages(messagesToSummarize, existingSummary, modelConfig);
64
-
65
- // 6. Update thread context meta
66
- const lastSummarizedMessage = messagesToSummarize.at(-1);
67
- const meta = {
68
- summary,
69
- summaryUpToMessageId: lastSummarizedMessage?.id ?? null,
70
- totalEstimatedTokens,
71
- lastOptimizedAt: new Date().toISOString(),
72
- } satisfies ThreadContextMeta;
73
-
74
- await config.db.threads.updateContextMeta(thread.id, meta);
75
- }
@@ -1,50 +0,0 @@
1
- import { generateText } from "ai";
2
- import type { UIMessage } from "ai";
3
- import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
4
- import type { ContextConfig } from "./types.ts";
5
-
6
- type SummarizationModelConfig = NonNullable<ContextConfig["summarizationModel"]>;
7
-
8
- export async function summarizeMessages(
9
- messages: UIMessage[],
10
- existingSummary: string | null,
11
- modelConfig: SummarizationModelConfig,
12
- ) {
13
- const provider = createOpenAICompatible({
14
- name: modelConfig.providerName ?? "summarization-provider",
15
- baseURL: modelConfig.baseURL,
16
- apiKey: modelConfig.apiKey,
17
- });
18
-
19
- const model = provider.chatModel(modelConfig.modelName);
20
-
21
- const conversationText = messages
22
- .map(function (msg) {
23
- const textParts = msg.parts
24
- .filter((p): p is Extract<typeof p, { type: "text" }> => p.type === "text")
25
- .map((p) => p.text);
26
- return `[${msg.role}]: ${textParts.join(" ")}`;
27
- })
28
- .join("\n");
29
-
30
- const summaryContext = existingSummary
31
- ? `\nPrior summary of earlier messages:\n${existingSummary}\n`
32
- : "";
33
-
34
- const { text } = await generateText({
35
- model,
36
- system: `You are a precise conversation summarizer. Produce a concise summary that preserves:
37
- - Key decisions and conclusions
38
- - Important entities (names, IDs, URLs, values)
39
- - User intent and goals
40
- - Any unresolved questions or next steps
41
-
42
- Maximum 500 tokens. Use bullet points. Do not include preamble.`,
43
- prompt: `${summaryContext}
44
- Summarize the following conversation:
45
-
46
- ${conversationText}`,
47
- });
48
-
49
- return text;
50
- }
@@ -1,60 +0,0 @@
1
- import type { UIMessage } from "ai";
2
-
3
- /** Average characters per token for English text. Used by the heuristic estimator. */
4
- export const CHARS_PER_TOKEN = 4;
5
-
6
- /**
7
- * Estimates token count for a string using the chars/4 heuristic.
8
- * ~10% accuracy for English text — good enough for budget decisions.
9
- */
10
- export function estimateTokens(text: string) {
11
- return Math.ceil(text.length / CHARS_PER_TOKEN);
12
- }
13
-
14
- /**
15
- * Estimates token count for a single UIMessage by walking its parts.
16
- */
17
- export function estimateMessageTokens(message: UIMessage) {
18
- let tokens = 0;
19
-
20
- for (const part of message.parts) {
21
- if (part.type === "text" || part.type === "reasoning") {
22
- tokens += estimateTokens(part.text);
23
- } else if ("toolCallId" in part) {
24
- // Tool invocation parts (tool-${name})
25
- if ("input" in part && part.input != null) {
26
- tokens += estimateTokens(JSON.stringify(part.input));
27
- }
28
- if ("output" in part && part.output != null) {
29
- tokens += estimateTokens(JSON.stringify(part.output));
30
- }
31
- } else if (part.type === "source-url") {
32
- tokens += estimateTokens(part.url);
33
- } else if (part.type === "source-document") {
34
- tokens += estimateTokens(part.title);
35
- } else if (part.type === "file") {
36
- tokens += estimateTokens(part.url);
37
- } else {
38
- // step-start, data parts, etc.
39
- tokens += 5;
40
- }
41
- }
42
-
43
- // Per-message overhead (role, metadata framing)
44
- tokens += 4;
45
-
46
- return tokens;
47
- }
48
-
49
- /**
50
- * Estimates token counts for an array of UIMessages.
51
- * Returns per-message estimates in the same order.
52
- */
53
- export function estimateMessagesTokens(messages: UIMessage[]) {
54
- return messages.map(function (message) {
55
- return {
56
- message,
57
- tokens: estimateMessageTokens(message),
58
- };
59
- });
60
- }
package/src/ai/helpers.ts DELETED
@@ -1,32 +0,0 @@
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(config: ResolvedCortexAgentConfig["embedding"]) {
15
- const provider = createOpenAICompatible({
16
- name: "embedding",
17
- baseURL: config.baseURL,
18
- apiKey: config.apiKey,
19
- });
20
-
21
- return provider.textEmbeddingModel(config.modelName);
22
- }
23
-
24
- export async function streamToBase64(stream: ReadableStream<Uint8Array>) {
25
- return new Response(stream)
26
- .arrayBuffer()
27
- .then((buffer) => Buffer.from(buffer).toString("base64"));
28
- }
29
-
30
- export function tokenToUserId(token: string) {
31
- return JSON.parse(Buffer.from(token.split(".")[1]!, "base64").toString())["sub"] as string;
32
- }
package/src/ai/index.ts DELETED
@@ -1,277 +0,0 @@
1
- import {
2
- type UIMessage,
3
- type ToolSet,
4
- convertToModelMessages,
5
- generateId,
6
- generateText,
7
- safeValidateUIMessages,
8
- stepCountIs,
9
- streamText,
10
- } from "ai";
11
- import { HTTPException } from "hono/http-exception";
12
- import type { ResolvedCortexAgentConfig } from "../config.ts";
13
- import type { MessageMetadata, Thread } from "../types.ts";
14
- import { createModel, createEmbeddingModel } from "./helpers.ts";
15
- import { buildSystemPrompt, resolveSession } from "./prompt.ts";
16
- import { createQueryGraphTool } from "./tools/query-graph.tool.ts";
17
- import { createCallEndpointTool } from "./tools/call-endpoint.tool.ts";
18
- import { createExecuteCodeTool } from "./tools/execute-code.tool.ts";
19
- import { captureFilesTool } from "./tools/capture-files.tool.ts";
20
- import { createRequestInterceptor } from "./interceptors/request-interceptor.ts";
21
- import { createNeo4jClient } from "../graph/neo4j.ts";
22
- import { resolveFromGraph } from "../graph/resolver.ts";
23
- import { notify } from "../ws/index.ts";
24
- import { buildContextMessages } from "./context/builder.ts";
25
- import { optimizeThreadContext, estimateTokens, trimMessagesToFit } from "./context/index.ts";
26
- import {
27
- registerStream,
28
- attachSseStream,
29
- removeStream,
30
- isStreamRunning,
31
- } from "./active-streams.ts";
32
- import { toThreadSummary } from "../types.ts";
33
-
34
- export async function stream(
35
- messages: unknown[],
36
- thread: Thread,
37
- userId: string,
38
- token: string,
39
- requestContext: Record<string, unknown>,
40
- config: ResolvedCortexAgentConfig,
41
- ) {
42
- const abortController = new AbortController();
43
-
44
- const validationResult = await safeValidateUIMessages({ messages });
45
- if (!validationResult.success) {
46
- throw new HTTPException(423, { message: "Invalid messages format" });
47
- }
48
-
49
- const validatedMessages = validationResult.data;
50
- await config.db.messages.upsert(thread.id, validatedMessages);
51
- const updatedThread = await config.db.threads.touch(thread.id);
52
-
53
- const activeStream = registerStream(thread.id, abortController);
54
-
55
- notify(userId, thread.agentId, {
56
- type: "thread:run-started",
57
- payload: { thread: toThreadSummary(updatedThread, true) },
58
- });
59
-
60
- // Extract prompt from the just-upserted messages (last user message)
61
- // so we can start graph resolution without waiting for history fetch
62
- const prompt =
63
- validatedMessages
64
- .filter((x) => x.role === "user")
65
- .at(-1)
66
- ?.parts.find((x) => x.type === "text")?.text ?? "";
67
-
68
- const model = createModel(config.model);
69
- const embeddingModel = createEmbeddingModel(config.embedding);
70
- const neo4j = createNeo4jClient(config.neo4j, embeddingModel);
71
-
72
- // Run independent operations in parallel
73
- const [contextResult, resolved, session] = await Promise.all([
74
- // Branch A: Load messages + build token-aware context window
75
- buildContextMessages(userId, thread, config.db, config.context),
76
- // Branch B: Resolve graph context (400-2000ms, the bottleneck)
77
- resolveFromGraph(prompt, {
78
- neo4j,
79
- embeddingModel,
80
- reranker: config.reranker,
81
- }),
82
- // Branch C: Resolve session data
83
- resolveSession(config, thread, token),
84
- ]);
85
-
86
- const { messages: contextMessages, allMessages: originalMessages } = contextResult;
87
-
88
- // Build tools
89
- const builtInTools: ToolSet = {
90
- captureFiles: captureFilesTool,
91
- queryGraph: createQueryGraphTool(neo4j),
92
- };
93
-
94
- if (config.backendFetch) {
95
- const backendFetchWithInterceptor = {
96
- ...config.backendFetch,
97
- transformRequestBody:
98
- config.backendFetch.transformRequestBody ??
99
- createRequestInterceptor(
100
- config.db,
101
- config.storage,
102
- config.backendFetch.interceptor,
103
- ),
104
- };
105
-
106
- builtInTools["callEndpoint"] = createCallEndpointTool(backendFetchWithInterceptor, token);
107
- builtInTools["executeCode"] = createExecuteCodeTool(backendFetchWithInterceptor, token);
108
- }
109
-
110
- const tools = {
111
- ...builtInTools,
112
- ...config.tools,
113
- } as ToolSet;
114
-
115
- const systemPrompt = await buildSystemPrompt(config, resolved, {
116
- session,
117
- requestContext,
118
- });
119
-
120
- // The context builder reserved a static token budget for the system prompt + tools.
121
- // Now that we have the actual values, verify the reserve was sufficient and trim
122
- // the oldest messages if it wasn't.
123
- const actualFixedCost = estimateTokens(systemPrompt) + estimateTokens(JSON.stringify(tools));
124
- const { reservedTokenBudget, maxContextTokens } = config.context;
125
- const trimmedMessages =
126
- actualFixedCost > reservedTokenBudget
127
- ? trimMessagesToFit(contextMessages, maxContextTokens - actualFixedCost)
128
- : contextMessages;
129
-
130
- const recentMessages = await convertToModelMessages(trimmedMessages);
131
-
132
- const result = streamText({
133
- model,
134
- system: systemPrompt,
135
- tools,
136
- messages: recentMessages,
137
- abortSignal: abortController.signal,
138
- stopWhen: stepCountIs(50),
139
- });
140
-
141
- return result.toUIMessageStreamResponse<UIMessage<MessageMetadata>>({
142
- originalMessages,
143
- generateMessageId: generateId,
144
- consumeSseStream: ({ stream: sseStream }) => {
145
- attachSseStream(thread.id, sseStream);
146
- },
147
- onFinish: async ({ messages: finishedMessages, isAborted }) => {
148
- if (isAborted) {
149
- finalizeAbortedMessages(finishedMessages);
150
- }
151
-
152
- // Record token usage (result promises reject on abort, so skip)
153
- const lastAssistantMessage = finishedMessages
154
- .filter((x) => x.role === "assistant")
155
- .at(-1);
156
- if (lastAssistantMessage && !isAborted) {
157
- const providerMetadata = await result.providerMetadata;
158
- const response = await result.response;
159
- const usage = await result.totalUsage;
160
- let metadata: MessageMetadata = {
161
- isAborted,
162
- providerMetadata,
163
- modelId: response.modelId,
164
- tokenUsage: {
165
- input: {
166
- noCache: usage.inputTokenDetails.noCacheTokens ?? 0,
167
- cacheRead: usage.inputTokenDetails.cacheReadTokens ?? 0,
168
- cacheWrite: usage.inputTokenDetails.cacheWriteTokens ?? 0,
169
- total: usage.inputTokens ?? 0,
170
- },
171
- output: {
172
- reasoning: usage.outputTokenDetails.reasoningTokens ?? 0,
173
- text: usage.outputTokenDetails.textTokens ?? 0,
174
- total: usage.outputTokens ?? 0,
175
- },
176
- total: usage.totalTokens ?? 0,
177
- },
178
- };
179
-
180
- lastAssistantMessage.metadata = metadata;
181
- } else if (lastAssistantMessage) {
182
- lastAssistantMessage.metadata = {
183
- isAborted,
184
- modelId: "",
185
- providerMetadata: undefined,
186
- };
187
- }
188
-
189
- const persistedThread = await config.db.threads.getById(userId, thread.id);
190
- if (!persistedThread) {
191
- removeStream(thread.id);
192
- return;
193
- }
194
-
195
- await config.db.messages.upsert(thread.id, finishedMessages);
196
- config.onStreamFinish?.({ messages: finishedMessages, isAborted });
197
-
198
- // XXX: we need to notify the user so that the client can
199
- // fetch new messages. The client can't fetch messages
200
- // immediately because messages may not have been saved yet.
201
- notify(userId, persistedThread.agentId, {
202
- type: "thread:messages-updated",
203
- payload: {
204
- threadId: thread.id,
205
- thread: toThreadSummary(persistedThread, false),
206
- },
207
- });
208
-
209
- setTimeout(() => removeStream(thread.id, activeStream.id), 10_000);
210
-
211
- // Fire-and-forget: optimize context for next request
212
- // Runs after response is delivered — no perceived latency
213
- try {
214
- optimizeThreadContext(thread, finishedMessages, config);
215
- } catch (err) {
216
- console.error("[cortex-server] Context optimization failed:", err);
217
- }
218
- },
219
- });
220
- }
221
-
222
- export async function generateTitle(
223
- threadId: string,
224
- prompt: string,
225
- userId: string,
226
- config: ResolvedCortexAgentConfig,
227
- ) {
228
- const model = createModel(config.model);
229
-
230
- const { output } = await generateText({
231
- model,
232
- system: `You are an expert in generating titles for threads of chats
233
- given the first message in that thread.
234
-
235
- When asked, only respond with the title without saying you're
236
- going to do so or any other speech. Spit out only the title.`,
237
- prompt: `Generate a title for this prompt: ${prompt}`,
238
- });
239
-
240
- await config.db.threads.updateTitle(threadId, output ?? "");
241
-
242
- const thread = await config.db.threads.getById(userId, threadId);
243
- if (!thread) return;
244
-
245
- notify(userId, thread.agentId, {
246
- type: "thread:title-updated",
247
- payload: { thread: toThreadSummary(thread, isStreamRunning(thread.id)) },
248
- });
249
- }
250
-
251
- const TERMINAL_TOOL_STATES = new Set(["output-available", "output-error", "output-denied"]);
252
-
253
- function finalizeAbortedMessages(messages: UIMessage[]) {
254
- const lastMessage = messages.at(-1);
255
- if (!lastMessage || lastMessage.role !== "assistant") return;
256
-
257
- lastMessage.parts = lastMessage.parts.map((part) => {
258
- if ((part.type === "text" || part.type === "reasoning") && part.state === "streaming") {
259
- return { ...part, state: "done" as const };
260
- }
261
-
262
- if ("toolCallId" in part && "state" in part) {
263
- const toolState = part.state;
264
- if (!TERMINAL_TOOL_STATES.has(toolState)) {
265
- const { approval: _, ...rest } = part;
266
- return {
267
- ...rest,
268
- state: "output-error" as const,
269
- errorText: "Generation was aborted",
270
- output: undefined,
271
- };
272
- }
273
- }
274
-
275
- return part;
276
- });
277
- }