@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,228 @@
1
+ import type { ToolSet } from 'ai'
2
+ import { Effect, Layer, Logger, References } from 'effect'
3
+
4
+ import type { CoreThreadProfile } from '../config/agent-defaults'
5
+ import { resolveAgentConfig, resolveAgentFactoryConfig } from '../config/agent-defaults'
6
+ import type { AgentFactory, AgentRuntimeConfigProvider, AgentToolBuilder } from '../config/agent-types'
7
+ import { configureLotaLogger, getLotaLoggers, toEffectLogLevel } from '../config/logger'
8
+ import type { LotaLogLevel } from '../config/logger'
9
+ import { resolveThreadConfig } from '../config/thread-defaults'
10
+ import type { LotaThreadConfig } from '../config/thread-defaults'
11
+ import { LOTA_SDK_DATABASE_NAME } from '../db/sdk-database'
12
+ import type { SurrealDatabaseConfig } from '../db/service'
13
+ import { SurrealDBService } from '../db/service'
14
+ import type { CreateRedisConnectionManagerOptions } from '../redis/connection'
15
+ import { createRedisConnectionManager } from '../redis/connection'
16
+ import type { ResolvedLotaRuntimeConfig } from '../runtime/runtime-config'
17
+ import type { LotaRuntimeAdapters, LotaRuntimeTurnHooks } from '../runtime/runtime-extensions'
18
+ import type { LotaRuntimeWorkerExtensions } from '../runtime/runtime-worker-registry'
19
+ import { QueueJobServiceLive } from '../services/queue-job.service'
20
+ import { getErrorMessage } from '../utils/errors'
21
+ import { DatabaseError, RedisError } from './errors'
22
+ import { effectTryServicePromise } from './helpers'
23
+ import {
24
+ AppLoggerTag,
25
+ AgentConfigServiceTag,
26
+ AgentFactoryServiceTag,
27
+ DatabaseServiceTag,
28
+ RedisServiceTag,
29
+ RuntimeAdaptersServiceTag,
30
+ RuntimeConfigServiceTag,
31
+ RuntimeWorkerExtensionsServiceTag,
32
+ ThreadConfigServiceTag,
33
+ ToolProvidersServiceTag,
34
+ TurnHooksServiceTag,
35
+ } from './services'
36
+
37
+ const EMPTY_TOOLS = {} as ToolSet
38
+ const EMPTY_WORKERS: LotaRuntimeWorkerExtensions = {}
39
+
40
+ export function DatabaseLive(config: SurrealDatabaseConfig) {
41
+ return Layer.effect(
42
+ DatabaseServiceTag,
43
+ Effect.acquireRelease(
44
+ Effect.try({
45
+ try: () => new SurrealDBService(config),
46
+ catch: (error) =>
47
+ new DatabaseError({ message: `Failed to create database service: ${getErrorMessage(error)}`, cause: error }),
48
+ }),
49
+ (databaseService) =>
50
+ Effect.ignore(
51
+ databaseService
52
+ .disconnect()
53
+ .pipe(
54
+ Effect.tapError((error) =>
55
+ Effect.logWarning(`Failed to clean up database service: ${getErrorMessage(error)}`),
56
+ ),
57
+ ),
58
+ ),
59
+ ),
60
+ )
61
+ }
62
+
63
+ export function RedisLive(config: CreateRedisConnectionManagerOptions) {
64
+ return Layer.effect(
65
+ RedisServiceTag,
66
+ Effect.acquireRelease(
67
+ Effect.try({
68
+ try: () => createRedisConnectionManager(config),
69
+ catch: (error) =>
70
+ new RedisError({
71
+ message: `Failed to create Redis connection manager: ${getErrorMessage(error)}`,
72
+ cause: error,
73
+ }),
74
+ }),
75
+ (redisManager) =>
76
+ Effect.ignore(
77
+ effectTryServicePromise(
78
+ () => redisManager.closeConnection(),
79
+ 'Failed to close Redis connection manager',
80
+ ).pipe(
81
+ Effect.tapError((error) =>
82
+ Effect.logWarning(`Failed to clean up Redis connection manager: ${getErrorMessage(error)}`),
83
+ ),
84
+ ),
85
+ ),
86
+ ),
87
+ )
88
+ }
89
+
90
+ export function AppLoggerLive(level: LotaLogLevel = 'info') {
91
+ return Layer.mergeAll(
92
+ Layer.effectDiscard(Effect.sync(() => configureLotaLogger(level))),
93
+ Layer.succeed(AppLoggerTag, getLotaLoggers()),
94
+ Logger.layer([Logger.consolePretty()]),
95
+ Layer.succeed(References.MinimumLogLevel, toEffectLogLevel(level)),
96
+ )
97
+ }
98
+
99
+ export function RuntimeConfigLive(config: ResolvedLotaRuntimeConfig) {
100
+ return Layer.succeed(RuntimeConfigServiceTag, config)
101
+ }
102
+
103
+ export function AgentConfigLive(config: {
104
+ roster: readonly string[]
105
+ leadAgentId: string
106
+ displayNames: Record<string, string>
107
+ shortDisplayNames?: Record<string, string>
108
+ descriptions?: Record<string, string>
109
+ routerModelId?: string
110
+ teamConsultParticipants: readonly string[]
111
+ getCoreThreadProfile?: (coreType: string) => CoreThreadProfile
112
+ }) {
113
+ return Layer.succeed(AgentConfigServiceTag, resolveAgentConfig(config))
114
+ }
115
+
116
+ export function AgentFactoryLive(config: {
117
+ createAgent?: AgentFactory
118
+ buildAgentTools?: AgentToolBuilder
119
+ getAgentRuntimeConfig?: AgentRuntimeConfigProvider
120
+ pluginRuntime?: Record<string, unknown>
121
+ }) {
122
+ return Layer.succeed(AgentFactoryServiceTag, resolveAgentFactoryConfig(config))
123
+ }
124
+
125
+ export function ThreadConfigLive(params: { agentRoster: readonly string[]; config?: LotaThreadConfig }) {
126
+ return Layer.succeed(ThreadConfigServiceTag, resolveThreadConfig(params))
127
+ }
128
+
129
+ export function RuntimeExtensionsLive(params: {
130
+ adapters?: LotaRuntimeAdapters
131
+ turnHooks?: LotaRuntimeTurnHooks
132
+ toolProviders?: ToolSet
133
+ extraWorkers?: LotaRuntimeWorkerExtensions
134
+ }) {
135
+ return Layer.mergeAll(
136
+ Layer.succeed(RuntimeAdaptersServiceTag, params.adapters ?? {}),
137
+ Layer.succeed(TurnHooksServiceTag, params.turnHooks ?? {}),
138
+ Layer.succeed(ToolProvidersServiceTag, params.toolProviders ?? EMPTY_TOOLS),
139
+ Layer.succeed(RuntimeWorkerExtensionsServiceTag, params.extraWorkers ?? EMPTY_WORKERS),
140
+ )
141
+ }
142
+
143
+ // ── Shared Infrastructure Layer Builders ─────────────────────────────
144
+
145
+ /**
146
+ * Builds the full infrastructure layer used by `createLotaRuntime`.
147
+ * Contains all config, database, redis, agent, thread, and extension layers.
148
+ */
149
+ export function buildInfrastructureLayer(
150
+ runtimeConfig: ResolvedLotaRuntimeConfig,
151
+ options: { resolvedAgentDisplayNames: Record<string, string>; observabilityLayer: Layer.Layer<never> },
152
+ ) {
153
+ return Layer.mergeAll(
154
+ AppLoggerLive(runtimeConfig.logging.level),
155
+ RuntimeConfigLive(runtimeConfig),
156
+ AgentConfigLive({
157
+ roster: runtimeConfig.agents.roster,
158
+ leadAgentId: runtimeConfig.agents.leadAgentId,
159
+ displayNames: options.resolvedAgentDisplayNames,
160
+ shortDisplayNames: runtimeConfig.agents.shortDisplayNames,
161
+ descriptions: runtimeConfig.agents.descriptions,
162
+ routerModelId: runtimeConfig.agents.routerModelId,
163
+ teamConsultParticipants: runtimeConfig.agents.teamConsultParticipants,
164
+ getCoreThreadProfile: runtimeConfig.agents.getCoreThreadProfile,
165
+ }),
166
+ AgentFactoryLive({
167
+ createAgent: runtimeConfig.agents.createAgent,
168
+ buildAgentTools: runtimeConfig.agents.buildAgentTools,
169
+ getAgentRuntimeConfig: runtimeConfig.agents.getAgentRuntimeConfig,
170
+ pluginRuntime: runtimeConfig.pluginRuntime,
171
+ }),
172
+ ThreadConfigLive({ agentRoster: runtimeConfig.agents.roster, config: runtimeConfig.threads }),
173
+ RuntimeExtensionsLive({
174
+ adapters: runtimeConfig.runtimeAdapters,
175
+ turnHooks: runtimeConfig.turnHooks,
176
+ toolProviders: runtimeConfig.toolProviders ?? {},
177
+ extraWorkers: runtimeConfig.extraWorkers,
178
+ }),
179
+ DatabaseLive({
180
+ url: runtimeConfig.database.url,
181
+ namespace: runtimeConfig.database.namespace,
182
+ database: LOTA_SDK_DATABASE_NAME,
183
+ username: runtimeConfig.database.username,
184
+ password: runtimeConfig.database.password,
185
+ }),
186
+ RedisLive({ url: runtimeConfig.redis.url }),
187
+ options.observabilityLayer,
188
+ )
189
+ }
190
+
191
+ /**
192
+ * Builds the minimal infrastructure layer used by sandboxed workers.
193
+ * Omits observability and uses empty runtime extensions.
194
+ */
195
+ export function buildWorkerInfrastructureLayer(runtimeConfig: ResolvedLotaRuntimeConfig) {
196
+ const infrastructureLayer = Layer.mergeAll(
197
+ AppLoggerLive(runtimeConfig.logging.level),
198
+ RuntimeConfigLive(runtimeConfig),
199
+ AgentConfigLive({
200
+ roster: runtimeConfig.agents.roster,
201
+ leadAgentId: runtimeConfig.agents.leadAgentId,
202
+ displayNames: runtimeConfig.agents.displayNames,
203
+ shortDisplayNames: runtimeConfig.agents.shortDisplayNames,
204
+ descriptions: runtimeConfig.agents.descriptions,
205
+ routerModelId: runtimeConfig.agents.routerModelId,
206
+ teamConsultParticipants: runtimeConfig.agents.teamConsultParticipants,
207
+ getCoreThreadProfile: runtimeConfig.agents.getCoreThreadProfile,
208
+ }),
209
+ AgentFactoryLive({
210
+ createAgent: runtimeConfig.agents.createAgent,
211
+ buildAgentTools: runtimeConfig.agents.buildAgentTools,
212
+ getAgentRuntimeConfig: runtimeConfig.agents.getAgentRuntimeConfig,
213
+ pluginRuntime: runtimeConfig.pluginRuntime,
214
+ }),
215
+ ThreadConfigLive({ agentRoster: runtimeConfig.agents.roster, config: runtimeConfig.threads }),
216
+ RuntimeExtensionsLive({}),
217
+ DatabaseLive({
218
+ url: runtimeConfig.database.url,
219
+ namespace: runtimeConfig.database.namespace,
220
+ database: LOTA_SDK_DATABASE_NAME,
221
+ username: runtimeConfig.database.username,
222
+ password: runtimeConfig.database.password,
223
+ }),
224
+ RedisLive({ url: runtimeConfig.redis.url }),
225
+ )
226
+
227
+ return QueueJobServiceLive.pipe(Layer.provideMerge(infrastructureLayer))
228
+ }
@@ -0,0 +1,25 @@
1
+ import type { ManagedRuntime } from 'effect'
2
+
3
+ // eslint-disable-next-line typescript-eslint/no-explicit-any -- ManagedRuntime is contravariant in R; `any` is the only valid wildcard
4
+ type SdkManagedRuntime = ManagedRuntime.ManagedRuntime<any, any>
5
+
6
+ let currentRuntime: SdkManagedRuntime | null = null
7
+
8
+ export function setCurrentRuntime<R, E>(runtime: ManagedRuntime.ManagedRuntime<R, E>): void {
9
+ currentRuntime = runtime as SdkManagedRuntime
10
+ }
11
+
12
+ export function clearCurrentRuntime(): void {
13
+ currentRuntime = null
14
+ }
15
+
16
+ export function getCurrentRuntime(): SdkManagedRuntime {
17
+ if (!currentRuntime) {
18
+ throw new Error('Lota Effect runtime is not configured. Call createLotaRuntime() first.')
19
+ }
20
+ return currentRuntime
21
+ }
22
+
23
+ export function getOptionalCurrentRuntime(): SdkManagedRuntime | null {
24
+ return currentRuntime
25
+ }
@@ -0,0 +1,31 @@
1
+ import type { Effect, ManagedRuntime } from 'effect'
2
+
3
+ import { clearCurrentRuntime, getCurrentRuntime, getOptionalCurrentRuntime, setCurrentRuntime } from './runtime-ref'
4
+
5
+ export function setLotaSdkRuntime<R, E>(runtime: ManagedRuntime.ManagedRuntime<R, E>): void {
6
+ setCurrentRuntime(runtime)
7
+ }
8
+
9
+ export function clearLotaSdkRuntime(): void {
10
+ clearCurrentRuntime()
11
+ }
12
+
13
+ export function getOptionalLotaSdkRuntime() {
14
+ return getOptionalCurrentRuntime()
15
+ }
16
+
17
+ /** Returns the current managed runtime or throws. */
18
+ export function getLotaSdkRuntime() {
19
+ return getCurrentRuntime()
20
+ }
21
+
22
+ /**
23
+ * Run an effect as a Promise using the current managed runtime's context.
24
+ * The runtime must be initialized via `createLotaRuntime()` before any call.
25
+ */
26
+ export function runPromise<A, E>(
27
+ effect: Effect.Effect<A, E, never>,
28
+ options?: { readonly signal?: AbortSignal },
29
+ ): Promise<A> {
30
+ return getCurrentRuntime().runPromise(effect, options)
31
+ }
@@ -0,0 +1,57 @@
1
+ import type { ToolSet } from 'ai'
2
+ import { Context } from 'effect'
3
+
4
+ import type { ResolvedAgentConfig } from '../config/agent-defaults'
5
+ import type { AgentFactory, AgentRuntimeConfigProvider, AgentToolBuilder } from '../config/agent-types'
6
+ import type { LotaLoggerSet } from '../config/logger'
7
+ import type { ResolvedThreadBootstrapConfig } from '../config/thread-defaults'
8
+ import type { SurrealDBService } from '../db/service'
9
+ import type { RedisConnectionManager } from '../redis/connection'
10
+ import type { ResolvedLotaRuntimeConfig } from '../runtime/runtime-config'
11
+ import type { LotaRuntimeAdapters, LotaRuntimeTurnHooks } from '../runtime/runtime-extensions'
12
+ import type { LotaRuntimeWorkerExtensions } from '../runtime/runtime-worker-registry'
13
+
14
+ export class DatabaseServiceTag extends Context.Service<DatabaseServiceTag, SurrealDBService>()('DatabaseService') {}
15
+
16
+ export class RedisServiceTag extends Context.Service<RedisServiceTag, RedisConnectionManager>()('RedisService') {}
17
+
18
+ export class AppLoggerTag extends Context.Service<AppLoggerTag, LotaLoggerSet>()('AppLogger') {}
19
+
20
+ export class RuntimeConfigServiceTag extends Context.Service<RuntimeConfigServiceTag, ResolvedLotaRuntimeConfig>()(
21
+ 'RuntimeConfigService',
22
+ ) {}
23
+
24
+ export class AgentConfigServiceTag extends Context.Service<AgentConfigServiceTag, ResolvedAgentConfig>()(
25
+ 'AgentConfigService',
26
+ ) {}
27
+
28
+ export class AgentFactoryServiceTag extends Context.Service<
29
+ AgentFactoryServiceTag,
30
+ {
31
+ readonly createAgent: AgentFactory
32
+ readonly buildAgentTools: AgentToolBuilder
33
+ readonly getAgentRuntimeConfig: AgentRuntimeConfigProvider
34
+ readonly pluginRuntime?: Record<string, unknown>
35
+ }
36
+ >()('AgentFactoryService') {}
37
+
38
+ export class ThreadConfigServiceTag extends Context.Service<ThreadConfigServiceTag, ResolvedThreadBootstrapConfig>()(
39
+ 'ThreadConfigService',
40
+ ) {}
41
+
42
+ export class RuntimeAdaptersServiceTag extends Context.Service<RuntimeAdaptersServiceTag, LotaRuntimeAdapters>()(
43
+ 'RuntimeAdaptersService',
44
+ ) {}
45
+
46
+ export class TurnHooksServiceTag extends Context.Service<TurnHooksServiceTag, LotaRuntimeTurnHooks>()(
47
+ 'TurnHooksService',
48
+ ) {}
49
+
50
+ export class ToolProvidersServiceTag extends Context.Service<ToolProvidersServiceTag, ToolSet>()(
51
+ 'ToolProvidersService',
52
+ ) {}
53
+
54
+ export class RuntimeWorkerExtensionsServiceTag extends Context.Service<
55
+ RuntimeWorkerExtensionsServiceTag,
56
+ LotaRuntimeWorkerExtensions
57
+ >()('RuntimeWorkerExtensionsService') {}
@@ -0,0 +1,43 @@
1
+ import { Effect } from 'effect'
2
+ import { ZodError } from 'zod'
3
+ import type { z } from 'zod'
4
+
5
+ import { ValidationError } from './errors'
6
+ import type { ValidationIssue } from './errors'
7
+
8
+ type ZodIssueLike = { readonly path: ReadonlyArray<PropertyKey>; readonly message: string }
9
+
10
+ function toValidationIssuePath(path: ReadonlyArray<PropertyKey>): string {
11
+ return path.map((segment) => String(segment)).join('.') || 'root'
12
+ }
13
+
14
+ function getZodIssues(error: Pick<ZodError, 'issues'> | ReadonlyArray<ZodIssueLike>): ReadonlyArray<ZodIssueLike> {
15
+ return 'issues' in error ? error.issues : error
16
+ }
17
+
18
+ export function toValidationIssues(
19
+ error: Pick<ZodError, 'issues'> | ReadonlyArray<ZodIssueLike>,
20
+ ): ReadonlyArray<ValidationIssue> {
21
+ return getZodIssues(error).map((issue) => ({ path: toValidationIssuePath(issue.path), message: issue.message }))
22
+ }
23
+
24
+ export function summarizeZodIssues(issues: ReadonlyArray<ZodIssueLike>, maxIssues = 5): string {
25
+ return issues
26
+ .slice(0, maxIssues)
27
+ .map((issue) => `${toValidationIssuePath(issue.path)}: ${issue.message}`)
28
+ .join('; ')
29
+ }
30
+
31
+ export function toValidationError(error: unknown, message?: string): ValidationError {
32
+ return new ValidationError({
33
+ message: message ?? (error instanceof ZodError ? 'Schema validation failed' : 'Validation failed'),
34
+ issues: error instanceof ZodError ? toValidationIssues(error) : undefined,
35
+ })
36
+ }
37
+
38
+ export function zodParse<TSchema extends z.ZodTypeAny>(
39
+ schema: TSchema,
40
+ value: unknown,
41
+ ): Effect.Effect<z.infer<TSchema>, ValidationError> {
42
+ return Effect.try({ try: () => schema.parse(value), catch: toValidationError })
43
+ }
@@ -1,8 +1,9 @@
1
1
  import { embed, embedMany } from 'ai'
2
+ import { Schema, Effect } from 'effect'
2
3
 
3
- import { aiGatewayEmbeddingModel } from '../ai-gateway/ai-gateway'
4
- import { getEmbeddingCache } from '../ai/embedding-cache'
5
- import { getRuntimeConfig } from '../runtime/runtime-config'
4
+ import { ConfigurationError } from '../effect/errors'
5
+ import { getOptionalCurrentRuntime } from '../effect/runtime-ref'
6
+ import { getDirectOpenRouterProvider, normalizeDirectOpenRouterModelId } from '../openrouter/direct-provider'
6
7
 
7
8
  const SUPPORTED_EMBEDDING_PREFIXES = ['openai/', 'openrouter/'] as const
8
9
 
@@ -15,85 +16,129 @@ type ProviderEmbeddingsOptions = {
15
16
  embedFn?: typeof embed
16
17
  embedManyFn?: typeof embedMany
17
18
  getCache?: () => SharedEmbeddingCache | null
18
- modelId?: string
19
+ modelId: string
20
+ openRouterApiKey?: string
19
21
  }
20
22
 
21
- function resolveEmbeddingModel(modelId: string) {
23
+ function resolveEmbeddingModel(modelId: string, openRouterApiKey?: string) {
22
24
  const normalized = modelId.trim()
23
25
  if (!normalized) {
24
- throw new Error('[embeddings-provider] Model id is required.')
26
+ throw new ConfigurationError({ message: '[embeddings-provider] Model id is required.', key: 'embeddingModelId' })
25
27
  }
26
28
 
27
29
  if (!SUPPORTED_EMBEDDING_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {
28
- throw new Error(
29
- `[embeddings-provider] Unsupported model id "${modelId}". Use one of: ${SUPPORTED_EMBEDDING_PREFIXES.join(', ')}*.`,
30
- )
30
+ throw new ConfigurationError({
31
+ message: `[embeddings-provider] Unsupported model id "${modelId}". Use one of: ${SUPPORTED_EMBEDDING_PREFIXES.join(', ')}*.`,
32
+ key: 'embeddingModelId',
33
+ })
31
34
  }
32
35
 
33
- return aiGatewayEmbeddingModel(normalized)
36
+ return getDirectOpenRouterProvider(openRouterApiKey).embeddingModel(normalizeDirectOpenRouterModelId(normalized))
34
37
  }
35
38
 
36
39
  function normalizeEmbedding(embedding: readonly number[]): number[] {
37
40
  return embedding.map((value) => Number(value))
38
41
  }
39
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
+
40
58
  export class ProviderEmbeddings {
41
59
  private readonly embedFn: typeof embed
42
60
  private readonly embedManyFn: typeof embedMany
43
61
  private readonly getCache: () => SharedEmbeddingCache | null
44
- private readonly configuredModelId?: string
45
- private resolvedModelId: string | null = null
62
+ private readonly resolvedModelId: string
46
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[]>>()
66
+
67
+ private readonly openRouterApiKey: string | undefined
47
68
 
48
- constructor(options: ProviderEmbeddingsOptions = {}) {
69
+ constructor(options: ProviderEmbeddingsOptions) {
49
70
  this.embedFn = options.embedFn ?? embed
50
71
  this.embedManyFn = options.embedManyFn ?? embedMany
51
- this.getCache = options.getCache ?? getEmbeddingCache
52
- this.configuredModelId = options.modelId
72
+ this.getCache = options.getCache ?? (() => null)
73
+ this.resolvedModelId = options.modelId
74
+ this.openRouterApiKey = options.openRouterApiKey
53
75
  }
54
76
 
55
77
  private getModelId(): string {
56
- if (!this.resolvedModelId) {
57
- this.resolvedModelId = this.configuredModelId ?? getRuntimeConfig().aiGateway.embeddingModel
58
- }
59
-
60
78
  return this.resolvedModelId
61
79
  }
62
80
 
63
81
  private getModel() {
64
82
  if (!this._model) {
65
- this._model = resolveEmbeddingModel(this.getModelId())
83
+ this._model = resolveEmbeddingModel(this.getModelId(), this.openRouterApiKey)
66
84
  }
67
85
  return this._model
68
86
  }
69
87
 
70
- private async loadCachedEmbedding(text: string): Promise<number[] | null> {
88
+ private loadCachedEmbedding(text: string): Promise<number[] | null> {
71
89
  const redisCache = this.getCache()
72
- if (!redisCache) return null
90
+ if (!redisCache) return Promise.resolve(null)
73
91
 
74
92
  return redisCache.get(this.getModelId(), text)
75
93
  }
76
94
 
77
- async embedQuery(text: string): Promise<number[]> {
95
+ private runEffect<A>(effect: Effect.Effect<A, EmbeddingProviderError>): Promise<A> {
96
+ const runtime = getOptionalCurrentRuntime()
97
+ return runtime ? runtime.runPromise(effect) : Effect.runPromise(effect)
98
+ }
99
+
100
+ embedQuery(text: string): Promise<number[]> {
78
101
  const input = text.trim()
79
- if (!input) return []
102
+ if (!input) return Promise.resolve([])
80
103
 
81
- const cached = await this.loadCachedEmbedding(input)
82
- if (cached) return cached
104
+ const dedupKey = `${this.getModelId()}:${input}`
105
+ const pending = this.inflightEmbeddings.get(dedupKey)
106
+ if (pending) return pending
83
107
 
84
- const result = await this.embedFn({ model: this.getModel(), value: input, maxRetries: 2 })
85
- const embedding = normalizeEmbedding(result.embedding)
108
+ const promise = this.runEffect(this.executeEmbedQueryEffect(input))
109
+ this.inflightEmbeddings.set(dedupKey, promise)
110
+ void promise.finally(() => this.inflightEmbeddings.delete(dedupKey))
86
111
 
87
- const redisCache = this.getCache()
88
- if (redisCache) {
89
- void redisCache.set(this.getModelId(), input, embedding)
90
- }
112
+ return promise
113
+ }
114
+
115
+ private executeEmbedQueryEffect(input: string): Effect.Effect<number[], EmbeddingProviderError> {
116
+ const self = this
117
+
118
+ return Effect.gen(function* () {
119
+ const cached = yield* tryEmbeddingPromise('Failed to load cached query embedding.', () =>
120
+ self.loadCachedEmbedding(input),
121
+ )
122
+ if (cached) {
123
+ return cached
124
+ }
125
+
126
+ const result = yield* tryEmbeddingPromise('Failed to generate query embedding.', () =>
127
+ self.embedFn({ model: self.getModel(), value: input, maxRetries: 2 }),
128
+ )
129
+ const embedding = normalizeEmbedding(result.embedding)
130
+
131
+ const redisCache = self.getCache()
132
+ if (redisCache) {
133
+ void redisCache.set(self.getModelId(), input, embedding)
134
+ }
91
135
 
92
- return embedding
136
+ return embedding
137
+ }).pipe(Effect.withSpan('ProviderEmbeddings.executeEmbedQuery'))
93
138
  }
94
139
 
95
- async embedDocuments(values: string[]): Promise<number[][]> {
96
- if (values.length === 0) return []
140
+ embedDocuments(values: string[]): Promise<number[][]> {
141
+ if (values.length === 0) return Promise.resolve([])
97
142
 
98
143
  const normalized = values.map((value) => value.trim())
99
144
  const nonEmptyEntries = normalized
@@ -101,56 +146,62 @@ export class ProviderEmbeddings {
101
146
  .filter((entry) => entry.value.length > 0)
102
147
 
103
148
  if (nonEmptyEntries.length === 0) {
104
- return normalized.map(() => [])
149
+ return Promise.resolve(normalized.map(() => []))
105
150
  }
106
151
 
107
152
  const uniqueTexts = [...new Set(nonEmptyEntries.map((entry) => entry.value))]
108
- const embeddingsByText = new Map<string, number[]>()
109
- let missingTexts = [...uniqueTexts]
110
-
111
- const redisCache = this.getCache()
112
- if (redisCache && missingTexts.length > 0) {
113
- const redisResults = await Promise.all(
114
- missingTexts.map(async (text) => ({ text, embedding: await redisCache.get(this.getModelId(), text) })),
115
- )
153
+ return this.runEffect(this.embedDocumentsEffect(normalized, uniqueTexts))
154
+ }
116
155
 
117
- missingTexts = []
118
- for (const result of redisResults) {
119
- if (!result.embedding) {
120
- missingTexts.push(result.text)
121
- continue
156
+ private embedDocumentsEffect(
157
+ normalized: string[],
158
+ uniqueTexts: string[],
159
+ ): Effect.Effect<number[][], EmbeddingProviderError> {
160
+ const self = this
161
+
162
+ return Effect.gen(function* () {
163
+ const embeddingsByText = new Map<string, number[]>()
164
+ let missingTexts = [...uniqueTexts]
165
+ const redisCache = self.getCache()
166
+ const redisResults =
167
+ redisCache && missingTexts.length > 0
168
+ ? yield* Effect.all(
169
+ missingTexts.map((text) =>
170
+ tryEmbeddingPromise('Failed to load cached document embedding.', () =>
171
+ redisCache.get(self.getModelId(), text),
172
+ ).pipe(Effect.map((embedding) => ({ text, embedding }))),
173
+ ),
174
+ )
175
+ : ([] as Array<{ text: string; embedding: number[] | null }>)
176
+
177
+ if (redisCache && missingTexts.length > 0) {
178
+ missingTexts = []
179
+ for (const result of redisResults) {
180
+ if (!result.embedding) {
181
+ missingTexts.push(result.text)
182
+ continue
183
+ }
184
+
185
+ embeddingsByText.set(result.text, result.embedding)
122
186
  }
123
-
124
- embeddingsByText.set(result.text, result.embedding)
125
187
  }
126
- }
127
188
 
128
- if (missingTexts.length > 0) {
129
- const result = await this.embedManyFn({ model: this.getModel(), values: missingTexts, maxRetries: 2 })
189
+ if (missingTexts.length === 0) {
190
+ return normalized.map((text) => (text ? (embeddingsByText.get(text) ?? []) : []))
191
+ }
130
192
 
193
+ const result = yield* tryEmbeddingPromise('Failed to generate document embeddings.', () =>
194
+ self.embedManyFn({ model: self.getModel(), values: missingTexts, maxRetries: 2 }),
195
+ )
131
196
  missingTexts.forEach((text, index) => {
132
197
  const embedding = normalizeEmbedding(result.embeddings[index] ?? [])
133
198
  embeddingsByText.set(text, embedding)
134
199
  if (redisCache) {
135
- void redisCache.set(this.getModelId(), text, embedding)
200
+ void redisCache.set(self.getModelId(), text, embedding)
136
201
  }
137
202
  })
138
- }
139
-
140
- return normalized.map((text) => (text ? (embeddingsByText.get(text) ?? []) : []))
141
- }
142
- }
143
203
 
144
- let defaultEmbeddings: ProviderEmbeddings | null = null
145
-
146
- export function getDefaultEmbeddings(): ProviderEmbeddings {
147
- if (!defaultEmbeddings) {
148
- defaultEmbeddings = new ProviderEmbeddings()
204
+ return normalized.map((text) => (text ? (embeddingsByText.get(text) ?? []) : []))
205
+ }).pipe(Effect.withSpan('ProviderEmbeddings.embedDocuments'))
149
206
  }
150
-
151
- return defaultEmbeddings
152
- }
153
-
154
- export function resetDefaultEmbeddingsForTests(): void {
155
- defaultEmbeddings = null
156
207
  }