@lota-sdk/core 0.4.8 → 0.4.9

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 (259) hide show
  1. package/package.json +11 -12
  2. package/src/ai/embedding-cache.ts +94 -22
  3. package/src/ai-gateway/ai-gateway.ts +738 -223
  4. package/src/config/agent-defaults.ts +176 -75
  5. package/src/config/agent-types.ts +54 -4
  6. package/src/config/constants.ts +8 -2
  7. package/src/config/logger.ts +286 -19
  8. package/src/config/thread-defaults.ts +33 -21
  9. package/src/create-runtime.ts +725 -387
  10. package/src/db/base.service.ts +52 -28
  11. package/src/db/cursor-pagination.ts +71 -30
  12. package/src/db/memory-store.helpers.ts +4 -7
  13. package/src/db/memory-store.ts +856 -598
  14. package/src/db/memory.ts +398 -275
  15. package/src/db/record-id.ts +32 -10
  16. package/src/db/schema-fingerprint.ts +30 -12
  17. package/src/db/service-normalization.ts +255 -0
  18. package/src/db/service.ts +726 -761
  19. package/src/db/startup.ts +140 -66
  20. package/src/db/transaction-conflict.ts +15 -0
  21. package/src/effect/awaitable-effect.ts +87 -0
  22. package/src/effect/errors.ts +121 -0
  23. package/src/effect/helpers.ts +98 -0
  24. package/src/effect/index.ts +22 -0
  25. package/src/effect/layers.ts +228 -0
  26. package/src/effect/runtime-ref.ts +25 -0
  27. package/src/effect/runtime.ts +31 -0
  28. package/src/effect/services.ts +57 -0
  29. package/src/effect/zod.ts +43 -0
  30. package/src/embeddings/provider.ts +122 -76
  31. package/src/index.ts +46 -1
  32. package/src/openrouter/direct-provider.ts +11 -35
  33. package/src/queues/autonomous-job.queue.ts +130 -74
  34. package/src/queues/context-compaction.queue.ts +60 -15
  35. package/src/queues/delayed-node-promotion.queue.ts +52 -15
  36. package/src/queues/document-processor.queue.ts +52 -77
  37. package/src/queues/memory-consolidation.queue.ts +47 -32
  38. package/src/queues/organization-learning.queue.ts +13 -4
  39. package/src/queues/plan-agent-heartbeat.queue.ts +65 -21
  40. package/src/queues/plan-scheduler.queue.ts +107 -31
  41. package/src/queues/post-chat-memory.queue.ts +66 -24
  42. package/src/queues/queue-factory.ts +142 -52
  43. package/src/queues/standalone-worker.ts +39 -0
  44. package/src/queues/title-generation.queue.ts +54 -9
  45. package/src/redis/connection.ts +84 -32
  46. package/src/redis/index.ts +6 -8
  47. package/src/redis/org-memory-lock.ts +60 -27
  48. package/src/redis/redis-lease-lock.ts +200 -121
  49. package/src/redis/runtime-connection.ts +10 -0
  50. package/src/redis/stream-context.ts +84 -46
  51. package/src/runtime/agent-identity-overrides.ts +2 -2
  52. package/src/runtime/agent-runtime-policy.ts +4 -1
  53. package/src/runtime/agent-stream-helpers.ts +20 -9
  54. package/src/runtime/chat-run-orchestration.ts +102 -19
  55. package/src/runtime/chat-run-registry.ts +36 -2
  56. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  57. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +114 -91
  58. package/src/runtime/execution-plan-visibility.ts +2 -2
  59. package/src/runtime/execution-plan.ts +42 -15
  60. package/src/runtime/graph-designer.ts +11 -7
  61. package/src/runtime/helper-model.ts +135 -48
  62. package/src/runtime/index.ts +7 -7
  63. package/src/runtime/indexed-repositories-policy.ts +3 -3
  64. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +40 -36
  65. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  66. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +1 -1
  67. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  68. package/src/runtime/{memory-scope.ts → memory/memory-scope.ts} +12 -6
  69. package/src/runtime/plugin-resolution.ts +144 -24
  70. package/src/runtime/plugin-types.ts +9 -1
  71. package/src/runtime/post-turn-side-effects.ts +197 -130
  72. package/src/runtime/retrieval-adapters.ts +38 -4
  73. package/src/runtime/runtime-config.ts +150 -61
  74. package/src/runtime/runtime-extensions.ts +21 -34
  75. package/src/runtime/social-chat/social-chat-agent-runner.ts +157 -0
  76. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +42 -20
  77. package/src/runtime/social-chat/social-chat.ts +594 -0
  78. package/src/runtime/specialist-runner.ts +36 -10
  79. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +427 -0
  80. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  81. package/src/runtime/thread-chat-helpers.ts +2 -2
  82. package/src/runtime/thread-plan-turn.ts +2 -1
  83. package/src/runtime/thread-turn-context.ts +172 -94
  84. package/src/runtime/turn-lifecycle.ts +93 -27
  85. package/src/services/agent-activity.service.ts +287 -203
  86. package/src/services/agent-executor.service.ts +329 -217
  87. package/src/services/artifact.service.ts +225 -148
  88. package/src/services/attachment.service.ts +137 -115
  89. package/src/services/autonomous-job.service.ts +888 -491
  90. package/src/services/chat-run-registry.service.ts +11 -1
  91. package/src/services/context-compaction.service.ts +136 -86
  92. package/src/services/document-chunk.service.ts +162 -90
  93. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  94. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  95. package/src/services/execution-plan/execution-plan-graph.ts +256 -0
  96. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  97. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  98. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  99. package/src/services/feedback-loop.service.ts +132 -76
  100. package/src/services/global-orchestrator.service.ts +80 -170
  101. package/src/services/graph-full-routing.ts +182 -0
  102. package/src/services/index.ts +18 -21
  103. package/src/services/institutional-memory.service.ts +220 -123
  104. package/src/services/learned-skill.service.ts +364 -259
  105. package/src/services/memory/memory-conversation.ts +95 -0
  106. package/src/services/memory/memory-org-memory.ts +39 -0
  107. package/src/services/memory/memory-preseeded.ts +80 -0
  108. package/src/services/memory/memory-rerank.ts +297 -0
  109. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +5 -5
  110. package/src/services/memory/memory.service.ts +692 -0
  111. package/src/services/memory/rerank.service.ts +209 -0
  112. package/src/services/monitoring-window.service.ts +92 -70
  113. package/src/services/mutating-approval.service.ts +62 -53
  114. package/src/services/node-workspace.service.ts +141 -98
  115. package/src/services/notification.service.ts +17 -16
  116. package/src/services/organization-member.service.ts +120 -66
  117. package/src/services/organization.service.ts +144 -51
  118. package/src/services/ownership-dispatcher.service.ts +415 -264
  119. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  120. package/src/services/plan/plan-agent-query.service.ts +322 -0
  121. package/src/services/plan/plan-approval.service.ts +102 -0
  122. package/src/services/plan/plan-artifact.service.ts +60 -0
  123. package/src/services/plan/plan-builder.service.ts +76 -0
  124. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  125. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  126. package/src/services/plan/plan-completion-side-effects.ts +175 -0
  127. package/src/services/plan/plan-coordination.service.ts +181 -0
  128. package/src/services/plan/plan-cycle.service.ts +398 -0
  129. package/src/services/plan/plan-deadline.service.ts +547 -0
  130. package/src/services/plan/plan-event-delivery.service.ts +261 -0
  131. package/src/services/plan/plan-executor-context.ts +35 -0
  132. package/src/services/plan/plan-executor-graph.ts +475 -0
  133. package/src/services/plan/plan-executor-helpers.ts +322 -0
  134. package/src/services/plan/plan-executor-persistence.ts +209 -0
  135. package/src/services/plan/plan-executor.service.ts +1654 -0
  136. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  137. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  138. package/src/services/plan/plan-run-serialization.ts +15 -0
  139. package/src/services/plan/plan-run.service.ts +644 -0
  140. package/src/services/plan/plan-scheduler.service.ts +385 -0
  141. package/src/services/plan/plan-template.service.ts +224 -0
  142. package/src/services/plan/plan-transaction-events.ts +33 -0
  143. package/src/services/plan/plan-validator.service.ts +907 -0
  144. package/src/services/plan/plan-workspace.service.ts +125 -0
  145. package/src/services/plugin-executor.service.ts +97 -68
  146. package/src/services/quality-metrics.service.ts +112 -94
  147. package/src/services/queue-job.service.ts +296 -230
  148. package/src/services/recent-activity-title.service.ts +65 -36
  149. package/src/services/recent-activity.service.ts +274 -259
  150. package/src/services/skill-resolver.service.ts +38 -12
  151. package/src/services/social-chat-history.service.ts +176 -125
  152. package/src/services/system-executor.service.ts +91 -61
  153. package/src/services/thread/thread-active-run.ts +203 -0
  154. package/src/services/thread/thread-bootstrap.ts +369 -0
  155. package/src/services/thread/thread-listing.ts +198 -0
  156. package/src/services/thread/thread-memory-block.ts +117 -0
  157. package/src/services/thread/thread-message.service.ts +363 -0
  158. package/src/services/thread/thread-record-store.ts +155 -0
  159. package/src/services/thread/thread-title.service.ts +74 -0
  160. package/src/services/thread/thread-turn-execution.ts +280 -0
  161. package/src/services/thread/thread-turn-message-context.ts +73 -0
  162. package/src/services/thread/thread-turn-preparation.service.ts +1146 -0
  163. package/src/services/thread/thread-turn-streaming.ts +402 -0
  164. package/src/services/thread/thread-turn-tracing.ts +35 -0
  165. package/src/services/thread/thread-turn.ts +343 -0
  166. package/src/services/thread/thread.service.ts +335 -0
  167. package/src/services/user.service.ts +82 -32
  168. package/src/services/write-intent-validator.service.ts +63 -51
  169. package/src/storage/attachment-parser.ts +69 -27
  170. package/src/storage/attachment-storage.service.ts +331 -275
  171. package/src/storage/generated-document-storage.service.ts +66 -34
  172. package/src/system-agents/agent-result.ts +3 -1
  173. package/src/system-agents/context-compaction.agent.ts +2 -2
  174. package/src/system-agents/delegated-agent-factory.ts +159 -90
  175. package/src/system-agents/memory-reranker.agent.ts +2 -2
  176. package/src/system-agents/memory.agent.ts +2 -2
  177. package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
  178. package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -2
  179. package/src/system-agents/skill-extractor.agent.ts +2 -2
  180. package/src/system-agents/skill-manager.agent.ts +2 -2
  181. package/src/system-agents/thread-router.agent.ts +157 -113
  182. package/src/system-agents/title-generator.agent.ts +2 -2
  183. package/src/tools/execution-plan.tool.ts +220 -161
  184. package/src/tools/fetch-webpage.tool.ts +21 -17
  185. package/src/tools/firecrawl-client.ts +16 -6
  186. package/src/tools/index.ts +1 -0
  187. package/src/tools/memory-block.tool.ts +14 -6
  188. package/src/tools/plan-approval.tool.ts +49 -47
  189. package/src/tools/read-file-parts.tool.ts +44 -33
  190. package/src/tools/remember-memory.tool.ts +65 -45
  191. package/src/tools/search-web.tool.ts +26 -22
  192. package/src/tools/search.tool.ts +41 -29
  193. package/src/tools/team-think.tool.ts +124 -83
  194. package/src/tools/user-questions.tool.ts +4 -3
  195. package/src/tools/web-tool-shared.ts +6 -0
  196. package/src/utils/async.ts +17 -23
  197. package/src/utils/crypto.ts +21 -0
  198. package/src/utils/date-time.ts +40 -1
  199. package/src/utils/errors.ts +95 -16
  200. package/src/utils/hono-error-handler.ts +24 -39
  201. package/src/utils/index.ts +2 -1
  202. package/src/utils/null-proto-record.ts +41 -0
  203. package/src/utils/sse-keepalive.ts +124 -21
  204. package/src/workers/bootstrap.ts +186 -51
  205. package/src/workers/memory-consolidation.worker.ts +325 -237
  206. package/src/workers/organization-learning.worker.ts +50 -16
  207. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  208. package/src/workers/regular-chat-memory-digest.runner.ts +175 -114
  209. package/src/workers/skill-extraction.runner.ts +176 -93
  210. package/src/workers/utils/file-section-chunker.ts +8 -10
  211. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  212. package/src/workers/utils/repomix-file-sections.ts +2 -2
  213. package/src/workers/utils/thread-message-query.ts +97 -38
  214. package/src/workers/worker-utils.ts +56 -31
  215. package/src/config/debug-logger.ts +0 -47
  216. package/src/redis/connection-accessor.ts +0 -26
  217. package/src/runtime/context-compaction-runtime.ts +0 -87
  218. package/src/runtime/social-chat-agent-runner.ts +0 -118
  219. package/src/runtime/social-chat.ts +0 -516
  220. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  221. package/src/services/adaptive-playbook.service.ts +0 -152
  222. package/src/services/artifact-provenance.service.ts +0 -172
  223. package/src/services/chat-attachments.service.ts +0 -17
  224. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  225. package/src/services/execution-plan.service.ts +0 -1118
  226. package/src/services/memory.service.ts +0 -914
  227. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  228. package/src/services/plan-agent-query.service.ts +0 -267
  229. package/src/services/plan-approval.service.ts +0 -83
  230. package/src/services/plan-artifact.service.ts +0 -50
  231. package/src/services/plan-builder.service.ts +0 -67
  232. package/src/services/plan-checkpoint.service.ts +0 -81
  233. package/src/services/plan-completion-side-effects.ts +0 -80
  234. package/src/services/plan-coordination.service.ts +0 -157
  235. package/src/services/plan-cycle.service.ts +0 -284
  236. package/src/services/plan-deadline.service.ts +0 -430
  237. package/src/services/plan-event-delivery.service.ts +0 -166
  238. package/src/services/plan-executor.service.ts +0 -1950
  239. package/src/services/plan-run.service.ts +0 -515
  240. package/src/services/plan-scheduler.service.ts +0 -240
  241. package/src/services/plan-template.service.ts +0 -177
  242. package/src/services/plan-validator.service.ts +0 -818
  243. package/src/services/plan-workspace.service.ts +0 -83
  244. package/src/services/rerank.service.ts +0 -156
  245. package/src/services/thread-message.service.ts +0 -275
  246. package/src/services/thread-plan-registry.service.ts +0 -22
  247. package/src/services/thread-title.service.ts +0 -39
  248. package/src/services/thread-turn-preparation.service.ts +0 -1147
  249. package/src/services/thread-turn.ts +0 -172
  250. package/src/services/thread.service.ts +0 -869
  251. package/src/utils/env.ts +0 -8
  252. /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
  253. /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
  254. /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
  255. /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
  256. /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
  257. /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
  258. /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
  259. /package/src/services/{thread.types.ts → thread/thread.types.ts} +0 -0
@@ -1,56 +1,106 @@
1
1
  import type { SdkUser, SdkUserRecord } from '@lota-sdk/shared'
2
2
  import { sdkUserRecordSchema, sdkUserSchema } from '@lota-sdk/shared'
3
+ import { Context, Schema, Effect, Layer } from 'effect'
3
4
 
4
- import { BaseService } from '../db/base.service'
5
5
  import { ensureRecordId, recordIdToString } from '../db/record-id'
6
6
  import type { RecordIdInput } from '../db/record-id'
7
- import { databaseService } from '../db/service'
7
+ import type { SurrealDBService } from '../db/service'
8
8
  import { TABLES } from '../db/tables'
9
+ import { NotFoundError } from '../effect/errors'
10
+ import { DatabaseServiceTag } from '../effect/services'
9
11
  import { toIsoDateTimeString } from '../utils/date-time'
10
12
 
11
- class UserService extends BaseService<typeof sdkUserRecordSchema> {
12
- constructor() {
13
- super(TABLES.USER, sdkUserRecordSchema)
14
- }
13
+ function toPublic(record: SdkUserRecord): SdkUser {
14
+ return sdkUserSchema.parse({
15
+ id: recordIdToString(ensureRecordId(record.id as RecordIdInput, TABLES.USER), TABLES.USER),
16
+ name: record.name,
17
+ email: record.email,
18
+ createdAt: toIsoDateTimeString(record.createdAt),
19
+ updatedAt: toIsoDateTimeString(record.updatedAt),
20
+ })
21
+ }
15
22
 
16
- toPublic(record: SdkUserRecord): SdkUser {
17
- return sdkUserSchema.parse({
18
- id: recordIdToString(ensureRecordId(record.id as RecordIdInput, TABLES.USER), TABLES.USER),
19
- name: record.name,
20
- email: record.email,
21
- createdAt: toIsoDateTimeString(record.createdAt),
22
- updatedAt: toIsoDateTimeString(record.updatedAt),
23
- })
24
- }
23
+ function userNotFoundError(userId: RecordIdInput): NotFoundError {
24
+ return new NotFoundError({
25
+ resource: TABLES.USER,
26
+ id: recordIdToString(userId, TABLES.USER),
27
+ message: `Record not found in ${TABLES.USER}: ${recordIdToString(userId, TABLES.USER)}`,
28
+ })
29
+ }
30
+
31
+ class UserServiceError extends Schema.TaggedErrorClass<UserServiceError>()('UserServiceError', {
32
+ operation: Schema.String,
33
+ cause: Schema.Defect,
34
+ }) {}
35
+
36
+ function toUserServiceError(operation: string, cause: unknown): UserServiceError {
37
+ return new UserServiceError({ operation, cause })
38
+ }
25
39
 
26
- async upsertUser(params: { id: RecordIdInput; name: string; email: string }): Promise<SdkUser> {
40
+ export function makeUserService(db: SurrealDBService) {
41
+ function upsertUserEffect(params: { id: RecordIdInput; name: string; email: string }) {
27
42
  const userRef = ensureRecordId(params.id, TABLES.USER)
28
- const record = await databaseService.upsert(
29
- TABLES.USER,
30
- userRef,
31
- { name: params.name, email: params.email },
32
- sdkUserRecordSchema,
43
+ return db.upsert(TABLES.USER, userRef, { name: params.name, email: params.email }, sdkUserRecordSchema).pipe(
44
+ Effect.mapError((cause) => toUserServiceError('upsertUser', cause)),
45
+ Effect.map(toPublic),
33
46
  )
34
- return this.toPublic(record)
35
47
  }
36
48
 
37
- async getUser(userId: RecordIdInput): Promise<SdkUser> {
38
- return this.toPublic(await this.getById(ensureRecordId(userId, TABLES.USER)))
49
+ function getUserEffect(userId: RecordIdInput) {
50
+ return db.findOne(TABLES.USER, { id: ensureRecordId(userId, TABLES.USER) }, sdkUserRecordSchema).pipe(
51
+ Effect.mapError((cause) => toUserServiceError('getUser', cause)),
52
+ Effect.flatMap((record) => (record ? Effect.succeed(record) : Effect.fail(userNotFoundError(userId)))),
53
+ Effect.map(toPublic),
54
+ )
39
55
  }
40
56
 
41
- async listUsers(): Promise<SdkUser[]> {
42
- return (await this.findAll({}, { orderBy: 'createdAt', orderDir: 'ASC' })).map((record) => this.toPublic(record))
57
+ function listUsersEffect() {
58
+ return db.findMany(TABLES.USER, {}, sdkUserRecordSchema, { orderBy: 'createdAt', orderDir: 'ASC' }).pipe(
59
+ Effect.mapError((cause) => toUserServiceError('listUsers', cause)),
60
+ Effect.map((records) => records.map(toPublic)),
61
+ )
43
62
  }
44
63
 
45
- async updateUser(userId: RecordIdInput, params: { name?: string; email?: string }): Promise<SdkUser> {
46
- return this.toPublic(await this.update(ensureRecordId(userId, TABLES.USER), params))
64
+ function updateUserEffect(userId: RecordIdInput, params: { name?: string; email?: string }) {
65
+ return db.update(TABLES.USER, ensureRecordId(userId, TABLES.USER), params, sdkUserRecordSchema).pipe(
66
+ Effect.mapError((cause) => toUserServiceError('updateUser', cause)),
67
+ Effect.flatMap((updated) => (updated ? Effect.succeed(updated) : Effect.fail(userNotFoundError(userId)))),
68
+ Effect.map(toPublic),
69
+ )
47
70
  }
48
71
 
49
- async deleteUser(userId: RecordIdInput): Promise<void> {
72
+ function deleteUserEffect(userId: RecordIdInput) {
50
73
  const userRef = ensureRecordId(userId, TABLES.USER)
51
- await databaseService.deleteWhere(TABLES.ORGANIZATION_MEMBER, { in: userRef })
52
- await this.delete(userRef)
74
+ return Effect.gen(function* () {
75
+ yield* db
76
+ .deleteWhere(TABLES.ORGANIZATION_MEMBER, { in: userRef })
77
+ .pipe(Effect.mapError((cause) => toUserServiceError('deleteUser.deleteMemberships', cause)))
78
+ const deleted = yield* db
79
+ .deleteById(TABLES.USER, userRef)
80
+ .pipe(Effect.mapError((cause) => toUserServiceError('deleteUser.deleteUser', cause)))
81
+ if (!deleted) {
82
+ return yield* userNotFoundError(userId)
83
+ }
84
+ })
85
+ }
86
+
87
+ return {
88
+ upsertUser: upsertUserEffect,
89
+ getUser: getUserEffect,
90
+ listUsers: listUsersEffect,
91
+ updateUser: updateUserEffect,
92
+ deleteUser: deleteUserEffect,
53
93
  }
54
94
  }
55
95
 
56
- export const userService = new UserService()
96
+ export class UserServiceTag extends Context.Service<UserServiceTag, ReturnType<typeof makeUserService>>()(
97
+ 'UserService',
98
+ ) {}
99
+
100
+ export const UserServiceLive = Layer.effect(
101
+ UserServiceTag,
102
+ Effect.gen(function* () {
103
+ const db = yield* DatabaseServiceTag
104
+ return makeUserService(db)
105
+ }),
106
+ )
@@ -1,6 +1,8 @@
1
1
  import type { PlanDataSchema, PlanNodeSpec, PlanSchemaRegistry, WriteIntent } from '@lota-sdk/shared'
2
+ import { Context, Layer } from 'effect'
2
3
 
3
- import { validateSchemaValue } from './plan-validator.service'
4
+ import { nowIsoDateTimeString } from '../utils/date-time'
5
+ import { validateSchemaValue } from './plan/plan-validator.service'
4
6
 
5
7
  export interface WriteValidationIssue {
6
8
  code: string
@@ -15,19 +17,51 @@ export interface WriteValidationResult {
15
17
  validatedAt: string
16
18
  }
17
19
 
18
- class WriteIntentValidatorService {
19
- validate(params: {
20
- intent: WriteIntent
21
- nodeSpec: PlanNodeSpec
22
- schemaRegistry: PlanSchemaRegistry
23
- existingDeliverables: Map<string, unknown>
24
- }): WriteValidationResult {
25
- const issues: WriteValidationIssue[] = []
26
- const { intent, nodeSpec, schemaRegistry, existingDeliverables } = params
20
+ function buildWriteValidationResult(issues: WriteValidationIssue[]): WriteValidationResult {
21
+ const hasFailed = issues.length > 0
22
+ return {
23
+ status: hasFailed ? 'fail' : 'pass',
24
+ issues,
25
+ ...(hasFailed ? { suggestion: issues.map((i) => i.message).join('; ') } : {}),
26
+ validatedAt: nowIsoDateTimeString(),
27
+ }
28
+ }
29
+
30
+ export function makeWriteIntentValidatorService() {
31
+ return {
32
+ validate(params: {
33
+ intent: WriteIntent
34
+ nodeSpec: PlanNodeSpec
35
+ schemaRegistry: PlanSchemaRegistry
36
+ existingDeliverables: Map<string, unknown>
37
+ }): WriteValidationResult {
38
+ const issues: WriteValidationIssue[] = []
39
+ const { intent, nodeSpec, schemaRegistry, existingDeliverables } = params
40
+
41
+ if (intent.targetPath.startsWith('structuredOutput')) {
42
+ if (nodeSpec.outputSchemaRef) {
43
+ const schema = schemaRegistry[nodeSpec.outputSchemaRef] as PlanDataSchema | undefined
44
+ if (schema) {
45
+ const schemaIssues = validateSchemaValue({ schema, value: intent.payload, path: intent.targetPath })
46
+ for (const message of schemaIssues) {
47
+ issues.push({ code: 'schema_validation_failed', message })
48
+ }
49
+ }
50
+ }
51
+ return buildWriteValidationResult(issues)
52
+ }
27
53
 
28
- if (intent.targetPath.startsWith('structuredOutput')) {
29
- if (nodeSpec.outputSchemaRef) {
30
- const schema = schemaRegistry[nodeSpec.outputSchemaRef] as PlanDataSchema | undefined
54
+ const deliverable = nodeSpec.deliverables.find((d) => d.name === intent.targetPath)
55
+ if (!deliverable) {
56
+ issues.push({
57
+ code: 'unknown_deliverable',
58
+ message: `"${intent.targetPath}" does not match any declared deliverable.`,
59
+ })
60
+ return buildWriteValidationResult(issues)
61
+ }
62
+
63
+ if (deliverable.schemaRef) {
64
+ const schema = schemaRegistry[deliverable.schemaRef] as PlanDataSchema | undefined
31
65
  if (schema) {
32
66
  const schemaIssues = validateSchemaValue({ schema, value: intent.payload, path: intent.targetPath })
33
67
  for (const message of schemaIssues) {
@@ -35,47 +69,25 @@ class WriteIntentValidatorService {
35
69
  }
36
70
  }
37
71
  }
38
- return this.buildResult(issues)
39
- }
40
-
41
- const deliverable = nodeSpec.deliverables.find((d) => d.name === intent.targetPath)
42
- if (!deliverable) {
43
- issues.push({
44
- code: 'unknown_deliverable',
45
- message: `"${intent.targetPath}" does not match any declared deliverable.`,
46
- })
47
- return this.buildResult(issues)
48
- }
49
72
 
50
- if (deliverable.schemaRef) {
51
- const schema = schemaRegistry[deliverable.schemaRef] as PlanDataSchema | undefined
52
- if (schema) {
53
- const schemaIssues = validateSchemaValue({ schema, value: intent.payload, path: intent.targetPath })
54
- for (const message of schemaIssues) {
55
- issues.push({ code: 'schema_validation_failed', message })
56
- }
73
+ if (intent.action === 'update' && !existingDeliverables.has(intent.targetPath)) {
74
+ issues.push({
75
+ code: 'update_target_not_found',
76
+ message: `Cannot update "${intent.targetPath}" no prior write exists.`,
77
+ })
57
78
  }
58
- }
59
-
60
- if (intent.action === 'update' && !existingDeliverables.has(intent.targetPath)) {
61
- issues.push({
62
- code: 'update_target_not_found',
63
- message: `Cannot update "${intent.targetPath}" — no prior write exists.`,
64
- })
65
- }
66
79
 
67
- return this.buildResult(issues)
68
- }
69
-
70
- private buildResult(issues: WriteValidationIssue[]): WriteValidationResult {
71
- const hasFailed = issues.length > 0
72
- return {
73
- status: hasFailed ? 'fail' : 'pass',
74
- issues,
75
- ...(hasFailed ? { suggestion: issues.map((i) => i.message).join('; ') } : {}),
76
- validatedAt: new Date().toISOString(),
77
- }
80
+ return buildWriteValidationResult(issues)
81
+ },
78
82
  }
79
83
  }
80
84
 
81
- export const writeIntentValidatorService = new WriteIntentValidatorService()
85
+ export class WriteIntentValidatorServiceTag extends Context.Service<
86
+ WriteIntentValidatorServiceTag,
87
+ ReturnType<typeof makeWriteIntentValidatorService>
88
+ >()('WriteIntentValidatorService') {}
89
+
90
+ export const WriteIntentValidatorServiceLive = Layer.succeed(
91
+ WriteIntentValidatorServiceTag,
92
+ makeWriteIntentValidatorService(),
93
+ )
@@ -1,9 +1,25 @@
1
1
  import { SUPPORTED_ATTACHMENT_MIME_TYPES, getAttachmentExtension } from '@lota-sdk/shared'
2
+ import { Schema, Effect } from 'effect'
2
3
  import mammoth from 'mammoth'
3
4
  import { PDFParse } from 'pdf-parse'
4
5
 
5
6
  const READ_FILE_PARTS_CHARS_PER_PAGE = 3500
6
7
 
8
+ class AttachmentParserError extends Schema.TaggedErrorClass<AttachmentParserError>()('AttachmentParserError', {
9
+ message: Schema.String,
10
+ cause: Schema.Defect,
11
+ }) {}
12
+
13
+ function tryAttachmentPromise<A>(
14
+ message: string,
15
+ thunk: () => PromiseLike<A>,
16
+ ): Effect.Effect<A, AttachmentParserError> {
17
+ return Effect.tryPromise({
18
+ try: () => Promise.resolve(thunk()),
19
+ catch: (cause) => new AttachmentParserError({ message, cause }),
20
+ })
21
+ }
22
+
7
23
  export function normalizeExtractedText(value: string): string {
8
24
  return value.replaceAll('\u0000', '').replace(/\r\n/g, '\n').trim()
9
25
  }
@@ -29,44 +45,70 @@ export function isTextAttachmentFile(file: File): boolean {
29
45
  return ext === 'txt' || ext === 'md' || ext === 'markdown'
30
46
  }
31
47
 
32
- export async function extractPdfPages(file: File): Promise<string[]> {
33
- const parser = new PDFParse({ data: new Uint8Array(await file.arrayBuffer()) })
34
- try {
35
- const info = await parser.getInfo()
36
- const textResult = await parser.getText()
37
-
38
- const pageTextByNumber = new Map<number, string>()
39
- for (const page of textResult.pages) {
40
- pageTextByNumber.set(page.num, normalizeExtractedText(page.text))
41
- }
42
-
43
- const pages: string[] = []
44
- for (let pageNumber = 1; pageNumber <= info.total; pageNumber += 1) {
45
- pages.push(pageTextByNumber.get(pageNumber) ?? '')
46
- }
47
- return pages
48
- } finally {
49
- await parser.destroy()
50
- }
48
+ export function extractPdfPages(file: File): Promise<string[]> {
49
+ return Effect.runPromise(
50
+ Effect.gen(function* () {
51
+ const buffer = yield* tryAttachmentPromise('Failed to read PDF attachment buffer.', () => file.arrayBuffer())
52
+ const parser = new PDFParse({ data: new Uint8Array(buffer) })
53
+
54
+ return yield* Effect.gen(function* () {
55
+ const info = yield* tryAttachmentPromise('Failed to read PDF attachment metadata.', () => parser.getInfo())
56
+ const textResult = yield* tryAttachmentPromise('Failed to extract PDF attachment text.', () => parser.getText())
57
+ const pageTextByNumber = new Map<number, string>()
58
+ for (const page of textResult.pages) {
59
+ pageTextByNumber.set(page.num, normalizeExtractedText(page.text))
60
+ }
61
+
62
+ const pages: string[] = []
63
+ for (let pageNumber = 1; pageNumber <= info.total; pageNumber += 1) {
64
+ pages.push(pageTextByNumber.get(pageNumber) ?? '')
65
+ }
66
+ return pages
67
+ }).pipe(
68
+ Effect.ensuring(
69
+ tryAttachmentPromise('Failed to destroy PDF parser.', () => parser.destroy()).pipe(
70
+ Effect.orDie,
71
+ Effect.asVoid,
72
+ ),
73
+ ),
74
+ )
75
+ }),
76
+ )
51
77
  }
52
78
 
53
- export async function extractDocxText(file: File): Promise<string> {
54
- const buffer = Buffer.from(await file.arrayBuffer())
55
- const result = await mammoth.extractRawText({ buffer })
56
- return normalizeExtractedText(result.value)
79
+ export function extractDocxText(file: File): Promise<string> {
80
+ return Effect.runPromise(
81
+ Effect.gen(function* () {
82
+ const arrayBuffer = yield* tryAttachmentPromise('Failed to read DOCX attachment buffer.', () =>
83
+ file.arrayBuffer(),
84
+ )
85
+ const result = yield* tryAttachmentPromise('Failed to extract DOCX attachment text.', () =>
86
+ mammoth.extractRawText({ buffer: Buffer.from(arrayBuffer) }),
87
+ )
88
+ return normalizeExtractedText(result.value)
89
+ }),
90
+ )
57
91
  }
58
92
 
59
- export async function extractAttachmentText(file: File): Promise<string> {
93
+ export function extractAttachmentText(file: File): Promise<string> {
60
94
  if (isTextAttachmentFile(file)) {
61
- return normalizeExtractedText(await file.text())
95
+ return Effect.runPromise(
96
+ tryAttachmentPromise('Failed to read text attachment.', () => file.text()).pipe(
97
+ Effect.map(normalizeExtractedText),
98
+ ),
99
+ )
62
100
  }
63
101
  if (isPdfAttachmentFile(file)) {
64
- return normalizeExtractedText((await extractPdfPages(file)).join('\n\n'))
102
+ return Effect.runPromise(
103
+ tryAttachmentPromise('Failed to extract PDF attachment text.', () => extractPdfPages(file)).pipe(
104
+ Effect.map((pages) => normalizeExtractedText(pages.join('\n\n'))),
105
+ ),
106
+ )
65
107
  }
66
108
  if (isDocxAttachmentFile(file)) {
67
109
  return extractDocxText(file)
68
110
  }
69
- return ''
111
+ return Promise.resolve('')
70
112
  }
71
113
 
72
114
  export function splitExtractedTextIntoPages(extractedText: string): string[] {