@lota-sdk/core 0.4.13 → 0.4.14

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 (138) hide show
  1. package/package.json +4 -4
  2. package/src/ai/embedding-cache.ts +17 -11
  3. package/src/ai-gateway/ai-gateway.ts +164 -94
  4. package/src/ai-gateway/index.ts +4 -1
  5. package/src/config/agent-defaults.ts +2 -2
  6. package/src/config/agent-types.ts +1 -1
  7. package/src/create-runtime.ts +259 -200
  8. package/src/db/cursor-pagination.ts +2 -9
  9. package/src/db/memory-store.ts +194 -175
  10. package/src/db/memory.ts +125 -71
  11. package/src/db/schema-fingerprint.ts +5 -4
  12. package/src/db/service-normalization.ts +4 -3
  13. package/src/db/service.ts +3 -2
  14. package/src/db/startup.ts +15 -16
  15. package/src/effect/errors.ts +161 -21
  16. package/src/effect/index.ts +0 -1
  17. package/src/embeddings/provider.ts +15 -7
  18. package/src/queues/autonomous-job.queue.ts +10 -22
  19. package/src/queues/delayed-node-promotion.queue.ts +8 -14
  20. package/src/queues/document-processor.queue.ts +13 -4
  21. package/src/queues/memory-consolidation.queue.ts +26 -14
  22. package/src/queues/plan-agent-heartbeat.queue.ts +10 -9
  23. package/src/queues/plan-scheduler.queue.ts +37 -15
  24. package/src/queues/queue-factory.ts +59 -35
  25. package/src/queues/standalone-worker.ts +3 -2
  26. package/src/redis/connection.ts +10 -3
  27. package/src/redis/org-memory-lock.ts +1 -1
  28. package/src/redis/redis-lease-lock.ts +5 -5
  29. package/src/redis/stream-context.ts +1 -1
  30. package/src/runtime/chat-message.ts +64 -1
  31. package/src/runtime/chat-run-orchestration.ts +33 -20
  32. package/src/runtime/context-compaction/context-compaction-runtime.ts +14 -7
  33. package/src/runtime/context-compaction/context-compaction.ts +78 -66
  34. package/src/runtime/domain-layer.ts +13 -7
  35. package/src/runtime/execution-plan.ts +7 -3
  36. package/src/runtime/memory/memory-block.ts +3 -9
  37. package/src/runtime/memory/memory-scope.ts +3 -1
  38. package/src/runtime/plugin-resolution.ts +2 -1
  39. package/src/runtime/post-turn-side-effects.ts +6 -5
  40. package/src/runtime/retrieval-adapters.ts +8 -20
  41. package/src/runtime/runtime-config.ts +3 -9
  42. package/src/runtime/runtime-extensions.ts +2 -4
  43. package/src/runtime/runtime-lifecycle.ts +56 -16
  44. package/src/runtime/runtime-services.ts +180 -102
  45. package/src/runtime/runtime-worker-registry.ts +3 -1
  46. package/src/runtime/social-chat/social-chat-agent-runner.ts +1 -1
  47. package/src/runtime/social-chat/social-chat-history.ts +21 -18
  48. package/src/runtime/social-chat/social-chat.ts +356 -223
  49. package/src/runtime/specialist-runner.ts +3 -1
  50. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +3 -2
  51. package/src/runtime/thread-turn-context.ts +142 -102
  52. package/src/runtime/turn-lifecycle.ts +15 -46
  53. package/src/services/agent-activity.service.ts +1 -1
  54. package/src/services/agent-executor.service.ts +107 -77
  55. package/src/services/autonomous-job.service.ts +354 -293
  56. package/src/services/background-work.service.ts +3 -3
  57. package/src/services/context-compaction.service.ts +7 -2
  58. package/src/services/document-chunk.service.ts +50 -32
  59. package/src/services/execution-plan/execution-plan-schedule.ts +5 -3
  60. package/src/services/execution-plan/execution-plan.service.ts +162 -179
  61. package/src/services/feedback-loop.service.ts +5 -4
  62. package/src/services/graph-full-routing.ts +37 -36
  63. package/src/services/institutional-memory.service.ts +28 -30
  64. package/src/services/learned-skill.service.ts +107 -72
  65. package/src/services/memory/memory-errors.ts +4 -23
  66. package/src/services/memory/memory-org-memory.ts +10 -5
  67. package/src/services/memory/memory-rerank.ts +18 -6
  68. package/src/services/memory/memory.service.ts +170 -111
  69. package/src/services/memory/rerank.service.ts +29 -20
  70. package/src/services/organization-member.service.ts +1 -1
  71. package/src/services/organization.service.ts +69 -75
  72. package/src/services/ownership-dispatcher.service.ts +40 -39
  73. package/src/services/plan/plan-agent-heartbeat.service.ts +26 -23
  74. package/src/services/plan/plan-agent-query.service.ts +39 -31
  75. package/src/services/plan/plan-completion-side-effects.ts +13 -17
  76. package/src/services/plan/plan-coordination.service.ts +2 -1
  77. package/src/services/plan/plan-cycle.service.ts +6 -5
  78. package/src/services/plan/plan-deadline.service.ts +57 -54
  79. package/src/services/plan/plan-event-delivery.service.ts +5 -4
  80. package/src/services/plan/plan-executor-graph.ts +18 -15
  81. package/src/services/plan/plan-executor.service.ts +235 -262
  82. package/src/services/plan/plan-run.service.ts +169 -93
  83. package/src/services/plan/plan-scheduler.service.ts +192 -202
  84. package/src/services/plan/plan-template.service.ts +1 -1
  85. package/src/services/plan/plan-transaction-events.ts +1 -1
  86. package/src/services/plan/plan-workspace.service.ts +23 -14
  87. package/src/services/plugin-executor.service.ts +5 -9
  88. package/src/services/queue-job.service.ts +117 -59
  89. package/src/services/recent-activity-title.service.ts +13 -12
  90. package/src/services/recent-activity.service.ts +6 -1
  91. package/src/services/social-chat-history.service.ts +29 -25
  92. package/src/services/system-executor.service.ts +5 -9
  93. package/src/services/thread/thread-active-run.ts +2 -2
  94. package/src/services/thread/thread-listing.ts +61 -57
  95. package/src/services/thread/thread-memory-block.ts +73 -48
  96. package/src/services/thread/thread-message.service.ts +76 -65
  97. package/src/services/thread/thread-record-store.ts +8 -8
  98. package/src/services/thread/thread-title.service.ts +10 -4
  99. package/src/services/thread/thread-turn-execution.ts +43 -45
  100. package/src/services/thread/thread-turn-preparation.service.ts +257 -135
  101. package/src/services/thread/thread-turn-streaming.ts +82 -85
  102. package/src/services/thread/thread-turn.ts +8 -8
  103. package/src/services/thread/thread.service.ts +135 -100
  104. package/src/services/user.service.ts +45 -48
  105. package/src/storage/attachment-parser.ts +6 -2
  106. package/src/storage/attachment-storage.service.ts +5 -6
  107. package/src/storage/generated-document-storage.service.ts +1 -1
  108. package/src/system-agents/context-compaction.agent.ts +10 -9
  109. package/src/system-agents/delegated-agent-factory.ts +30 -6
  110. package/src/system-agents/memory-reranker.agent.ts +10 -9
  111. package/src/system-agents/memory.agent.ts +10 -9
  112. package/src/system-agents/recent-activity-title-refiner.agent.ts +13 -15
  113. package/src/system-agents/regular-chat-memory-digest.agent.ts +13 -12
  114. package/src/system-agents/skill-extractor.agent.ts +13 -12
  115. package/src/system-agents/skill-manager.agent.ts +13 -12
  116. package/src/system-agents/thread-router.agent.ts +10 -5
  117. package/src/system-agents/title-generator.agent.ts +13 -12
  118. package/src/tools/fetch-webpage.tool.ts +13 -13
  119. package/src/tools/memory-block.tool.ts +3 -1
  120. package/src/tools/plan-approval.tool.ts +4 -2
  121. package/src/tools/read-file-parts.tool.ts +10 -4
  122. package/src/tools/remember-memory.tool.ts +3 -1
  123. package/src/tools/research-topic.tool.ts +9 -5
  124. package/src/tools/search-web.tool.ts +16 -16
  125. package/src/tools/search.tool.ts +20 -5
  126. package/src/tools/team-think.tool.ts +61 -38
  127. package/src/utils/async.ts +5 -5
  128. package/src/utils/errors.ts +19 -18
  129. package/src/utils/sse-keepalive.ts +28 -25
  130. package/src/workers/bootstrap.ts +75 -11
  131. package/src/workers/memory-consolidation.worker.ts +82 -91
  132. package/src/workers/organization-learning.worker.ts +14 -4
  133. package/src/workers/regular-chat-memory-digest.runner.ts +105 -67
  134. package/src/workers/skill-extraction.runner.ts +97 -61
  135. package/src/workers/utils/repo-structure-extractor.ts +13 -8
  136. package/src/workers/utils/thread-message-query.ts +24 -24
  137. package/src/workers/worker-utils.ts +23 -4
  138. package/src/effect/helpers.ts +0 -123
@@ -6,7 +6,7 @@ import type { RecordIdRef } from '../../db/record-id'
6
6
  import type { SurrealDBService } from '../../db/service'
7
7
  import { TABLES } from '../../db/tables'
8
8
  import type { BadRequestError, ServiceError } from '../../effect/errors'
9
- import { makeEffectTryPromiseWithMessage } from '../../effect/helpers'
9
+ import { ERROR_TAGS } from '../../effect/errors'
10
10
  import { ThreadSchema } from './thread.types'
11
11
  import type { NormalizedThread, ThreadRecord } from './thread.types'
12
12
 
@@ -41,15 +41,11 @@ export function createThreadListingHelpers(deps: {
41
41
  options?: { checkLease?: boolean },
42
42
  ): Effect.Effect<NormalizedThread[], BadRequestError | ServiceError>
43
43
  }) {
44
- class ThreadListingError extends Schema.TaggedErrorClass<ThreadListingError>()('ThreadListingError', {
44
+ class ThreadListingError extends Schema.TaggedErrorClass<ThreadListingError>()(ERROR_TAGS.ThreadListingError, {
45
45
  message: Schema.String,
46
46
  cause: Schema.optional(Schema.Defect),
47
47
  }) {}
48
48
 
49
- const effectTryPromise = makeEffectTryPromiseWithMessage(
50
- (message, cause) => new ThreadListingError({ message, cause }),
51
- )
52
-
53
49
  function listThreads(
54
50
  userId: RecordIdRef,
55
51
  orgId: RecordIdRef,
@@ -62,18 +58,19 @@ export function createThreadListingHelpers(deps: {
62
58
  if (type === 'default' || type === 'thread') {
63
59
  const vars: Record<string, unknown> = { userId, orgId, type }
64
60
  return Effect.gen(function* () {
65
- const threads = yield* effectTryPromise(
66
- () =>
67
- deps.db.queryMany(
68
- new BoundQuery(buildListThreadsQuery({ includeArchived, paginate: false, type }), vars),
69
- ThreadSchema,
61
+ const threads = yield* deps.db
62
+ .queryMany(
63
+ new BoundQuery(buildListThreadsQuery({ includeArchived, paginate: false, type }), vars),
64
+ ThreadSchema,
65
+ )
66
+ .pipe(Effect.mapError((cause) => new ThreadListingError({ message: 'Failed to list threads.', cause })))
67
+ const normalizedThreads = yield* deps
68
+ .normalizeThreads(threads, { checkLease: false })
69
+ .pipe(
70
+ Effect.mapError(
71
+ (cause) => new ThreadListingError({ message: 'Failed to normalize listed threads.', cause }),
70
72
  ),
71
- 'Failed to list threads.',
72
- )
73
- const normalizedThreads = yield* effectTryPromise(
74
- () => deps.normalizeThreads(threads, { checkLease: false }),
75
- 'Failed to normalize listed threads.',
76
- )
73
+ )
77
74
  return { threads: normalizedThreads, hasMore: false }
78
75
  })
79
76
  }
@@ -89,20 +86,23 @@ export function createThreadListingHelpers(deps: {
89
86
  }
90
87
 
91
88
  return Effect.gen(function* () {
92
- const threads = yield* effectTryPromise(
93
- () =>
94
- deps.db.queryMany(
95
- new BoundQuery(buildListThreadsQuery({ includeArchived, paginate: true, type, types }), vars),
96
- ThreadSchema,
97
- ),
98
- 'Failed to list paginated threads.',
99
- )
89
+ const threads = yield* deps.db
90
+ .queryMany(
91
+ new BoundQuery(buildListThreadsQuery({ includeArchived, paginate: true, type, types }), vars),
92
+ ThreadSchema,
93
+ )
94
+ .pipe(
95
+ Effect.mapError((cause) => new ThreadListingError({ message: 'Failed to list paginated threads.', cause })),
96
+ )
100
97
  const hasMore = threads.length > take
101
98
  const sliced = hasMore ? threads.slice(0, take) : threads
102
- const normalizedThreads = yield* effectTryPromise(
103
- () => deps.normalizeThreads(sliced, { checkLease: false }),
104
- 'Failed to normalize paginated threads.',
105
- )
99
+ const normalizedThreads = yield* deps
100
+ .normalizeThreads(sliced, { checkLease: false })
101
+ .pipe(
102
+ Effect.mapError(
103
+ (cause) => new ThreadListingError({ message: 'Failed to normalize paginated threads.', cause }),
104
+ ),
105
+ )
106
106
  return { threads: normalizedThreads, hasMore }
107
107
  })
108
108
  }
@@ -131,23 +131,28 @@ export function createThreadListingHelpers(deps: {
131
131
  }
132
132
 
133
133
  return Effect.gen(function* () {
134
- const threads = yield* effectTryPromise(
135
- () =>
136
- deps.db.queryMany(
137
- new BoundQuery(
138
- `SELECT * FROM ${TABLES.THREAD}
134
+ const threads = yield* deps.db
135
+ .queryMany(
136
+ new BoundQuery(
137
+ `SELECT * FROM ${TABLES.THREAD}
139
138
  WHERE ${whereClauses.join('\n AND ')}
140
139
  ORDER BY createdAt ASC, id ASC`,
141
- variables,
142
- ),
143
- ThreadSchema,
140
+ variables,
141
+ ),
142
+ ThreadSchema,
143
+ )
144
+ .pipe(
145
+ Effect.mapError(
146
+ (cause) => new ThreadListingError({ message: 'Failed to list organization threads.', cause }),
147
+ ),
148
+ )
149
+ return yield* deps
150
+ .normalizeThreads(threads, { checkLease: false })
151
+ .pipe(
152
+ Effect.mapError(
153
+ (cause) => new ThreadListingError({ message: 'Failed to normalize organization threads.', cause }),
144
154
  ),
145
- 'Failed to list organization threads.',
146
- )
147
- return yield* effectTryPromise(
148
- () => deps.normalizeThreads(threads, { checkLease: false }),
149
- 'Failed to normalize organization threads.',
150
- )
155
+ )
151
156
  })
152
157
  }
153
158
 
@@ -171,27 +176,26 @@ export function createThreadListingHelpers(deps: {
171
176
  }
172
177
 
173
178
  return Effect.gen(function* () {
174
- const threads = yield* effectTryPromise(
175
- () =>
176
- deps.db.queryMany(
177
- new BoundQuery(
178
- `SELECT * FROM ${TABLES.THREAD}
179
+ const threads = yield* deps.db
180
+ .queryMany(
181
+ new BoundQuery(
182
+ `SELECT * FROM ${TABLES.THREAD}
179
183
  WHERE userId = $userId
180
184
  AND organizationId = $orgId
181
185
  ${excludeCondition}
182
186
  AND status != "archived"
183
187
  ORDER BY updatedAt DESC
184
188
  LIMIT $limit`,
185
- vars,
186
- ),
187
- ThreadSchema,
189
+ vars,
188
190
  ),
189
- 'Failed to list recent threads.',
190
- )
191
- return yield* effectTryPromise(
192
- () => deps.normalizeThreads(threads, { checkLease: false }),
193
- 'Failed to normalize recent threads.',
194
- )
191
+ ThreadSchema,
192
+ )
193
+ .pipe(Effect.mapError((cause) => new ThreadListingError({ message: 'Failed to list recent threads.', cause })))
194
+ return yield* deps
195
+ .normalizeThreads(threads, { checkLease: false })
196
+ .pipe(
197
+ Effect.mapError((cause) => new ThreadListingError({ message: 'Failed to normalize recent threads.', cause })),
198
+ )
195
199
  })
196
200
  }
197
201
 
@@ -5,7 +5,7 @@ import { serverLogger } from '../../config/logger'
5
5
  import type { RecordIdRef } from '../../db/record-id'
6
6
  import { ensureRecordId, recordIdToString } from '../../db/record-id'
7
7
  import { TABLES } from '../../db/tables'
8
- import { makeEffectTryPromiseWithMessage } from '../../effect/helpers'
8
+ import { ERROR_TAGS } from '../../effect/errors'
9
9
  import {
10
10
  appendToMemoryBlock,
11
11
  compactMemoryBlockEntries,
@@ -14,7 +14,7 @@ import {
14
14
  parseMemoryBlock,
15
15
  serializeMemoryBlock,
16
16
  } from '../../runtime/memory/memory-block'
17
- import type { BackgroundWorkService } from '../background-work.service'
17
+ import type { BackgroundWorkServiceTag } from '../background-work.service'
18
18
  import type { makeContextCompactionService } from '../context-compaction.service'
19
19
  import { MEMORY_BLOCK_COMPACTION_CHUNK_ENTRIES, MEMORY_BLOCK_COMPACTION_TRIGGER_ENTRIES } from './thread-constants'
20
20
  import type { ThreadRecordStore } from './thread-record-store'
@@ -22,16 +22,12 @@ import type { ThreadRecord } from './thread.types'
22
22
 
23
23
  type ContextCompactionServiceLike = Pick<ReturnType<typeof makeContextCompactionService>, 'compactMemoryBlock'>
24
24
 
25
- type BackgroundWorker = Context.Service.Shape<typeof BackgroundWorkService>
25
+ type BackgroundWorker = Context.Service.Shape<typeof BackgroundWorkServiceTag>
26
26
 
27
- class ThreadMemoryBlockError extends Schema.TaggedErrorClass<ThreadMemoryBlockError>()('ThreadMemoryBlockError', {
28
- message: Schema.String,
29
- cause: Schema.optional(Schema.Defect),
30
- }) {}
31
-
32
- const tryThreadMemoryBlockEffect = makeEffectTryPromiseWithMessage(
33
- (message, cause) => new ThreadMemoryBlockError({ message, cause }),
34
- )
27
+ class ThreadMemoryBlockError extends Schema.TaggedErrorClass<ThreadMemoryBlockError>()(
28
+ ERROR_TAGS.ThreadMemoryBlockError,
29
+ { message: Schema.String, cause: Schema.optional(Schema.Defect) },
30
+ ) {}
35
31
 
36
32
  export function formatMemoryBlockForPrompt(thread: Pick<ThreadRecord, 'memoryBlock' | 'memoryBlockSummary'>): string {
37
33
  return formatPersistedMemoryBlockForPrompt({
@@ -49,10 +45,17 @@ export function createThreadMemoryBlockHelpers(deps: {
49
45
  return Effect.gen(function* () {
50
46
  const threadRef = ensureRecordId(threadId, TABLES.THREAD)
51
47
  const threadIdString = recordIdToString(threadRef, TABLES.THREAD)
52
- const thread = yield* tryThreadMemoryBlockEffect(
53
- () => deps.threadStore.getById(threadRef),
54
- `Failed to load thread ${threadIdString} for memory block append`,
55
- )
48
+ const thread = yield* deps.threadStore
49
+ .getById(threadRef)
50
+ .pipe(
51
+ Effect.mapError(
52
+ (cause) =>
53
+ new ThreadMemoryBlockError({
54
+ message: `Failed to load thread ${threadIdString} for memory block append`,
55
+ cause,
56
+ }),
57
+ ),
58
+ )
56
59
  const entries = parseMemoryBlock(thread.memoryBlock)
57
60
 
58
61
  const labelMatch = entry.match(/^(\w+):\s*/i)
@@ -62,10 +65,17 @@ export function createThreadMemoryBlockHelpers(deps: {
62
65
  const updatedEntries = appendToMemoryBlock(entries, role, content)
63
66
  const serialized = serializeMemoryBlock(updatedEntries)
64
67
 
65
- yield* tryThreadMemoryBlockEffect(
66
- () => deps.threadStore.update(threadRef, { memoryBlock: serialized }),
67
- `Failed to persist memory block for thread ${threadIdString}`,
68
- )
68
+ yield* deps.threadStore
69
+ .update(threadRef, { memoryBlock: serialized })
70
+ .pipe(
71
+ Effect.mapError(
72
+ (cause) =>
73
+ new ThreadMemoryBlockError({
74
+ message: `Failed to persist memory block for thread ${threadIdString}`,
75
+ cause,
76
+ }),
77
+ ),
78
+ )
69
79
 
70
80
  if (updatedEntries.length >= MEMORY_BLOCK_COMPACTION_TRIGGER_ENTRIES) {
71
81
  yield* deps.background.run(
@@ -88,39 +98,54 @@ export function createThreadMemoryBlockHelpers(deps: {
88
98
  return Effect.gen(function* () {
89
99
  const threadRef = ensureRecordId(threadId, TABLES.THREAD)
90
100
  const threadIdString = recordIdToString(threadRef, TABLES.THREAD)
91
- const thread = yield* tryThreadMemoryBlockEffect(
92
- () => deps.threadStore.getById(threadRef),
93
- `Failed to load thread ${threadIdString} for memory block compaction`,
94
- )
95
- const result = yield* tryThreadMemoryBlockEffect(
96
- () =>
97
- compactMemoryBlockEntries({
98
- previousSummary: thread.memoryBlockSummary,
99
- entries: parseMemoryBlock(thread.memoryBlock),
100
- triggerEntries: MEMORY_BLOCK_COMPACTION_TRIGGER_ENTRIES,
101
- chunkEntries: MEMORY_BLOCK_COMPACTION_CHUNK_ENTRIES,
102
- compact: (params) =>
103
- deps.contextCompactionService
104
- .compactMemoryBlock(params)
105
- .pipe(
106
- Effect.mapError(
107
- (cause) => new MemoryBlockCompactError({ message: 'compact callback failed', cause }),
108
- ),
109
- ),
110
- }),
111
- `Failed to compact memory block for thread ${threadIdString}`,
101
+ const thread = yield* deps.threadStore
102
+ .getById(threadRef)
103
+ .pipe(
104
+ Effect.mapError(
105
+ (cause) =>
106
+ new ThreadMemoryBlockError({
107
+ message: `Failed to load thread ${threadIdString} for memory block compaction`,
108
+ cause,
109
+ }),
110
+ ),
111
+ )
112
+ const result = yield* compactMemoryBlockEntries({
113
+ previousSummary: thread.memoryBlockSummary,
114
+ entries: parseMemoryBlock(thread.memoryBlock),
115
+ triggerEntries: MEMORY_BLOCK_COMPACTION_TRIGGER_ENTRIES,
116
+ chunkEntries: MEMORY_BLOCK_COMPACTION_CHUNK_ENTRIES,
117
+ compact: (params) =>
118
+ deps.contextCompactionService
119
+ .compactMemoryBlock(params)
120
+ .pipe(
121
+ Effect.mapError((cause) => new MemoryBlockCompactError({ message: 'compact callback failed', cause })),
122
+ ),
123
+ }).pipe(
124
+ Effect.mapError(
125
+ (cause) =>
126
+ new ThreadMemoryBlockError({
127
+ message: `Failed to compact memory block for thread ${threadIdString}`,
128
+ cause,
129
+ }),
130
+ ),
112
131
  )
113
132
 
114
133
  if (!result.compacted) return false
115
134
 
116
- yield* tryThreadMemoryBlockEffect(
117
- () =>
118
- deps.threadStore.update(threadRef, {
119
- memoryBlockSummary: result.summary || '',
120
- memoryBlock: serializeMemoryBlock(result.entries),
121
- }),
122
- `Failed to persist compacted memory block for thread ${threadIdString}`,
123
- )
135
+ yield* deps.threadStore
136
+ .update(threadRef, {
137
+ memoryBlockSummary: result.summary || '',
138
+ memoryBlock: serializeMemoryBlock(result.entries),
139
+ })
140
+ .pipe(
141
+ Effect.mapError(
142
+ (cause) =>
143
+ new ThreadMemoryBlockError({
144
+ message: `Failed to persist compacted memory block for thread ${threadIdString}`,
145
+ cause,
146
+ }),
147
+ ),
148
+ )
124
149
 
125
150
  return true
126
151
  })
@@ -15,7 +15,6 @@ import { TABLES } from '../../db/tables'
15
15
  import { ThreadMessageRowSchema } from '../../db/thread-message-row'
16
16
  import type { ThreadMessageRow } from '../../db/thread-message-row'
17
17
  import { ServiceError } from '../../effect/errors'
18
- import { effectTryServicePromise } from '../../effect/helpers'
19
18
  import { AgentConfigServiceTag, DatabaseServiceTag } from '../../effect/services'
20
19
  import { sha256Hex } from '../../utils/crypto'
21
20
  import { nowEpochMillis, unsafeDateFrom } from '../../utils/date-time'
@@ -128,32 +127,37 @@ export function makeThreadMessageService(db: SurrealDBService, agentConfig: Reso
128
127
  }
129
128
 
130
129
  const rowId = toThreadMessageRowId(threadId, messageId)
131
- const existingRow = yield* effectTryServicePromise(
132
- () => db.findOne(TABLES.THREAD_MESSAGE, { threadId, messageId }, ThreadMessageExistingRowSchema),
133
- `Failed to load existing thread message "${messageId}".`,
134
- )
130
+ const existingRow = yield* db
131
+ .findOne(TABLES.THREAD_MESSAGE, { threadId, messageId }, ThreadMessageExistingRowSchema)
132
+ .pipe(
133
+ Effect.mapError(
134
+ (cause) => new ServiceError({ message: `Failed to load existing thread message "${messageId}".`, cause }),
135
+ ),
136
+ )
135
137
  const persistedCreatedAt =
136
138
  existingRow === null ? requireTimestamp(message.metadata?.createdAt) : requireTimestamp(existingRow.createdAt)
137
139
  const metadata = withCreatedAtMetadata({ ...message.metadata, createdAt: persistedCreatedAt })
138
140
 
139
- yield* effectTryServicePromise(
140
- () =>
141
- db.upsert(
142
- TABLES.THREAD_MESSAGE,
143
- rowId,
144
- {
145
- threadId,
146
- messageId,
147
- role,
148
- parts,
149
- metadata,
150
- createdAt: existingRow ? existingRow.createdAt : unsafeDateFrom(persistedCreatedAt),
151
- },
152
- ThreadMessageRowSchema,
153
- { mutation: 'content' },
141
+ yield* db
142
+ .upsert(
143
+ TABLES.THREAD_MESSAGE,
144
+ rowId,
145
+ {
146
+ threadId,
147
+ messageId,
148
+ role,
149
+ parts,
150
+ metadata,
151
+ createdAt: existingRow ? existingRow.createdAt : unsafeDateFrom(persistedCreatedAt),
152
+ },
153
+ ThreadMessageRowSchema,
154
+ { mutation: 'content' },
155
+ )
156
+ .pipe(
157
+ Effect.mapError(
158
+ (cause) => new ServiceError({ message: `Failed to upsert thread message "${messageId}".`, cause }),
154
159
  ),
155
- `Failed to upsert thread message "${messageId}".`,
156
- )
160
+ )
157
161
  }),
158
162
  ).pipe(Effect.asVoid)
159
163
  }
@@ -163,20 +167,19 @@ export function makeThreadMessageService(db: SurrealDBService, agentConfig: Reso
163
167
  upsertMessagesEffect: upsertMessages,
164
168
  listMessages(threadId: RecordIdRef) {
165
169
  const threadRef = toThreadRef(threadId)
166
- return effectTryServicePromise(
167
- () =>
168
- db.query<unknown>(surql`
169
- SELECT * FROM threadMessage
170
- WHERE threadId = ${threadRef}
171
- ORDER BY createdAt ASC, id ASC
172
- `),
173
- 'Failed to list thread messages.',
174
- ).pipe(
175
- Effect.flatMap((rows) =>
176
- Effect.forEach(rows, (row) => parseRowOrFail(ThreadMessageRowSchema, row, 'listMessages')),
177
- ),
178
- Effect.map((rows) => rows.map((row) => toChatMessage(row))),
179
- )
170
+ return db
171
+ .query<unknown>(surql`
172
+ SELECT * FROM threadMessage
173
+ WHERE threadId = ${threadRef}
174
+ ORDER BY createdAt ASC, id ASC
175
+ `)
176
+ .pipe(
177
+ Effect.mapError((cause) => new ServiceError({ message: 'Failed to list thread messages.', cause })),
178
+ Effect.flatMap((rows) =>
179
+ Effect.forEach(rows, (row) => parseRowOrFail(ThreadMessageRowSchema, row, 'listMessages')),
180
+ ),
181
+ Effect.map((rows) => rows.map((row) => toChatMessage(row))),
182
+ )
180
183
  },
181
184
  listMessagesEffect(threadId: RecordIdRef) {
182
185
  return service.listMessages(threadId)
@@ -202,19 +205,21 @@ export function makeThreadMessageService(db: SurrealDBService, agentConfig: Reso
202
205
  }
203
206
 
204
207
  return Effect.gen(function* () {
205
- const cursorRow = yield* effectTryServicePromise(
206
- () => db.findOne(TABLES.THREAD_MESSAGE, { threadId: threadRef, messageId: cursorMessageId }, CursorRowSchema),
207
- `Failed to load cursor message "${cursorMessageId}".`,
208
- )
208
+ const cursorRow = yield* db
209
+ .findOne(TABLES.THREAD_MESSAGE, { threadId: threadRef, messageId: cursorMessageId }, CursorRowSchema)
210
+ .pipe(
211
+ Effect.mapError(
212
+ (cause) => new ServiceError({ message: `Failed to load cursor message "${cursorMessageId}".`, cause }),
213
+ ),
214
+ )
209
215
  if (!cursorRow) {
210
216
  return yield* new ServiceError({ message: `Thread cursor message not found: ${cursorMessageId}` })
211
217
  }
212
218
 
213
219
  const cursorCreatedAt = cursorRow.createdAt
214
220
  const cursorId = toThreadMessageRowId(threadRef, cursorMessageId)
215
- const rows = yield* effectTryServicePromise(
216
- () =>
217
- db.query<unknown>(surql`
221
+ const rows = yield* db
222
+ .query<unknown>(surql`
218
223
  SELECT * FROM threadMessage
219
224
  WHERE threadId = ${threadRef}
220
225
  AND (
@@ -222,9 +227,12 @@ export function makeThreadMessageService(db: SurrealDBService, agentConfig: Reso
222
227
  OR (createdAt = ${cursorCreatedAt} AND id > ${cursorId})
223
228
  )
224
229
  ORDER BY createdAt ASC, id ASC
225
- `),
226
- 'Failed to list thread messages after cursor.',
227
- )
230
+ `)
231
+ .pipe(
232
+ Effect.mapError(
233
+ (cause) => new ServiceError({ message: 'Failed to list thread messages after cursor.', cause }),
234
+ ),
235
+ )
228
236
  const parsedRows = yield* Effect.forEach(rows, (row) =>
229
237
  parseRowOrFail(ThreadMessageRowSchema, row, 'listMessagesAfterCursor'),
230
238
  )
@@ -237,21 +245,20 @@ export function makeThreadMessageService(db: SurrealDBService, agentConfig: Reso
237
245
 
238
246
  listRecentMessages(threadId: RecordIdRef, limit: number) {
239
247
  const threadRef = toThreadRef(threadId)
240
- return effectTryServicePromise(
241
- () =>
242
- db.query<unknown>(surql`
243
- SELECT * FROM threadMessage
244
- WHERE threadId = ${threadRef}
245
- ORDER BY createdAt DESC, id DESC
246
- LIMIT ${Math.max(1, limit)}
247
- `),
248
- 'Failed to list recent thread messages.',
249
- ).pipe(
250
- Effect.flatMap((rows) =>
251
- Effect.forEach(rows, (row) => parseRowOrFail(ThreadMessageRowSchema, row, 'listRecentMessages')),
252
- ),
253
- Effect.map((rows) => rows.reverse().map((row) => toChatMessage(row))),
254
- )
248
+ return db
249
+ .query<unknown>(surql`
250
+ SELECT * FROM threadMessage
251
+ WHERE threadId = ${threadRef}
252
+ ORDER BY createdAt DESC, id DESC
253
+ LIMIT ${Math.max(1, limit)}
254
+ `)
255
+ .pipe(
256
+ Effect.mapError((cause) => new ServiceError({ message: 'Failed to list recent thread messages.', cause })),
257
+ Effect.flatMap((rows) =>
258
+ Effect.forEach(rows, (row) => parseRowOrFail(ThreadMessageRowSchema, row, 'listRecentMessages')),
259
+ ),
260
+ Effect.map((rows) => rows.reverse().map((row) => toChatMessage(row))),
261
+ )
255
262
  },
256
263
  listRecentMessagesEffect(threadId: RecordIdRef, limit: number) {
257
264
  return service.listRecentMessages(threadId, limit)
@@ -331,10 +338,14 @@ export function makeThreadMessageService(db: SurrealDBService, agentConfig: Reso
331
338
  ensureBootstrapWelcomeMessage(params: { threadId: RecordIdRef; agentId: string; text: string }) {
332
339
  const threadRef = toThreadRef(params.threadId)
333
340
  return Effect.gen(function* () {
334
- const existingRow = yield* effectTryServicePromise(
335
- () => db.findOne(TABLES.THREAD_MESSAGE, { threadId: threadRef }, ThreadMessageExistingRowSchema),
336
- 'Failed to check for existing bootstrap welcome message.',
337
- )
341
+ const existingRow = yield* db
342
+ .findOne(TABLES.THREAD_MESSAGE, { threadId: threadRef }, ThreadMessageExistingRowSchema)
343
+ .pipe(
344
+ Effect.mapError(
345
+ (cause) =>
346
+ new ServiceError({ message: 'Failed to check for existing bootstrap welcome message.', cause }),
347
+ ),
348
+ )
338
349
  if (existingRow) return
339
350
 
340
351
  const messageText = params.text.trim()
@@ -136,19 +136,19 @@ export function createThreadRecordStore(deps: { db: SurrealDBService }): ThreadR
136
136
  params: UniqueThreadLookupParams,
137
137
  ): Effect.Effect<ThreadRecord, NotFoundError | DatabaseError> {
138
138
  const poll = findThreadByUniqueLookup(params).pipe(
139
- Effect.flatMap((result) => (result ? Effect.succeed(result) : Effect.fail('not_found' as const))),
139
+ Effect.flatMap((result) =>
140
+ result
141
+ ? Effect.succeed(result)
142
+ : Effect.fail(
143
+ new NotFoundError({ resource: TABLES.THREAD, message: buildExistingThreadLookupMessage(params) }),
144
+ ),
145
+ ),
140
146
  )
141
147
 
142
148
  return Effect.retry(poll, {
143
149
  times: THREAD_UNIQUE_LOOKUP_MAX_ATTEMPTS - 1,
144
150
  schedule: Schedule.fixed(Duration.millis(THREAD_UNIQUE_LOOKUP_RETRY_DELAY_MS)),
145
- }).pipe(
146
- Effect.mapError((error) =>
147
- error === 'not_found'
148
- ? new NotFoundError({ resource: TABLES.THREAD, message: buildExistingThreadLookupMessage(params) })
149
- : error,
150
- ),
151
- )
151
+ })
152
152
  }
153
153
 
154
154
  return { findById, getById, findAll, create, update, deleteById, findThreadByUniqueLookup, waitForExistingThread }
@@ -1,13 +1,16 @@
1
1
  import { THREAD } from '@lota-sdk/shared'
2
2
  import { Context, Schema, Effect, Layer } from 'effect'
3
3
 
4
+ import { AiGatewayModelsTag } from '../../ai-gateway/ai-gateway'
5
+ import type { AiGatewayModels } from '../../ai-gateway/ai-gateway'
4
6
  import { chatLogger } from '../../config/logger'
5
7
  import type { RecordIdRef } from '../../db/record-id'
8
+ import { ERROR_TAGS } from '../../effect/errors'
6
9
  import type { HelperModelRuntime } from '../../runtime/helper-model'
7
10
  import { HelperModelTag } from '../../runtime/helper-model'
8
11
  import { deriveTitle, limitTitleWords, normalizeTitle } from '../../runtime/title-helpers'
9
12
  import {
10
- createThreadTitleGeneratorAgent,
13
+ makeThreadTitleGeneratorAgentFactory,
11
14
  THREAD_TITLE_GENERATOR_PROMPT,
12
15
  } from '../../system-agents/title-generator.agent'
13
16
  import type { makeThreadService } from './thread.service'
@@ -15,7 +18,7 @@ import { ThreadServiceTag } from './thread.service'
15
18
 
16
19
  const THREAD_TITLE_TIMEOUT_MS = 30_000
17
20
 
18
- class ThreadTitleError extends Schema.TaggedErrorClass<ThreadTitleError>()('ThreadTitleError', {
21
+ class ThreadTitleError extends Schema.TaggedErrorClass<ThreadTitleError>()(ERROR_TAGS.ThreadTitleError, {
19
22
  message: Schema.String,
20
23
  cause: Schema.optional(Schema.Defect),
21
24
  }) {}
@@ -23,7 +26,9 @@ class ThreadTitleError extends Schema.TaggedErrorClass<ThreadTitleError>()('Thre
23
26
  export function makeThreadTitleService(
24
27
  threadService: ReturnType<typeof makeThreadService>,
25
28
  helperModelRuntime: HelperModelRuntime,
29
+ aiGatewayModels: AiGatewayModels,
26
30
  ) {
31
+ const createAgent = makeThreadTitleGeneratorAgentFactory(aiGatewayModels)
27
32
  return {
28
33
  generateAndPersistTitle(threadId: RecordIdRef, sourceText: string) {
29
34
  return Effect.gen(function* () {
@@ -31,7 +36,7 @@ export function makeThreadTitleService(
31
36
  try: () =>
32
37
  helperModelRuntime.generateHelperText({
33
38
  tag: 'thread-title',
34
- createAgent: createThreadTitleGeneratorAgent,
39
+ createAgent,
35
40
  defaultSystemPrompt: THREAD_TITLE_GENERATOR_PROMPT,
36
41
  timeoutMs: THREAD_TITLE_TIMEOUT_MS,
37
42
  messages: [{ role: 'user', content: sourceText }],
@@ -69,6 +74,7 @@ export const ThreadTitleServiceLive = Layer.effect(
69
74
  Effect.gen(function* () {
70
75
  const threadService = yield* ThreadServiceTag
71
76
  const helperModelRuntime = yield* HelperModelTag
72
- return makeThreadTitleService(threadService, helperModelRuntime)
77
+ const aiGatewayModels = yield* AiGatewayModelsTag
78
+ return makeThreadTitleService(threadService, helperModelRuntime, aiGatewayModels)
73
79
  }),
74
80
  )