@lota-sdk/core 0.1.17 → 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 +2 -2
- package/src/{bifrost/bifrost.ts → ai-gateway/ai-gateway.ts} +115 -79
- package/src/ai-gateway/cache-headers.ts +8 -0
- package/src/{bifrost → ai-gateway}/index.ts +1 -1
- package/src/config/model-constants.ts +1 -1
- package/src/embeddings/provider.ts +2 -2
- package/src/index.ts +1 -1
- package/src/runtime/runtime-config.ts +1 -5
- package/src/system-agents/context-compaction.agent.ts +4 -4
- package/src/system-agents/memory-reranker.agent.ts +4 -4
- package/src/system-agents/memory.agent.ts +4 -4
- package/src/system-agents/recent-activity-title-refiner.agent.ts +4 -4
- package/src/system-agents/regular-chat-memory-digest.agent.ts +4 -4
- package/src/system-agents/skill-extractor.agent.ts +4 -4
- package/src/system-agents/skill-manager.agent.ts +4 -4
- package/src/system-agents/title-generator.agent.ts +4 -4
- package/src/tools/research-topic.tool.ts +4 -4
- package/src/bifrost/cache-headers.ts +0 -8
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",
|
|
@@ -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.1.
|
|
35
|
+
"@lota-sdk/shared": "0.1.18",
|
|
36
36
|
"@mendable/firecrawl-js": "^4.17.0",
|
|
37
37
|
"@surrealdb/node": "^3.0.3",
|
|
38
38
|
"ai": "^6.0.137",
|
|
@@ -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
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from './
|
|
1
|
+
export * from './ai-gateway'
|
|
2
2
|
export * from './cache-headers'
|
|
@@ -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
|
@@ -220,8 +220,6 @@ export const LotaRuntimeConfigSchema = z.object({
|
|
|
220
220
|
aiGateway: z.object({
|
|
221
221
|
url: z.string().trim().min(1),
|
|
222
222
|
key: z.string().trim().min(1),
|
|
223
|
-
admin: z.string().trim().min(1).optional(),
|
|
224
|
-
pass: z.string().trim().min(1).optional(),
|
|
225
223
|
embeddingModel: z.string().trim().min(1).default('openai/text-embedding-3-small'),
|
|
226
224
|
}),
|
|
227
225
|
s3: z.object({
|
|
@@ -294,9 +292,7 @@ export const LOTA_RUNTIME_ENV_KEYS = Object.freeze([
|
|
|
294
292
|
'SURREALDB_PASSWORD',
|
|
295
293
|
'REDIS_URL',
|
|
296
294
|
'AI_GATEWAY_URL',
|
|
297
|
-
'
|
|
298
|
-
'AI_GATEWAY_ADMIN',
|
|
299
|
-
'AI_GATEWAY_PASS',
|
|
295
|
+
'LOTA_KEY',
|
|
300
296
|
'AI_EMBEDDING_MODEL',
|
|
301
297
|
'S3_ENDPOINT',
|
|
302
298
|
'S3_BUCKET',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ToolLoopAgent } from 'ai'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { aiGatewayOpenRouterResponseHealingModel } from '../ai-gateway/ai-gateway'
|
|
4
|
+
import { buildAiGatewayCacheHeaders } from '../ai-gateway/cache-headers'
|
|
5
5
|
import {
|
|
6
6
|
OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
|
|
7
7
|
OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
|
|
@@ -40,8 +40,8 @@ Return valid data for:
|
|
|
40
40
|
export function createContextCompactionAgent(options: CreateHelperToolLoopAgentOptions) {
|
|
41
41
|
return new ToolLoopAgent({
|
|
42
42
|
id: 'context-compaction',
|
|
43
|
-
model:
|
|
44
|
-
headers:
|
|
43
|
+
model: aiGatewayOpenRouterResponseHealingModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
|
|
44
|
+
headers: buildAiGatewayCacheHeaders('context-compaction'),
|
|
45
45
|
providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
|
|
46
46
|
...resolveHelperAgentOptions(options, { instructions: CONTEXT_COMPACTION_PROMPT }),
|
|
47
47
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ToolLoopAgent } from 'ai'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { aiGatewayOpenRouterResponseHealingModel } from '../ai-gateway/ai-gateway'
|
|
4
|
+
import { buildAiGatewayCacheHeaders } from '../ai-gateway/cache-headers'
|
|
5
5
|
import {
|
|
6
6
|
OPENROUTER_LOW_REASONING_PROVIDER_OPTIONS,
|
|
7
7
|
OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
|
|
@@ -32,8 +32,8 @@ Set every item.relevance as a string; use empty string when no reason is needed.
|
|
|
32
32
|
export function createMemoryRerankerAgent(options: CreateHelperToolLoopAgentOptions) {
|
|
33
33
|
return new ToolLoopAgent({
|
|
34
34
|
id: 'memory-reranker',
|
|
35
|
-
model:
|
|
36
|
-
headers:
|
|
35
|
+
model: aiGatewayOpenRouterResponseHealingModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
|
|
36
|
+
headers: buildAiGatewayCacheHeaders('memory-reranker'),
|
|
37
37
|
providerOptions: OPENROUTER_LOW_REASONING_PROVIDER_OPTIONS,
|
|
38
38
|
...resolveHelperAgentOptions(options),
|
|
39
39
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ToolLoopAgent } from 'ai'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { aiGatewayOpenRouterResponseHealingModel } from '../ai-gateway/ai-gateway'
|
|
4
|
+
import { buildAiGatewayCacheHeaders } from '../ai-gateway/cache-headers'
|
|
5
5
|
import {
|
|
6
6
|
OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
|
|
7
7
|
OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
|
|
@@ -52,8 +52,8 @@ Return only the schema fields with no extra formatting.
|
|
|
52
52
|
export function createOrgMemoryAgent(options: CreateHelperToolLoopAgentOptions) {
|
|
53
53
|
return new ToolLoopAgent({
|
|
54
54
|
id: 'org-memory',
|
|
55
|
-
model:
|
|
56
|
-
headers:
|
|
55
|
+
model: aiGatewayOpenRouterResponseHealingModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
|
|
56
|
+
headers: buildAiGatewayCacheHeaders('org-memory'),
|
|
57
57
|
providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
|
|
58
58
|
...resolveHelperAgentOptions(options),
|
|
59
59
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ToolLoopAgent } from 'ai'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { aiGatewayModel } from '../ai-gateway/ai-gateway'
|
|
4
|
+
import { buildAiGatewayCacheHeaders } from '../ai-gateway/cache-headers'
|
|
5
5
|
import { getLeadAgentDisplayName } from '../config/agent-defaults'
|
|
6
6
|
import {
|
|
7
7
|
OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
|
|
@@ -79,8 +79,8 @@ Return only the title text. No quotes, labels, JSON, markdown, or explanation.
|
|
|
79
79
|
export function createRecentActivityTitleRefinerAgent(options: CreateHelperToolLoopAgentOptions) {
|
|
80
80
|
return new ToolLoopAgent({
|
|
81
81
|
id: 'recent-activity-title-refiner',
|
|
82
|
-
model:
|
|
83
|
-
headers:
|
|
82
|
+
model: aiGatewayModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
|
|
83
|
+
headers: buildAiGatewayCacheHeaders('recent-activity-title-refiner'),
|
|
84
84
|
providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
|
|
85
85
|
...resolveHelperAgentOptions(options, {
|
|
86
86
|
instructions: buildRecentActivityTitleRefinerPrompt(),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ToolLoopAgent } from 'ai'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { aiGatewayOpenRouterResponseHealingModel } from '../ai-gateway/ai-gateway'
|
|
4
|
+
import { buildAiGatewayCacheHeaders } from '../ai-gateway/cache-headers'
|
|
5
5
|
import {
|
|
6
6
|
OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
|
|
7
7
|
OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
|
|
@@ -66,8 +66,8 @@ Return only schema fields.
|
|
|
66
66
|
export function createRegularChatMemoryDigestAgent(options: CreateHelperToolLoopAgentOptions) {
|
|
67
67
|
return new ToolLoopAgent({
|
|
68
68
|
id: 'regular-chat-memory-digest',
|
|
69
|
-
model:
|
|
70
|
-
headers:
|
|
69
|
+
model: aiGatewayOpenRouterResponseHealingModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
|
|
70
|
+
headers: buildAiGatewayCacheHeaders('regular-chat-memory-digest'),
|
|
71
71
|
providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
|
|
72
72
|
...resolveHelperAgentOptions(options, {
|
|
73
73
|
instructions: regularChatMemoryDigestPrompt,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ToolLoopAgent } from 'ai'
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { aiGatewayOpenRouterResponseHealingModel } from '../ai-gateway/ai-gateway'
|
|
5
|
+
import { buildAiGatewayCacheHeaders } from '../ai-gateway/cache-headers'
|
|
6
6
|
import {
|
|
7
7
|
OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
|
|
8
8
|
OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
|
|
@@ -79,8 +79,8 @@ export type SkillCandidate = z.infer<typeof SkillCandidateSchema>
|
|
|
79
79
|
export function createSkillExtractorAgent(options: CreateHelperToolLoopAgentOptions) {
|
|
80
80
|
return new ToolLoopAgent({
|
|
81
81
|
id: 'skill-extractor',
|
|
82
|
-
model:
|
|
83
|
-
headers:
|
|
82
|
+
model: aiGatewayOpenRouterResponseHealingModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
|
|
83
|
+
headers: buildAiGatewayCacheHeaders('skill-extractor'),
|
|
84
84
|
providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
|
|
85
85
|
...resolveHelperAgentOptions(options, {
|
|
86
86
|
instructions: skillExtractorPrompt,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ToolLoopAgent } from 'ai'
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { aiGatewayOpenRouterResponseHealingModel } from '../ai-gateway/ai-gateway'
|
|
5
|
+
import { buildAiGatewayCacheHeaders } from '../ai-gateway/cache-headers'
|
|
6
6
|
import {
|
|
7
7
|
OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
|
|
8
8
|
OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
|
|
@@ -69,8 +69,8 @@ export const SkillManagerOutputSchema = z.object({
|
|
|
69
69
|
export function createSkillManagerAgent(options: CreateHelperToolLoopAgentOptions) {
|
|
70
70
|
return new ToolLoopAgent({
|
|
71
71
|
id: 'skill-manager',
|
|
72
|
-
model:
|
|
73
|
-
headers:
|
|
72
|
+
model: aiGatewayOpenRouterResponseHealingModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
|
|
73
|
+
headers: buildAiGatewayCacheHeaders('skill-manager'),
|
|
74
74
|
providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
|
|
75
75
|
...resolveHelperAgentOptions(options, {
|
|
76
76
|
instructions: skillManagerPrompt,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ToolLoopAgent } from 'ai'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { aiGatewayModel } from '../ai-gateway/ai-gateway'
|
|
4
|
+
import { buildAiGatewayCacheHeaders } from '../ai-gateway/cache-headers'
|
|
5
5
|
import {
|
|
6
6
|
OPENROUTER_FAST_REASONING_MODEL_ID,
|
|
7
7
|
OPENROUTER_MINIMAL_REASONING_PROVIDER_OPTIONS,
|
|
@@ -33,8 +33,8 @@ Return only the title text. No quotes, no labels, no explanation.
|
|
|
33
33
|
export function createWorkstreamTitleGeneratorAgent(options: CreateHelperToolLoopAgentOptions) {
|
|
34
34
|
return new ToolLoopAgent({
|
|
35
35
|
id: 'workstream-title-generator',
|
|
36
|
-
model:
|
|
37
|
-
headers:
|
|
36
|
+
model: aiGatewayModel(OPENROUTER_FAST_REASONING_MODEL_ID),
|
|
37
|
+
headers: buildAiGatewayCacheHeaders('workstream-title-generator'),
|
|
38
38
|
providerOptions: OPENROUTER_MINIMAL_REASONING_PROVIDER_OPTIONS,
|
|
39
39
|
...resolveHelperAgentOptions(options, {
|
|
40
40
|
instructions: WORKSTREAM_TITLE_GENERATOR_PROMPT,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { aiGatewayChatModel } from '../ai-gateway/ai-gateway'
|
|
2
|
+
import { buildAiGatewayCacheHeaders } from '../ai-gateway/cache-headers'
|
|
3
3
|
import {
|
|
4
4
|
OPENROUTER_MEDIUM_REASONING_PROVIDER_OPTIONS,
|
|
5
5
|
OPENROUTER_WEB_RESEARCH_MODEL_ID,
|
|
@@ -13,9 +13,9 @@ export const researchTopicTool = createDelegatedAgentTool({
|
|
|
13
13
|
id: 'researchTopic',
|
|
14
14
|
description:
|
|
15
15
|
'Delegate a research task to a dedicated research agent that searches the web, fetches pages, and returns a synthesized markdown report. Call multiple instances in parallel for broad research across different topics.',
|
|
16
|
-
model: () =>
|
|
16
|
+
model: () => aiGatewayChatModel(OPENROUTER_WEB_RESEARCH_MODEL_ID),
|
|
17
17
|
providerOptions: OPENROUTER_MEDIUM_REASONING_PROVIDER_OPTIONS,
|
|
18
|
-
headers:
|
|
18
|
+
headers: buildAiGatewayCacheHeaders('researchTopic'),
|
|
19
19
|
instructions: RESEARCHER_PROMPT,
|
|
20
20
|
tools: { searchWeb: searchWebTool.create(), fetchWebpage: fetchWebpageTool.create() },
|
|
21
21
|
})
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
const BIFROST_CACHE_KEY_HEADER = 'x-bf-cache-key'
|
|
2
|
-
const BIFROST_CACHE_TTL_HEADER = 'x-bf-cache-ttl'
|
|
3
|
-
|
|
4
|
-
export function buildBifrostCacheHeaders(cacheKey: string, ttl?: string): Record<string, string> {
|
|
5
|
-
const headers: Record<string, string> = { [BIFROST_CACHE_KEY_HEADER]: cacheKey }
|
|
6
|
-
if (ttl) headers[BIFROST_CACHE_TTL_HEADER] = ttl
|
|
7
|
-
return headers
|
|
8
|
-
}
|