@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,81 @@
|
|
|
1
|
+
import { base64ToArrayBuffer } from '@tanstack/ai-utils'
|
|
2
|
+
import type { ImagePart, MediaInputMetadata } from '@tanstack/ai'
|
|
3
|
+
|
|
4
|
+
const DEFAULT_MIME = 'image/png'
|
|
5
|
+
const MIME_TO_EXT: Record<string, string> = {
|
|
6
|
+
'image/png': 'png',
|
|
7
|
+
'image/jpeg': 'jpg',
|
|
8
|
+
'image/jpg': 'jpg',
|
|
9
|
+
'image/webp': 'webp',
|
|
10
|
+
'image/gif': 'gif',
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function extForMime(mimeType: string): string {
|
|
14
|
+
return MIME_TO_EXT[mimeType] ?? mimeType.split('/')[1] ?? 'png'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function ensureFileSupport(): void {
|
|
18
|
+
if (typeof File === 'undefined') {
|
|
19
|
+
throw new Error(
|
|
20
|
+
'`File` is not available in this environment. ' +
|
|
21
|
+
'Image-conditioned generation requires Node 20+ or a browser context.',
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Convert a TanStack `ImagePart` into an OpenAI-compatible `File`.
|
|
28
|
+
*
|
|
29
|
+
* HTTP(S) URLs are fetched only when `allowUrlFetch` is set. The gateway
|
|
30
|
+
* edit and `input_reference` endpoints need uploaded bytes, not a URL.
|
|
31
|
+
*/
|
|
32
|
+
export async function imagePartToFile(
|
|
33
|
+
part: ImagePart<MediaInputMetadata>,
|
|
34
|
+
fallbackName: string,
|
|
35
|
+
allowUrlFetch: boolean,
|
|
36
|
+
abortSignal?: AbortSignal,
|
|
37
|
+
): Promise<File> {
|
|
38
|
+
ensureFileSupport()
|
|
39
|
+
|
|
40
|
+
if (part.source.type === 'data') {
|
|
41
|
+
const mimeType = part.source.mimeType || DEFAULT_MIME
|
|
42
|
+
const bytes = base64ToArrayBuffer(part.source.value)
|
|
43
|
+
return new File([bytes], `${fallbackName}.${extForMime(mimeType)}`, {
|
|
44
|
+
type: mimeType,
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (/^https?:\/\//i.test(part.source.value) && !allowUrlFetch) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`lovable: HTTP(S) URL image inputs are not fetched by default because ` +
|
|
51
|
+
`the edit / input_reference endpoints require uploaded bytes, so ` +
|
|
52
|
+
`the image would be downloaded and buffered in memory (risking OOM on ` +
|
|
53
|
+
`constrained runtimes). Pass a data: URI, or set \`allowUrlFetch: true\` ` +
|
|
54
|
+
`on the adapter config to opt into fetching. URL: ${part.source.value}`,
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const response = await fetch(
|
|
59
|
+
part.source.value,
|
|
60
|
+
abortSignal ? { signal: abortSignal } : undefined,
|
|
61
|
+
)
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Failed to fetch image input (${response.status} ${response.statusText}): ${part.source.value}`,
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
const blob = await response.blob()
|
|
68
|
+
const mimeType =
|
|
69
|
+
part.source.mimeType || blob.type || inferMimeFromUrl(part.source.value)
|
|
70
|
+
return new File([blob], `${fallbackName}.${extForMime(mimeType)}`, {
|
|
71
|
+
type: mimeType,
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function inferMimeFromUrl(url: string): string {
|
|
76
|
+
const match = url.match(/\.(png|jpe?g|webp|gif)(?:\?|#|$)/i)
|
|
77
|
+
if (!match || !match[1]) return DEFAULT_MIME
|
|
78
|
+
const ext = match[1].toLowerCase()
|
|
79
|
+
if (ext === 'jpg' || ext === 'jpeg') return 'image/jpeg'
|
|
80
|
+
return `image/${ext}`
|
|
81
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type LovableImageSize = '1024x1024' | '1536x1024' | '1024x1536' | 'auto'
|
|
2
|
+
|
|
3
|
+
export type LovableImageQuality = 'high' | 'medium' | 'low' | 'auto'
|
|
4
|
+
|
|
5
|
+
export type LovableImageOutputFormat = 'png' | 'jpeg' | 'webp'
|
|
6
|
+
|
|
7
|
+
export type LovableImageBackground = 'transparent' | 'opaque' | 'auto'
|
|
8
|
+
|
|
9
|
+
export type LovableImageModeration = 'low' | 'auto'
|
|
10
|
+
|
|
11
|
+
export interface LovableImageProviderOptions {
|
|
12
|
+
quality?: LovableImageQuality
|
|
13
|
+
output_format?: LovableImageOutputFormat
|
|
14
|
+
background?: LovableImageBackground
|
|
15
|
+
moderation?: LovableImageModeration
|
|
16
|
+
user?: string
|
|
17
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @tanstack/ai-lovable
|
|
3
|
+
*
|
|
4
|
+
* Lovable AI Gateway adapter for TanStack AI.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
LovableTextAdapter,
|
|
9
|
+
type LovableTextConfig,
|
|
10
|
+
type LovableTextProviderOptions,
|
|
11
|
+
} from './adapters/text'
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
createLovableText,
|
|
15
|
+
lovableText,
|
|
16
|
+
type LovableTextApi,
|
|
17
|
+
type LovableResponsesApiConfig,
|
|
18
|
+
type LovableChatApiConfig,
|
|
19
|
+
} from './adapters/factory'
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
LovableResponsesTextAdapter,
|
|
23
|
+
createLovableResponsesText,
|
|
24
|
+
lovableResponsesText,
|
|
25
|
+
type LovableResponsesTextConfig,
|
|
26
|
+
type LovableResponsesTextProviderOptions,
|
|
27
|
+
} from './adapters/responses-text'
|
|
28
|
+
|
|
29
|
+
export {
|
|
30
|
+
createLovableSummarize,
|
|
31
|
+
lovableSummarize,
|
|
32
|
+
type LovableSummarizeConfig,
|
|
33
|
+
type LovableSummarizeModel,
|
|
34
|
+
} from './adapters/summarize'
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
LovableEmbeddingAdapter,
|
|
38
|
+
createLovableEmbedding,
|
|
39
|
+
lovableEmbedding,
|
|
40
|
+
type LovableEmbeddingConfig,
|
|
41
|
+
} from './adapters/embedding'
|
|
42
|
+
export type { LovableEmbeddingProviderOptions } from './embedding/embedding-provider-options'
|
|
43
|
+
|
|
44
|
+
export {
|
|
45
|
+
LovableImageAdapter,
|
|
46
|
+
createLovableImage,
|
|
47
|
+
lovableImage,
|
|
48
|
+
type LovableImageConfig,
|
|
49
|
+
} from './adapters/image'
|
|
50
|
+
export type { LovableImageProviderOptions } from './image/image-provider-options'
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @experimental Video generation is an experimental feature and may change.
|
|
54
|
+
*/
|
|
55
|
+
export {
|
|
56
|
+
LovableVideoAdapter,
|
|
57
|
+
createLovableVideo,
|
|
58
|
+
lovableVideo,
|
|
59
|
+
type LovableVideoConfig,
|
|
60
|
+
} from './adapters/video'
|
|
61
|
+
export type {
|
|
62
|
+
LovableVideoProviderOptions,
|
|
63
|
+
LovableVideoSize,
|
|
64
|
+
LovableHdVideoSize,
|
|
65
|
+
Lovable4kVideoSize,
|
|
66
|
+
LovableVideoDuration,
|
|
67
|
+
LovableVideoSeconds,
|
|
68
|
+
} from './video/video-provider-options'
|
|
69
|
+
|
|
70
|
+
export {
|
|
71
|
+
LovableTTSAdapter,
|
|
72
|
+
createLovableSpeech,
|
|
73
|
+
lovableSpeech,
|
|
74
|
+
type LovableTTSConfig,
|
|
75
|
+
} from './adapters/tts'
|
|
76
|
+
export type {
|
|
77
|
+
LovableTTSProviderOptions,
|
|
78
|
+
LovableTTSVoice,
|
|
79
|
+
LovableTTSFormat,
|
|
80
|
+
} from './audio/tts-provider-options'
|
|
81
|
+
|
|
82
|
+
export {
|
|
83
|
+
LovableTranscriptionAdapter,
|
|
84
|
+
createLovableTranscription,
|
|
85
|
+
lovableTranscription,
|
|
86
|
+
type LovableTranscriptionConfig,
|
|
87
|
+
} from './adapters/transcription'
|
|
88
|
+
export type { LovableTranscriptionProviderOptions } from './audio/transcription-provider-options'
|
|
89
|
+
|
|
90
|
+
export {
|
|
91
|
+
LOVABLE_CHAT_MODELS,
|
|
92
|
+
LOVABLE_IMAGE_MODELS,
|
|
93
|
+
LOVABLE_VIDEO_MODELS,
|
|
94
|
+
LOVABLE_EMBEDDING_MODELS,
|
|
95
|
+
LOVABLE_TTS_MODELS,
|
|
96
|
+
LOVABLE_TRANSCRIPTION_MODELS,
|
|
97
|
+
} from './model-meta'
|
|
98
|
+
export type {
|
|
99
|
+
LovableChatModel,
|
|
100
|
+
LovableModelId,
|
|
101
|
+
LovableChatModelProviderOptionsByName,
|
|
102
|
+
LovableModelInputModalitiesByName,
|
|
103
|
+
LovableImageModel,
|
|
104
|
+
LovableVideoModel,
|
|
105
|
+
LovableEmbeddingModel,
|
|
106
|
+
LovableTTSModel,
|
|
107
|
+
LovableTranscriptionModel,
|
|
108
|
+
ResolveProviderOptions,
|
|
109
|
+
ResolveInputModalities,
|
|
110
|
+
} from './model-meta'
|
|
111
|
+
|
|
112
|
+
export type {
|
|
113
|
+
LovableTextMetadata,
|
|
114
|
+
LovableImageMetadata,
|
|
115
|
+
LovableAudioMetadata,
|
|
116
|
+
LovableVideoMetadata,
|
|
117
|
+
LovableDocumentMetadata,
|
|
118
|
+
LovableMessageMetadataByModality,
|
|
119
|
+
} from './message-types'
|
|
120
|
+
|
|
121
|
+
export {
|
|
122
|
+
getLovableApiKeyFromEnv,
|
|
123
|
+
lovableGatewayHeaders,
|
|
124
|
+
type LovableClientConfig,
|
|
125
|
+
} from './utils/client'
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata for Lovable AI Gateway document content parts.
|
|
3
|
+
*/
|
|
4
|
+
export interface LovableDocumentMetadata {}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Metadata for Lovable AI Gateway text content parts.
|
|
8
|
+
*/
|
|
9
|
+
export interface LovableTextMetadata {}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Metadata for Lovable AI Gateway image content parts.
|
|
13
|
+
*/
|
|
14
|
+
export interface LovableImageMetadata {
|
|
15
|
+
/**
|
|
16
|
+
* Specifies the detail level of the image.
|
|
17
|
+
* - 'auto': Let the model decide based on image size and content
|
|
18
|
+
* - 'low': Use low resolution processing (faster, cheaper, less detail)
|
|
19
|
+
* - 'high': Use high resolution processing (slower, more expensive, more detail)
|
|
20
|
+
*
|
|
21
|
+
* @default 'auto'
|
|
22
|
+
*/
|
|
23
|
+
detail?: 'auto' | 'low' | 'high'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Metadata for Lovable AI Gateway audio content parts.
|
|
28
|
+
*/
|
|
29
|
+
export interface LovableAudioMetadata {}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Metadata for Lovable AI Gateway video content parts.
|
|
33
|
+
*/
|
|
34
|
+
export interface LovableVideoMetadata {}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Map of modality types to their Lovable AI Gateway metadata types.
|
|
38
|
+
*/
|
|
39
|
+
export interface LovableMessageMetadataByModality {
|
|
40
|
+
text: LovableTextMetadata
|
|
41
|
+
image: LovableImageMetadata
|
|
42
|
+
audio: LovableAudioMetadata
|
|
43
|
+
video: LovableVideoMetadata
|
|
44
|
+
document: LovableDocumentMetadata
|
|
45
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type { LovableEmbeddingProviderOptions } from './embedding/embedding-provider-options'
|
|
2
|
+
import type {
|
|
3
|
+
LovableImageProviderOptions,
|
|
4
|
+
LovableImageSize,
|
|
5
|
+
} from './image/image-provider-options'
|
|
6
|
+
import type { LovableTextProviderOptions } from './text/text-provider-options'
|
|
7
|
+
import type {
|
|
8
|
+
LovableHdVideoSize,
|
|
9
|
+
LovableVideoDuration,
|
|
10
|
+
LovableVideoProviderOptions,
|
|
11
|
+
LovableVideoSize,
|
|
12
|
+
} from './video/video-provider-options'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Chat models listed on https://docs.lovable.dev/features/ai.
|
|
16
|
+
* Ids use the `google/` and `openai/` form that the gateway accepts.
|
|
17
|
+
*/
|
|
18
|
+
export const LOVABLE_CHAT_MODELS = [
|
|
19
|
+
'google/gemini-3.7-flash',
|
|
20
|
+
'google/gemini-3.6-flash',
|
|
21
|
+
'google/gemini-3.5-flash',
|
|
22
|
+
'google/gemini-3.1-pro-preview',
|
|
23
|
+
'google/gemini-3.1-flash-lite',
|
|
24
|
+
'google/gemini-3-flash-preview',
|
|
25
|
+
'google/gemini-2.5-pro',
|
|
26
|
+
'google/gemini-2.5-flash',
|
|
27
|
+
'google/gemini-2.5-flash-lite',
|
|
28
|
+
'openai/gpt-5.6-sol',
|
|
29
|
+
'openai/gpt-5.6-terra',
|
|
30
|
+
'openai/gpt-5.6-luna',
|
|
31
|
+
'openai/gpt-5.5-pro',
|
|
32
|
+
'openai/gpt-5.5',
|
|
33
|
+
'openai/gpt-5.4-pro',
|
|
34
|
+
'openai/gpt-5.4',
|
|
35
|
+
'openai/gpt-5.4-mini',
|
|
36
|
+
'openai/gpt-5.4-nano',
|
|
37
|
+
'openai/gpt-5.2',
|
|
38
|
+
'openai/gpt-5',
|
|
39
|
+
'openai/gpt-5-mini',
|
|
40
|
+
'openai/gpt-5-nano',
|
|
41
|
+
] as const
|
|
42
|
+
|
|
43
|
+
export type LovableChatModel = (typeof LOVABLE_CHAT_MODELS)[number]
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A curated chat model id, or any other id the gateway accepts.
|
|
47
|
+
* Uncurated ids fall back to text-only input and the generic provider options.
|
|
48
|
+
*/
|
|
49
|
+
export type LovableModelId = LovableChatModel | (string & {})
|
|
50
|
+
|
|
51
|
+
export type LovableChatModelProviderOptionsByName = {
|
|
52
|
+
[K in LovableChatModel]: LovableTextProviderOptions
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type LovableModelInputModalitiesByName = {
|
|
56
|
+
[K in LovableChatModel]: readonly ['text', 'image']
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type LovableChatModelToolCapabilitiesByName = {
|
|
60
|
+
[K in LovableChatModel]: readonly []
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export type ResolveProviderOptions<TModel extends string> =
|
|
64
|
+
TModel extends keyof LovableChatModelProviderOptionsByName
|
|
65
|
+
? LovableChatModelProviderOptionsByName[TModel]
|
|
66
|
+
: LovableTextProviderOptions
|
|
67
|
+
|
|
68
|
+
export type ResolveInputModalities<TModel extends string> =
|
|
69
|
+
TModel extends keyof LovableModelInputModalitiesByName
|
|
70
|
+
? LovableModelInputModalitiesByName[TModel]
|
|
71
|
+
: readonly ['text']
|
|
72
|
+
|
|
73
|
+
export const LOVABLE_IMAGE_MODELS = [
|
|
74
|
+
'openai/gpt-image-2',
|
|
75
|
+
'openai/gpt-image-1-mini',
|
|
76
|
+
'google/gemini-3.1-flash-image',
|
|
77
|
+
'google/gemini-3.1-flash-lite-image',
|
|
78
|
+
'google/gemini-3-pro-image',
|
|
79
|
+
'google/gemini-2.5-flash-image',
|
|
80
|
+
] as const
|
|
81
|
+
|
|
82
|
+
export type LovableImageModel = (typeof LOVABLE_IMAGE_MODELS)[number]
|
|
83
|
+
|
|
84
|
+
export type LovableImageModelProviderOptionsByName = {
|
|
85
|
+
[K in LovableImageModel]: LovableImageProviderOptions
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type LovableImageModelSizeByName = {
|
|
89
|
+
[K in LovableImageModel]: LovableImageSize
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export type LovableImageModelInputModalitiesByName = {
|
|
93
|
+
[K in LovableImageModel]: readonly ['image']
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @experimental Video generation is an experimental feature and may change.
|
|
98
|
+
*/
|
|
99
|
+
export const LOVABLE_VIDEO_MODELS = [
|
|
100
|
+
'google/veo-3.1-lite',
|
|
101
|
+
'google/veo-3.1-fast',
|
|
102
|
+
'google/veo-3.1',
|
|
103
|
+
] as const
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @experimental Video generation is an experimental feature and may change.
|
|
107
|
+
*/
|
|
108
|
+
export type LovableVideoModel = (typeof LOVABLE_VIDEO_MODELS)[number]
|
|
109
|
+
|
|
110
|
+
export type LovableVideoModelProviderOptionsByName = {
|
|
111
|
+
[K in LovableVideoModel]: LovableVideoProviderOptions
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export type LovableVideoModelSizeByName = {
|
|
115
|
+
'google/veo-3.1-lite': LovableHdVideoSize
|
|
116
|
+
'google/veo-3.1-fast': LovableVideoSize
|
|
117
|
+
'google/veo-3.1': LovableVideoSize
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export type LovableVideoModelDurationByName = {
|
|
121
|
+
[K in LovableVideoModel]: LovableVideoDuration
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export type LovableVideoModelInputModalitiesByName = {
|
|
125
|
+
[K in LovableVideoModel]: readonly ['image']
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export const LOVABLE_EMBEDDING_MODELS = [
|
|
129
|
+
'google/gemini-embedding-2',
|
|
130
|
+
'google/gemini-embedding-001',
|
|
131
|
+
'openai/text-embedding-3-small',
|
|
132
|
+
'openai/text-embedding-3-large',
|
|
133
|
+
] as const
|
|
134
|
+
|
|
135
|
+
export type LovableEmbeddingModel = (typeof LOVABLE_EMBEDDING_MODELS)[number]
|
|
136
|
+
|
|
137
|
+
export type LovableEmbeddingModelProviderOptionsByName = {
|
|
138
|
+
[K in LovableEmbeddingModel]: LovableEmbeddingProviderOptions
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export type LovableEmbeddingModelInputModalitiesByName = {
|
|
142
|
+
[K in LovableEmbeddingModel]: readonly ['text']
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export const LOVABLE_TTS_MODELS = [
|
|
146
|
+
'openai/gpt-4o-mini-tts',
|
|
147
|
+
'google/gemini-2.5-flash-tts',
|
|
148
|
+
'google/gemini-2.5-pro-tts',
|
|
149
|
+
'google/gemini-2.5-flash-lite-preview-tts',
|
|
150
|
+
'google/gemini-3.1-flash-tts-preview',
|
|
151
|
+
] as const
|
|
152
|
+
|
|
153
|
+
export type LovableTTSModel = (typeof LOVABLE_TTS_MODELS)[number]
|
|
154
|
+
|
|
155
|
+
export const LOVABLE_TRANSCRIPTION_MODELS = [
|
|
156
|
+
'openai/gpt-4o-mini-transcribe',
|
|
157
|
+
'openai/gpt-4o-transcribe',
|
|
158
|
+
] as const
|
|
159
|
+
|
|
160
|
+
export type LovableTranscriptionModel =
|
|
161
|
+
(typeof LOVABLE_TRANSCRIPTION_MODELS)[number]
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { LovableTextProviderOptions } from './text-provider-options'
|
|
2
|
+
|
|
3
|
+
export type LovableResponsesProviderOptions = Pick<
|
|
4
|
+
LovableTextProviderOptions,
|
|
5
|
+
'temperature' | 'top_p' | 'max_output_tokens' | 'user'
|
|
6
|
+
>
|
|
7
|
+
|
|
8
|
+
export type ExternalResponsesProviderOptions = LovableResponsesProviderOptions
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface LovableTextProviderOptions {
|
|
2
|
+
temperature?: number | null
|
|
3
|
+
top_p?: number | null
|
|
4
|
+
max_tokens?: number | null
|
|
5
|
+
max_completion_tokens?: number | null
|
|
6
|
+
max_output_tokens?: number | null
|
|
7
|
+
frequency_penalty?: number | null
|
|
8
|
+
presence_penalty?: number | null
|
|
9
|
+
stop?: string | null | Array<string>
|
|
10
|
+
seed?: number | null
|
|
11
|
+
reasoning?: boolean | Record<string, unknown> | null
|
|
12
|
+
include_reasoning?: boolean | null
|
|
13
|
+
response_format?: unknown
|
|
14
|
+
structured_outputs?: boolean | null
|
|
15
|
+
user?: string | null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type ExternalTextProviderOptions = LovableTextProviderOptions
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { getApiKeyFromEnv } from '@tanstack/ai-utils'
|
|
2
|
+
import type { ClientOptions } from 'openai'
|
|
3
|
+
|
|
4
|
+
export const LOVABLE_DEFAULT_BASE_URL = 'https://ai.gateway.lovable.dev/v1'
|
|
5
|
+
|
|
6
|
+
export interface LovableClientConfig extends Omit<ClientOptions, 'apiKey'> {
|
|
7
|
+
apiKey: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getLovableApiKeyFromEnv(): string {
|
|
11
|
+
try {
|
|
12
|
+
return getApiKeyFromEnv('LOVABLE_API_KEY')
|
|
13
|
+
} catch (cause) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
'LOVABLE_API_KEY is required. Set it in the environment or pass apiKey to the factory.',
|
|
16
|
+
{ cause },
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function lovableGatewayHeaders(apiKey: string): {
|
|
22
|
+
'Lovable-API-Key': string
|
|
23
|
+
'X-Lovable-AIG-SDK': string
|
|
24
|
+
} {
|
|
25
|
+
return {
|
|
26
|
+
'Lovable-API-Key': apiKey,
|
|
27
|
+
'X-Lovable-AIG-SDK': 'tanstack-ai',
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function withLovableDefaults(
|
|
32
|
+
config: LovableClientConfig,
|
|
33
|
+
): ClientOptions {
|
|
34
|
+
const { defaultHeaders, ...rest } = config
|
|
35
|
+
return {
|
|
36
|
+
...rest,
|
|
37
|
+
baseURL: config.baseURL || LOVABLE_DEFAULT_BASE_URL,
|
|
38
|
+
defaultHeaders: {
|
|
39
|
+
...lovableGatewayHeaders(config.apiKey),
|
|
40
|
+
...defaultHeaders,
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function openaiRequestOptions(
|
|
46
|
+
abortSignal?: AbortSignal,
|
|
47
|
+
): { signal: AbortSignal } | undefined {
|
|
48
|
+
if (!abortSignal) return undefined
|
|
49
|
+
return { signal: abortSignal }
|
|
50
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @experimental Video generation is an experimental feature and may change.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type LovableHdVideoSize =
|
|
6
|
+
| '1280x720'
|
|
7
|
+
| '720x1280'
|
|
8
|
+
| '1920x1080'
|
|
9
|
+
| '1080x1920'
|
|
10
|
+
|
|
11
|
+
export type Lovable4kVideoSize = '3840x2160' | '2160x3840'
|
|
12
|
+
|
|
13
|
+
export type LovableVideoSize = LovableHdVideoSize | Lovable4kVideoSize
|
|
14
|
+
|
|
15
|
+
export type LovableVideoSeconds = '4' | '6' | '8'
|
|
16
|
+
|
|
17
|
+
export type LovableVideoDuration = 4 | 6 | 8
|
|
18
|
+
|
|
19
|
+
export interface LovableVideoProviderOptions {
|
|
20
|
+
size?: LovableVideoSize
|
|
21
|
+
seconds?: LovableVideoSeconds
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const HD_SIZES: ReadonlyArray<LovableHdVideoSize> = [
|
|
25
|
+
'1280x720',
|
|
26
|
+
'720x1280',
|
|
27
|
+
'1920x1080',
|
|
28
|
+
'1080x1920',
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
const FOUR_K_SIZES: ReadonlyArray<Lovable4kVideoSize> = [
|
|
32
|
+
'3840x2160',
|
|
33
|
+
'2160x3840',
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
const ALL_SIZES: ReadonlyArray<LovableVideoSize> = [
|
|
37
|
+
...HD_SIZES,
|
|
38
|
+
...FOUR_K_SIZES,
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
const HIGH_RES_SIZES: ReadonlySet<string> = new Set([
|
|
42
|
+
'1920x1080',
|
|
43
|
+
'1080x1920',
|
|
44
|
+
'3840x2160',
|
|
45
|
+
'2160x3840',
|
|
46
|
+
])
|
|
47
|
+
|
|
48
|
+
const FOUR_K_MODELS: ReadonlySet<string> = new Set([
|
|
49
|
+
'google/veo-3.1-fast',
|
|
50
|
+
'google/veo-3.1',
|
|
51
|
+
])
|
|
52
|
+
|
|
53
|
+
export function is4kVideoSize(size: string): size is Lovable4kVideoSize {
|
|
54
|
+
return (FOUR_K_SIZES as ReadonlyArray<string>).includes(size)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function isHighResVideoSize(size: string): boolean {
|
|
58
|
+
return HIGH_RES_SIZES.has(size)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function supports4kVideo(model: string): boolean {
|
|
62
|
+
return FOUR_K_MODELS.has(model)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function validateVideoSize(
|
|
66
|
+
model: string,
|
|
67
|
+
size?: string,
|
|
68
|
+
): asserts size is LovableVideoSize | undefined {
|
|
69
|
+
if (!size) return
|
|
70
|
+
|
|
71
|
+
if (!(ALL_SIZES as ReadonlyArray<string>).includes(size)) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`Size "${size}" is not supported by model "${model}". Supported sizes: ${ALL_SIZES.join(', ')}`,
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (is4kVideoSize(size) && !supports4kVideo(model)) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`Size "${size}" is 4K. Only google/veo-3.1-fast and google/veo-3.1 support 4K.`,
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function validateVideoSeconds(
|
|
85
|
+
model: string,
|
|
86
|
+
seconds?: number | string,
|
|
87
|
+
): asserts seconds is LovableVideoSeconds | LovableVideoDuration | undefined {
|
|
88
|
+
if (seconds === undefined) return
|
|
89
|
+
|
|
90
|
+
const isValid =
|
|
91
|
+
typeof seconds === 'string'
|
|
92
|
+
? seconds === '4' || seconds === '6' || seconds === '8'
|
|
93
|
+
: seconds === 4 || seconds === 6 || seconds === 8
|
|
94
|
+
|
|
95
|
+
if (!isValid) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`Duration "${seconds}" is not supported by model "${model}". Supported durations: 4, 6, or 8 seconds`,
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function validateHighResDuration(
|
|
103
|
+
model: string,
|
|
104
|
+
size: string | undefined,
|
|
105
|
+
seconds: number | string | undefined,
|
|
106
|
+
): void {
|
|
107
|
+
if (!size || !isHighResVideoSize(size)) return
|
|
108
|
+
|
|
109
|
+
const asNumber =
|
|
110
|
+
seconds === undefined
|
|
111
|
+
? undefined
|
|
112
|
+
: typeof seconds === 'string'
|
|
113
|
+
? Number(seconds)
|
|
114
|
+
: seconds
|
|
115
|
+
|
|
116
|
+
if (asNumber !== undefined && asNumber !== 8) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`Model "${model}" requires 8 second clips at 1080p and 4K. Received duration ${seconds}.`,
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function toApiSeconds(
|
|
124
|
+
seconds: number | string | undefined,
|
|
125
|
+
): LovableVideoSeconds | undefined {
|
|
126
|
+
if (seconds === undefined) return undefined
|
|
127
|
+
return String(seconds) as LovableVideoSeconds
|
|
128
|
+
}
|