egaki 0.0.2 → 0.2.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/CHANGELOG.md +69 -0
- package/README.md +88 -15
- package/dist/antigravity-auth.d.ts +15 -0
- package/dist/antigravity-auth.d.ts.map +1 -0
- package/dist/antigravity-auth.js +544 -0
- package/dist/antigravity-image.d.ts +11 -0
- package/dist/antigravity-image.d.ts.map +1 -0
- package/dist/antigravity-image.js +285 -0
- package/dist/cli.js +215 -20
- package/dist/credentials.d.ts +1 -1
- package/dist/credentials.js +1 -1
- package/dist/gateway-model-candidates.generated.d.ts +11 -0
- package/dist/gateway-model-candidates.generated.d.ts.map +1 -0
- package/dist/gateway-model-candidates.generated.js +64 -0
- package/dist/model-catalog.d.ts.map +1 -1
- package/dist/model-catalog.js +169 -0
- package/dist/models.d.ts +7 -2
- package/dist/models.d.ts.map +1 -1
- package/dist/models.js +50 -1
- package/dist/open-browser.d.ts +2 -0
- package/dist/open-browser.d.ts.map +1 -0
- package/dist/open-browser.js +37 -0
- package/dist/subscription.d.ts.map +1 -1
- package/dist/subscription.js +38 -14
- package/dist/video-model-catalog.d.ts +52 -0
- package/dist/video-model-catalog.d.ts.map +1 -0
- package/dist/video-model-catalog.js +245 -0
- package/package.json +6 -6
- package/src/cli.ts +342 -23
- package/src/credentials.ts +1 -1
- package/src/model-catalog.ts +175 -0
- package/src/models.ts +64 -3
- package/src/open-browser.ts +48 -0
- package/src/subscription.ts +41 -14
- package/src/video-model-catalog.ts +302 -0
package/src/models.ts
CHANGED
|
@@ -20,15 +20,20 @@ import pc from 'picocolors'
|
|
|
20
20
|
import { PROVIDERS, EGAKI_GATEWAY_URL } from './credentials.js'
|
|
21
21
|
import { CATALOG, findModel } from './model-catalog.js'
|
|
22
22
|
import type { ModelEntry } from './model-catalog.js'
|
|
23
|
+
import { VIDEO_CATALOG, findVideoModel } from './video-model-catalog.js'
|
|
24
|
+
import type { VideoModelEntry } from './video-model-catalog.js'
|
|
23
25
|
|
|
24
|
-
export type
|
|
26
|
+
export type AnyModelEntry = ModelEntry | VideoModelEntry
|
|
27
|
+
export type { ModelEntry, VideoModelEntry }
|
|
25
28
|
|
|
26
29
|
export const IMAGE_MODELS = CATALOG.map((m) => m.id) as [string, ...string[]]
|
|
30
|
+
export const VIDEO_MODELS = VIDEO_CATALOG.map((m) => m.id) as [string, ...string[]]
|
|
27
31
|
|
|
28
32
|
export const DEFAULT_MODEL = 'nano-banana-pro-preview'
|
|
33
|
+
export const DEFAULT_VIDEO_MODEL = 'veo-3.1-fast-generate-001'
|
|
29
34
|
|
|
30
|
-
export function getModelConfig(modelId: string):
|
|
31
|
-
const entry = findModel(modelId)
|
|
35
|
+
export function getModelConfig(modelId: string): AnyModelEntry {
|
|
36
|
+
const entry = findModel(modelId) ?? findVideoModel(modelId)
|
|
32
37
|
if (!entry) {
|
|
33
38
|
console.error(pc.red(`Unknown model: ${modelId}`))
|
|
34
39
|
process.exit(1)
|
|
@@ -122,6 +127,19 @@ async function createGatewayTextModel(modelId: string, provider: string): Promis
|
|
|
122
127
|
return gateway(gatewayModelId)
|
|
123
128
|
}
|
|
124
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Create a gateway-backed video model using createGateway from the AI SDK.
|
|
132
|
+
*/
|
|
133
|
+
async function createGatewayVideoModel(modelId: string, provider: string) {
|
|
134
|
+
const { createGateway } = await import('ai')
|
|
135
|
+
const gateway = createGateway({
|
|
136
|
+
apiKey: process.env['EGAKI_API_KEY']!,
|
|
137
|
+
baseURL: EGAKI_GATEWAY_URL,
|
|
138
|
+
})
|
|
139
|
+
const gatewayModelId = `${provider}/${modelId}`
|
|
140
|
+
return gateway.video(gatewayModelId)
|
|
141
|
+
}
|
|
142
|
+
|
|
125
143
|
// Lazily import the provider and create the right model instance.
|
|
126
144
|
// This avoids loading all provider SDKs upfront — only the one needed
|
|
127
145
|
// for the selected model gets imported.
|
|
@@ -167,6 +185,10 @@ export async function createTextModel(
|
|
|
167
185
|
modelId: string,
|
|
168
186
|
): Promise<LanguageModel> {
|
|
169
187
|
const config = getModelConfig(modelId)
|
|
188
|
+
if (config.strategy !== 'text') {
|
|
189
|
+
console.error(pc.red(`Model ${modelId} is not a text model`))
|
|
190
|
+
process.exit(1)
|
|
191
|
+
}
|
|
170
192
|
ensureProviderKey(config.provider)
|
|
171
193
|
|
|
172
194
|
// Direct provider key takes priority
|
|
@@ -195,3 +217,42 @@ export async function createTextModel(
|
|
|
195
217
|
console.error(pc.red(`No API key available for provider: ${config.provider}`))
|
|
196
218
|
process.exit(1)
|
|
197
219
|
}
|
|
220
|
+
|
|
221
|
+
export async function createVideoModel(modelId: string): Promise<any> {
|
|
222
|
+
const config = getModelConfig(modelId)
|
|
223
|
+
if (config.strategy !== 'video') {
|
|
224
|
+
console.error(pc.red(`Model ${modelId} is not a video model`))
|
|
225
|
+
process.exit(1)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
ensureProviderKey(config.provider)
|
|
229
|
+
|
|
230
|
+
// Direct provider key takes priority
|
|
231
|
+
if (hasDirectProviderKey(config.provider)) {
|
|
232
|
+
switch (config.provider) {
|
|
233
|
+
case 'google': {
|
|
234
|
+
const { google } = await import('@ai-sdk/google')
|
|
235
|
+
return google.video(modelId)
|
|
236
|
+
}
|
|
237
|
+
case 'fal': {
|
|
238
|
+
const { fal } = await import('@ai-sdk/fal')
|
|
239
|
+
return fal.video(modelId)
|
|
240
|
+
}
|
|
241
|
+
default:
|
|
242
|
+
console.error(
|
|
243
|
+
pc.red(
|
|
244
|
+
`Direct video generation is only supported for Google and Fal keys, got provider: ${config.provider}`,
|
|
245
|
+
),
|
|
246
|
+
)
|
|
247
|
+
process.exit(1)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Fall back to egaki gateway
|
|
252
|
+
if (hasEgakiKey()) {
|
|
253
|
+
return createGatewayVideoModel(modelId, config.provider)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
console.error(pc.red(`No API key available for provider: ${config.provider}`))
|
|
257
|
+
process.exit(1)
|
|
258
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Cross-platform browser opener for URLs used by CLI flows.
|
|
2
|
+
// Never throws: missing OS commands are handled gracefully.
|
|
3
|
+
import { spawnSync } from 'node:child_process'
|
|
4
|
+
|
|
5
|
+
type OpenCommand = {
|
|
6
|
+
command: string
|
|
7
|
+
args: string[]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function getOpenCommands(url: string): OpenCommand[] {
|
|
11
|
+
if (process.platform === 'darwin') {
|
|
12
|
+
return [{ command: 'open', args: [url] }]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (process.platform === 'win32') {
|
|
16
|
+
// `start` is a cmd builtin, so invoke through cmd.exe.
|
|
17
|
+
return [{ command: 'cmd', args: ['/c', 'start', '', url] }]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return [
|
|
21
|
+
{ command: 'xdg-open', args: [url] },
|
|
22
|
+
{ command: 'gio', args: ['open', url] },
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function openUrlInBrowser(url: string): boolean {
|
|
27
|
+
// Avoid launching browser commands in non-interactive contexts (CI/pipes).
|
|
28
|
+
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
29
|
+
return false
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
for (const candidate of getOpenCommands(url)) {
|
|
33
|
+
try {
|
|
34
|
+
const result = spawnSync(candidate.command, candidate.args, {
|
|
35
|
+
stdio: 'ignore',
|
|
36
|
+
windowsHide: true,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
if (!result.error && result.status === 0) {
|
|
40
|
+
return true
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
// Keep trying fallback commands. This helper must never crash the CLI.
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return false
|
|
48
|
+
}
|
package/src/subscription.ts
CHANGED
|
@@ -16,9 +16,10 @@ import {
|
|
|
16
16
|
spinner,
|
|
17
17
|
} from '@clack/prompts'
|
|
18
18
|
import { EGAKI_GATEWAY_URL, saveProviderKey, getProviderKey } from './credentials.js'
|
|
19
|
+
import { openUrlInBrowser } from './open-browser.js'
|
|
19
20
|
|
|
20
|
-
// Strip /
|
|
21
|
-
const GATEWAY_BASE = EGAKI_GATEWAY_URL.replace(/\/
|
|
21
|
+
// Strip /vN/ai suffix to get the base URL for non-proxy endpoints (/buy, /api/*)
|
|
22
|
+
const GATEWAY_BASE = EGAKI_GATEWAY_URL.replace(/\/v\d+\/ai$/, '')
|
|
22
23
|
|
|
23
24
|
type PlanInfo = {
|
|
24
25
|
id: string
|
|
@@ -86,24 +87,42 @@ export async function subscribeInteractive(): Promise<void> {
|
|
|
86
87
|
process.exit(0)
|
|
87
88
|
}
|
|
88
89
|
|
|
89
|
-
//
|
|
90
|
-
const
|
|
91
|
-
message: '
|
|
92
|
-
placeholder: 'you@example.com',
|
|
93
|
-
validate: (value) => {
|
|
94
|
-
if (!value || !value.includes('@')) {
|
|
95
|
-
return 'Please enter a valid email'
|
|
96
|
-
}
|
|
97
|
-
},
|
|
90
|
+
// Optional email prefill for checkout
|
|
91
|
+
const prefillEmail = await confirm({
|
|
92
|
+
message: 'Prefill your email in checkout URL? (optional)',
|
|
98
93
|
})
|
|
99
94
|
|
|
100
|
-
if (isCancel(
|
|
95
|
+
if (isCancel(prefillEmail)) {
|
|
101
96
|
cancel('Subscribe cancelled.')
|
|
102
97
|
process.exit(0)
|
|
103
98
|
}
|
|
104
99
|
|
|
100
|
+
let email: string | undefined
|
|
101
|
+
if (prefillEmail) {
|
|
102
|
+
const enteredEmail = await text({
|
|
103
|
+
message: 'Email for the subscription',
|
|
104
|
+
placeholder: 'you@example.com',
|
|
105
|
+
validate: (value) => {
|
|
106
|
+
if (!value || !value.includes('@')) {
|
|
107
|
+
return 'Please enter a valid email'
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
if (isCancel(enteredEmail)) {
|
|
113
|
+
cancel('Subscribe cancelled.')
|
|
114
|
+
process.exit(0)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
email = enteredEmail
|
|
118
|
+
}
|
|
119
|
+
|
|
105
120
|
// Show checkout URL
|
|
106
|
-
const
|
|
121
|
+
const params = new URLSearchParams({ plan: planId })
|
|
122
|
+
if (email) {
|
|
123
|
+
params.set('email', email)
|
|
124
|
+
}
|
|
125
|
+
const checkoutUrl = `${GATEWAY_BASE}/buy?${params.toString()}`
|
|
107
126
|
|
|
108
127
|
log.info('')
|
|
109
128
|
note(
|
|
@@ -120,6 +139,13 @@ export async function subscribeInteractive(): Promise<void> {
|
|
|
120
139
|
'Checkout',
|
|
121
140
|
)
|
|
122
141
|
|
|
142
|
+
const opened = openUrlInBrowser(checkoutUrl)
|
|
143
|
+
if (opened) {
|
|
144
|
+
log.success('Opened checkout URL in your browser')
|
|
145
|
+
} else if (process.stdout.isTTY && process.stdin.isTTY) {
|
|
146
|
+
log.info('Could not auto-open a browser. Open the URL above manually.')
|
|
147
|
+
}
|
|
148
|
+
|
|
123
149
|
// Ask if they want to enter the key now
|
|
124
150
|
const hasKey = await confirm({
|
|
125
151
|
message: 'Do you already have your API key?',
|
|
@@ -176,7 +202,8 @@ export function subscribeNonInteractive(email?: string, plan?: string): void {
|
|
|
176
202
|
}
|
|
177
203
|
|
|
178
204
|
console.log('')
|
|
179
|
-
console.log('After payment,
|
|
205
|
+
console.log('After payment, your API key is shown on the success page.')
|
|
206
|
+
console.log('If an email was provided to Stripe checkout, a copy is also sent by email.')
|
|
180
207
|
console.log(`Then run: ${pc.cyan('egaki login --provider egaki --key egaki_...')}`)
|
|
181
208
|
console.log('')
|
|
182
209
|
console.log('Tip: egaki supports both BYOK and subscription mode.')
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
// Single source of truth for video generation models.
|
|
2
|
+
// Stores provider, strategy, capabilities, and duration-based pricing metadata.
|
|
3
|
+
// Gateway billing uses this catalog to estimate per-request video cost.
|
|
4
|
+
|
|
5
|
+
export type VideoDurationPricingTier = {
|
|
6
|
+
/** USD per second for this variant */
|
|
7
|
+
costPerSecond: number
|
|
8
|
+
/** Optional resolution discriminator (e.g. 720p, 1080p, 4k) */
|
|
9
|
+
resolution?: string
|
|
10
|
+
/** Optional mode discriminator (e.g. std, pro) */
|
|
11
|
+
mode?: string
|
|
12
|
+
/** Optional audio discriminator */
|
|
13
|
+
audio?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type PerVideoSecondCost = {
|
|
17
|
+
type: 'per-video-second'
|
|
18
|
+
/** Fallback duration when request does not specify one (seconds) */
|
|
19
|
+
defaultDurationSec: number
|
|
20
|
+
tiers: VideoDurationPricingTier[]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type UnknownVideoCost = {
|
|
24
|
+
type: 'unknown'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type VideoModelCost = PerVideoSecondCost | UnknownVideoCost
|
|
28
|
+
|
|
29
|
+
export type VideoModelFeatures = {
|
|
30
|
+
/** Supports text prompt to video generation */
|
|
31
|
+
textToVideo: boolean
|
|
32
|
+
/** Supports image-to-video prompt object ({ image, text }) */
|
|
33
|
+
imageToVideo: boolean
|
|
34
|
+
/** Optional capabilities exposed by provider */
|
|
35
|
+
capabilities: Array<'t2v' | 'i2v' | 'r2v' | 'motion-control' | 'editing'>
|
|
36
|
+
/** Optional supported aspect ratios */
|
|
37
|
+
aspectRatios?: string[]
|
|
38
|
+
/** Optional supported resolutions */
|
|
39
|
+
resolutions?: string[]
|
|
40
|
+
/** Optional duration range in seconds */
|
|
41
|
+
durationRangeSec?: { min: number; max: number }
|
|
42
|
+
seed: boolean
|
|
43
|
+
multipleVideos: boolean
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type VideoModelEntry = {
|
|
47
|
+
id: string
|
|
48
|
+
name: string
|
|
49
|
+
description?: string
|
|
50
|
+
provider: string
|
|
51
|
+
strategy: 'video'
|
|
52
|
+
released: string
|
|
53
|
+
cost: VideoModelCost
|
|
54
|
+
features: VideoModelFeatures
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const commonRatios = ['16:9', '9:16', '1:1', '4:3', '3:4']
|
|
58
|
+
|
|
59
|
+
export const VIDEO_CATALOG: VideoModelEntry[] = [
|
|
60
|
+
{
|
|
61
|
+
id: 'veo-3.1-generate-001',
|
|
62
|
+
name: 'Veo 3.1',
|
|
63
|
+
provider: 'google',
|
|
64
|
+
strategy: 'video',
|
|
65
|
+
released: '2026-01',
|
|
66
|
+
cost: {
|
|
67
|
+
type: 'per-video-second',
|
|
68
|
+
defaultDurationSec: 8,
|
|
69
|
+
tiers: [
|
|
70
|
+
{ resolution: '720p', audio: false, costPerSecond: 0.2 },
|
|
71
|
+
{ resolution: '720p', audio: true, costPerSecond: 0.4 },
|
|
72
|
+
{ resolution: '1080p', audio: false, costPerSecond: 0.2 },
|
|
73
|
+
{ resolution: '1080p', audio: true, costPerSecond: 0.4 },
|
|
74
|
+
{ resolution: '4k', audio: false, costPerSecond: 0.4 },
|
|
75
|
+
{ resolution: '4k', audio: true, costPerSecond: 0.6 },
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
features: {
|
|
79
|
+
textToVideo: true,
|
|
80
|
+
imageToVideo: false,
|
|
81
|
+
capabilities: ['t2v'],
|
|
82
|
+
aspectRatios: ['16:9', '9:16'],
|
|
83
|
+
resolutions: ['720p', '1080p', '4k'],
|
|
84
|
+
durationRangeSec: { min: 4, max: 8 },
|
|
85
|
+
seed: true,
|
|
86
|
+
multipleVideos: true,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 'veo-3.1-fast-generate-001',
|
|
91
|
+
name: 'Veo 3.1 Fast',
|
|
92
|
+
provider: 'google',
|
|
93
|
+
strategy: 'video',
|
|
94
|
+
released: '2026-01',
|
|
95
|
+
cost: {
|
|
96
|
+
type: 'per-video-second',
|
|
97
|
+
defaultDurationSec: 8,
|
|
98
|
+
tiers: [
|
|
99
|
+
{ resolution: '720p', audio: false, costPerSecond: 0.1 },
|
|
100
|
+
{ resolution: '720p', audio: true, costPerSecond: 0.15 },
|
|
101
|
+
{ resolution: '1080p', audio: false, costPerSecond: 0.1 },
|
|
102
|
+
{ resolution: '1080p', audio: true, costPerSecond: 0.15 },
|
|
103
|
+
{ resolution: '4k', audio: false, costPerSecond: 0.3 },
|
|
104
|
+
{ resolution: '4k', audio: true, costPerSecond: 0.35 },
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
features: {
|
|
108
|
+
textToVideo: true,
|
|
109
|
+
imageToVideo: false,
|
|
110
|
+
capabilities: ['t2v'],
|
|
111
|
+
aspectRatios: ['16:9', '9:16'],
|
|
112
|
+
resolutions: ['720p', '1080p', '4k'],
|
|
113
|
+
durationRangeSec: { min: 4, max: 8 },
|
|
114
|
+
seed: true,
|
|
115
|
+
multipleVideos: true,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: 'veo-3.0-generate-001',
|
|
120
|
+
name: 'Veo 3.0',
|
|
121
|
+
provider: 'google',
|
|
122
|
+
strategy: 'video',
|
|
123
|
+
released: '2025-12',
|
|
124
|
+
cost: {
|
|
125
|
+
type: 'per-video-second',
|
|
126
|
+
defaultDurationSec: 8,
|
|
127
|
+
tiers: [
|
|
128
|
+
{ resolution: '720p', audio: false, costPerSecond: 0.2 },
|
|
129
|
+
{ resolution: '720p', audio: true, costPerSecond: 0.4 },
|
|
130
|
+
{ resolution: '1080p', audio: false, costPerSecond: 0.2 },
|
|
131
|
+
{ resolution: '1080p', audio: true, costPerSecond: 0.4 },
|
|
132
|
+
],
|
|
133
|
+
},
|
|
134
|
+
features: {
|
|
135
|
+
textToVideo: true,
|
|
136
|
+
imageToVideo: false,
|
|
137
|
+
capabilities: ['t2v'],
|
|
138
|
+
aspectRatios: ['16:9', '9:16'],
|
|
139
|
+
resolutions: ['720p', '1080p'],
|
|
140
|
+
durationRangeSec: { min: 4, max: 8 },
|
|
141
|
+
seed: true,
|
|
142
|
+
multipleVideos: true,
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 'veo-3.0-fast-generate-001',
|
|
147
|
+
name: 'Veo 3.0 Fast',
|
|
148
|
+
provider: 'google',
|
|
149
|
+
strategy: 'video',
|
|
150
|
+
released: '2025-12',
|
|
151
|
+
cost: {
|
|
152
|
+
type: 'per-video-second',
|
|
153
|
+
defaultDurationSec: 8,
|
|
154
|
+
tiers: [
|
|
155
|
+
{ resolution: '720p', audio: false, costPerSecond: 0.1 },
|
|
156
|
+
{ resolution: '720p', audio: true, costPerSecond: 0.15 },
|
|
157
|
+
{ resolution: '1080p', audio: false, costPerSecond: 0.1 },
|
|
158
|
+
{ resolution: '1080p', audio: true, costPerSecond: 0.15 },
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
features: {
|
|
162
|
+
textToVideo: true,
|
|
163
|
+
imageToVideo: false,
|
|
164
|
+
capabilities: ['t2v'],
|
|
165
|
+
aspectRatios: ['16:9', '9:16'],
|
|
166
|
+
resolutions: ['720p', '1080p'],
|
|
167
|
+
durationRangeSec: { min: 4, max: 8 },
|
|
168
|
+
seed: true,
|
|
169
|
+
multipleVideos: true,
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
id: 'grok-imagine-video',
|
|
174
|
+
name: 'Grok Imagine Video',
|
|
175
|
+
provider: 'xai',
|
|
176
|
+
strategy: 'video',
|
|
177
|
+
released: '2026-03',
|
|
178
|
+
cost: {
|
|
179
|
+
type: 'per-video-second',
|
|
180
|
+
defaultDurationSec: 5,
|
|
181
|
+
tiers: [
|
|
182
|
+
{ resolution: '480p', costPerSecond: 0.05 },
|
|
183
|
+
{ resolution: '720p', costPerSecond: 0.07 },
|
|
184
|
+
],
|
|
185
|
+
},
|
|
186
|
+
features: {
|
|
187
|
+
textToVideo: true,
|
|
188
|
+
imageToVideo: true,
|
|
189
|
+
capabilities: ['t2v', 'i2v', 'editing'],
|
|
190
|
+
aspectRatios: ['1:1', '16:9', '9:16', '4:3', '3:4', '3:2', '2:3'],
|
|
191
|
+
resolutions: ['480p', '720p'],
|
|
192
|
+
durationRangeSec: { min: 1, max: 15 },
|
|
193
|
+
seed: false,
|
|
194
|
+
multipleVideos: false,
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
id: 'kling-v2.6-t2v',
|
|
199
|
+
name: 'Kling v2.6 T2V',
|
|
200
|
+
provider: 'klingai',
|
|
201
|
+
strategy: 'video',
|
|
202
|
+
released: '2025-12',
|
|
203
|
+
cost: {
|
|
204
|
+
type: 'per-video-second',
|
|
205
|
+
defaultDurationSec: 5,
|
|
206
|
+
tiers: [
|
|
207
|
+
{ mode: 'std', costPerSecond: 0.042 },
|
|
208
|
+
{ mode: 'pro', audio: false, costPerSecond: 0.07 },
|
|
209
|
+
{ mode: 'pro', audio: true, costPerSecond: 0.14 },
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
features: {
|
|
213
|
+
textToVideo: true,
|
|
214
|
+
imageToVideo: false,
|
|
215
|
+
capabilities: ['t2v'],
|
|
216
|
+
aspectRatios: commonRatios,
|
|
217
|
+
durationRangeSec: { min: 5, max: 10 },
|
|
218
|
+
seed: false,
|
|
219
|
+
multipleVideos: false,
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
id: 'wan-v2.6-t2v',
|
|
224
|
+
name: 'Wan v2.6 T2V',
|
|
225
|
+
provider: 'alibaba',
|
|
226
|
+
strategy: 'video',
|
|
227
|
+
released: '2026-01',
|
|
228
|
+
cost: {
|
|
229
|
+
type: 'per-video-second',
|
|
230
|
+
defaultDurationSec: 5,
|
|
231
|
+
tiers: [
|
|
232
|
+
{ resolution: '720p', costPerSecond: 0.1 },
|
|
233
|
+
{ resolution: '1080p', costPerSecond: 0.15 },
|
|
234
|
+
],
|
|
235
|
+
},
|
|
236
|
+
features: {
|
|
237
|
+
textToVideo: true,
|
|
238
|
+
imageToVideo: false,
|
|
239
|
+
capabilities: ['t2v'],
|
|
240
|
+
resolutions: ['720p', '1080p'],
|
|
241
|
+
durationRangeSec: { min: 2, max: 15 },
|
|
242
|
+
seed: false,
|
|
243
|
+
multipleVideos: false,
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
// Fal direct BYOK models — duration pricing depends on Fal endpoint/config.
|
|
248
|
+
{
|
|
249
|
+
id: 'luma-ray-2',
|
|
250
|
+
name: 'Luma Ray 2 (Fal)',
|
|
251
|
+
provider: 'fal',
|
|
252
|
+
strategy: 'video',
|
|
253
|
+
released: '2025-11',
|
|
254
|
+
cost: { type: 'unknown' },
|
|
255
|
+
features: {
|
|
256
|
+
textToVideo: true,
|
|
257
|
+
imageToVideo: true,
|
|
258
|
+
capabilities: ['t2v', 'i2v'],
|
|
259
|
+
aspectRatios: commonRatios,
|
|
260
|
+
seed: true,
|
|
261
|
+
multipleVideos: false,
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
id: 'minimax-video',
|
|
266
|
+
name: 'MiniMax Video (Fal)',
|
|
267
|
+
provider: 'fal',
|
|
268
|
+
strategy: 'video',
|
|
269
|
+
released: '2025-10',
|
|
270
|
+
cost: { type: 'unknown' },
|
|
271
|
+
features: {
|
|
272
|
+
textToVideo: true,
|
|
273
|
+
imageToVideo: false,
|
|
274
|
+
capabilities: ['t2v'],
|
|
275
|
+
aspectRatios: commonRatios,
|
|
276
|
+
seed: false,
|
|
277
|
+
multipleVideos: false,
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
id: 'hunyuan-video',
|
|
282
|
+
name: 'Hunyuan Video (Fal)',
|
|
283
|
+
provider: 'fal',
|
|
284
|
+
strategy: 'video',
|
|
285
|
+
released: '2025-09',
|
|
286
|
+
cost: { type: 'unknown' },
|
|
287
|
+
features: {
|
|
288
|
+
textToVideo: true,
|
|
289
|
+
imageToVideo: true,
|
|
290
|
+
capabilities: ['t2v', 'i2v'],
|
|
291
|
+
aspectRatios: commonRatios,
|
|
292
|
+
seed: true,
|
|
293
|
+
multipleVideos: false,
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
]
|
|
297
|
+
|
|
298
|
+
const videoCatalogIndex = new Map(VIDEO_CATALOG.map((m) => [m.id, m]))
|
|
299
|
+
|
|
300
|
+
export function findVideoModel(id: string): VideoModelEntry | undefined {
|
|
301
|
+
return videoCatalogIndex.get(id)
|
|
302
|
+
}
|