@lota-sdk/core 0.4.23 → 0.4.25

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.23",
3
+ "version": "0.4.25",
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.23",
35
+ "@lota-sdk/shared": "0.4.25",
36
36
  "@mendable/firecrawl-js": "^4.20.0",
37
37
  "@surrealdb/node": "^3.0.3",
38
38
  "ai": "^6.0.170",
@@ -74,6 +74,17 @@ function isAiGenerationError(error: unknown): error is AiGenerationError {
74
74
  return isRecord(error) && error._tag === ERROR_TAGS.AiGenerationError
75
75
  }
76
76
 
77
+ export function isAiGenerationContentFilterError(error: unknown): error is AiGenerationError {
78
+ if (!isAiGenerationError(error)) return false
79
+
80
+ const text = [error.message, error.providerData, error.responseBody]
81
+ .filter((part): part is string => typeof part === 'string' && part.length > 0)
82
+ .join('\n')
83
+ .toLowerCase()
84
+
85
+ return text.includes('content_filter') || text.includes('content management policy')
86
+ }
87
+
77
88
  function getNumericField(value: Record<string, unknown>, key: string): number | null {
78
89
  const field = value[key]
79
90
  if (typeof field === 'number' && Number.isFinite(field)) return field
@@ -13,6 +13,7 @@ export {
13
13
  extractAiGatewayChatReasoningText,
14
14
  injectAiGatewayChatReasoningContent,
15
15
  injectAiGatewayChatReasoningStream,
16
+ isAiGenerationContentFilterError,
16
17
  normalizeAiGatewayChatProviderOptions,
17
18
  normalizeAiGatewayJsonSchemas,
18
19
  normalizeAiGatewayUrl,
@@ -11,6 +11,8 @@ import type { QueueJobService, WorkerHandle } from '../workers/worker-utils'
11
11
  import { createQueueFactoryWithDeps } from './queue-factory'
12
12
  import { runStandaloneQueueWorker } from './standalone-worker'
13
13
 
14
+ const POST_CHAT_MEMORY_CONCURRENCY = 2
15
+
14
16
  interface PostChatMemoryMessage {
15
17
  role: 'user' | 'agent'
16
18
  content: string
@@ -105,7 +107,7 @@ export function makePostChatMemoryQueueRuntime(
105
107
  name: 'post-chat-memory',
106
108
  displayName: 'Post-chat memory',
107
109
  jobName: 'extract-memory',
108
- concurrency: 10,
110
+ concurrency: POST_CHAT_MEMORY_CONCURRENCY,
109
111
  lockDuration: 900_000,
110
112
  maxStalledCount: 10,
111
113
  stalledInterval: 120_000,
@@ -13,6 +13,7 @@ import { createQueueFactoryWithDeps } from './queue-factory'
13
13
  import { runStandaloneQueueWorker } from './standalone-worker'
14
14
 
15
15
  export const TITLE_GENERATION_QUEUE = 'title-generation'
16
+ const TITLE_GENERATION_CONCURRENCY = 2
16
17
 
17
18
  // This queue merges thread title generation and recent-activity title
18
19
  // refinement because both are short-lived title synthesis jobs with the same
@@ -68,7 +69,7 @@ export function makeTitleGenerationQueueRuntime(
68
69
  name: TITLE_GENERATION_QUEUE,
69
70
  displayName: 'Title generation',
70
71
  jobName: 'title-generation',
71
- concurrency: 10,
72
+ concurrency: TITLE_GENERATION_CONCURRENCY,
72
73
  lockDuration: 300_000,
73
74
  defaultJobOptions: { attempts: 3, backoff: { type: 'exponential', delay: 2_000 } },
74
75
  connectionProvider,
@@ -1,9 +1,10 @@
1
1
  import { Context, Effect, Layer } from 'effect'
2
2
 
3
- import { AiGatewayModelsTag } from '../ai-gateway/ai-gateway'
3
+ import { AiGatewayModelsTag, isAiGenerationContentFilterError } from '../ai-gateway/ai-gateway'
4
4
  import type { AiGatewayModels } from '../ai-gateway/ai-gateway'
5
5
  import type { ResolvedAgentConfig } from '../config/agent-defaults'
6
- import { ServiceError } from '../effect/errors'
6
+ import { chatLogger } from '../config/logger'
7
+ import { ERROR_TAGS, ServiceError } from '../effect/errors'
7
8
  import { AgentConfigServiceTag } from '../effect/services'
8
9
  import type { HelperModelRuntime } from '../runtime/helper-model'
9
10
  import { HelperModelTag } from '../runtime/helper-model'
@@ -62,20 +63,34 @@ export function makeRecentActivityTitleService(
62
63
  return
63
64
  }
64
65
 
65
- const refinedTitle = normalizeTitle(
66
- yield* Effect.tryPromise({
67
- try: () =>
68
- helperModelRuntime.generateHelperText({
69
- tag: 'recent-activity-title-refinement',
70
- createAgent: refinerAgentFactory,
71
- defaultSystemPrompt: RECENT_ACTIVITY_TITLE_REFINER_PROMPT,
72
- timeoutMs: RECENT_ACTIVITY_TITLE_TIMEOUT_MS,
73
- messages: [{ role: 'user', content: promptInput }],
74
- }),
75
- catch: (cause) =>
76
- new ServiceError({ message: 'Failed to generate recent activity title refinement.', cause }),
77
- }),
66
+ const maybeRefinedTitle = yield* Effect.tryPromise({
67
+ try: () =>
68
+ helperModelRuntime.generateHelperText({
69
+ tag: 'recent-activity-title-refinement',
70
+ createAgent: refinerAgentFactory,
71
+ defaultSystemPrompt: RECENT_ACTIVITY_TITLE_REFINER_PROMPT,
72
+ timeoutMs: RECENT_ACTIVITY_TITLE_TIMEOUT_MS,
73
+ messages: [{ role: 'user', content: promptInput }],
74
+ }),
75
+ catch: (cause) =>
76
+ isAiGenerationContentFilterError(cause)
77
+ ? cause
78
+ : new ServiceError({ message: 'Failed to generate recent activity title refinement.', cause }),
79
+ }).pipe(
80
+ Effect.catchTag(ERROR_TAGS.AiGenerationError, (error) =>
81
+ isAiGenerationContentFilterError(error)
82
+ ? Effect.sync(() => {
83
+ chatLogger.warn`Skipping recent activity title refinement after provider content filter (activityId=${activityId})`
84
+ return null
85
+ })
86
+ : Effect.fail(error),
87
+ ),
78
88
  )
89
+ if (maybeRefinedTitle === null) {
90
+ return
91
+ }
92
+
93
+ const refinedTitle = normalizeTitle(maybeRefinedTitle)
79
94
  if (
80
95
  !recentActivityService.isAgentTitleUseful({
81
96
  currentTitle: candidate.title,
@@ -87,7 +87,7 @@ export const createWorkerShutdown = (worker: Worker, name: string, logger: typeo
87
87
  return Effect.runPromise(
88
88
  Effect.asVoid(
89
89
  Effect.tryPromise({
90
- try: () => worker.close(),
90
+ try: () => worker.close(true),
91
91
  catch: (cause) => new QueueWorkerError({ phase: 'close', cause }),
92
92
  }),
93
93
  ),