@lota-sdk/core 0.1.5

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 (153) hide show
  1. package/infrastructure/schema/00_workstream.surql +55 -0
  2. package/infrastructure/schema/01_memory.surql +47 -0
  3. package/infrastructure/schema/02_execution_plan.surql +62 -0
  4. package/infrastructure/schema/03_learned_skill.surql +32 -0
  5. package/infrastructure/schema/04_runtime_bootstrap.surql +8 -0
  6. package/package.json +128 -0
  7. package/src/ai/definitions.ts +308 -0
  8. package/src/bifrost/bifrost.ts +256 -0
  9. package/src/config/agent-defaults.ts +99 -0
  10. package/src/config/constants.ts +33 -0
  11. package/src/config/env-shapes.ts +122 -0
  12. package/src/config/logger.ts +29 -0
  13. package/src/config/model-constants.ts +31 -0
  14. package/src/config/search.ts +17 -0
  15. package/src/config/workstream-defaults.ts +68 -0
  16. package/src/db/base.service.ts +55 -0
  17. package/src/db/cursor-pagination.ts +73 -0
  18. package/src/db/memory-query-builder.ts +207 -0
  19. package/src/db/memory-store.helpers.ts +118 -0
  20. package/src/db/memory-store.rows.ts +29 -0
  21. package/src/db/memory-store.ts +974 -0
  22. package/src/db/memory-types.ts +193 -0
  23. package/src/db/memory.ts +505 -0
  24. package/src/db/record-id.ts +78 -0
  25. package/src/db/service.ts +932 -0
  26. package/src/db/startup.ts +152 -0
  27. package/src/db/tables.ts +20 -0
  28. package/src/document/org-document-chunking.ts +224 -0
  29. package/src/document/parsing.ts +40 -0
  30. package/src/embeddings/provider.ts +76 -0
  31. package/src/index.ts +302 -0
  32. package/src/queues/context-compaction.queue.ts +82 -0
  33. package/src/queues/document-processor.queue.ts +118 -0
  34. package/src/queues/memory-consolidation.queue.ts +65 -0
  35. package/src/queues/post-chat-memory.queue.ts +128 -0
  36. package/src/queues/recent-activity-title-refinement.queue.ts +69 -0
  37. package/src/queues/regular-chat-memory-digest.config.ts +12 -0
  38. package/src/queues/regular-chat-memory-digest.queue.ts +73 -0
  39. package/src/queues/skill-extraction.config.ts +9 -0
  40. package/src/queues/skill-extraction.queue.ts +62 -0
  41. package/src/redis/connection.ts +176 -0
  42. package/src/redis/index.ts +30 -0
  43. package/src/redis/org-memory-lock.ts +43 -0
  44. package/src/redis/redis-lease-lock.ts +158 -0
  45. package/src/runtime/agent-contract.ts +1 -0
  46. package/src/runtime/agent-prompt-context.ts +119 -0
  47. package/src/runtime/agent-runtime-policy.ts +192 -0
  48. package/src/runtime/agent-stream-helpers.ts +117 -0
  49. package/src/runtime/agent-types.ts +22 -0
  50. package/src/runtime/approval-continuation.ts +16 -0
  51. package/src/runtime/chat-attachments.ts +46 -0
  52. package/src/runtime/chat-message.ts +10 -0
  53. package/src/runtime/chat-request-routing.ts +21 -0
  54. package/src/runtime/chat-run-orchestration.ts +25 -0
  55. package/src/runtime/chat-run-registry.ts +20 -0
  56. package/src/runtime/chat-types.ts +18 -0
  57. package/src/runtime/context-compaction-constants.ts +11 -0
  58. package/src/runtime/context-compaction-runtime.ts +86 -0
  59. package/src/runtime/context-compaction.ts +909 -0
  60. package/src/runtime/execution-plan.ts +59 -0
  61. package/src/runtime/helper-model.ts +405 -0
  62. package/src/runtime/indexed-repositories-policy.ts +28 -0
  63. package/src/runtime/instruction-sections.ts +8 -0
  64. package/src/runtime/llm-content.ts +71 -0
  65. package/src/runtime/memory-block.ts +264 -0
  66. package/src/runtime/memory-digest-policy.ts +14 -0
  67. package/src/runtime/memory-format.ts +8 -0
  68. package/src/runtime/memory-pipeline.ts +570 -0
  69. package/src/runtime/memory-prompts-fact.ts +47 -0
  70. package/src/runtime/memory-prompts-parse.ts +3 -0
  71. package/src/runtime/memory-prompts-update.ts +37 -0
  72. package/src/runtime/memory-scope.ts +43 -0
  73. package/src/runtime/plugin-types.ts +10 -0
  74. package/src/runtime/retrieval-adapters.ts +25 -0
  75. package/src/runtime/retrieval-pipeline.ts +3 -0
  76. package/src/runtime/runtime-extensions.ts +154 -0
  77. package/src/runtime/skill-extraction-policy.ts +3 -0
  78. package/src/runtime/team-consultation-orchestrator.ts +245 -0
  79. package/src/runtime/team-consultation-prompts.ts +32 -0
  80. package/src/runtime/title-helpers.ts +12 -0
  81. package/src/runtime/turn-lifecycle.ts +28 -0
  82. package/src/runtime/workstream-chat-helpers.ts +187 -0
  83. package/src/runtime/workstream-routing-policy.ts +301 -0
  84. package/src/runtime/workstream-state.ts +261 -0
  85. package/src/services/attachment.service.ts +159 -0
  86. package/src/services/chat-attachments.service.ts +17 -0
  87. package/src/services/chat-run-registry.service.ts +3 -0
  88. package/src/services/context-compaction-runtime.ts +13 -0
  89. package/src/services/context-compaction.service.ts +115 -0
  90. package/src/services/document-chunk.service.ts +141 -0
  91. package/src/services/execution-plan.service.ts +890 -0
  92. package/src/services/learned-skill.service.ts +328 -0
  93. package/src/services/memory-assessment.service.ts +43 -0
  94. package/src/services/memory.service.ts +807 -0
  95. package/src/services/memory.utils.ts +84 -0
  96. package/src/services/mutating-approval.service.ts +110 -0
  97. package/src/services/recent-activity-title.service.ts +74 -0
  98. package/src/services/recent-activity.service.ts +397 -0
  99. package/src/services/workstream-change-tracker.service.ts +313 -0
  100. package/src/services/workstream-message.service.ts +283 -0
  101. package/src/services/workstream-title.service.ts +58 -0
  102. package/src/services/workstream-turn-preparation.ts +1340 -0
  103. package/src/services/workstream-turn.ts +37 -0
  104. package/src/services/workstream.service.ts +854 -0
  105. package/src/services/workstream.types.ts +118 -0
  106. package/src/storage/attachment-parser.ts +101 -0
  107. package/src/storage/attachment-storage.service.ts +391 -0
  108. package/src/storage/attachments.types.ts +11 -0
  109. package/src/storage/attachments.utils.ts +58 -0
  110. package/src/storage/generated-document-storage.service.ts +55 -0
  111. package/src/system-agents/agent-result.ts +27 -0
  112. package/src/system-agents/context-compacter.agent.ts +46 -0
  113. package/src/system-agents/delegated-agent-factory.ts +177 -0
  114. package/src/system-agents/helper-agent-options.ts +20 -0
  115. package/src/system-agents/memory-reranker.agent.ts +38 -0
  116. package/src/system-agents/memory.agent.ts +58 -0
  117. package/src/system-agents/recent-activity-title-refiner.agent.ts +53 -0
  118. package/src/system-agents/regular-chat-memory-digest.agent.ts +75 -0
  119. package/src/system-agents/researcher.agent.ts +34 -0
  120. package/src/system-agents/skill-extractor.agent.ts +88 -0
  121. package/src/system-agents/skill-manager.agent.ts +80 -0
  122. package/src/system-agents/title-generator.agent.ts +42 -0
  123. package/src/system-agents/workstream-tracker.agent.ts +58 -0
  124. package/src/tools/execution-plan.tool.ts +163 -0
  125. package/src/tools/fetch-webpage.tool.ts +132 -0
  126. package/src/tools/firecrawl-client.ts +12 -0
  127. package/src/tools/memory-block.tool.ts +55 -0
  128. package/src/tools/read-file-parts.tool.ts +80 -0
  129. package/src/tools/remember-memory.tool.ts +85 -0
  130. package/src/tools/research-topic.tool.ts +15 -0
  131. package/src/tools/search-tools.ts +55 -0
  132. package/src/tools/search-web.tool.ts +175 -0
  133. package/src/tools/team-think.tool.ts +125 -0
  134. package/src/tools/tool-contract.ts +21 -0
  135. package/src/tools/user-questions.tool.ts +18 -0
  136. package/src/utils/async.ts +50 -0
  137. package/src/utils/date-time.ts +34 -0
  138. package/src/utils/error.ts +10 -0
  139. package/src/utils/errors.ts +28 -0
  140. package/src/utils/hono-error-handler.ts +71 -0
  141. package/src/utils/string.ts +51 -0
  142. package/src/workers/bootstrap.ts +44 -0
  143. package/src/workers/memory-consolidation.worker.ts +318 -0
  144. package/src/workers/regular-chat-memory-digest.helpers.ts +100 -0
  145. package/src/workers/regular-chat-memory-digest.runner.ts +363 -0
  146. package/src/workers/regular-chat-memory-digest.worker.ts +22 -0
  147. package/src/workers/skill-extraction.runner.ts +331 -0
  148. package/src/workers/skill-extraction.worker.ts +22 -0
  149. package/src/workers/utils/repo-indexer-chunker.ts +331 -0
  150. package/src/workers/utils/repo-structure-extractor.ts +645 -0
  151. package/src/workers/utils/repomix-process-concurrency.ts +65 -0
  152. package/src/workers/utils/sandbox-error.ts +5 -0
  153. package/src/workers/worker-utils.ts +182 -0
@@ -0,0 +1,261 @@
1
+ import { z } from 'zod'
2
+
3
+ const DecisionConfidenceSchema = z.enum(['high', 'medium', 'low'])
4
+ const StateSourceSchema = z.enum(['user', 'agent'])
5
+
6
+ const WorkstreamPlanSchema = z.object({
7
+ id: z.string(),
8
+ text: z.string(),
9
+ source: StateSourceSchema,
10
+ approved: z.boolean(),
11
+ timestamp: z.number(),
12
+ sourceMessageIds: z.array(z.string()).default([]),
13
+ })
14
+
15
+ const WorkstreamConstraintSchema = z.object({
16
+ id: z.string(),
17
+ text: z.string(),
18
+ source: StateSourceSchema,
19
+ approved: z.boolean(),
20
+ timestamp: z.number(),
21
+ sourceMessageIds: z.array(z.string()).default([]),
22
+ })
23
+
24
+ const WorkstreamDecisionSchema = z.object({
25
+ id: z.string(),
26
+ decision: z.string(),
27
+ rationale: z.string(),
28
+ agent: z.string(),
29
+ sourceMessageIds: z.array(z.string()).default([]),
30
+ confidence: DecisionConfidenceSchema,
31
+ timestamp: z.number(),
32
+ })
33
+
34
+ const WorkstreamTaskSchema = z.object({
35
+ id: z.string(),
36
+ title: z.string(),
37
+ status: z.enum(['open', 'in-progress', 'done', 'blocked']),
38
+ owner: z.string(),
39
+ externalId: z.string().optional(),
40
+ source: StateSourceSchema,
41
+ timestamp: z.number(),
42
+ sourceMessageIds: z.array(z.string()).default([]),
43
+ })
44
+
45
+ const WorkstreamQuestionSchema = z.object({
46
+ id: z.string(),
47
+ text: z.string(),
48
+ source: StateSourceSchema,
49
+ timestamp: z.number(),
50
+ sourceMessageIds: z.array(z.string()).default([]),
51
+ })
52
+
53
+ const WorkstreamArtifactSchema = z.object({
54
+ id: z.string(),
55
+ name: z.string(),
56
+ type: z.string(),
57
+ pointer: z.string(),
58
+ timestamp: z.number(),
59
+ })
60
+
61
+ const WorkstreamAgentNoteSchema = z.object({
62
+ id: z.string(),
63
+ agent: z.string(),
64
+ summary: z.string(),
65
+ timestamp: z.number(),
66
+ })
67
+
68
+ export const WorkstreamStateSchema = z.object({
69
+ currentPlan: WorkstreamPlanSchema.nullable(),
70
+ activeConstraints: z.array(WorkstreamConstraintSchema),
71
+ keyDecisions: z.array(WorkstreamDecisionSchema),
72
+ tasks: z.array(WorkstreamTaskSchema),
73
+ openQuestions: z.array(WorkstreamQuestionSchema),
74
+ risks: z.array(z.string()),
75
+ artifacts: z.array(WorkstreamArtifactSchema),
76
+ agentContributions: z.array(WorkstreamAgentNoteSchema),
77
+ approvedBy: z.string().optional(),
78
+ approvedAt: z.number().int().optional(),
79
+ approvalMessageId: z.string().optional(),
80
+ approvalNote: z.string().optional(),
81
+ lastUpdated: z.number(),
82
+ })
83
+
84
+ export type WorkstreamState = z.infer<typeof WorkstreamStateSchema>
85
+
86
+ export const WorkstreamStateDeltaSchema = z.object({
87
+ currentPlan: z.string().nullable().optional(),
88
+ newDecisions: z
89
+ .array(
90
+ z.object({
91
+ decision: z.string(),
92
+ rationale: z.string(),
93
+ agent: z.string(),
94
+ sourceMessageIds: z.array(z.string()),
95
+ confidence: DecisionConfidenceSchema,
96
+ }),
97
+ )
98
+ .optional(),
99
+ resolvedQuestions: z.array(z.string()).optional(),
100
+ newQuestions: z.array(z.string()).optional(),
101
+ newConstraints: z.array(z.string()).optional(),
102
+ newRisks: z.array(z.string()).optional(),
103
+ taskUpdates: z
104
+ .array(
105
+ z.object({
106
+ title: z.string(),
107
+ status: WorkstreamTaskSchema.shape.status,
108
+ owner: z.string(),
109
+ externalId: z.string().nullable(),
110
+ sourceMessageIds: z.array(z.string()),
111
+ }),
112
+ )
113
+ .optional(),
114
+ artifacts: z.array(z.object({ name: z.string(), type: z.string(), pointer: z.string() })).optional(),
115
+ agentNote: z.object({ agent: z.string(), summary: z.string() }).optional(),
116
+ conflicts: z
117
+ .array(z.object({ newFact: z.string(), conflictsWith: z.string(), recommendation: z.string() }))
118
+ .optional(),
119
+ approvedBy: z.string().optional(),
120
+ approvedAt: z.number().int().optional(),
121
+ approvalMessageId: z.string().optional(),
122
+ approvalNote: z.string().optional(),
123
+ })
124
+
125
+ export type WorkstreamStateDelta = z.infer<typeof WorkstreamStateDeltaSchema>
126
+
127
+ const StructuredWorkstreamPlanDeltaSchema = z.object({
128
+ action: z.enum(['unchanged', 'clear', 'set']),
129
+ text: z.string().nullable(),
130
+ })
131
+
132
+ const StructuredWorkstreamDecisionDeltaSchema = z.object({
133
+ decision: z.string(),
134
+ rationale: z.string(),
135
+ agent: z.string(),
136
+ sourceMessageIds: z.array(z.string()),
137
+ confidence: DecisionConfidenceSchema,
138
+ })
139
+
140
+ const StructuredWorkstreamTaskDeltaSchema = z.object({
141
+ title: z.string(),
142
+ status: WorkstreamTaskSchema.shape.status,
143
+ owner: z.string(),
144
+ externalId: z.string().nullable(),
145
+ sourceMessageIds: z.array(z.string()),
146
+ })
147
+
148
+ const StructuredWorkstreamArtifactDeltaSchema = z.object({ name: z.string(), type: z.string(), pointer: z.string() })
149
+
150
+ const StructuredWorkstreamAgentNoteDeltaSchema = z.object({ agent: z.string(), summary: z.string() })
151
+
152
+ const StructuredWorkstreamConflictDeltaSchema = z.object({
153
+ newFact: z.string(),
154
+ conflictsWith: z.string(),
155
+ recommendation: z.string(),
156
+ })
157
+
158
+ export const StructuredWorkstreamStateDeltaSchema = z.object({
159
+ currentPlan: StructuredWorkstreamPlanDeltaSchema,
160
+ newDecisions: z.array(StructuredWorkstreamDecisionDeltaSchema),
161
+ resolvedQuestions: z.array(z.string()),
162
+ newQuestions: z.array(z.string()),
163
+ newConstraints: z.array(z.string()),
164
+ newRisks: z.array(z.string()),
165
+ taskUpdates: z.array(StructuredWorkstreamTaskDeltaSchema),
166
+ artifacts: z.array(StructuredWorkstreamArtifactDeltaSchema),
167
+ agentNote: StructuredWorkstreamAgentNoteDeltaSchema.nullable(),
168
+ conflicts: z.array(StructuredWorkstreamConflictDeltaSchema),
169
+ approvedBy: z.string().nullable(),
170
+ approvedAt: z.number().int().nullable(),
171
+ approvalMessageId: z.string().nullable(),
172
+ approvalNote: z.string().nullable(),
173
+ })
174
+
175
+ export type StructuredWorkstreamStateDelta = z.infer<typeof StructuredWorkstreamStateDeltaSchema>
176
+
177
+ export function createEmptyStructuredWorkstreamStateDelta(): StructuredWorkstreamStateDelta {
178
+ return {
179
+ currentPlan: { action: 'unchanged', text: null },
180
+ newDecisions: [],
181
+ resolvedQuestions: [],
182
+ newQuestions: [],
183
+ newConstraints: [],
184
+ newRisks: [],
185
+ taskUpdates: [],
186
+ artifacts: [],
187
+ agentNote: null,
188
+ conflicts: [],
189
+ approvedBy: null,
190
+ approvedAt: null,
191
+ approvalMessageId: null,
192
+ approvalNote: null,
193
+ }
194
+ }
195
+
196
+ export function parseStructuredWorkstreamStateDelta(value: unknown): WorkstreamStateDelta {
197
+ const parsed = StructuredWorkstreamStateDeltaSchema.parse(value)
198
+
199
+ return {
200
+ ...(parsed.currentPlan.action === 'clear'
201
+ ? { currentPlan: null }
202
+ : parsed.currentPlan.action === 'set' && typeof parsed.currentPlan.text === 'string'
203
+ ? { currentPlan: parsed.currentPlan.text }
204
+ : {}),
205
+ ...(parsed.newDecisions.length > 0 ? { newDecisions: parsed.newDecisions } : {}),
206
+ ...(parsed.resolvedQuestions.length > 0 ? { resolvedQuestions: parsed.resolvedQuestions } : {}),
207
+ ...(parsed.newQuestions.length > 0 ? { newQuestions: parsed.newQuestions } : {}),
208
+ ...(parsed.newConstraints.length > 0 ? { newConstraints: parsed.newConstraints } : {}),
209
+ ...(parsed.newRisks.length > 0 ? { newRisks: parsed.newRisks } : {}),
210
+ ...(parsed.taskUpdates.length > 0
211
+ ? {
212
+ taskUpdates: parsed.taskUpdates.map((taskUpdate) => ({
213
+ title: taskUpdate.title,
214
+ status: taskUpdate.status,
215
+ owner: taskUpdate.owner,
216
+ externalId: taskUpdate.externalId,
217
+ sourceMessageIds: taskUpdate.sourceMessageIds,
218
+ })),
219
+ }
220
+ : {}),
221
+ ...(parsed.artifacts.length > 0 ? { artifacts: parsed.artifacts } : {}),
222
+ ...(parsed.agentNote ? { agentNote: parsed.agentNote } : {}),
223
+ ...(parsed.conflicts.length > 0 ? { conflicts: parsed.conflicts } : {}),
224
+ ...(parsed.approvedBy !== null ? { approvedBy: parsed.approvedBy } : {}),
225
+ ...(parsed.approvedAt !== null ? { approvedAt: parsed.approvedAt } : {}),
226
+ ...(parsed.approvalMessageId !== null ? { approvalMessageId: parsed.approvalMessageId } : {}),
227
+ ...(parsed.approvalNote !== null ? { approvalNote: parsed.approvalNote } : {}),
228
+ }
229
+ }
230
+
231
+ export const StructuredCompactionOutputSchema = z.object({
232
+ summary: z.string(),
233
+ stateDelta: StructuredWorkstreamStateDeltaSchema,
234
+ })
235
+
236
+ export interface CompactionOutput {
237
+ summary: string
238
+ stateDelta: WorkstreamStateDelta
239
+ }
240
+
241
+ export const WORKSTREAM_STATE_MAX_KEY_DECISIONS = 8
242
+ export const WORKSTREAM_STATE_MAX_ACTIVE_CONSTRAINTS = 6
243
+ export const WORKSTREAM_STATE_MAX_TASKS = 10
244
+ export const WORKSTREAM_STATE_MAX_OPEN_QUESTIONS = 5
245
+ export const WORKSTREAM_STATE_MAX_RISKS = 5
246
+ export const WORKSTREAM_STATE_MAX_ARTIFACTS = 10
247
+ export const WORKSTREAM_STATE_MAX_AGENT_CONTRIBUTIONS = 6
248
+
249
+ export function createEmptyWorkstreamState(now = Date.now()): WorkstreamState {
250
+ return {
251
+ currentPlan: null,
252
+ activeConstraints: [],
253
+ keyDecisions: [],
254
+ tasks: [],
255
+ openQuestions: [],
256
+ risks: [],
257
+ artifacts: [],
258
+ agentContributions: [],
259
+ lastUpdated: now,
260
+ }
261
+ }
@@ -0,0 +1,159 @@
1
+ import type { RecordIdRef } from '../db/record-id'
2
+ import { recordIdToString } from '../db/record-id'
3
+ import { TABLES } from '../db/tables'
4
+ import { attachmentStorageService } from '../storage/attachment-storage.service'
5
+ import type { UploadedWorkstreamAttachment as SdkUploadedWorkstreamAttachment } from '../storage/attachment-storage.service'
6
+ import type { MessagePartLike, ReadableUploadMetadata as SdkReadableUploadMetadata } from '../storage/attachments.types'
7
+
8
+ export type ReadableUploadMetadata = SdkReadableUploadMetadata
9
+
10
+ function toOrgId(orgId: RecordIdRef): string {
11
+ return recordIdToString(orgId, TABLES.ORGANIZATION)
12
+ }
13
+
14
+ function toUserId(userId: RecordIdRef): string {
15
+ return recordIdToString(userId, TABLES.USER)
16
+ }
17
+
18
+ class AttachmentService {
19
+ hydrateSignedFileUrlsInMessageParts({
20
+ parts,
21
+ orgId,
22
+ userId,
23
+ }: {
24
+ parts: readonly MessagePartLike[]
25
+ orgId: RecordIdRef
26
+ userId: RecordIdRef
27
+ }): MessagePartLike[] {
28
+ return attachmentStorageService.hydrateSignedFileUrlsInMessageParts({
29
+ parts,
30
+ orgId: toOrgId(orgId),
31
+ userId: toUserId(userId),
32
+ })
33
+ }
34
+
35
+ listReadableUploadsFromMessages({
36
+ messages,
37
+ orgId,
38
+ userId,
39
+ }: {
40
+ messages: ReadonlyArray<{ parts: readonly MessagePartLike[] }>
41
+ orgId: RecordIdRef
42
+ userId: RecordIdRef
43
+ }): ReadableUploadMetadata[] {
44
+ return attachmentStorageService.listReadableUploadsFromMessages({
45
+ messages,
46
+ orgId: toOrgId(orgId),
47
+ userId: toUserId(userId),
48
+ })
49
+ }
50
+
51
+ async extractStoredAttachmentText({
52
+ storageKey,
53
+ name,
54
+ contentType,
55
+ }: {
56
+ storageKey: string
57
+ name: string
58
+ contentType: string
59
+ }): Promise<string> {
60
+ return await attachmentStorageService.extractStoredAttachmentText({ storageKey, name, contentType })
61
+ }
62
+
63
+ async extractStoredAttachmentPages({
64
+ storageKey,
65
+ name,
66
+ contentType,
67
+ }: {
68
+ storageKey: string
69
+ name: string
70
+ contentType: string
71
+ }): Promise<{ pageMode: 'logical' | 'pdf'; pages: string[] }> {
72
+ return await attachmentStorageService.extractStoredAttachmentPages({ storageKey, name, contentType })
73
+ }
74
+
75
+ async readFilePartsFromUpload({
76
+ upload,
77
+ orgId,
78
+ userId,
79
+ part,
80
+ pagesPerPart,
81
+ }: {
82
+ upload: ReadableUploadMetadata
83
+ orgId: RecordIdRef
84
+ userId: RecordIdRef
85
+ part?: number
86
+ pagesPerPart?: number
87
+ }) {
88
+ return await attachmentStorageService.readFilePartsFromUpload({
89
+ upload,
90
+ orgId: toOrgId(orgId),
91
+ userId: toUserId(userId),
92
+ part,
93
+ pagesPerPart,
94
+ })
95
+ }
96
+
97
+ getAttachmentUrl(storageKey: string): string {
98
+ return attachmentStorageService.getAttachmentUrl(storageKey)
99
+ }
100
+
101
+ async writeOrganizationDocument({
102
+ orgId,
103
+ namespace,
104
+ relativePath,
105
+ content,
106
+ contentType,
107
+ }: {
108
+ orgId: RecordIdRef
109
+ namespace: string
110
+ relativePath: string
111
+ content: string
112
+ contentType: string
113
+ }): Promise<{ storageKey: string; sizeBytes: number }> {
114
+ return await attachmentStorageService.writeOrganizationDocument({
115
+ orgId: toOrgId(orgId),
116
+ namespace,
117
+ relativePath,
118
+ content,
119
+ contentType,
120
+ })
121
+ }
122
+
123
+ async uploadOrganizationDocument({
124
+ file,
125
+ orgId,
126
+ namespace,
127
+ relativePath,
128
+ }: {
129
+ file: File
130
+ orgId: RecordIdRef
131
+ namespace: string
132
+ relativePath: string
133
+ }) {
134
+ return await attachmentStorageService.uploadOrganizationDocument({
135
+ file,
136
+ orgId: toOrgId(orgId),
137
+ namespace,
138
+ relativePath,
139
+ })
140
+ }
141
+
142
+ async uploadWorkstreamAttachment({
143
+ file,
144
+ orgId,
145
+ userId,
146
+ }: {
147
+ file: File
148
+ orgId: RecordIdRef
149
+ userId: RecordIdRef
150
+ }): Promise<SdkUploadedWorkstreamAttachment> {
151
+ return await attachmentStorageService.uploadWorkstreamAttachment({
152
+ file,
153
+ orgId: toOrgId(orgId),
154
+ userId: toUserId(userId),
155
+ })
156
+ }
157
+ }
158
+
159
+ export const attachmentService = new AttachmentService()
@@ -0,0 +1,17 @@
1
+ import type { ChatMessage } from '@lota-sdk/shared/schemas/chat-message'
2
+
3
+ import type { RecordIdRef } from '../db/record-id'
4
+ import { attachmentService } from './attachment.service'
5
+ import type { ReadableUploadMetadata } from './attachment.service'
6
+
7
+ export function listReadableUploadsFromChatMessages(params: {
8
+ messages: ChatMessage[]
9
+ orgId: RecordIdRef
10
+ userId: RecordIdRef
11
+ }): ReadableUploadMetadata[] {
12
+ return attachmentService.listReadableUploadsFromMessages({
13
+ messages: params.messages.map((message) => ({ parts: message.parts as Array<Record<string, unknown>> })),
14
+ orgId: params.orgId,
15
+ userId: params.userId,
16
+ })
17
+ }
@@ -0,0 +1,3 @@
1
+ import { ChatRunRegistry } from '../runtime/chat-run-registry'
2
+
3
+ export const chatRunRegistry = new ChatRunRegistry()
@@ -0,0 +1,13 @@
1
+ import { createWiredContextCompactionRuntime } from '../runtime/context-compaction-runtime'
2
+ import { createHelperModelRuntime } from '../runtime/helper-model'
3
+
4
+ const helperModelRuntime = createHelperModelRuntime()
5
+
6
+ const wiredRuntime = createWiredContextCompactionRuntime({
7
+ helperModelRuntime,
8
+ now: () => Date.now(),
9
+ randomId: () => Bun.randomUUIDv7(),
10
+ })
11
+
12
+ export const contextCompactionRuntime = wiredRuntime
13
+ export const compactMemoryBlockSummary = wiredRuntime.compactMemoryBlockSummary
@@ -0,0 +1,115 @@
1
+ import type { ChatMessage } from '@lota-sdk/shared/schemas/chat-message'
2
+
3
+ import { chatLogger } from '../config/logger'
4
+ import type { RecordIdRef } from '../db/record-id'
5
+ import { recordIdToString } from '../db/record-id'
6
+ import { databaseService } from '../db/service'
7
+ import { TABLES } from '../db/tables'
8
+ import { parseWorkstreamState, toStateFieldsUpdated } from '../runtime/context-compaction'
9
+ import { CONTEXT_SIZE, WORKSTREAM_RAW_TAIL_MESSAGES } from '../runtime/context-compaction-constants'
10
+ import type { WorkstreamState } from '../runtime/workstream-state'
11
+ import { compactMemoryBlockSummary, contextCompactionRuntime } from './context-compaction-runtime'
12
+ import { workstreamMessageService } from './workstream-message.service'
13
+ import { WorkstreamSchema } from './workstream.types'
14
+
15
+ interface PersistedCompactionMetrics {
16
+ domain: 'workstream'
17
+ entityId: string
18
+ inputChars: number
19
+ outputChars: number
20
+ savedChars: number
21
+ summaryLength: number
22
+ compactedMessageCount: number
23
+ remainingMessageCount: number
24
+ estimatedTokens: number
25
+ stateFieldsUpdated: string[]
26
+ conflictsDetected: number
27
+ }
28
+
29
+ class ContextCompactionService {
30
+ createSummaryMessage(summaryText: string) {
31
+ return contextCompactionRuntime.createSummaryMessage(summaryText)
32
+ }
33
+
34
+ formatWorkstreamStateForPrompt(state: WorkstreamState | null | undefined) {
35
+ return contextCompactionRuntime.formatWorkstreamStateForPrompt(state)
36
+ }
37
+
38
+ estimateThreshold(contextSize = CONTEXT_SIZE): number {
39
+ return contextCompactionRuntime.estimateThreshold(contextSize)
40
+ }
41
+
42
+ shouldCompactHistory(params: { summaryText: string; liveMessages: ChatMessage[]; contextSize?: number }) {
43
+ return contextCompactionRuntime.shouldCompactHistory(params)
44
+ }
45
+
46
+ async compactWorkstreamHistory(params: {
47
+ workstreamId: RecordIdRef
48
+ contextSize?: number
49
+ }): Promise<{ compacted: boolean; state: WorkstreamState | null }> {
50
+ const workstream = await databaseService.findOne(TABLES.WORKSTREAM, { id: params.workstreamId }, WorkstreamSchema)
51
+ if (!workstream) {
52
+ throw new Error(
53
+ `Workstream not found for compaction: ${recordIdToString(params.workstreamId, TABLES.WORKSTREAM)}`,
54
+ )
55
+ }
56
+
57
+ const currentState = parseWorkstreamState(workstream.state)
58
+ const liveMessages = await workstreamMessageService.listMessagesAfterCursor(
59
+ params.workstreamId,
60
+ typeof workstream.lastCompactedMessageId === 'string' ? workstream.lastCompactedMessageId : undefined,
61
+ )
62
+
63
+ const result = await contextCompactionRuntime.compactHistory({
64
+ summaryText: typeof workstream.chatSummary === 'string' ? workstream.chatSummary : '',
65
+ liveMessages,
66
+ tailMessageCount: WORKSTREAM_RAW_TAIL_MESSAGES,
67
+ contextSize: params.contextSize,
68
+ existingState: currentState,
69
+ })
70
+
71
+ if (!result.compacted || !result.lastCompactedMessageId) {
72
+ return { compacted: false, state: currentState }
73
+ }
74
+
75
+ if (result.compactedMessages.length > 0) {
76
+ await workstreamMessageService.upsertMessages({
77
+ workstreamId: params.workstreamId,
78
+ messages: result.compactedMessages,
79
+ })
80
+ }
81
+
82
+ await databaseService.update(
83
+ TABLES.WORKSTREAM,
84
+ params.workstreamId,
85
+ { chatSummary: result.summaryText, lastCompactedMessageId: result.lastCompactedMessageId, state: result.state },
86
+ WorkstreamSchema,
87
+ )
88
+
89
+ this.logCompactionMetrics({
90
+ domain: 'workstream',
91
+ entityId: recordIdToString(params.workstreamId, TABLES.WORKSTREAM),
92
+ inputChars: result.inputChars,
93
+ outputChars: result.outputChars,
94
+ savedChars: Math.max(0, result.inputChars - result.outputChars),
95
+ summaryLength: result.summaryText.length,
96
+ compactedMessageCount: result.compactedMessageCount,
97
+ remainingMessageCount: result.remainingMessageCount,
98
+ estimatedTokens: result.estimatedTokens,
99
+ stateFieldsUpdated: toStateFieldsUpdated(result.stateDelta),
100
+ conflictsDetected: result.stateDelta.conflicts?.length ?? 0,
101
+ })
102
+
103
+ return { compacted: true, state: result.state }
104
+ }
105
+
106
+ async compactMemoryBlock(params: { previousSummary: string; newEntriesText: string }): Promise<string> {
107
+ return await compactMemoryBlockSummary(params)
108
+ }
109
+
110
+ private logCompactionMetrics(metrics: PersistedCompactionMetrics): void {
111
+ chatLogger.info`Persisted chat compaction applied metrics=${JSON.stringify(metrics)}`
112
+ }
113
+ }
114
+
115
+ export const contextCompactionService = new ContextCompactionService()