@lota-sdk/core 0.4.4 → 0.4.5

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.4",
3
+ "version": "0.4.5",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -32,7 +32,7 @@
32
32
  "@chat-adapter/slack": "^4.23.0",
33
33
  "@chat-adapter/state-ioredis": "^4.23.0",
34
34
  "@logtape/logtape": "^2.0.5",
35
- "@lota-sdk/shared": "0.4.4",
35
+ "@lota-sdk/shared": "0.4.5",
36
36
  "@mendable/firecrawl-js": "^4.18.1",
37
37
  "@surrealdb/node": "^3.0.3",
38
38
  "ai": "^6.0.145",
@@ -305,17 +305,6 @@ export function injectAiGatewayExtraParamsRequestBody(
305
305
  return JSON.stringify({ ...parsed, extra_params: mergedExtraParams })
306
306
  }
307
307
 
308
- export function injectAiGatewayOpenAIPromptCacheRetentionRequestBody(
309
- body: BodyInit | null | undefined,
310
- ): BodyInit | null | undefined {
311
- const parsed = parseAiGatewayJsonRequestBody(body)
312
- if (!parsed) return body
313
- if (!readString(parsed.model)?.startsWith('openai/')) return body
314
- if (readString(parsed.prompt_cache_retention) !== null) return body
315
-
316
- return JSON.stringify({ ...parsed, prompt_cache_retention: OPENAI_PROMPT_CACHE_RETENTION })
317
- }
318
-
319
308
  function createAiGatewayFetch(extraParams?: AiGatewayExtraParams): typeof fetch {
320
309
  const fetchWithMutations = (input: RequestInfo | URL, init?: RequestInit | BunFetchRequestInit) => {
321
310
  const parsedBody = parseAiGatewayJsonRequestBody(init?.body)
@@ -14,15 +14,6 @@ const DEFAULT_CONFIG: BackgroundProcessingConfig = {
14
14
 
15
15
  let resolvedConfig: BackgroundProcessingConfig = { ...DEFAULT_CONFIG }
16
16
 
17
- export function configureBackgroundProcessing(config?: Partial<BackgroundProcessingConfig>): void {
18
- resolvedConfig = {
19
- memoryExtractionFrequency: config?.memoryExtractionFrequency ?? DEFAULT_CONFIG.memoryExtractionFrequency,
20
- skillExtractionFrequency: config?.skillExtractionFrequency ?? DEFAULT_CONFIG.skillExtractionFrequency,
21
- memoryDigestFrequency: config?.memoryDigestFrequency ?? DEFAULT_CONFIG.memoryDigestFrequency,
22
- memoryConsolidationFrequency: config?.memoryConsolidationFrequency ?? DEFAULT_CONFIG.memoryConsolidationFrequency,
23
- }
24
- }
25
-
26
17
  export function getBackgroundProcessingConfig(): BackgroundProcessingConfig {
27
18
  return resolvedConfig
28
19
  }
@@ -8,7 +8,6 @@ export {
8
8
  OPENROUTER_LOW_REASONING_PROVIDER_OPTIONS,
9
9
  OPENROUTER_MEDIUM_REASONING_PROVIDER_OPTIONS,
10
10
  OPENROUTER_MINIMAL_REASONING_PROVIDER_OPTIONS,
11
- OPENROUTER_STRUCTURED_REASONING_MODEL_ID,
12
11
  OPENROUTER_TEAM_AGENT_MODEL_ID,
13
12
  OPENROUTER_WEB_RESEARCH_MODEL_ID,
14
13
  OPENROUTER_XHIGH_REASONING_PROVIDER_OPTIONS,
@@ -2,7 +2,6 @@ import type { ChatMessage } from '@lota-sdk/shared'
2
2
 
3
3
  import { configureEmbeddingCache } from './ai/embedding-cache'
4
4
  import { configureAgentFactory, configureAgents } from './config/agent-defaults'
5
- import { configureBackgroundProcessing } from './config/background-processing'
6
5
  import { configureLotaLogger } from './config/logger'
7
6
  import { configureThreads } from './config/thread-defaults'
8
7
  import { ensureRecordId } from './db/record-id'
@@ -251,7 +250,6 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
251
250
  const redisManager = createRedisConnectionManager({ url: runtimeConfig.redis.url })
252
251
  setRedisConnectionManager(redisManager)
253
252
  configureEmbeddingCache(redisManager.getConnection(), runtimeConfig.memory.embeddingCacheTtlSeconds)
254
- configureBackgroundProcessing()
255
253
  configureSocialChatHistory({ keyPrefix: runtimeConfig.socialChat?.historyRedisKeyPrefix })
256
254
 
257
255
  const socialChatAgentId = runtimeConfig.socialChat?.agentId?.trim() || 'socialChat'
package/src/db/service.ts CHANGED
@@ -44,8 +44,6 @@ export interface SurrealDatabaseLogger {
44
44
  error?: (message: string) => void
45
45
  }
46
46
 
47
- export type BoundQueryLike = { query: string; bindings?: Record<string, unknown> }
48
-
49
47
  interface FindManyOptions {
50
48
  limit?: number
51
49
  offset?: number
@@ -91,18 +89,6 @@ function configureMutation(
91
89
  return builder.merge(data)
92
90
  }
93
91
 
94
- function isBoundQueryLike(value: unknown): value is BoundQueryLike {
95
- if (!isRecord(value)) {
96
- return false
97
- }
98
-
99
- if (typeof value.query !== 'string') {
100
- return false
101
- }
102
-
103
- return value.bindings === undefined || isRecord(value.bindings)
104
- }
105
-
106
92
  const CONNECT_MAX_ATTEMPTS = 5
107
93
  const CONNECT_RETRY_BASE_DELAY_MS = 100
108
94
  const CONNECT_RETRY_JITTER_MS = 50
@@ -396,11 +382,7 @@ export class SurrealDBService {
396
382
  return { clause: clauses.join(' AND '), bindings }
397
383
  }
398
384
 
399
- private normalizeBoundQuery(query: BoundQuery | BoundQueryLike): BoundQuery {
400
- if (!(query instanceof BoundQuery) && !isBoundQueryLike(query)) {
401
- throw new SurrealDBError('Invalid query object: expected a BoundQuery-like value')
402
- }
403
-
385
+ private normalizeBoundQuery(query: BoundQuery): BoundQuery {
404
386
  return new BoundQuery(query.query, this.normalizeBindings(query.bindings))
405
387
  }
406
388
 
@@ -533,7 +515,7 @@ export class SurrealDBService {
533
515
  private wrapTransaction(tx: SurrealTransaction): DatabaseTransaction {
534
516
  return {
535
517
  query: async (query: unknown) => {
536
- const boundQuery = this.normalizeBoundQuery(query as BoundQuery | BoundQueryLike)
518
+ const boundQuery = this.normalizeBoundQuery(query as BoundQuery)
537
519
  const queryText = this.resolveQueryText(boundQuery)
538
520
 
539
521
  try {
@@ -578,16 +560,16 @@ export class SurrealDBService {
578
560
  }
579
561
  }
580
562
 
581
- private resolveQueryText(query: BoundQuery | BoundQueryLike): string {
563
+ private resolveQueryText(query: BoundQuery): string {
582
564
  return query.query
583
565
  }
584
566
 
585
- async query<T>(query: BoundQuery | BoundQueryLike): Promise<T[]> {
567
+ async query<T>(query: BoundQuery): Promise<T[]> {
586
568
  const statements = await this.queryAll<T>(query)
587
569
  return statements.at(0) ?? []
588
570
  }
589
571
 
590
- async queryAll<T>(query: BoundQuery | BoundQueryLike, schema?: z.ZodTypeAny): Promise<T[][]> {
572
+ async queryAll<T>(query: BoundQuery, schema?: z.ZodTypeAny): Promise<T[][]> {
591
573
  const client = await this.ensureConnected()
592
574
  const boundQuery = this.normalizeBoundQuery(query)
593
575
  const queryText = this.resolveQueryText(boundQuery)
@@ -607,13 +589,13 @@ export class SurrealDBService {
607
589
  }
608
590
  }
609
591
 
610
- async queryOne<T extends z.ZodTypeAny>(query: BoundQuery | BoundQueryLike, schema: T): Promise<z.infer<T> | null> {
592
+ async queryOne<T extends z.ZodTypeAny>(query: BoundQuery, schema: T): Promise<z.infer<T> | null> {
611
593
  const results = await this.query<unknown>(query)
612
594
  const first = results.at(0)
613
595
  return first ? this.parseSchema(schema, first) : null
614
596
  }
615
597
 
616
- async queryMany<T extends z.ZodTypeAny>(query: BoundQuery | BoundQueryLike, schema: T): Promise<z.infer<T>[]> {
598
+ async queryMany<T extends z.ZodTypeAny>(query: BoundQuery, schema: T): Promise<z.infer<T>[]> {
617
599
  const results = await this.query<unknown>(query)
618
600
  return results.map((row) => this.parseSchema(schema, row))
619
601
  }
@@ -129,10 +129,6 @@ export function startAutonomousJobWorker(options: AutonomousJobWorkerOptions = {
129
129
  return handle
130
130
  }
131
131
 
132
- export function getAutonomousJobQueueHandle(): WorkerHandle {
133
- return startAutonomousJobWorker()
134
- }
135
-
136
132
  if (import.meta.main) {
137
133
  startAutonomousJobWorker()
138
134
  }
@@ -7,6 +7,7 @@ import type {
7
7
  GetArtifactResult,
8
8
  PublishArtifactArgs,
9
9
  } from '@lota-sdk/shared'
10
+ import { BoundQuery } from 'surrealdb'
10
11
 
11
12
  import type { RecordIdInput } from '../db/record-id'
12
13
  import { ensureRecordId, recordIdToString } from '../db/record-id'
@@ -361,13 +362,10 @@ class ArtifactService {
361
362
  async listBacklinks(artifact: Pick<ArtifactRecord, 'id' | 'organizationId'>): Promise<ArtifactRecord[]> {
362
363
  const artifactId = recordIdToString(artifact.id, TABLES.ARTIFACT)
363
364
  const records = await databaseService.queryMany(
364
- {
365
- query: `SELECT * FROM ${TABLES.ARTIFACT} WHERE organizationId = $organizationId AND references[*].targetId CONTAINS $targetId`,
366
- bindings: {
367
- organizationId: ensureRecordId(artifact.organizationId, TABLES.ORGANIZATION),
368
- targetId: artifactId,
369
- },
370
- },
365
+ new BoundQuery(
366
+ `SELECT * FROM ${TABLES.ARTIFACT} WHERE organizationId = $organizationId AND references[*].targetId CONTAINS $targetId`,
367
+ { organizationId: ensureRecordId(artifact.organizationId, TABLES.ORGANIZATION), targetId: artifactId },
368
+ ),
371
369
  ArtifactRecordSchema,
372
370
  )
373
371
  return records
@@ -22,7 +22,7 @@ import type {
22
22
  } from '@lota-sdk/shared'
23
23
  import type { Job } from 'bullmq'
24
24
  import { CronExpressionParser } from 'cron-parser'
25
- import { RecordId } from 'surrealdb'
25
+ import { BoundQuery, RecordId } from 'surrealdb'
26
26
  import { z } from 'zod'
27
27
 
28
28
  import { ensureRecordId, recordIdToString } from '../db/record-id'
@@ -262,17 +262,14 @@ class AutonomousJobService {
262
262
 
263
263
  private async findRecoverableRunRow(autonomousJobId: RecordIdInput): Promise<AutonomousJobRunRow | null> {
264
264
  const rows = await databaseService.queryMany(
265
- {
266
- query: `SELECT * FROM ${TABLES.AUTONOMOUS_JOB_RUN}
265
+ new BoundQuery(
266
+ `SELECT * FROM ${TABLES.AUTONOMOUS_JOB_RUN}
267
267
  WHERE autonomousJobId = $autonomousJobId
268
268
  AND status IN $statuses
269
269
  ORDER BY createdAt DESC
270
270
  LIMIT 1`,
271
- bindings: {
272
- autonomousJobId: ensureRecordId(autonomousJobId, TABLES.AUTONOMOUS_JOB),
273
- statuses: ['queued', 'running'],
274
- },
275
- },
271
+ { autonomousJobId: ensureRecordId(autonomousJobId, TABLES.AUTONOMOUS_JOB), statuses: ['queued', 'running'] },
272
+ ),
276
273
  AutonomousJobRunRowSchema,
277
274
  )
278
275
 
@@ -371,10 +368,9 @@ class AutonomousJobService {
371
368
  async recoverActiveJobs(now = new Date()): Promise<void> {
372
369
  await databaseService.connect()
373
370
  const activeRows = await databaseService.queryMany(
374
- {
375
- query: `SELECT * FROM ${TABLES.AUTONOMOUS_JOB} WHERE status = $status ORDER BY createdAt ASC`,
376
- bindings: { status: 'active' },
377
- },
371
+ new BoundQuery(`SELECT * FROM ${TABLES.AUTONOMOUS_JOB} WHERE status = $status ORDER BY createdAt ASC`, {
372
+ status: 'active',
373
+ }),
378
374
  AutonomousJobRowSchema,
379
375
  )
380
376
 
@@ -1,5 +1,6 @@
1
1
  import type { PlanExecutionVisibility, PlanNodeSpecRecord, PlanRunRecord, PlanSpecRecord } from '@lota-sdk/shared'
2
2
  import { PlanRunSchema } from '@lota-sdk/shared'
3
+ import { BoundQuery } from 'surrealdb'
3
4
 
4
5
  import type { RecordIdInput } from '../db/record-id'
5
6
  import { ensureRecordId, recordIdToString } from '../db/record-id'
@@ -254,10 +255,10 @@ class PlanAgentQueryService {
254
255
 
255
256
  const whereOrganization = organizationId ? ' AND organizationId = $organizationId' : ''
256
257
  return databaseService.queryMany(
257
- {
258
- query: `SELECT * FROM ${TABLES.PLAN_RUN} WHERE status INSIDE $statuses${whereOrganization} ORDER BY updatedAt DESC`,
258
+ new BoundQuery(
259
+ `SELECT * FROM ${TABLES.PLAN_RUN} WHERE status INSIDE $statuses${whereOrganization} ORDER BY updatedAt DESC`,
259
260
  bindings,
260
- },
261
+ ),
261
262
  PlanRunSchema,
262
263
  )
263
264
  }
@@ -18,10 +18,6 @@ function buildImplicitLinearEdges(draft: PlanDraft) {
18
18
  }
19
19
 
20
20
  class PlanBuilderService {
21
- roleAssignment(draft: PlanDraft): PlanDraft {
22
- return draft
23
- }
24
-
25
21
  structureDesign(draft: PlanDraft): PlanDraft {
26
22
  return {
27
23
  ...draft,
@@ -63,8 +59,7 @@ class PlanBuilderService {
63
59
  }
64
60
 
65
61
  prepareDraft(draft: PlanDraft): PlanDraft {
66
- const withRoles = this.roleAssignment(draft)
67
- const withStructure = this.structureDesign(withRoles)
62
+ const withStructure = this.structureDesign(draft)
68
63
  return this.semanticCompletion(withStructure)
69
64
  }
70
65
  }
@@ -8,7 +8,7 @@ import type {
8
8
  PlanRunRecord,
9
9
  } from '@lota-sdk/shared'
10
10
  import { PlanEventSchema, PlanNodeRunSchema, PlanNodeSpecRecordSchema, PlanRunSchema } from '@lota-sdk/shared'
11
- import { RecordId } from 'surrealdb'
11
+ import { BoundQuery, RecordId } from 'surrealdb'
12
12
 
13
13
  import type { RecordIdInput } from '../db/record-id'
14
14
  import { ensureRecordId, recordIdToString } from '../db/record-id'
@@ -185,10 +185,9 @@ class PlanDeadlineService {
185
185
  entries: Array<{ nodeRun: PlanNodeRunRecord; nodeSpec: PlanNodeSpecRecord; evaluation: DeadlineEvaluationResult }>
186
186
  }> {
187
187
  const activeNodeRuns = await databaseService.queryMany(
188
- {
189
- query: `SELECT * FROM ${TABLES.PLAN_NODE_RUN} WHERE status IN $statuses`,
190
- bindings: { statuses: ['running', 'awaiting-human'] },
191
- },
188
+ new BoundQuery(`SELECT * FROM ${TABLES.PLAN_NODE_RUN} WHERE status IN $statuses`, {
189
+ statuses: ['running', 'awaiting-human'],
190
+ }),
192
191
  PlanNodeRunSchema,
193
192
  )
194
193
 
@@ -380,20 +379,20 @@ class PlanDeadlineService {
380
379
 
381
380
  if (dedupeKey) {
382
381
  const existing = await databaseService.queryMany(
383
- {
384
- query: `SELECT * FROM ${TABLES.PLAN_EVENT}
382
+ new BoundQuery(
383
+ `SELECT * FROM ${TABLES.PLAN_EVENT}
385
384
  WHERE runId = $runId
386
385
  AND nodeId = $nodeId
387
386
  AND eventType = $eventType
388
387
  AND detail.dedupeKey = $dedupeKey
389
388
  LIMIT 1`,
390
- bindings: {
389
+ {
391
390
  runId: ensureRecordId(params.run.id, TABLES.PLAN_RUN),
392
391
  nodeId: params.nodeRun.nodeId,
393
392
  eventType: params.eventType,
394
393
  dedupeKey,
395
394
  },
396
- },
395
+ ),
397
396
  PlanEventSchema,
398
397
  )
399
398
  if (existing.length > 0) {
@@ -1,6 +1,7 @@
1
1
  import { PlanCycleRecordSchema, PlanScheduleRecordSchema } from '@lota-sdk/shared'
2
2
  import type { PlanScheduleRecord, PlanScheduleSpec } from '@lota-sdk/shared'
3
3
  import { CronExpressionParser } from 'cron-parser'
4
+ import { BoundQuery } from 'surrealdb'
4
5
 
5
6
  import type { RecordIdInput } from '../db/record-id'
6
7
  import { ensureRecordId, recordIdToString } from '../db/record-id'
@@ -156,10 +157,9 @@ class PlanSchedulerService {
156
157
  /** Re-enqueue BullMQ jobs for all active schedules. Called once at worker startup. */
157
158
  async recoverActiveSchedules(): Promise<void> {
158
159
  const activeSchedules = await databaseService.queryMany(
159
- {
160
- query: `SELECT * FROM ${TABLES.PLAN_SCHEDULE} WHERE status = $status ORDER BY nextFireAt ASC`,
161
- bindings: { status: 'active' },
162
- },
160
+ new BoundQuery(`SELECT * FROM ${TABLES.PLAN_SCHEDULE} WHERE status = $status ORDER BY nextFireAt ASC`, {
161
+ status: 'active',
162
+ }),
163
163
  PlanScheduleRecordSchema,
164
164
  )
165
165
 
@@ -5,17 +5,11 @@ import type { Job, Worker } from 'bullmq'
5
5
 
6
6
  import { chatLogger } from '../config/logger'
7
7
  import { queueJobService } from '../services/queue-job.service'
8
- import { truncateText } from '../utils/string'
9
-
10
8
  export const DEFAULT_JOB_RETENTION = { removeOnComplete: true, removeOnFail: { age: 24 * 60 * 60, count: 200 } }
11
9
  export const LOW_JOB_RETENTION = { removeOnComplete: true, removeOnFail: { age: 6 * 60 * 60, count: 50 } }
12
10
  export const LONG_JOB_LOCK_DURATION_MS = 600_000
13
11
 
14
12
  const DEFAULT_SHUTDOWN_TIMEOUT_MS = 10_000
15
- const MAX_TRACE_STRING_CHARS = 2_000
16
- const MAX_TRACE_ARRAY_ITEMS = 12
17
- const MAX_TRACE_OBJECT_KEYS = 24
18
- const MAX_TRACE_DEPTH = 4
19
13
 
20
14
  export function getWorkerPath(workerName: string): string {
21
15
  return fileURLToPath(new URL(path.join('.', workerName), import.meta.url))
@@ -35,68 +29,6 @@ interface TracedWorkerJobLike {
35
29
  timestamp?: number
36
30
  }
37
31
 
38
- function truncateTraceString(value: string, maxChars = MAX_TRACE_STRING_CHARS): string {
39
- return truncateText(value, maxChars)
40
- }
41
-
42
- function normalizeTraceValue(value: unknown, depth = 0): unknown {
43
- if (value === null || value === undefined) return value
44
- if (typeof value === 'string') return truncateTraceString(value)
45
- if (typeof value === 'number' || typeof value === 'boolean') return value
46
- if (typeof value === 'bigint') return value.toString()
47
- if (typeof value === 'symbol') return value.description ? `Symbol(${value.description})` : 'Symbol()'
48
- if (typeof value === 'function') return value.name ? `[function ${value.name}]` : '[function anonymous]'
49
- if (value instanceof Date) return value.toISOString()
50
-
51
- if (depth >= MAX_TRACE_DEPTH) {
52
- if (Array.isArray(value)) return `[array(${value.length})]`
53
- return '[object]'
54
- }
55
-
56
- if (Array.isArray(value)) {
57
- return value.slice(0, MAX_TRACE_ARRAY_ITEMS).map((item) => normalizeTraceValue(item, depth + 1))
58
- }
59
-
60
- if (!(value instanceof Date) && typeof value === 'object') {
61
- const record = value as Record<string, unknown>
62
- return Object.fromEntries(
63
- Object.entries(record)
64
- .slice(0, MAX_TRACE_OBJECT_KEYS)
65
- .map(([key, entryValue]) => [key, normalizeTraceValue(entryValue, depth + 1)]),
66
- )
67
- }
68
-
69
- return '[unknown]'
70
- }
71
-
72
- function serializeTraceValue(value: unknown): string {
73
- const serialized = JSON.stringify(normalizeTraceValue(value))
74
- return truncateTraceString(serialized || 'null')
75
- }
76
-
77
- function traceTextValue(value: unknown): string {
78
- if (typeof value === 'string') return truncateTraceString(value)
79
- if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
80
- return truncateTraceString(String(value))
81
- }
82
- if (value instanceof Date) return value.toISOString()
83
- return serializeTraceValue(value)
84
- }
85
-
86
- export function buildWorkerObservationMetadata(
87
- queueName: string,
88
- job: { id?: unknown; name: string; attemptsMade: number | null | undefined },
89
- ): Record<string, string> {
90
- return {
91
- queue: traceTextValue(queueName),
92
- job_name: traceTextValue(job.name),
93
- ...(job.id !== undefined ? { job_id: traceTextValue(job.id) } : {}),
94
- ...(job.attemptsMade !== null && job.attemptsMade !== undefined
95
- ? { attempts_made: traceTextValue(job.attemptsMade) }
96
- : {}),
97
- }
98
- }
99
-
100
32
  export const attachWorkerEvents = (worker: Worker, name: string, logger: typeof chatLogger = chatLogger) => {
101
33
  worker.on('ready', () => {
102
34
  logger.info`${name} worker ready`