@lota-sdk/core 0.1.17 → 0.1.19

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.
@@ -1,325 +0,0 @@
1
- import { devToolsMiddleware } from '@ai-sdk/devtools'
2
- import { createOpenAI } from '@ai-sdk/openai'
3
- import { wrapLanguageModel } from 'ai'
4
- import type { LanguageModelMiddleware } from 'ai'
5
-
6
- import { getRequiredEnv } from '../utils/env'
7
- import { isRecord, readString } from '../utils/string'
8
-
9
- type BifrostLanguageModel = Parameters<typeof wrapLanguageModel>[0]['model']
10
- type BifrostExtraParams = Record<string, unknown>
11
- type BifrostChatResponse = { body?: unknown }
12
- type BifrostTransformParamsOptions = Parameters<NonNullable<LanguageModelMiddleware['transformParams']>>[0]
13
- type WrapStreamOptions = Parameters<NonNullable<LanguageModelMiddleware['wrapStream']>>[0]
14
- type BifrostCallOptions = WrapStreamOptions['params']
15
- type BifrostGenerateResult = Awaited<ReturnType<WrapStreamOptions['doGenerate']>>
16
- type BifrostStreamResult = Awaited<ReturnType<WrapStreamOptions['doStream']>>
17
- type BifrostGeneratedContent = BifrostGenerateResult['content'][number]
18
- type BifrostStreamPart = BifrostStreamResult['stream'] extends ReadableStream<infer T> ? T : never
19
-
20
- const OPENROUTER_RESPONSE_HEALING_EXTRA_PARAMS = {
21
- plugins: [{ id: 'response-healing' }],
22
- } as const satisfies BifrostExtraParams
23
-
24
- function readRequiredGatewayEnv(name: 'AI_GATEWAY_KEY' | 'AI_GATEWAY_URL'): string {
25
- return getRequiredEnv(name).trim()
26
- }
27
-
28
- function readReasoningDetailsText(value: unknown): string | null {
29
- if (!Array.isArray(value)) return null
30
-
31
- const textParts = value
32
- .map((item) => (isRecord(item) ? readString(item.text) : null))
33
- .filter((item): item is string => item !== null)
34
-
35
- if (textParts.length === 0) return null
36
-
37
- return textParts.join('\n\n')
38
- }
39
-
40
- function readReasoningDeltaText(value: unknown): string | null {
41
- return typeof value === 'string' && value.length > 0 ? value : null
42
- }
43
-
44
- function readBifrostChatReasoningText(message: Record<string, unknown>): string | null {
45
- return (
46
- readString(message.reasoning) ??
47
- readString(message.reasoning_content) ??
48
- readReasoningDetailsText(message.reasoning_details)
49
- )
50
- }
51
-
52
- export function extractBifrostChatReasoningText(responseBody: unknown): string | null {
53
- if (!isRecord(responseBody) || !Array.isArray(responseBody.choices)) return null
54
-
55
- for (const choice of responseBody.choices) {
56
- if (!isRecord(choice) || !isRecord(choice.message)) continue
57
-
58
- const reasoningText = readBifrostChatReasoningText(choice.message)
59
- if (reasoningText) return reasoningText
60
- }
61
-
62
- return null
63
- }
64
-
65
- export function extractBifrostChatReasoningDeltaText(rawChunk: unknown): string | null {
66
- if (!isRecord(rawChunk) || !Array.isArray(rawChunk.choices)) return null
67
-
68
- for (const choice of rawChunk.choices) {
69
- if (!isRecord(choice) || !isRecord(choice.delta)) continue
70
-
71
- const reasoningText =
72
- readReasoningDeltaText(choice.delta.reasoning) ??
73
- readReasoningDeltaText(choice.delta.reasoning_content) ??
74
- readReasoningDetailsText(choice.delta.reasoning_details)
75
- if (reasoningText) return reasoningText
76
- }
77
-
78
- return null
79
- }
80
-
81
- type BifrostResponsesReasoningDelta = { id: string; delta: string; itemId: string }
82
-
83
- export function extractBifrostResponsesReasoningDelta(rawChunk: unknown): BifrostResponsesReasoningDelta | null {
84
- if (!isRecord(rawChunk) || rawChunk.type !== 'response.reasoning_summary_text.delta') return null
85
- if ('summary_index' in rawChunk) return null
86
-
87
- const itemId = readString(rawChunk.item_id)
88
- const delta = readReasoningDeltaText(rawChunk.delta)
89
- if (!itemId || !delta) return null
90
-
91
- return { id: `${itemId}:0`, delta, itemId }
92
- }
93
-
94
- export function injectBifrostChatReasoningContent(
95
- content: readonly BifrostGeneratedContent[],
96
- response?: BifrostChatResponse,
97
- ): BifrostGeneratedContent[] {
98
- if (content.some((part) => part.type === 'reasoning')) {
99
- return [...content]
100
- }
101
-
102
- const reasoningText = extractBifrostChatReasoningText(response?.body)
103
- if (!reasoningText) return [...content]
104
-
105
- return [{ type: 'reasoning', text: reasoningText }, ...content]
106
- }
107
-
108
- function isReasoningEnabled(params: BifrostCallOptions): boolean {
109
- if (!isRecord(params.providerOptions) || !isRecord(params.providerOptions.openai)) return false
110
-
111
- const openaiOptions = params.providerOptions.openai
112
- if (openaiOptions.forceReasoning === true) return true
113
- if (typeof openaiOptions.reasoningSummary === 'string' && openaiOptions.reasoningSummary.length > 0) return true
114
- return typeof openaiOptions.reasoningEffort === 'string' && openaiOptions.reasoningEffort !== 'none'
115
- }
116
-
117
- function shouldCloseInjectedReasoning(chunk: BifrostStreamPart): boolean {
118
- return chunk.type !== 'stream-start' && chunk.type !== 'response-metadata' && chunk.type !== 'raw'
119
- }
120
-
121
- export function injectBifrostChatReasoningStream(
122
- stream: ReadableStream<BifrostStreamPart>,
123
- ): ReadableStream<BifrostStreamPart> {
124
- const reasoningId = 'bifrost-reasoning-0'
125
- let reasoningOpen = false
126
- let reasoningClosed = false
127
-
128
- return stream.pipeThrough(
129
- new TransformStream<BifrostStreamPart, BifrostStreamPart>({
130
- transform(chunk, controller) {
131
- const closeReasoning = () => {
132
- if (!reasoningOpen || reasoningClosed) return
133
-
134
- controller.enqueue({ type: 'reasoning-end', id: reasoningId } satisfies BifrostStreamPart)
135
- reasoningClosed = true
136
- }
137
-
138
- if (chunk.type === 'raw') {
139
- const reasoningDelta = reasoningClosed ? null : extractBifrostChatReasoningDeltaText(chunk.rawValue)
140
- controller.enqueue(chunk)
141
-
142
- if (reasoningDelta) {
143
- if (!reasoningOpen) {
144
- controller.enqueue({ type: 'reasoning-start', id: reasoningId } satisfies BifrostStreamPart)
145
- reasoningOpen = true
146
- }
147
-
148
- controller.enqueue({
149
- type: 'reasoning-delta',
150
- id: reasoningId,
151
- delta: reasoningDelta,
152
- } satisfies BifrostStreamPart)
153
- }
154
- return
155
- }
156
-
157
- if (shouldCloseInjectedReasoning(chunk)) {
158
- closeReasoning()
159
- }
160
-
161
- controller.enqueue(chunk)
162
- },
163
- flush(controller) {
164
- if (!reasoningOpen || reasoningClosed) return
165
- controller.enqueue({ type: 'reasoning-end', id: reasoningId } satisfies BifrostStreamPart)
166
- },
167
- }),
168
- )
169
- }
170
-
171
- export function injectBifrostResponsesReasoningStream(
172
- stream: ReadableStream<BifrostStreamPart>,
173
- ): ReadableStream<BifrostStreamPart> {
174
- return stream.pipeThrough(
175
- new TransformStream<BifrostStreamPart, BifrostStreamPart>({
176
- transform(chunk, controller) {
177
- controller.enqueue(chunk)
178
-
179
- if (chunk.type !== 'raw') return
180
-
181
- const reasoningDelta = extractBifrostResponsesReasoningDelta(chunk.rawValue)
182
- if (!reasoningDelta) return
183
-
184
- controller.enqueue({
185
- type: 'reasoning-delta',
186
- id: reasoningDelta.id,
187
- delta: reasoningDelta.delta,
188
- providerMetadata: { openai: { itemId: reasoningDelta.itemId } },
189
- } satisfies BifrostStreamPart)
190
- },
191
- }),
192
- )
193
- }
194
-
195
- function addBifrostReasoningRawChunks(
196
- params: BifrostCallOptions,
197
- type: BifrostTransformParamsOptions['type'],
198
- ): BifrostCallOptions {
199
- if (type !== 'stream' || !isReasoningEnabled(params) || params.includeRawChunks === true) {
200
- return params
201
- }
202
-
203
- return { ...params, includeRawChunks: true }
204
- }
205
-
206
- export function injectBifrostExtraParamsRequestBody(
207
- body: BodyInit | null | undefined,
208
- extraParams: BifrostExtraParams,
209
- ): BodyInit | null | undefined {
210
- if (typeof body !== 'string') return body
211
-
212
- let parsed: unknown
213
- try {
214
- parsed = JSON.parse(body)
215
- } catch {
216
- return body
217
- }
218
-
219
- if (!isRecord(parsed)) return body
220
-
221
- const mergedExtraParams = isRecord(parsed.extra_params)
222
- ? { ...parsed.extra_params, ...extraParams }
223
- : { ...extraParams }
224
-
225
- return JSON.stringify({ ...parsed, extra_params: mergedExtraParams })
226
- }
227
-
228
- function createBifrostFetchWithExtraParams(extraParams: BifrostExtraParams): typeof fetch {
229
- const fetchWithExtraParams = (input: RequestInfo | URL, init?: RequestInit | BunFetchRequestInit) =>
230
- globalThis.fetch(input, { ...init, body: injectBifrostExtraParamsRequestBody(init?.body, extraParams) })
231
-
232
- return Object.assign(fetchWithExtraParams, { preconnect: globalThis.fetch.preconnect.bind(globalThis.fetch) })
233
- }
234
-
235
- function createBifrostProvider(extraParams?: BifrostExtraParams) {
236
- const apiKey = readRequiredGatewayEnv('AI_GATEWAY_KEY')
237
- if (!apiKey.startsWith('sk-bf-')) {
238
- throw new Error('[bifrost] AI_GATEWAY_KEY must use the Bifrost virtual-key format (sk-bf-*).')
239
- }
240
-
241
- return createOpenAI({
242
- baseURL: readRequiredGatewayEnv('AI_GATEWAY_URL'),
243
- apiKey,
244
- headers: { 'x-bf-vk': apiKey, ...(extraParams ? { 'x-bf-passthrough-extra-params': 'true' } : {}) },
245
- ...(extraParams ? { fetch: createBifrostFetchWithExtraParams(extraParams) } : {}),
246
- })
247
- }
248
-
249
- function withBifrostDevTools<TModel extends BifrostLanguageModel>(model: TModel): TModel {
250
- return wrapLanguageModel({ model, middleware: devToolsMiddleware() }) as TModel
251
- }
252
-
253
- let provider: ReturnType<typeof createOpenAI> | null = null
254
- let openRouterResponseHealingProvider: ReturnType<typeof createOpenAI> | null = null
255
-
256
- export function getBifrostProvider() {
257
- if (provider) return provider
258
-
259
- provider = createBifrostProvider()
260
-
261
- return provider
262
- }
263
-
264
- export function getBifrostOpenRouterResponseHealingProvider() {
265
- if (openRouterResponseHealingProvider) return openRouterResponseHealingProvider
266
-
267
- openRouterResponseHealingProvider = createBifrostProvider(OPENROUTER_RESPONSE_HEALING_EXTRA_PARAMS)
268
-
269
- return openRouterResponseHealingProvider
270
- }
271
-
272
- export function bifrostModel(modelId: string) {
273
- return withBifrostDevTools(
274
- wrapLanguageModel({
275
- model: getBifrostProvider()(modelId),
276
- middleware: {
277
- specificationVersion: 'v3',
278
- transformParams: async ({ params, type }) => addBifrostReasoningRawChunks(params, type),
279
- wrapStream: async ({ doStream, params }) => {
280
- const result = await doStream()
281
- if (!isReasoningEnabled(params)) return result
282
-
283
- return { ...result, stream: injectBifrostResponsesReasoningStream(result.stream) }
284
- },
285
- },
286
- }),
287
- )
288
- }
289
-
290
- export function bifrostOpenRouterResponseHealingModel(modelId: string) {
291
- return withBifrostDevTools(getBifrostOpenRouterResponseHealingProvider()(modelId))
292
- }
293
-
294
- export function bifrostChatModel(modelId: string) {
295
- return withBifrostDevTools(
296
- wrapLanguageModel({
297
- model: getBifrostProvider().chat(modelId),
298
- middleware: {
299
- specificationVersion: 'v3',
300
- transformParams: async ({ params, type }) => addBifrostReasoningRawChunks(params, type),
301
- wrapGenerate: async ({ doGenerate }) => {
302
- const result = await doGenerate()
303
-
304
- return {
305
- ...result,
306
- content: injectBifrostChatReasoningContent(
307
- result.content,
308
- result.response as BifrostChatResponse | undefined,
309
- ),
310
- }
311
- },
312
- wrapStream: async ({ doStream, params }) => {
313
- const result = await doStream()
314
- if (!isReasoningEnabled(params)) return result
315
-
316
- return { ...result, stream: injectBifrostChatReasoningStream(result.stream) }
317
- },
318
- },
319
- }),
320
- )
321
- }
322
-
323
- export function bifrostEmbeddingModel(modelId: string) {
324
- return getBifrostProvider().embeddingModel(modelId)
325
- }
@@ -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
- }