@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
@@ -0,0 +1,209 @@
1
+ import { Context, Effect, Layer } from 'effect'
2
+ import * as Schema from 'effect/Schema'
3
+ import { z } from 'zod'
4
+
5
+ import { OPENROUTER_FAST_RERANK_MODEL_ID } from '../../config/model-constants'
6
+ import { RuntimeConfigServiceTag } from '../../effect/services'
7
+ import { toValidationError } from '../../effect/zod'
8
+ import { normalizeDirectOpenRouterModelId, resolveOpenRouterApiKey } from '../../openrouter/direct-provider'
9
+ import type { ResolvedLotaRuntimeConfig } from '../../runtime/runtime-config'
10
+
11
+ const OPENROUTER_RERANK_URL = 'https://openrouter.ai/api/v1/rerank' as const
12
+
13
+ class RerankServiceError extends Schema.TaggedErrorClass<RerankServiceError>()('RerankServiceError', {
14
+ operation: Schema.String,
15
+ message: Schema.String,
16
+ cause: Schema.Defect,
17
+ }) {}
18
+
19
+ function toRerankServiceError(operation: string, message: string, cause: unknown): RerankServiceError {
20
+ return new RerankServiceError({ operation, message, cause })
21
+ }
22
+
23
+ function tryRerankPromise<A>(
24
+ operation: string,
25
+ message: string,
26
+ thunk: () => PromiseLike<A> | A,
27
+ ): Effect.Effect<A, RerankServiceError> {
28
+ return Effect.tryPromise({
29
+ try: () => Promise.resolve(thunk()),
30
+ catch: (cause) => toRerankServiceError(operation, message, cause),
31
+ })
32
+ }
33
+
34
+ const RerankResponseSchema = z
35
+ .object({
36
+ model: z.string().optional(),
37
+ results: z.array(
38
+ z
39
+ .object({
40
+ index: z.number().int().nonnegative().optional(),
41
+ relevance_score: z.number().optional(),
42
+ document: z.union([z.string(), z.object({ text: z.string().optional() }).passthrough()]).optional(),
43
+ })
44
+ .passthrough(),
45
+ ),
46
+ usage: z.object({ search_units: z.number().optional(), cost: z.number().optional() }).passthrough().optional(),
47
+ })
48
+ .passthrough()
49
+
50
+ export interface RerankDocument {
51
+ id: string
52
+ text: string
53
+ }
54
+
55
+ export interface RerankResultItem extends RerankDocument {
56
+ index: number
57
+ relevanceScore: number | null
58
+ }
59
+
60
+ export interface RerankUsage {
61
+ searchUnits?: number
62
+ cost?: number
63
+ }
64
+
65
+ export interface RerankResult {
66
+ modelId: string
67
+ results: RerankResultItem[]
68
+ usage?: RerankUsage
69
+ }
70
+
71
+ function clampTopN(topN: number | undefined, total: number): number {
72
+ if (!Number.isFinite(topN)) return total
73
+ return Math.max(1, Math.min(total, Math.trunc(topN as number)))
74
+ }
75
+
76
+ function matchRerankedDocument(
77
+ candidate: z.infer<typeof RerankResponseSchema>['results'][number],
78
+ documents: RerankDocument[],
79
+ ): RerankDocument | null {
80
+ if (typeof candidate.index === 'number' && candidate.index >= 0 && candidate.index < documents.length) {
81
+ return documents[candidate.index] ?? null
82
+ }
83
+
84
+ const text =
85
+ typeof candidate.document === 'string'
86
+ ? candidate.document
87
+ : typeof candidate.document?.text === 'string'
88
+ ? candidate.document.text
89
+ : null
90
+ if (!text) return null
91
+
92
+ return documents.find((document) => document.text === text) ?? null
93
+ }
94
+
95
+ export function makeRerankService(config: ResolvedLotaRuntimeConfig) {
96
+ function readConfiguredRerankModelId(): string | null {
97
+ const configured = config.memory.rerankerModelId
98
+ if (typeof configured !== 'string') return null
99
+ const trimmed = configured.trim()
100
+ return trimmed.length > 0 ? trimmed : null
101
+ }
102
+
103
+ function resolveRerankModelId(modelId?: string): string {
104
+ const explicit = modelId?.trim()
105
+ if (explicit) return normalizeDirectOpenRouterModelId(explicit)
106
+
107
+ const configured = readConfiguredRerankModelId()
108
+ if (configured) return normalizeDirectOpenRouterModelId(configured)
109
+
110
+ return OPENROUTER_FAST_RERANK_MODEL_ID
111
+ }
112
+
113
+ return {
114
+ rerankDocuments(params: { query: string; documents: RerankDocument[]; topN?: number; modelId?: string }) {
115
+ return Effect.gen(function* () {
116
+ if (params.documents.length === 0) {
117
+ return { modelId: resolveRerankModelId(params.modelId), results: [] as RerankResultItem[] }
118
+ }
119
+
120
+ const apiKey = resolveOpenRouterApiKey(config.aiGateway.openRouterApiKey)
121
+ const modelId = resolveRerankModelId(params.modelId)
122
+ const topN = clampTopN(params.topN, params.documents.length)
123
+
124
+ const response = yield* tryRerankPromise('fetch-rerank', 'Failed to fetch rerank results.', () =>
125
+ Bun.fetch(OPENROUTER_RERANK_URL, {
126
+ method: 'POST',
127
+ headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
128
+ body: JSON.stringify({
129
+ model: modelId,
130
+ query: params.query,
131
+ documents: params.documents.map((document) => document.text),
132
+ top_n: topN,
133
+ }),
134
+ }),
135
+ )
136
+
137
+ const responseText = yield* tryRerankPromise(
138
+ 'read-rerank-response',
139
+ 'Failed to read rerank response body.',
140
+ () => response.text(),
141
+ )
142
+
143
+ if (!response.ok) {
144
+ return yield* new RerankServiceError({
145
+ operation: 'fetch-rerank',
146
+ message: `OpenRouter rerank failed (${response.status}): ${responseText}`,
147
+ cause: responseText,
148
+ })
149
+ }
150
+
151
+ const payload = yield* Schema.decodeUnknownEffect(Schema.UnknownFromJsonString)(responseText).pipe(
152
+ Effect.mapError((cause) =>
153
+ toRerankServiceError(
154
+ 'parse-rerank-json',
155
+ `Rerank service returned invalid JSON (status ${response.status}): ${responseText.slice(0, 200)}`,
156
+ cause,
157
+ ),
158
+ ),
159
+ )
160
+
161
+ const parsedResponse = RerankResponseSchema.safeParse(payload)
162
+ if (!parsedResponse.success) {
163
+ return yield* toValidationError(parsedResponse.error, 'Rerank service returned an invalid response payload')
164
+ }
165
+ const parsed = parsedResponse.data
166
+ const seen = new Set<string>()
167
+ const results: RerankResultItem[] = []
168
+
169
+ for (const item of parsed.results) {
170
+ const matched = matchRerankedDocument(item, params.documents)
171
+ if (!matched || seen.has(matched.id)) continue
172
+ seen.add(matched.id)
173
+
174
+ results.push({
175
+ id: matched.id,
176
+ text: matched.text,
177
+ index: item.index ?? params.documents.findIndex((document) => document.id === matched.id),
178
+ relevanceScore: typeof item.relevance_score === 'number' ? item.relevance_score : null,
179
+ })
180
+
181
+ if (results.length >= topN) break
182
+ }
183
+
184
+ return {
185
+ modelId: parsed.model?.trim() || modelId,
186
+ results,
187
+ usage: parsed.usage
188
+ ? {
189
+ ...(parsed.usage.search_units !== undefined ? { searchUnits: parsed.usage.search_units } : {}),
190
+ ...(parsed.usage.cost !== undefined ? { cost: parsed.usage.cost } : {}),
191
+ }
192
+ : undefined,
193
+ }
194
+ })
195
+ },
196
+ }
197
+ }
198
+
199
+ export class RerankServiceTag extends Context.Service<RerankServiceTag, ReturnType<typeof makeRerankService>>()(
200
+ 'RerankService',
201
+ ) {}
202
+
203
+ export const RerankServiceLive = Layer.effect(
204
+ RerankServiceTag,
205
+ Effect.gen(function* () {
206
+ const runtimeConfig = yield* RuntimeConfigServiceTag
207
+ return makeRerankService(runtimeConfig)
208
+ }),
209
+ )
@@ -1,86 +1,108 @@
1
1
  import type { MonitoringWindowConfig } from '@lota-sdk/shared'
2
+ import { Context, Effect, Layer } from 'effect'
2
3
 
3
- import { planSchedulerService } from './plan-scheduler.service'
4
+ import { ValidationError } from '../effect/errors'
5
+ import type { makePlanSchedulerService } from './plan/plan-scheduler.service'
6
+ import { PlanSchedulerServiceTag } from './plan/plan-scheduler.service'
4
7
 
5
- class MonitoringWindowService {
6
- computeCheckTimes(config: MonitoringWindowConfig): { offsetMs: number; intervalMs: number }[] {
7
- const times: { offsetMs: number; intervalMs: number }[] = []
8
- let currentInterval = config.initialIntervalMs
9
- let elapsed = 0
10
- while (elapsed < config.durationMs) {
11
- times.push({ offsetMs: elapsed, intervalMs: currentInterval })
12
- elapsed += currentInterval
13
- currentInterval = Math.min(currentInterval / config.decayFactor, config.maxIntervalMs)
14
- }
15
- return times
8
+ function computeCheckTimes(config: MonitoringWindowConfig): { offsetMs: number; intervalMs: number }[] {
9
+ const times: { offsetMs: number; intervalMs: number }[] = []
10
+ let currentInterval = config.initialIntervalMs
11
+ let elapsed = 0
12
+ while (elapsed < config.durationMs) {
13
+ times.push({ offsetMs: elapsed, intervalMs: currentInterval })
14
+ elapsed += currentInterval
15
+ currentInterval = Math.min(currentInterval / config.decayFactor, config.maxIntervalMs)
16
16
  }
17
+ return times
18
+ }
17
19
 
18
- async startMonitoringWindow(params: {
19
- runId: string
20
- nodeId: string
21
- config: MonitoringWindowConfig
22
- organizationId: string
23
- threadId: string
24
- }): Promise<void> {
25
- await planSchedulerService.createSchedule({
26
- organizationId: params.organizationId,
27
- threadId: params.threadId,
28
- runId: params.runId,
29
- nodeId: params.nodeId,
30
- scheduleSpec: {
31
- type: 'monitoring',
32
- intervalMs: params.config.initialIntervalMs,
33
- maxFires: this.computeCheckTimes(params.config).length,
34
- missedPolicy: 'skip',
35
- },
36
- })
37
- }
20
+ function evaluateTerminationEffect(params: {
21
+ samples: unknown[]
22
+ config: MonitoringWindowConfig
23
+ }): Effect.Effect<boolean, ValidationError> {
24
+ if (!params.config.earlyTermination) return Effect.succeed(false)
25
+ if (params.samples.length < params.config.earlyTermination.minSamples) return Effect.succeed(false)
38
26
 
39
- evaluateTermination(params: { samples: unknown[]; config: MonitoringWindowConfig }): boolean {
40
- if (!params.config.earlyTermination) return false
41
- if (params.samples.length < params.config.earlyTermination.minSamples) return false
27
+ const { condition, minSamples } = params.config.earlyTermination
28
+ const numericSamples = params.samples
29
+ .map((s) =>
30
+ typeof s === 'number'
31
+ ? s
32
+ : typeof s === 'object' && s !== null && 'value' in s
33
+ ? Number((s as Record<string, unknown>).value)
34
+ : NaN,
35
+ )
36
+ .filter((n) => !isNaN(n))
42
37
 
43
- const { condition, minSamples } = params.config.earlyTermination
44
- const numericSamples = params.samples
45
- .map((s) =>
46
- typeof s === 'number'
47
- ? s
48
- : typeof s === 'object' && s !== null && 'value' in s
49
- ? Number((s as Record<string, unknown>).value)
50
- : NaN,
51
- )
52
- .filter((n) => !isNaN(n))
38
+ if (numericSamples.length === 0) return Effect.succeed(false)
53
39
 
54
- // No numeric data to evaluate against — don't terminate
55
- if (numericSamples.length === 0) return false
40
+ if (condition === 'stable') {
41
+ const recent = numericSamples.slice(-minSamples)
42
+ if (recent.length < 2) return Effect.succeed(false)
43
+ const mean = recent.reduce((a, b) => a + b, 0) / recent.length
44
+ const variance = recent.reduce((a, b) => a + (b - mean) ** 2, 0) / recent.length
45
+ const cv = mean !== 0 ? Math.sqrt(variance) / Math.abs(mean) : 0
46
+ return Effect.succeed(cv < 0.1)
47
+ }
56
48
 
57
- if (condition === 'stable') {
58
- const recent = numericSamples.slice(-minSamples)
59
- if (recent.length < 2) return false
60
- const mean = recent.reduce((a, b) => a + b, 0) / recent.length
61
- const variance = recent.reduce((a, b) => a + (b - mean) ** 2, 0) / recent.length
62
- const cv = mean !== 0 ? Math.sqrt(variance) / Math.abs(mean) : 0
63
- return cv < 0.1
64
- }
49
+ const thresholdMatch = condition.match(/^threshold:(\d+(?:\.\d+)?)$/)
50
+ if (thresholdMatch?.[1]) {
51
+ const threshold = parseFloat(thresholdMatch[1])
52
+ return Effect.succeed(numericSamples.some((v) => v >= threshold))
53
+ }
65
54
 
66
- const thresholdMatch = condition.match(/^threshold:(\d+(?:\.\d+)?)$/)
67
- if (thresholdMatch?.[1]) {
68
- const threshold = parseFloat(thresholdMatch[1])
69
- return numericSamples.some((v) => v >= threshold)
70
- }
55
+ if (condition === 'trend:declining') {
56
+ const recent = numericSamples.slice(-minSamples)
57
+ return Effect.succeed(recent.length >= 2 && recent.every((v, i) => i === 0 || v <= (recent[i - 1] ?? v)))
58
+ }
71
59
 
72
- if (condition === 'trend:declining') {
73
- const recent = numericSamples.slice(-minSamples)
74
- return recent.length >= 2 && recent.every((v, i) => i === 0 || v <= (recent[i - 1] ?? v))
75
- }
60
+ if (condition === 'trend:increasing') {
61
+ const recent = numericSamples.slice(-minSamples)
62
+ return Effect.succeed(recent.length >= 2 && recent.every((v, i) => i === 0 || v >= (recent[i - 1] ?? v)))
63
+ }
76
64
 
77
- if (condition === 'trend:increasing') {
78
- const recent = numericSamples.slice(-minSamples)
79
- return recent.length >= 2 && recent.every((v, i) => i === 0 || v >= (recent[i - 1] ?? v))
80
- }
65
+ return Effect.fail(new ValidationError({ message: `Unknown monitoring termination condition: ${condition}` }))
66
+ }
81
67
 
82
- throw new Error(`Unknown monitoring termination condition: ${condition}`)
68
+ export function makeMonitoringWindowService(planSchedulerService: ReturnType<typeof makePlanSchedulerService>) {
69
+ return {
70
+ computeCheckTimes,
71
+ startMonitoringWindow(params: {
72
+ runId: string
73
+ nodeId: string
74
+ config: MonitoringWindowConfig
75
+ organizationId: string
76
+ threadId: string
77
+ }) {
78
+ return Effect.asVoid(
79
+ planSchedulerService.createSchedule({
80
+ organizationId: params.organizationId,
81
+ threadId: params.threadId,
82
+ runId: params.runId,
83
+ nodeId: params.nodeId,
84
+ scheduleSpec: {
85
+ type: 'monitoring',
86
+ intervalMs: params.config.initialIntervalMs,
87
+ maxFires: computeCheckTimes(params.config).length,
88
+ missedPolicy: 'skip',
89
+ },
90
+ }),
91
+ )
92
+ },
93
+ evaluateTermination: evaluateTerminationEffect,
83
94
  }
84
95
  }
85
96
 
86
- export const monitoringWindowService = new MonitoringWindowService()
97
+ export class MonitoringWindowServiceTag extends Context.Service<
98
+ MonitoringWindowServiceTag,
99
+ ReturnType<typeof makeMonitoringWindowService>
100
+ >()('MonitoringWindowService') {}
101
+
102
+ export const MonitoringWindowServiceLive = Layer.effect(
103
+ MonitoringWindowServiceTag,
104
+ Effect.gen(function* () {
105
+ const planSchedulerService = yield* PlanSchedulerServiceTag
106
+ return makeMonitoringWindowService(planSchedulerService)
107
+ }),
108
+ )
@@ -1,18 +1,13 @@
1
- import type { RecordIdRef } from '../db/record-id'
1
+ import { Context, Effect, Layer } from 'effect'
2
+
2
3
  import { ensureRecordId } from '../db/record-id'
3
4
  import { TABLES } from '../db/tables'
4
5
  import { extractMessageText } from '../runtime/thread-chat-helpers'
5
- import { threadMessageService } from './thread-message.service'
6
+ import type { makeThreadMessageService } from './thread/thread-message.service'
7
+ import { ThreadMessageServiceTag } from './thread/thread-message.service'
6
8
 
7
9
  const APPROVAL_VERIFICATION_MESSAGE_WINDOW = 20
8
10
 
9
- type VerifyMutatingApproval = (params: {
10
- threadId: string
11
- approvalReason: string
12
- approvalToken: string
13
- approvalMessageId?: string
14
- }) => Promise<{ ok: true } | { ok: false; reason: string }>
15
-
16
11
  function extractQuotedValue(value: string): string {
17
12
  const trimmed = value.trim()
18
13
  if (
@@ -58,50 +53,64 @@ function messageContainsExactApproval(messageText: string, approvalToken: string
58
53
  return extracted.token === approvalToken && extracted.reason === approvalReason
59
54
  }
60
55
 
61
- async function verifyMutatingApprovalForThread(
62
- threadId: RecordIdRef,
63
- params: { approvalReason: string; approvalToken: string; approvalMessageId?: string },
64
- ): Promise<{ ok: true } | { ok: false; reason: string }> {
65
- const token = extractQuotedValue(params.approvalToken)
66
- const reason = extractQuotedValue(params.approvalReason)
67
- const approvalMessageId = params.approvalMessageId?.trim()
68
- if (!token || !reason) {
69
- return { ok: false, reason: 'Mutating approval metadata is incomplete.' }
70
- }
71
- if (params.approvalMessageId !== undefined && !approvalMessageId) {
72
- return { ok: false, reason: 'approvalMessageId cannot be empty when provided.' }
73
- }
74
-
75
- const recentMessages = await threadMessageService.listRecentMessages(threadId, APPROVAL_VERIFICATION_MESSAGE_WINDOW)
76
- const userMessages = recentMessages.filter((message) => message.role === 'user')
77
- if (userMessages.length === 0) {
78
- return { ok: false, reason: 'No user message history available to verify mutating approval.' }
79
- }
80
-
81
- const latestUserMessage = userMessages.at(-1)
82
- if (!latestUserMessage) {
83
- return { ok: false, reason: 'Latest user message was not found for mutating approval verification.' }
84
- }
85
-
86
- if (approvalMessageId && approvalMessageId !== latestUserMessage.id) {
87
- return {
88
- ok: false,
89
- reason:
90
- 'approvalMessageId must reference the latest user message for mutating approval verification; stale approvals are not accepted.',
91
- }
56
+ export function makeMutatingApprovalService(threadMessageService: ReturnType<typeof makeThreadMessageService>) {
57
+ return (params: { threadId: string; approvalReason: string; approvalToken: string; approvalMessageId?: string }) => {
58
+ const threadId = ensureRecordId(params.threadId, TABLES.THREAD)
59
+ return Effect.gen(function* () {
60
+ const token = extractQuotedValue(params.approvalToken)
61
+ const reason = extractQuotedValue(params.approvalReason)
62
+ const approvalMessageId = params.approvalMessageId?.trim()
63
+ if (!token || !reason) {
64
+ return { ok: false as const, reason: 'Mutating approval metadata is incomplete.' }
65
+ }
66
+ if (params.approvalMessageId !== undefined && !approvalMessageId) {
67
+ return { ok: false as const, reason: 'approvalMessageId cannot be empty when provided.' }
68
+ }
69
+
70
+ const recentMessages = yield* threadMessageService.listRecentMessages(
71
+ threadId,
72
+ APPROVAL_VERIFICATION_MESSAGE_WINDOW,
73
+ )
74
+ const userMessages = recentMessages.filter((message) => message.role === 'user')
75
+ if (userMessages.length === 0) {
76
+ return { ok: false as const, reason: 'No user message history available to verify mutating approval.' }
77
+ }
78
+
79
+ const latestUserMessage = userMessages.at(-1)
80
+ if (!latestUserMessage) {
81
+ return { ok: false as const, reason: 'Latest user message was not found for mutating approval verification.' }
82
+ }
83
+
84
+ if (approvalMessageId && approvalMessageId !== latestUserMessage.id) {
85
+ return {
86
+ ok: false as const,
87
+ reason:
88
+ 'approvalMessageId must reference the latest user message for mutating approval verification; stale approvals are not accepted.',
89
+ }
90
+ }
91
+
92
+ if (!messageContainsExactApproval(extractMessageText(latestUserMessage), token, reason)) {
93
+ return {
94
+ ok: false as const,
95
+ reason:
96
+ 'Referenced approval message must contain exact approval fields: "Approval Token: <token>" and "Approval Reason: <reason>".',
97
+ }
98
+ }
99
+
100
+ return { ok: true as const }
101
+ })
92
102
  }
93
-
94
- if (!messageContainsExactApproval(extractMessageText(latestUserMessage), token, reason)) {
95
- return {
96
- ok: false,
97
- reason:
98
- 'Referenced approval message must contain exact approval fields: "Approval Token: <token>" and "Approval Reason: <reason>".',
99
- }
100
- }
101
-
102
- return { ok: true }
103
103
  }
104
104
 
105
- export const verifyMutatingApproval: VerifyMutatingApproval = async (params) => {
106
- return verifyMutatingApprovalForThread(ensureRecordId(params.threadId, TABLES.THREAD), params)
107
- }
105
+ export class MutatingApprovalServiceTag extends Context.Service<
106
+ MutatingApprovalServiceTag,
107
+ ReturnType<typeof makeMutatingApprovalService>
108
+ >()('MutatingApprovalService') {}
109
+
110
+ export const MutatingApprovalServiceLive = Layer.effect(
111
+ MutatingApprovalServiceTag,
112
+ Effect.gen(function* () {
113
+ const threadMessageService = yield* ThreadMessageServiceTag
114
+ return makeMutatingApprovalService(threadMessageService)
115
+ }),
116
+ )