@lota-sdk/core 0.4.7 → 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/model-constants.ts +1 -0
  9. package/src/config/thread-defaults.ts +33 -21
  10. package/src/create-runtime.ts +725 -383
  11. package/src/db/base.service.ts +52 -28
  12. package/src/db/cursor-pagination.ts +71 -30
  13. package/src/db/memory-store.helpers.ts +4 -7
  14. package/src/db/memory-store.ts +856 -598
  15. package/src/db/memory.ts +398 -275
  16. package/src/db/record-id.ts +32 -10
  17. package/src/db/schema-fingerprint.ts +30 -12
  18. package/src/db/service-normalization.ts +255 -0
  19. package/src/db/service.ts +726 -761
  20. package/src/db/startup.ts +140 -66
  21. package/src/db/transaction-conflict.ts +15 -0
  22. package/src/effect/awaitable-effect.ts +87 -0
  23. package/src/effect/errors.ts +121 -0
  24. package/src/effect/helpers.ts +98 -0
  25. package/src/effect/index.ts +22 -0
  26. package/src/effect/layers.ts +228 -0
  27. package/src/effect/runtime-ref.ts +25 -0
  28. package/src/effect/runtime.ts +31 -0
  29. package/src/effect/services.ts +57 -0
  30. package/src/effect/zod.ts +43 -0
  31. package/src/embeddings/provider.ts +122 -71
  32. package/src/index.ts +46 -1
  33. package/src/openrouter/direct-provider.ts +29 -0
  34. package/src/queues/autonomous-job.queue.ts +130 -74
  35. package/src/queues/context-compaction.queue.ts +60 -15
  36. package/src/queues/delayed-node-promotion.queue.ts +52 -15
  37. package/src/queues/document-processor.queue.ts +52 -77
  38. package/src/queues/memory-consolidation.queue.ts +47 -32
  39. package/src/queues/organization-learning.queue.ts +13 -4
  40. package/src/queues/plan-agent-heartbeat.queue.ts +65 -21
  41. package/src/queues/plan-scheduler.queue.ts +107 -31
  42. package/src/queues/post-chat-memory.queue.ts +66 -24
  43. package/src/queues/queue-factory.ts +142 -52
  44. package/src/queues/standalone-worker.ts +39 -0
  45. package/src/queues/title-generation.queue.ts +54 -9
  46. package/src/redis/connection.ts +84 -32
  47. package/src/redis/index.ts +6 -8
  48. package/src/redis/org-memory-lock.ts +60 -27
  49. package/src/redis/redis-lease-lock.ts +200 -121
  50. package/src/redis/runtime-connection.ts +10 -0
  51. package/src/redis/stream-context.ts +84 -46
  52. package/src/runtime/agent-identity-overrides.ts +2 -2
  53. package/src/runtime/agent-runtime-policy.ts +4 -1
  54. package/src/runtime/agent-stream-helpers.ts +20 -9
  55. package/src/runtime/chat-run-orchestration.ts +102 -19
  56. package/src/runtime/chat-run-registry.ts +36 -2
  57. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  58. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +114 -91
  59. package/src/runtime/execution-plan-visibility.ts +2 -2
  60. package/src/runtime/execution-plan.ts +42 -15
  61. package/src/runtime/graph-designer.ts +11 -7
  62. package/src/runtime/helper-model.ts +135 -48
  63. package/src/runtime/index.ts +7 -7
  64. package/src/runtime/indexed-repositories-policy.ts +3 -3
  65. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +40 -36
  66. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  67. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +1 -1
  68. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  69. package/src/runtime/{memory-scope.ts → memory/memory-scope.ts} +12 -6
  70. package/src/runtime/plugin-resolution.ts +144 -24
  71. package/src/runtime/plugin-types.ts +9 -1
  72. package/src/runtime/post-turn-side-effects.ts +197 -130
  73. package/src/runtime/retrieval-adapters.ts +38 -4
  74. package/src/runtime/runtime-config.ts +165 -61
  75. package/src/runtime/runtime-extensions.ts +21 -34
  76. package/src/runtime/social-chat/social-chat-agent-runner.ts +157 -0
  77. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +42 -20
  78. package/src/runtime/social-chat/social-chat.ts +594 -0
  79. package/src/runtime/specialist-runner.ts +36 -10
  80. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +427 -0
  81. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  82. package/src/runtime/thread-chat-helpers.ts +2 -2
  83. package/src/runtime/thread-plan-turn.ts +2 -1
  84. package/src/runtime/thread-turn-context.ts +172 -94
  85. package/src/runtime/turn-lifecycle.ts +93 -27
  86. package/src/services/agent-activity.service.ts +287 -203
  87. package/src/services/agent-executor.service.ts +329 -217
  88. package/src/services/artifact.service.ts +225 -148
  89. package/src/services/attachment.service.ts +137 -115
  90. package/src/services/autonomous-job.service.ts +888 -491
  91. package/src/services/chat-run-registry.service.ts +11 -1
  92. package/src/services/context-compaction.service.ts +136 -86
  93. package/src/services/document-chunk.service.ts +162 -90
  94. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  95. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  96. package/src/services/execution-plan/execution-plan-graph.ts +256 -0
  97. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  98. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  99. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  100. package/src/services/feedback-loop.service.ts +132 -76
  101. package/src/services/global-orchestrator.service.ts +80 -170
  102. package/src/services/graph-full-routing.ts +182 -0
  103. package/src/services/index.ts +18 -20
  104. package/src/services/institutional-memory.service.ts +220 -123
  105. package/src/services/learned-skill.service.ts +364 -259
  106. package/src/services/memory/memory-conversation.ts +95 -0
  107. package/src/services/memory/memory-org-memory.ts +39 -0
  108. package/src/services/memory/memory-preseeded.ts +80 -0
  109. package/src/services/memory/memory-rerank.ts +297 -0
  110. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +5 -5
  111. package/src/services/memory/memory.service.ts +692 -0
  112. package/src/services/memory/rerank.service.ts +209 -0
  113. package/src/services/monitoring-window.service.ts +92 -70
  114. package/src/services/mutating-approval.service.ts +62 -53
  115. package/src/services/node-workspace.service.ts +141 -98
  116. package/src/services/notification.service.ts +17 -16
  117. package/src/services/organization-member.service.ts +120 -66
  118. package/src/services/organization.service.ts +144 -51
  119. package/src/services/ownership-dispatcher.service.ts +415 -264
  120. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  121. package/src/services/plan/plan-agent-query.service.ts +322 -0
  122. package/src/services/plan/plan-approval.service.ts +102 -0
  123. package/src/services/plan/plan-artifact.service.ts +60 -0
  124. package/src/services/plan/plan-builder.service.ts +76 -0
  125. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  126. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  127. package/src/services/plan/plan-completion-side-effects.ts +175 -0
  128. package/src/services/plan/plan-coordination.service.ts +181 -0
  129. package/src/services/plan/plan-cycle.service.ts +398 -0
  130. package/src/services/plan/plan-deadline.service.ts +547 -0
  131. package/src/services/plan/plan-event-delivery.service.ts +261 -0
  132. package/src/services/plan/plan-executor-context.ts +35 -0
  133. package/src/services/plan/plan-executor-graph.ts +475 -0
  134. package/src/services/plan/plan-executor-helpers.ts +322 -0
  135. package/src/services/plan/plan-executor-persistence.ts +209 -0
  136. package/src/services/plan/plan-executor.service.ts +1654 -0
  137. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  138. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  139. package/src/services/plan/plan-run-serialization.ts +15 -0
  140. package/src/services/plan/plan-run.service.ts +644 -0
  141. package/src/services/plan/plan-scheduler.service.ts +385 -0
  142. package/src/services/plan/plan-template.service.ts +224 -0
  143. package/src/services/plan/plan-transaction-events.ts +33 -0
  144. package/src/services/plan/plan-validator.service.ts +907 -0
  145. package/src/services/plan/plan-workspace.service.ts +125 -0
  146. package/src/services/plugin-executor.service.ts +97 -68
  147. package/src/services/quality-metrics.service.ts +112 -94
  148. package/src/services/queue-job.service.ts +296 -230
  149. package/src/services/recent-activity-title.service.ts +65 -36
  150. package/src/services/recent-activity.service.ts +274 -259
  151. package/src/services/skill-resolver.service.ts +38 -12
  152. package/src/services/social-chat-history.service.ts +176 -125
  153. package/src/services/system-executor.service.ts +91 -61
  154. package/src/services/thread/thread-active-run.ts +203 -0
  155. package/src/services/thread/thread-bootstrap.ts +369 -0
  156. package/src/services/thread/thread-listing.ts +198 -0
  157. package/src/services/thread/thread-memory-block.ts +117 -0
  158. package/src/services/thread/thread-message.service.ts +363 -0
  159. package/src/services/thread/thread-record-store.ts +155 -0
  160. package/src/services/thread/thread-title.service.ts +74 -0
  161. package/src/services/thread/thread-turn-execution.ts +280 -0
  162. package/src/services/thread/thread-turn-message-context.ts +73 -0
  163. package/src/services/thread/thread-turn-preparation.service.ts +1146 -0
  164. package/src/services/thread/thread-turn-streaming.ts +402 -0
  165. package/src/services/thread/thread-turn-tracing.ts +35 -0
  166. package/src/services/thread/thread-turn.ts +343 -0
  167. package/src/services/thread/thread.service.ts +335 -0
  168. package/src/services/user.service.ts +82 -32
  169. package/src/services/write-intent-validator.service.ts +63 -51
  170. package/src/storage/attachment-parser.ts +69 -27
  171. package/src/storage/attachment-storage.service.ts +331 -275
  172. package/src/storage/generated-document-storage.service.ts +66 -34
  173. package/src/system-agents/agent-result.ts +3 -1
  174. package/src/system-agents/context-compaction.agent.ts +2 -2
  175. package/src/system-agents/delegated-agent-factory.ts +159 -90
  176. package/src/system-agents/memory-reranker.agent.ts +2 -2
  177. package/src/system-agents/memory.agent.ts +2 -2
  178. package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
  179. package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -2
  180. package/src/system-agents/skill-extractor.agent.ts +2 -2
  181. package/src/system-agents/skill-manager.agent.ts +2 -2
  182. package/src/system-agents/thread-router.agent.ts +157 -113
  183. package/src/system-agents/title-generator.agent.ts +2 -2
  184. package/src/tools/execution-plan.tool.ts +220 -161
  185. package/src/tools/fetch-webpage.tool.ts +21 -17
  186. package/src/tools/firecrawl-client.ts +16 -6
  187. package/src/tools/index.ts +1 -0
  188. package/src/tools/memory-block.tool.ts +14 -6
  189. package/src/tools/plan-approval.tool.ts +49 -47
  190. package/src/tools/read-file-parts.tool.ts +44 -33
  191. package/src/tools/remember-memory.tool.ts +65 -45
  192. package/src/tools/search-web.tool.ts +26 -22
  193. package/src/tools/search.tool.ts +41 -29
  194. package/src/tools/team-think.tool.ts +124 -83
  195. package/src/tools/user-questions.tool.ts +4 -3
  196. package/src/tools/web-tool-shared.ts +6 -0
  197. package/src/utils/async.ts +17 -23
  198. package/src/utils/crypto.ts +21 -0
  199. package/src/utils/date-time.ts +40 -1
  200. package/src/utils/errors.ts +95 -16
  201. package/src/utils/hono-error-handler.ts +24 -39
  202. package/src/utils/index.ts +2 -1
  203. package/src/utils/null-proto-record.ts +41 -0
  204. package/src/utils/sse-keepalive.ts +124 -21
  205. package/src/workers/bootstrap.ts +186 -51
  206. package/src/workers/memory-consolidation.worker.ts +325 -237
  207. package/src/workers/organization-learning.worker.ts +50 -16
  208. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  209. package/src/workers/regular-chat-memory-digest.runner.ts +175 -114
  210. package/src/workers/skill-extraction.runner.ts +176 -93
  211. package/src/workers/utils/file-section-chunker.ts +8 -10
  212. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  213. package/src/workers/utils/repomix-file-sections.ts +2 -2
  214. package/src/workers/utils/thread-message-query.ts +97 -38
  215. package/src/workers/worker-utils.ts +56 -31
  216. package/src/config/debug-logger.ts +0 -47
  217. package/src/redis/connection-accessor.ts +0 -26
  218. package/src/runtime/context-compaction-runtime.ts +0 -87
  219. package/src/runtime/social-chat-agent-runner.ts +0 -118
  220. package/src/runtime/social-chat.ts +0 -516
  221. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  222. package/src/services/adaptive-playbook.service.ts +0 -152
  223. package/src/services/artifact-provenance.service.ts +0 -172
  224. package/src/services/chat-attachments.service.ts +0 -17
  225. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  226. package/src/services/execution-plan.service.ts +0 -1118
  227. package/src/services/memory.service.ts +0 -844
  228. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  229. package/src/services/plan-agent-query.service.ts +0 -267
  230. package/src/services/plan-approval.service.ts +0 -83
  231. package/src/services/plan-artifact.service.ts +0 -50
  232. package/src/services/plan-builder.service.ts +0 -67
  233. package/src/services/plan-checkpoint.service.ts +0 -81
  234. package/src/services/plan-completion-side-effects.ts +0 -80
  235. package/src/services/plan-coordination.service.ts +0 -157
  236. package/src/services/plan-cycle.service.ts +0 -284
  237. package/src/services/plan-deadline.service.ts +0 -430
  238. package/src/services/plan-event-delivery.service.ts +0 -166
  239. package/src/services/plan-executor.service.ts +0 -1950
  240. package/src/services/plan-run.service.ts +0 -515
  241. package/src/services/plan-scheduler.service.ts +0 -240
  242. package/src/services/plan-template.service.ts +0 -177
  243. package/src/services/plan-validator.service.ts +0 -818
  244. package/src/services/plan-workspace.service.ts +0 -83
  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[] {