@lota-sdk/core 0.4.32 → 0.4.34

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.32",
3
+ "version": "0.4.34",
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.32",
35
+ "@lota-sdk/shared": "0.4.34",
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,
@@ -7,6 +7,8 @@ import { ensureRecordId, recordIdToString } from '../../db/record-id'
7
7
  import { TABLES } from '../../db/tables'
8
8
  import { isRetriableTransactionConflict } from '../../db/transaction-conflict'
9
9
  import { ERROR_TAGS } from '../../effect/errors'
10
+ import type { RedisConnectionManager } from '../../redis/connection'
11
+ import { withLeaseLock } from '../../redis/redis-lease-lock'
10
12
  import {
11
13
  appendToMemoryBlock,
12
14
  compactMemoryBlockEntries,
@@ -27,6 +29,9 @@ type BackgroundWorker = Context.Service.Shape<typeof BackgroundWorkServiceTag>
27
29
 
28
30
  const MEMORY_BLOCK_COMPACTION_RETRY_ATTEMPTS = 4
29
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
30
35
 
31
36
  class ThreadMemoryBlockError extends Schema.TaggedErrorClass<ThreadMemoryBlockError>()(
32
37
  ERROR_TAGS.ThreadMemoryBlockError,
@@ -44,7 +49,38 @@ export function createThreadMemoryBlockHelpers(deps: {
44
49
  threadStore: ThreadRecordStore
45
50
  contextCompactionService: ContextCompactionServiceLike
46
51
  background: BackgroundWorker
52
+ redis?: RedisConnectionManager
47
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
+
48
84
  function appendMemoryBlock(threadId: RecordIdRef, entry: string): Effect.Effect<string, ThreadMemoryBlockError> {
49
85
  return Effect.gen(function* () {
50
86
  const threadRef = ensureRecordId(threadId, TABLES.THREAD)
@@ -156,7 +192,9 @@ export function createThreadMemoryBlockHelpers(deps: {
156
192
  }
157
193
 
158
194
  function compactMemoryBlock(threadId: RecordIdRef): Effect.Effect<boolean, ThreadMemoryBlockError> {
159
- return compactMemoryBlockOnce(threadId).pipe(
195
+ const threadRef = ensureRecordId(threadId, TABLES.THREAD)
196
+ const threadIdString = recordIdToString(threadRef, TABLES.THREAD)
197
+ const compact = compactMemoryBlockOnce(threadRef).pipe(
160
198
  Effect.retry({
161
199
  times: MEMORY_BLOCK_COMPACTION_RETRY_ATTEMPTS - 1,
162
200
  schedule: Schedule.jittered(
@@ -165,6 +203,8 @@ export function createThreadMemoryBlockHelpers(deps: {
165
203
  while: isRetriableTransactionConflict,
166
204
  }),
167
205
  )
206
+
207
+ return withCompactionLock(threadIdString, compact)
168
208
  }
169
209
 
170
210
  return { appendMemoryBlock, compactMemoryBlock }
@@ -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
  }
@@ -4,10 +4,7 @@ import { ToolLoopAgent } from 'ai'
4
4
  import type { AiGatewayModels } from '../ai-gateway/ai-gateway'
5
5
  import { buildAiGatewayDirectCacheHeaders } from '../ai-gateway/cache-headers'
6
6
  import type { ResolvedAgentConfig } from '../config/agent-defaults'
7
- import {
8
- OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
9
- OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
10
- } from '../config/model-constants'
7
+ import { OPENROUTER_STRUCTURED_HELPER_MODEL_ID } from '../config/model-constants'
11
8
  import { resolveHelperAgentOptions } from './helper-agent-options'
12
9
 
13
10
  const RECENT_ACTIVITY_TITLE_MAX_TOKENS = 256
@@ -82,7 +79,6 @@ export function makeRecentActivityTitleRefinerAgentFactory(models: AiGatewayMode
82
79
  id: 'recent-activity-title-refiner',
83
80
  model: models.chatModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
84
81
  headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
85
- providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
86
82
  ...resolveHelperAgentOptions(options, {
87
83
  instructions: buildRecentActivityTitleRefinerPrompt(agentConfig),
88
84
  maxOutputTokens: RECENT_ACTIVITY_TITLE_MAX_TOKENS,