egaki 0.2.0 → 0.4.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 +70 -0
- package/README.md +32 -5
- package/dist/chatgpt-auth.d.ts +25 -0
- package/dist/chatgpt-auth.d.ts.map +1 -0
- package/dist/chatgpt-auth.js +18 -11
- package/dist/cli.js +246 -19
- package/dist/credentials.d.ts +17 -4
- package/dist/credentials.d.ts.map +1 -1
- package/dist/credentials.js +55 -10
- package/dist/login.d.ts +1 -1
- package/dist/login.d.ts.map +1 -1
- package/dist/login.js +44 -3
- package/dist/model-catalog.d.ts.map +1 -1
- package/dist/model-catalog.js +87 -3
- package/dist/models.d.ts +5 -0
- package/dist/models.d.ts.map +1 -1
- package/dist/models.js +58 -8
- package/dist/video-model-catalog.d.ts.map +1 -1
- package/dist/video-model-catalog.js +61 -0
- package/package.json +5 -2
- package/skills/egaki/SKILL.md +100 -0
- package/src/chatgpt-auth.ts +387 -0
- package/src/cli.ts +317 -22
- package/src/credentials.ts +75 -17
- package/src/login.ts +49 -4
- package/src/model-catalog.ts +92 -3
- package/src/models.ts +59 -8
- package/src/video-model-catalog.ts +61 -0
package/src/login.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Login command — interactive and non-interactive API key management.
|
|
2
2
|
// Interactive mode uses clack prompts for provider selection and key input.
|
|
3
3
|
// Non-interactive mode supports --provider + --key flags and stdin piping.
|
|
4
|
+
// ChatGPT OAuth uses a browser flow instead of pasting a key.
|
|
4
5
|
import {
|
|
5
6
|
intro,
|
|
6
7
|
outro,
|
|
@@ -17,8 +18,10 @@ import {
|
|
|
17
18
|
saveProviderKey,
|
|
18
19
|
removeProviderKey,
|
|
19
20
|
getKeyStatus,
|
|
20
|
-
|
|
21
|
+
getChatGptAuth,
|
|
22
|
+
saveChatGptAuth,
|
|
21
23
|
} from './credentials.js'
|
|
24
|
+
import { chatGptOAuthLogin, extractPlanType } from './chatgpt-auth.js'
|
|
22
25
|
|
|
23
26
|
export async function loginInteractive(): Promise<void> {
|
|
24
27
|
intro(pc.bold('egaki login'))
|
|
@@ -32,6 +35,11 @@ export async function loginInteractive(): Promise<void> {
|
|
|
32
35
|
if (status.source === 'stored') {
|
|
33
36
|
return pc.green('(saved)')
|
|
34
37
|
}
|
|
38
|
+
if (status.source === 'oauth') {
|
|
39
|
+
const auth = getChatGptAuth()
|
|
40
|
+
const email = auth?.email
|
|
41
|
+
return pc.green(`(signed in${email ? ` as ${email}` : ''})`)
|
|
42
|
+
}
|
|
35
43
|
return pc.dim('(not configured)')
|
|
36
44
|
})()
|
|
37
45
|
// Highlight the egaki option as recommended
|
|
@@ -61,6 +69,14 @@ export async function loginInteractive(): Promise<void> {
|
|
|
61
69
|
process.exit(1)
|
|
62
70
|
}
|
|
63
71
|
|
|
72
|
+
// ChatGPT uses browser OAuth instead of key paste
|
|
73
|
+
if (provider === 'chatgpt') {
|
|
74
|
+
const auth = await chatGptOAuthLogin()
|
|
75
|
+
saveChatGptAuth(auth)
|
|
76
|
+
outro('Done — ChatGPT OAuth saved')
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
|
|
64
80
|
log.info(
|
|
65
81
|
`${pc.dim('Env var:')} ${info.envVar} ${pc.dim('(also used by the CLI when set)')}`,
|
|
66
82
|
)
|
|
@@ -91,13 +107,13 @@ export async function loginInteractive(): Promise<void> {
|
|
|
91
107
|
outro('Done')
|
|
92
108
|
}
|
|
93
109
|
|
|
94
|
-
export function loginNonInteractive({
|
|
110
|
+
export async function loginNonInteractive({
|
|
95
111
|
provider,
|
|
96
112
|
key,
|
|
97
113
|
}: {
|
|
98
114
|
provider: string
|
|
99
115
|
key: string
|
|
100
|
-
}): void {
|
|
116
|
+
}): Promise<void> {
|
|
101
117
|
const info = PROVIDERS[provider]
|
|
102
118
|
if (!info) {
|
|
103
119
|
const available = Object.keys(PROVIDERS).join(', ')
|
|
@@ -107,6 +123,14 @@ export function loginNonInteractive({
|
|
|
107
123
|
process.exit(1)
|
|
108
124
|
}
|
|
109
125
|
|
|
126
|
+
// ChatGPT uses browser OAuth — key flag is not needed
|
|
127
|
+
if (provider === 'chatgpt') {
|
|
128
|
+
const auth = await chatGptOAuthLogin()
|
|
129
|
+
saveChatGptAuth(auth)
|
|
130
|
+
console.log(pc.green('ChatGPT OAuth saved'))
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
110
134
|
if (!key || key.trim().length === 0) {
|
|
111
135
|
console.error(pc.red('API key cannot be empty'))
|
|
112
136
|
process.exit(1)
|
|
@@ -121,6 +145,26 @@ export function showLoginStatus(): void {
|
|
|
121
145
|
|
|
122
146
|
for (const [key, info] of Object.entries(PROVIDERS)) {
|
|
123
147
|
const status = getKeyStatus(key)
|
|
148
|
+
|
|
149
|
+
if (key === 'chatgpt') {
|
|
150
|
+
const auth = getChatGptAuth()
|
|
151
|
+
if (auth) {
|
|
152
|
+
const plan = extractPlanType(auth)
|
|
153
|
+
const email = auth.email ?? auth.accountId ?? 'unknown'
|
|
154
|
+
const expired = auth.expires < Date.now()
|
|
155
|
+
const expiryLabel = expired
|
|
156
|
+
? pc.yellow('(token expired, will auto-refresh)')
|
|
157
|
+
: pc.dim(`(token valid)`)
|
|
158
|
+
console.log(`${pc.green('*')} ${info.label} ${pc.green('(signed in)')}`)
|
|
159
|
+
console.log(pc.dim(` account: ${email}${plan ? `, plan: ${plan}` : ''}`))
|
|
160
|
+
console.log(` ${expiryLabel}`)
|
|
161
|
+
} else {
|
|
162
|
+
console.log(`${pc.dim('-')} ${info.label} ${pc.dim('(not signed in)')}`)
|
|
163
|
+
console.log(pc.dim(` run: egaki login → select ChatGPT`))
|
|
164
|
+
}
|
|
165
|
+
continue
|
|
166
|
+
}
|
|
167
|
+
|
|
124
168
|
const icon = status.available ? pc.green('*') : pc.dim('-')
|
|
125
169
|
const source = (() => {
|
|
126
170
|
if (status.source === 'env') {
|
|
@@ -150,7 +194,8 @@ export function removeLogin(provider: string): void {
|
|
|
150
194
|
}
|
|
151
195
|
|
|
152
196
|
removeProviderKey(provider)
|
|
153
|
-
|
|
197
|
+
const label = provider === 'chatgpt' ? 'ChatGPT OAuth' : info.label
|
|
198
|
+
console.log(pc.green(`${label} key removed`))
|
|
154
199
|
}
|
|
155
200
|
|
|
156
201
|
// Read API key from stdin (for piping: echo "sk-xxx" | egaki login --provider google)
|
package/src/model-catalog.ts
CHANGED
|
@@ -109,6 +109,22 @@ const xaiImage = {
|
|
|
109
109
|
strategy: 'image' as const,
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
const vertexImagen = {
|
|
113
|
+
provider: 'vertex',
|
|
114
|
+
strategy: 'image' as const,
|
|
115
|
+
features: {
|
|
116
|
+
...googleImagen.features,
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const vertexText = {
|
|
121
|
+
provider: 'vertex',
|
|
122
|
+
strategy: 'text' as const,
|
|
123
|
+
features: {
|
|
124
|
+
...googleText.features,
|
|
125
|
+
},
|
|
126
|
+
}
|
|
127
|
+
|
|
112
128
|
const fluxAspectRatios = ['1:1', '3:4', '4:3', '9:16', '16:9', '9:21', '21:9']
|
|
113
129
|
|
|
114
130
|
// ─── catalog ─────────────────────────────────────────────────────────────────
|
|
@@ -196,6 +212,64 @@ export const CATALOG: ModelEntry[] = [
|
|
|
196
212
|
cost: { type: 'per-token', inputPerM: 0.5, outputPerM: 3.0 },
|
|
197
213
|
},
|
|
198
214
|
|
|
215
|
+
// ── Vertex: Imagen ──────────────────────────────────────────────────────
|
|
216
|
+
// Same models as Google AI Studio but routed through Vertex AI / Google Cloud billing.
|
|
217
|
+
// Use `egaki login --provider vertex --key <key>` to configure.
|
|
218
|
+
{
|
|
219
|
+
id: 'vertex/imagen-4.0-generate-001',
|
|
220
|
+
name: 'Imagen 4 (Vertex)',
|
|
221
|
+
released: '2025-08-15',
|
|
222
|
+
...vertexImagen,
|
|
223
|
+
cost: { type: 'per-image', perImage: 0.04 },
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
id: 'vertex/imagen-4.0-ultra-generate-001',
|
|
227
|
+
name: 'Imagen 4 Ultra (Vertex)',
|
|
228
|
+
released: '2025-08-15',
|
|
229
|
+
...vertexImagen,
|
|
230
|
+
cost: { type: 'per-image', perImage: 0.06 },
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
id: 'vertex/imagen-4.0-fast-generate-001',
|
|
234
|
+
name: 'Imagen 4 Fast (Vertex)',
|
|
235
|
+
released: '2025-08-15',
|
|
236
|
+
...vertexImagen,
|
|
237
|
+
cost: { type: 'per-image', perImage: 0.02 },
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
// ── Vertex: Gemini text+image ─────────────────────────────────────────
|
|
241
|
+
{
|
|
242
|
+
id: 'vertex/gemini-2.5-flash-image',
|
|
243
|
+
name: 'Gemini 2.5 Flash Image (Vertex)',
|
|
244
|
+
released: '2025-08-26',
|
|
245
|
+
...vertexText,
|
|
246
|
+
cost: { type: 'per-token', inputPerM: 0.3, outputPerM: 30 },
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
id: 'vertex/gemini-3-pro-image-preview',
|
|
250
|
+
name: 'Gemini 3 Pro Image (Vertex)',
|
|
251
|
+
released: '2025-11-20',
|
|
252
|
+
...vertexText,
|
|
253
|
+
cost: { type: 'per-token', inputPerM: 1.25, outputPerM: 10 },
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
id: 'vertex/gemini-3.1-flash-image-preview',
|
|
257
|
+
name: 'Gemini 3.1 Flash Image (Vertex)',
|
|
258
|
+
released: '2026-02-26',
|
|
259
|
+
...vertexText,
|
|
260
|
+
features: {
|
|
261
|
+
...vertexText.features,
|
|
262
|
+
aspectRatios: [
|
|
263
|
+
...vertexText.features.aspectRatios,
|
|
264
|
+
'4:1',
|
|
265
|
+
'1:4',
|
|
266
|
+
'8:1',
|
|
267
|
+
'1:8',
|
|
268
|
+
],
|
|
269
|
+
},
|
|
270
|
+
cost: { type: 'per-token', inputPerM: 0.5, outputPerM: 3.0 },
|
|
271
|
+
},
|
|
272
|
+
|
|
199
273
|
// ── BFL (AI Gateway) ───────────────────────────────────────────────────
|
|
200
274
|
{
|
|
201
275
|
id: 'flux-kontext-max',
|
|
@@ -432,9 +506,24 @@ export const CATALOG: ModelEntry[] = [
|
|
|
432
506
|
multipleImages: true,
|
|
433
507
|
},
|
|
434
508
|
},
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
509
|
+
{
|
|
510
|
+
id: 'chatgpt-image-latest',
|
|
511
|
+
name: 'ChatGPT Image',
|
|
512
|
+
description:
|
|
513
|
+
'Rolling-latest alias that tracks the newest ChatGPT image model. Currently points ' +
|
|
514
|
+
'to gpt-image-1.5. Pricing may shift when the alias retargets to a newer model.',
|
|
515
|
+
released: '2026-01',
|
|
516
|
+
...openaiImage,
|
|
517
|
+
cost: { type: 'per-image', perImage: 0.034 },
|
|
518
|
+
features: {
|
|
519
|
+
editing: true,
|
|
520
|
+
inpainting: true,
|
|
521
|
+
aspectRatios: [],
|
|
522
|
+
sizes: ['1024x1024', '1536x1024', '1024x1536'],
|
|
523
|
+
seed: false,
|
|
524
|
+
multipleImages: true,
|
|
525
|
+
},
|
|
526
|
+
},
|
|
438
527
|
|
|
439
528
|
// ── Replicate ──────────────────────────────────────────────────────────
|
|
440
529
|
{
|
package/src/models.ts
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// dist/index.d.ts and add entries here.
|
|
18
18
|
import type { ImageModel, LanguageModel } from 'ai'
|
|
19
19
|
import pc from 'picocolors'
|
|
20
|
-
import { PROVIDERS, EGAKI_GATEWAY_URL } from './credentials.js'
|
|
20
|
+
import { PROVIDERS, EGAKI_GATEWAY_URL, shouldUseChatGptBackend } from './credentials.js'
|
|
21
21
|
import { CATALOG, findModel } from './model-catalog.js'
|
|
22
22
|
import type { ModelEntry } from './model-catalog.js'
|
|
23
23
|
import { VIDEO_CATALOG, findVideoModel } from './video-model-catalog.js'
|
|
@@ -29,6 +29,16 @@ export type { ModelEntry, VideoModelEntry }
|
|
|
29
29
|
export const IMAGE_MODELS = CATALOG.map((m) => m.id) as [string, ...string[]]
|
|
30
30
|
export const VIDEO_MODELS = VIDEO_CATALOG.map((m) => m.id) as [string, ...string[]]
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Strip provider prefix from a model ID (e.g. "vertex/imagen-4.0-generate-001" → "imagen-4.0-generate-001").
|
|
34
|
+
* Provider SDKs expect bare model IDs, but the catalog uses prefixed IDs for routing.
|
|
35
|
+
*/
|
|
36
|
+
function stripProviderPrefix(modelId: string): string {
|
|
37
|
+
const slash = modelId.indexOf('/')
|
|
38
|
+
if (slash === -1) return modelId
|
|
39
|
+
return modelId.slice(slash + 1)
|
|
40
|
+
}
|
|
41
|
+
|
|
32
42
|
export const DEFAULT_MODEL = 'nano-banana-pro-preview'
|
|
33
43
|
export const DEFAULT_VIDEO_MODEL = 'veo-3.1-fast-generate-001'
|
|
34
44
|
|
|
@@ -66,6 +76,20 @@ export function ensureProviderKey(providerName: string): void {
|
|
|
66
76
|
// If direct provider key exists, we're good
|
|
67
77
|
if (hasDirectProviderKey(providerName)) return
|
|
68
78
|
|
|
79
|
+
// Vertex models require a direct key — the upstream Vercel AI Gateway
|
|
80
|
+
// does not support vertex/ routing, so egaki gateway can't proxy them.
|
|
81
|
+
if (providerName === 'vertex') {
|
|
82
|
+
console.error('')
|
|
83
|
+
console.error(pc.red(pc.bold('Vertex models require a direct Google Cloud API key')))
|
|
84
|
+
console.error('')
|
|
85
|
+
console.error(` ${pc.cyan('egaki login --provider vertex --key <key>')}`)
|
|
86
|
+
console.error('')
|
|
87
|
+
console.error(pc.dim('Get a key at https://console.cloud.google.com/apis/credentials'))
|
|
88
|
+
console.error(pc.dim('Egaki subscription does not cover Vertex routing yet.'))
|
|
89
|
+
console.error('')
|
|
90
|
+
process.exit(1)
|
|
91
|
+
}
|
|
92
|
+
|
|
69
93
|
// If egaki key exists, we'll route through the gateway
|
|
70
94
|
if (hasEgakiKey()) return
|
|
71
95
|
|
|
@@ -109,8 +133,11 @@ async function createGatewayImageModel(modelId: string, provider: string): Promi
|
|
|
109
133
|
apiKey: process.env['EGAKI_API_KEY']!,
|
|
110
134
|
baseURL: EGAKI_GATEWAY_URL,
|
|
111
135
|
})
|
|
112
|
-
// Gateway expects provider/model format for routing
|
|
113
|
-
|
|
136
|
+
// Gateway expects provider/model format for routing.
|
|
137
|
+
// For models that already have a prefix (e.g. vertex/imagen-4.0), strip it
|
|
138
|
+
// and use the catalog provider. For bare IDs, prepend the provider.
|
|
139
|
+
const bareId = stripProviderPrefix(modelId)
|
|
140
|
+
const gatewayModelId = `${provider}/${bareId}`
|
|
114
141
|
return gateway.image(gatewayModelId)
|
|
115
142
|
}
|
|
116
143
|
|
|
@@ -123,7 +150,8 @@ async function createGatewayTextModel(modelId: string, provider: string): Promis
|
|
|
123
150
|
apiKey: process.env['EGAKI_API_KEY']!,
|
|
124
151
|
baseURL: EGAKI_GATEWAY_URL,
|
|
125
152
|
})
|
|
126
|
-
const
|
|
153
|
+
const bareId = stripProviderPrefix(modelId)
|
|
154
|
+
const gatewayModelId = `${provider}/${bareId}`
|
|
127
155
|
return gateway(gatewayModelId)
|
|
128
156
|
}
|
|
129
157
|
|
|
@@ -136,10 +164,21 @@ async function createGatewayVideoModel(modelId: string, provider: string) {
|
|
|
136
164
|
apiKey: process.env['EGAKI_API_KEY']!,
|
|
137
165
|
baseURL: EGAKI_GATEWAY_URL,
|
|
138
166
|
})
|
|
139
|
-
const
|
|
167
|
+
const bareId = stripProviderPrefix(modelId)
|
|
168
|
+
const gatewayModelId = `${provider}/${bareId}`
|
|
140
169
|
return gateway.video(gatewayModelId)
|
|
141
170
|
}
|
|
142
171
|
|
|
172
|
+
/**
|
|
173
|
+
* ChatGPT OAuth image generation goes through the Codex backend instead of the
|
|
174
|
+
* OpenAI Image API, so OpenAI image models need the custom responses path.
|
|
175
|
+
*/
|
|
176
|
+
export function shouldUseResponsesApi(modelId: string): boolean {
|
|
177
|
+
const config = findModel(modelId)
|
|
178
|
+
if (!config) return false
|
|
179
|
+
return config.provider === 'openai' && config.strategy === 'image' && shouldUseChatGptBackend()
|
|
180
|
+
}
|
|
181
|
+
|
|
143
182
|
// Lazily import the provider and create the right model instance.
|
|
144
183
|
// This avoids loading all provider SDKs upfront — only the one needed
|
|
145
184
|
// for the selected model gets imported.
|
|
@@ -156,6 +195,10 @@ export async function createImageModel(modelId: string): Promise<ImageModel> {
|
|
|
156
195
|
const { google } = await import('@ai-sdk/google')
|
|
157
196
|
return google.image(modelId)
|
|
158
197
|
}
|
|
198
|
+
case 'vertex': {
|
|
199
|
+
const { vertex } = await import('@ai-sdk/google-vertex')
|
|
200
|
+
return vertex.image(stripProviderPrefix(modelId))
|
|
201
|
+
}
|
|
159
202
|
case 'openai': {
|
|
160
203
|
const { openai } = await import('@ai-sdk/openai')
|
|
161
204
|
return openai.image(modelId)
|
|
@@ -198,11 +241,15 @@ export async function createTextModel(
|
|
|
198
241
|
const { google } = await import('@ai-sdk/google')
|
|
199
242
|
return google(modelId)
|
|
200
243
|
}
|
|
244
|
+
case 'vertex': {
|
|
245
|
+
const { vertex } = await import('@ai-sdk/google-vertex')
|
|
246
|
+
return vertex(stripProviderPrefix(modelId))
|
|
247
|
+
}
|
|
201
248
|
default:
|
|
202
|
-
// Only Google
|
|
249
|
+
// Only Google/Vertex support generateText with responseModalities for images
|
|
203
250
|
console.error(
|
|
204
251
|
pc.red(
|
|
205
|
-
`Text+image generation is only supported for Google models, got provider: ${config.provider}`,
|
|
252
|
+
`Text+image generation is only supported for Google/Vertex models, got provider: ${config.provider}`,
|
|
206
253
|
),
|
|
207
254
|
)
|
|
208
255
|
process.exit(1)
|
|
@@ -234,6 +281,10 @@ export async function createVideoModel(modelId: string): Promise<any> {
|
|
|
234
281
|
const { google } = await import('@ai-sdk/google')
|
|
235
282
|
return google.video(modelId)
|
|
236
283
|
}
|
|
284
|
+
case 'vertex': {
|
|
285
|
+
const { vertex } = await import('@ai-sdk/google-vertex')
|
|
286
|
+
return vertex.video(stripProviderPrefix(modelId))
|
|
287
|
+
}
|
|
237
288
|
case 'fal': {
|
|
238
289
|
const { fal } = await import('@ai-sdk/fal')
|
|
239
290
|
return fal.video(modelId)
|
|
@@ -241,7 +292,7 @@ export async function createVideoModel(modelId: string): Promise<any> {
|
|
|
241
292
|
default:
|
|
242
293
|
console.error(
|
|
243
294
|
pc.red(
|
|
244
|
-
`Direct video generation is only supported for Google and Fal keys, got provider: ${config.provider}`,
|
|
295
|
+
`Direct video generation is only supported for Google, Vertex, and Fal keys, got provider: ${config.provider}`,
|
|
245
296
|
),
|
|
246
297
|
)
|
|
247
298
|
process.exit(1)
|
|
@@ -169,6 +169,67 @@ export const VIDEO_CATALOG: VideoModelEntry[] = [
|
|
|
169
169
|
multipleVideos: true,
|
|
170
170
|
},
|
|
171
171
|
},
|
|
172
|
+
// ── Vertex: Veo ──────────────────────────────────────────────────────
|
|
173
|
+
// Same Veo models routed through Vertex AI / Google Cloud billing.
|
|
174
|
+
// Use `egaki login --provider vertex --key <key>` to configure.
|
|
175
|
+
{
|
|
176
|
+
id: 'vertex/veo-3.1-generate-001',
|
|
177
|
+
name: 'Veo 3.1 (Vertex)',
|
|
178
|
+
provider: 'vertex',
|
|
179
|
+
strategy: 'video',
|
|
180
|
+
released: '2026-01',
|
|
181
|
+
cost: {
|
|
182
|
+
type: 'per-video-second',
|
|
183
|
+
defaultDurationSec: 8,
|
|
184
|
+
tiers: [
|
|
185
|
+
{ resolution: '720p', audio: false, costPerSecond: 0.2 },
|
|
186
|
+
{ resolution: '720p', audio: true, costPerSecond: 0.4 },
|
|
187
|
+
{ resolution: '1080p', audio: false, costPerSecond: 0.2 },
|
|
188
|
+
{ resolution: '1080p', audio: true, costPerSecond: 0.4 },
|
|
189
|
+
{ resolution: '4k', audio: false, costPerSecond: 0.4 },
|
|
190
|
+
{ resolution: '4k', audio: true, costPerSecond: 0.6 },
|
|
191
|
+
],
|
|
192
|
+
},
|
|
193
|
+
features: {
|
|
194
|
+
textToVideo: true,
|
|
195
|
+
imageToVideo: false,
|
|
196
|
+
capabilities: ['t2v'],
|
|
197
|
+
aspectRatios: ['16:9', '9:16'],
|
|
198
|
+
resolutions: ['720p', '1080p', '4k'],
|
|
199
|
+
durationRangeSec: { min: 4, max: 8 },
|
|
200
|
+
seed: true,
|
|
201
|
+
multipleVideos: true,
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
id: 'vertex/veo-3.1-fast-generate-001',
|
|
206
|
+
name: 'Veo 3.1 Fast (Vertex)',
|
|
207
|
+
provider: 'vertex',
|
|
208
|
+
strategy: 'video',
|
|
209
|
+
released: '2026-01',
|
|
210
|
+
cost: {
|
|
211
|
+
type: 'per-video-second',
|
|
212
|
+
defaultDurationSec: 8,
|
|
213
|
+
tiers: [
|
|
214
|
+
{ resolution: '720p', audio: false, costPerSecond: 0.1 },
|
|
215
|
+
{ resolution: '720p', audio: true, costPerSecond: 0.15 },
|
|
216
|
+
{ resolution: '1080p', audio: false, costPerSecond: 0.1 },
|
|
217
|
+
{ resolution: '1080p', audio: true, costPerSecond: 0.15 },
|
|
218
|
+
{ resolution: '4k', audio: false, costPerSecond: 0.3 },
|
|
219
|
+
{ resolution: '4k', audio: true, costPerSecond: 0.35 },
|
|
220
|
+
],
|
|
221
|
+
},
|
|
222
|
+
features: {
|
|
223
|
+
textToVideo: true,
|
|
224
|
+
imageToVideo: false,
|
|
225
|
+
capabilities: ['t2v'],
|
|
226
|
+
aspectRatios: ['16:9', '9:16'],
|
|
227
|
+
resolutions: ['720p', '1080p', '4k'],
|
|
228
|
+
durationRangeSec: { min: 4, max: 8 },
|
|
229
|
+
seed: true,
|
|
230
|
+
multipleVideos: true,
|
|
231
|
+
},
|
|
232
|
+
},
|
|
172
233
|
{
|
|
173
234
|
id: 'grok-imagine-video',
|
|
174
235
|
name: 'Grok Imagine Video',
|