@m6d/cortex-server 1.6.0 → 1.8.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 (254) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/src/{adapters → lib/adapters}/database.d.ts +16 -1
  3. package/dist/src/lib/ai/context/compressor.d.ts +18 -0
  4. package/dist/src/{ai → lib/ai}/context/index.d.ts +2 -1
  5. package/dist/src/lib/ai/context/intra-turn-compressor.d.ts +24 -0
  6. package/dist/src/{ai → lib/ai}/context/types.d.ts +2 -0
  7. package/dist/src/lib/config.d.ts +153 -0
  8. package/dist/src/{db → lib/db}/schema.d.ts +104 -1
  9. package/dist/src/{index.d.ts → lib/index.d.ts} +2 -2
  10. package/dist/src/{types.d.ts → lib/types.d.ts} +15 -14
  11. package/index.ts +1 -1
  12. package/package.json +8 -3
  13. package/src/{adapters → lib/adapters}/database.ts +20 -1
  14. package/src/{adapters → lib/adapters}/minio.ts +2 -1
  15. package/src/{adapters → lib/adapters}/mssql.ts +38 -2
  16. package/src/lib/ai/context/compressor.ts +259 -0
  17. package/src/{ai → lib/ai}/context/index.ts +6 -1
  18. package/src/lib/ai/context/intra-turn-compressor.ts +264 -0
  19. package/src/{ai → lib/ai}/context/types.ts +4 -1
  20. package/src/{ai → lib/ai}/index.ts +114 -13
  21. package/src/lib/ai/prompt.ts +354 -0
  22. package/src/{ai → lib/ai}/tools/execute-code.tool.ts +5 -2
  23. package/src/{auth → lib/auth}/middleware.ts +7 -0
  24. package/src/lib/config.ts +181 -0
  25. package/src/{db/migrations/20260309012148_cloudy_maria_hill → lib/db/migrations/20260326231647_nice_speedball}/migration.sql +12 -0
  26. package/src/{db/migrations/20260309012148_cloudy_maria_hill → lib/db/migrations/20260326231647_nice_speedball}/snapshot.json +106 -1
  27. package/src/{db → lib/db}/schema.ts +15 -2
  28. package/src/{factory.ts → lib/factory.ts} +1 -0
  29. package/src/{index.ts → lib/index.ts} +3 -1
  30. package/src/{routes → lib/routes}/files.ts +9 -2
  31. package/src/{routes → lib/routes}/threads.ts +8 -1
  32. package/src/{types.ts → lib/types.ts} +16 -15
  33. package/src/sample/db/client.ts +9 -0
  34. package/src/sample/db/migrations/20260411023125_rich_purple_man/migration.sql +5 -0
  35. package/src/sample/db/migrations/20260411023125_rich_purple_man/snapshot.json +50 -0
  36. package/src/sample/db/schema.ts +10 -0
  37. package/src/sample/domains/account/concepts/session.concept.ts +17 -0
  38. package/src/sample/domains/account/endpoints/listCurrentSessions.endpoint.ts +31 -0
  39. package/src/sample/domains/account/endpoints/revokeAllOtherSessions.endpoint.ts +22 -0
  40. package/src/sample/domains/account/endpoints/revokeCurrentSession.endpoint.ts +33 -0
  41. package/src/sample/domains/account/index.ts +18 -0
  42. package/src/sample/domains/appraisals/concepts/appraisalCycle.concept.ts +15 -0
  43. package/src/sample/domains/appraisals/concepts/appraisalRecord.concept.ts +20 -0
  44. package/src/sample/domains/appraisals/concepts/appraisalRecordCompetency.concept.ts +10 -0
  45. package/src/sample/domains/appraisals/concepts/appraisalRecordFunctionalRequirement.concept.ts +10 -0
  46. package/src/sample/domains/appraisals/concepts/appraisalRecordGoal.concept.ts +17 -0
  47. package/src/sample/domains/appraisals/endpoints/getAppraisalRecordById.endpoint.ts +321 -0
  48. package/src/sample/domains/appraisals/endpoints/getAppraisalRecordCompetencies.endpoint.ts +120 -0
  49. package/src/sample/domains/appraisals/endpoints/getAppraisalRecordFunctionalRequirements.endpoint.ts +126 -0
  50. package/src/sample/domains/appraisals/endpoints/getAppraisalRecordGoals.endpoint.ts +104 -0
  51. package/src/sample/domains/appraisals/endpoints/getAppraisalRecordScore.endpoint.ts +58 -0
  52. package/src/sample/domains/appraisals/endpoints/listAppraisalCyclesSimple.endpoint.ts +52 -0
  53. package/src/sample/domains/appraisals/endpoints/listAppraisalRecords.endpoint.ts +245 -0
  54. package/src/sample/domains/appraisals/endpoints/listAppraisalTemplatesSimple.endpoint.ts +52 -0
  55. package/src/sample/domains/appraisals/index.ts +40 -0
  56. package/src/sample/domains/appraisals/rules/appraisalRecordScoping.rule.ts +7 -0
  57. package/src/sample/domains/attendance/concepts/attendancePermission.concept.ts +17 -0
  58. package/src/sample/domains/attendance/concepts/attendanceTransaction.concept.ts +17 -0
  59. package/src/sample/domains/attendance/concepts/publicHoliday.concept.ts +10 -0
  60. package/src/sample/domains/attendance/concepts/punch.concept.ts +19 -0
  61. package/src/sample/domains/attendance/endpoints/getAttendanceTransactionById.endpoint.ts +444 -0
  62. package/src/sample/domains/attendance/endpoints/getCurrentAttendanceProfile.endpoint.ts +886 -0
  63. package/src/sample/domains/attendance/endpoints/getCurrentAttendanceStatistics.endpoint.ts +49 -0
  64. package/src/sample/domains/attendance/endpoints/listAttendancePermissions.endpoint.ts +176 -0
  65. package/src/sample/domains/attendance/endpoints/listAttendanceTransactionStates.endpoint.ts +26 -0
  66. package/src/sample/domains/attendance/endpoints/listAttendanceTransactions.endpoint.ts +373 -0
  67. package/src/sample/domains/attendance/endpoints/listPublicHolidaysSimple.endpoint.ts +52 -0
  68. package/src/sample/domains/attendance/endpoints/listPunchTypes.endpoint.ts +25 -0
  69. package/src/sample/domains/attendance/endpoints/listPunches.endpoint.ts +178 -0
  70. package/src/sample/domains/attendance/index.ts +41 -0
  71. package/src/sample/domains/attendance/services/attendancePermissions.service.ts +15 -0
  72. package/src/sample/domains/employees/concepts/employee.concept.ts +20 -0
  73. package/src/sample/domains/employees/concepts/employeeBankAccount.concept.ts +19 -0
  74. package/src/sample/domains/employees/concepts/employeeChild.concept.ts +19 -0
  75. package/src/sample/domains/employees/concepts/employeeDocument.concept.ts +21 -0
  76. package/src/sample/domains/employees/concepts/employeeEvent.concept.ts +21 -0
  77. package/src/sample/domains/employees/concepts/employeeExperience.concept.ts +18 -0
  78. package/src/sample/domains/employees/concepts/employeeJobLevelChange.concept.ts +21 -0
  79. package/src/sample/domains/employees/concepts/employeeMedal.concept.ts +20 -0
  80. package/src/sample/domains/employees/concepts/employeeQualification.concept.ts +20 -0
  81. package/src/sample/domains/employees/concepts/employeeSpouse.concept.ts +21 -0
  82. package/src/sample/domains/employees/concepts/employeeViolation.concept.ts +19 -0
  83. package/src/sample/domains/employees/endpoints/getCurrentEmployee.endpoint.ts +1651 -0
  84. package/src/sample/domains/employees/endpoints/getCurrentIdentity.endpoint.ts +197 -0
  85. package/src/sample/domains/employees/endpoints/getCurrentManagers.endpoint.ts +142 -0
  86. package/src/sample/domains/employees/endpoints/getEmployeeById.endpoint.ts +1659 -0
  87. package/src/sample/domains/employees/endpoints/getEmployeeStatistics.endpoint.ts +43 -0
  88. package/src/sample/domains/employees/endpoints/listBankAccounts.endpoint.ts +223 -0
  89. package/src/sample/domains/employees/endpoints/listChildren.endpoint.ts +211 -0
  90. package/src/sample/domains/employees/endpoints/listDocuments.endpoint.ts +222 -0
  91. package/src/sample/domains/employees/endpoints/listEmployeesSimple.endpoint.ts +156 -0
  92. package/src/sample/domains/employees/endpoints/listEvents.endpoint.ts +120 -0
  93. package/src/sample/domains/employees/endpoints/listExperiences.endpoint.ts +188 -0
  94. package/src/sample/domains/employees/endpoints/listJobLevelChanges.endpoint.ts +240 -0
  95. package/src/sample/domains/employees/endpoints/listMedals.endpoint.ts +266 -0
  96. package/src/sample/domains/employees/endpoints/listQualifications.endpoint.ts +265 -0
  97. package/src/sample/domains/employees/endpoints/listSpouses.endpoint.ts +278 -0
  98. package/src/sample/domains/employees/endpoints/listViolations.endpoint.ts +245 -0
  99. package/src/sample/domains/employees/index.ts +75 -0
  100. package/src/sample/domains/employees/services/employees.service.ts +181 -0
  101. package/src/sample/domains/index.ts +10 -0
  102. package/src/sample/domains/leaves/concepts/leave.concept.ts +21 -0
  103. package/src/sample/domains/leaves/concepts/leaveBalance.concept.ts +17 -0
  104. package/src/sample/domains/leaves/concepts/leaveBalanceTransaction.concept.ts +16 -0
  105. package/src/sample/domains/leaves/concepts/leaveType.concept.ts +13 -0
  106. package/src/sample/domains/leaves/concepts/leaveTypes.concept.ts +205 -0
  107. package/src/sample/domains/leaves/endpoints/getBalance.endpoint.ts +53 -0
  108. package/src/sample/domains/leaves/endpoints/getLeaveById.endpoint.ts +53 -0
  109. package/src/sample/domains/leaves/endpoints/listBalanceTransactions.endpoint.ts +196 -0
  110. package/src/sample/domains/leaves/endpoints/listBalanceTypes.endpoint.ts +26 -0
  111. package/src/sample/domains/leaves/endpoints/listBalances.endpoint.ts +201 -0
  112. package/src/sample/domains/leaves/endpoints/listLeaveTypes.endpoint.ts +28 -0
  113. package/src/sample/domains/leaves/endpoints/listLeaves.endpoint.ts +234 -0
  114. package/src/sample/domains/leaves/index.ts +38 -0
  115. package/src/sample/domains/leaves/services/leaveRequests.service.ts +232 -0
  116. package/src/sample/domains/notifications/concepts/notification.concept.ts +19 -0
  117. package/src/sample/domains/notifications/endpoints/countMyNotifications.endpoint.ts +21 -0
  118. package/src/sample/domains/notifications/endpoints/getMyNotification.endpoint.ts +57 -0
  119. package/src/sample/domains/notifications/endpoints/listMyNotifications.endpoint.ts +59 -0
  120. package/src/sample/domains/notifications/endpoints/markMyNotificationAsRead.endpoint.ts +32 -0
  121. package/src/sample/domains/notifications/index.ts +20 -0
  122. package/src/sample/domains/payroll/concepts/payslip.concept.ts +18 -0
  123. package/src/sample/domains/payroll/concepts/salaryCertificate.concept.ts +17 -0
  124. package/src/sample/domains/payroll/concepts/salaryCertificateRequestingEntity.concept.ts +8 -0
  125. package/src/sample/domains/payroll/endpoints/getPayslipById.endpoint.ts +238 -0
  126. package/src/sample/domains/payroll/endpoints/listPayslips.endpoint.ts +216 -0
  127. package/src/sample/domains/payroll/endpoints/listSalaryCertificateLanguages.endpoint.ts +25 -0
  128. package/src/sample/domains/payroll/endpoints/listSalaryCertificateRequestingEntities.endpoint.ts +62 -0
  129. package/src/sample/domains/payroll/endpoints/listSalaryCertificateTypes.endpoint.ts +24 -0
  130. package/src/sample/domains/payroll/endpoints/listSalaryCertificates.endpoint.ts +122 -0
  131. package/src/sample/domains/payroll/endpoints/previewSalaryCertificate.endpoint.ts +45 -0
  132. package/src/sample/domains/payroll/index.ts +32 -0
  133. package/src/sample/domains/payroll/rules/payrollEmployeeContext.rule.ts +7 -0
  134. package/src/sample/domains/payroll/services/salaryCertificates.service.ts +15 -0
  135. package/src/sample/domains/servicing/concepts/service.concept.ts +6 -0
  136. package/src/sample/domains/servicing/concepts/serviceRequest.concept.ts +12 -0
  137. package/src/sample/domains/servicing/concepts/serviceRequestApproval.concept.ts +9 -0
  138. package/src/sample/domains/servicing/endpoints/cancelServiceRequest.endpoint.ts +21 -0
  139. package/src/sample/domains/servicing/endpoints/createServiceRequest.endpoint.ts +511 -0
  140. package/src/sample/domains/servicing/endpoints/getServiceRequest.endpoint.ts +352 -0
  141. package/src/sample/domains/servicing/endpoints/getServiceRequestFormItems.endpoint.ts +219 -0
  142. package/src/sample/domains/servicing/endpoints/getServiceRequestTransactionFormItems.endpoint.ts +216 -0
  143. package/src/sample/domains/servicing/endpoints/getServiceStates.endpoint.ts +47 -0
  144. package/src/sample/domains/servicing/endpoints/invokeServiceRequestAction.endpoint.ts +139 -0
  145. package/src/sample/domains/servicing/endpoints/listServiceRequests.endpoint.ts +292 -0
  146. package/src/sample/domains/servicing/endpoints/listServices.endpoint.ts +108 -0
  147. package/src/sample/domains/servicing/endpoints/updateServiceRequest.endpoint.ts +355 -0
  148. package/src/sample/domains/servicing/index.ts +44 -0
  149. package/src/sample/domains/servicing/rules/checkValidActions.rule.ts +7 -0
  150. package/src/sample/domains/servicing/rules/createServiceRequest.rule.ts +7 -0
  151. package/src/sample/domains/servicing/rules/filterServiceRequestsByState.rule.ts +7 -0
  152. package/src/sample/domains/servicing/rules/passingOptionFields.rule.ts +7 -0
  153. package/src/sample/domains/shared/rules/pagination.rule.ts +7 -0
  154. package/src/sample/domains/suggestions/concepts/suggestion.concept.ts +19 -0
  155. package/src/sample/domains/suggestions/concepts/suggestionCategory.concept.ts +7 -0
  156. package/src/sample/domains/suggestions/concepts/suggestionImpactLevel.concept.ts +7 -0
  157. package/src/sample/domains/suggestions/endpoints/createSuggestion.endpoint.ts +705 -0
  158. package/src/sample/domains/suggestions/endpoints/deleteSuggestion.endpoint.ts +32 -0
  159. package/src/sample/domains/suggestions/endpoints/getSuggestionById.endpoint.ts +635 -0
  160. package/src/sample/domains/suggestions/endpoints/listSuggestionCategories.endpoint.ts +62 -0
  161. package/src/sample/domains/suggestions/endpoints/listSuggestionImpactLevels.endpoint.ts +62 -0
  162. package/src/sample/domains/suggestions/endpoints/listSuggestionStates.endpoint.ts +24 -0
  163. package/src/sample/domains/suggestions/endpoints/listSuggestions.endpoint.ts +132 -0
  164. package/src/sample/domains/suggestions/endpoints/updateSuggestion.endpoint.ts +690 -0
  165. package/src/sample/domains/suggestions/endpoints/updateSuggestionState.endpoint.ts +39 -0
  166. package/src/sample/domains/suggestions/index.ts +34 -0
  167. package/src/sample/domains/suggestions/rules/suggestionScope.rule.ts +7 -0
  168. package/src/sample/domains/surveys/concepts/survey.concept.ts +10 -0
  169. package/src/sample/domains/surveys/concepts/surveyResponse.concept.ts +14 -0
  170. package/src/sample/domains/surveys/endpoints/getSurveyResponseById.endpoint.ts +131 -0
  171. package/src/sample/domains/surveys/endpoints/getTargetedSurvey.endpoint.ts +129 -0
  172. package/src/sample/domains/surveys/endpoints/listTargetedSurveys.endpoint.ts +160 -0
  173. package/src/sample/domains/surveys/endpoints/respondToSurvey.endpoint.ts +144 -0
  174. package/src/sample/domains/surveys/index.ts +21 -0
  175. package/src/sample/drizzle.config.ts +11 -0
  176. package/src/sample/index.bak.ts +341 -0
  177. package/src/sample/index.ts +318 -0
  178. package/src/sample/official-statements/defaults.ts +57 -0
  179. package/src/sample/official-statements/routes.ts +137 -0
  180. package/src/sample/official-statements/service.ts +148 -0
  181. package/src/sample/official-statements/types.ts +23 -0
  182. package/dist/src/ai/context/compressor.d.ts +0 -7
  183. package/dist/src/ai/tools/call-endpoint.tool.d.ts +0 -7
  184. package/dist/src/config.d.ts +0 -159
  185. package/src/ai/context/compressor.ts +0 -47
  186. package/src/ai/prompt.ts +0 -126
  187. package/src/ai/tools/call-endpoint.tool.ts +0 -89
  188. package/src/config.ts +0 -164
  189. package/src/db/migrations/20260315000000_add_context_meta/migration.sql +0 -1
  190. package/dist/src/{adapters → lib/adapters}/minio.d.ts +0 -0
  191. package/dist/src/{adapters → lib/adapters}/mssql.d.ts +0 -0
  192. package/dist/src/{adapters → lib/adapters}/storage.d.ts +0 -0
  193. package/dist/src/{ai → lib/ai}/active-streams.d.ts +0 -0
  194. package/dist/src/{ai → lib/ai}/context/builder.d.ts +0 -0
  195. package/dist/src/{ai → lib/ai}/context/summarizer.d.ts +0 -0
  196. package/dist/src/{ai → lib/ai}/context/token-estimator.d.ts +0 -0
  197. package/dist/src/{ai → lib/ai}/fetch.d.ts +0 -0
  198. package/dist/src/{ai → lib/ai}/helpers.d.ts +0 -0
  199. package/dist/src/{ai → lib/ai}/index.d.ts +0 -0
  200. package/dist/src/{ai → lib/ai}/interceptors/request-interceptor.d.ts +0 -0
  201. package/dist/src/{ai → lib/ai}/prompt.d.ts +0 -0
  202. package/dist/src/{ai → lib/ai}/tools/capture-files.tool.d.ts +0 -0
  203. package/dist/src/{ai → lib/ai}/tools/execute-code.tool.d.ts +0 -0
  204. package/dist/src/{ai → lib/ai}/tools/query-graph.tool.d.ts +0 -0
  205. package/dist/src/{auth → lib/auth}/middleware.d.ts +0 -0
  206. package/dist/src/{cli → lib/cli}/extract-endpoints.d.ts +0 -0
  207. package/dist/src/{db → lib/db}/migrate.d.ts +0 -0
  208. package/dist/src/{factory.d.ts → lib/factory.d.ts} +0 -0
  209. package/dist/src/{graph → lib/graph}/expand-domains.d.ts +0 -0
  210. package/dist/src/{graph → lib/graph}/generate-cypher.d.ts +0 -0
  211. package/dist/src/{graph → lib/graph}/helpers.d.ts +2 -2
  212. /package/dist/src/{graph → lib/graph}/index.d.ts +0 -0
  213. /package/dist/src/{graph → lib/graph}/neo4j.d.ts +0 -0
  214. /package/dist/src/{graph → lib/graph}/resolver.d.ts +0 -0
  215. /package/dist/src/{graph → lib/graph}/seed.d.ts +0 -0
  216. /package/dist/src/{graph → lib/graph}/types.d.ts +0 -0
  217. /package/dist/src/{graph → lib/graph}/validate.d.ts +0 -0
  218. /package/dist/src/{routes → lib/routes}/chat.d.ts +0 -0
  219. /package/dist/src/{routes → lib/routes}/files.d.ts +0 -0
  220. /package/dist/src/{routes → lib/routes}/index.d.ts +0 -0
  221. /package/dist/src/{routes → lib/routes}/threads.d.ts +0 -0
  222. /package/dist/src/{routes → lib/routes}/ws.d.ts +0 -0
  223. /package/dist/src/{ws → lib/ws}/connections.d.ts +0 -0
  224. /package/dist/src/{ws → lib/ws}/events.d.ts +0 -0
  225. /package/dist/src/{ws → lib/ws}/index.d.ts +0 -0
  226. /package/dist/src/{ws → lib/ws}/notify.d.ts +0 -0
  227. /package/src/{adapters → lib/adapters}/storage.ts +0 -0
  228. /package/src/{ai → lib/ai}/active-streams.ts +0 -0
  229. /package/src/{ai → lib/ai}/context/builder.ts +0 -0
  230. /package/src/{ai → lib/ai}/context/summarizer.ts +0 -0
  231. /package/src/{ai → lib/ai}/context/token-estimator.ts +0 -0
  232. /package/src/{ai → lib/ai}/fetch.ts +0 -0
  233. /package/src/{ai → lib/ai}/helpers.ts +0 -0
  234. /package/src/{ai → lib/ai}/interceptors/request-interceptor.ts +0 -0
  235. /package/src/{ai → lib/ai}/tools/capture-files.tool.ts +0 -0
  236. /package/src/{ai → lib/ai}/tools/query-graph.tool.ts +0 -0
  237. /package/src/{cli → lib/cli}/extract-endpoints.ts +0 -0
  238. /package/src/{db → lib/db}/migrate.ts +0 -0
  239. /package/src/{graph → lib/graph}/expand-domains.ts +0 -0
  240. /package/src/{graph → lib/graph}/generate-cypher.ts +0 -0
  241. /package/src/{graph → lib/graph}/helpers.ts +0 -0
  242. /package/src/{graph → lib/graph}/index.ts +0 -0
  243. /package/src/{graph → lib/graph}/neo4j.ts +0 -0
  244. /package/src/{graph → lib/graph}/resolver.ts +0 -0
  245. /package/src/{graph → lib/graph}/seed.ts +0 -0
  246. /package/src/{graph → lib/graph}/types.ts +0 -0
  247. /package/src/{graph → lib/graph}/validate.ts +0 -0
  248. /package/src/{routes → lib/routes}/chat.ts +0 -0
  249. /package/src/{routes → lib/routes}/index.ts +0 -0
  250. /package/src/{routes → lib/routes}/ws.ts +0 -0
  251. /package/src/{ws → lib/ws}/connections.ts +0 -0
  252. /package/src/{ws → lib/ws}/events.ts +0 -0
  253. /package/src/{ws → lib/ws}/index.ts +0 -0
  254. /package/src/{ws → lib/ws}/notify.ts +0 -0
@@ -0,0 +1,259 @@
1
+ import type { UIMessage } from "ai";
2
+ import { estimateTokens, CHARS_PER_TOKEN } from "./token-estimator.ts";
3
+ import type { MessageMetadata } from "../../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
+ const charBudget = maxTokensPerResult * CHARS_PER_TOKEN;
39
+ const truncatedOutput = smartStructuralCompress(part.output, charBudget);
40
+
41
+ return { ...part, output: truncatedOutput } as typeof part;
42
+ });
43
+
44
+ return { ...message, parts: compressedParts };
45
+ });
46
+ }
47
+
48
+ /**
49
+ * Structurally compresses a value to fit within a character budget.
50
+ * Understands JSON arrays, error objects, and nested objects — preserving
51
+ * semantic meaning better than naive string truncation.
52
+ */
53
+ export function smartStructuralCompress(value: unknown, charBudget: number) {
54
+ if (typeof value === "string") {
55
+ return compressString(value, charBudget);
56
+ }
57
+
58
+ if (Array.isArray(value)) {
59
+ return compressArray(value, charBudget);
60
+ }
61
+
62
+ if (typeof value === "object" && value !== null) {
63
+ return compressObject(value as Record<string, unknown>, charBudget);
64
+ }
65
+
66
+ // Primitives
67
+ const str = String(value);
68
+ if (str.length <= charBudget) return str;
69
+ return str.slice(0, charBudget - 20) + "\n[...truncated]";
70
+ }
71
+
72
+ function compressString(value: string, charBudget: number) {
73
+ if (value.length <= charBudget) return value;
74
+
75
+ // Try to parse as JSON first — if it's a stringified structure, compress structurally
76
+ try {
77
+ const parsed: unknown = JSON.parse(value);
78
+ if (typeof parsed === "object" && parsed !== null) {
79
+ const result = smartStructuralCompress(parsed, charBudget) as string;
80
+ return result;
81
+ }
82
+ } catch {
83
+ // Not JSON, just truncate
84
+ }
85
+
86
+ return value.slice(0, charBudget - 20) + "\n[...truncated]";
87
+ }
88
+
89
+ function compressArray(value: unknown[], charBudget: number) {
90
+ const totalCount = value.length;
91
+
92
+ if (totalCount === 0) return "[]";
93
+
94
+ // Infer schema from first object element (if items are objects)
95
+ const firstObj = value.find(
96
+ (item): item is Record<string, unknown> =>
97
+ typeof item === "object" && item !== null && !Array.isArray(item),
98
+ );
99
+ const schemaKeys = firstObj ? Object.keys(firstObj) : null;
100
+
101
+ // Keep as many items as fit within ~60% of budget, leaving room for the summary footer
102
+ const itemBudget = Math.floor(charBudget * 0.6);
103
+ const items: unknown[] = [];
104
+ let accumulated = 0;
105
+
106
+ for (const item of value) {
107
+ const itemStr = JSON.stringify(item);
108
+ if (accumulated + itemStr.length > itemBudget && items.length > 0) break;
109
+ accumulated += itemStr.length;
110
+ items.push(item);
111
+ }
112
+
113
+ const shownStr = JSON.stringify(items, null, 2);
114
+ const remaining = totalCount - items.length;
115
+
116
+ if (remaining === 0 && shownStr.length <= charBudget) return shownStr;
117
+
118
+ const schemaHint = schemaKeys ? ` Schema: {${schemaKeys.join(", ")}}` : "";
119
+ const footer = `\n[COMPRESSED: ${remaining} more items of ${totalCount} total not shown.${schemaHint} Re-execute with filters or pagination to access specific items.]`;
120
+
121
+ const result = shownStr + footer;
122
+ if (result.length <= charBudget) return result;
123
+
124
+ // If still too large, fall back to a tighter representation
125
+ const tightStr = JSON.stringify(items);
126
+ return tightStr.slice(0, charBudget - footer.length - 20) + footer;
127
+ }
128
+
129
+ function compressObject(value: Record<string, unknown>, charBudget: number) {
130
+ // Check if it looks like an error — keep message, drop stack
131
+ if (isErrorLike(value)) {
132
+ return compressErrorObject(value, charBudget);
133
+ }
134
+
135
+ const fullStr = JSON.stringify(value, null, 2);
136
+ if (fullStr.length <= charBudget) return fullStr;
137
+
138
+ const allKeys = Object.keys(value);
139
+
140
+ // Phase 1: Partition keys into scalar (cheap) and complex (expensive)
141
+ const scalarEntries: string[] = [];
142
+ const complexKeys: string[] = [];
143
+ let scalarCharsUsed = 0;
144
+
145
+ for (const key of allKeys) {
146
+ const val = value[key];
147
+ const valStr = JSON.stringify(val);
148
+
149
+ if (valStr === undefined) {
150
+ const entry = ` "${key}": null`;
151
+ scalarEntries.push(entry);
152
+ scalarCharsUsed += entry.length + 2; // +2 for comma and newline
153
+ } else if (isScalar(val)) {
154
+ const entry = ` "${key}": ${valStr}`;
155
+ scalarEntries.push(entry);
156
+ scalarCharsUsed += entry.length + 2;
157
+ } else {
158
+ complexKeys.push(key);
159
+ }
160
+ }
161
+
162
+ // Phase 2: Distribute remaining budget to complex values
163
+ const compressionHeader = ` "__compressed": "Some values were compressed. If you need the full data for a specific key, re-execute the tool returning only that key."`;
164
+ const headerCost = compressionHeader.length + 2;
165
+ const structuralOverhead = 4; // { } and newlines
166
+ const remainingBudget = charBudget - scalarCharsUsed - headerCost - structuralOverhead;
167
+ const complexBudget = Math.floor(
168
+ Math.max(remainingBudget, 100) / Math.max(complexKeys.length, 1),
169
+ );
170
+
171
+ const complexEntries: string[] = [];
172
+ const truncatedKeys: string[] = [];
173
+
174
+ for (const key of complexKeys) {
175
+ const val = value[key];
176
+ const valStr = JSON.stringify(val);
177
+
178
+ if (valStr !== undefined && valStr.length <= complexBudget) {
179
+ complexEntries.push(` "${key}": ${valStr}`);
180
+ } else {
181
+ truncatedKeys.push(key);
182
+ complexEntries.push(` "${key}": ${summarizeValue(val)}`);
183
+ }
184
+ }
185
+
186
+ // Build result with compression notice
187
+ const allEntries = [...scalarEntries, ...complexEntries];
188
+
189
+ if (truncatedKeys.length > 0) {
190
+ allEntries.push(compressionHeader);
191
+ allEntries.push(` "__truncatedKeys": ${JSON.stringify(truncatedKeys)}`);
192
+ }
193
+
194
+ const result = `{\n${allEntries.join(",\n")}\n}`;
195
+ if (result.length <= charBudget) return result;
196
+
197
+ // Final fallback: keep scalars + summaries, trim from the complex entries end
198
+ return result.slice(0, charBudget - 20) + "\n[...truncated]";
199
+ }
200
+
201
+ function isScalar(val: unknown): boolean {
202
+ return (
203
+ val === null ||
204
+ typeof val === "string" ||
205
+ typeof val === "number" ||
206
+ typeof val === "boolean"
207
+ );
208
+ }
209
+
210
+ function isErrorLike(value: Record<string, unknown>) {
211
+ return "error" in value || "message" in value || "stack" in value || "stackTrace" in value;
212
+ }
213
+
214
+ function compressErrorObject(value: Record<string, unknown>, charBudget: number) {
215
+ // Keep error/message fields, drop stack traces
216
+ const { stack: _stack, stackTrace: _stackTrace, ...rest } = value;
217
+ const compressed = JSON.stringify(rest, null, 2);
218
+
219
+ if (compressed.length <= charBudget) return compressed;
220
+ return compressed.slice(0, charBudget - 20) + "\n[...truncated]";
221
+ }
222
+
223
+ /**
224
+ * Produces a short human-readable summary of a value's shape and key data.
225
+ * Used both for inline object-key placeholders and for tool result one-liners.
226
+ */
227
+ export function summarizeValue(val: unknown) {
228
+ if (Array.isArray(val)) {
229
+ const count = val.length;
230
+ const firstItem = val[0];
231
+ const schemaHint =
232
+ typeof firstItem === "object" && firstItem !== null
233
+ ? ` {${Object.keys(firstItem).slice(0, 5).join(", ")}}`
234
+ : "";
235
+ return `[Array(${count})${schemaHint}]`;
236
+ }
237
+
238
+ if (typeof val === "object" && val !== null) {
239
+ const obj = val as Record<string, unknown>;
240
+ const keys = Object.keys(obj);
241
+
242
+ // Detect common status/success response patterns
243
+ if ("status" in obj || "success" in obj) {
244
+ const status = obj["status"] ?? obj["success"];
245
+ const dataKey = keys.find((k) => k === "data" || k === "result" || k === "results");
246
+ const dataVal = dataKey ? obj[dataKey] : null;
247
+ const dataSummary = Array.isArray(dataVal) ? `, ${dataVal.length} items` : "";
248
+ return `{${String(status)}${dataSummary}}`;
249
+ }
250
+
251
+ return `{Object(${keys.length} keys: ${keys.slice(0, 5).join(", ")}${keys.length > 5 ? ", ..." : ""})}`;
252
+ }
253
+
254
+ if (typeof val === "string") {
255
+ return val.length > 80 ? `"${val.slice(0, 77)}..."` : JSON.stringify(val);
256
+ }
257
+
258
+ return String(val);
259
+ }
@@ -6,9 +6,14 @@ export {
6
6
  estimateMessageTokens,
7
7
  estimateMessagesTokens,
8
8
  } from "./token-estimator.ts";
9
- export { compressToolResults } from "./compressor.ts";
9
+ export { compressToolResults, smartStructuralCompress, summarizeValue } from "./compressor.ts";
10
10
  export { summarizeMessages } from "./summarizer.ts";
11
11
  export { buildContextMessages, trimMessagesToFit } from "./builder.ts";
12
+ export {
13
+ compressIntraTurnToolResults,
14
+ estimateToolResultTokens,
15
+ summarizeOldStepResults,
16
+ } from "./intra-turn-compressor.ts";
12
17
 
13
18
  import type { UIMessage } from "ai";
14
19
  import type { ResolvedCortexAgentConfig } from "../../config.ts";
@@ -0,0 +1,264 @@
1
+ import type {
2
+ LanguageModelV3Prompt,
3
+ LanguageModelV3Message,
4
+ LanguageModelV3ToolResultPart,
5
+ } from "@ai-sdk/provider";
6
+ import { generateText } from "ai";
7
+ import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
8
+ import { estimateTokens } from "./token-estimator.ts";
9
+ import { summarizeValue } from "./compressor.ts";
10
+ import type { ContextConfig } from "./types.ts";
11
+
12
+ type ToolRoleMessage = LanguageModelV3Message & { role: "tool" };
13
+
14
+ type SummarizationModelConfig = NonNullable<ContextConfig["summarizationModel"]>;
15
+
16
+ /**
17
+ * Compresses tool results from older steps in the prompt.
18
+ *
19
+ * On step N, tool results from steps 1..N-2 are aggressively compressed
20
+ * to short one-liners. Only the most recent tool result stays in full.
21
+ * Does not mutate the input — returns a new prompt array.
22
+ */
23
+ export function compressIntraTurnToolResults(
24
+ prompt: LanguageModelV3Prompt,
25
+ toolResultMaxTokens: number,
26
+ ) {
27
+ // Collect indices of all tool-role messages
28
+ const toolMessageIndices: number[] = [];
29
+ for (let i = 0; i < prompt.length; i++) {
30
+ if (prompt[i]!.role === "tool") {
31
+ toolMessageIndices.push(i);
32
+ }
33
+ }
34
+
35
+ // Need at least 3 tool messages to have "old" ones to compress
36
+ // (keep last 1 fully intact, compress the rest)
37
+ if (toolMessageIndices.length < 3) return prompt;
38
+
39
+ // Clone the prompt so we don't mutate the original
40
+ const result = prompt.map((msg) => ({ ...msg }));
41
+
42
+ // Compress all tool messages except the last one
43
+ const indicesToCompress = toolMessageIndices.slice(0, -1);
44
+
45
+ for (const idx of indicesToCompress) {
46
+ const msg = result[idx] as ToolRoleMessage;
47
+ const compressedContent = msg.content.map((part) => {
48
+ if (part.type !== "tool-result") return part;
49
+ return compressToolResultPart(part, toolResultMaxTokens);
50
+ });
51
+
52
+ result[idx] = { ...msg, content: compressedContent } as LanguageModelV3Message;
53
+ }
54
+
55
+ return result;
56
+ }
57
+
58
+ function compressToolResultPart(
59
+ part: LanguageModelV3ToolResultPart,
60
+ maxTokens: number,
61
+ ): LanguageModelV3ToolResultPart {
62
+ const output = part.output;
63
+
64
+ // Already small enough
65
+ const outputStr = stringifyOutput(output);
66
+ if (estimateTokens(outputStr) <= maxTokens) return part;
67
+
68
+ const compressed = compressOutputToOneLiner(output, part.toolName);
69
+
70
+ return {
71
+ ...part,
72
+ output: { type: "text", value: compressed },
73
+ };
74
+ }
75
+
76
+ function stringifyOutput(output: LanguageModelV3ToolResultPart["output"]): string {
77
+ switch (output.type) {
78
+ case "text":
79
+ case "error-text":
80
+ return output.value;
81
+ case "json":
82
+ case "error-json":
83
+ return JSON.stringify(output.value);
84
+ case "execution-denied":
85
+ return output.reason ?? "denied";
86
+ case "content":
87
+ return JSON.stringify(output.value);
88
+ default:
89
+ return "";
90
+ }
91
+ }
92
+
93
+ function compressOutputToOneLiner(
94
+ output: LanguageModelV3ToolResultPart["output"],
95
+ toolName: string,
96
+ ): string {
97
+ const tag = `[COMPRESSED prior ${toolName} result]`;
98
+
99
+ switch (output.type) {
100
+ case "text": {
101
+ const preview = extractPreview(output.value);
102
+ return `${tag} ${preview}`;
103
+ }
104
+ case "json":
105
+ return `${tag} ${summarizeValue(output.value)}`;
106
+ case "error-text": {
107
+ const firstLine = output.value.split("\n")[0] ?? "error";
108
+ return `${tag} ERROR - ${firstLine}`;
109
+ }
110
+ case "error-json": {
111
+ const errStr = JSON.stringify(output.value).slice(0, 200);
112
+ return `${tag} ERROR - ${errStr}`;
113
+ }
114
+ case "execution-denied":
115
+ return `${tag} execution denied${output.reason ? ` - ${output.reason}` : ""}`;
116
+ case "content": {
117
+ const textParts = output.value
118
+ .filter((p): p is Extract<typeof p, { type: "text" }> => p.type === "text")
119
+ .map((p) => p.text);
120
+ const joined = textParts.join(" ");
121
+ const preview = extractPreview(joined);
122
+ return `${tag} ${preview}`;
123
+ }
124
+ default:
125
+ return `${tag} completed`;
126
+ }
127
+ }
128
+
129
+ function extractPreview(text: string): string {
130
+ // Try to parse as JSON for structured summary
131
+ try {
132
+ const parsed: unknown = JSON.parse(text);
133
+ if (typeof parsed === "object" && parsed !== null) {
134
+ return summarizeValue(parsed);
135
+ }
136
+ } catch {
137
+ // Not JSON
138
+ }
139
+
140
+ if (text.length <= 150) return text;
141
+ return text.slice(0, 147) + "...";
142
+ }
143
+
144
+ /**
145
+ * Estimates the total token count of tool results in the prompt.
146
+ */
147
+ export function estimateToolResultTokens(prompt: LanguageModelV3Prompt): number {
148
+ let total = 0;
149
+
150
+ for (const msg of prompt) {
151
+ if (msg.role !== "tool") continue;
152
+ for (const part of msg.content) {
153
+ if (part.type === "tool-result") {
154
+ total += estimateTokens(stringifyOutput(part.output));
155
+ }
156
+ }
157
+ }
158
+
159
+ return total;
160
+ }
161
+
162
+ /**
163
+ * When accumulated tool result tokens exceed the threshold, summarizes
164
+ * older step results using a lightweight LLM call.
165
+ *
166
+ * Replaces all tool results except the last two with a single synthetic
167
+ * summary in the oldest tool message.
168
+ */
169
+ export async function summarizeOldStepResults(
170
+ prompt: LanguageModelV3Prompt,
171
+ thresholdTokens: number,
172
+ modelConfig: SummarizationModelConfig,
173
+ ) {
174
+ const totalToolTokens = estimateToolResultTokens(prompt);
175
+ if (totalToolTokens <= thresholdTokens) return prompt;
176
+
177
+ // Collect tool-role message indices
178
+ const toolMessageIndices: number[] = [];
179
+ for (let i = 0; i < prompt.length; i++) {
180
+ if (prompt[i]!.role === "tool") {
181
+ toolMessageIndices.push(i);
182
+ }
183
+ }
184
+
185
+ // Keep the last 2 tool messages intact, summarize the rest
186
+ if (toolMessageIndices.length < 3) return prompt;
187
+
188
+ const indicesToSummarize = toolMessageIndices.slice(0, -2);
189
+
190
+ // Collect tool result text from older steps
191
+ const resultTexts: string[] = [];
192
+ for (const idx of indicesToSummarize) {
193
+ const msg = prompt[idx] as ToolRoleMessage;
194
+ for (const part of msg.content) {
195
+ if (part.type === "tool-result") {
196
+ resultTexts.push(`[${part.toolName}]: ${stringifyOutput(part.output)}`);
197
+ }
198
+ }
199
+ }
200
+
201
+ if (resultTexts.length === 0) return prompt;
202
+
203
+ // Summarize with LLM
204
+ const provider = createOpenAICompatible({
205
+ name: modelConfig.providerName ?? "summarization-provider",
206
+ baseURL: modelConfig.baseURL,
207
+ apiKey: modelConfig.apiKey,
208
+ });
209
+
210
+ const model = provider.chatModel(modelConfig.modelName);
211
+
212
+ const { text: summary } = await generateText({
213
+ model,
214
+ system: `You are a tool result summarizer. Given previous tool call results from an AI agent's workflow, produce a concise summary preserving:
215
+ - Key data values, IDs, and counts
216
+ - Success/failure status of each operation
217
+ - Any error messages
218
+ - Data that subsequent tool calls may need
219
+
220
+ Maximum 300 tokens. Use bullet points. No preamble.`,
221
+ prompt: `Summarize these tool results:\n\n${resultTexts.join("\n\n")}`,
222
+ });
223
+
224
+ // Replace old tool messages with collapsed versions containing just the summary
225
+ const result = [...prompt];
226
+
227
+ for (let i = 0; i < indicesToSummarize.length; i++) {
228
+ const idx = indicesToSummarize[i]!;
229
+ const originalMsg = prompt[idx] as ToolRoleMessage;
230
+
231
+ if (i === 0) {
232
+ // First old tool message gets the summary
233
+ const summarizedContent = originalMsg.content.map((part) => {
234
+ if (part.type !== "tool-result") return part;
235
+ return {
236
+ ...part,
237
+ output: {
238
+ type: "text" as const,
239
+ value: `[Summary of ${resultTexts.length} earlier tool results]:\n${summary}`,
240
+ },
241
+ };
242
+ });
243
+
244
+ // Keep only the first tool-result part with the summary
245
+ const firstResultIdx = summarizedContent.findIndex((p) => p.type === "tool-result");
246
+ const collapsedContent =
247
+ firstResultIdx >= 0 ? [summarizedContent[firstResultIdx]!] : summarizedContent;
248
+
249
+ result[idx] = { ...originalMsg, content: collapsedContent } as LanguageModelV3Message;
250
+ } else {
251
+ // Subsequent old tool messages get one-liners
252
+ const collapsedContent = originalMsg.content.map((part) => {
253
+ if (part.type !== "tool-result") return part;
254
+ return {
255
+ ...part,
256
+ output: { type: "text" as const, value: `[see summary above]` },
257
+ };
258
+ });
259
+ result[idx] = { ...originalMsg, content: collapsedContent } as LanguageModelV3Message;
260
+ }
261
+ }
262
+
263
+ return result;
264
+ }
@@ -10,6 +10,8 @@ export type ContextConfig = {
10
10
  };
11
11
  toolResultMaxTokens: number;
12
12
  recentMessagesToKeep: number;
13
+ /** Token threshold for triggering intra-turn summarization of older tool results */
14
+ intraTurnSummarizationThresholdTokens: number;
13
15
  };
14
16
 
15
17
  export type ThreadContextMeta = {
@@ -23,6 +25,7 @@ export const DEFAULT_CONTEXT_CONFIG: ContextConfig = {
23
25
  maxContextTokens: 120_000,
24
26
  reservedTokenBudget: 8_000,
25
27
  summarizationThreshold: 0.75,
26
- toolResultMaxTokens: 2_000,
28
+ toolResultMaxTokens: 1_000,
27
29
  recentMessagesToKeep: 6,
30
+ intraTurnSummarizationThresholdTokens: 20_000,
28
31
  };