@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
|
@@ -4,10 +4,15 @@ import type { Context } from 'effect'
|
|
|
4
4
|
import type IORedis from 'ioredis'
|
|
5
5
|
|
|
6
6
|
import { serverLogger } from '../config/logger'
|
|
7
|
+
import { ERROR_TAGS } from '../effect/errors'
|
|
7
8
|
import { DatabaseServiceTag, RedisServiceTag } from '../effect/services'
|
|
9
|
+
import type { makePlanCycleService } from '../services/plan/plan-cycle.service'
|
|
8
10
|
import { PlanCycleServiceTag } from '../services/plan/plan-cycle.service'
|
|
11
|
+
import type { makePlanDeadlineService } from '../services/plan/plan-deadline.service'
|
|
9
12
|
import { PlanDeadlineServiceTag } from '../services/plan/plan-deadline.service'
|
|
13
|
+
import type { makePlanExecutorService } from '../services/plan/plan-executor.service'
|
|
10
14
|
import { PlanExecutorServiceTag } from '../services/plan/plan-executor.service'
|
|
15
|
+
import type { makePlanSchedulerService } from '../services/plan/plan-scheduler.service'
|
|
11
16
|
import { PlanSchedulerServiceTag } from '../services/plan/plan-scheduler.service'
|
|
12
17
|
import { QueueJobServiceTag } from '../services/queue-job.service'
|
|
13
18
|
import { nowEpochMillis } from '../utils/date-time'
|
|
@@ -15,6 +20,20 @@ import type { QueueJobService, WorkerHandle } from '../workers/worker-utils'
|
|
|
15
20
|
import { createQueueFactoryWithDeps } from './queue-factory'
|
|
16
21
|
import { runStandaloneQueueWorker } from './standalone-worker'
|
|
17
22
|
|
|
23
|
+
type PlanSchedulerWorkerServiceMethods = Pick<
|
|
24
|
+
ReturnType<typeof makePlanSchedulerService>,
|
|
25
|
+
'fireScheduleById' | 'recoverActiveSchedules'
|
|
26
|
+
>
|
|
27
|
+
|
|
28
|
+
type PlanDeadlineWorkerServiceMethods = Pick<
|
|
29
|
+
ReturnType<typeof makePlanDeadlineService>,
|
|
30
|
+
'checkDeadlines' | 'recoverDeadlineChecks'
|
|
31
|
+
>
|
|
32
|
+
|
|
33
|
+
type PlanExecutorWorkerServiceMethods = Pick<ReturnType<typeof makePlanExecutorService>, 'promoteDelayedNode'>
|
|
34
|
+
|
|
35
|
+
type PlanCycleWorkerServiceMethods = Pick<ReturnType<typeof makePlanCycleService>, 'advanceCycle'>
|
|
36
|
+
|
|
18
37
|
export interface PlanSchedulerFireJob {
|
|
19
38
|
type: 'fire-schedule'
|
|
20
39
|
scheduleId: string
|
|
@@ -31,17 +50,21 @@ export const PLAN_SCHEDULER_QUEUE = 'plan-scheduler'
|
|
|
31
50
|
|
|
32
51
|
export interface PlanSchedulerWorkerDeps {
|
|
33
52
|
databaseService: Context.Service.Shape<typeof DatabaseServiceTag>
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
53
|
+
runPromise: <A, E, R>(effect: Effect.Effect<A, E, R>) => Promise<A>
|
|
54
|
+
planSchedulerService: PlanSchedulerWorkerServiceMethods
|
|
55
|
+
planDeadlineService: PlanDeadlineWorkerServiceMethods
|
|
56
|
+
planExecutorService: PlanExecutorWorkerServiceMethods
|
|
57
|
+
planCycleService: PlanCycleWorkerServiceMethods
|
|
38
58
|
}
|
|
39
59
|
|
|
40
|
-
class PlanSchedulerQueueError extends Schema.TaggedErrorClass<PlanSchedulerQueueError>()(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
60
|
+
class PlanSchedulerQueueError extends Schema.TaggedErrorClass<PlanSchedulerQueueError>()(
|
|
61
|
+
ERROR_TAGS.PlanSchedulerQueueError,
|
|
62
|
+
{
|
|
63
|
+
stage: Schema.Literals(['remove-schedule-fire-job', 'recover-active-schedules', 'recover-deadline-checks']),
|
|
64
|
+
message: Schema.String,
|
|
65
|
+
cause: Schema.Defect,
|
|
66
|
+
},
|
|
67
|
+
) {}
|
|
45
68
|
|
|
46
69
|
function toPlanSchedulerQueueError(stage: PlanSchedulerQueueError['stage'], cause: unknown): PlanSchedulerQueueError {
|
|
47
70
|
return new PlanSchedulerQueueError({ stage, message: cause instanceof Error ? cause.message : String(cause), cause })
|
|
@@ -49,10 +72,8 @@ function toPlanSchedulerQueueError(stage: PlanSchedulerQueueError['stage'], caus
|
|
|
49
72
|
|
|
50
73
|
function processPlanSchedulerJob(deps: PlanSchedulerWorkerDeps, job: Job<PlanSchedulerJob>): Promise<void> {
|
|
51
74
|
const { planSchedulerService, planDeadlineService, planExecutorService, planCycleService } = deps
|
|
52
|
-
const runWithResolvedContext = <A, E>(effect: Effect.Effect<A, E,
|
|
53
|
-
|
|
54
|
-
// that resolved these service tags, so residual R collapses at runtime.
|
|
55
|
-
Effect.runPromise(Effect.asVoid(effect as Effect.Effect<A, E, never>))
|
|
75
|
+
const runWithResolvedContext = <A, E, R>(effect: Effect.Effect<A, E, R>): Promise<void> =>
|
|
76
|
+
deps.runPromise(Effect.asVoid(effect))
|
|
56
77
|
|
|
57
78
|
switch (job.data.type) {
|
|
58
79
|
case 'fire-schedule':
|
|
@@ -132,7 +153,7 @@ export function makePlanSchedulerQueueRuntime(params: MakePlanSchedulerQueueRunt
|
|
|
132
153
|
void Effect.runFork(
|
|
133
154
|
deps.planSchedulerService.recoverActiveSchedules().pipe(
|
|
134
155
|
Effect.mapError((cause) => toPlanSchedulerQueueError('recover-active-schedules', cause)),
|
|
135
|
-
Effect.catchTag(
|
|
156
|
+
Effect.catchTag(ERROR_TAGS.PlanSchedulerQueueError, (error) =>
|
|
136
157
|
Effect.sync(() => {
|
|
137
158
|
serverLogger.error`Plan scheduler startup recovery failed: ${error.message}`
|
|
138
159
|
}),
|
|
@@ -144,7 +165,7 @@ export function makePlanSchedulerQueueRuntime(params: MakePlanSchedulerQueueRunt
|
|
|
144
165
|
void Effect.runFork(
|
|
145
166
|
deps.planDeadlineService.recoverDeadlineChecks().pipe(
|
|
146
167
|
Effect.mapError((cause) => toPlanSchedulerQueueError('recover-deadline-checks', cause)),
|
|
147
|
-
Effect.catchTag(
|
|
168
|
+
Effect.catchTag(ERROR_TAGS.PlanSchedulerQueueError, (error) =>
|
|
148
169
|
Effect.sync(() => {
|
|
149
170
|
serverLogger.error`Plan deadline recovery failed: ${error.message}`
|
|
150
171
|
}),
|
|
@@ -168,6 +189,7 @@ runStandaloneQueueWorker((runtime) => {
|
|
|
168
189
|
planSchedulerQueue.startWorker({
|
|
169
190
|
deps: {
|
|
170
191
|
databaseService: resolve(DatabaseServiceTag),
|
|
192
|
+
runPromise: (effect) => runtime.runPromise(effect),
|
|
171
193
|
planSchedulerService: resolve(PlanSchedulerServiceTag),
|
|
172
194
|
planDeadlineService: resolve(PlanDeadlineServiceTag),
|
|
173
195
|
planExecutorService: resolve(PlanExecutorServiceTag),
|
|
@@ -5,6 +5,7 @@ import type IORedis from 'ioredis'
|
|
|
5
5
|
|
|
6
6
|
import type { LotaLogger } from '../config/logger'
|
|
7
7
|
import { serverLogger } from '../config/logger'
|
|
8
|
+
import { ERROR_TAGS } from '../effect/errors'
|
|
8
9
|
import type { TrackedBullJobLike } from '../services/queue-job.service'
|
|
9
10
|
import {
|
|
10
11
|
attachWorkerEvents,
|
|
@@ -15,7 +16,7 @@ import {
|
|
|
15
16
|
} from '../workers/worker-utils'
|
|
16
17
|
import type { QueueJobService, WorkerHandle } from '../workers/worker-utils'
|
|
17
18
|
|
|
18
|
-
class QueueFactoryError extends Schema.TaggedErrorClass<QueueFactoryError>()(
|
|
19
|
+
class QueueFactoryError extends Schema.TaggedErrorClass<QueueFactoryError>()(ERROR_TAGS.QueueFactoryError, {
|
|
19
20
|
message: Schema.String,
|
|
20
21
|
cause: Schema.optional(Schema.Defect),
|
|
21
22
|
}) {}
|
|
@@ -148,7 +149,7 @@ function createQueueFactoryRuntime<TJob>(config: QueueFactoryConfigBase): {
|
|
|
148
149
|
return Reflect.get(target, property, receiver) as unknown
|
|
149
150
|
}
|
|
150
151
|
|
|
151
|
-
const value = (target
|
|
152
|
+
const value = Reflect.get(target, property, receiver) as unknown
|
|
152
153
|
if (typeof value !== 'function' || !queueMethodsThatWaitForClose.has(property as QueueMethod)) {
|
|
153
154
|
return value
|
|
154
155
|
}
|
|
@@ -229,46 +230,69 @@ function createQueueFactoryRuntime<TJob>(config: QueueFactoryConfigBase): {
|
|
|
229
230
|
...(config.maxStalledCount !== undefined ? { maxStalledCount: config.maxStalledCount } : {}),
|
|
230
231
|
}
|
|
231
232
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
233
|
+
let worker: Worker
|
|
234
|
+
try {
|
|
235
|
+
worker = workerConfig.processorPath
|
|
236
|
+
? new Worker(config.name, workerConfig.processorPath, workerOptions)
|
|
237
|
+
: new Worker(
|
|
237
238
|
config.name,
|
|
238
|
-
(
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
239
|
+
createTracedWorkerProcessor(
|
|
240
|
+
config.name,
|
|
241
|
+
(job) =>
|
|
242
|
+
Effect.runPromise(
|
|
243
|
+
Effect.gen(function* () {
|
|
244
|
+
const inlineWorkerConfig = workerConfig as QueueWorkerConfigInline<TJob>
|
|
245
|
+
const typedJob = job as Job<TJob>
|
|
246
|
+
const prepare = inlineWorkerConfig.prepare
|
|
247
|
+
if (prepare) {
|
|
248
|
+
yield* Effect.tryPromise({
|
|
249
|
+
try: () => prepare(typedJob),
|
|
250
|
+
catch: (cause) =>
|
|
251
|
+
new QueueFactoryError({
|
|
252
|
+
message: `Worker prepare failed for queue "${config.name}".`,
|
|
253
|
+
cause,
|
|
254
|
+
}),
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
return yield* Effect.tryPromise({
|
|
258
|
+
try: () => inlineWorkerConfig.processor(typedJob),
|
|
247
259
|
catch: (cause) =>
|
|
248
|
-
new QueueFactoryError({
|
|
260
|
+
new QueueFactoryError({
|
|
261
|
+
message: `Worker processor failed for queue "${config.name}".`,
|
|
262
|
+
cause,
|
|
263
|
+
}),
|
|
249
264
|
})
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
265
|
+
}),
|
|
266
|
+
),
|
|
267
|
+
config.queueJobService,
|
|
268
|
+
),
|
|
269
|
+
workerOptions,
|
|
270
|
+
)
|
|
271
|
+
} catch (error) {
|
|
272
|
+
// Construction threw — nothing to clean up since the Worker is created
|
|
273
|
+
// atomically by BullMQ; just rethrow so the caller can fail loudly.
|
|
274
|
+
logger.error`Failed to construct BullMQ worker "${config.displayName}": ${error}`
|
|
275
|
+
throw error
|
|
276
|
+
}
|
|
262
277
|
|
|
263
|
-
|
|
278
|
+
// Acquire-style setup — if ANY step below throws, best-effort close the
|
|
279
|
+
// already-constructed worker before bubbling the error so no connection
|
|
280
|
+
// leaks.
|
|
281
|
+
try {
|
|
282
|
+
attachWorkerEvents(worker, config.displayName, logger)
|
|
283
|
+
const shutdown = createWorkerShutdown(worker, config.displayName, logger)
|
|
264
284
|
|
|
265
|
-
|
|
285
|
+
if (registerSignals) {
|
|
286
|
+
registerShutdownSignals({ name: config.displayName, shutdown, logger })
|
|
287
|
+
}
|
|
266
288
|
|
|
267
|
-
|
|
268
|
-
|
|
289
|
+
return { worker, shutdown }
|
|
290
|
+
} catch (error) {
|
|
291
|
+
void worker.close().catch((closeError: unknown) => {
|
|
292
|
+
logger.warn`Failed to close BullMQ worker "${config.displayName}" after setup error: ${closeError}`
|
|
293
|
+
})
|
|
294
|
+
throw error
|
|
269
295
|
}
|
|
270
|
-
|
|
271
|
-
return { worker, shutdown }
|
|
272
296
|
}
|
|
273
297
|
|
|
274
298
|
return { getQueue, enqueue, startWorker }
|
|
@@ -2,10 +2,11 @@ import type { ManagedRuntime } from 'effect'
|
|
|
2
2
|
import { Schema, Effect } from 'effect'
|
|
3
3
|
|
|
4
4
|
import { serverLogger } from '../config/logger'
|
|
5
|
+
import { ERROR_TAGS } from '../effect/errors'
|
|
5
6
|
import { initializeSandboxedWorkerRuntime } from '../workers/bootstrap'
|
|
6
7
|
|
|
7
8
|
class StandaloneQueueWorkerError extends Schema.TaggedErrorClass<StandaloneQueueWorkerError>()(
|
|
8
|
-
|
|
9
|
+
ERROR_TAGS.StandaloneQueueWorkerError,
|
|
9
10
|
{ message: Schema.String, cause: Schema.Defect },
|
|
10
11
|
) {}
|
|
11
12
|
|
|
@@ -28,7 +29,7 @@ export function runStandaloneQueueWorker(start: (runtime: ManagedRuntime.Managed
|
|
|
28
29
|
|
|
29
30
|
yield* Effect.sync(() => start(runtime))
|
|
30
31
|
}).pipe(
|
|
31
|
-
Effect.catchTag(
|
|
32
|
+
Effect.catchTag(ERROR_TAGS.StandaloneQueueWorkerError, (error) =>
|
|
32
33
|
Effect.sync(() => {
|
|
33
34
|
serverLogger.error`Standalone queue worker failed: ${error.message}`
|
|
34
35
|
process.exit(1)
|
package/src/redis/connection.ts
CHANGED
|
@@ -3,7 +3,6 @@ import IORedis from 'ioredis'
|
|
|
3
3
|
import type { RedisOptions } from 'ioredis'
|
|
4
4
|
|
|
5
5
|
import { RedisError } from '../effect/errors'
|
|
6
|
-
import { effectTryServicePromise } from '../effect/helpers'
|
|
7
6
|
import { getErrorMessage } from '../utils/errors'
|
|
8
7
|
|
|
9
8
|
export interface RedisConnectionLogger {
|
|
@@ -147,7 +146,10 @@ function acquireRedisClient(
|
|
|
147
146
|
|
|
148
147
|
if (client.status !== 'end') {
|
|
149
148
|
const quitExit = yield* Effect.exit(
|
|
150
|
-
|
|
149
|
+
Effect.tryPromise({
|
|
150
|
+
try: () => client.quit(),
|
|
151
|
+
catch: (cause) => new RedisError({ message: 'Failed to close Redis connection manager', cause }),
|
|
152
|
+
}),
|
|
151
153
|
)
|
|
152
154
|
if (Exit.isFailure(quitExit)) {
|
|
153
155
|
log(options.logger, 'warn', `Redis quit failed, forcing disconnect: ${getErrorMessage(quitExit.cause)}`)
|
|
@@ -190,7 +192,12 @@ function startHealthCheckFiber(
|
|
|
190
192
|
isHealthCheckRunning = true
|
|
191
193
|
try {
|
|
192
194
|
if (client.status === 'ready') {
|
|
193
|
-
const pingExit = yield* Effect.exit(
|
|
195
|
+
const pingExit = yield* Effect.exit(
|
|
196
|
+
Effect.tryPromise({
|
|
197
|
+
try: () => client.ping(),
|
|
198
|
+
catch: (cause) => new RedisError({ message: 'Redis health check failed', cause }),
|
|
199
|
+
}),
|
|
200
|
+
)
|
|
194
201
|
if (Exit.isFailure(pingExit)) {
|
|
195
202
|
log(logger, 'warn', `Redis health check failed: ${getErrorMessage(pingExit.cause)}`)
|
|
196
203
|
state.isHealthy = false
|
|
@@ -16,7 +16,7 @@ const ORG_MEMORY_LOCK_WAIT_LOG_INTERVAL_MS = 30_000
|
|
|
16
16
|
const ORG_MEMORY_LOCK_MAX_WAIT_MS = 15 * 60 * 1000
|
|
17
17
|
|
|
18
18
|
class OrgMemoryLockCallbackError extends Schema.TaggedErrorClass<OrgMemoryLockCallbackError>()(
|
|
19
|
-
'OrgMemoryLockCallbackError',
|
|
19
|
+
'@lota-sdk/core/OrgMemoryLockCallbackError',
|
|
20
20
|
{ message: Schema.String, cause: Schema.Defect },
|
|
21
21
|
) {}
|
|
22
22
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Clock, Deferred, Duration, Effect, Random, Schedule } from 'effect'
|
|
2
2
|
import type IORedis from 'ioredis'
|
|
3
3
|
|
|
4
|
-
import { LockAcquisitionError, LockLostError, RedisError } from '../effect/errors'
|
|
4
|
+
import { ERROR_TAGS, LockAcquisitionError, LockLostError, RedisError } from '../effect/errors'
|
|
5
5
|
import { RedisServiceTag } from '../effect/services'
|
|
6
6
|
import { getErrorMessage } from '../utils/errors'
|
|
7
7
|
|
|
@@ -119,7 +119,7 @@ function acquireLock(
|
|
|
119
119
|
schedule: Schedule.fixed(Duration.millis(options.retryDelayMs)),
|
|
120
120
|
}).pipe(
|
|
121
121
|
Effect.asVoid,
|
|
122
|
-
Effect.catchTag(
|
|
122
|
+
Effect.catchTag(ERROR_TAGS.LockAcquisitionError, () =>
|
|
123
123
|
Effect.fail(new LockAcquisitionError({ lockKey: options.lockKey, maxWaitMs: options.maxWaitMs })),
|
|
124
124
|
),
|
|
125
125
|
)
|
|
@@ -170,7 +170,7 @@ function startRefreshFiber(
|
|
|
170
170
|
Effect.andThen(
|
|
171
171
|
Effect.sync(() => {
|
|
172
172
|
const message =
|
|
173
|
-
error._tag ===
|
|
173
|
+
error._tag === ERROR_TAGS.LockLostError
|
|
174
174
|
? `${options.label} refresh was rejected for key ${options.lockKey}`
|
|
175
175
|
: error.message
|
|
176
176
|
|
|
@@ -188,8 +188,8 @@ function startRefreshFiber(
|
|
|
188
188
|
yield* Effect.sleep(Duration.millis(options.refreshIntervalMs))
|
|
189
189
|
|
|
190
190
|
yield* refreshLock(redis, options, lockValue).pipe(
|
|
191
|
-
Effect.catchTag(
|
|
192
|
-
Effect.catchTag(
|
|
191
|
+
Effect.catchTag(ERROR_TAGS.LockLostError, handleLockLoss),
|
|
192
|
+
Effect.catchTag(ERROR_TAGS.RedisError, handleLockLoss),
|
|
193
193
|
)
|
|
194
194
|
}
|
|
195
195
|
})
|
|
@@ -22,7 +22,7 @@ function toPublisher(client: Redis): Publisher {
|
|
|
22
22
|
type SharedSubscriberEvent = { readonly channel: string; readonly message: string }
|
|
23
23
|
|
|
24
24
|
class SharedSubscriberCloseError extends Schema.TaggedErrorClass<SharedSubscriberCloseError>()(
|
|
25
|
-
'SharedSubscriberCloseError',
|
|
25
|
+
'@lota-sdk/core/SharedSubscriberCloseError',
|
|
26
26
|
{ message: Schema.String, cause: Schema.optional(Schema.Defect) },
|
|
27
27
|
) {}
|
|
28
28
|
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { chatLogger } from '../config/logger'
|
|
2
|
+
import type { ChatMessageLike, MessagePartLike } from './chat-types'
|
|
3
|
+
|
|
4
|
+
const AI_SDK_SUPPORTED_MESSAGE_PART_TYPES = new Set([
|
|
5
|
+
'text',
|
|
6
|
+
'reasoning',
|
|
7
|
+
'source-url',
|
|
8
|
+
'source-document',
|
|
9
|
+
'file',
|
|
10
|
+
'step-start',
|
|
11
|
+
'dynamic-tool',
|
|
12
|
+
])
|
|
2
13
|
|
|
3
14
|
export function hasMessageContent(parts: readonly MessagePartLike[]): boolean {
|
|
4
15
|
for (const part of parts) {
|
|
@@ -8,3 +19,55 @@ export function hasMessageContent(parts: readonly MessagePartLike[]): boolean {
|
|
|
8
19
|
|
|
9
20
|
return false
|
|
10
21
|
}
|
|
22
|
+
|
|
23
|
+
function isAiSdkSupportedMessagePart(part: MessagePartLike): boolean {
|
|
24
|
+
if (typeof part.type !== 'string') {
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
AI_SDK_SUPPORTED_MESSAGE_PART_TYPES.has(part.type) || part.type.startsWith('data-') || part.type.startsWith('tool-')
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function isSubstantiveAssistantMessagePart(part: MessagePartLike): boolean {
|
|
34
|
+
return isAiSdkSupportedMessagePart(part) && part.type !== 'step-start'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function sanitizeAssistantMessageParts<TPart extends MessagePartLike>(parts: readonly TPart[]): TPart[] {
|
|
38
|
+
return parts.filter((part): part is TPart => isSubstantiveAssistantMessagePart(part))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function sanitizePersistedMessages<TMessage extends ChatMessageLike>(messages: readonly TMessage[]): TMessage[] {
|
|
42
|
+
const sanitizedMessages: TMessage[] = []
|
|
43
|
+
|
|
44
|
+
for (const message of messages) {
|
|
45
|
+
const rawMessageId = (message as Record<string, unknown>).id
|
|
46
|
+
const messageId = typeof rawMessageId === 'string' && rawMessageId.trim() ? rawMessageId : 'unknown'
|
|
47
|
+
|
|
48
|
+
if (message.parts.length === 0) {
|
|
49
|
+
chatLogger.warn`Dropping persisted thread message with empty parts during history sanitization (role=${message.role}, id=${messageId})`
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (message.role !== 'assistant') {
|
|
54
|
+
sanitizedMessages.push(message)
|
|
55
|
+
continue
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const sanitizedParts = sanitizeAssistantMessageParts(message.parts)
|
|
59
|
+
if (sanitizedParts.length === 0) {
|
|
60
|
+
chatLogger.warn`Dropping persisted assistant thread message without substantive parts during history sanitization (id=${messageId})`
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (sanitizedParts.length === message.parts.length) {
|
|
65
|
+
sanitizedMessages.push(message)
|
|
66
|
+
continue
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
sanitizedMessages.push({ ...message, parts: sanitizedParts } as TMessage)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return sanitizedMessages
|
|
73
|
+
}
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { Cause, Context, Schema, Duration, Effect, Latch, Layer } from 'effect'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { ERROR_TAGS } from '../effect/errors'
|
|
4
4
|
import { nowEpochMillis } from '../utils/date-time'
|
|
5
5
|
|
|
6
6
|
const COMPACTION_WAIT_REFRESH_MS = 1_000
|
|
7
7
|
const COMPACTION_MAX_WAIT_MS = 120_000
|
|
8
8
|
|
|
9
|
-
class WaitForCompactionError extends Schema.TaggedErrorClass<WaitForCompactionError>()(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
class WaitForCompactionError extends Schema.TaggedErrorClass<WaitForCompactionError>()(
|
|
10
|
+
ERROR_TAGS.WaitForCompactionError,
|
|
11
|
+
{
|
|
12
|
+
entityId: Schema.String,
|
|
13
|
+
entityLabel: Schema.String,
|
|
14
|
+
message: Schema.String,
|
|
15
|
+
cause: Schema.optional(Schema.Defect),
|
|
16
|
+
},
|
|
17
|
+
) {}
|
|
15
18
|
|
|
16
19
|
function toWaitForCompactionError(params: {
|
|
17
20
|
entityId: string
|
|
@@ -28,12 +31,12 @@ function toWaitForCompactionError(params: {
|
|
|
28
31
|
|
|
29
32
|
interface CompactionCoordination {
|
|
30
33
|
readonly signal: (entityId: string, compacting: boolean) => Effect.Effect<void>
|
|
31
|
-
readonly waitIfNeeded: <TEntity>(params: {
|
|
34
|
+
readonly waitIfNeeded: <TEntity, TError, TRequirements>(params: {
|
|
32
35
|
entityId: string
|
|
33
36
|
entityLabel: string
|
|
34
|
-
loadEntity: () =>
|
|
37
|
+
loadEntity: () => Effect.Effect<TEntity, TError, TRequirements>
|
|
35
38
|
isCompacting: (entity: TEntity) => boolean
|
|
36
|
-
}) => Effect.Effect<TEntity, WaitForCompactionError>
|
|
39
|
+
}) => Effect.Effect<TEntity, WaitForCompactionError, TRequirements>
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
export class CompactionCoordinationTag extends Context.Service<CompactionCoordinationTag, CompactionCoordination>()(
|
|
@@ -65,18 +68,25 @@ export const CompactionCoordinationLive = Layer.effect(
|
|
|
65
68
|
}
|
|
66
69
|
}),
|
|
67
70
|
|
|
68
|
-
waitIfNeeded: Effect.fn('CompactionCoordination.waitIfNeeded')(function* <
|
|
71
|
+
waitIfNeeded: Effect.fn('CompactionCoordination.waitIfNeeded')(function* <
|
|
72
|
+
TEntity,
|
|
73
|
+
TError,
|
|
74
|
+
TRequirements,
|
|
75
|
+
>(params: {
|
|
69
76
|
entityId: string
|
|
70
77
|
entityLabel: string
|
|
71
|
-
loadEntity: () =>
|
|
78
|
+
loadEntity: () => Effect.Effect<TEntity, TError, TRequirements>
|
|
72
79
|
isCompacting: (entity: TEntity) => boolean
|
|
73
80
|
}) {
|
|
74
81
|
const deadline = nowEpochMillis() + COMPACTION_MAX_WAIT_MS
|
|
75
82
|
const latch = getLatch(params.entityId)
|
|
76
|
-
let entity = yield*
|
|
77
|
-
|
|
78
|
-
(
|
|
79
|
-
|
|
83
|
+
let entity = yield* params
|
|
84
|
+
.loadEntity()
|
|
85
|
+
.pipe(
|
|
86
|
+
Effect.mapError((cause) =>
|
|
87
|
+
toWaitForCompactionError({ entityId: params.entityId, entityLabel: params.entityLabel, cause }),
|
|
88
|
+
),
|
|
89
|
+
)
|
|
80
90
|
|
|
81
91
|
while (params.isCompacting(entity)) {
|
|
82
92
|
Latch.closeUnsafe(latch)
|
|
@@ -94,10 +104,13 @@ export const CompactionCoordinationLive = Layer.effect(
|
|
|
94
104
|
Effect.timeout(Duration.millis(refreshWindowMs)),
|
|
95
105
|
Effect.catchIf(Cause.isTimeoutError, () => Effect.void),
|
|
96
106
|
)
|
|
97
|
-
entity = yield*
|
|
98
|
-
|
|
99
|
-
(
|
|
100
|
-
|
|
107
|
+
entity = yield* params
|
|
108
|
+
.loadEntity()
|
|
109
|
+
.pipe(
|
|
110
|
+
Effect.mapError((cause) =>
|
|
111
|
+
toWaitForCompactionError({ entityId: params.entityId, entityLabel: params.entityLabel, cause }),
|
|
112
|
+
),
|
|
113
|
+
)
|
|
101
114
|
}
|
|
102
115
|
|
|
103
116
|
Latch.openUnsafe(latch)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Schema, Effect } from 'effect'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import type { AiGatewayModels } from '../../ai-gateway/ai-gateway'
|
|
4
|
+
import { makeContextCompactionAgentFactory } from '../../system-agents/context-compaction.agent'
|
|
4
5
|
import type { GenerateHelperStructuredParams, GenerateHelperTextParams } from '../helper-model'
|
|
5
6
|
import {
|
|
6
7
|
buildContextCompactionPrompt,
|
|
@@ -29,12 +30,13 @@ interface HelperModelRuntime {
|
|
|
29
30
|
|
|
30
31
|
interface CreateContextCompactionRuntimeDeps {
|
|
31
32
|
helperModelRuntime: HelperModelRuntime
|
|
33
|
+
aiGatewayModels: AiGatewayModels
|
|
32
34
|
now?: () => number
|
|
33
35
|
randomId?: () => string
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
class ContextCompactionRuntimeError extends Schema.TaggedErrorClass<ContextCompactionRuntimeError>()(
|
|
37
|
-
'ContextCompactionRuntimeError',
|
|
39
|
+
'@lota-sdk/core/ContextCompactionRuntimeError',
|
|
38
40
|
{ message: Schema.String, cause: Schema.Defect },
|
|
39
41
|
) {}
|
|
40
42
|
|
|
@@ -48,11 +50,15 @@ function tryContextCompactionPromise<A>(
|
|
|
48
50
|
})
|
|
49
51
|
}
|
|
50
52
|
|
|
51
|
-
function runContextCompacterEffect(
|
|
53
|
+
function runContextCompacterEffect(
|
|
54
|
+
helperModelRuntime: HelperModelRuntime,
|
|
55
|
+
aiGatewayModels: AiGatewayModels,
|
|
56
|
+
params: ContextCompactionRunnerParams,
|
|
57
|
+
) {
|
|
52
58
|
return tryContextCompactionPromise('Failed to compact runtime context.', () =>
|
|
53
59
|
helperModelRuntime.generateHelperStructured({
|
|
54
60
|
tag: 'context-compaction',
|
|
55
|
-
createAgent:
|
|
61
|
+
createAgent: makeContextCompactionAgentFactory(aiGatewayModels),
|
|
56
62
|
messages: [
|
|
57
63
|
{
|
|
58
64
|
role: 'user',
|
|
@@ -69,10 +75,11 @@ function runContextCompacterEffect(helperModelRuntime: HelperModelRuntime, param
|
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
export function createWiredContextCompactionRuntime(deps: CreateContextCompactionRuntimeDeps) {
|
|
72
|
-
const { helperModelRuntime } = deps
|
|
78
|
+
const { helperModelRuntime, aiGatewayModels } = deps
|
|
79
|
+
const createAgent = makeContextCompactionAgentFactory(aiGatewayModels)
|
|
73
80
|
|
|
74
81
|
const runtime = createContextCompactionRuntime({
|
|
75
|
-
runCompacter: (params) => runContextCompacterEffect(helperModelRuntime, params),
|
|
82
|
+
runCompacter: (params) => runContextCompacterEffect(helperModelRuntime, aiGatewayModels, params),
|
|
76
83
|
now: deps.now,
|
|
77
84
|
randomId: deps.randomId,
|
|
78
85
|
thresholdRatio: CONTEXT_COMPACTION_THRESHOLD_RATIO,
|
|
@@ -92,7 +99,7 @@ export function createWiredContextCompactionRuntime(deps: CreateContextCompactio
|
|
|
92
99
|
return tryContextCompactionPromise('Failed to compact memory block summary.', () =>
|
|
93
100
|
helperModelRuntime.generateHelperText({
|
|
94
101
|
tag: 'memory-block-compaction',
|
|
95
|
-
createAgent
|
|
102
|
+
createAgent,
|
|
96
103
|
messages: [{ role: 'user', content: buildMemoryBlockCompactionPrompt({ previousSummary, newEntriesText }) }],
|
|
97
104
|
maxOutputTokens: CONTEXT_COMPACTION_MAX_OUTPUT_TOKENS,
|
|
98
105
|
}),
|