@lota-sdk/core 0.4.12 → 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.
- package/package.json +4 -4
- package/src/ai/embedding-cache.ts +17 -11
- package/src/ai-gateway/ai-gateway.ts +164 -94
- package/src/ai-gateway/index.ts +4 -1
- package/src/config/agent-defaults.ts +2 -2
- package/src/config/agent-types.ts +1 -1
- package/src/create-runtime.ts +259 -200
- package/src/db/cursor-pagination.ts +2 -9
- package/src/db/memory-store.ts +194 -175
- package/src/db/memory.ts +125 -71
- package/src/db/schema-fingerprint.ts +5 -4
- package/src/db/service-normalization.ts +4 -3
- package/src/db/service.ts +3 -2
- package/src/db/startup.ts +15 -16
- package/src/effect/errors.ts +161 -21
- package/src/effect/index.ts +0 -1
- package/src/embeddings/provider.ts +15 -7
- package/src/queues/autonomous-job.queue.ts +10 -22
- package/src/queues/delayed-node-promotion.queue.ts +8 -14
- package/src/queues/document-processor.queue.ts +13 -4
- package/src/queues/memory-consolidation.queue.ts +26 -14
- package/src/queues/plan-agent-heartbeat.queue.ts +10 -9
- package/src/queues/plan-scheduler.queue.ts +37 -15
- package/src/queues/queue-factory.ts +59 -35
- package/src/queues/standalone-worker.ts +3 -2
- package/src/redis/connection.ts +10 -3
- package/src/redis/org-memory-lock.ts +1 -1
- package/src/redis/redis-lease-lock.ts +5 -5
- package/src/redis/stream-context.ts +1 -1
- package/src/runtime/chat-message.ts +64 -1
- package/src/runtime/chat-run-orchestration.ts +33 -20
- package/src/runtime/context-compaction/context-compaction-runtime.ts +14 -7
- package/src/runtime/context-compaction/context-compaction.ts +78 -66
- package/src/runtime/domain-layer.ts +13 -7
- package/src/runtime/execution-plan.ts +7 -3
- package/src/runtime/live-turn-trace.ts +6 -49
- package/src/runtime/memory/memory-block.ts +3 -9
- package/src/runtime/memory/memory-scope.ts +3 -1
- package/src/runtime/plugin-resolution.ts +2 -1
- package/src/runtime/post-turn-side-effects.ts +6 -5
- package/src/runtime/retrieval-adapters.ts +8 -20
- package/src/runtime/runtime-config.ts +3 -9
- package/src/runtime/runtime-extensions.ts +2 -4
- package/src/runtime/runtime-lifecycle.ts +56 -16
- package/src/runtime/runtime-services.ts +180 -102
- package/src/runtime/runtime-worker-registry.ts +3 -1
- package/src/runtime/social-chat/social-chat-agent-runner.ts +1 -1
- package/src/runtime/social-chat/social-chat-history.ts +21 -18
- package/src/runtime/social-chat/social-chat.ts +356 -223
- package/src/runtime/specialist-runner.ts +3 -1
- package/src/runtime/team-consultation/team-consultation-orchestrator.ts +3 -2
- package/src/runtime/thread-turn-context.ts +142 -102
- package/src/runtime/turn-lifecycle.ts +15 -46
- package/src/services/agent-activity.service.ts +1 -1
- package/src/services/agent-executor.service.ts +107 -77
- package/src/services/autonomous-job.service.ts +354 -293
- package/src/services/background-work.service.ts +3 -3
- package/src/services/context-compaction.service.ts +7 -2
- package/src/services/document-chunk.service.ts +50 -32
- package/src/services/execution-plan/execution-plan-schedule.ts +5 -3
- package/src/services/execution-plan/execution-plan.service.ts +162 -179
- package/src/services/feedback-loop.service.ts +5 -4
- package/src/services/graph-full-routing.ts +37 -36
- package/src/services/institutional-memory.service.ts +28 -30
- package/src/services/learned-skill.service.ts +107 -72
- package/src/services/memory/memory-errors.ts +4 -23
- package/src/services/memory/memory-org-memory.ts +10 -5
- package/src/services/memory/memory-rerank.ts +18 -6
- package/src/services/memory/memory.service.ts +170 -111
- package/src/services/memory/rerank.service.ts +29 -20
- package/src/services/organization-member.service.ts +1 -1
- package/src/services/organization.service.ts +69 -75
- package/src/services/ownership-dispatcher.service.ts +40 -39
- package/src/services/plan/plan-agent-heartbeat.service.ts +26 -23
- package/src/services/plan/plan-agent-query.service.ts +39 -31
- package/src/services/plan/plan-completion-side-effects.ts +13 -17
- package/src/services/plan/plan-coordination.service.ts +2 -1
- package/src/services/plan/plan-cycle.service.ts +6 -5
- package/src/services/plan/plan-deadline.service.ts +57 -54
- package/src/services/plan/plan-event-delivery.service.ts +5 -4
- package/src/services/plan/plan-executor-graph.ts +18 -15
- package/src/services/plan/plan-executor.service.ts +235 -262
- package/src/services/plan/plan-run.service.ts +169 -93
- package/src/services/plan/plan-scheduler.service.ts +192 -202
- package/src/services/plan/plan-template.service.ts +1 -1
- package/src/services/plan/plan-transaction-events.ts +1 -1
- package/src/services/plan/plan-workspace.service.ts +23 -14
- package/src/services/plugin-executor.service.ts +5 -9
- package/src/services/queue-job.service.ts +117 -59
- package/src/services/recent-activity-title.service.ts +13 -12
- package/src/services/recent-activity.service.ts +6 -1
- package/src/services/social-chat-history.service.ts +29 -25
- package/src/services/system-executor.service.ts +5 -9
- package/src/services/thread/thread-active-run.ts +2 -2
- package/src/services/thread/thread-listing.ts +61 -57
- package/src/services/thread/thread-memory-block.ts +73 -48
- package/src/services/thread/thread-message.service.ts +76 -65
- package/src/services/thread/thread-record-store.ts +8 -8
- package/src/services/thread/thread-title.service.ts +10 -4
- package/src/services/thread/thread-turn-execution.ts +43 -45
- package/src/services/thread/thread-turn-preparation.service.ts +257 -135
- package/src/services/thread/thread-turn-streaming.ts +82 -85
- package/src/services/thread/thread-turn.ts +8 -8
- package/src/services/thread/thread.service.ts +135 -100
- package/src/services/user.service.ts +45 -48
- package/src/storage/attachment-parser.ts +6 -2
- package/src/storage/attachment-storage.service.ts +5 -6
- package/src/storage/generated-document-storage.service.ts +1 -1
- package/src/system-agents/context-compaction.agent.ts +10 -9
- package/src/system-agents/delegated-agent-factory.ts +30 -6
- package/src/system-agents/memory-reranker.agent.ts +10 -9
- package/src/system-agents/memory.agent.ts +10 -9
- package/src/system-agents/recent-activity-title-refiner.agent.ts +13 -15
- package/src/system-agents/regular-chat-memory-digest.agent.ts +13 -12
- package/src/system-agents/skill-extractor.agent.ts +13 -12
- package/src/system-agents/skill-manager.agent.ts +13 -12
- package/src/system-agents/thread-router.agent.ts +10 -5
- package/src/system-agents/title-generator.agent.ts +13 -12
- package/src/tools/fetch-webpage.tool.ts +13 -13
- package/src/tools/memory-block.tool.ts +3 -1
- package/src/tools/plan-approval.tool.ts +4 -2
- package/src/tools/read-file-parts.tool.ts +10 -4
- package/src/tools/remember-memory.tool.ts +3 -1
- package/src/tools/research-topic.tool.ts +9 -5
- package/src/tools/search-web.tool.ts +16 -16
- package/src/tools/search.tool.ts +20 -5
- package/src/tools/team-think.tool.ts +61 -38
- package/src/utils/async.ts +5 -5
- package/src/utils/errors.ts +19 -18
- package/src/utils/sse-keepalive.ts +28 -25
- package/src/workers/bootstrap.ts +75 -11
- package/src/workers/memory-consolidation.worker.ts +82 -91
- package/src/workers/organization-learning.worker.ts +14 -4
- package/src/workers/regular-chat-memory-digest.runner.ts +105 -67
- package/src/workers/skill-extraction.runner.ts +97 -61
- package/src/workers/utils/repo-structure-extractor.ts +13 -8
- package/src/workers/utils/thread-message-query.ts +24 -24
- package/src/workers/worker-utils.ts +23 -4
- 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 {
|
|
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>()(
|
|
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*
|
|
66
|
-
(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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*
|
|
93
|
-
(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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*
|
|
103
|
-
|
|
104
|
-
|
|
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*
|
|
135
|
-
(
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
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*
|
|
175
|
-
(
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
186
|
-
),
|
|
187
|
-
ThreadSchema,
|
|
189
|
+
vars,
|
|
188
190
|
),
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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 {
|
|
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 {
|
|
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
|
|
25
|
+
type BackgroundWorker = Context.Service.Shape<typeof BackgroundWorkServiceTag>
|
|
26
26
|
|
|
27
|
-
class ThreadMemoryBlockError extends Schema.TaggedErrorClass<ThreadMemoryBlockError>()(
|
|
28
|
-
|
|
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*
|
|
53
|
-
|
|
54
|
-
|
|
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*
|
|
66
|
-
|
|
67
|
-
|
|
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*
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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*
|
|
117
|
-
(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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*
|
|
132
|
-
|
|
133
|
-
|
|
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*
|
|
140
|
-
(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
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
|
|
167
|
-
(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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*
|
|
206
|
-
|
|
207
|
-
|
|
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*
|
|
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
|
-
|
|
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
|
|
241
|
-
(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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*
|
|
335
|
-
|
|
336
|
-
|
|
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) =>
|
|
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
|
-
})
|
|
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
|
-
|
|
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>()(
|
|
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
|
|
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
|
-
|
|
77
|
+
const aiGatewayModels = yield* AiGatewayModelsTag
|
|
78
|
+
return makeThreadTitleService(threadService, helperModelRuntime, aiGatewayModels)
|
|
73
79
|
}),
|
|
74
80
|
)
|