@lota-sdk/core 0.4.31 → 0.4.33

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lota-sdk/core",
3
- "version": "0.4.31",
3
+ "version": "0.4.33",
4
4
  "files": [
5
5
  "src",
6
6
  "infrastructure/schema"
@@ -32,7 +32,7 @@
32
32
  "@ai-sdk/provider": "^3.0.9",
33
33
  "@chat-adapter/slack": "^4.26.0",
34
34
  "@chat-adapter/state-ioredis": "^4.26.0",
35
- "@lota-sdk/shared": "0.4.31",
35
+ "@lota-sdk/shared": "0.4.33",
36
36
  "@mendable/firecrawl-js": "^4.20.0",
37
37
  "@surrealdb/node": "^3.0.3",
38
38
  "ai": "^6.0.170",
@@ -99,12 +99,15 @@ export function createWiredContextCompactionRuntime(deps: CreateContextCompactio
99
99
  if (!previousSummary && !newEntriesText) return Effect.succeed('')
100
100
 
101
101
  return tryContextCompactionPromise('Failed to compact memory block summary.', () =>
102
- helperModelRuntime.generateHelperText({
103
- tag: 'memory-block-compaction',
104
- createAgent,
105
- messages: [{ role: 'user', content: buildMemoryBlockCompactionPrompt({ previousSummary, newEntriesText }) }],
106
- maxOutputTokens: CONTEXT_COMPACTION_MAX_OUTPUT_TOKENS,
107
- }),
102
+ helperModelRuntime
103
+ .generateHelperStructured({
104
+ tag: 'memory-block-compaction',
105
+ createAgent,
106
+ messages: [{ role: 'user', content: buildMemoryBlockCompactionPrompt({ previousSummary, newEntriesText }) }],
107
+ schema: ContextCompactionOutputSchema,
108
+ maxOutputTokens: CONTEXT_COMPACTION_MAX_OUTPUT_TOKENS,
109
+ })
110
+ .then((result) => result.summary),
108
111
  ).pipe(
109
112
  Effect.retry({
110
113
  times: MEMORY_BLOCK_COMPACTION_RETRY_ATTEMPTS,
@@ -1,11 +1,14 @@
1
1
  import type { Context } from 'effect'
2
- import { Schema, Effect } from 'effect'
2
+ import { Duration, Schema, Effect, Schedule } from 'effect'
3
3
 
4
4
  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 { isRetriableTransactionConflict } from '../../db/transaction-conflict'
8
9
  import { ERROR_TAGS } from '../../effect/errors'
10
+ import type { RedisConnectionManager } from '../../redis/connection'
11
+ import { withLeaseLock } from '../../redis/redis-lease-lock'
9
12
  import {
10
13
  appendToMemoryBlock,
11
14
  compactMemoryBlockEntries,
@@ -24,6 +27,12 @@ type ContextCompactionServiceLike = Pick<ReturnType<typeof makeContextCompaction
24
27
 
25
28
  type BackgroundWorker = Context.Service.Shape<typeof BackgroundWorkServiceTag>
26
29
 
30
+ const MEMORY_BLOCK_COMPACTION_RETRY_ATTEMPTS = 4
31
+ const MEMORY_BLOCK_COMPACTION_RETRY_BASE_DELAY_MS = 50
32
+ const MEMORY_BLOCK_COMPACTION_LOCK_TTL_MS = 120_000
33
+ const MEMORY_BLOCK_COMPACTION_LOCK_MAX_WAIT_MS = 250
34
+ const MEMORY_BLOCK_COMPACTION_LOCK_RETRY_DELAY_MS = 125
35
+
27
36
  class ThreadMemoryBlockError extends Schema.TaggedErrorClass<ThreadMemoryBlockError>()(
28
37
  ERROR_TAGS.ThreadMemoryBlockError,
29
38
  { message: Schema.String, cause: Schema.optional(Schema.Defect) },
@@ -40,7 +49,38 @@ export function createThreadMemoryBlockHelpers(deps: {
40
49
  threadStore: ThreadRecordStore
41
50
  contextCompactionService: ContextCompactionServiceLike
42
51
  background: BackgroundWorker
52
+ redis?: RedisConnectionManager
43
53
  }) {
54
+ function withCompactionLock(
55
+ threadIdString: string,
56
+ effect: Effect.Effect<boolean, ThreadMemoryBlockError>,
57
+ ): Effect.Effect<boolean, ThreadMemoryBlockError> {
58
+ if (!deps.redis) return effect
59
+
60
+ return withLeaseLock(
61
+ {
62
+ redis: deps.redis.getConnection(),
63
+ lockKey: `thread-memory-block:compact:${threadIdString}`,
64
+ lockTtlMs: MEMORY_BLOCK_COMPACTION_LOCK_TTL_MS,
65
+ maxWaitMs: MEMORY_BLOCK_COMPACTION_LOCK_MAX_WAIT_MS,
66
+ retryDelayMs: MEMORY_BLOCK_COMPACTION_LOCK_RETRY_DELAY_MS,
67
+ label: 'thread memory block compaction',
68
+ logger: serverLogger,
69
+ },
70
+ () => effect,
71
+ ).pipe(
72
+ Effect.catchTag(ERROR_TAGS.LockAcquisitionError, () => Effect.succeed(false)),
73
+ Effect.mapError((cause) =>
74
+ cause._tag === ERROR_TAGS.ThreadMemoryBlockError
75
+ ? cause
76
+ : new ThreadMemoryBlockError({
77
+ message: `Failed to coordinate memory block compaction for thread ${threadIdString}`,
78
+ cause,
79
+ }),
80
+ ),
81
+ )
82
+ }
83
+
44
84
  function appendMemoryBlock(threadId: RecordIdRef, entry: string): Effect.Effect<string, ThreadMemoryBlockError> {
45
85
  return Effect.gen(function* () {
46
86
  const threadRef = ensureRecordId(threadId, TABLES.THREAD)
@@ -94,7 +134,7 @@ export function createThreadMemoryBlockHelpers(deps: {
94
134
  })
95
135
  }
96
136
 
97
- function compactMemoryBlock(threadId: RecordIdRef): Effect.Effect<boolean, ThreadMemoryBlockError> {
137
+ function compactMemoryBlockOnce(threadId: RecordIdRef): Effect.Effect<boolean, ThreadMemoryBlockError> {
98
138
  return Effect.gen(function* () {
99
139
  const threadRef = ensureRecordId(threadId, TABLES.THREAD)
100
140
  const threadIdString = recordIdToString(threadRef, TABLES.THREAD)
@@ -151,5 +191,21 @@ export function createThreadMemoryBlockHelpers(deps: {
151
191
  })
152
192
  }
153
193
 
194
+ function compactMemoryBlock(threadId: RecordIdRef): Effect.Effect<boolean, ThreadMemoryBlockError> {
195
+ const threadRef = ensureRecordId(threadId, TABLES.THREAD)
196
+ const threadIdString = recordIdToString(threadRef, TABLES.THREAD)
197
+ const compact = compactMemoryBlockOnce(threadRef).pipe(
198
+ Effect.retry({
199
+ times: MEMORY_BLOCK_COMPACTION_RETRY_ATTEMPTS - 1,
200
+ schedule: Schedule.jittered(
201
+ Schedule.exponential(Duration.millis(MEMORY_BLOCK_COMPACTION_RETRY_BASE_DELAY_MS), 2),
202
+ ),
203
+ while: isRetriableTransactionConflict,
204
+ }),
205
+ )
206
+
207
+ return withCompactionLock(threadIdString, compact)
208
+ }
209
+
154
210
  return { appendMemoryBlock, compactMemoryBlock }
155
211
  }
@@ -170,6 +170,7 @@ export function makeThreadService(deps: ThreadServiceDeps) {
170
170
  threadStore,
171
171
  contextCompactionService: { compactMemoryBlock: deps.contextCompactionService.compactMemoryBlock },
172
172
  background: deps.background,
173
+ redis: deps.redis,
173
174
  })
174
175
 
175
176
  const getById = Effect.fn('ThreadService.getById')(function* (threadId: RecordIdRef) {
@@ -3,10 +3,7 @@ import { ToolLoopAgent } from 'ai'
3
3
 
4
4
  import type { AiGatewayModels } from '../ai-gateway/ai-gateway'
5
5
  import { buildAiGatewayDirectCacheHeaders } from '../ai-gateway/cache-headers'
6
- import {
7
- OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
8
- OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
9
- } from '../config/model-constants'
6
+ import { OPENROUTER_STRUCTURED_HELPER_MODEL_ID } from '../config/model-constants'
10
7
  import { resolveHelperAgentOptions } from './helper-agent-options'
11
8
 
12
9
  const CONTEXT_COMPACTION_PROMPT = `<agent-instructions>
@@ -37,7 +34,6 @@ export function makeContextCompactionAgentFactory(models: AiGatewayModels) {
37
34
  id: 'context-compaction',
38
35
  model: models.chatModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
39
36
  headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
40
- providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
41
37
  ...resolveHelperAgentOptions(options, { instructions: CONTEXT_COMPACTION_PROMPT }),
42
38
  })
43
39
  }