@lota-sdk/core 0.4.8 → 0.4.10

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 (272) hide show
  1. package/package.json +11 -12
  2. package/src/ai/embedding-cache.ts +96 -22
  3. package/src/ai-gateway/ai-gateway.ts +766 -223
  4. package/src/config/agent-defaults.ts +189 -75
  5. package/src/config/agent-types.ts +54 -4
  6. package/src/config/background-processing.ts +1 -1
  7. package/src/config/constants.ts +8 -2
  8. package/src/config/index.ts +0 -1
  9. package/src/config/logger.ts +299 -19
  10. package/src/config/thread-defaults.ts +40 -20
  11. package/src/create-runtime.ts +200 -449
  12. package/src/db/base.service.ts +52 -28
  13. package/src/db/cursor-pagination.ts +71 -30
  14. package/src/db/memory-query-builder.ts +2 -1
  15. package/src/db/memory-store.helpers.ts +4 -7
  16. package/src/db/memory-store.ts +868 -601
  17. package/src/db/memory.ts +396 -280
  18. package/src/db/record-id.ts +32 -10
  19. package/src/db/schema-fingerprint.ts +30 -12
  20. package/src/db/service-normalization.ts +288 -0
  21. package/src/db/service.ts +912 -779
  22. package/src/db/startup.ts +153 -68
  23. package/src/db/transaction-conflict.ts +15 -0
  24. package/src/effect/awaitable-effect.ts +96 -0
  25. package/src/effect/errors.ts +121 -0
  26. package/src/effect/helpers.ts +123 -0
  27. package/src/effect/index.ts +24 -0
  28. package/src/effect/layers.ts +238 -0
  29. package/src/effect/runtime-ref.ts +25 -0
  30. package/src/effect/runtime.ts +46 -0
  31. package/src/effect/services.ts +61 -0
  32. package/src/effect/zod.ts +43 -0
  33. package/src/embeddings/provider.ts +128 -83
  34. package/src/index.ts +48 -1
  35. package/src/openrouter/direct-provider.ts +11 -35
  36. package/src/queues/autonomous-job.queue.ts +117 -73
  37. package/src/queues/context-compaction.queue.ts +50 -17
  38. package/src/queues/delayed-node-promotion.queue.ts +46 -17
  39. package/src/queues/document-processor.queue.ts +52 -77
  40. package/src/queues/memory-consolidation.queue.ts +47 -32
  41. package/src/queues/organization-learning.queue.ts +26 -4
  42. package/src/queues/plan-agent-heartbeat.queue.ts +71 -24
  43. package/src/queues/plan-scheduler.queue.ts +97 -33
  44. package/src/queues/post-chat-memory.queue.ts +56 -26
  45. package/src/queues/queue-factory.ts +227 -59
  46. package/src/queues/standalone-worker.ts +39 -0
  47. package/src/queues/title-generation.queue.ts +45 -11
  48. package/src/redis/connection.ts +182 -113
  49. package/src/redis/index.ts +6 -8
  50. package/src/redis/org-memory-lock.ts +60 -27
  51. package/src/redis/redis-lease-lock.ts +200 -121
  52. package/src/redis/runtime-connection.ts +20 -0
  53. package/src/redis/stream-context.ts +92 -46
  54. package/src/runtime/agent-identity-overrides.ts +2 -2
  55. package/src/runtime/agent-runtime-policy.ts +5 -2
  56. package/src/runtime/agent-stream-helpers.ts +24 -9
  57. package/src/runtime/chat-run-orchestration.ts +102 -19
  58. package/src/runtime/chat-run-registry.ts +36 -2
  59. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  60. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +161 -94
  61. package/src/runtime/domain-layer.ts +192 -0
  62. package/src/runtime/execution-plan-visibility.ts +2 -2
  63. package/src/runtime/execution-plan.ts +42 -15
  64. package/src/runtime/graph-designer.ts +16 -4
  65. package/src/runtime/helper-model.ts +139 -48
  66. package/src/runtime/index.ts +7 -8
  67. package/src/runtime/indexed-repositories-policy.ts +3 -3
  68. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +50 -36
  69. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  70. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +54 -67
  71. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  72. package/src/runtime/memory/memory-scope.ts +53 -0
  73. package/src/runtime/plugin-resolution.ts +124 -25
  74. package/src/runtime/plugin-types.ts +9 -1
  75. package/src/runtime/post-turn-side-effects.ts +177 -130
  76. package/src/runtime/retrieval-adapters.ts +40 -6
  77. package/src/runtime/runtime-accessors.ts +92 -0
  78. package/src/runtime/runtime-config.ts +150 -61
  79. package/src/runtime/runtime-extensions.ts +23 -25
  80. package/src/runtime/runtime-lifecycle.ts +124 -0
  81. package/src/runtime/runtime-services.ts +386 -0
  82. package/src/runtime/runtime-token.ts +47 -0
  83. package/src/runtime/social-chat/social-chat-agent-runner.ts +159 -0
  84. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +51 -20
  85. package/src/runtime/social-chat/social-chat.ts +630 -0
  86. package/src/runtime/specialist-runner.ts +36 -10
  87. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +433 -0
  88. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  89. package/src/runtime/thread-chat-helpers.ts +2 -2
  90. package/src/runtime/thread-plan-turn.ts +2 -1
  91. package/src/runtime/thread-turn-context.ts +183 -111
  92. package/src/runtime/turn-lifecycle.ts +93 -27
  93. package/src/services/agent-activity.service.ts +287 -203
  94. package/src/services/agent-executor.service.ts +253 -149
  95. package/src/services/artifact.service.ts +231 -149
  96. package/src/services/attachment.service.ts +171 -115
  97. package/src/services/autonomous-job.service.ts +890 -491
  98. package/src/services/background-work.service.ts +54 -0
  99. package/src/services/chat-run-registry.service.ts +13 -1
  100. package/src/services/context-compaction.service.ts +136 -86
  101. package/src/services/document-chunk.service.ts +151 -88
  102. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  103. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  104. package/src/services/execution-plan/execution-plan-graph.ts +278 -0
  105. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  106. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  107. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  108. package/src/services/feedback-loop.service.ts +132 -76
  109. package/src/services/global-orchestrator.service.ts +101 -168
  110. package/src/services/graph-full-routing.ts +193 -0
  111. package/src/services/index.ts +19 -21
  112. package/src/services/institutional-memory.service.ts +213 -125
  113. package/src/services/learned-skill.service.ts +368 -260
  114. package/src/services/memory/memory-conversation.ts +95 -0
  115. package/src/services/memory/memory-errors.ts +27 -0
  116. package/src/services/memory/memory-org-memory.ts +50 -0
  117. package/src/services/memory/memory-preseeded.ts +86 -0
  118. package/src/services/memory/memory-rerank.ts +297 -0
  119. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +6 -5
  120. package/src/services/memory/memory.service.ts +674 -0
  121. package/src/services/memory/rerank.service.ts +201 -0
  122. package/src/services/monitoring-window.service.ts +92 -70
  123. package/src/services/mutating-approval.service.ts +62 -53
  124. package/src/services/node-workspace.service.ts +141 -98
  125. package/src/services/notification.service.ts +29 -16
  126. package/src/services/organization-member.service.ts +120 -66
  127. package/src/services/organization.service.ts +153 -77
  128. package/src/services/ownership-dispatcher.service.ts +456 -263
  129. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  130. package/src/services/plan/plan-agent-query.service.ts +322 -0
  131. package/src/services/{plan-approval.service.ts → plan/plan-approval.service.ts} +45 -22
  132. package/src/services/plan/plan-artifact.service.ts +60 -0
  133. package/src/services/plan/plan-builder.service.ts +76 -0
  134. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  135. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  136. package/src/services/plan/plan-completion-side-effects.ts +169 -0
  137. package/src/services/plan/plan-coordination.service.ts +181 -0
  138. package/src/services/plan/plan-cycle.service.ts +405 -0
  139. package/src/services/plan/plan-deadline.service.ts +533 -0
  140. package/src/services/plan/plan-event-delivery.service.ts +266 -0
  141. package/src/services/plan/plan-executor-context.ts +35 -0
  142. package/src/services/plan/plan-executor-graph.ts +522 -0
  143. package/src/services/plan/plan-executor-helpers.ts +307 -0
  144. package/src/services/plan/plan-executor-persistence.ts +209 -0
  145. package/src/services/plan/plan-executor.service.ts +1737 -0
  146. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  147. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  148. package/src/services/plan/plan-run-serialization.ts +15 -0
  149. package/src/services/plan/plan-run.service.ts +637 -0
  150. package/src/services/plan/plan-scheduler.service.ts +379 -0
  151. package/src/services/plan/plan-template.service.ts +224 -0
  152. package/src/services/plan/plan-transaction-events.ts +36 -0
  153. package/src/services/plan/plan-validator.service.ts +907 -0
  154. package/src/services/plan/plan-workspace.service.ts +131 -0
  155. package/src/services/plugin-executor.service.ts +102 -68
  156. package/src/services/quality-metrics.service.ts +112 -94
  157. package/src/services/queue-job.service.ts +288 -231
  158. package/src/services/recent-activity-title.service.ts +73 -36
  159. package/src/services/recent-activity.service.ts +274 -259
  160. package/src/services/skill-resolver.service.ts +38 -12
  161. package/src/services/social-chat-history.service.ts +190 -122
  162. package/src/services/system-executor.service.ts +96 -61
  163. package/src/services/thread/thread-active-run.ts +203 -0
  164. package/src/services/thread/thread-bootstrap.ts +385 -0
  165. package/src/services/thread/thread-listing.ts +199 -0
  166. package/src/services/thread/thread-memory-block.ts +130 -0
  167. package/src/services/thread/thread-message.service.ts +379 -0
  168. package/src/services/thread/thread-record-store.ts +155 -0
  169. package/src/services/thread/thread-title.service.ts +74 -0
  170. package/src/services/thread/thread-turn-execution.ts +280 -0
  171. package/src/services/thread/thread-turn-message-context.ts +73 -0
  172. package/src/services/thread/thread-turn-preparation.service.ts +1148 -0
  173. package/src/services/thread/thread-turn-streaming.ts +403 -0
  174. package/src/services/thread/thread-turn-tracing.ts +35 -0
  175. package/src/services/thread/thread-turn.ts +376 -0
  176. package/src/services/thread/thread.service.ts +344 -0
  177. package/src/services/user.service.ts +82 -32
  178. package/src/services/write-intent-validator.service.ts +63 -51
  179. package/src/storage/attachment-parser.ts +69 -27
  180. package/src/storage/attachment-storage.service.ts +334 -275
  181. package/src/storage/generated-document-storage.service.ts +66 -34
  182. package/src/system-agents/agent-result.ts +3 -1
  183. package/src/system-agents/context-compaction.agent.ts +3 -3
  184. package/src/system-agents/delegated-agent-factory.ts +159 -90
  185. package/src/system-agents/helper-agent-options.ts +1 -1
  186. package/src/system-agents/memory-reranker.agent.ts +3 -3
  187. package/src/system-agents/memory.agent.ts +3 -3
  188. package/src/system-agents/recent-activity-title-refiner.agent.ts +3 -3
  189. package/src/system-agents/regular-chat-memory-digest.agent.ts +3 -3
  190. package/src/system-agents/skill-extractor.agent.ts +3 -3
  191. package/src/system-agents/skill-manager.agent.ts +3 -3
  192. package/src/system-agents/thread-router.agent.ts +157 -113
  193. package/src/system-agents/title-generator.agent.ts +3 -3
  194. package/src/tools/execution-plan.tool.ts +241 -171
  195. package/src/tools/fetch-webpage.tool.ts +29 -18
  196. package/src/tools/firecrawl-client.ts +26 -6
  197. package/src/tools/index.ts +1 -0
  198. package/src/tools/memory-block.tool.ts +14 -6
  199. package/src/tools/plan-approval.tool.ts +57 -47
  200. package/src/tools/read-file-parts.tool.ts +44 -33
  201. package/src/tools/remember-memory.tool.ts +65 -45
  202. package/src/tools/search-web.tool.ts +33 -22
  203. package/src/tools/search.tool.ts +41 -29
  204. package/src/tools/team-think.tool.ts +125 -84
  205. package/src/tools/user-questions.tool.ts +4 -3
  206. package/src/tools/web-tool-shared.ts +6 -0
  207. package/src/utils/async.ts +25 -22
  208. package/src/utils/crypto.ts +21 -0
  209. package/src/utils/date-time.ts +40 -1
  210. package/src/utils/errors.ts +111 -20
  211. package/src/utils/hono-error-handler.ts +24 -39
  212. package/src/utils/index.ts +2 -1
  213. package/src/utils/null-proto-record.ts +41 -0
  214. package/src/utils/sse-keepalive.ts +124 -21
  215. package/src/workers/bootstrap.ts +164 -52
  216. package/src/workers/memory-consolidation.worker.ts +325 -237
  217. package/src/workers/organization-learning.worker.ts +50 -16
  218. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  219. package/src/workers/regular-chat-memory-digest.runner.ts +185 -114
  220. package/src/workers/skill-extraction.runner.ts +176 -93
  221. package/src/workers/utils/file-section-chunker.ts +8 -10
  222. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  223. package/src/workers/utils/repomix-file-sections.ts +2 -2
  224. package/src/workers/utils/thread-message-query.ts +97 -38
  225. package/src/workers/worker-utils.ts +74 -31
  226. package/src/config/debug-logger.ts +0 -47
  227. package/src/config/search.ts +0 -3
  228. package/src/redis/connection-accessor.ts +0 -26
  229. package/src/runtime/agent-types.ts +0 -1
  230. package/src/runtime/context-compaction-runtime.ts +0 -87
  231. package/src/runtime/memory-scope.ts +0 -43
  232. package/src/runtime/social-chat-agent-runner.ts +0 -118
  233. package/src/runtime/social-chat.ts +0 -516
  234. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  235. package/src/services/adaptive-playbook.service.ts +0 -152
  236. package/src/services/artifact-provenance.service.ts +0 -172
  237. package/src/services/chat-attachments.service.ts +0 -17
  238. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  239. package/src/services/execution-plan.service.ts +0 -1118
  240. package/src/services/memory.service.ts +0 -914
  241. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  242. package/src/services/plan-agent-query.service.ts +0 -267
  243. package/src/services/plan-artifact.service.ts +0 -50
  244. package/src/services/plan-builder.service.ts +0 -67
  245. package/src/services/plan-checkpoint.service.ts +0 -81
  246. package/src/services/plan-completion-side-effects.ts +0 -80
  247. package/src/services/plan-coordination.service.ts +0 -157
  248. package/src/services/plan-cycle.service.ts +0 -284
  249. package/src/services/plan-deadline.service.ts +0 -430
  250. package/src/services/plan-event-delivery.service.ts +0 -166
  251. package/src/services/plan-executor.service.ts +0 -1950
  252. package/src/services/plan-run.service.ts +0 -515
  253. package/src/services/plan-scheduler.service.ts +0 -240
  254. package/src/services/plan-template.service.ts +0 -177
  255. package/src/services/plan-validator.service.ts +0 -818
  256. package/src/services/plan-workspace.service.ts +0 -83
  257. package/src/services/rerank.service.ts +0 -156
  258. package/src/services/thread-message.service.ts +0 -275
  259. package/src/services/thread-plan-registry.service.ts +0 -22
  260. package/src/services/thread-title.service.ts +0 -39
  261. package/src/services/thread-turn-preparation.service.ts +0 -1147
  262. package/src/services/thread-turn.ts +0 -172
  263. package/src/services/thread.service.ts +0 -869
  264. package/src/utils/env.ts +0 -8
  265. /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
  266. /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
  267. /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
  268. /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
  269. /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
  270. /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
  271. /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
  272. /package/src/services/{thread.types.ts → thread/thread.types.ts} +0 -0
@@ -1,12 +1,9 @@
1
1
  import { embed, embedMany } from 'ai'
2
+ import { Schema, Effect } from 'effect'
2
3
 
3
- import { getEmbeddingCache } from '../ai/embedding-cache'
4
- import {
5
- getDirectOpenRouterProvider,
6
- normalizeDirectOpenRouterModelId,
7
- resetDirectOpenRouterProviderForTests,
8
- } from '../openrouter/direct-provider'
9
- import { getRuntimeConfig } from '../runtime/runtime-config'
4
+ import { ConfigurationError } from '../effect/errors'
5
+ import { runPromiseWithOptionalLotaRuntime } from '../effect/runtime'
6
+ import { getDirectOpenRouterProvider, normalizeDirectOpenRouterModelId } from '../openrouter/direct-provider'
10
7
 
11
8
  const SUPPORTED_EMBEDDING_PREFIXES = ['openai/', 'openrouter/'] as const
12
9
 
@@ -19,85 +16,128 @@ type ProviderEmbeddingsOptions = {
19
16
  embedFn?: typeof embed
20
17
  embedManyFn?: typeof embedMany
21
18
  getCache?: () => SharedEmbeddingCache | null
22
- modelId?: string
19
+ modelId: string
20
+ openRouterApiKey?: string
23
21
  }
24
22
 
25
- function resolveEmbeddingModel(modelId: string) {
23
+ function resolveEmbeddingModel(modelId: string, openRouterApiKey?: string) {
26
24
  const normalized = modelId.trim()
27
25
  if (!normalized) {
28
- throw new Error('[embeddings-provider] Model id is required.')
26
+ throw new ConfigurationError({ message: '[embeddings-provider] Model id is required.', key: 'embeddingModelId' })
29
27
  }
30
28
 
31
29
  if (!SUPPORTED_EMBEDDING_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {
32
- throw new Error(
33
- `[embeddings-provider] Unsupported model id "${modelId}". Use one of: ${SUPPORTED_EMBEDDING_PREFIXES.join(', ')}*.`,
34
- )
30
+ throw new ConfigurationError({
31
+ message: `[embeddings-provider] Unsupported model id "${modelId}". Use one of: ${SUPPORTED_EMBEDDING_PREFIXES.join(', ')}*.`,
32
+ key: 'embeddingModelId',
33
+ })
35
34
  }
36
35
 
37
- return getDirectOpenRouterProvider().embeddingModel(normalizeDirectOpenRouterModelId(normalized))
36
+ return getDirectOpenRouterProvider(openRouterApiKey).embeddingModel(normalizeDirectOpenRouterModelId(normalized))
38
37
  }
39
38
 
40
39
  function normalizeEmbedding(embedding: readonly number[]): number[] {
41
40
  return embedding.map((value) => Number(value))
42
41
  }
43
42
 
43
+ class EmbeddingProviderError extends Schema.TaggedErrorClass<EmbeddingProviderError>()('EmbeddingProviderError', {
44
+ message: Schema.String,
45
+ cause: Schema.Defect,
46
+ }) {}
47
+
48
+ function tryEmbeddingPromise<A>(
49
+ message: string,
50
+ thunk: () => PromiseLike<A>,
51
+ ): Effect.Effect<A, EmbeddingProviderError> {
52
+ return Effect.tryPromise({
53
+ try: () => Promise.resolve(thunk()),
54
+ catch: (cause) => new EmbeddingProviderError({ message, cause }),
55
+ })
56
+ }
57
+
44
58
  export class ProviderEmbeddings {
45
59
  private readonly embedFn: typeof embed
46
60
  private readonly embedManyFn: typeof embedMany
47
61
  private readonly getCache: () => SharedEmbeddingCache | null
48
- private readonly configuredModelId?: string
49
- private resolvedModelId: string | null = null
62
+ private readonly resolvedModelId: string
50
63
  private _model: ReturnType<typeof resolveEmbeddingModel> | null = null
64
+ /** In-flight dedup: concurrent embedQuery calls for the same text share one API round-trip. */
65
+ private readonly inflightEmbeddings = new Map<string, Promise<number[]>>()
51
66
 
52
- constructor(options: ProviderEmbeddingsOptions = {}) {
67
+ private readonly openRouterApiKey: string | undefined
68
+
69
+ constructor(options: ProviderEmbeddingsOptions) {
53
70
  this.embedFn = options.embedFn ?? embed
54
71
  this.embedManyFn = options.embedManyFn ?? embedMany
55
- this.getCache = options.getCache ?? getEmbeddingCache
56
- this.configuredModelId = options.modelId
72
+ this.getCache = options.getCache ?? (() => null)
73
+ this.resolvedModelId = options.modelId
74
+ this.openRouterApiKey = options.openRouterApiKey
57
75
  }
58
76
 
59
77
  private getModelId(): string {
60
- if (!this.resolvedModelId) {
61
- this.resolvedModelId = this.configuredModelId ?? getRuntimeConfig().aiGateway.embeddingModel
62
- }
63
-
64
78
  return this.resolvedModelId
65
79
  }
66
80
 
67
81
  private getModel() {
68
82
  if (!this._model) {
69
- this._model = resolveEmbeddingModel(this.getModelId())
83
+ this._model = resolveEmbeddingModel(this.getModelId(), this.openRouterApiKey)
70
84
  }
71
85
  return this._model
72
86
  }
73
87
 
74
- private async loadCachedEmbedding(text: string): Promise<number[] | null> {
88
+ private loadCachedEmbedding(text: string): Promise<number[] | null> {
75
89
  const redisCache = this.getCache()
76
- if (!redisCache) return null
90
+ if (!redisCache) return Promise.resolve(null)
77
91
 
78
92
  return redisCache.get(this.getModelId(), text)
79
93
  }
80
94
 
81
- async embedQuery(text: string): Promise<number[]> {
95
+ private runEffect<A>(effect: Effect.Effect<A, EmbeddingProviderError>): Promise<A> {
96
+ return runPromiseWithOptionalLotaRuntime(effect)
97
+ }
98
+
99
+ embedQuery(text: string): Promise<number[]> {
82
100
  const input = text.trim()
83
- if (!input) return []
101
+ if (!input) return Promise.resolve([])
84
102
 
85
- const cached = await this.loadCachedEmbedding(input)
86
- if (cached) return cached
103
+ const dedupKey = `${this.getModelId()}:${input}`
104
+ const pending = this.inflightEmbeddings.get(dedupKey)
105
+ if (pending) return pending
87
106
 
88
- const result = await this.embedFn({ model: this.getModel(), value: input, maxRetries: 2 })
89
- const embedding = normalizeEmbedding(result.embedding)
107
+ const promise = this.runEffect(this.executeEmbedQueryEffect(input))
108
+ this.inflightEmbeddings.set(dedupKey, promise)
109
+ void promise.finally(() => this.inflightEmbeddings.delete(dedupKey))
90
110
 
91
- const redisCache = this.getCache()
92
- if (redisCache) {
93
- void redisCache.set(this.getModelId(), input, embedding)
94
- }
111
+ return promise
112
+ }
113
+
114
+ private executeEmbedQueryEffect(input: string): Effect.Effect<number[], EmbeddingProviderError> {
115
+ return Effect.gen(
116
+ function* (this: ProviderEmbeddings) {
117
+ const cached = yield* tryEmbeddingPromise('Failed to load cached query embedding.', () =>
118
+ this.loadCachedEmbedding(input),
119
+ )
120
+ if (cached) {
121
+ return cached
122
+ }
123
+
124
+ const result = yield* tryEmbeddingPromise('Failed to generate query embedding.', () =>
125
+ this.embedFn({ model: this.getModel(), value: input, maxRetries: 2 }),
126
+ )
127
+ const embedding = normalizeEmbedding(result.embedding)
128
+
129
+ const redisCache = this.getCache()
130
+ if (redisCache) {
131
+ void redisCache.set(this.getModelId(), input, embedding)
132
+ }
95
133
 
96
- return embedding
134
+ return embedding
135
+ }.bind(this),
136
+ ).pipe(Effect.withSpan('ProviderEmbeddings.executeEmbedQuery'))
97
137
  }
98
138
 
99
- async embedDocuments(values: string[]): Promise<number[][]> {
100
- if (values.length === 0) return []
139
+ embedDocuments(values: string[]): Promise<number[][]> {
140
+ if (values.length === 0) return Promise.resolve([])
101
141
 
102
142
  const normalized = values.map((value) => value.trim())
103
143
  const nonEmptyEntries = normalized
@@ -105,57 +145,62 @@ export class ProviderEmbeddings {
105
145
  .filter((entry) => entry.value.length > 0)
106
146
 
107
147
  if (nonEmptyEntries.length === 0) {
108
- return normalized.map(() => [])
148
+ return Promise.resolve(normalized.map(() => []))
109
149
  }
110
150
 
111
151
  const uniqueTexts = [...new Set(nonEmptyEntries.map((entry) => entry.value))]
112
- const embeddingsByText = new Map<string, number[]>()
113
- let missingTexts = [...uniqueTexts]
152
+ return this.runEffect(this.embedDocumentsEffect(normalized, uniqueTexts))
153
+ }
114
154
 
115
- const redisCache = this.getCache()
116
- if (redisCache && missingTexts.length > 0) {
117
- const redisResults = await Promise.all(
118
- missingTexts.map(async (text) => ({ text, embedding: await redisCache.get(this.getModelId(), text) })),
119
- )
120
-
121
- missingTexts = []
122
- for (const result of redisResults) {
123
- if (!result.embedding) {
124
- missingTexts.push(result.text)
125
- continue
155
+ private embedDocumentsEffect(
156
+ normalized: string[],
157
+ uniqueTexts: string[],
158
+ ): Effect.Effect<number[][], EmbeddingProviderError> {
159
+ return Effect.gen(
160
+ function* (this: ProviderEmbeddings) {
161
+ const embeddingsByText = new Map<string, number[]>()
162
+ let missingTexts = [...uniqueTexts]
163
+ const redisCache = this.getCache()
164
+ const redisResults =
165
+ redisCache && missingTexts.length > 0
166
+ ? yield* Effect.all(
167
+ missingTexts.map((text) =>
168
+ tryEmbeddingPromise('Failed to load cached document embedding.', () =>
169
+ redisCache.get(this.getModelId(), text),
170
+ ).pipe(Effect.map((embedding) => ({ text, embedding }))),
171
+ ),
172
+ )
173
+ : ([] as Array<{ text: string; embedding: number[] | null }>)
174
+
175
+ if (redisCache && missingTexts.length > 0) {
176
+ missingTexts = []
177
+ for (const result of redisResults) {
178
+ if (!result.embedding) {
179
+ missingTexts.push(result.text)
180
+ continue
181
+ }
182
+
183
+ embeddingsByText.set(result.text, result.embedding)
184
+ }
126
185
  }
127
186
 
128
- embeddingsByText.set(result.text, result.embedding)
129
- }
130
- }
131
-
132
- if (missingTexts.length > 0) {
133
- const result = await this.embedManyFn({ model: this.getModel(), values: missingTexts, maxRetries: 2 })
134
-
135
- missingTexts.forEach((text, index) => {
136
- const embedding = normalizeEmbedding(result.embeddings[index] ?? [])
137
- embeddingsByText.set(text, embedding)
138
- if (redisCache) {
139
- void redisCache.set(this.getModelId(), text, embedding)
187
+ if (missingTexts.length === 0) {
188
+ return normalized.map((text) => (text ? (embeddingsByText.get(text) ?? []) : []))
140
189
  }
141
- })
142
- }
143
190
 
144
- return normalized.map((text) => (text ? (embeddingsByText.get(text) ?? []) : []))
191
+ const result = yield* tryEmbeddingPromise('Failed to generate document embeddings.', () =>
192
+ this.embedManyFn({ model: this.getModel(), values: missingTexts, maxRetries: 2 }),
193
+ )
194
+ missingTexts.forEach((text, index) => {
195
+ const embedding = normalizeEmbedding(result.embeddings[index] ?? [])
196
+ embeddingsByText.set(text, embedding)
197
+ if (redisCache) {
198
+ void redisCache.set(this.getModelId(), text, embedding)
199
+ }
200
+ })
201
+
202
+ return normalized.map((text) => (text ? (embeddingsByText.get(text) ?? []) : []))
203
+ }.bind(this),
204
+ ).pipe(Effect.withSpan('ProviderEmbeddings.embedDocuments'))
145
205
  }
146
206
  }
147
-
148
- let defaultEmbeddings: ProviderEmbeddings | null = null
149
-
150
- export function getDefaultEmbeddings(): ProviderEmbeddings {
151
- if (!defaultEmbeddings) {
152
- defaultEmbeddings = new ProviderEmbeddings()
153
- }
154
-
155
- return defaultEmbeddings
156
- }
157
-
158
- export function resetDefaultEmbeddingsForTests(): void {
159
- defaultEmbeddings = null
160
- resetDirectOpenRouterProviderForTests()
161
- }
package/src/index.ts CHANGED
@@ -6,10 +6,57 @@ export * from './db'
6
6
  export * from './document'
7
7
  export * from './queues'
8
8
  export * from './redis'
9
- export * from './runtime/index'
9
+ export * from './runtime'
10
10
  export * from './services'
11
11
  export * from './storage'
12
12
  export * from './system-agents'
13
13
  export * from './tools'
14
14
  export * from './utils'
15
15
  export * from './workers'
16
+ export { Effect } from 'effect'
17
+ export {
18
+ ActiveThreadRunConflictError,
19
+ AgentConfigLive,
20
+ AgentFactoryLive,
21
+ AiGenerationError,
22
+ AppLoggerLive,
23
+ AppLoggerTag,
24
+ BaseServicePersistenceError,
25
+ ConfigurationError,
26
+ ConflictError,
27
+ DatabaseError,
28
+ DatabaseLive,
29
+ DatabaseServiceTag as EffectDatabaseService,
30
+ ForbiddenError,
31
+ LockAcquisitionError,
32
+ LockLostError,
33
+ RedisError,
34
+ RedisLive,
35
+ RedisServiceTag as EffectRedisService,
36
+ RuntimeAdaptersServiceTag,
37
+ RuntimeConfigLive,
38
+ RuntimeConfigServiceTag,
39
+ RuntimeExtensionsLive,
40
+ RuntimeWorkerExtensionsServiceTag,
41
+ ServiceError,
42
+ ThreadConfigLive,
43
+ ThreadConfigServiceTag,
44
+ ThreadTurnError,
45
+ TimeoutError,
46
+ ToolProvidersServiceTag,
47
+ TurnHooksServiceTag,
48
+ ValidationError,
49
+ clearLotaSdkRuntime,
50
+ getLotaSdkRuntime,
51
+ isEffectError,
52
+ setLotaSdkRuntime,
53
+ summarizeZodIssues,
54
+ toAwaitableEffect,
55
+ toAwaitableService,
56
+ toValidationError,
57
+ toValidationIssues,
58
+ zodParse,
59
+ } from './effect'
60
+ export { AppError, BadRequestError, NotFoundError } from './utils'
61
+ export type { AppErrorLike, AppErrorResponse } from './utils'
62
+ export type { EffectError, ValidationIssue } from './effect'
@@ -1,53 +1,29 @@
1
1
  import { createOpenAI } from '@ai-sdk/openai'
2
2
 
3
- import { getRuntimeConfig } from '../runtime/runtime-config'
3
+ import { ConfigurationError } from '../effect/errors'
4
4
 
5
5
  const DIRECT_OPENROUTER_BASE_URL = 'https://openrouter.ai/api/v1' as const
6
6
  const OPENROUTER_MODEL_PREFIX = 'openrouter/' as const
7
7
 
8
- let directOpenRouterProvider: ReturnType<typeof createOpenAI> | null = null
9
- let directOpenRouterProviderKey: string | null = null
8
+ export function resolveOpenRouterApiKey(openRouterApiKey: string | undefined): string {
9
+ const key = openRouterApiKey?.trim()
10
+ if (key) return key
10
11
 
11
- function readConfiguredOpenRouterApiKey(): string | null {
12
- try {
13
- return getRuntimeConfig().aiGateway.openRouterApiKey?.trim() || null
14
- } catch {
15
- return null
16
- }
17
- }
18
-
19
- export function resolveOpenRouterApiKey(): string {
20
- const configured = readConfiguredOpenRouterApiKey()
21
- if (configured) return configured
22
-
23
- const envKey = process.env.OPENROUTER_API_KEY?.trim()
24
- if (envKey) return envKey
25
-
26
- throw new Error('Missing OpenRouter API key. Set aiGateway.openRouterApiKey or OPENROUTER_API_KEY.')
12
+ throw new ConfigurationError({
13
+ message: 'Missing OpenRouter API key. Configure createLotaRuntime({ aiGateway: { openRouterApiKey } }).',
14
+ key: 'aiGateway.openRouterApiKey',
15
+ })
27
16
  }
28
17
 
29
18
  export function normalizeDirectOpenRouterModelId(modelId: string): string {
30
19
  const normalized = modelId.trim()
31
20
  if (!normalized) {
32
- throw new Error('OpenRouter model id is required.')
21
+ throw new ConfigurationError({ message: 'OpenRouter model id is required.', key: 'openRouterModelId' })
33
22
  }
34
23
 
35
24
  return normalized.startsWith(OPENROUTER_MODEL_PREFIX) ? normalized.slice(OPENROUTER_MODEL_PREFIX.length) : normalized
36
25
  }
37
26
 
38
- export function getDirectOpenRouterProvider() {
39
- const apiKey = resolveOpenRouterApiKey()
40
- if (directOpenRouterProvider && directOpenRouterProviderKey === apiKey) {
41
- return directOpenRouterProvider
42
- }
43
-
44
- directOpenRouterProvider = createOpenAI({ baseURL: DIRECT_OPENROUTER_BASE_URL, apiKey })
45
- directOpenRouterProviderKey = apiKey
46
-
47
- return directOpenRouterProvider
48
- }
49
-
50
- export function resetDirectOpenRouterProviderForTests(): void {
51
- directOpenRouterProvider = null
52
- directOpenRouterProviderKey = null
27
+ export function getDirectOpenRouterProvider(openRouterApiKey?: string) {
28
+ return createOpenAI({ baseURL: DIRECT_OPENROUTER_BASE_URL, apiKey: resolveOpenRouterApiKey(openRouterApiKey) })
53
29
  }
@@ -1,14 +1,22 @@
1
1
  import type { AutonomousJobSchedule } from '@lota-sdk/shared'
2
2
  import type { Job } from 'bullmq'
3
+ import { Effect, Schema } from 'effect'
4
+ import type { Context } from 'effect'
5
+ import type IORedis from 'ioredis'
3
6
 
4
7
  import { serverLogger } from '../config/logger'
5
- import { databaseService } from '../db/service'
6
- import { autonomousJobService } from '../services/autonomous-job.service'
7
- import { queueJobService } from '../services/queue-job.service'
8
+ import { DatabaseServiceTag, RedisServiceTag } from '../effect/services'
9
+ import { AutonomousJobServiceTag } from '../services/autonomous-job.service'
8
10
  import { buildAutonomousAtJobId } from '../utils/autonomous-job-ids'
9
11
  import type { WorkerHandle } from '../workers/worker-utils'
10
12
  import { DEFAULT_JOB_RETENTION } from '../workers/worker-utils'
11
- import { createQueueFactory } from './queue-factory'
13
+ import { createQueueFactoryWithDeps, recordEnqueuedJobMetadata } from './queue-factory'
14
+ import { runStandaloneQueueWorker } from './standalone-worker'
15
+
16
+ class AutonomousJobQueueError extends Schema.TaggedErrorClass<AutonomousJobQueueError>()(
17
+ '@lota-sdk/core/AutonomousJobQueueError',
18
+ { message: Schema.String, cause: Schema.optional(Schema.Defect) },
19
+ ) {}
12
20
 
13
21
  export interface AutonomousJobQueuePayload {
14
22
  autonomousJobId: string
@@ -18,25 +26,31 @@ export interface AutonomousJobQueuePayload {
18
26
 
19
27
  export const AUTONOMOUS_JOB_QUEUE = 'autonomous-job'
20
28
 
29
+ interface AutonomousJobQueueDeps {
30
+ databaseService: Context.Service.Shape<typeof DatabaseServiceTag>
31
+ autonomousJobService: Context.Service.Shape<typeof AutonomousJobServiceTag>
32
+ }
33
+
21
34
  const DEFAULT_AUTONOMOUS_JOB_OPTIONS = {
22
35
  ...DEFAULT_JOB_RETENTION,
23
36
  attempts: 3,
24
37
  backoff: { type: 'exponential', delay: 5_000 },
25
38
  } as const
26
39
 
27
- async function processAutonomousJob(
40
+ function processAutonomousJob(
41
+ deps: AutonomousJobQueueDeps,
28
42
  job: Job<AutonomousJobQueuePayload>,
29
43
  ): Promise<{ status: string; summary?: string }> {
30
- await databaseService.connect()
31
- return autonomousJobService.executeQueuedRun(job)
44
+ return Effect.runPromise(deps.autonomousJobService.executeQueuedRun(job))
32
45
  }
33
46
 
34
- const autonomousJobQueue = createQueueFactory<AutonomousJobQueuePayload>({
47
+ const autonomousJobQueue = createQueueFactoryWithDeps<AutonomousJobQueuePayload, AutonomousJobQueueDeps>({
35
48
  name: AUTONOMOUS_JOB_QUEUE,
36
49
  displayName: 'Autonomous job',
37
50
  jobName: 'run-autonomous-job',
38
51
  concurrency: 2,
39
52
  defaultJobOptions: DEFAULT_AUTONOMOUS_JOB_OPTIONS,
53
+ prepare: ({ databaseService }) => databaseService.connect(),
40
54
  processor: processAutonomousJob,
41
55
  })
42
56
 
@@ -44,91 +58,121 @@ function buildAutonomousSchedulerId(autonomousJobId: string): string {
44
58
  return `autonomous:${autonomousJobId}`
45
59
  }
46
60
 
47
- export async function enqueueAutonomousJobRun(params: {
61
+ export function enqueueAutonomousJobRun(params: {
48
62
  payload: AutonomousJobQueuePayload
49
63
  delayMs?: number
50
64
  jobId?: string
51
65
  }): Promise<{ bullmqJobId: string; queueJobId?: string }> {
52
- const queuedJob = await autonomousJobQueue
53
- .getQueue()
54
- .add('run-autonomous-job', params.payload, {
55
- ...(typeof params.delayMs === 'number' ? { delay: Math.max(0, params.delayMs) } : {}),
56
- ...(params.jobId ? { jobId: params.jobId } : {}),
57
- })
58
-
59
- const bullmqJobId = String(queuedJob.id)
60
- let queueJobId: string | undefined
61
-
62
- try {
63
- queueJobId = await queueJobService.recordEnqueued({
64
- queueName: AUTONOMOUS_JOB_QUEUE,
65
- id: queuedJob.id,
66
- name: queuedJob.name,
67
- data: queuedJob.data,
68
- opts: queuedJob.opts,
69
- attemptsMade: queuedJob.attemptsMade,
70
- timestamp: queuedJob.timestamp,
71
- })
72
- } catch (error) {
73
- serverLogger.error`Failed to persist queued job metadata (queue=${AUTONOMOUS_JOB_QUEUE}, job=${queuedJob.id}): ${error}`
74
- }
75
-
76
- return { bullmqJobId, queueJobId }
66
+ return Effect.runPromise(
67
+ Effect.gen(function* () {
68
+ const queuedJob = yield* Effect.tryPromise({
69
+ try: () =>
70
+ autonomousJobQueue
71
+ .getQueue()
72
+ .add('run-autonomous-job', params.payload, {
73
+ ...(typeof params.delayMs === 'number' ? { delay: Math.max(0, params.delayMs) } : {}),
74
+ ...(params.jobId ? { jobId: params.jobId } : {}),
75
+ }),
76
+ catch: (cause) => new AutonomousJobQueueError({ message: 'Failed to enqueue autonomous job run.', cause }),
77
+ })
78
+
79
+ const bullmqJobId = String(queuedJob.id)
80
+ const queueJobId = yield* recordEnqueuedJobMetadata({ queueName: AUTONOMOUS_JOB_QUEUE, job: queuedJob })
81
+ return { bullmqJobId, queueJobId }
82
+ }),
83
+ )
77
84
  }
78
85
 
79
- export async function upsertAutonomousJobScheduler(params: {
86
+ export function upsertAutonomousJobScheduler(params: {
80
87
  autonomousJobId: string
81
88
  schedule: Extract<AutonomousJobSchedule, { kind: 'cron' | 'every' }>
82
89
  }): Promise<void> {
83
90
  const repeatOpts =
84
91
  params.schedule.kind === 'cron' ? { pattern: params.schedule.cron } : { every: params.schedule.intervalMs }
85
- const queuedJob = await autonomousJobQueue
86
- .getQueue()
87
- .upsertJobScheduler(buildAutonomousSchedulerId(params.autonomousJobId), repeatOpts, {
88
- name: 'run-autonomous-job',
89
- data: { autonomousJobId: params.autonomousJobId, trigger: 'scheduled' },
90
- opts: DEFAULT_AUTONOMOUS_JOB_OPTIONS,
91
- })
92
-
93
- try {
94
- await queueJobService.recordEnqueued({
95
- queueName: AUTONOMOUS_JOB_QUEUE,
96
- id: queuedJob.id,
97
- name: queuedJob.name,
98
- data: queuedJob.data,
99
- opts: queuedJob.opts,
100
- attemptsMade: queuedJob.attemptsMade,
101
- timestamp: queuedJob.timestamp,
102
- })
103
- } catch (error) {
104
- serverLogger.error`Failed to persist queued job metadata (queue=${AUTONOMOUS_JOB_QUEUE}, job=${queuedJob.id}): ${error}`
105
- }
92
+ return Effect.runPromise(
93
+ Effect.gen(function* () {
94
+ const queuedJob = yield* Effect.tryPromise({
95
+ try: () =>
96
+ autonomousJobQueue
97
+ .getQueue()
98
+ .upsertJobScheduler(buildAutonomousSchedulerId(params.autonomousJobId), repeatOpts, {
99
+ name: 'run-autonomous-job',
100
+ data: { autonomousJobId: params.autonomousJobId, trigger: 'scheduled' },
101
+ opts: DEFAULT_AUTONOMOUS_JOB_OPTIONS,
102
+ }),
103
+ catch: (cause) =>
104
+ new AutonomousJobQueueError({
105
+ message: `Failed to upsert autonomous job scheduler for ${params.autonomousJobId}.`,
106
+ cause,
107
+ }),
108
+ })
109
+
110
+ yield* recordEnqueuedJobMetadata({ queueName: AUTONOMOUS_JOB_QUEUE, job: queuedJob })
111
+ }),
112
+ )
106
113
  }
107
114
 
108
- export async function removeAutonomousJobScheduler(autonomousJobId: string): Promise<void> {
109
- await autonomousJobQueue.getQueue().removeJobScheduler(buildAutonomousSchedulerId(autonomousJobId))
115
+ export function removeAutonomousJobScheduler(autonomousJobId: string): Promise<void> {
116
+ return Effect.runPromise(
117
+ Effect.asVoid(
118
+ Effect.tryPromise({
119
+ try: () => autonomousJobQueue.getQueue().removeJobScheduler(buildAutonomousSchedulerId(autonomousJobId)),
120
+ catch: (cause) =>
121
+ new AutonomousJobQueueError({
122
+ message: `Failed to remove autonomous job scheduler for ${autonomousJobId}.`,
123
+ cause,
124
+ }),
125
+ }),
126
+ ),
127
+ )
110
128
  }
111
129
 
112
- export async function removeAutonomousAtJob(autonomousJobId: string): Promise<void> {
113
- try {
114
- await autonomousJobQueue.getQueue().remove(buildAutonomousAtJobId(autonomousJobId))
115
- } catch {
116
- // The delayed job may have already fired or never existed.
117
- }
130
+ export function removeAutonomousAtJob(autonomousJobId: string): Promise<void> {
131
+ return Effect.runPromise(
132
+ Effect.catch(
133
+ Effect.asVoid(
134
+ Effect.tryPromise({
135
+ try: () => autonomousJobQueue.getQueue().remove(buildAutonomousAtJobId(autonomousJobId)),
136
+ catch: (cause) =>
137
+ new AutonomousJobQueueError({
138
+ message: `Failed to remove autonomous-at job for ${autonomousJobId}.`,
139
+ cause,
140
+ }),
141
+ }),
142
+ ),
143
+ () => Effect.void,
144
+ ),
145
+ )
118
146
  }
119
147
 
120
- type AutonomousJobWorkerOptions = Parameters<typeof autonomousJobQueue.startWorker>[0]
121
-
122
- export function startAutonomousJobWorker(options: AutonomousJobWorkerOptions = {}): WorkerHandle {
123
- const handle = autonomousJobQueue.startWorker(options)
148
+ interface AutonomousJobWorkerOptions {
149
+ registerSignals?: boolean
150
+ connectionProvider: () => IORedis
151
+ deps: AutonomousJobQueueDeps
152
+ }
124
153
 
125
- autonomousJobService.recoverActiveJobs().catch((error: unknown) => {
126
- serverLogger.error`Autonomous job startup recovery failed: ${error}`
154
+ export function startAutonomousJobWorker(options: AutonomousJobWorkerOptions): WorkerHandle {
155
+ const handle = autonomousJobQueue.startWorker({
156
+ deps: options.deps,
157
+ registerSignals: options.registerSignals,
158
+ connectionProvider: options.connectionProvider,
127
159
  })
128
160
 
161
+ void Effect.runPromise(
162
+ Effect.catch(options.deps.autonomousJobService.recoverActiveJobs(), (error) =>
163
+ Effect.sync(() => {
164
+ serverLogger.error`Autonomous job startup recovery failed: ${error}`
165
+ }),
166
+ ),
167
+ )
168
+
129
169
  return handle
130
170
  }
131
171
 
132
- if (import.meta.main) {
133
- startAutonomousJobWorker()
134
- }
172
+ runStandaloneQueueWorker((runtime) => {
173
+ const resolve = <I, T>(tag: Context.Key<I, T>): T => runtime.runSync(Effect.service(tag))
174
+ startAutonomousJobWorker({
175
+ connectionProvider: () => resolve(RedisServiceTag).getConnectionForBullMQ(),
176
+ deps: { databaseService: resolve(DatabaseServiceTag), autonomousJobService: resolve(AutonomousJobServiceTag) },
177
+ })
178
+ })