@lota-sdk/core 0.1.16 → 0.1.18
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 +6 -3
- package/src/ai/definitions.ts +1 -1
- package/src/ai/embedding-cache.ts +2 -4
- package/src/{bifrost/bifrost.ts → ai-gateway/ai-gateway.ts} +115 -79
- package/src/ai-gateway/cache-headers.ts +8 -0
- package/src/ai-gateway/index.ts +2 -0
- package/src/config/model-constants.ts +1 -1
- package/src/create-runtime.ts +26 -1
- package/src/db/memory-store.helpers.ts +1 -3
- package/src/db/schema-fingerprint.ts +1 -3
- package/src/embeddings/provider.ts +2 -2
- package/src/index.ts +1 -1
- package/src/queues/document-processor.queue.ts +2 -4
- package/src/queues/post-chat-memory.queue.ts +8 -2
- package/src/queues/recent-activity-title-refinement.queue.ts +1 -1
- package/src/queues/skill-extraction.queue.ts +1 -1
- package/src/queues/workstream-title-generation.queue.ts +1 -1
- package/src/redis/redis-lease-lock.ts +1 -2
- package/src/runtime/agent-runtime-policy.ts +3 -14
- package/src/runtime/context-compaction.ts +2 -4
- package/src/runtime/index.ts +1 -1
- package/src/runtime/runtime-config.ts +87 -7
- package/src/runtime/runtime-extensions.ts +0 -1
- package/src/runtime/social-chat.ts +752 -0
- package/src/runtime/team-consultation-orchestrator.ts +0 -4
- package/src/services/agent-executor.service.ts +0 -1
- package/src/services/document-chunk.service.ts +1 -3
- package/src/services/index.ts +1 -0
- package/src/services/memory.service.ts +7 -2
- package/src/services/recent-activity.service.ts +1 -3
- package/src/services/social-chat-history.service.ts +197 -0
- package/src/services/workstream-message.service.ts +1 -3
- package/src/services/workstream-turn-preparation.service.ts +0 -23
- package/src/system-agents/context-compaction.agent.ts +4 -2
- package/src/system-agents/delegated-agent-factory.ts +3 -0
- package/src/system-agents/memory-reranker.agent.ts +5 -3
- package/src/system-agents/memory.agent.ts +4 -2
- package/src/system-agents/recent-activity-title-refiner.agent.ts +4 -2
- package/src/system-agents/regular-chat-memory-digest.agent.ts +4 -2
- package/src/system-agents/skill-extractor.agent.ts +4 -2
- package/src/system-agents/skill-manager.agent.ts +4 -2
- package/src/system-agents/title-generator.agent.ts +4 -2
- package/src/tools/research-topic.tool.ts +4 -2
- package/src/tools/team-think.tool.ts +0 -3
- package/src/workers/regular-chat-memory-digest.helpers.ts +1 -1
- package/src/workers/regular-chat-memory-digest.runner.ts +43 -10
- package/src/workers/skill-extraction.runner.ts +25 -5
- package/src/workers/utils/repo-structure-extractor.ts +2 -2
- package/src/workers/utils/workstream-message-query.ts +3 -5
- package/src/bifrost/index.ts +0 -1
- package/src/runtime/workstream-routing-policy.ts +0 -267
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lota-sdk/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
}
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
|
-
"lint": "
|
|
19
|
+
"lint": "bunx oxlint --fix -c ../oxlint.config.ts src",
|
|
20
20
|
"format": "bunx oxfmt src",
|
|
21
21
|
"typecheck": "bunx tsgo --noEmit",
|
|
22
22
|
"test:unit": "bun test ../tests/unit/core",
|
|
@@ -29,12 +29,15 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@ai-sdk/devtools": "^0.0.15",
|
|
31
31
|
"@ai-sdk/openai": "^3.0.48",
|
|
32
|
+
"@chat-adapter/slack": "^4.23.0",
|
|
33
|
+
"@chat-adapter/state-ioredis": "^4.23.0",
|
|
32
34
|
"@logtape/logtape": "^2.0.5",
|
|
33
|
-
"@lota-sdk/shared": "0.1.
|
|
35
|
+
"@lota-sdk/shared": "0.1.18",
|
|
34
36
|
"@mendable/firecrawl-js": "^4.17.0",
|
|
35
37
|
"@surrealdb/node": "^3.0.3",
|
|
36
38
|
"ai": "^6.0.137",
|
|
37
39
|
"bullmq": "^5.71.0",
|
|
40
|
+
"chat": "^4.23.0",
|
|
38
41
|
"cron-parser": "^5.5.0",
|
|
39
42
|
"hono": "^4.12.9",
|
|
40
43
|
"ioredis": "5.9.3",
|
package/src/ai/definitions.ts
CHANGED
|
@@ -283,7 +283,7 @@ export const generalRule = defineRule({
|
|
|
283
283
|
name: 'general',
|
|
284
284
|
instructions: `# General Rules
|
|
285
285
|
|
|
286
|
-
- Be concise and direct.
|
|
286
|
+
- Be concise and direct. Prefer short, direct responses. Do not pad with context the user did not ask for.
|
|
287
287
|
- Follow the user's request and constraints.
|
|
288
288
|
- Ask clarifying questions when necessary.
|
|
289
289
|
- Be formal and professional when tone is unclear.
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto'
|
|
2
|
-
|
|
3
1
|
import type IORedis from 'ioredis'
|
|
4
2
|
|
|
5
3
|
import { aiLogger } from '../config/logger'
|
|
6
4
|
|
|
7
|
-
export const DEFAULT_EMBEDDING_CACHE_TTL_SECONDS =
|
|
5
|
+
export const DEFAULT_EMBEDDING_CACHE_TTL_SECONDS = 7200
|
|
8
6
|
const EMBEDDING_CACHE_KEY_PREFIX = 'emb'
|
|
9
7
|
|
|
10
8
|
export class EmbeddingCache {
|
|
@@ -14,7 +12,7 @@ export class EmbeddingCache {
|
|
|
14
12
|
) {}
|
|
15
13
|
|
|
16
14
|
private buildKey(model: string, text: string): string {
|
|
17
|
-
const hash =
|
|
15
|
+
const hash = new Bun.CryptoHasher('sha256').update(text).digest('hex')
|
|
18
16
|
return `${EMBEDDING_CACHE_KEY_PREFIX}:${model}:${hash}`
|
|
19
17
|
}
|
|
20
18
|
|
|
@@ -3,26 +3,57 @@ import { createOpenAI } from '@ai-sdk/openai'
|
|
|
3
3
|
import { wrapLanguageModel } from 'ai'
|
|
4
4
|
import type { LanguageModelMiddleware } from 'ai'
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { getRuntimeConfig } from '../runtime/runtime-config'
|
|
7
7
|
import { isRecord, readString } from '../utils/string'
|
|
8
8
|
|
|
9
|
-
type
|
|
10
|
-
type
|
|
11
|
-
type
|
|
12
|
-
type
|
|
9
|
+
type AiGatewayLanguageModel = Parameters<typeof wrapLanguageModel>[0]['model']
|
|
10
|
+
type AiGatewayExtraParams = Record<string, unknown>
|
|
11
|
+
type AiGatewayChatResponse = { body?: unknown }
|
|
12
|
+
type AiGatewayTransformParamsOptions = Parameters<NonNullable<LanguageModelMiddleware['transformParams']>>[0]
|
|
13
13
|
type WrapStreamOptions = Parameters<NonNullable<LanguageModelMiddleware['wrapStream']>>[0]
|
|
14
|
-
type
|
|
15
|
-
type
|
|
16
|
-
type
|
|
17
|
-
type
|
|
18
|
-
type
|
|
19
|
-
|
|
14
|
+
type AiGatewayCallOptions = WrapStreamOptions['params']
|
|
15
|
+
type AiGatewayGenerateResult = Awaited<ReturnType<WrapStreamOptions['doGenerate']>>
|
|
16
|
+
type AiGatewayStreamResult = Awaited<ReturnType<WrapStreamOptions['doStream']>>
|
|
17
|
+
type AiGatewayGeneratedContent = AiGatewayGenerateResult['content'][number]
|
|
18
|
+
type AiGatewayStreamPart = AiGatewayStreamResult['stream'] extends ReadableStream<infer T> ? T : never
|
|
19
|
+
type AiGatewayConfig = { apiKey: string; baseURL: string }
|
|
20
|
+
|
|
21
|
+
const EXPECTED_GATEWAY_KEY_PREFIX = 'sk-bf-'
|
|
22
|
+
const AI_GATEWAY_VIRTUAL_KEY_HEADER = 'x-bf-vk'
|
|
23
|
+
const AI_GATEWAY_EXTRA_PARAMS_HEADER = 'x-bf-passthrough-extra-params'
|
|
24
|
+
const DEFAULT_AI_GATEWAY_URL = 'https://ai-gateway.gobrainy.ai' as const
|
|
20
25
|
const OPENROUTER_RESPONSE_HEALING_EXTRA_PARAMS = {
|
|
21
26
|
plugins: [{ id: 'response-healing' }],
|
|
22
|
-
} as const satisfies
|
|
27
|
+
} as const satisfies AiGatewayExtraParams
|
|
28
|
+
|
|
29
|
+
function normalizeAiGatewayUrl(value: string): string {
|
|
30
|
+
const trimmed = value.trim()
|
|
31
|
+
if (!trimmed) {
|
|
32
|
+
throw new Error('[ai-gateway] AI gateway URL is required.')
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const normalized = trimmed.replace(/\/+$/, '')
|
|
36
|
+
return normalized.endsWith('/v1') ? normalized : `${normalized}/v1`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function readDirectEnvAiGatewayConfig(): AiGatewayConfig {
|
|
40
|
+
const apiKey = (process.env.LOTA_KEY ?? process.env.VENTUROS_KEY ?? '').trim()
|
|
41
|
+
if (!apiKey) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
'[ai-gateway] Missing AI gateway key. Set LOTA_KEY or VENTUROS_KEY, or configure createLotaRuntime({ aiGateway: { key } }).',
|
|
44
|
+
)
|
|
45
|
+
}
|
|
23
46
|
|
|
24
|
-
|
|
25
|
-
|
|
47
|
+
return { apiKey, baseURL: normalizeAiGatewayUrl(process.env.AI_GATEWAY_URL?.trim() || DEFAULT_AI_GATEWAY_URL) }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function readAiGatewayConfig(): AiGatewayConfig {
|
|
51
|
+
try {
|
|
52
|
+
const { aiGateway } = getRuntimeConfig()
|
|
53
|
+
return { apiKey: aiGateway.key.trim(), baseURL: normalizeAiGatewayUrl(aiGateway.url) }
|
|
54
|
+
} catch {
|
|
55
|
+
return readDirectEnvAiGatewayConfig()
|
|
56
|
+
}
|
|
26
57
|
}
|
|
27
58
|
|
|
28
59
|
function readReasoningDetailsText(value: unknown): string | null {
|
|
@@ -41,7 +72,7 @@ function readReasoningDeltaText(value: unknown): string | null {
|
|
|
41
72
|
return typeof value === 'string' && value.length > 0 ? value : null
|
|
42
73
|
}
|
|
43
74
|
|
|
44
|
-
function
|
|
75
|
+
function readAiGatewayChatReasoningText(message: Record<string, unknown>): string | null {
|
|
45
76
|
return (
|
|
46
77
|
readString(message.reasoning) ??
|
|
47
78
|
readString(message.reasoning_content) ??
|
|
@@ -49,20 +80,20 @@ function readBifrostChatReasoningText(message: Record<string, unknown>): string
|
|
|
49
80
|
)
|
|
50
81
|
}
|
|
51
82
|
|
|
52
|
-
export function
|
|
83
|
+
export function extractAiGatewayChatReasoningText(responseBody: unknown): string | null {
|
|
53
84
|
if (!isRecord(responseBody) || !Array.isArray(responseBody.choices)) return null
|
|
54
85
|
|
|
55
86
|
for (const choice of responseBody.choices) {
|
|
56
87
|
if (!isRecord(choice) || !isRecord(choice.message)) continue
|
|
57
88
|
|
|
58
|
-
const reasoningText =
|
|
89
|
+
const reasoningText = readAiGatewayChatReasoningText(choice.message)
|
|
59
90
|
if (reasoningText) return reasoningText
|
|
60
91
|
}
|
|
61
92
|
|
|
62
93
|
return null
|
|
63
94
|
}
|
|
64
95
|
|
|
65
|
-
export function
|
|
96
|
+
export function extractAiGatewayChatReasoningDeltaText(rawChunk: unknown): string | null {
|
|
66
97
|
if (!isRecord(rawChunk) || !Array.isArray(rawChunk.choices)) return null
|
|
67
98
|
|
|
68
99
|
for (const choice of rawChunk.choices) {
|
|
@@ -78,9 +109,9 @@ export function extractBifrostChatReasoningDeltaText(rawChunk: unknown): string
|
|
|
78
109
|
return null
|
|
79
110
|
}
|
|
80
111
|
|
|
81
|
-
type
|
|
112
|
+
type AiGatewayResponsesReasoningDelta = { id: string; delta: string; itemId: string }
|
|
82
113
|
|
|
83
|
-
export function
|
|
114
|
+
export function extractAiGatewayResponsesReasoningDelta(rawChunk: unknown): AiGatewayResponsesReasoningDelta | null {
|
|
84
115
|
if (!isRecord(rawChunk) || rawChunk.type !== 'response.reasoning_summary_text.delta') return null
|
|
85
116
|
if ('summary_index' in rawChunk) return null
|
|
86
117
|
|
|
@@ -91,21 +122,21 @@ export function extractBifrostResponsesReasoningDelta(rawChunk: unknown): Bifros
|
|
|
91
122
|
return { id: `${itemId}:0`, delta, itemId }
|
|
92
123
|
}
|
|
93
124
|
|
|
94
|
-
export function
|
|
95
|
-
content: readonly
|
|
96
|
-
response?:
|
|
97
|
-
):
|
|
125
|
+
export function injectAiGatewayChatReasoningContent(
|
|
126
|
+
content: readonly AiGatewayGeneratedContent[],
|
|
127
|
+
response?: AiGatewayChatResponse,
|
|
128
|
+
): AiGatewayGeneratedContent[] {
|
|
98
129
|
if (content.some((part) => part.type === 'reasoning')) {
|
|
99
130
|
return [...content]
|
|
100
131
|
}
|
|
101
132
|
|
|
102
|
-
const reasoningText =
|
|
133
|
+
const reasoningText = extractAiGatewayChatReasoningText(response?.body)
|
|
103
134
|
if (!reasoningText) return [...content]
|
|
104
135
|
|
|
105
136
|
return [{ type: 'reasoning', text: reasoningText }, ...content]
|
|
106
137
|
}
|
|
107
138
|
|
|
108
|
-
function isReasoningEnabled(params:
|
|
139
|
+
function isReasoningEnabled(params: AiGatewayCallOptions): boolean {
|
|
109
140
|
if (!isRecord(params.providerOptions) || !isRecord(params.providerOptions.openai)) return false
|
|
110
141
|
|
|
111
142
|
const openaiOptions = params.providerOptions.openai
|
|
@@ -114,34 +145,34 @@ function isReasoningEnabled(params: BifrostCallOptions): boolean {
|
|
|
114
145
|
return typeof openaiOptions.reasoningEffort === 'string' && openaiOptions.reasoningEffort !== 'none'
|
|
115
146
|
}
|
|
116
147
|
|
|
117
|
-
function shouldCloseInjectedReasoning(chunk:
|
|
148
|
+
function shouldCloseInjectedReasoning(chunk: AiGatewayStreamPart): boolean {
|
|
118
149
|
return chunk.type !== 'stream-start' && chunk.type !== 'response-metadata' && chunk.type !== 'raw'
|
|
119
150
|
}
|
|
120
151
|
|
|
121
|
-
export function
|
|
122
|
-
stream: ReadableStream<
|
|
123
|
-
): ReadableStream<
|
|
124
|
-
const reasoningId = '
|
|
152
|
+
export function injectAiGatewayChatReasoningStream(
|
|
153
|
+
stream: ReadableStream<AiGatewayStreamPart>,
|
|
154
|
+
): ReadableStream<AiGatewayStreamPart> {
|
|
155
|
+
const reasoningId = 'ai-gateway-reasoning-0'
|
|
125
156
|
let reasoningOpen = false
|
|
126
157
|
let reasoningClosed = false
|
|
127
158
|
|
|
128
159
|
return stream.pipeThrough(
|
|
129
|
-
new TransformStream<
|
|
160
|
+
new TransformStream<AiGatewayStreamPart, AiGatewayStreamPart>({
|
|
130
161
|
transform(chunk, controller) {
|
|
131
162
|
const closeReasoning = () => {
|
|
132
163
|
if (!reasoningOpen || reasoningClosed) return
|
|
133
164
|
|
|
134
|
-
controller.enqueue({ type: 'reasoning-end', id: reasoningId } satisfies
|
|
165
|
+
controller.enqueue({ type: 'reasoning-end', id: reasoningId } satisfies AiGatewayStreamPart)
|
|
135
166
|
reasoningClosed = true
|
|
136
167
|
}
|
|
137
168
|
|
|
138
169
|
if (chunk.type === 'raw') {
|
|
139
|
-
const reasoningDelta = reasoningClosed ? null :
|
|
170
|
+
const reasoningDelta = reasoningClosed ? null : extractAiGatewayChatReasoningDeltaText(chunk.rawValue)
|
|
140
171
|
controller.enqueue(chunk)
|
|
141
172
|
|
|
142
173
|
if (reasoningDelta) {
|
|
143
174
|
if (!reasoningOpen) {
|
|
144
|
-
controller.enqueue({ type: 'reasoning-start', id: reasoningId } satisfies
|
|
175
|
+
controller.enqueue({ type: 'reasoning-start', id: reasoningId } satisfies AiGatewayStreamPart)
|
|
145
176
|
reasoningOpen = true
|
|
146
177
|
}
|
|
147
178
|
|
|
@@ -149,7 +180,7 @@ export function injectBifrostChatReasoningStream(
|
|
|
149
180
|
type: 'reasoning-delta',
|
|
150
181
|
id: reasoningId,
|
|
151
182
|
delta: reasoningDelta,
|
|
152
|
-
} satisfies
|
|
183
|
+
} satisfies AiGatewayStreamPart)
|
|
153
184
|
}
|
|
154
185
|
return
|
|
155
186
|
}
|
|
@@ -162,23 +193,23 @@ export function injectBifrostChatReasoningStream(
|
|
|
162
193
|
},
|
|
163
194
|
flush(controller) {
|
|
164
195
|
if (!reasoningOpen || reasoningClosed) return
|
|
165
|
-
controller.enqueue({ type: 'reasoning-end', id: reasoningId } satisfies
|
|
196
|
+
controller.enqueue({ type: 'reasoning-end', id: reasoningId } satisfies AiGatewayStreamPart)
|
|
166
197
|
},
|
|
167
198
|
}),
|
|
168
199
|
)
|
|
169
200
|
}
|
|
170
201
|
|
|
171
|
-
export function
|
|
172
|
-
stream: ReadableStream<
|
|
173
|
-
): ReadableStream<
|
|
202
|
+
export function injectAiGatewayResponsesReasoningStream(
|
|
203
|
+
stream: ReadableStream<AiGatewayStreamPart>,
|
|
204
|
+
): ReadableStream<AiGatewayStreamPart> {
|
|
174
205
|
return stream.pipeThrough(
|
|
175
|
-
new TransformStream<
|
|
206
|
+
new TransformStream<AiGatewayStreamPart, AiGatewayStreamPart>({
|
|
176
207
|
transform(chunk, controller) {
|
|
177
208
|
controller.enqueue(chunk)
|
|
178
209
|
|
|
179
210
|
if (chunk.type !== 'raw') return
|
|
180
211
|
|
|
181
|
-
const reasoningDelta =
|
|
212
|
+
const reasoningDelta = extractAiGatewayResponsesReasoningDelta(chunk.rawValue)
|
|
182
213
|
if (!reasoningDelta) return
|
|
183
214
|
|
|
184
215
|
controller.enqueue({
|
|
@@ -186,16 +217,16 @@ export function injectBifrostResponsesReasoningStream(
|
|
|
186
217
|
id: reasoningDelta.id,
|
|
187
218
|
delta: reasoningDelta.delta,
|
|
188
219
|
providerMetadata: { openai: { itemId: reasoningDelta.itemId } },
|
|
189
|
-
} satisfies
|
|
220
|
+
} satisfies AiGatewayStreamPart)
|
|
190
221
|
},
|
|
191
222
|
}),
|
|
192
223
|
)
|
|
193
224
|
}
|
|
194
225
|
|
|
195
|
-
function
|
|
196
|
-
params:
|
|
197
|
-
type:
|
|
198
|
-
):
|
|
226
|
+
function addAiGatewayReasoningRawChunks(
|
|
227
|
+
params: AiGatewayCallOptions,
|
|
228
|
+
type: AiGatewayTransformParamsOptions['type'],
|
|
229
|
+
): AiGatewayCallOptions {
|
|
199
230
|
if (type !== 'stream' || !isReasoningEnabled(params) || params.includeRawChunks === true) {
|
|
200
231
|
return params
|
|
201
232
|
}
|
|
@@ -203,9 +234,9 @@ function addBifrostReasoningRawChunks(
|
|
|
203
234
|
return { ...params, includeRawChunks: true }
|
|
204
235
|
}
|
|
205
236
|
|
|
206
|
-
export function
|
|
237
|
+
export function injectAiGatewayExtraParamsRequestBody(
|
|
207
238
|
body: BodyInit | null | undefined,
|
|
208
|
-
extraParams:
|
|
239
|
+
extraParams: AiGatewayExtraParams,
|
|
209
240
|
): BodyInit | null | undefined {
|
|
210
241
|
if (typeof body !== 'string') return body
|
|
211
242
|
|
|
@@ -225,87 +256,90 @@ export function injectBifrostExtraParamsRequestBody(
|
|
|
225
256
|
return JSON.stringify({ ...parsed, extra_params: mergedExtraParams })
|
|
226
257
|
}
|
|
227
258
|
|
|
228
|
-
function
|
|
259
|
+
function createAiGatewayFetchWithExtraParams(extraParams: AiGatewayExtraParams): typeof fetch {
|
|
229
260
|
const fetchWithExtraParams = (input: RequestInfo | URL, init?: RequestInit | BunFetchRequestInit) =>
|
|
230
|
-
globalThis.fetch(input, { ...init, body:
|
|
261
|
+
globalThis.fetch(input, { ...init, body: injectAiGatewayExtraParamsRequestBody(init?.body, extraParams) })
|
|
231
262
|
|
|
232
263
|
return Object.assign(fetchWithExtraParams, { preconnect: globalThis.fetch.preconnect.bind(globalThis.fetch) })
|
|
233
264
|
}
|
|
234
265
|
|
|
235
|
-
function
|
|
236
|
-
const apiKey =
|
|
237
|
-
if (!apiKey.startsWith(
|
|
238
|
-
throw new Error(
|
|
266
|
+
function createAiGatewayProvider(extraParams?: AiGatewayExtraParams) {
|
|
267
|
+
const { apiKey, baseURL } = readAiGatewayConfig()
|
|
268
|
+
if (!apiKey.startsWith(EXPECTED_GATEWAY_KEY_PREFIX)) {
|
|
269
|
+
throw new Error(`[ai-gateway] Gateway keys must use the ${EXPECTED_GATEWAY_KEY_PREFIX}* format.`)
|
|
239
270
|
}
|
|
240
271
|
|
|
241
272
|
return createOpenAI({
|
|
242
|
-
baseURL
|
|
273
|
+
baseURL,
|
|
243
274
|
apiKey,
|
|
244
|
-
headers: {
|
|
245
|
-
|
|
275
|
+
headers: {
|
|
276
|
+
[AI_GATEWAY_VIRTUAL_KEY_HEADER]: apiKey,
|
|
277
|
+
...(extraParams ? { [AI_GATEWAY_EXTRA_PARAMS_HEADER]: 'true' } : {}),
|
|
278
|
+
},
|
|
279
|
+
...(extraParams ? { fetch: createAiGatewayFetchWithExtraParams(extraParams) } : {}),
|
|
246
280
|
})
|
|
247
281
|
}
|
|
248
282
|
|
|
249
|
-
function
|
|
283
|
+
function withAiGatewayDevTools<TModel extends AiGatewayLanguageModel>(model: TModel): TModel {
|
|
250
284
|
return wrapLanguageModel({ model, middleware: devToolsMiddleware() }) as TModel
|
|
251
285
|
}
|
|
252
286
|
|
|
253
287
|
let provider: ReturnType<typeof createOpenAI> | null = null
|
|
254
288
|
let openRouterResponseHealingProvider: ReturnType<typeof createOpenAI> | null = null
|
|
255
289
|
|
|
256
|
-
export function
|
|
290
|
+
export function getAiGatewayProvider() {
|
|
257
291
|
if (provider) return provider
|
|
258
292
|
|
|
259
|
-
provider =
|
|
293
|
+
provider = createAiGatewayProvider()
|
|
260
294
|
|
|
261
295
|
return provider
|
|
262
296
|
}
|
|
263
297
|
|
|
264
|
-
export function
|
|
298
|
+
export function getAiGatewayOpenRouterResponseHealingProvider() {
|
|
265
299
|
if (openRouterResponseHealingProvider) return openRouterResponseHealingProvider
|
|
266
300
|
|
|
267
|
-
openRouterResponseHealingProvider =
|
|
301
|
+
openRouterResponseHealingProvider = createAiGatewayProvider(OPENROUTER_RESPONSE_HEALING_EXTRA_PARAMS)
|
|
268
302
|
|
|
269
303
|
return openRouterResponseHealingProvider
|
|
270
304
|
}
|
|
271
305
|
|
|
272
|
-
export function
|
|
273
|
-
return
|
|
306
|
+
export function aiGatewayModel(modelId: string) {
|
|
307
|
+
return withAiGatewayDevTools(
|
|
274
308
|
wrapLanguageModel({
|
|
275
|
-
model:
|
|
309
|
+
model: getAiGatewayProvider()(modelId),
|
|
276
310
|
middleware: {
|
|
277
311
|
specificationVersion: 'v3',
|
|
278
|
-
transformParams: async ({ params, type }) =>
|
|
312
|
+
transformParams: async ({ params, type }) => addAiGatewayReasoningRawChunks(params, type),
|
|
279
313
|
wrapStream: async ({ doStream, params }) => {
|
|
280
314
|
const result = await doStream()
|
|
281
315
|
if (!isReasoningEnabled(params)) return result
|
|
282
316
|
|
|
283
|
-
return { ...result, stream:
|
|
317
|
+
return { ...result, stream: injectAiGatewayResponsesReasoningStream(result.stream) }
|
|
284
318
|
},
|
|
285
319
|
},
|
|
286
320
|
}),
|
|
287
321
|
)
|
|
288
322
|
}
|
|
289
323
|
|
|
290
|
-
export function
|
|
291
|
-
return
|
|
324
|
+
export function aiGatewayOpenRouterResponseHealingModel(modelId: string) {
|
|
325
|
+
return withAiGatewayDevTools(getAiGatewayOpenRouterResponseHealingProvider()(modelId))
|
|
292
326
|
}
|
|
293
327
|
|
|
294
|
-
export function
|
|
295
|
-
return
|
|
328
|
+
export function aiGatewayChatModel(modelId: string) {
|
|
329
|
+
return withAiGatewayDevTools(
|
|
296
330
|
wrapLanguageModel({
|
|
297
|
-
model:
|
|
331
|
+
model: getAiGatewayProvider().chat(modelId),
|
|
298
332
|
middleware: {
|
|
299
333
|
specificationVersion: 'v3',
|
|
300
|
-
transformParams: async ({ params, type }) =>
|
|
334
|
+
transformParams: async ({ params, type }) => addAiGatewayReasoningRawChunks(params, type),
|
|
301
335
|
wrapGenerate: async ({ doGenerate }) => {
|
|
302
336
|
const result = await doGenerate()
|
|
303
337
|
|
|
304
338
|
return {
|
|
305
339
|
...result,
|
|
306
|
-
content:
|
|
340
|
+
content: injectAiGatewayChatReasoningContent(
|
|
307
341
|
result.content,
|
|
308
|
-
result.response as
|
|
342
|
+
result.response as AiGatewayChatResponse | undefined,
|
|
309
343
|
),
|
|
310
344
|
}
|
|
311
345
|
},
|
|
@@ -313,13 +347,15 @@ export function bifrostChatModel(modelId: string) {
|
|
|
313
347
|
const result = await doStream()
|
|
314
348
|
if (!isReasoningEnabled(params)) return result
|
|
315
349
|
|
|
316
|
-
return { ...result, stream:
|
|
350
|
+
return { ...result, stream: injectAiGatewayChatReasoningStream(result.stream) }
|
|
317
351
|
},
|
|
318
352
|
},
|
|
319
353
|
}),
|
|
320
354
|
)
|
|
321
355
|
}
|
|
322
356
|
|
|
323
|
-
export function
|
|
324
|
-
return
|
|
357
|
+
export function aiGatewayEmbeddingModel(modelId: string) {
|
|
358
|
+
return getAiGatewayProvider().embeddingModel(modelId)
|
|
325
359
|
}
|
|
360
|
+
|
|
361
|
+
export { DEFAULT_AI_GATEWAY_URL, normalizeAiGatewayUrl }
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const AI_GATEWAY_CACHE_KEY_HEADER = 'x-bf-cache-key'
|
|
2
|
+
const AI_GATEWAY_CACHE_TTL_HEADER = 'x-bf-cache-ttl'
|
|
3
|
+
|
|
4
|
+
export function buildAiGatewayCacheHeaders(cacheKey: string, ttl?: string): Record<string, string> {
|
|
5
|
+
const headers: Record<string, string> = { [AI_GATEWAY_CACHE_KEY_HEADER]: cacheKey }
|
|
6
|
+
if (ttl) headers[AI_GATEWAY_CACHE_TTL_HEADER] = ttl
|
|
7
|
+
return headers
|
|
8
|
+
}
|
package/src/create-runtime.ts
CHANGED
|
@@ -25,6 +25,8 @@ import type { LotaRuntimeConfig, ResolvedLotaRuntimeConfig } from './runtime/run
|
|
|
25
25
|
import { configureRuntimeExtensions } from './runtime/runtime-extensions'
|
|
26
26
|
import type { LotaRuntimeWorkers } from './runtime/runtime-worker-registry'
|
|
27
27
|
import { buildRuntimeWorkerRegistry } from './runtime/runtime-worker-registry'
|
|
28
|
+
import type { LotaRuntimeSocialChat } from './runtime/social-chat'
|
|
29
|
+
import { createSocialChatRuntime } from './runtime/social-chat'
|
|
28
30
|
import type { attachmentService } from './services/attachment.service'
|
|
29
31
|
import { attachmentService as attachmentServiceSingleton } from './services/attachment.service'
|
|
30
32
|
import { coordinationRegistryService as coordinationRegistryServiceSingleton } from './services/coordination-registry.service'
|
|
@@ -47,6 +49,10 @@ import type { recentActivityTitleService } from './services/recent-activity-titl
|
|
|
47
49
|
import { recentActivityTitleService as recentActivityTitleServiceSingleton } from './services/recent-activity-title.service'
|
|
48
50
|
import type { recentActivityService } from './services/recent-activity.service'
|
|
49
51
|
import { recentActivityService as recentActivityServiceSingleton } from './services/recent-activity.service'
|
|
52
|
+
import {
|
|
53
|
+
configureSocialChatHistory,
|
|
54
|
+
socialChatHistoryService as socialChatHistoryServiceSingleton,
|
|
55
|
+
} from './services/social-chat-history.service'
|
|
50
56
|
import { getBuiltInSystemExecutors } from './services/system-executor.service'
|
|
51
57
|
import type { userService } from './services/user.service'
|
|
52
58
|
import { userService as userServiceSingleton } from './services/user.service'
|
|
@@ -115,6 +121,7 @@ export interface LotaRuntime {
|
|
|
115
121
|
userService: typeof userService
|
|
116
122
|
recentActivityService: typeof recentActivityService
|
|
117
123
|
recentActivityTitleService: typeof recentActivityTitleService
|
|
124
|
+
socialChatHistoryService: typeof socialChatHistoryServiceSingleton
|
|
118
125
|
executionPlanService: typeof executionPlanService
|
|
119
126
|
workstreamMessageService: typeof workstreamMessageService
|
|
120
127
|
workstreamService: typeof workstreamService
|
|
@@ -184,6 +191,7 @@ export interface LotaRuntime {
|
|
|
184
191
|
closeConnection: () => Promise<void>
|
|
185
192
|
}
|
|
186
193
|
workers: LotaRuntimeWorkers
|
|
194
|
+
socialChat: LotaRuntimeSocialChat
|
|
187
195
|
schemaFiles: Array<string | URL>
|
|
188
196
|
contributions: { envKeys: readonly string[]; schemaFiles: Array<string | URL> }
|
|
189
197
|
config: ResolvedLotaRuntimeConfig
|
|
@@ -218,11 +226,21 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
|
|
|
218
226
|
setRedisConnectionManager(redisManager)
|
|
219
227
|
configureEmbeddingCache(redisManager.getConnection(), runtimeConfig.memory.embeddingCacheTtlSeconds)
|
|
220
228
|
configureBackgroundProcessing(runtimeConfig.backgroundProcessing)
|
|
229
|
+
configureSocialChatHistory({ keyPrefix: runtimeConfig.socialChat?.historyRedisKeyPrefix })
|
|
230
|
+
|
|
231
|
+
const socialChatAgentId = runtimeConfig.socialChat?.agentId?.trim() || 'socialChat'
|
|
232
|
+
const socialChatAgentDisplayName = runtimeConfig.socialChat?.agentDisplayName?.trim() || 'Lota'
|
|
233
|
+
if (runtimeConfig.socialChat && !runtimeConfig.agents.roster.includes(socialChatAgentId)) {
|
|
234
|
+
throw new Error(`socialChat.agentId must be present in agents.roster: ${socialChatAgentId}`)
|
|
235
|
+
}
|
|
236
|
+
const agentDisplayNames = runtimeConfig.socialChat
|
|
237
|
+
? { ...runtimeConfig.agents.displayNames, [socialChatAgentId]: socialChatAgentDisplayName }
|
|
238
|
+
: runtimeConfig.agents.displayNames
|
|
221
239
|
|
|
222
240
|
configureAgents({
|
|
223
241
|
roster: runtimeConfig.agents.roster,
|
|
224
242
|
leadAgentId: runtimeConfig.agents.leadAgentId,
|
|
225
|
-
displayNames:
|
|
243
|
+
displayNames: agentDisplayNames,
|
|
226
244
|
shortDisplayNames: runtimeConfig.agents.shortDisplayNames,
|
|
227
245
|
teamConsultParticipants: runtimeConfig.agents.teamConsultParticipants,
|
|
228
246
|
getCoreWorkstreamProfile: runtimeConfig.agents.getCoreWorkstreamProfile,
|
|
@@ -264,6 +282,10 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
|
|
|
264
282
|
const contributionEnvKeys = [...LOTA_RUNTIME_ENV_KEYS, ...pluginContributions.flatMap((plugin) => plugin.envKeys)]
|
|
265
283
|
const connectPluginDatabases = createPluginDatabaseConnector(pluginRuntime)
|
|
266
284
|
const workers = buildRuntimeWorkerRegistry(runtimeConfig.extraWorkers)
|
|
285
|
+
const socialChat = createSocialChatRuntime({
|
|
286
|
+
redisClient: redisManager.getConnection(),
|
|
287
|
+
socialChat: runtimeConfig.socialChat,
|
|
288
|
+
})
|
|
267
289
|
|
|
268
290
|
const lota = {
|
|
269
291
|
organizations: {
|
|
@@ -369,6 +391,7 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
|
|
|
369
391
|
userService: userServiceSingleton,
|
|
370
392
|
recentActivityService: recentActivityServiceSingleton,
|
|
371
393
|
recentActivityTitleService: recentActivityTitleServiceSingleton,
|
|
394
|
+
socialChatHistoryService: socialChatHistoryServiceSingleton,
|
|
372
395
|
executionPlanService: executionPlanServiceSingleton,
|
|
373
396
|
workstreamMessageService: workstreamMessageServiceSingleton,
|
|
374
397
|
workstreamService: workstreamServiceSingleton,
|
|
@@ -388,6 +411,7 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
|
|
|
388
411
|
closeConnection: async () => await redisManager.closeConnection(),
|
|
389
412
|
},
|
|
390
413
|
workers,
|
|
414
|
+
socialChat,
|
|
391
415
|
schemaFiles,
|
|
392
416
|
contributions: { envKeys: [...new Set(contributionEnvKeys)], schemaFiles: hostContributionSchemaFiles },
|
|
393
417
|
config: runtimeConfig,
|
|
@@ -412,6 +436,7 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
|
|
|
412
436
|
disconnected = true
|
|
413
437
|
|
|
414
438
|
try {
|
|
439
|
+
await socialChat.shutdown()
|
|
415
440
|
await closeSharedSubscriber()
|
|
416
441
|
await db.disconnect()
|
|
417
442
|
await redisManager.closeConnection()
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto'
|
|
2
|
-
|
|
3
1
|
import type { BasicSearchRow, SurrealMemoryRow } from './memory-store.rows'
|
|
4
2
|
import type { MemoryRecord, MemorySearchResult } from './memory-types'
|
|
5
3
|
import { recordIdToString } from './record-id'
|
|
@@ -114,5 +112,5 @@ export function processGraphAwareRows<T extends BasicSearchRow>(
|
|
|
114
112
|
}
|
|
115
113
|
|
|
116
114
|
export function hashContent(content: string, scopeId: string, memoryType: string): string {
|
|
117
|
-
return
|
|
115
|
+
return new Bun.CryptoHasher('sha256').update(`${scopeId}:${memoryType}:${content}`).digest('hex')
|
|
118
116
|
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto'
|
|
2
|
-
|
|
3
1
|
function toSchemaFilePath(value: string | URL): string {
|
|
4
2
|
return value instanceof URL ? value.pathname : value
|
|
5
3
|
}
|
|
6
4
|
|
|
7
5
|
export async function computeSchemaFingerprint(schemaFiles: readonly (string | URL)[]): Promise<string> {
|
|
8
|
-
const hash =
|
|
6
|
+
const hash = new Bun.CryptoHasher('sha256')
|
|
9
7
|
|
|
10
8
|
// Sequential reads required: hash must be computed in deterministic file order
|
|
11
9
|
for (const schemaFile of schemaFiles) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { embed, embedMany } from 'ai'
|
|
2
2
|
|
|
3
|
+
import { aiGatewayEmbeddingModel } from '../ai-gateway/ai-gateway'
|
|
3
4
|
import { getEmbeddingCache } from '../ai/embedding-cache'
|
|
4
|
-
import { bifrostEmbeddingModel } from '../bifrost/bifrost'
|
|
5
5
|
import { getRuntimeConfig } from '../runtime/runtime-config'
|
|
6
6
|
|
|
7
7
|
const SUPPORTED_EMBEDDING_PREFIXES = ['openai/', 'openrouter/'] as const
|
|
@@ -30,7 +30,7 @@ function resolveEmbeddingModel(modelId: string) {
|
|
|
30
30
|
)
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
return
|
|
33
|
+
return aiGatewayEmbeddingModel(normalized)
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
function normalizeEmbedding(embedding: readonly number[]): number[] {
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto'
|
|
2
|
-
|
|
3
1
|
import { Queue, Worker } from 'bullmq'
|
|
4
2
|
import type IORedis from 'ioredis'
|
|
5
3
|
|
|
@@ -43,7 +41,7 @@ export function buildDocumentProcessorJobId(
|
|
|
43
41
|
'orgId' | 'source' | 'sourceId' | 'sourceCanonicalKey' | 'sourceVersionKey' | 'title'
|
|
44
42
|
>,
|
|
45
43
|
): string {
|
|
46
|
-
const digest =
|
|
44
|
+
const digest = new Bun.CryptoHasher('sha256')
|
|
47
45
|
.update(
|
|
48
46
|
JSON.stringify({
|
|
49
47
|
orgId: job.orgId,
|
|
@@ -73,7 +71,7 @@ export function createDocumentProcessorQueueRuntime<TJob extends DocumentProcess
|
|
|
73
71
|
} {
|
|
74
72
|
const queueName = params.queueName ?? DEFAULT_DOCUMENT_PROCESSOR_QUEUE
|
|
75
73
|
const workerName = params.workerName ?? DEFAULT_WORKER_NAME
|
|
76
|
-
const concurrency = params.concurrency ??
|
|
74
|
+
const concurrency = params.concurrency ?? 10
|
|
77
75
|
const lockDuration = params.lockDuration ?? 300_000
|
|
78
76
|
const jobName = 'process-document' as Parameters<Queue<TJob, unknown, string>['add']>[0]
|
|
79
77
|
const toQueueData = (job: TJob): Parameters<Queue<TJob, unknown, string>['add']>[1] =>
|