@tanstack/ai-lovable 0.0.0
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/LICENSE +21 -0
- package/README.md +37 -0
- package/dist/esm/adapters/embedding.d.ts +16 -0
- package/dist/esm/adapters/embedding.js +66 -0
- package/dist/esm/adapters/embedding.js.map +1 -0
- package/dist/esm/adapters/factory.d.ts +16 -0
- package/dist/esm/adapters/factory.js +28 -0
- package/dist/esm/adapters/factory.js.map +1 -0
- package/dist/esm/adapters/image.d.ts +26 -0
- package/dist/esm/adapters/image.js +145 -0
- package/dist/esm/adapters/image.js.map +1 -0
- package/dist/esm/adapters/responses-text.d.ts +24 -0
- package/dist/esm/adapters/responses-text.js +30 -0
- package/dist/esm/adapters/responses-text.js.map +1 -0
- package/dist/esm/adapters/summarize.d.ts +9 -0
- package/dist/esm/adapters/summarize.js +17 -0
- package/dist/esm/adapters/summarize.js.map +1 -0
- package/dist/esm/adapters/text.d.ts +20 -0
- package/dist/esm/adapters/text.js +21 -0
- package/dist/esm/adapters/text.js.map +1 -0
- package/dist/esm/adapters/transcription.d.ts +19 -0
- package/dist/esm/adapters/transcription.js +123 -0
- package/dist/esm/adapters/transcription.js.map +1 -0
- package/dist/esm/adapters/tts.d.ts +16 -0
- package/dist/esm/adapters/tts.js +69 -0
- package/dist/esm/adapters/tts.js.map +1 -0
- package/dist/esm/adapters/video.d.ts +41 -0
- package/dist/esm/adapters/video.js +164 -0
- package/dist/esm/adapters/video.js.map +1 -0
- package/dist/esm/audio/transcription-provider-options.d.ts +6 -0
- package/dist/esm/audio/tts-provider-options.d.ts +8 -0
- package/dist/esm/byok.d.ts +1 -0
- package/dist/esm/byok.js +11 -0
- package/dist/esm/byok.js.map +1 -0
- package/dist/esm/embedding/embedding-provider-options.d.ts +9 -0
- package/dist/esm/image/image-input-to-file.d.ts +8 -0
- package/dist/esm/image/image-input-to-file.js +47 -0
- package/dist/esm/image/image-input-to-file.js.map +1 -0
- package/dist/esm/image/image-provider-options.d.ts +12 -0
- package/dist/esm/index.d.ts +26 -0
- package/dist/esm/index.js +12 -0
- package/dist/esm/message-types.d.ts +44 -0
- package/dist/esm/model-meta.d.ts +71 -0
- package/dist/esm/model-meta.js +63 -0
- package/dist/esm/model-meta.js.map +1 -0
- package/dist/esm/text/responses-provider-options.d.ts +3 -0
- package/dist/esm/text/text-provider-options.d.ts +17 -0
- package/dist/esm/utils/client.d.ts +14 -0
- package/dist/esm/utils/client.js +35 -0
- package/dist/esm/utils/client.js.map +1 -0
- package/dist/esm/video/video-provider-options.d.ts +19 -0
- package/dist/esm/video/video-provider-options.js +47 -0
- package/dist/esm/video/video-provider-options.js.map +1 -0
- package/package.json +79 -0
- package/src/adapters/embedding.ts +102 -0
- package/src/adapters/factory.ts +78 -0
- package/src/adapters/image.ts +304 -0
- package/src/adapters/responses-text.ts +66 -0
- package/src/adapters/summarize.ts +35 -0
- package/src/adapters/text.ts +49 -0
- package/src/adapters/transcription.ts +226 -0
- package/src/adapters/tts.ts +97 -0
- package/src/adapters/video.ts +303 -0
- package/src/audio/transcription-provider-options.ts +7 -0
- package/src/audio/tts-provider-options.ts +21 -0
- package/src/byok.ts +7 -0
- package/src/embedding/embedding-provider-options.ts +9 -0
- package/src/image/image-input-to-file.ts +81 -0
- package/src/image/image-provider-options.ts +17 -0
- package/src/index.ts +125 -0
- package/src/message-types.ts +45 -0
- package/src/model-meta.ts +161 -0
- package/src/text/responses-provider-options.ts +8 -0
- package/src/text/text-provider-options.ts +18 -0
- package/src/utils/client.ts +50 -0
- package/src/video/video-provider-options.ts +128 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { getLovableApiKeyFromEnv } from '../utils/client'
|
|
2
|
+
import { LovableTextAdapter } from './text'
|
|
3
|
+
import { LovableResponsesTextAdapter } from './responses-text'
|
|
4
|
+
import type { LovableClientConfig } from '../utils/client'
|
|
5
|
+
import type { LovableTextConfig } from './text'
|
|
6
|
+
import type { LovableResponsesTextConfig } from './responses-text'
|
|
7
|
+
import type { LovableModelId } from '../model-meta'
|
|
8
|
+
|
|
9
|
+
export type LovableTextApi = 'responses' | 'chat' | 'chat-completions'
|
|
10
|
+
|
|
11
|
+
/** Config for the branching factory's Responses mode (default, or api: 'responses'). */
|
|
12
|
+
export type LovableResponsesApiConfig = Omit<
|
|
13
|
+
LovableResponsesTextConfig,
|
|
14
|
+
'apiKey'
|
|
15
|
+
> & {
|
|
16
|
+
api?: 'responses'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Config for the branching factory's Chat Completions mode (api required). */
|
|
20
|
+
export type LovableChatApiConfig = Omit<LovableTextConfig, 'apiKey'> & {
|
|
21
|
+
api: 'chat' | 'chat-completions'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type AnyLovableTextAdapter<TModel extends LovableModelId> =
|
|
25
|
+
| LovableResponsesTextAdapter<TModel>
|
|
26
|
+
| LovableTextAdapter<TModel>
|
|
27
|
+
|
|
28
|
+
function stripApi<T extends { api?: unknown }>(config: T): Omit<T, 'api'> {
|
|
29
|
+
const { api, ...rest } = config
|
|
30
|
+
void api
|
|
31
|
+
return rest
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function build<TModel extends LovableModelId>(
|
|
35
|
+
model: TModel,
|
|
36
|
+
config: LovableClientConfig & { api?: LovableTextApi },
|
|
37
|
+
): AnyLovableTextAdapter<TModel> {
|
|
38
|
+
if (config.api === 'chat' || config.api === 'chat-completions') {
|
|
39
|
+
return new LovableTextAdapter(stripApi(config), model)
|
|
40
|
+
}
|
|
41
|
+
return new LovableResponsesTextAdapter(stripApi(config), model)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function createLovableText<TModel extends LovableModelId>(
|
|
45
|
+
model: TModel,
|
|
46
|
+
apiKey: string,
|
|
47
|
+
config?: LovableResponsesApiConfig,
|
|
48
|
+
): LovableResponsesTextAdapter<TModel>
|
|
49
|
+
export function createLovableText<TModel extends LovableModelId>(
|
|
50
|
+
model: TModel,
|
|
51
|
+
apiKey: string,
|
|
52
|
+
config: LovableChatApiConfig,
|
|
53
|
+
): LovableTextAdapter<TModel>
|
|
54
|
+
export function createLovableText<TModel extends LovableModelId>(
|
|
55
|
+
model: TModel,
|
|
56
|
+
apiKey: string,
|
|
57
|
+
config?: LovableResponsesApiConfig | LovableChatApiConfig,
|
|
58
|
+
): AnyLovableTextAdapter<TModel> {
|
|
59
|
+
return build(model, { ...config, apiKey })
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function lovableText<TModel extends LovableModelId>(
|
|
63
|
+
model: TModel,
|
|
64
|
+
config?: LovableResponsesApiConfig,
|
|
65
|
+
): LovableResponsesTextAdapter<TModel>
|
|
66
|
+
export function lovableText<TModel extends LovableModelId>(
|
|
67
|
+
model: TModel,
|
|
68
|
+
config: LovableChatApiConfig,
|
|
69
|
+
): LovableTextAdapter<TModel>
|
|
70
|
+
export function lovableText<TModel extends LovableModelId>(
|
|
71
|
+
model: TModel,
|
|
72
|
+
config?: LovableResponsesApiConfig | LovableChatApiConfig,
|
|
73
|
+
): AnyLovableTextAdapter<TModel> {
|
|
74
|
+
return build(model, {
|
|
75
|
+
...config,
|
|
76
|
+
apiKey: getLovableApiKeyFromEnv(),
|
|
77
|
+
})
|
|
78
|
+
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import OpenAI from 'openai'
|
|
2
|
+
import { resolveMediaPrompt } from '@tanstack/ai'
|
|
3
|
+
import { BaseImageAdapter } from '@tanstack/ai/adapters'
|
|
4
|
+
import { toRunErrorPayload } from '@tanstack/ai/adapter-internals'
|
|
5
|
+
import { buildImagesUsage } from '@tanstack/openai-base'
|
|
6
|
+
import { generateId } from '@tanstack/ai-utils'
|
|
7
|
+
import {
|
|
8
|
+
getLovableApiKeyFromEnv,
|
|
9
|
+
openaiRequestOptions,
|
|
10
|
+
withLovableDefaults,
|
|
11
|
+
} from '../utils/client'
|
|
12
|
+
import { imagePartToFile } from '../image/image-input-to-file'
|
|
13
|
+
import type {
|
|
14
|
+
GeneratedImage,
|
|
15
|
+
ImageGenerationOptions,
|
|
16
|
+
ImageGenerationResult,
|
|
17
|
+
ImagePart,
|
|
18
|
+
MediaInputMetadata,
|
|
19
|
+
} from '@tanstack/ai'
|
|
20
|
+
import type OpenAI_SDK from 'openai'
|
|
21
|
+
import type {
|
|
22
|
+
LovableImageModel,
|
|
23
|
+
LovableImageModelInputModalitiesByName,
|
|
24
|
+
LovableImageModelProviderOptionsByName,
|
|
25
|
+
LovableImageModelSizeByName,
|
|
26
|
+
} from '../model-meta'
|
|
27
|
+
import type { LovableImageProviderOptions } from '../image/image-provider-options'
|
|
28
|
+
import type { LovableClientConfig } from '../utils/client'
|
|
29
|
+
|
|
30
|
+
const MAX_EDIT_IMAGES = 16
|
|
31
|
+
|
|
32
|
+
export interface LovableImageConfig extends LovableClientConfig {
|
|
33
|
+
/**
|
|
34
|
+
* Opt into fetching HTTP(S) image URL inputs for image edits. The gateway
|
|
35
|
+
* `/images/edits` endpoint requires uploaded file bytes, so an HTTP(S) URL
|
|
36
|
+
* has to be downloaded and buffered in memory. When `false` (the default),
|
|
37
|
+
* HTTP(S) URL image inputs throw.
|
|
38
|
+
*/
|
|
39
|
+
allowUrlFetch?: boolean
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function supportsImageMask(model: string): boolean {
|
|
43
|
+
return model.startsWith('openai/')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function generatedImagesFromResponse(
|
|
47
|
+
data:
|
|
48
|
+
| Array<{
|
|
49
|
+
b64_json?: string | null
|
|
50
|
+
url?: string | null
|
|
51
|
+
revised_prompt?: string | null
|
|
52
|
+
}>
|
|
53
|
+
| undefined
|
|
54
|
+
| null,
|
|
55
|
+
emptyMessage: string,
|
|
56
|
+
): Array<GeneratedImage> {
|
|
57
|
+
const images: Array<GeneratedImage> = (data ?? []).flatMap(
|
|
58
|
+
(item): Array<GeneratedImage> => {
|
|
59
|
+
const revisedPromptField =
|
|
60
|
+
item.revised_prompt !== undefined && item.revised_prompt !== null
|
|
61
|
+
? { revisedPrompt: item.revised_prompt }
|
|
62
|
+
: {}
|
|
63
|
+
if (item.b64_json) {
|
|
64
|
+
return [{ b64Json: item.b64_json, ...revisedPromptField }]
|
|
65
|
+
}
|
|
66
|
+
if (item.url) {
|
|
67
|
+
return [{ url: item.url, ...revisedPromptField }]
|
|
68
|
+
}
|
|
69
|
+
return []
|
|
70
|
+
},
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
if (images.length === 0) {
|
|
74
|
+
throw new Error(emptyMessage)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return images
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export class LovableImageAdapter<
|
|
81
|
+
TModel extends LovableImageModel,
|
|
82
|
+
> extends BaseImageAdapter<
|
|
83
|
+
TModel,
|
|
84
|
+
LovableImageProviderOptions,
|
|
85
|
+
LovableImageModelProviderOptionsByName,
|
|
86
|
+
LovableImageModelSizeByName,
|
|
87
|
+
LovableImageModelInputModalitiesByName
|
|
88
|
+
> {
|
|
89
|
+
override readonly kind = 'image' as const
|
|
90
|
+
readonly name = 'lovable' as const
|
|
91
|
+
|
|
92
|
+
protected client: OpenAI
|
|
93
|
+
private readonly allowUrlFetch: boolean
|
|
94
|
+
|
|
95
|
+
constructor(config: LovableImageConfig, model: TModel) {
|
|
96
|
+
super(model, {})
|
|
97
|
+
const { allowUrlFetch, ...clientOptions } = config
|
|
98
|
+
this.client = new OpenAI(withLovableDefaults(clientOptions))
|
|
99
|
+
this.allowUrlFetch = allowUrlFetch ?? false
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async generateImages(
|
|
103
|
+
options: ImageGenerationOptions<LovableImageProviderOptions>,
|
|
104
|
+
): Promise<ImageGenerationResult> {
|
|
105
|
+
const { model, numberOfImages, size, modelOptions } = options
|
|
106
|
+
const resolved = resolveMediaPrompt(options.prompt)
|
|
107
|
+
const prompt = resolved.text
|
|
108
|
+
|
|
109
|
+
if (resolved.videos.length > 0) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`${this.name}.generateImages does not support video prompt parts (model: ${model}).`,
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
if (resolved.audios.length > 0) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`${this.name}.generateImages does not support audio prompt parts (model: ${model}).`,
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (resolved.images.length > 0) {
|
|
121
|
+
return this.editImages({
|
|
122
|
+
model,
|
|
123
|
+
prompt,
|
|
124
|
+
numberOfImages,
|
|
125
|
+
size,
|
|
126
|
+
modelOptions,
|
|
127
|
+
imageInputs: resolved.images,
|
|
128
|
+
logger: options.logger,
|
|
129
|
+
abortSignal: options.abortSignal,
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const request: OpenAI_SDK.Images.ImageGenerateParams = {
|
|
134
|
+
model,
|
|
135
|
+
prompt,
|
|
136
|
+
n: numberOfImages ?? 1,
|
|
137
|
+
...(modelOptions ?? {}),
|
|
138
|
+
}
|
|
139
|
+
if (size !== undefined) {
|
|
140
|
+
request.size = size
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
options.logger.request(
|
|
145
|
+
`activity=image provider=${this.name} model=${model} n=${request.n ?? 1} size=${request.size ?? 'default'}`,
|
|
146
|
+
{ provider: this.name, model },
|
|
147
|
+
)
|
|
148
|
+
const response = await this.client.images.generate(
|
|
149
|
+
{
|
|
150
|
+
...request,
|
|
151
|
+
stream: false,
|
|
152
|
+
},
|
|
153
|
+
openaiRequestOptions(options.abortSignal),
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
const images = generatedImagesFromResponse(
|
|
157
|
+
response.data,
|
|
158
|
+
`${this.name}: image response contained no images`,
|
|
159
|
+
)
|
|
160
|
+
const usage = buildImagesUsage(response.usage)
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
id: generateId(this.name),
|
|
164
|
+
model,
|
|
165
|
+
images,
|
|
166
|
+
...(usage ? { usage } : {}),
|
|
167
|
+
}
|
|
168
|
+
} catch (error: unknown) {
|
|
169
|
+
options.logger.errors(`${this.name}.generateImages fatal`, {
|
|
170
|
+
error: toRunErrorPayload(error, `${this.name}.generateImages failed`),
|
|
171
|
+
source: `${this.name}.generateImages`,
|
|
172
|
+
})
|
|
173
|
+
throw error
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private async editImages(args: {
|
|
178
|
+
model: string
|
|
179
|
+
prompt: string
|
|
180
|
+
numberOfImages?: number
|
|
181
|
+
size?: string
|
|
182
|
+
modelOptions?: LovableImageProviderOptions
|
|
183
|
+
imageInputs: ReadonlyArray<ImagePart<MediaInputMetadata>>
|
|
184
|
+
logger: ImageGenerationOptions<LovableImageProviderOptions>['logger']
|
|
185
|
+
abortSignal?: AbortSignal
|
|
186
|
+
}): Promise<ImageGenerationResult> {
|
|
187
|
+
const {
|
|
188
|
+
model,
|
|
189
|
+
prompt,
|
|
190
|
+
numberOfImages,
|
|
191
|
+
size,
|
|
192
|
+
modelOptions,
|
|
193
|
+
logger,
|
|
194
|
+
abortSignal,
|
|
195
|
+
} = args
|
|
196
|
+
|
|
197
|
+
const maskParts = args.imageInputs.filter(
|
|
198
|
+
(part) => part.metadata?.role === 'mask',
|
|
199
|
+
)
|
|
200
|
+
const sourceParts = args.imageInputs.filter(
|
|
201
|
+
(part) => part.metadata?.role !== 'mask',
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
if (maskParts.length > 0 && !supportsImageMask(model)) {
|
|
205
|
+
throw new Error(
|
|
206
|
+
`${this.name}: model "${model}" does not support mask image parts. Use an openai/ image model for masked edits.`,
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
if (maskParts.length > 1) {
|
|
210
|
+
throw new Error(
|
|
211
|
+
`${this.name}: only one input with metadata.role === 'mask' is supported per request.`,
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
if (sourceParts.length === 0) {
|
|
215
|
+
throw new Error(
|
|
216
|
+
`${this.name}: the prompt contained only mask image parts; at least one source image is required.`,
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
if (sourceParts.length > MAX_EDIT_IMAGES) {
|
|
220
|
+
throw new Error(
|
|
221
|
+
`${this.name}: model "${model}" accepts at most ${MAX_EDIT_IMAGES} source image(s); received ${sourceParts.length}.`,
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const sourceFiles = await Promise.all(
|
|
226
|
+
sourceParts.map((part, i) =>
|
|
227
|
+
imagePartToFile(part, `source-${i}`, this.allowUrlFetch, abortSignal),
|
|
228
|
+
),
|
|
229
|
+
)
|
|
230
|
+
const [firstSourceFile] = sourceFiles
|
|
231
|
+
const maskFile = maskParts[0]
|
|
232
|
+
? await imagePartToFile(
|
|
233
|
+
maskParts[0],
|
|
234
|
+
'mask',
|
|
235
|
+
this.allowUrlFetch,
|
|
236
|
+
abortSignal,
|
|
237
|
+
)
|
|
238
|
+
: undefined
|
|
239
|
+
|
|
240
|
+
const request: OpenAI_SDK.Images.ImageEditParamsNonStreaming = {
|
|
241
|
+
model,
|
|
242
|
+
prompt,
|
|
243
|
+
image:
|
|
244
|
+
firstSourceFile && sourceFiles.length === 1
|
|
245
|
+
? firstSourceFile
|
|
246
|
+
: sourceFiles,
|
|
247
|
+
n: numberOfImages ?? 1,
|
|
248
|
+
stream: false,
|
|
249
|
+
...((modelOptions ??
|
|
250
|
+
{}) as Partial<OpenAI_SDK.Images.ImageEditParamsNonStreaming>),
|
|
251
|
+
}
|
|
252
|
+
if (size !== undefined) {
|
|
253
|
+
request.size = size
|
|
254
|
+
}
|
|
255
|
+
if (maskFile) {
|
|
256
|
+
request.mask = maskFile
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
logger.request(
|
|
261
|
+
`activity=imageEdit provider=${this.name} model=${model} n=${request.n ?? 1} size=${request.size ?? 'default'} sources=${sourceFiles.length}${maskFile ? ' mask' : ''}`,
|
|
262
|
+
{ provider: this.name, model },
|
|
263
|
+
)
|
|
264
|
+
const response = await this.client.images.edit(
|
|
265
|
+
request,
|
|
266
|
+
openaiRequestOptions(abortSignal),
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
const images = generatedImagesFromResponse(
|
|
270
|
+
response.data,
|
|
271
|
+
`${this.name}: image edit response contained no images`,
|
|
272
|
+
)
|
|
273
|
+
const usage = buildImagesUsage(response.usage)
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
id: generateId(this.name),
|
|
277
|
+
model,
|
|
278
|
+
images,
|
|
279
|
+
...(usage ? { usage } : {}),
|
|
280
|
+
}
|
|
281
|
+
} catch (error: unknown) {
|
|
282
|
+
logger.errors(`${this.name}.editImages fatal`, {
|
|
283
|
+
error: toRunErrorPayload(error, `${this.name}.editImages failed`),
|
|
284
|
+
source: `${this.name}.editImages`,
|
|
285
|
+
})
|
|
286
|
+
throw error
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function createLovableImage<TModel extends LovableImageModel>(
|
|
292
|
+
model: TModel,
|
|
293
|
+
apiKey: string,
|
|
294
|
+
config?: Omit<LovableImageConfig, 'apiKey'>,
|
|
295
|
+
): LovableImageAdapter<TModel> {
|
|
296
|
+
return new LovableImageAdapter({ apiKey, ...config }, model)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function lovableImage<TModel extends LovableImageModel>(
|
|
300
|
+
model: TModel,
|
|
301
|
+
config?: Omit<LovableImageConfig, 'apiKey'>,
|
|
302
|
+
): LovableImageAdapter<TModel> {
|
|
303
|
+
return createLovableImage(model, getLovableApiKeyFromEnv(), config)
|
|
304
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import OpenAI from 'openai'
|
|
2
|
+
import { OpenAIBaseResponsesTextAdapter } from '@tanstack/openai-base'
|
|
3
|
+
import { getLovableApiKeyFromEnv, withLovableDefaults } from '../utils/client'
|
|
4
|
+
import type { Modality } from '@tanstack/ai'
|
|
5
|
+
import type {
|
|
6
|
+
LovableChatModelToolCapabilitiesByName,
|
|
7
|
+
LovableModelId,
|
|
8
|
+
ResolveInputModalities,
|
|
9
|
+
ResolveProviderOptions,
|
|
10
|
+
} from '../model-meta'
|
|
11
|
+
import type { LovableMessageMetadataByModality } from '../message-types'
|
|
12
|
+
import type { ExternalResponsesProviderOptions } from '../text/responses-provider-options'
|
|
13
|
+
import type { LovableClientConfig } from '../utils/client'
|
|
14
|
+
|
|
15
|
+
export interface LovableResponsesTextConfig extends LovableClientConfig {}
|
|
16
|
+
|
|
17
|
+
export type LovableResponsesTextProviderOptions =
|
|
18
|
+
ExternalResponsesProviderOptions
|
|
19
|
+
|
|
20
|
+
type ResolveToolCapabilities<TModel extends string> =
|
|
21
|
+
TModel extends keyof LovableChatModelToolCapabilitiesByName
|
|
22
|
+
? NonNullable<LovableChatModelToolCapabilitiesByName[TModel]>
|
|
23
|
+
: readonly []
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Lovable AI Gateway Responses text adapter.
|
|
27
|
+
*
|
|
28
|
+
* Talks to the OpenAI-compatible Responses API at
|
|
29
|
+
* `https://ai.gateway.lovable.dev/v1`.
|
|
30
|
+
*/
|
|
31
|
+
export class LovableResponsesTextAdapter<
|
|
32
|
+
TModel extends LovableModelId,
|
|
33
|
+
TProviderOptions extends Record<string, any> = ResolveProviderOptions<TModel>,
|
|
34
|
+
TInputModalities extends ReadonlyArray<Modality> =
|
|
35
|
+
ResolveInputModalities<TModel>,
|
|
36
|
+
TToolCapabilities extends ReadonlyArray<string> =
|
|
37
|
+
ResolveToolCapabilities<TModel>,
|
|
38
|
+
> extends OpenAIBaseResponsesTextAdapter<
|
|
39
|
+
TModel,
|
|
40
|
+
TProviderOptions,
|
|
41
|
+
TInputModalities,
|
|
42
|
+
LovableMessageMetadataByModality,
|
|
43
|
+
TToolCapabilities
|
|
44
|
+
> {
|
|
45
|
+
override readonly kind = 'text' as const
|
|
46
|
+
override readonly name = 'lovable' as const
|
|
47
|
+
|
|
48
|
+
constructor(config: LovableResponsesTextConfig, model: TModel) {
|
|
49
|
+
super(model, 'lovable', new OpenAI(withLovableDefaults(config)))
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function createLovableResponsesText<TModel extends LovableModelId>(
|
|
54
|
+
model: TModel,
|
|
55
|
+
apiKey: string,
|
|
56
|
+
config?: Omit<LovableResponsesTextConfig, 'apiKey'>,
|
|
57
|
+
): LovableResponsesTextAdapter<TModel> {
|
|
58
|
+
return new LovableResponsesTextAdapter({ apiKey, ...config }, model)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function lovableResponsesText<TModel extends LovableModelId>(
|
|
62
|
+
model: TModel,
|
|
63
|
+
config?: Omit<LovableResponsesTextConfig, 'apiKey'>,
|
|
64
|
+
): LovableResponsesTextAdapter<TModel> {
|
|
65
|
+
return createLovableResponsesText(model, getLovableApiKeyFromEnv(), config)
|
|
66
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ChatStreamSummarizeAdapter } from '@tanstack/ai/adapters'
|
|
2
|
+
import { getLovableApiKeyFromEnv } from '../utils/client'
|
|
3
|
+
import { LovableTextAdapter } from './text'
|
|
4
|
+
import type { InferTextProviderOptions } from '@tanstack/ai/adapters'
|
|
5
|
+
import type { LovableModelId } from '../model-meta'
|
|
6
|
+
import type { LovableClientConfig } from '../utils/client'
|
|
7
|
+
|
|
8
|
+
export interface LovableSummarizeConfig extends LovableClientConfig {}
|
|
9
|
+
|
|
10
|
+
export type LovableSummarizeModel = LovableModelId
|
|
11
|
+
|
|
12
|
+
export function createLovableSummarize<TModel extends LovableSummarizeModel>(
|
|
13
|
+
model: TModel,
|
|
14
|
+
apiKey: string,
|
|
15
|
+
config?: Omit<LovableSummarizeConfig, 'apiKey'>,
|
|
16
|
+
): ChatStreamSummarizeAdapter<
|
|
17
|
+
TModel,
|
|
18
|
+
InferTextProviderOptions<LovableTextAdapter<TModel>>
|
|
19
|
+
> {
|
|
20
|
+
return new ChatStreamSummarizeAdapter(
|
|
21
|
+
new LovableTextAdapter({ apiKey, ...config }, model),
|
|
22
|
+
model,
|
|
23
|
+
'lovable',
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function lovableSummarize<TModel extends LovableSummarizeModel>(
|
|
28
|
+
model: TModel,
|
|
29
|
+
config?: Omit<LovableSummarizeConfig, 'apiKey'>,
|
|
30
|
+
): ChatStreamSummarizeAdapter<
|
|
31
|
+
TModel,
|
|
32
|
+
InferTextProviderOptions<LovableTextAdapter<TModel>>
|
|
33
|
+
> {
|
|
34
|
+
return createLovableSummarize(model, getLovableApiKeyFromEnv(), config)
|
|
35
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import OpenAI from 'openai'
|
|
2
|
+
import { OpenAIBaseChatCompletionsTextAdapter } from '@tanstack/openai-base'
|
|
3
|
+
import { withLovableDefaults } from '../utils/client'
|
|
4
|
+
import type { Modality } from '@tanstack/ai'
|
|
5
|
+
import type {
|
|
6
|
+
LovableChatModelToolCapabilitiesByName,
|
|
7
|
+
LovableModelId,
|
|
8
|
+
ResolveInputModalities,
|
|
9
|
+
ResolveProviderOptions,
|
|
10
|
+
} from '../model-meta'
|
|
11
|
+
import type { LovableMessageMetadataByModality } from '../message-types'
|
|
12
|
+
import type { LovableClientConfig } from '../utils/client'
|
|
13
|
+
|
|
14
|
+
type ResolveToolCapabilities<TModel extends string> =
|
|
15
|
+
TModel extends keyof LovableChatModelToolCapabilitiesByName
|
|
16
|
+
? NonNullable<LovableChatModelToolCapabilitiesByName[TModel]>
|
|
17
|
+
: readonly []
|
|
18
|
+
|
|
19
|
+
export interface LovableTextConfig extends LovableClientConfig {}
|
|
20
|
+
|
|
21
|
+
export type { ExternalTextProviderOptions as LovableTextProviderOptions } from '../text/text-provider-options'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Lovable AI Gateway text adapter.
|
|
25
|
+
*
|
|
26
|
+
* Talks to the OpenAI-compatible Chat Completions API at
|
|
27
|
+
* `https://ai.gateway.lovable.dev/v1`.
|
|
28
|
+
*/
|
|
29
|
+
export class LovableTextAdapter<
|
|
30
|
+
TModel extends LovableModelId,
|
|
31
|
+
TProviderOptions extends Record<string, any> = ResolveProviderOptions<TModel>,
|
|
32
|
+
TInputModalities extends ReadonlyArray<Modality> =
|
|
33
|
+
ResolveInputModalities<TModel>,
|
|
34
|
+
TToolCapabilities extends ReadonlyArray<string> =
|
|
35
|
+
ResolveToolCapabilities<TModel>,
|
|
36
|
+
> extends OpenAIBaseChatCompletionsTextAdapter<
|
|
37
|
+
TModel,
|
|
38
|
+
TProviderOptions,
|
|
39
|
+
TInputModalities,
|
|
40
|
+
LovableMessageMetadataByModality,
|
|
41
|
+
TToolCapabilities
|
|
42
|
+
> {
|
|
43
|
+
override readonly kind = 'text' as const
|
|
44
|
+
override readonly name = 'lovable' as const
|
|
45
|
+
|
|
46
|
+
constructor(config: LovableTextConfig, model: TModel) {
|
|
47
|
+
super(model, 'lovable', new OpenAI(withLovableDefaults(config)))
|
|
48
|
+
}
|
|
49
|
+
}
|