@lota-sdk/core 0.1.14 → 0.1.15
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 +5 -5
- package/src/ai/embedding-cache.ts +7 -6
- package/src/ai/index.ts +1 -0
- package/src/bifrost/bifrost.ts +12 -7
- package/src/config/agent-defaults.ts +1 -1
- package/src/config/logger.ts +7 -9
- package/src/{runtime.ts → create-runtime.ts} +6 -6
- package/src/db/cursor-pagination.ts +1 -1
- package/src/db/memory-store.ts +10 -6
- package/src/db/memory.ts +6 -4
- package/src/db/schema-fingerprint.ts +1 -0
- package/src/db/service.ts +40 -43
- package/src/db/startup.ts +3 -3
- package/src/index.ts +1 -1
- package/src/queues/context-compaction.queue.ts +4 -8
- package/src/queues/document-processor.queue.ts +7 -7
- package/src/queues/memory-consolidation.queue.ts +7 -8
- package/src/queues/post-chat-memory.queue.ts +2 -6
- package/src/queues/recent-activity-title-refinement.queue.ts +2 -6
- package/src/queues/regular-chat-memory-digest.queue.ts +4 -7
- package/src/queues/skill-extraction.queue.ts +4 -7
- package/src/queues/workstream-title-generation.queue.ts +2 -6
- package/src/redis/connection.ts +6 -3
- package/src/redis/index.ts +1 -0
- package/src/redis/org-memory-lock.ts +1 -1
- package/src/redis/redis-lease-lock.ts +41 -8
- package/src/runtime/agent-stream-helpers.ts +2 -1
- package/src/runtime/context-compaction-constants.ts +1 -1
- package/src/runtime/context-compaction-runtime.ts +6 -4
- package/src/runtime/context-compaction.ts +19 -38
- package/src/runtime/execution-plan.ts +2 -2
- package/src/runtime/helper-model.ts +3 -1
- package/src/runtime/index.ts +12 -1
- package/src/runtime/memory-block.ts +3 -2
- package/src/runtime/memory-pipeline.ts +24 -5
- package/src/runtime/plugin-types.ts +1 -1
- package/src/runtime/runtime-extensions.ts +89 -13
- package/src/runtime/title-helpers.ts +11 -2
- package/src/runtime/workstream-chat-helpers.ts +5 -6
- package/src/runtime/workstream-routing-policy.ts +0 -30
- package/src/runtime/workstream-state.ts +17 -7
- package/src/services/attachment.service.ts +1 -1
- package/src/services/context-compaction.service.ts +3 -3
- package/src/services/document-chunk.service.ts +37 -32
- package/src/services/execution-plan.service.ts +2 -0
- package/src/services/learned-skill.service.ts +6 -10
- package/src/services/{memory.utils.ts → memory-utils.ts} +4 -8
- package/src/services/memory.service.ts +21 -18
- package/src/services/organization-member.service.ts +1 -1
- package/src/services/plan-artifact.service.ts +1 -0
- package/src/services/plan-executor.service.ts +2 -18
- package/src/services/plan-helpers.ts +15 -0
- package/src/services/plan-validator.service.ts +3 -18
- package/src/services/recent-activity-title.service.ts +3 -10
- package/src/services/recent-activity.service.ts +6 -12
- package/src/services/workstream-message.service.ts +26 -16
- package/src/services/workstream-title.service.ts +1 -9
- package/src/services/{workstream-turn-preparation.ts → workstream-turn-preparation.service.ts} +401 -314
- package/src/services/workstream-turn.ts +2 -2
- package/src/services/workstream.service.ts +22 -10
- package/src/services/workstream.types.ts +7 -16
- package/src/storage/attachment-storage.service.ts +4 -4
- package/src/storage/{attachments.utils.ts → attachment-utils.ts} +1 -4
- package/src/storage/index.ts +2 -2
- package/src/system-agents/{context-compacter.agent.ts → context-compaction.agent.ts} +4 -4
- package/src/system-agents/delegated-agent-factory.ts +3 -2
- package/src/system-agents/index.ts +8 -0
- package/src/system-agents/memory-reranker.agent.ts +1 -1
- package/src/system-agents/memory.agent.ts +1 -1
- package/src/system-agents/recent-activity-title-refiner.agent.ts +1 -1
- package/src/tools/execution-plan.tool.ts +6 -2
- package/src/tools/fetch-webpage.tool.ts +20 -18
- package/src/tools/index.ts +2 -2
- package/src/tools/read-file-parts.tool.ts +1 -1
- package/src/tools/search-web.tool.ts +18 -15
- package/src/tools/{search-tools.ts → search.tool.ts} +1 -1
- package/src/tools/team-think.tool.ts +9 -5
- package/src/tools/{tool-contract.ts → tool-contracts.ts} +9 -2
- package/src/utils/async.ts +1 -1
- package/src/utils/errors.ts +15 -0
- package/src/utils/hono-error-handler.ts +1 -2
- package/src/utils/index.ts +10 -2
- package/src/utils/string.ts +14 -0
- package/src/workers/bootstrap.ts +2 -2
- package/src/workers/memory-consolidation.worker.ts +12 -12
- package/src/workers/regular-chat-memory-digest.helpers.ts +2 -7
- package/src/workers/regular-chat-memory-digest.runner.ts +9 -103
- package/src/workers/skill-extraction.runner.ts +7 -101
- package/src/workers/utils/file-section-chunker.ts +5 -3
- package/src/workers/utils/workstream-message-query.ts +106 -0
- package/src/workers/worker-utils.ts +4 -0
- package/src/runtime/retrieval-pipeline.ts +0 -3
- package/src/utils/error.ts +0 -10
- /package/src/services/{context-compaction-runtime.ts → context-compaction-runtime.singleton.ts} +0 -0
- /package/src/storage/{attachments.types.ts → attachment-types.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lota-sdk/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -28,19 +28,19 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@ai-sdk/devtools": "^0.0.15",
|
|
31
|
-
"@ai-sdk/openai": "^3.0.
|
|
31
|
+
"@ai-sdk/openai": "^3.0.47",
|
|
32
32
|
"@logtape/logtape": "^2.0.4",
|
|
33
|
-
"@lota-sdk/shared": "0.1.
|
|
33
|
+
"@lota-sdk/shared": "0.1.15",
|
|
34
34
|
"@mendable/firecrawl-js": "^4.16.0",
|
|
35
35
|
"@surrealdb/node": "^3.0.3",
|
|
36
|
-
"ai": "^6.0.
|
|
36
|
+
"ai": "^6.0.134",
|
|
37
37
|
"bullmq": "^5.71.0",
|
|
38
38
|
"hono": "^4.12.8",
|
|
39
39
|
"ioredis": "5.9.3",
|
|
40
40
|
"mammoth": "^1.12.0",
|
|
41
41
|
"pdf-parse": "^2.4.5",
|
|
42
42
|
"resumable-stream": "^2.2.12",
|
|
43
|
-
"surrealdb": "^2.0.
|
|
43
|
+
"surrealdb": "^2.0.3",
|
|
44
44
|
"zod": "^4.3.6"
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -4,17 +4,18 @@ import type IORedis from 'ioredis'
|
|
|
4
4
|
|
|
5
5
|
import { aiLogger } from '../config/logger'
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
export const DEFAULT_EMBEDDING_CACHE_TTL_SECONDS = 3600
|
|
8
|
+
const EMBEDDING_CACHE_KEY_PREFIX = 'emb'
|
|
8
9
|
|
|
9
10
|
export class EmbeddingCache {
|
|
10
11
|
constructor(
|
|
11
12
|
private redis: IORedis,
|
|
12
|
-
private ttlSeconds: number =
|
|
13
|
+
private ttlSeconds: number = DEFAULT_EMBEDDING_CACHE_TTL_SECONDS,
|
|
13
14
|
) {}
|
|
14
15
|
|
|
15
16
|
private buildKey(model: string, text: string): string {
|
|
16
17
|
const hash = createHash('sha256').update(text).digest('hex')
|
|
17
|
-
return
|
|
18
|
+
return `${EMBEDDING_CACHE_KEY_PREFIX}:${model}:${hash}`
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
async get(model: string, text: string): Promise<number[] | null> {
|
|
@@ -23,7 +24,7 @@ export class EmbeddingCache {
|
|
|
23
24
|
if (!cached) return null
|
|
24
25
|
return JSON.parse(cached.toString()) as number[]
|
|
25
26
|
} catch (error) {
|
|
26
|
-
aiLogger.
|
|
27
|
+
aiLogger.warn`Embedding cache get failed: ${error}`
|
|
27
28
|
return null
|
|
28
29
|
}
|
|
29
30
|
}
|
|
@@ -32,7 +33,7 @@ export class EmbeddingCache {
|
|
|
32
33
|
try {
|
|
33
34
|
await this.redis.set(this.buildKey(model, text), JSON.stringify(embedding), 'EX', this.ttlSeconds)
|
|
34
35
|
} catch (error) {
|
|
35
|
-
aiLogger.
|
|
36
|
+
aiLogger.warn`Embedding cache set failed: ${error}`
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
}
|
|
@@ -40,7 +41,7 @@ export class EmbeddingCache {
|
|
|
40
41
|
let embeddingCacheInstance: EmbeddingCache | null = null
|
|
41
42
|
|
|
42
43
|
export function configureEmbeddingCache(redis: IORedis, ttlSeconds?: number): void {
|
|
43
|
-
embeddingCacheInstance = new EmbeddingCache(redis, ttlSeconds ??
|
|
44
|
+
embeddingCacheInstance = new EmbeddingCache(redis, ttlSeconds ?? DEFAULT_EMBEDDING_CACHE_TTL_SECONDS)
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
export function getEmbeddingCache(): EmbeddingCache | null {
|
package/src/ai/index.ts
CHANGED
package/src/bifrost/bifrost.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { LanguageModelMiddleware } from 'ai'
|
|
|
5
5
|
|
|
6
6
|
import { isRecord, readString } from '../utils/string'
|
|
7
7
|
|
|
8
|
+
type BifrostLanguageModel = Parameters<typeof wrapLanguageModel>[0]['model']
|
|
8
9
|
type BifrostExtraParams = Record<string, unknown>
|
|
9
10
|
type BifrostChatResponse = { body?: unknown }
|
|
10
11
|
type BifrostTransformParamsOptions = Parameters<NonNullable<LanguageModelMiddleware['transformParams']>>[0]
|
|
@@ -131,7 +132,7 @@ export function injectBifrostChatReasoningStream(
|
|
|
131
132
|
const closeReasoning = () => {
|
|
132
133
|
if (!reasoningOpen || reasoningClosed) return
|
|
133
134
|
|
|
134
|
-
controller.enqueue({ type: 'reasoning-end', id: reasoningId }
|
|
135
|
+
controller.enqueue({ type: 'reasoning-end', id: reasoningId } satisfies BifrostStreamPart)
|
|
135
136
|
reasoningClosed = true
|
|
136
137
|
}
|
|
137
138
|
|
|
@@ -141,11 +142,15 @@ export function injectBifrostChatReasoningStream(
|
|
|
141
142
|
|
|
142
143
|
if (reasoningDelta) {
|
|
143
144
|
if (!reasoningOpen) {
|
|
144
|
-
controller.enqueue({ type: 'reasoning-start', id: reasoningId }
|
|
145
|
+
controller.enqueue({ type: 'reasoning-start', id: reasoningId } satisfies BifrostStreamPart)
|
|
145
146
|
reasoningOpen = true
|
|
146
147
|
}
|
|
147
148
|
|
|
148
|
-
controller.enqueue({
|
|
149
|
+
controller.enqueue({
|
|
150
|
+
type: 'reasoning-delta',
|
|
151
|
+
id: reasoningId,
|
|
152
|
+
delta: reasoningDelta,
|
|
153
|
+
} satisfies BifrostStreamPart)
|
|
149
154
|
}
|
|
150
155
|
return
|
|
151
156
|
}
|
|
@@ -158,7 +163,7 @@ export function injectBifrostChatReasoningStream(
|
|
|
158
163
|
},
|
|
159
164
|
flush(controller) {
|
|
160
165
|
if (!reasoningOpen || reasoningClosed) return
|
|
161
|
-
controller.enqueue({ type: 'reasoning-end', id: reasoningId }
|
|
166
|
+
controller.enqueue({ type: 'reasoning-end', id: reasoningId } satisfies BifrostStreamPart)
|
|
162
167
|
},
|
|
163
168
|
}),
|
|
164
169
|
)
|
|
@@ -182,7 +187,7 @@ export function injectBifrostResponsesReasoningStream(
|
|
|
182
187
|
id: reasoningDelta.id,
|
|
183
188
|
delta: reasoningDelta.delta,
|
|
184
189
|
providerMetadata: { openai: { itemId: reasoningDelta.itemId } },
|
|
185
|
-
}
|
|
190
|
+
} satisfies BifrostStreamPart)
|
|
186
191
|
},
|
|
187
192
|
}),
|
|
188
193
|
)
|
|
@@ -242,12 +247,12 @@ function createBifrostProvider(extraParams?: BifrostExtraParams) {
|
|
|
242
247
|
})
|
|
243
248
|
}
|
|
244
249
|
|
|
245
|
-
function withBifrostDevTools<TModel>(model: TModel): TModel {
|
|
250
|
+
function withBifrostDevTools<TModel extends BifrostLanguageModel>(model: TModel): TModel {
|
|
246
251
|
if (process.env.NODE_ENV === 'production') {
|
|
247
252
|
return model
|
|
248
253
|
}
|
|
249
254
|
|
|
250
|
-
return wrapLanguageModel({ model
|
|
255
|
+
return wrapLanguageModel({ model, middleware: devToolsMiddleware() }) as TModel
|
|
251
256
|
}
|
|
252
257
|
|
|
253
258
|
let provider: ReturnType<typeof createOpenAI> | null = null
|
package/src/config/logger.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { LogLevel } from '@logtape/logtape'
|
|
2
2
|
import { configure, getAnsiColorFormatter, getConsoleSink, getLogger as getLogTapeLogger } from '@logtape/logtape'
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const LOG_CATEGORY = 'lota-sdk'
|
|
5
|
+
|
|
6
|
+
export async function configureLotaLogger(logLevel: LogLevel = 'info'): Promise<void> {
|
|
5
7
|
const formatter = getAnsiColorFormatter({ level: 'FULL' })
|
|
6
8
|
|
|
7
9
|
await configure({
|
|
@@ -10,7 +12,7 @@ export async function configureLotaLogger(logLevel: LogLevel): Promise<void> {
|
|
|
10
12
|
loggers: [
|
|
11
13
|
{ category: ['logtape', 'meta'], lowestLevel: 'warning', sinks: ['console'] },
|
|
12
14
|
{ category: ['server'], lowestLevel: logLevel, sinks: ['console'] },
|
|
13
|
-
{ category: [
|
|
15
|
+
{ category: [LOG_CATEGORY], lowestLevel: logLevel, sinks: ['console'] },
|
|
14
16
|
{ category: ['hono'], lowestLevel: logLevel, sinks: ['console'] },
|
|
15
17
|
],
|
|
16
18
|
})
|
|
@@ -20,10 +22,6 @@ export function getLogger(category: readonly string[]) {
|
|
|
20
22
|
return getLogTapeLogger([...category])
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
export
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
export const serverLogger = getLogger(['lota-sdk'])
|
|
28
|
-
export const chatLogger = getLogger(['lota-sdk', 'chat'])
|
|
29
|
-
export const aiLogger = getLogger(['lota-sdk', 'ai'])
|
|
25
|
+
export const serverLogger = getLogger([LOG_CATEGORY])
|
|
26
|
+
export const chatLogger = getLogger([LOG_CATEGORY, 'chat'])
|
|
27
|
+
export const aiLogger = getLogger([LOG_CATEGORY, 'ai'])
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import type { ChatMessage } from '@lota-sdk/shared'
|
|
2
|
+
|
|
1
3
|
import { configureEmbeddingCache } from './ai/embedding-cache'
|
|
2
4
|
import { configureAgentFactory, configureAgents } from './config/agent-defaults'
|
|
3
5
|
import { configureBackgroundProcessing } from './config/background-processing'
|
|
4
|
-
import {
|
|
6
|
+
import { configureLotaLogger } from './config/logger'
|
|
5
7
|
import { configureWorkstreams } from './config/workstream-defaults'
|
|
6
8
|
import { ensureRecordId } from './db/record-id'
|
|
7
9
|
import { computeSchemaFingerprint } from './db/schema-fingerprint'
|
|
@@ -76,7 +78,6 @@ type UnarchiveSdkWorkstream = (
|
|
|
76
78
|
export interface LotaRuntime {
|
|
77
79
|
services: {
|
|
78
80
|
database: SurrealDBService
|
|
79
|
-
databaseService: SurrealDBService
|
|
80
81
|
redis: RedisConnectionManager
|
|
81
82
|
closeRedisConnection: () => Promise<void>
|
|
82
83
|
attachmentService: typeof attachmentService
|
|
@@ -132,7 +133,7 @@ export interface LotaRuntime {
|
|
|
132
133
|
delete: typeof workstreamServiceSingleton.deleteWorkstream
|
|
133
134
|
stop: typeof workstreamServiceSingleton.stopActiveRun
|
|
134
135
|
listMessages: typeof workstreamMessageServiceSingleton.listMessageHistoryPage
|
|
135
|
-
getMessage: (params: { workstreamId: string; messageId: string }) => Promise<
|
|
136
|
+
getMessage: (params: { workstreamId: string; messageId: string }) => Promise<ChatMessage>
|
|
136
137
|
sendMessage: (params: {
|
|
137
138
|
workstreamId: string
|
|
138
139
|
organizationId: string
|
|
@@ -170,7 +171,7 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
|
|
|
170
171
|
const resolvedConfig = parseLotaRuntimeConfig(config)
|
|
171
172
|
configureRuntimeConfig(resolvedConfig)
|
|
172
173
|
|
|
173
|
-
await
|
|
174
|
+
await configureLotaLogger(resolvedConfig.logging.level)
|
|
174
175
|
|
|
175
176
|
const db = new SurrealDBServiceClass({
|
|
176
177
|
url: resolvedConfig.database.url,
|
|
@@ -306,7 +307,6 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
|
|
|
306
307
|
return {
|
|
307
308
|
services: {
|
|
308
309
|
database: db,
|
|
309
|
-
databaseService: db,
|
|
310
310
|
redis: redisManager,
|
|
311
311
|
closeRedisConnection: async () => await redisManager.closeConnection(),
|
|
312
312
|
attachmentService: attachmentServiceSingleton,
|
|
@@ -375,7 +375,7 @@ function getBuiltInSchemaFiles(): URL[] {
|
|
|
375
375
|
function createPluginDatabaseConnector(pluginRuntime: Record<string, LotaPlugin>): () => Promise<void> {
|
|
376
376
|
return async () => {
|
|
377
377
|
for (const plugin of Object.values(pluginRuntime)) {
|
|
378
|
-
const services = plugin.services
|
|
378
|
+
const services = plugin.services
|
|
379
379
|
const connectDatabase = services.connectDatabase
|
|
380
380
|
if (typeof connectDatabase !== 'function') {
|
|
381
381
|
continue
|
|
@@ -64,7 +64,7 @@ async function listRowsBefore(
|
|
|
64
64
|
throw new Error(`Cursor message not found in ${config.table}: ${params.beforeMessageId}`)
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
const cursorCreatedAt = new Date(toTimestamp(cursorRow.createdAt))
|
|
67
|
+
const cursorCreatedAt = new Date(toTimestamp(cursorRow.createdAt) ?? Date.now())
|
|
68
68
|
const cursorId = config.toRowId(params.parentId, params.beforeMessageId)
|
|
69
69
|
|
|
70
70
|
return await databaseService.query<unknown>(
|
package/src/db/memory-store.ts
CHANGED
|
@@ -24,6 +24,10 @@ const MEMORY_TABLE = TABLES.MEMORY
|
|
|
24
24
|
const MEMORY_HISTORY_TABLE = TABLES.MEMORY_HISTORY
|
|
25
25
|
const MEMORY_RELATION_TABLE = TABLES.MEMORY_RELATION
|
|
26
26
|
const MIN_RELEVANCE_SCORE = 0.25
|
|
27
|
+
const STRONG_GRAPH_BOOSTS = { support: 0.1, contradict: 0.2 } as const
|
|
28
|
+
const WEAK_GRAPH_BOOSTS = { support: 0.05, contradict: 0.1 } as const
|
|
29
|
+
const CANDIDATE_FANOUT_MULTIPLIER = 4
|
|
30
|
+
const CANDIDATE_SLICE_FLOOR = 50
|
|
27
31
|
const TOUCH_MEMORIES_MAX_ATTEMPTS = 4
|
|
28
32
|
const TOUCH_MEMORIES_RETRY_BASE_DELAY_MS = 25
|
|
29
33
|
const TOUCH_MEMORIES_RETRY_JITTER_MS = 20
|
|
@@ -153,7 +157,7 @@ export class SurrealMemoryStore {
|
|
|
153
157
|
relationCounts,
|
|
154
158
|
options.limit,
|
|
155
159
|
(row) => 1 / (1 + row.distance),
|
|
156
|
-
|
|
160
|
+
STRONG_GRAPH_BOOSTS,
|
|
157
161
|
MIN_RELEVANCE_SCORE,
|
|
158
162
|
)
|
|
159
163
|
|
|
@@ -317,7 +321,7 @@ export class SurrealMemoryStore {
|
|
|
317
321
|
if (b.textScore !== a.textScore) return b.textScore - a.textScore
|
|
318
322
|
return a.index - b.index
|
|
319
323
|
})
|
|
320
|
-
.slice(0, Math.max(options.limit *
|
|
324
|
+
.slice(0, Math.max(options.limit * CANDIDATE_FANOUT_MULTIPLIER, CANDIDATE_SLICE_FLOOR))
|
|
321
325
|
|
|
322
326
|
if (options.fastMode) {
|
|
323
327
|
return this.mapFastRows(scoredRows, options.limit, (row) => row.textScore)
|
|
@@ -330,7 +334,7 @@ export class SurrealMemoryStore {
|
|
|
330
334
|
recentRelationCounts,
|
|
331
335
|
options.limit,
|
|
332
336
|
(row) => row.textScore,
|
|
333
|
-
|
|
337
|
+
WEAK_GRAPH_BOOSTS,
|
|
334
338
|
MIN_RELEVANCE_SCORE,
|
|
335
339
|
)
|
|
336
340
|
|
|
@@ -483,7 +487,7 @@ export class SurrealMemoryStore {
|
|
|
483
487
|
relationCounts,
|
|
484
488
|
limit,
|
|
485
489
|
(row) => 1 / (1 + row.distance),
|
|
486
|
-
|
|
490
|
+
STRONG_GRAPH_BOOSTS,
|
|
487
491
|
MIN_RELEVANCE_SCORE,
|
|
488
492
|
)
|
|
489
493
|
|
|
@@ -520,7 +524,7 @@ export class SurrealMemoryStore {
|
|
|
520
524
|
relationCounts,
|
|
521
525
|
limit,
|
|
522
526
|
(row) => row.rrfScore,
|
|
523
|
-
|
|
527
|
+
WEAK_GRAPH_BOOSTS,
|
|
524
528
|
MIN_RELEVANCE_SCORE,
|
|
525
529
|
)
|
|
526
530
|
|
|
@@ -619,7 +623,7 @@ export class SurrealMemoryStore {
|
|
|
619
623
|
relationCounts,
|
|
620
624
|
options.limit,
|
|
621
625
|
(row) => row.linearScore,
|
|
622
|
-
|
|
626
|
+
WEAK_GRAPH_BOOSTS,
|
|
623
627
|
MIN_RELEVANCE_SCORE,
|
|
624
628
|
)
|
|
625
629
|
|
package/src/db/memory.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { getFactRetrievalMessages } from '../runtime/memory-prompts-fact'
|
|
|
12
12
|
import { parseMessages } from '../runtime/memory-prompts-parse'
|
|
13
13
|
import { getClassifyMemoryDeltaPrompt } from '../runtime/memory-prompts-update'
|
|
14
14
|
import { getRuntimeConfig } from '../runtime/runtime-config'
|
|
15
|
+
import { compactWhitespace } from '../utils/string'
|
|
15
16
|
import type { SurrealMemoryStore } from './memory-store'
|
|
16
17
|
import { getDefaultMemoryStore } from './memory-store'
|
|
17
18
|
import { hashContent, isUniqueIndexConflict } from './memory-store.helpers'
|
|
@@ -39,6 +40,7 @@ const MEMORY_DELTA_MAX_CANDIDATE_MEMORIES = 80
|
|
|
39
40
|
const MEMORY_DELTA_MAX_CANDIDATES_PER_FACT = 10
|
|
40
41
|
const MEMORY_DELTA_MIN_BASELINE_CANDIDATES = 12
|
|
41
42
|
const MEMORY_DELTA_MEMORY_TEXT_MAX_CHARS = 400
|
|
43
|
+
const CANDIDATE_FANOUT_MULTIPLIER = 4
|
|
42
44
|
const helperModelRuntime = createHelperModelRuntime()
|
|
43
45
|
|
|
44
46
|
interface PreparedScopeUpdate {
|
|
@@ -277,7 +279,7 @@ export class Memory {
|
|
|
277
279
|
typeof extractionOptions?.maxFacts === 'number' ? { maxFacts: extractionOptions.maxFacts } : {},
|
|
278
280
|
)
|
|
279
281
|
} catch (error) {
|
|
280
|
-
aiLogger.
|
|
282
|
+
aiLogger.warn`Failed to extract facts: ${error}`
|
|
281
283
|
return []
|
|
282
284
|
}
|
|
283
285
|
}
|
|
@@ -313,7 +315,7 @@ export class Memory {
|
|
|
313
315
|
}
|
|
314
316
|
|
|
315
317
|
private normalizeMemoryDeltaText(value: string, maxChars?: number): string {
|
|
316
|
-
const normalized = value
|
|
318
|
+
const normalized = compactWhitespace(value)
|
|
317
319
|
if (!normalized) return ''
|
|
318
320
|
if (typeof maxChars !== 'number' || normalized.length <= maxChars) return normalized
|
|
319
321
|
return `${normalized.slice(0, maxChars - 3)}...`
|
|
@@ -384,7 +386,7 @@ export class Memory {
|
|
|
384
386
|
const targetCandidateCount = Math.min(MEMORY_DELTA_MAX_CANDIDATE_MEMORIES, normalizedExisting.length)
|
|
385
387
|
const baselineCount = Math.min(
|
|
386
388
|
targetCandidateCount,
|
|
387
|
-
Math.max(MEMORY_DELTA_MIN_BASELINE_CANDIDATES, newFacts.length *
|
|
389
|
+
Math.max(MEMORY_DELTA_MIN_BASELINE_CANDIDATES, newFacts.length * CANDIDATE_FANOUT_MULTIPLIER),
|
|
388
390
|
)
|
|
389
391
|
|
|
390
392
|
const selectedIds = new Set<string>(
|
|
@@ -502,7 +504,7 @@ export class Memory {
|
|
|
502
504
|
await this.store.addRelation(fromId, toId, relation.relation)
|
|
503
505
|
aiLogger.debug`Created ${relation.relation} relation: ${fromId} -> ${toId}`
|
|
504
506
|
} catch (error) {
|
|
505
|
-
aiLogger.warn`Failed to create relation: ${error}`
|
|
507
|
+
aiLogger.warn`Failed to create memory relation (non-fatal, graph may be incomplete): ${error}`
|
|
506
508
|
}
|
|
507
509
|
}
|
|
508
510
|
}
|
|
@@ -7,6 +7,7 @@ function toSchemaFilePath(value: string | URL): string {
|
|
|
7
7
|
export async function computeSchemaFingerprint(schemaFiles: readonly (string | URL)[]): Promise<string> {
|
|
8
8
|
const hash = createHash('sha256')
|
|
9
9
|
|
|
10
|
+
// Sequential reads required: hash must be computed in deterministic file order
|
|
10
11
|
for (const schemaFile of schemaFiles) {
|
|
11
12
|
const sortKey = toSchemaFilePath(schemaFile)
|
|
12
13
|
const file = schemaFile instanceof URL ? Bun.file(schemaFile.pathname) : Bun.file(schemaFile)
|
package/src/db/service.ts
CHANGED
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
import type { ExprLike, Mutation, SurrealTransaction, Values } from 'surrealdb'
|
|
13
13
|
import type { z } from 'zod'
|
|
14
14
|
|
|
15
|
+
import { withTimeout } from '../utils/async'
|
|
16
|
+
import { isRecord } from '../utils/string'
|
|
15
17
|
import type { RecordIdInput } from './record-id'
|
|
16
18
|
import { ensureRecordId, readCustomStringValue } from './record-id'
|
|
17
19
|
import type { DatabaseTable } from './tables'
|
|
@@ -88,12 +90,8 @@ function configureMutation(
|
|
|
88
90
|
return builder.merge(data)
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
function isRecordValue(value: unknown): value is Record<string, unknown> {
|
|
92
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
93
|
function isBoundQueryLike(value: unknown): value is BoundQueryLike {
|
|
96
|
-
if (!
|
|
94
|
+
if (!isRecord(value)) {
|
|
97
95
|
return false
|
|
98
96
|
}
|
|
99
97
|
|
|
@@ -101,7 +99,7 @@ function isBoundQueryLike(value: unknown): value is BoundQueryLike {
|
|
|
101
99
|
return false
|
|
102
100
|
}
|
|
103
101
|
|
|
104
|
-
return value.bindings === undefined ||
|
|
102
|
+
return value.bindings === undefined || isRecord(value.bindings)
|
|
105
103
|
}
|
|
106
104
|
|
|
107
105
|
function toStringLikeValue(value: unknown): string | null {
|
|
@@ -122,23 +120,6 @@ const CONNECT_RETRY_BASE_DELAY_MS = 100
|
|
|
122
120
|
const CONNECT_RETRY_JITTER_MS = 50
|
|
123
121
|
const CONNECT_ATTEMPT_TIMEOUT_MS = 5_000
|
|
124
122
|
|
|
125
|
-
async function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string): Promise<T> {
|
|
126
|
-
let timeoutId: ReturnType<typeof setTimeout> | undefined
|
|
127
|
-
|
|
128
|
-
try {
|
|
129
|
-
return await Promise.race([
|
|
130
|
-
promise,
|
|
131
|
-
new Promise<T>((_, reject) => {
|
|
132
|
-
timeoutId = setTimeout(() => reject(new Error(message)), timeoutMs)
|
|
133
|
-
}),
|
|
134
|
-
])
|
|
135
|
-
} finally {
|
|
136
|
-
if (timeoutId) {
|
|
137
|
-
clearTimeout(timeoutId)
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
123
|
export class SurrealDBService {
|
|
143
124
|
private client: Surreal | null = null
|
|
144
125
|
private isConnected = false
|
|
@@ -252,7 +233,7 @@ export class SurrealDBService {
|
|
|
252
233
|
: { username: this.config.username ?? '', password: this.config.password ?? '' },
|
|
253
234
|
}),
|
|
254
235
|
CONNECT_ATTEMPT_TIMEOUT_MS,
|
|
255
|
-
`
|
|
236
|
+
`SurrealDB connect (${this.config.url})`,
|
|
256
237
|
)
|
|
257
238
|
|
|
258
239
|
this.isConnected = true
|
|
@@ -278,7 +259,7 @@ export class SurrealDBService {
|
|
|
278
259
|
}
|
|
279
260
|
}
|
|
280
261
|
|
|
281
|
-
this.toSurrealError(lastError)
|
|
262
|
+
return this.toSurrealError(lastError)
|
|
282
263
|
})()
|
|
283
264
|
|
|
284
265
|
try {
|
|
@@ -327,8 +308,16 @@ export class SurrealDBService {
|
|
|
327
308
|
|
|
328
309
|
private normalizeRecordId(id: unknown, table: DatabaseTable): ReturnType<typeof ensureRecordId> {
|
|
329
310
|
try {
|
|
330
|
-
|
|
311
|
+
const recordId = ensureRecordId(id as RecordIdInput, table)
|
|
312
|
+
const resolvedTable = String(recordId.table)
|
|
313
|
+
if (resolvedTable !== table) {
|
|
314
|
+
throw new SurrealDBError(`Record id table mismatch: expected "${table}" but got "${resolvedTable}"`)
|
|
315
|
+
}
|
|
316
|
+
return recordId
|
|
331
317
|
} catch (error) {
|
|
318
|
+
if (error instanceof SurrealDBError) {
|
|
319
|
+
throw error
|
|
320
|
+
}
|
|
332
321
|
if (error instanceof Error) {
|
|
333
322
|
throw new SurrealDBError(`Invalid record id for table "${table}": ${error.message}`, undefined, {
|
|
334
323
|
cause: error,
|
|
@@ -392,7 +381,7 @@ export class SurrealDBService {
|
|
|
392
381
|
return value.map((entry) => this.normalizeRuntimeValue(entry))
|
|
393
382
|
}
|
|
394
383
|
|
|
395
|
-
if (!
|
|
384
|
+
if (!isRecord(value)) {
|
|
396
385
|
return value
|
|
397
386
|
}
|
|
398
387
|
|
|
@@ -413,6 +402,8 @@ export class SurrealDBService {
|
|
|
413
402
|
return Object.fromEntries(entries.map(([key, entryValue]) => [key, this.normalizeRuntimeValue(entryValue)]))
|
|
414
403
|
}
|
|
415
404
|
|
|
405
|
+
// Cast is safe: normalizeRuntimeValue preserves Record shape when input is a Record
|
|
406
|
+
// (non-array objects are mapped entry-by-entry and returned as Object.fromEntries)
|
|
416
407
|
private normalizeBindings(bindings?: Record<string, unknown>): Record<string, unknown> | undefined {
|
|
417
408
|
if (!bindings) {
|
|
418
409
|
return undefined
|
|
@@ -421,6 +412,7 @@ export class SurrealDBService {
|
|
|
421
412
|
return this.normalizeRuntimeValue(bindings) as Record<string, unknown>
|
|
422
413
|
}
|
|
423
414
|
|
|
415
|
+
// Cast is safe: normalizeRuntimeValue preserves Record shape when input is a Record
|
|
424
416
|
private normalizeMutationData(data: Record<string, unknown>): Record<string, unknown> {
|
|
425
417
|
const normalized = this.normalizeRuntimeValue(data) as Record<string, unknown>
|
|
426
418
|
return this.stripNullValues(normalized)
|
|
@@ -512,9 +504,11 @@ export class SurrealDBService {
|
|
|
512
504
|
create: (target: unknown) => {
|
|
513
505
|
const normalizedTarget = this.normalizeCreateTarget(target)
|
|
514
506
|
const builder = normalizedTarget instanceof Table ? tx.create(normalizedTarget) : tx.create(normalizedTarget)
|
|
507
|
+
// Cast needed: SurrealDB SDK transaction builder type differs nominally from internal CreateMutationBuilder interface
|
|
515
508
|
return this.wrapCreateBuilder(builder as unknown as CreateMutationBuilder)
|
|
516
509
|
},
|
|
517
510
|
update: (target: unknown) =>
|
|
511
|
+
// Cast needed: SurrealDB SDK transaction builder type differs nominally from internal MutationBuilder interface
|
|
518
512
|
this.wrapMutationBuilder(tx.update(ensureRecordId(target as RecordIdInput)) as unknown as MutationBuilder),
|
|
519
513
|
delete: (target: unknown) => tx.delete(ensureRecordId(target as RecordIdInput)),
|
|
520
514
|
relate: (from: unknown, edgeTable: unknown, to: unknown, data?: Values<Record<string, unknown>>) =>
|
|
@@ -556,7 +550,7 @@ export class SurrealDBService {
|
|
|
556
550
|
return this.normalizeQueryRows<T>(response.result, schema)
|
|
557
551
|
})
|
|
558
552
|
} catch (error) {
|
|
559
|
-
this.toSurrealError(error, queryText)
|
|
553
|
+
return this.toSurrealError(error, queryText)
|
|
560
554
|
}
|
|
561
555
|
}
|
|
562
556
|
|
|
@@ -604,7 +598,7 @@ export class SurrealDBService {
|
|
|
604
598
|
const first = rows.at(0)
|
|
605
599
|
return first ? schema.parse(first) : null
|
|
606
600
|
} catch (error) {
|
|
607
|
-
this.toSurrealError(error, `SELECT * FROM ${table} LIMIT 1`)
|
|
601
|
+
return this.toSurrealError(error, `SELECT * FROM ${table} LIMIT 1`)
|
|
608
602
|
}
|
|
609
603
|
}
|
|
610
604
|
|
|
@@ -656,7 +650,7 @@ export class SurrealDBService {
|
|
|
656
650
|
const rows = await query
|
|
657
651
|
return rows.map((row) => schema.parse(row))
|
|
658
652
|
} catch (error) {
|
|
659
|
-
this.toSurrealError(error, `SELECT * FROM ${table}`)
|
|
653
|
+
return this.toSurrealError(error, `SELECT * FROM ${table}`)
|
|
660
654
|
}
|
|
661
655
|
}
|
|
662
656
|
|
|
@@ -685,7 +679,7 @@ export class SurrealDBService {
|
|
|
685
679
|
|
|
686
680
|
return schema.parse(first)
|
|
687
681
|
} catch (error) {
|
|
688
|
-
this.toSurrealError(error, `CREATE ${table}`)
|
|
682
|
+
return this.toSurrealError(error, `CREATE ${table}`)
|
|
689
683
|
}
|
|
690
684
|
}
|
|
691
685
|
|
|
@@ -707,7 +701,7 @@ export class SurrealDBService {
|
|
|
707
701
|
const created = await client.create<unknown>(recordId).content(this.normalizeMutationData(data)).output('after')
|
|
708
702
|
return schema.parse(created)
|
|
709
703
|
} catch (error) {
|
|
710
|
-
this.toSurrealError(error, `CREATE ${recordId.toString()}`)
|
|
704
|
+
return this.toSurrealError(error, `CREATE ${recordId.toString()}`)
|
|
711
705
|
}
|
|
712
706
|
}
|
|
713
707
|
|
|
@@ -734,7 +728,7 @@ export class SurrealDBService {
|
|
|
734
728
|
const updated = await configured.output('after')
|
|
735
729
|
return updated ? schema.parse(updated) : null
|
|
736
730
|
} catch (error) {
|
|
737
|
-
this.toSurrealError(error, `UPDATE ${recordId.toString()}`)
|
|
731
|
+
return this.toSurrealError(error, `UPDATE ${recordId.toString()}`)
|
|
738
732
|
}
|
|
739
733
|
}
|
|
740
734
|
|
|
@@ -763,7 +757,7 @@ export class SurrealDBService {
|
|
|
763
757
|
}
|
|
764
758
|
return schema.parse(upserted)
|
|
765
759
|
} catch (error) {
|
|
766
|
-
this.toSurrealError(error, `UPSERT ${recordId.toString()}`)
|
|
760
|
+
return this.toSurrealError(error, `UPSERT ${recordId.toString()}`)
|
|
767
761
|
}
|
|
768
762
|
}
|
|
769
763
|
|
|
@@ -779,7 +773,7 @@ export class SurrealDBService {
|
|
|
779
773
|
await client.delete<unknown>(recordId).output('before')
|
|
780
774
|
return true
|
|
781
775
|
} catch (error) {
|
|
782
|
-
this.toSurrealError(error, `DELETE ${recordId.toString()}`)
|
|
776
|
+
return this.toSurrealError(error, `DELETE ${recordId.toString()}`)
|
|
783
777
|
}
|
|
784
778
|
}
|
|
785
779
|
|
|
@@ -809,7 +803,7 @@ export class SurrealDBService {
|
|
|
809
803
|
|
|
810
804
|
return matched.length
|
|
811
805
|
} catch (error) {
|
|
812
|
-
this.toSurrealError(error, `DELETE ${table} WHERE ...`)
|
|
806
|
+
return this.toSurrealError(error, `DELETE ${table} WHERE ...`)
|
|
813
807
|
}
|
|
814
808
|
}
|
|
815
809
|
|
|
@@ -835,7 +829,7 @@ export class SurrealDBService {
|
|
|
835
829
|
}
|
|
836
830
|
return 1
|
|
837
831
|
} catch (error) {
|
|
838
|
-
this.toSurrealError(error, `UPDATE ${table} WHERE ...`)
|
|
832
|
+
return this.toSurrealError(error, `UPDATE ${table} WHERE ...`)
|
|
839
833
|
}
|
|
840
834
|
}
|
|
841
835
|
|
|
@@ -854,7 +848,7 @@ export class SurrealDBService {
|
|
|
854
848
|
.output('after')
|
|
855
849
|
return inserted as T[]
|
|
856
850
|
} catch (error) {
|
|
857
|
-
this.toSurrealError(error, `INSERT ${table}`)
|
|
851
|
+
return this.toSurrealError(error, `INSERT ${table}`)
|
|
858
852
|
}
|
|
859
853
|
}
|
|
860
854
|
|
|
@@ -884,7 +878,7 @@ export class SurrealDBService {
|
|
|
884
878
|
}
|
|
885
879
|
return related
|
|
886
880
|
} catch (error) {
|
|
887
|
-
this.toSurrealError(error, `RELATE ${fromRef.toString()}->${edgeTable}->${toRef.toString()}`)
|
|
881
|
+
return this.toSurrealError(error, `RELATE ${fromRef.toString()}->${edgeTable}->${toRef.toString()}`)
|
|
888
882
|
}
|
|
889
883
|
}
|
|
890
884
|
|
|
@@ -893,7 +887,7 @@ export class SurrealDBService {
|
|
|
893
887
|
try {
|
|
894
888
|
return this.wrapTransaction(await client.beginTransaction())
|
|
895
889
|
} catch (error) {
|
|
896
|
-
this.toSurrealError(error, 'BEGIN TRANSACTION')
|
|
890
|
+
return this.toSurrealError(error, 'BEGIN TRANSACTION')
|
|
897
891
|
}
|
|
898
892
|
}
|
|
899
893
|
|
|
@@ -942,10 +936,13 @@ export const databaseService = new Proxy({} as SurrealDBService, {
|
|
|
942
936
|
return databaseServiceOverrides.get(property)
|
|
943
937
|
}
|
|
944
938
|
|
|
945
|
-
const
|
|
946
|
-
const value =
|
|
939
|
+
const resolved = resolveConfiguredDatabaseService()
|
|
940
|
+
const value: unknown = Reflect.get(resolved, property)
|
|
947
941
|
if (typeof value === 'function') {
|
|
948
|
-
return bindTargetMethod(
|
|
942
|
+
return bindTargetMethod(
|
|
943
|
+
resolved as unknown as Record<PropertyKey, unknown>,
|
|
944
|
+
value as (...args: unknown[]) => unknown,
|
|
945
|
+
)
|
|
949
946
|
}
|
|
950
947
|
|
|
951
948
|
return value
|
package/src/db/startup.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { BoundQuery, RecordId } from 'surrealdb'
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { getErrorMessage } from '../utils/errors'
|
|
5
|
+
import type { SurrealDBService, SurrealDatabaseLogger } from './service'
|
|
6
|
+
import { TABLES } from './tables'
|
|
7
7
|
|
|
8
8
|
const DATABASE_BOOTSTRAP_KEY = 'database-schema-ready'
|
|
9
9
|
const DEFAULT_RETRY_DELAY_MS = 1_000
|
package/src/index.ts
CHANGED