@lota-sdk/core 0.4.25 → 0.4.26
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 +2 -2
- package/src/ai-gateway/ai-gateway.ts +11 -4
- package/src/ai-gateway/index.ts +1 -0
- package/src/services/social-chat-history.service.ts +8 -2
- package/src/workers/regular-chat-memory-digest.helpers.ts +27 -6
- package/src/workers/regular-chat-memory-digest.runner.ts +6 -3
- package/src/workers/skill-extraction.runner.ts +3 -0
- package/src/workers/utils/thread-message-query.ts +10 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lota-sdk/core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.26",
|
|
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.
|
|
35
|
+
"@lota-sdk/shared": "0.4.26",
|
|
36
36
|
"@mendable/firecrawl-js": "^4.20.0",
|
|
37
37
|
"@surrealdb/node": "^3.0.3",
|
|
38
38
|
"ai": "^6.0.170",
|
|
@@ -1117,11 +1117,18 @@ export function normalizeAiGatewayChatProviderOptions(
|
|
|
1117
1117
|
return { ...params, providerOptions: nextProviderOptions as AiGatewayCallOptions['providerOptions'] }
|
|
1118
1118
|
}
|
|
1119
1119
|
|
|
1120
|
-
function
|
|
1121
|
-
if (
|
|
1122
|
-
|
|
1123
|
-
|
|
1120
|
+
function readEnabledEnvFlag(value: string | undefined): boolean {
|
|
1121
|
+
if (!value) return false
|
|
1122
|
+
return ['1', 'true', 'yes', 'on'].includes(value.trim().toLowerCase())
|
|
1123
|
+
}
|
|
1124
1124
|
|
|
1125
|
+
export function isAiGatewayDevToolsEnabled(env: Record<string, string | undefined> = Bun.env): boolean {
|
|
1126
|
+
if (env.NODE_ENV === 'production') return false
|
|
1127
|
+
return readEnabledEnvFlag(env.LOTA_AI_GATEWAY_DEVTOOLS) || readEnabledEnvFlag(env.AI_GATEWAY_DEVTOOLS)
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
function withAiGatewayDevTools<TModel extends AiGatewayLanguageModel>(model: TModel): TModel {
|
|
1131
|
+
if (!isAiGatewayDevToolsEnabled()) return model
|
|
1125
1132
|
return wrapLanguageModel({ model, middleware: devToolsMiddleware() }) as TModel
|
|
1126
1133
|
}
|
|
1127
1134
|
|
package/src/ai-gateway/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ export {
|
|
|
13
13
|
extractAiGatewayChatReasoningText,
|
|
14
14
|
injectAiGatewayChatReasoningContent,
|
|
15
15
|
injectAiGatewayChatReasoningStream,
|
|
16
|
+
isAiGatewayDevToolsEnabled,
|
|
16
17
|
isAiGenerationContentFilterError,
|
|
17
18
|
normalizeAiGatewayChatProviderOptions,
|
|
18
19
|
normalizeAiGatewayJsonSchemas,
|
|
@@ -182,6 +182,7 @@ export function makeSocialChatHistoryService(
|
|
|
182
182
|
workspaceId: string
|
|
183
183
|
cursor: LotaRuntimeBackgroundCursor | null
|
|
184
184
|
onboardingCutoff: Date | null
|
|
185
|
+
limit?: number
|
|
185
186
|
}) =>
|
|
186
187
|
Effect.gen(function* () {
|
|
187
188
|
const conn = redis.getConnection()
|
|
@@ -189,11 +190,15 @@ export function makeSocialChatHistoryService(
|
|
|
189
190
|
const scoreStart =
|
|
190
191
|
params.cursor?.createdAt.getTime() ??
|
|
191
192
|
(params.onboardingCutoff ? params.onboardingCutoff.getTime() : Number.NEGATIVE_INFINITY)
|
|
193
|
+
const limit = typeof params.limit === 'number' ? Math.max(1, Math.trunc(params.limit)) : undefined
|
|
194
|
+
const rangeLimitArgs = limit === undefined ? [] : (['LIMIT', 0, limit + 1] as const)
|
|
192
195
|
const storageKeys = yield* Effect.tryPromise({
|
|
193
196
|
try: () =>
|
|
194
197
|
params.cursor || params.onboardingCutoff
|
|
195
|
-
? conn.zrangebyscore(indexKey, scoreStart, '+inf')
|
|
196
|
-
:
|
|
198
|
+
? conn.zrangebyscore(indexKey, scoreStart, '+inf', ...rangeLimitArgs)
|
|
199
|
+
: limit === undefined
|
|
200
|
+
? conn.zrange(indexKey, 0, -1)
|
|
201
|
+
: conn.zrange(indexKey, 0, limit - 1),
|
|
197
202
|
catch: (cause) => new SocialChatHistoryError({ message: 'Failed to list workspace message keys.', cause }),
|
|
198
203
|
})
|
|
199
204
|
if (storageKeys.length === 0) return [] as SocialChatHistoryMessage[]
|
|
@@ -226,6 +231,7 @@ export function makeSocialChatHistoryService(
|
|
|
226
231
|
workspaceId: params.workspaceId,
|
|
227
232
|
cursor: params.cursor,
|
|
228
233
|
onboardingCutoff: params.onboardingCutoff,
|
|
234
|
+
limit: 1,
|
|
229
235
|
}).pipe(Effect.map((messages) => messages.length > 0))
|
|
230
236
|
|
|
231
237
|
const getBackgroundCursorEffect = (kind: LotaRuntimeBackgroundCursorKind, workspaceId: string) =>
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { compactWhitespace, readRecord, readString } from '../utils/string'
|
|
1
|
+
import { compactWhitespace, readRecord, readString, truncateText } from '../utils/string'
|
|
2
2
|
import type { DigestMessage } from './utils/thread-message-query'
|
|
3
3
|
|
|
4
4
|
type DigestMessageForTranscript = Pick<DigestMessage, 'source' | 'sourceId' | 'role' | 'parts' | 'metadata'>
|
|
5
5
|
type DigestMessagePart = DigestMessageForTranscript['parts'][number]
|
|
6
6
|
|
|
7
|
+
const DIGEST_TRANSCRIPT_MAX_CHARS = 60_000
|
|
8
|
+
const DIGEST_TRANSCRIPT_MESSAGE_MAX_CHARS = 4_000
|
|
9
|
+
|
|
7
10
|
function normalizeFilePartMetadata(part: DigestMessagePart): string | null {
|
|
8
11
|
if (part.type !== 'file') return null
|
|
9
12
|
const partRecord = readRecord(part)
|
|
@@ -41,6 +44,10 @@ function extractAssistantLabel(
|
|
|
41
44
|
return 'assistant'
|
|
42
45
|
}
|
|
43
46
|
|
|
47
|
+
function normalizeTranscriptText(value: string): string {
|
|
48
|
+
return truncateText(compactWhitespace(value), DIGEST_TRANSCRIPT_MESSAGE_MAX_CHARS)
|
|
49
|
+
}
|
|
50
|
+
|
|
44
51
|
export function buildDigestTranscript(params: {
|
|
45
52
|
messages: DigestMessageForTranscript[]
|
|
46
53
|
isKnownAgentName: (value: string) => boolean
|
|
@@ -48,13 +55,27 @@ export function buildDigestTranscript(params: {
|
|
|
48
55
|
const lines: string[] = []
|
|
49
56
|
const involvedAgentNames = new Set<string>()
|
|
50
57
|
const { isKnownAgentName } = params
|
|
58
|
+
let transcriptChars = 0
|
|
59
|
+
|
|
60
|
+
const appendLine = (line: string): boolean => {
|
|
61
|
+
const separatorChars = lines.length === 0 ? 0 : 1
|
|
62
|
+
const remaining = DIGEST_TRANSCRIPT_MAX_CHARS - transcriptChars - separatorChars
|
|
63
|
+
if (remaining <= 3) return false
|
|
64
|
+
|
|
65
|
+
const nextLine = line.length > remaining ? truncateText(line, remaining) : line
|
|
66
|
+
lines.push(nextLine)
|
|
67
|
+
transcriptChars += separatorChars + nextLine.length
|
|
68
|
+
return line.length <= remaining
|
|
69
|
+
}
|
|
51
70
|
|
|
52
71
|
for (const message of params.messages) {
|
|
53
72
|
if (message.role !== 'user' && message.role !== 'assistant') continue
|
|
54
73
|
|
|
55
74
|
const sourcePrefix = `[${message.source}:${message.sourceId}]`
|
|
56
75
|
const textParts = message.parts
|
|
57
|
-
.flatMap((part) =>
|
|
76
|
+
.flatMap((part) =>
|
|
77
|
+
part.type === 'text' && typeof part.text === 'string' ? [normalizeTranscriptText(part.text)] : [],
|
|
78
|
+
)
|
|
58
79
|
.filter((value) => value.length > 0)
|
|
59
80
|
const fileParts = message.parts
|
|
60
81
|
.map((part) => normalizeFilePartMetadata(part))
|
|
@@ -62,10 +83,10 @@ export function buildDigestTranscript(params: {
|
|
|
62
83
|
|
|
63
84
|
if (message.role === 'user') {
|
|
64
85
|
for (const textPart of textParts) {
|
|
65
|
-
|
|
86
|
+
if (!appendLine(`${sourcePrefix} User: ${textPart}`)) break
|
|
66
87
|
}
|
|
67
88
|
if (fileParts.length > 0) {
|
|
68
|
-
|
|
89
|
+
if (!appendLine(`${sourcePrefix} User files: ${fileParts.join('; ')}`)) break
|
|
69
90
|
}
|
|
70
91
|
continue
|
|
71
92
|
}
|
|
@@ -76,10 +97,10 @@ export function buildDigestTranscript(params: {
|
|
|
76
97
|
}
|
|
77
98
|
|
|
78
99
|
for (const textPart of textParts) {
|
|
79
|
-
|
|
100
|
+
if (!appendLine(`${sourcePrefix} [${assistantLabel}] ${textPart}`)) break
|
|
80
101
|
}
|
|
81
102
|
if (fileParts.length > 0) {
|
|
82
|
-
|
|
103
|
+
if (!appendLine(`${sourcePrefix} [${assistantLabel}] files: ${fileParts.join('; ')}`)) break
|
|
83
104
|
}
|
|
84
105
|
}
|
|
85
106
|
|
|
@@ -22,9 +22,10 @@ import type { MemoryServiceTag } from '../services/memory/memory.service'
|
|
|
22
22
|
import type { SocialChatHistoryServiceTag } from '../services/social-chat-history.service'
|
|
23
23
|
import { makeRegularChatMemoryDigestAgentFactory } from '../system-agents/regular-chat-memory-digest.agent'
|
|
24
24
|
import { nowIsoDateTimeString } from '../utils/date-time'
|
|
25
|
-
import { compactWhitespace } from '../utils/string'
|
|
25
|
+
import { compactWhitespace, truncateText } from '../utils/string'
|
|
26
26
|
import { buildDigestTranscript, resolveWorkspaceBootstrapCutoff } from './regular-chat-memory-digest.helpers'
|
|
27
27
|
import {
|
|
28
|
+
BACKGROUND_LEARNING_MESSAGE_BATCH_LIMIT,
|
|
28
29
|
compareDigestMessageOrder,
|
|
29
30
|
listEligibleThreadMessages,
|
|
30
31
|
listThreadIdsForOrg,
|
|
@@ -87,7 +88,7 @@ function buildMemoryContext(memories: Array<{ content: string }>): string {
|
|
|
87
88
|
.map((memory, index) => {
|
|
88
89
|
const content = compactWhitespace(memory.content)
|
|
89
90
|
if (!content) return ''
|
|
90
|
-
return `${index + 1}. ${content}`
|
|
91
|
+
return `${index + 1}. ${truncateText(content, 500)}`
|
|
91
92
|
})
|
|
92
93
|
.filter((line) => line.length > 0)
|
|
93
94
|
.join('\n')
|
|
@@ -177,7 +178,7 @@ function loadExistingOrganizationMemories(db: SurrealDBService, orgId: string):
|
|
|
177
178
|
AND archivedAt IS NONE
|
|
178
179
|
AND (validUntil IS NONE OR validUntil > time::now())
|
|
179
180
|
ORDER BY createdAt DESC, id DESC
|
|
180
|
-
LIMIT
|
|
181
|
+
LIMIT 100`,
|
|
181
182
|
{ orgId },
|
|
182
183
|
),
|
|
183
184
|
WorkspaceMemoryRowSchema,
|
|
@@ -242,6 +243,7 @@ function runRegularChatMemoryDigestEffect(
|
|
|
242
243
|
threadIds,
|
|
243
244
|
cursor: existingThreadCursor,
|
|
244
245
|
onboardingCutoff: threadOnboardingCutoff,
|
|
246
|
+
limit: BACKGROUND_LEARNING_MESSAGE_BATCH_LIMIT,
|
|
245
247
|
}),
|
|
246
248
|
catch: (cause) => new Cause.UnknownError(cause),
|
|
247
249
|
})
|
|
@@ -257,6 +259,7 @@ function runRegularChatMemoryDigestEffect(
|
|
|
257
259
|
workspaceId: orgId,
|
|
258
260
|
cursor: existingSocialCursor,
|
|
259
261
|
onboardingCutoff: socialOnboardingCutoff,
|
|
262
|
+
limit: BACKGROUND_LEARNING_MESSAGE_BATCH_LIMIT,
|
|
260
263
|
})
|
|
261
264
|
|
|
262
265
|
if (threadMessages.length === 0 && socialMessages.length === 0) {
|
|
@@ -20,6 +20,7 @@ import type { SkillCandidate } from '../system-agents/skill-extractor.agent'
|
|
|
20
20
|
import { makeSkillManagerAgentFactory, SkillManagerOutputSchema } from '../system-agents/skill-manager.agent'
|
|
21
21
|
import { buildDigestTranscript, resolveWorkspaceBootstrapCutoff } from './regular-chat-memory-digest.helpers'
|
|
22
22
|
import {
|
|
23
|
+
BACKGROUND_LEARNING_MESSAGE_BATCH_LIMIT,
|
|
23
24
|
compareDigestMessageOrder,
|
|
24
25
|
listEligibleThreadMessages,
|
|
25
26
|
listThreadIdsForOrg,
|
|
@@ -157,6 +158,7 @@ function runSkillExtractionEffect(
|
|
|
157
158
|
threadIds,
|
|
158
159
|
cursor: existingCursor,
|
|
159
160
|
onboardingCutoff,
|
|
161
|
+
limit: BACKGROUND_LEARNING_MESSAGE_BATCH_LIMIT,
|
|
160
162
|
}),
|
|
161
163
|
catch: (cause) => new Cause.UnknownError(cause),
|
|
162
164
|
})
|
|
@@ -164,6 +166,7 @@ function runSkillExtractionEffect(
|
|
|
164
166
|
workspaceId: orgId,
|
|
165
167
|
cursor: existingSocialCursor,
|
|
166
168
|
onboardingCutoff: socialOnboardingCutoff,
|
|
169
|
+
limit: BACKGROUND_LEARNING_MESSAGE_BATCH_LIMIT,
|
|
167
170
|
})
|
|
168
171
|
const messages = [...threadMessages, ...socialMessages]
|
|
169
172
|
|
|
@@ -16,6 +16,8 @@ import type { LotaRuntimeBackgroundCursor } from '../../runtime/runtime-extensio
|
|
|
16
16
|
import type { SocialChatHistoryMessage } from '../../services/social-chat-history.service'
|
|
17
17
|
import { unsafeDateFrom } from '../../utils/date-time'
|
|
18
18
|
|
|
19
|
+
export const BACKGROUND_LEARNING_MESSAGE_BATCH_LIMIT = 80
|
|
20
|
+
|
|
19
21
|
interface ThreadDigestMessage {
|
|
20
22
|
source: 'thread'
|
|
21
23
|
sourceId: string
|
|
@@ -103,11 +105,13 @@ export function listEligibleThreadMessages(params: {
|
|
|
103
105
|
threadIds: RecordIdRef[]
|
|
104
106
|
cursor: LotaRuntimeBackgroundCursor | null
|
|
105
107
|
onboardingCutoff: Date | null
|
|
108
|
+
limit?: number
|
|
106
109
|
}): Promise<DigestMessage[]> {
|
|
107
110
|
if (params.threadIds.length === 0) return Promise.resolve([])
|
|
108
111
|
|
|
109
112
|
return Effect.runPromise(
|
|
110
113
|
Effect.gen(function* () {
|
|
114
|
+
const limit = Math.max(1, Math.trunc(params.limit ?? BACKGROUND_LEARNING_MESSAGE_BATCH_LIMIT))
|
|
111
115
|
const query = params.cursor
|
|
112
116
|
? new BoundQuery(
|
|
113
117
|
`SELECT type::string(id) AS id, type::string(threadId) AS threadId, role, parts, metadata, createdAt FROM ${TABLES.THREAD_MESSAGE}
|
|
@@ -116,11 +120,13 @@ export function listEligibleThreadMessages(params: {
|
|
|
116
120
|
createdAt > $cursorCreatedAt
|
|
117
121
|
OR (createdAt = $cursorCreatedAt AND id > $cursorRowId)
|
|
118
122
|
)
|
|
119
|
-
ORDER BY createdAt ASC, id ASC
|
|
123
|
+
ORDER BY createdAt ASC, id ASC
|
|
124
|
+
LIMIT $limit`,
|
|
120
125
|
{
|
|
121
126
|
threadIds: params.threadIds,
|
|
122
127
|
cursorCreatedAt: params.cursor.createdAt,
|
|
123
128
|
cursorRowId: ensureRecordId(params.cursor.id, TABLES.THREAD_MESSAGE),
|
|
129
|
+
limit,
|
|
124
130
|
},
|
|
125
131
|
)
|
|
126
132
|
: params.onboardingCutoff
|
|
@@ -128,8 +134,9 @@ export function listEligibleThreadMessages(params: {
|
|
|
128
134
|
`SELECT type::string(id) AS id, type::string(threadId) AS threadId, role, parts, metadata, createdAt FROM ${TABLES.THREAD_MESSAGE}
|
|
129
135
|
WHERE threadId IN $threadIds
|
|
130
136
|
AND createdAt > $onboardingCutoff
|
|
131
|
-
ORDER BY createdAt ASC, id ASC
|
|
132
|
-
|
|
137
|
+
ORDER BY createdAt ASC, id ASC
|
|
138
|
+
LIMIT $limit`,
|
|
139
|
+
{ threadIds: params.threadIds, onboardingCutoff: params.onboardingCutoff, limit },
|
|
133
140
|
)
|
|
134
141
|
: null
|
|
135
142
|
|